/. * 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/(?[^/]+)/(?.+)$#', $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, }; } }