homeos/app/Jobs/IngestDiscoveryMessage.php

67 lines
2.1 KiB
PHP

<?php
namespace App\Jobs;
use App\Events\DeviceDiscovered;
use App\Models\DiscoveryFinding;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
/**
* Ingests a discovery finding published by the sidecar to homeos/discovery/<source>/<id>.
* Upserts discovery_findings (preserving an assigned/ignored status) and announces new ones.
*/
class IngestDiscoveryMessage implements ShouldQueue
{
use Queueable;
public function __construct(
public string $topic,
public string $payload,
) {}
public function handle(): void
{
if (! preg_match('#^homeos/discovery/(?<source>[^/]+)/(?<id>.+)$#', $this->topic, $m)) {
return;
}
$data = json_decode($this->payload, true);
if (! is_array($data)) {
return;
}
$existing = DiscoveryFinding::where('source', $m['source'])->where('identifier', $m['id'])->first();
$finding = DiscoveryFinding::updateOrCreate(
['source' => $m['source'], 'identifier' => $m['id']],
[
'name' => $data['name'] ?? null,
'vendor' => $data['vendor'] ?? $this->guessVendor($data),
'model' => $data['model'] ?? data_get($data, 'properties.model'),
'ip' => $data['ip'] ?? null,
'raw' => $data,
'last_seen_at' => now(),
'status' => $existing?->status ?? 'new', // keep assigned/ignored once set
],
);
// Announce only on FIRST discovery — the sidecar re-publishes retained/periodic
// messages, so broadcasting on every message would spam the "Neue Geräte" panel.
if ($existing === null) {
DeviceDiscovered::dispatch($finding->uuid);
}
}
private function guessVendor(array $data): ?string
{
$service = ($data['service'] ?? '').' '.($data['name'] ?? '');
return match (true) {
str_contains(strtolower($service), 'shelly') => 'Shelly',
str_contains(strtolower($service), 'googlecast') => 'Google',
default => null,
};
}
}