Skip to content

Integration Behaviour

Varitras edited this page Nov 1, 2026 · 5 revisions

Integration Behaviour

How the Neumann Connect integration talks to the speakers, finds and follows them, and what it does around standby, resets, the EQ and backups. The speakers' own behaviour and the protocol are described in the Neumann KH SSC Reference (Neumann Control wiki).

Source marks [measured], [code], [khtool]: see the legend on Home.

SSC client

Item Behaviour Source
Connection One TCP connection per speaker, kept open and reused [code]
Scope handling Appends %<interface> automatically when the address is link-local, has no scope yet and an interface is configured; setup requires an interface for a link-local address. Detects link-local by the range fe80::/10, not by a fe80 string prefix [code]
Framing Terminates requests with CR+LF; splits replies on LF, so CR+LF and LF are both accepted. Lines that are not valid JSON objects are ignored. Refuses to send NaN / Infinity [code]
One leaf per message A design choice, not a device limit: every reply belongs to exactly one path. Polling cost grows with the number of paths [code]
Matching replies Every value request carries an osc/xid, which the speaker echoes in the same line, error replies included; a line with another xid is a late answer and dropped, and the line with the own xid is the answer. Requests without an xid - osc methods (discovery schema/limits walk) and EQ sets the xid would push past the 29-token limit (20 bands) - are matched by their path; only after such an answer are complete lines still waiting discarded before the next request (stale drain, up to 10 ms). Requests on one connection are serialized behind a lock, handed over in FIFO order; a cancelled or timed-out request drops the connection [code]
Known limit A late extra line for path X that arrives after the discard but before the real answer cannot be told apart from a fresh answer to X. The tested devices answer every leaf query with exactly one line [measured] [code], [measured]
Reading an answer Every request names the path it expects (expect_path); the client returns as soon as that path or an osc/error has arrived. While an answer is still incomplete, each new line restarts a 0.4 s settle window and lines are merged [code]
Timeouts and caps Connect/response timeout 3.0 s; per answer 256 lines, 10 s, 4 MiB; a single line at most 1 MiB. Reaching a cap drops the connection [code]
Errors Since 1.19.1 the first [int, {...}] pair anywhere in the osc/error tree is taken (a flat pair as well); its position is ignored. Earlier versions expected a flat pair and read no code from real devices. Code 404 means "this model does not have the path" [code]
Not used Address patterns, osc/ping, osc/version, subscriptions: the integration polls single leaves instead [code]
Duplicate "osc" keys The integration uses a standard JSON parser; it is not affected: error replies carry the xid next to error in one osc object (measured on both models), and no subscriptions are used [code], [measured]

Polling

Item Behaviour Source
Poll interval Every 30 s, cycle limit 25 s [code]
Rates fast = every cycle; fast, while enabled = every cycle while an entity reading the path is enabled, for the paths only entities disabled by default read (outputs 1 and 2, output meter and clip, standby countdown; the first refresh reads them once); slow = every 10th cycle (5 min), cached in between; setup = read once when the entry is set up; none = never polled. Per path: Entities-and-Polling [code]
Cost With the entities' default setting, about 45 % (KH 120 II) / 68 % (KH 750) fewer requests than polling everything every cycle, counted from the poll lists [code]
A path the speaker does not have Skipped during polling, logged at debug level [code]

Identity

Item Behaviour Source
product Decides which entities and poll paths exist; if a device reports no product, the model is shown as KH DSP [code]
vendor Device manufacturer. A device whose vendor does not contain "neumann" is not rejected: setup warns with the reported product and vendor and continues after confirmation. A device without vendor counts as Neumann [code]
serial Config entry unique ID, device identifier, identity check [code]
version Device firmware (sw_version); re-read at every setup. A firmware update while Home Assistant keeps running shows up after the next reload or restart [code]
hw_version Diagnostic sensor "Hardware version" [code]
Unusable answers An identity answer that is not plain text or a number (list, object, boolean, empty) counts as "no value" [code]
Subwoofer names Subwoofer features apply to KH 750 and KH 750 DSP [code]
Masking Serials in logs and exports show only the last 3 characters; addresses are masked (see Troubleshooting) [code]

Discovery

