AudioControl Multi-Zone Amplifier
The audiocontrol driver controls AudioControl networked multi-zone matrix amplifiers (Director M-series, D2800, D4600). Telnet ASCII commands on port 23 drive the amp and poll zone status via SYSTEMstat?.
Prerequisites
- Amplifier on the same LAN as GEM, with a static IP or DHCP reservation.
The /json/signalprocessing.json endpoint is only used once at connect for model detection — its body is too large for the device HTTP server to serve reliably on multi-zone amps (truncates around 24 KB), so all live zone state comes over telnet.
Setup
- System → Devices: add a device with driver
audiocontrol. Setip. Defaultportis 23. - Save and enable. On connect the driver:
- probes
/json/signalprocessing.jsonforampModel/ampNameand writes them tomodel/amp_name. If the JSON endpoint is unreachable it falls back toSYSTEMstat?over telnet, parsing theAMPLIFIER NAME:line. - starts polling the amp every
sync_intervalms (default 5000) viaSYSTEMstat?. Each powered zone row in the response auto-creates a GEM zone the first time it is seen, under theavsubsystem, with a numeric address (1..N). Digital Out rows are polled but never auto-created — see below.
- probes
A Digital Out usually isn't a room of its own — it feeds a whole-house source to a zone that has its own local receiver doing the amplification. Creating a zone for it would just add an output nobody controls to the zone list, so only the powered outputs are created.
If a site genuinely does control a Digital Out directly, add the zone by hand (System → Zones, device = the amp, address a or b) and the driver will sync its state like any other. Digital Out zones created by earlier versions are left alone and keep updating — nothing is removed on upgrade.
Zone names are unique system-wide, but amp labels are not — two outputs can both be called "Main Patio", and the formatted name may already belong to a zone on another device. Auto-create handles the collision instead of failing:
- If the name is free, the zone is created as
<device>_<label>. - If the name belongs to an unrelated zone, the address is appended (
<device>_<label>_3). The zone's label still shows the amp's human name. - If the row that owns the name is this amp's own zone at this address (for example a zone under a disabled subsystem, so it isn't loaded), the zone is left alone — it is never recreated or driven. Re-enable the subsystem and reload the device to pick it back up.
An address whose create fails is retried on a 10-minute backoff rather than on every poll, so a permanent collision doesn't flood the log. This is the shared behavior for every driver that discovers its own zones — see auto-created zones.
- You don't have to wait for the first poll — run the
sync_zonescommand to force an immediateSYSTEMstat?sweep and create any missing zones now. It reports how many zones itcreatedversus how many alreadyexisting. - Run
sync_av_zonesto create matching AV Zone rows for the main (numeric) zone outputs (so each shows up in the AV routing graph). Existing AV Zone rows are left alone. Digital Out zones are skipped — they're driven by theDXOcommands rather thanZ[address]templates, and their always-on power state would otherwise trip AV power divergence detection. If a Digital Out already has an AV Zone row from an earlier sync, delete it (or setpower_reconcile=falseon the zone) to stop divergence alerts.
Attributes
Device — required
| Attribute | Type | Description |
|---|---|---|
ip | string | LAN IP of the amplifier. |
Device — optional
| Attribute | Type | Default | Description |
|---|---|---|---|
port | int | 23 | Telnet port. |
zone_count | int | 8 | Number of output zones. |
sync_interval | int (ms) | 5000 | Zone status poll cadence. |
Reflected device attributes
| Attribute | Source |
|---|---|
model | ampModel from one-shot JSON probe, or AMPLIFIER NAME: from SYSTEMstat?. |
amp_name | ampName from the one-shot JSON probe (user-renamable). |
global_temp, global_voltage, global_protection, thermal_protection | Parsed from SYSTEMstat? lines. |
active_zones | JSON list of the names of zones currently powered on, recomputed after each SYSTEMstat? poll settles (read-only at-a-glance summary of what the amp is driving). |
last_error | Last poll error message. |
Zone
- Address required: yes.
- Address format: numeric
1..Nfor wired zones,a/bfor Digital Out zones.
The driver writes the following onto each polled zone:
| Attribute | Type | Source |
|---|---|---|
power_state | string | on / off |
mute_state | string | on / off. The regular status poll does not report mute, and there is no dedicated mute-query command, so this only populates if you issue a raw mute query through passthrough (e.g. Z1mute?). |
volume | int | Current zone volume (0–100 scale). |
input | string | Raw source label the zone is currently on, exactly as the amp reports it — e.g. MX13 & 13 (matrix channel pair), DXa (Digital In A), Local. This is the source label, not a numeric id. |
signal_sense | string | on / off |
Which zones receive state
Two bindings deliver poll state to a zone, and either one is enough:
- Direct — the zone's own device is the amp and the zone address is the amp output (
1..N,a/b). This is what auto-create sets up. The full row syncs:power_state,input,volume,signal_sense. - Via AV routing — the zone's AV Zone lists the amp as its volume device and carries the amp output in the AV Zone address (or in the
ar=component of a compound address). This is the usual shape for a room whose zone belongs to its display device (a TV, a receiver) while the amp only handles audio.volumeandmute_statealways sync from the amp — the volume device owns them.power_state,inputandsignal_sensesync only when the zone has no device of its own; when another device owns the zone, that device owns those attributes.
You do not need to re-point a zone's device/address at the amp just to get volume updating — the AV Zone volume-device link is sufficient.
Commands
Power & status
| Command | Args | Notes |
|---|---|---|
power_on / power_off | — | Whole-amp power. |
query_status | — | Issues SYSTEMstat?; populates global telemetry attributes. |
query_voltage / query_temp / query_protect / query_short | — | Single-value queries. |
query_groups / query_amp_name | — | |
query_zone_on / query_zone_off / query_input / query_group_input | — | |
sync_zones | — | Force a zone status poll now and auto-create any missing GEM zones. |
sync_av_zones | — | Walk the main (numeric) zone outputs and ensure a matching AV Zone row exists. Digital Out zones are skipped. |
all_zones_on / all_zones_off | — | |
mute_all / unmute_all | — | Mute or unmute every zone at once. |
Zone control
| Command | Args | Notes |
|---|---|---|
zone_on / zone_off | address | Per-zone power. |
volume | address, volume | Set absolute zone volume. The amp reports volume on a 0–100 scale. |
volume_up / volume_down | address | Step the zone volume up/down by one increment. |
mute_on / mute_off | address | |
set_bass / set_treble | address, level | |
bass_up / bass_down / treble_up / treble_down | address | |
loudness_on / loudness_off | address | |
zone_signalsense_on / zone_signalsense_off | address | Per-zone signal sense. |
all_signalsense_on / all_signalsense_off | — | Global. |
get_volume | address | |
recall_eq | address, preset (1-6) | |
query_zone_stat / query_zone_eq / query_zone_bass / query_zone_treble / query_zone_loudness / query_zone_input / query_zone_signalsense / query_zone_power | address | |
get_zone_input | address | Alias of query_zone_input. |
Source routing
| Command | Args | Notes |
|---|---|---|
set_source | address, source_id | M-series numbered local source (legacy). For D2800 / D4600 fixed sources use set_input with local / bus_a / bus_b instead. |
set_input | address, input | Route any source to a zone. A bare 1-8 selects matrix channel pair MX1-MX8 (pairs 1-2 … 15-16). D2800 / D4600 fixed sources may be given as LX / BXa / BXb or as local / bus_a / bus_b, and full codes (MX3, DXa, V0CS2) pass through verbatim. Digital In runs DXa-DXd on the four-input models. |
set_dante_input | address, input (raw suffix) | Route a Dante source by raw source suffix — e.g. V0CS3 (Dante input 3 via Dante Controller), V1CS2 (AudioControl Dante amp source), MX5 (matrix), DXa (digital). Appended after Z[n]source. |
set_source_dante | address, dante_id, input_index | M6800D / Director: route a Dante source to a zone. dante_id 1-8 = AudioControl Dante amps (0 for non-AudioControl sources via Dante Controller); input_index 1-8 stereo pair from amp web UI. Digital inputs cannot be Dante-routed. |
set_output | address (a/b), input | Route any source to Digital Output A or B. A bare 1-8 selects matrix channel pair MX1-MX8; full codes (MX3, DXa-DXd, V0CS2) pass through verbatim. Replaces set_digital_output, set_dxo_source_matrix and set_dxo_source_dante — macros naming any of those still route correctly. |
query_dxo_input | address (a/b) | Query the source currently routed to Digital Output A or B. The arg was output before it was aligned with set_output; macros still passing output keep working. |
Lipsync (D4600 zones 5-8)
| Command | Args | Notes |
|---|---|---|
lipsync_up / lipsync_down | address | Step delay by 5 ms. |
set_lipsync | address, steps (0-19) | 5 ms per step. |
Groups
| Command | Args | Notes |
|---|---|---|
group_zone | address, group_id | Add zone to a group. |
ungroup_zone | address, group_id | Remove a zone from a group. |
group_volume | group_id, volume | Set every zone in a group to a volume. |
group_mute / group_unmute / group_on / group_off | group_id |
Escape hatch
| Command | Args | Notes |
|---|---|---|
passthrough | input | Send a raw telnet command to the amp. The string is transmitted verbatim. Useful for one-off diagnostics or commands not yet wrapped by the driver. |
Known quirks
- The amp does not push state changes — front-panel volume / source changes only appear after the next poll.
- Only the powered outputs auto-create zones, with numeric ids. Digital Outs are skipped — they typically feed a whole-house source to a zone with its own local receiver rather than driving a room. Add one manually with address
a/bif a site controls it directly. - AV zones aren't auto-created on connect — run
sync_av_zonesonce to populate them (main zone outputs only; Digital Out zones are excluded). - D-series input codes. The D2800 / D4600 have no numbered MX inputs. Each zone has three fixed sources: Local (
LX) — the per-zone RCA pair above that zone's speaker outputs — plus the two shared analog buses, Bus A (BXa) and Bus B (BXb), each of which can feed any number of zones at once. The firmware also accepts the M-series codes as aliases:MX2= Local,DXa= Bus A,DXb= Bus B.set_inputexpands whichever spelling you give it, so a generic AV source macro that passes the av source's address intoset_inputworks on both families — putBXa(orbus_a) in the av source's address field and reference it from the macro's command step as[av_source_address]. M-series numbered local sources still use the legacyset_source. - Garbage commands echo back as
xx<cmd>xxand are logged as warnings — useful for catching typos in custom macros.
Troubleshooting
| Symptom | Check |
|---|---|
| Zone state diverges from front panel | Lower sync_interval or run sync_zones manually; the amp does not push events. |
| Zone physically on but GEM shows it off (e.g. an off command lost to a network blip) | Self-heals: the driver's power_state poll feeds GEM's physical power reconciliation, which restores the zone's source (macros suppressed) so the off controls return. Opt out per zone with power_reconcile = false. |
| Model never populated | One-shot JSON probe failed; check http://<ip>/json/signalprocessing.json in a browser, or watch logs for the telnet fallback. |
sync_zones reports errors | Confirm the av subsystem exists in GEM — auto-created zones live under it. Also confirm telnet on port 23 is reachable. |
A zone was created with the address appended to its name (..._3) | Another zone already held the plain name. Expected — rename the zone's label freely; the name only has to stay unique. |
Log says zone row exists but is not loaded, skipping create | A zone row for that address exists but isn't in the live collections — usually its subsystem is disabled, or the row was disabled. Re-enable it and reload the device. |