/rpc` * (e.g. Switch.Set / Light.Set / Shelly.Reboot). Gen2+ does not accept the simple * command topic. The device applies the change and publishes a fresh * `/status/` which our listener ingests. Vendor specifics stay here (H3). */ class ShellyMqttDriver implements DeviceDriver { private const RPC_SRC = 'homeos'; 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 reboot(Device $device): void { $this->rpc($device, 'Shelly.Reboot'); } /** Light.Set with optional brightness (0–100) and rgb ([r,g,b]) for RGBW lights. */ 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($entity->device, 'Light.Set', $rpc); } public function capabilities(): array { return ['switch', 'light']; } /** Map an entity ("switch:0", "light:0") to the matching Gen2 Set RPC. */ private function setComponentOn(Entity $entity, bool $on): void { [$component, $id] = array_pad(explode(':', $entity->key, 2), 2, '0'); // Switch.Set / Light.Set — component name capitalised. $this->rpc($entity->device, ucfirst($component).'.Set', ['id' => (int) $id, 'on' => $on]); } private function rpc(Device $device, string $method, array $params = []): void { $payload = ['id' => 1, 'src' => self::RPC_SRC, 'method' => $method]; if ($params !== []) { $payload['params'] = $params; } MQTT::connection()->publish(ShellyTopics::rpcTopic($this->prefixOf($device)), json_encode($payload), 0); } private function prefixOf(Device $device): string { $prefix = data_get($device->config, 'mqtt_prefix'); if (blank($prefix)) { throw new \RuntimeException("Device {$device->id} has no mqtt_prefix configured."); } return $prefix; } }