67 lines
2.4 KiB
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;
|
|
}
|
|
}
|