Fix R15: control Shelly Gen2+ via RPC (Switch.Set/Light.Set), not command topic
Gen2+ devices ignore <prefix>/command/<component>; they take JSON-RPC on
<prefix>/rpc. The driver now sends Switch.Set / Light.Set (and Shelly.Reboot),
so commands actually change device state instead of being audited as a no-op.
Verified: a toggle publishes {"method":"Switch.Set","params":{"id":0,"on":true}}
to <prefix>/rpc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/phase-1-bootstrap
parent
1eec714d4a
commit
00fac6a910
|
|
@ -8,37 +8,35 @@ use App\Support\Mqtt\ShellyTopics;
|
|||
use PhpMqtt\Client\Facades\MQTT;
|
||||
|
||||
/**
|
||||
* Controls Shelly Gen2+ devices over MQTT via the simple command topic
|
||||
* `<prefix>/command/<component>` = on|off|toggle. Vendor specifics stay here (H3).
|
||||
* Controls Shelly Gen2+ devices via JSON-RPC over MQTT on `<prefix>/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
|
||||
* `<prefix>/status/<component>` 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->publishCommand($entity, 'on');
|
||||
$this->setComponentOn($entity, true);
|
||||
}
|
||||
|
||||
public function turnOff(Entity $entity): void
|
||||
{
|
||||
$this->publishCommand($entity, 'off');
|
||||
$this->setComponentOn($entity, false);
|
||||
}
|
||||
|
||||
public function setState(Entity $entity, array $state): void
|
||||
{
|
||||
if (array_key_exists('on', $state)) {
|
||||
$state['on'] ? $this->turnOn($entity) : $this->turnOff($entity);
|
||||
$this->setComponentOn($entity, (bool) $state['on']);
|
||||
}
|
||||
}
|
||||
|
||||
public function reboot(Device $device): void
|
||||
{
|
||||
$prefix = $this->prefixOf($device);
|
||||
|
||||
MQTT::connection()->publish(
|
||||
ShellyTopics::rpcTopic($prefix),
|
||||
json_encode(['id' => 1, 'src' => 'homeos', 'method' => 'Shelly.Reboot']),
|
||||
0,
|
||||
);
|
||||
$this->rpc($device, 'Shelly.Reboot');
|
||||
}
|
||||
|
||||
public function capabilities(): array
|
||||
|
|
@ -46,13 +44,24 @@ class ShellyMqttDriver implements DeviceDriver
|
|||
return ['switch', 'light'];
|
||||
}
|
||||
|
||||
private function publishCommand(Entity $entity, string $value): void
|
||||
/** Map an entity ("switch:0", "light:0") to the matching Gen2 Set RPC. */
|
||||
private function setComponentOn(Entity $entity, bool $on): void
|
||||
{
|
||||
MQTT::connection()->publish(
|
||||
ShellyTopics::commandTopic($this->prefixOf($entity->device), $entity->key),
|
||||
$value,
|
||||
0,
|
||||
);
|
||||
[$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
|
||||
|
|
|
|||
|
|
@ -29,13 +29,7 @@ class ShellyTopics
|
|||
return [$m['prefix'], $m['component']];
|
||||
}
|
||||
|
||||
/** Simple command topic for a component, e.g. "<prefix>/command/switch:0" => "on|off|toggle". */
|
||||
public static function commandTopic(string $prefix, string $component): string
|
||||
{
|
||||
return "{$prefix}/command/{$component}";
|
||||
}
|
||||
|
||||
/** JSON-RPC topic for device-level methods (e.g. Shelly.Reboot). */
|
||||
/** JSON-RPC topic for control (Switch.Set, Light.Set, Shelly.Reboot, …). */
|
||||
public static function rpcTopic(string $prefix): string
|
||||
{
|
||||
return "{$prefix}/rpc";
|
||||
|
|
|
|||
Loading…
Reference in New Issue