Path Trigger What it does Source
Scan "Search network automatically" in the setup dialog Active 4 s mDNS search, then asks each candidate for its identity (at most 32 records resolved, 20 s budget) [code]
Passive discovery Home Assistant receives an announcement Updates a known entry or offers a new speaker [code]
Relocate After a failed setup of an entry with a stored serial Searches for the entry's speaker by serial (see below) [code]
  • The service type _ssc._tcp.local. is declared in manifest.json; of the TXT keys only id (the serial) is used [code].
  • Home Assistant's passive discovery re-reads a changed address only when the PTR record is refreshed at 75 % of its TTL (4500 s), i.e. after roughly 56 minutes (TTL [measured]; refresh timing derived from Home Assistant / python-zeroconf behaviour, not observed on hardware).

Address rules [code]:

  • An IPv4 address is never picked; an announcement without any IPv6 address is rejected (no_ipv6), and the scan skips such a record.
  • A link-local address is preferred; a global address is used only if no link-local address is announced (a global address dies with the next ISP prefix change [measured]).
  • A discovered link-local address carries its scope ID (%<scope>), so no separate interface is stored.
  • Manual entry: a link-local address requires the network interface (or fe80::1%eth0 notation in the address field).
  • Home Assistant and the speakers must share the same layer-2 segment.

Following a moved speaker

The serial is the anchor. An address is only ever changed for the entry whose serial the device at the new address reports. [code]

Situation Behaviour Source
Announcement for a known serial, address unchanged Nothing happens, no network contact [code]
Announcement for a known serial, new address or port The device at the new address is asked for its serial; on a match the entry's host/port are updated and the entry reloads [code]
Setup fails (speaker off, unreachable, wrong device) Relocate: 4 s mDNS search over every candidate other than the current address. A candidate whose announcement carries its serial is taken by that serial without a connection; only candidates without one are asked (8 in parallel). The first exact match becomes the new address, and Home Assistant's next scheduled retry connects and checks the serial against the device [code]
Relocate finds only the current address Nothing is written [code]
Entry has no stored serial No relocate and no identity check; the address must be fixed via Reconfigure [code]
Address changed manually via Reconfigure Device must report the stored serial, otherwise aborted with "wrong device", nothing changed [code]

After an ISP prefix change, link-local preference, passive discovery and relocate together brought all affected entries back without user action. [measured]

Wrong device at a stored address

Every setup of an entry with a stored serial reads the serial before the first poll. A different serial, an unusable answer or no answer at all fails the setup as "not ready"; nothing of the other device is published, and relocate searches for the real speaker. The check is fail-closed. The same exact-serial comparison is used for relocate, reconfigure and passive discovery. [code]

Case Behaviour Source
Announcement without TXT id Ignored (no_serial) [code]
Unknown device that reports no serial itself Not offered via passive discovery (no_serial); manual setup and scan still accept it, with host_port as unique ID [code]
Unknown device whose reported serial differs from TXT id The serial reported by the device wins [code]
Known serial announced, but the device answers a different serial Ignored (wrong_device), entry unchanged [code]

Limitation: the serial check catches stale or wrong records, not a device on the same segment that deliberately claims another speaker's serial. [code]

Device discovery button

"Run device discovery" asks one configured speaker for everything it can tell about itself. It is not the network search above. [code]

