homeos/app/Services/ShellyLocalOnboarder.php

67 lines
2.4 KiB
PHP

<?php
namespace App\Services;
use App\Models\Device;
use App\Support\Shelly\ShellyRpc;
/**
* Onboards a Shelly over its LOCAL API — the Home-Assistant way. Given just an IP it reads the
* device identity + full status, creates (or updates) the device with protocol `http`, and
* populates its entities. No MQTT setup on the device.
*/
class ShellyLocalOnboarder
{
public function __construct(
private readonly ShellyRpc $rpc,
private readonly ShellyStatusApplier $applier,
) {}
/**
* @throws \Throwable when the device can't be reached / doesn't speak Shelly RPC
*/
public function onboard(string $ip, ?string $name = null, ?int $roomId = null): Device
{
$info = $this->rpc->info($ip); // {id, model, gen, name, …} — throws if unreachable
$status = $this->rpc->status($ip);
$shellyId = $info['id'] ?? null;
// Reuse an existing row so switching to local control never duplicates. Match on the STABLE
// Shelly id first; only reuse an IP match when that row is the SAME (or an unidentified)
// device — otherwise a DHCP-reassigned IP would let this Shelly overwrite an unrelated one.
$device = $shellyId ? Device::where('config->mqtt_prefix', $shellyId)->first() : null;
if ($device === null) {
$byIp = Device::where('config->ip', $ip)->first();
$byIpId = $byIp ? data_get($byIp->config, 'mqtt_prefix') : null;
if ($byIp !== null && (blank($byIpId) || $byIpId === $shellyId)) {
$device = $byIp;
}
}
$device ??= new Device;
$device->fill([
'name' => $name ?: ($device->name ?: ($info['name'] ?? $shellyId ?? $ip)),
'vendor' => 'Shelly',
'model' => $info['model'] ?? $device->model,
'protocol' => 'http',
'status' => 'active',
'last_seen_at' => now(),
]);
if ($roomId !== null) {
$device->room_id = $roomId;
}
$device->config = array_merge($device->config ?? [], array_filter([
'ip' => $ip,
'transport' => 'local',
'mqtt_prefix' => $shellyId, // keep so discovery dedup + optional MQTT still work
], fn ($v) => $v !== null));
$device->save();
$this->applier->applyStatus($device, $status, (int) round(microtime(true) * 1_000_000));
return $device;
}
}