, reported_state: array} */ public function snapshot(string $externalId): array { $results = Redis::pipeline(function ($pipe) use ($externalId) { $pipe->get("device:{$externalId}:status"); $pipe->get("device:{$externalId}:last_seen"); $pipe->get("device:{$externalId}:desired_state"); $pipe->get("device:{$externalId}:reported_state"); }); [$status, $lastSeen, $desired, $reported] = $results; return [ 'status' => $status ?: 'unknown', 'last_seen' => $lastSeen ? CarbonImmutable::createFromTimestamp((int) $lastSeen) : null, 'desired_state' => $desired ? json_decode($desired, true) : [], 'reported_state' => $reported ? json_decode($reported, true) : [], ]; } /** * Status for many devices in a single round-trip (MGET), so the device * list doesn't do one Redis call per row. * * @param array $externalIds * @return array external_id => status ("online"/"offline"/"unknown") */ public function statuses(array $externalIds): array { if (empty($externalIds)) { return []; } $keys = array_map(fn (string $id) => "device:{$id}:status", $externalIds); $values = Redis::mget($keys); return array_combine($externalIds, array_map(fn ($v) => $v ?: 'unknown', $values)); } /** * external_id of every device device-control-service has ever heard * from over MQTT (`devices:known`, a Redis Set it maintains) — includes * devices that sent telemetry/ack before anyone registered them in * Postgres. Used to suggest an external_id when adding a device instead * of requiring it to be typed exactly from memory. * * @return array */ public function knownExternalIds(): array { return Redis::smembers('devices:known'); } }