Part What it reads Limits Source
Known paths Every path the integration knows for the model, one leaf per message; a path the device rejects is skipped 120 s for the whole part; on timeout or lost connection nothing is recorded and the previous discovery is kept [code]
Schema walk The device's own tree via osc/schema, one level per request, then osc/limits for every leaf Best effort: 30 s, at most 500 nodes and 10 levels; if the device refuses, this part stays empty [code]
  • Since 1.19.1 each schema and limits request returns as soon as its reply arrives: 3.4 s on a KH 120 II, 7.4 s on a KH 750 [measured]. Before, each reply waited the full 0.4 s and the walk stopped at its 30 s limit after 50 leaves [measured].
  • The result is written as a JSON file to <config>/neumann_kh/ (not served over HTTP) and kept in a Home Assistant store per serial; the export carries the masked serial only. A run that read nothing is refused and the previous discovery is kept. [code]
  • The writeable flags it records are not reliable (see the reference's Limits and Untested). [measured]

Standby, reboot and reset

Aspect Behaviour Source
Entities in standby Become unavailable; this is expected, not an error [code]
Log One info line when the speaker drops out and one when it is back - no error entries [code]
Recovery A running entry keeps polling every 30 s and recovers on the first successful poll after wake-up [code], [measured]
Reboot while Home Assistant runs A few poll cycles fail, then polling picks up again; the config entry is not reloaded [code]
Factory reset Button Restore factory defaults writes FACTORY_DEFAULTS to device/restore (two presses, see Troubleshooting). A missing reply is treated as "reset may have run" and all cached values are discarded [code]
Control mode Read-only diagnostic sensor Control mode (NETWORK / LOCAL), enabled by default; a backup restore does not write it. The select of earlier versions is removed from the entity registry at setup [code]

Model handling

Item Behaviour Source
Untested models KH 80 / KH 80 DSP, KH 120 (logo table), KH 150 and the AES67 variants are listed in the model tables; untested models get the ranges of the tested model of the same kind (delay max 5760 samples for monitors, 1000 for the subwoofer) [code]
KH 805 II / KH 810 II / KH 870 II Not in the model tables, so no model-specific entities [code]
KH 750 audio/out5 At setup, level and mute switch to audio/out5 only if audio/out/level answers 404 and audio/out5/level answers a finite number; anything else keeps audio/out. Delay always stays in audio/out. Entities and backups always use the audio/out name, so a backup can be restored across both layouts [code]
KH 80 save device/save_settings is sent only to KH 80 / KH 80 DSP: after each accepted write except Identify, once per EQ reset and once per backup restore (not after a factory reset). A 404 means "nothing to save"; any other error is reported [code], [khtool] (issue tracker)
Identify A switch, not a button, because the device keeps flashing for several minutes after a set [measured] [code]

EQ

Each eq2/eq3 container gets two entities, both in the entity category "Configuration" and enabled by default [code]. The crossover eq1 is not exposed [code].

Entity Name Behaviour Source
Switch {container} enabled Writes the same value to every entry of the enabled array. Shows "on" when at least one band is enabled [code]
Button {container}: reset to flat Sets gain and then boost of every band to 0.0; frequency, q, type and enabled stay unchanged. This makes the bands flat, because gain is a flat level and boost the filter height [measured] [code], [measured]
  • Container names all start with "EQ" (e.g. "EQ2 Main output", "EQ3 Output 1"), so they stay together in the configuration list. On the KH 750, "Subwoofer main output" (audio/out) is the subwoofer's own woofer path, "Output 1" / "Output 2" are the output sockets A and B [measured], [code].
  • Entity count: 4 on the KH 120 II (2 containers) and 12 on the KH 750 (6 containers). Mapping every band parameter to its own entity would create 360 entities on the KH 750 [code].
  • The enabled arrays are read on the slow cycle; gain, boost, frequency, q and type are not polled [code].
  • During a reset, other device actions such as backup or restore are blocked, so a backup cannot capture a half-written EQ curve [code].

Backup and restore

  • A backup stores all known values except live measurements (meters, standby countdown), including all six leaves of every eq2/eq3 container; the crossover eq1 is not part of it. A restore writes all six EQ leaves back [code].
  • During a backup, paths the device rejects (for example with 404) are skipped; a lost connection or the time limit aborts it [code].
  • A backup taken while the CONTROL (MODE) switch is on LOCAL is refused, and the previous backup is kept: on LOCAL the speaker reports its local settings, not the network ones (measured on the KH 750). A stored backup that holds LOCAL is refused at restore [code].
  • A restore writes mute before the levels and unmute after them, so a backup "muted, high level" never plays unmuted at that level on the way [code].
  • The restore notification counts values the speaker refused and values it did not confirm separately, and logs each of them (a refusal with its error code). If the speaker confirmed none of the values sent, the restore reports them as unconfirmed rather than unchanged, and still saves them on a KH 80 [code].
  • A full backup reads 33 paths on a KH 120 II and 69 on a KH 750 [code]. With the larger path set of earlier versions (37 and 89 paths) it took about 1.0 s and 2.5 s [measured].
  • Buttons, confirmation and refusals: Troubleshooting.

See also: Entities-and-Polling · Troubleshooting · Neumann KH SSC Reference

Clone this wiki locally