Skip to content

About

Controls Neumann KH DSP loudspeakers (KH 120 II, KH 750 DSP) via the Sennheiser Sound Control Protocol (SSC)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Neumann Connect – Home Assistant Custom Component

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).

Disclaimer

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).

Supported models

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.

Installation

HACS (custom repository)

This integration is not in the HACS default list, so add the repository once by hand:

  1. HACS → ⋮ (top right) → Custom repositories
  2. Repository https://github.com/Varitras/neumann-connect, type Integration, then Add
  3. Find Neumann Connect in HACS and download it
  4. Restart Home Assistant

Pre-releases (1.18.1b5 and the like) are only offered once beta versions are enabled for the repository in HACS.

Manually

  1. Copy the folder custom_components/neumann_kh into your Home Assistant configuration directory (e.g. /config/custom_components/neumann_kh).
  2. Restart Home Assistant.

Setup

  1. Settings → Devices & Services → Add Integration → "Neumann KH (SSC)".
  2. 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).
  3. 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").

Changing connection details later

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.

Automatic discovery (mDNS/Zeroconf)

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.

Determining the IPv6 link-local address & interface (only for "Enter manually (IP address)")

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 address

Determining 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 eth0 manually)

If Home Assistant runs in Docker: the container needs network_mode: host or direct layer-2 access to the loudspeakers' network segment, otherwise the link-local address is not reachable.

Entities created per loudspeaker

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.

Additional entities only on a detected subwoofer (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.

Polling

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.

Known limitations

  • 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_select stays read-only. The writable path is audio/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. On LOCAL the 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 to NETWORK to 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 on LOCAL is 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/dimm and device/save_settings are 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.

Name memory, backup & device discovery

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 the Create backup button – 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 the Run device discovery button (diagnostic) – combines our known paths with a best-effort attempt via osc/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.

EQ (parametric equalizer)

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 entire enabled array). 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).

About

Controls Neumann KH DSP loudspeakers (KH 120 II, KH 750 DSP) via the Sennheiser Sound Control Protocol (SSC)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages