Поле External ID при создании/изменении устройства теперь input+datalist: можно вписать значение вручную (как раньше) или выбрать из подсказок — идентификаторы устройств, от которых device-control-service уже получал MQTT-телеметрию/ack (Redis-множество devices:known), но которые ещё не зарегистрированы в Postgres. Уже зарегистрированные id из списка подсказок исключаются. DeviceShadow::knownExternalIds() — новый read-метод (SMEMBERS devices:known), зеркалит то же множество, что Go-сервисы уже читают/ пишут для health-check/online-детекции.
72 lines
2.5 KiB
PHP
72 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
|
/**
|
|
* Read-only access to device-control-service's Device Shadow in Redis.
|
|
* Laravel never writes these keys — device-control-service owns them; this
|
|
* is purely for the dashboard/device pages to show live state.
|
|
*/
|
|
class DeviceShadow
|
|
{
|
|
/**
|
|
* Full shadow snapshot for one device, one round-trip via pipeline.
|
|
*
|
|
* @return array{status: string, last_seen: ?CarbonImmutable, desired_state: array<string, mixed>, reported_state: array<string, mixed>}
|
|
*/
|
|
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<int, string> $externalIds
|
|
* @return array<string, string> 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<int, string>
|
|
*/
|
|
public function knownExternalIds(): array
|
|
{
|
|
return Redis::smembers('devices:known');
|
|
}
|
|
}
|