81 lines
2.3 KiB
PHP
81 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace App\Support\Drivers;
|
|
|
|
use App\Models\Device;
|
|
use App\Models\Entity;
|
|
use App\Support\Shelly\ShellyRpc;
|
|
|
|
/**
|
|
* Controls Shelly Gen2+ devices over their LOCAL HTTP API (`POST http://<ip>/rpc`) — the same
|
|
* approach Home Assistant uses, no MQTT setup on the device. The device applies the change and
|
|
* we re-poll its status to reflect it. Vendor specifics stay here (H3).
|
|
*/
|
|
class ShellyHttpDriver implements DeviceDriver
|
|
{
|
|
public function __construct(private readonly ShellyRpc $rpc) {}
|
|
|
|
public function turnOn(Entity $entity): void
|
|
{
|
|
$this->setComponentOn($entity, true);
|
|
}
|
|
|
|
public function turnOff(Entity $entity): void
|
|
{
|
|
$this->setComponentOn($entity, false);
|
|
}
|
|
|
|
public function setState(Entity $entity, array $state): void
|
|
{
|
|
if (array_key_exists('on', $state)) {
|
|
$this->setComponentOn($entity, (bool) $state['on']);
|
|
}
|
|
}
|
|
|
|
public function setLight(Entity $entity, array $params): void
|
|
{
|
|
[, $id] = array_pad(explode(':', $entity->key, 2), 2, '0');
|
|
|
|
$rpc = ['id' => (int) $id];
|
|
if (array_key_exists('on', $params)) {
|
|
$rpc['on'] = (bool) $params['on'];
|
|
}
|
|
if (isset($params['brightness'])) {
|
|
$rpc['brightness'] = max(0, min(100, (int) $params['brightness']));
|
|
}
|
|
if (isset($params['rgb']) && is_array($params['rgb'])) {
|
|
$rpc['rgb'] = array_map(fn ($v) => max(0, min(255, (int) $v)), array_slice($params['rgb'], 0, 3));
|
|
}
|
|
|
|
$this->rpc->call($this->ip($entity->device), 'Light.Set', $rpc);
|
|
}
|
|
|
|
public function reboot(Device $device): void
|
|
{
|
|
$this->rpc->call($this->ip($device), 'Shelly.Reboot');
|
|
}
|
|
|
|
public function capabilities(): array
|
|
{
|
|
return ['switch', 'light'];
|
|
}
|
|
|
|
private function setComponentOn(Entity $entity, bool $on): void
|
|
{
|
|
[$component, $id] = array_pad(explode(':', $entity->key, 2), 2, '0');
|
|
|
|
$this->rpc->call($this->ip($entity->device), ucfirst($component).'.Set', ['id' => (int) $id, 'on' => $on]);
|
|
}
|
|
|
|
private function ip(Device $device): string
|
|
{
|
|
$ip = data_get($device->config, 'ip');
|
|
|
|
if (blank($ip)) {
|
|
throw new \RuntimeException("Device {$device->id} has no local IP configured.");
|
|
}
|
|
|
|
return $ip;
|
|
}
|
|
}
|