/* * SPDX-FileCopyrightText: 2026 Benjamin Harris * SPDX-License-Identifier: Apache-2.0 * * Dante device discovery over mDNS / DNS-SD. * * CLEAN-ROOM: implemented from the behavioural notes in * docs/dante-protocol-notes.md (service types, ports, TXT keys), NOT from * any GPL/AGPL reference source. See CLAUDE.md. * * This is the read-only half of Dante discovery: it browses the network for * Dante devices and their transmit channels / multicast bundles and reports * what it finds. It does not advertise this device (that needs the ARC/CMC * servers behind it) and does not touch audio. * * Dante publishes these DNS-SD service types, all under _udp.local: * _netaudio-arc device control endpoint (ARC port, e.g. 4440) * _netaudio-cmc device announce endpoint (CMC port, e.g. 8800) * _netaudio-chan one instance per TX channel (flow-control port) * _netaudio-bund a multicast bundle other devices can join */ #pragma once #include #include #include "esp_err.h" #ifdef __cplusplus extern "C" { #endif /* Which Dante DNS-SD service a result came from. */ typedef enum { DANTE_SVC_ARC = 0, /* _netaudio-arc — device control endpoint */ DANTE_SVC_CMC, /* _netaudio-cmc — device announce endpoint */ DANTE_SVC_CHAN, /* _netaudio-chan — a transmit channel */ DANTE_SVC_BUND, /* _netaudio-bund — a multicast bundle */ DANTE_SVC_COUNT } dante_service_type_t; /* One discovered service instance, flattened from an mDNS PTR+SRV+TXT+A set. * String fields are only valid for the duration of the callback. */ typedef struct { dante_service_type_t service; /* which _netaudio-* type */ const char *instance; /* DNS-SD instance name, e.g. "chanName@host" */ const char *hostname; /* SRV target host */ uint32_t ipv4; /* resolved IPv4 (host byte order), 0 if none */ uint16_t port; /* SRV port */ const char *const *txt_keys; /* TXT record keys (txt_count entries) */ const char *const *txt_vals; /* TXT record values (txt_count entries) */ size_t txt_count; } dante_discovery_result_t; /* Callback invoked once per discovered instance. `user` is passed through * from the scan/start call. Must not block for long. */ typedef void (*dante_discovery_cb_t)(const dante_discovery_result_t *result, void *user); /* * One-shot synchronous scan of all Dante service types. * * Issues an mDNS PTR query per service type, waits up to `timeout_ms` for * each, and invokes `cb` for every instance found. If `cb` is NULL, results * are logged at INFO level instead. Safe to call whether or not the AES67 * stack has already initialised mDNS (init is shared and idempotent). * * Returns ESP_OK if the scan ran (even if nothing was found). */ esp_err_t dante_discovery_scan(uint32_t timeout_ms, dante_discovery_cb_t cb, void *user); /* * Start a background task that repeats dante_discovery_scan() every * `interval_ms`. `cb`/`user` are as above. Only one background scanner may * run at a time. Pin/priority follow project convention (core 0, low prio). */ esp_err_t dante_discovery_start(uint32_t interval_ms, dante_discovery_cb_t cb, void *user); /* Stop the background scanner started by dante_discovery_start(). */ void dante_discovery_stop(void); /* Human-readable name for a service type, e.g. "_netaudio-arc". */ const char *dante_service_type_str(dante_service_type_t svc); #ifdef __cplusplus } #endif