58 lines
2.0 KiB
PHP
58 lines
2.0 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 for this device (already added locally, or auto-onboarded via MQTT
|
|
// under the same id) so switching to local control never creates a duplicate.
|
|
$device = Device::where('config->ip', $ip)->first()
|
|
?? ($shellyId ? Device::where('config->mqtt_prefix', $shellyId)->first() : null)
|
|
?? 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;
|
|
}
|
|
}
|