English | Deutsch
Controls Neumann KH DSP loudspeakers (KH 80 DSP, KH 120 II, KH 150, KH 750 DSP) via the Sennheiser Sound Control Protocol (SSC), TCP port 45. No additional pip package needs to be installed – a lean, self-contained asyncio client is included for the SSC protocol itself, and device discovery uses Home Assistant's built-in Zeroconf component.
Without Home Assistant: a standalone desktop app for the same speakers is in development at Neumann-Control.
Change history: see CHANGELOG.md. Protocol and device details (SSC, measured speaker behaviour): see the wiki.
Requires Home Assistant 2026.10 or newer.
The SSC address paths were first taken from the khtool project; the paths the integration uses and how they behave have since been checked on KH 120 II and KH 750 DSP speakers (KH 80 DSP and KH 150 untested).
This is a private hobby/test project, developed for my own setup and shared "as-is". It is not an official integration, and there is no warranty and no guaranteed support or maintenance. Use at your own risk – especially with writable settings that directly change the actual loudspeaker hardware (EQ, levels, delay, factory reset). Always double-check changes in Neumann's own MA 1 / Neumann.Control software or directly on the device.
Developed with AI assistance (Claude), with all testing, decisions and validation performed by me against my own installation (KH 120 II, KH 750 DSP).
Tested against real hardware: KH 120 II, KH 750 DSP.
Likely to work, but unverified (same DSP/SSC basis according to the manufacturer, no testing of my own): KH 80 DSP, KH 150, and their AES67 variants (KH 120 II AES67, KH 150 AES67). Value ranges (delay, logo brightness, etc.) are taken from the KH 120 II and may differ slightly on these models. The KH 80 DSP only keeps changes across a power cycle when told to save them, so the integration sends a save command to it after every change (not tested on hardware).
Cannot be supported (no DSP/network capability, purely analog): KH 310, KH 420 and other classic analog KH monitors. These cannot be addressed via SSC – the automatic search will not list them and a manual setup cannot connect, which is not a bug in this integration.
Completely untested, possibly compatible: The newer DSP subwoofers KH 805 II, KH 810 II, KH 870 II (introduced 2024/2025, described by the manufacturer as "building on the KH 750 DSP") are not yet included in the model detection – if you own one of these devices and would like to test, feel free to get in touch.
This integration is not in the HACS default list, so add the repository once by hand:
- HACS → ⋮ (top right) → Custom repositories
- Repository
https://github.com/Varitras/neumann-connect, type Integration, then Add - Find Neumann Connect in HACS and download it
- Restart Home Assistant
Pre-releases (1.18.1b5 and the like) are only offered once beta versions are
enabled for the repository in HACS.
- Copy the folder
custom_components/neumann_khinto your Home Assistant configuration directory (e.g./config/custom_components/neumann_kh). - Restart Home Assistant.
- Settings → Devices & Services → Add Integration → "Neumann KH (SSC)".
- You will get a menu with two options:
- "Search network automatically" – active mDNS scan (see below), results shown as a selection list. The recommended default route.
- "Enter manually (IP address)" – name, IP address, interface dropdown, port (fallback in case the automatic search does not find a device).
- Create a separate entry for each loudspeaker (e.g. "KH 120 II Left", "KH 120 II Right", "KH 750 DSP Sub 1", "KH 750 DSP Sub 2").
If a loudspeaker ends up on a different address, interface or port, use Settings → Devices & Services → [device] → ⋮ → Reconfigure. The entry is updated in place, so entity IDs, history and any automation referencing them are kept – there is no need to delete and re-add it.
The new address is checked before anything is saved. If a speaker with a different serial number answers there, the change is refused: otherwise one speaker's history would silently end up attached to another physical unit.
According to the SSC specification, Neumann KH loudspeakers announce
themselves on the network via mDNS/Bonjour (service type _ssc._tcp.local.) –
just like AirPlay devices, for example. The integration uses Home Assistant's
already-running Zeroconf instance to actively search for a few seconds and
shows the devices it finds (model, IP address, serial number) for selection.
Home Assistant also passes the speakers' own announcements on. A speaker that is not set up yet appears under Settings → Devices & Services as a discovered device and only needs a name. For one that is already set up, a changed address is taken over once the speaker has confirmed its serial number. This only works for speakers that announce a global or ULA IPv6 address as well: Home Assistant ignores announcements that carry link-local addresses only. Such a speaker is found by the scan above.
Advantage with IPv6 link-local: The discovered address already includes
the scope ID automatically (e.g. fe80::...%3) – for automatically
discovered devices you do not need to specify the network interface
manually.
The search deliberately prefers the link-local address even when a speaker also announces a global one. A global IPv6 address carries the prefix the ISP delegates, and many connections are assigned a new one on the daily forced reconnect – every stored global address then points nowhere. The link-local address is derived from the MAC and stays valid for as long as the speaker sits on the same network segment.
If nothing is found: simply submit the form again without a selection to
repeat the search – or go back to the menu and choose "Enter manually (IP address)". mDNS
only works reliably if HA is in the same network segment as the loudspeakers
in terms of multicast (with Docker, for example, network_mode: host is
required) – the same prerequisite that applies to the link-local connection
anyway.
By default the loudspeakers are only reachable via their IPv6 link-local
address (fe80::...). This address strictly requires a scope ID (network
interface), otherwise no operating system can resolve the route.
Selecting the interface: In the config flow, the interface field is a dropdown listing all network interfaces that Home Assistant knows about on the host (including their currently assigned IPv4/IPv6 addresses to help you identify them). If the interface you want does not appear there (e.g. with certain Docker network setups), you can also type a custom value into the same field.
Finding the address (e.g. on the HA host or on a machine in the same network segment):
# Using khtool itself (can be used independently of this integration):
python3 ./khtool.py --scan # finds the speakers, saves khtool.json and exits
python3 ./khtool.py -i eth0 -q # queries them; the output names each IPv6 addressDetermining interface names on the HA host (only if you do want or need to type it in manually – normally the dropdown selection in the config flow is sufficient):
ip -6 addr show scope link(Interface names such as eth0, end0, enp1s0 – depending on the system.)
In the config flow you then enter, for example:
- IP address:
fe80::2a36:38ff:fe12:3456 - Interface: select from the dropdown (or type
eth0manually)
If Home Assistant runs in Docker: the container needs
network_mode: hostor direct layer-2 access to the loudspeakers' network segment, otherwise the link-local address is not reachable.
Writable vs. read-only (by entity type):
| Type | Writable? |
|---|---|
number |
Yes – numeric value via slider/input field |
select |
Yes – fixed set of options |
switch |
Yes – on/off |
text |
Yes – free text |
button |
Triggers a one-off action (no value, no reading/writing) |
sensor |
No, read-only |
binary_sensor |
No, read-only |
Values that ought to be writable according to the khtool metadata but are
confirmed by actual testing not to be writable (see "Known limitations")
are therefore deliberately implemented as sensor/binary_sensor instead of
number/select/switch – not because HA technically requires it, but
because a write attempt would fail there anyway.
Enabled by default: most entities are. The exceptions are marked "default: disabled" in the tables below – deliberately not repeated here, because a second list is one that drifts out of step with the first.
| Entity | Type | Range | SSC path |
|---|---|---|---|
| Output level | number |
0–120 dB | audio/out/level |
| Delay | number |
0–5760 samples at 48 kHz (KH 750 DSP: 0–1000) | audio/out/delay |
| Logo brightness* | number |
0–125 % | ui/logo/brightness |
| Auto standby time | number |
1–240 min | device/standby/auto_standby_time |
| Standby threshold level | number |
−80 to −55 dBu | device/standby/level |
| Mute | switch |
– | audio/out/mute |
| Identify | switch |
– | device/identification/visual |
| Phase invert (non-subwoofer only) | switch |
– | audio/out/phaseinversion |
Auto standby (non-subwoofer only; binary_sensor on the KH 750 DSP instead) |
switch |
– | device/standby/enabled |
| Input interface (writable, confirmed on KH 120 II and KH 750 DSP) | select |
ANALOG ONLY/DIGITAL ONLY/DIGITAL DISCARDS ANALOG | audio/in/interface |
| Device name (default: disabled) | text |
max. 52 characters | device/name |
| Input level (live) | sensor |
dB | m/in/level |
| Standby countdown (default: disabled, diagnostic) | sensor |
min | device/standby/countdown |
| Hardware version, current input (diagnostic) | sensor |
text | device/identity/hw_version, audio/in/current_input |
| Control mode (diagnostic; the CONTROL switch on the back, see "Known limitations") | sensor |
NETWORK/LOCAL | ui/control_mode |
| Input gain, input select, bass gain, mid gain, treble gain, output level (SPL) (non-subwoofer only; confirmed by testing not to be writable, diagnostic) | sensor |
dB or text | ui/input_gain, ui/input_select, ui/bass_gain, ui/mid_gain, ui/treble_gain, ui/output_level |
| Input clipping | binary_sensor |
– | m/in/clip |
| Warning (diagnostic) | binary_sensor |
– | warnings |
| Restore factory defaults (default: disabled, two-step confirmation) | button |
– | device/restore |
| Create backup (all known values except live measurements) | button |
– | – |
| Restore backup (writes the stored backup back, disabled by default, two-step confirmation) | button |
– | – |
| Run device discovery (diagnostic) | button |
– | – |
* Only on the KH 80 DSP / KH 150 / KH 120 II – the KH 750 DSP does not offer this setting (checked on firmware 2_1_2). The integration detects the model automatically during setup and hides this entity for the KH 750 DSP.
The KH 750 DSP has two additional bass-management outputs (out1/out2) for
connected satellite loudspeakers. All entities listed below are created only
if KH 750 was detected as the model during setup (the device only identifies
itself as KH 750 over SSC, without "DSP" – the integration accepts both
spellings).
| Entity | Type | Range | SSC path |
|---|---|---|---|
| Output 1/2 level (default: disabled) | number |
0–120 dB | audio/out1/level, audio/out2/level |
| Output 1/2 delay (default: disabled) | number |
0–1000 samples | audio/out1/delay, audio/out2/delay |
| Output 1/2 mute (default: disabled) | switch |
– | audio/out1/mute, audio/out2/mute |
| Device temperature (default: enabled, diagnostic; the device reports Kelvin) | sensor |
°C | device/temperature |
| Output level (live) (default: disabled) | sensor |
dB | m/out/level |
| Output label (main output, diagnostic) | sensor |
text | audio/out/label |
| Output 1/2 label, output 1/2 loudspeaker (default: disabled, diagnostic, read-only) | sensor |
text (loudspeaker: "Not assigned" instead of "UNKNOWN") | audio/out1/label, audio/out1/loudspeaker, audio/out2/label, audio/out2/loudspeaker |
| Subwoofer input gain, low cut, output level, phase, phase inversion, bass management, channel B input mode (confirmed by testing not to be writable, diagnostic) | sensor |
dB or text | ui/subwoofer_input_gain, ui/subwoofer_low_cut, ui/subwoofer_output_level, ui/subwoofer_phase, ui/subwoofer_phase_inversion, ui/bass_management, ui/channel_b_input_mode |
| Output clipping (default: disabled) | binary_sensor |
– | m/out/clip |
| Digital bypass (diagnostic) | binary_sensor |
– | audio/digital_bypass |
| Auto standby (read-only – confirmed by hardware testing not to be writable on the KH 750 DSP, see "Known limitations") | binary_sensor |
– | device/standby/enabled |
Entities that are disabled by default can be enabled manually under Settings → Devices & Services → [device] → Entities.
A loudspeaker is polled every 30 seconds. Not everything is fetched on every
cycle: rarely changing values (identity, most rear-panel settings, device name)
are only queried on every tenth cycle, so every 5 minutes. The control mode and
auto standby switches are read on every cycle. Values that only entities
disabled by default show (level, delay and mute of outputs 1 and 2, output
meter and clip, standby countdown) are only polled while such an entity is enabled. Each value is fetched
individually (one leaf path per SSC message), not as a combined
message and not as a container query: a container query such as
{"device":null} is answered with an error, and a combined message only
answers the leaves the device has, next to an error for the rest. One leaf per
message keeps every answer unambiguous. If the device rejects a single value,
only that one is skipped – the remaining values are still updated.
Standby behaviour (important, not a bug): when a loudspeaker goes into
standby, it also shuts down its network stack and stops responding to SSC
requests (observed on both the KH 120 II and the KH 750 DSP). All entities then correctly become
"unavailable" – this is the behaviour recommended by Home Assistant
(CoordinatorEntity automatically marks entities as unavailable as soon as a
poll cycle fails) and is not a bug in the integration. As soon as the device
wakes from standby, Home Assistant picks it up again on its own. How quickly
it returns depends on which of two mechanisms is running.
If the speaker was already unavailable when Home Assistant started, the config entry never finished setting up and Home Assistant retries it with a gap that doubles each time (5 s, 10 s, 20 s, ...) until it reaches 10 minutes. It never gives up. Home Assistant notes each retry in the log itself.
If the entry was up and running when the speaker went to sleep, the poll cycle simply keeps going at its normal 30 second interval and picks the speaker up on the first cycle that succeeds. The log gets one info line when the speaker drops out and one when it is back – no errors.
The retry timing is Home Assistant's own. In addition, after every failed setup the integration searches the network for the speaker by its serial number and stores the new address if it has moved.
Too many connections: a speaker accepts only about four network connections at a time (measured on the KH 120 II and the KH 750 DSP). Home Assistant keeps one open per speaker. If Neumann.Control, MA 1 or other tools hold the rest, a new connection times out and the entities show as unavailable until one is closed.
-
Auto standby is only non-writable on the KH 750 DSP (it works on the KH 120 II), where it follows the AUTO STANDBY switch on the back. Hence: KH 120 II →
switch, KH 750 DSP →binary_sensor. -
ui/input_selectstays read-only. The writable path isaudio/in/interface, confirmed on both the KH 120 II and the KH 750 DSP and enabled by default there. -
Every value request carries a transaction id (
osc/xid), which both test speakers echo in the same line, error replies included; a line with another id is a late answer to an earlier request and is dropped. Only EQ writes of 20 bands go without one, because the id would push them past the speakers' request size limit, and the schema and limits walk of the device discovery; their replies are matched by path, where a trailing line of the previous answer could in a narrow case pass for the next one. Both test devices answer with exactly one line. -
Control mode (
ui/control_mode) is set with the CONTROL switch on the back (KH 750 DSP: CONTROL MODE) and cannot be changed over the network, so it is a sensor. OnLOCALthe speaker keeps answering, so its values stay visible, but it does not accept changes over the network (a write is answered with "authorisation needed") and uses its local settings instead of the network configuration (measured: a different output level and auto standby time). Set the switch toNETWORKto control it from Home Assistant. The refused write is measured on both models, the different local settings on the KH 750 DSP. A backup taken while the switch is onLOCALis refused, so it cannot replace the last good one. -
Restore backup has the same two-step safety confirmation as the factory reset below: the first press only arms it, a second press between 2 s and 30 s later writes the backup. Mute is written before the levels and unmute after them. The notification counts values the speaker refused or did not confirm separately, and each of them is logged (a refusal with its error code).
-
Factory reset (
device/restore) has a two-step safety confirmation: the first press only arms it, a second press between 2 s and 30 s later triggers it. Alternatively via a physical switch sequence on the device itself:- KH 80 DSP: while booting (logo still red), move the SETTINGS switch up and down repeatedly until a few seconds after the logo turns white.
- KH 750 DSP: while booting (power light solid red), move the AUTO STANDBY switch up and down repeatedly. The power light flashes red while the defaults are applied, then turns green.
- KH 120 II / KH 150: while booting (logo red), move the CONTROL switch up and down repeatedly until the logo turns solid white for a few seconds. It then flashes red quickly before turning white again.
(Source: Neumann KH Monitor Troubleshooting)
-
The following KH 750 values are confirmed by testing not to be writable and are therefore read-only: bass management, channel B input mode, subwoofer input gain/low cut/output level/phase/phase inversion.
-
The following KH 120 II values are likewise confirmed by testing not to be writable: input gain, input select, bass, mid, treble, output level (SPL).
-
audio/out/dimmanddevice/save_settingsare answered with a 404 by both test models, so neither path exists on them. The dimm entity and the "save settings" button were removed rather than left showing "unknown" and failing on every press. The save command is only sent to the KH 80 DSP; KH 120 II and KH 750 DSP were measured not to need it. -
"Identify" is a switch, not an auto-stop button: the flashing only stops by itself after several minutes.
-
Device temperature (KH 750 DSP): reported in Kelvin, converted to °C.
-
On KH 750 firmware 1.2.x the output level and mute use a different path, which the integration detects at setup. This variant has not been tested on hardware.
Three separate, persistent stores (independent of config entries, so they also
survive deleting and re-adding a device; all in storage.py), one entry per
serial number:
.storage/neumann_kh_names: the most recently used name. When setting up again via automatic discovery, the name field is pre-filled from this..storage/neumann_kh_backups: the result of theCreate backupbutton – all known values except live measurements, including the rarely polled settings and the full EQ of every container (gain, boost, frequency, Q and filter type per band). It is also written as a JSON file to<config>/neumann_kh/– reachable through the file editor or a share, but never over HTTP, unlike/config/www/..storage/neumann_kh_discovery: the result of theRun device discoverybutton (diagnostic) – combines our known paths with a best-effort attempt viaosc/schema+osc/limits(optional SSC methods, answered by the tested KH 120 II and KH 750 – on a firmware without them this part is simply left empty). It is also written as a JSON file to<config>/neumann_kh/; the notification shows its path. The serial number is redacted in this export (only the last 3 characters remain visible).
Backup and discovery only ever run manually via their respective buttons – there is no automatic triggering in the background.
The selection list in the automatic scan also contains a "🔄 Search again" entry to restart the network search directly from the list.
A complete 1:1 mapping of all EQ parameters (type/frequency/gain/boost/Q/ enabled per band) would come to 360 entities on the KH 750 DSP (6 containers × 10 bands × 6 parameters) – no longer manageable. Deliberately reduced to container level instead:
- One on/off switch per EQ container (
switch, category "Configuration", enabled by default): switches all bands of that container together (writes the same value into the entireenabledarray). Shows "on" as soon as at least one band is active. - One "{container}: reset to flat" button per EQ container (
button, category "Configuration", enabled by default): sets gain and boost of all bands in that container to 0 dB. Frequency/Q/type/enabled remain unchanged – a true factory reset is not possible, as there are no documented default frequencies per band.
All container names deliberately start with "EQ" (e.g. "EQ2 Main output", "EQ3 Output 1") so that they appear grouped together alphabetically in the configuration section.
Containers covered:
| Model | Container | Bands |
|---|---|---|
| KH 120 II (non-subwoofer) | audio/out/eq2 |
10 |
| KH 120 II (non-subwoofer) | audio/out/eq3 |
20 |
| KH 750 DSP (main output) | audio/out/eq2 |
10 |
| KH 750 DSP (main output) | audio/out/eq3 |
10 |
| KH 750 DSP (out1/out2 each) | eq2 |
10 |
| KH 750 DSP (out1/out2 each) | eq3 |
10 |
That adds up to 4 entities (KH 120 II: 2 containers × switch+button) or 12 entities (KH 750 DSP: 6 containers × switch+button).