/rpc`). * This is the same transport Home Assistant uses — no MQTT setup on the device, just its IP. * Vendor specifics stay here (H3). */ class ShellyRpc { /** * @param array $params * @return array */ public function call(string $ip, string $method, array $params = []): array { $body = ['id' => 1, 'src' => 'homeos', 'method' => $method]; if ($params !== []) { $body['params'] = $params; } $response = Http::timeout(5)->connectTimeout(3)->acceptJson()->post($this->url($ip), $body); $json = $response->json(); if (! is_array($json)) { throw new \RuntimeException("Shelly {$ip}: invalid RPC response."); } if (isset($json['error'])) { throw new \RuntimeException("Shelly {$ip} RPC error: ".json_encode($json['error'])); } return $json['result'] ?? []; } /** Full component status keyed by component ("switch:0", "input:0", …). */ public function status(string $ip): array { return $this->call($ip, 'Shelly.GetStatus'); } /** Device identity: {id, model, gen, name, ver, …}. */ public function info(string $ip): array { return $this->call($ip, 'Shelly.GetDeviceInfo'); } /** True if the device answers RPC (used to validate a manual IP before onboarding). */ public function reachable(string $ip): bool { try { $this->info($ip); return true; } catch (\Throwable) { return false; } } private function url(string $ip): string { return 'http://'.$ip.'/rpc'; } }