Skip to content

Multi-zone support: protocol carries all 8 zones, anantha hardcodes zone 1 #20

@AevumDecessus

Description

@AevumDecessus

Summary

The TODO line "Multi-zone controls (currently only does single zone control, because that's what I have)" can be approached with concrete protocol-level information now. The thermostat firmware publishes the full schedule, activity, and runtime state for all 8 zones (1 through 8) on every DBIRTH/<thingName>/zones message and via NDATA/DDATA/zones updates. Zone 1 is the only one currently rendered by anantha's dashboard and HA bridge; zones 2-8 are present in the wire data and silently dropped.

This isn't a bug - the project's TODO already calls this out. This issue is a writeup of what the protocol exposes, so anyone (the maintainer or a contributor) picking up multi-zone work has a concrete starting point.

What the wire protocol carries

The DBIRTH/<id>/zones payload from a Sparkplug rebirth response contains 1050 ConfigSetting entries, structured as 8 zones x ~131 metrics each. The per-zone shape is uniform across all 8 zones:

Group Count per zone Pattern
Schedule 105 <N>/program/<Day>/period <P>/{time,activity,enabled} for 7 days x 5 periods x 3 fields
Activity setpoints 15 <N>/activities/<activity>/{htsp,clsp,fan} for 5 activities x 3 fields
Hold/override 3 <N>/hold/{hold,holdActivity,otmr}
Live state ~5 <N>/{rt,rh,htsp,clsp,fan} (current readings)
Zone config ~9 <N>/{enabled,name,occEnabled,occupancy,occupancyOverride,setback,tempoffset,cfmlimit,airflowlimit,damperposition,zoneconditioning,currentActivity}

Where <N> is the zone number 1-8. Activities are away, home, manual, sleep, wake.

Plus zone-related metrics in other namespaces:

  • profile/zones (40 entries): zoning hardware metadata
  • profile/zoneboards (2 entries): zoning controller boards
  • sensor/zones/<N>/{favorite, tempControlSetup}: per-zone sensor config
  • sensor/remote/zone <N>/rt and sensor/smart/zone <N>/rt (8 each): per-zone remote/smart sensor readings

On a single-zone install (the test thermostat used for this analysis), zones 2-8 publish with enabled = false but their full schema is still present. So:

  • Configured zones can be detected via <N>/enabled = true.
  • Multi-zone installs will populate zones 2-8 with real data; the protocol shape doesn't change between configurations.

Where anantha currently hardcodes zone 1

The single-zone assumption is baked into four files:

  • Dashboard rendering
    • cmd/anantha/cmd/templates.go
      • RenderSchedule iterates hardcoded keys 1/program/<Day>/period <P>/{time,activity,enabled}. Other zones never make it into the rendered HTML.
      • RenderProfiles iterates hardcoded keys 1/activities/<activity>/{htsp,clsp,fan}. Same pattern.
  • Dashboard SSE
    • cmd/anantha/cmd/serve.go
      • The /events handler subscribes to a hardcoded topic list including 1/clsp, 1/currentActivity, 1/fan, 1/htsp, 1/name, 1/rh, 1/rt, 1/zoneconditioning. No equivalent for zones 2-8.
  • Prometheus metrics
    • cmd/anantha/cmd/metrics.go
      • zone_temp and zone_humidity gauges hardcode zone 1: zoneTempGauge.WithLabelValues("1").Set(...) driven by OnChange1("1/rt", ...).
  • HA MQTT bridge
    • cmd/anantha/cmd/hamqtt.go
      • Subscribes to <prefix>/zone/1/{fanmode,preset_mode,temp_low,temp_high}/set for incoming HA commands.
      • Publishes state on <prefix>/zone/1/{humidity,temperature,fanmode,temp_low,temp_high,preset_mode}/current.
      • The discovery JSON registers a single climate entity for zone 1.
      • Writes to the thermostat use zones/1/... topic-prefixed metric names via NCMD.

There are no zones 2-8 codepaths anywhere. The zone-1 hardcode is consistent across all of these.

What "multi-zone support" would mean concretely

The cleanest way to add multi-zone is per-zone parameterization of every existing zone-1 path:

Dashboard rendering

  • RenderSchedule iterates zones with <N>/enabled == true and outputs one schedule grid per enabled zone (or a tab/accordion UI if you want to keep the page compact).
  • RenderProfiles similarly outputs per-zone activity profile cards.
  • The SSE /events handler dynamically subscribes to the zone keys for each enabled zone rather than the hardcoded 1/... list.

Prometheus metrics

  • zone_temp and zone_humidity gauges already accept a zone label. The work is wiring OnChange1("<N>/rt", ...) for each enabled zone instead of just zone 1.
  • Other zone-tagged metrics (humidity, setpoints, fan mode) could be added with the same per-zone-OnChange1 pattern.

HA MQTT bridge

  • Discovery: publish one homeassistant/climate/<clientID>-zone<N>/config per enabled zone, with topic strings parameterized on <N>. (Or keep the existing single-zone entity for zone 1 and add additional entities only for <N> >= 2 if backward compat matters.)
  • State publishes: parameterize the existing OnChange1("1/clsp", ...) etc. handlers across enabled zones.
  • Command subscriptions: subscribe to per-zone command topics and translate to the corresponding zones/<N>/... Carrier-side write.

The command-side write path is already partially confirmed: the HA bridge writes zones/1/hold/hold, zones/1/activities/manual/htsp, etc. via NCMD today and the firmware accepts those. By symmetry, zones/2/..., zones/3/... etc. should also be accepted, but this is a write path that nobody has exercised against the live firmware. Worth confirming on a multi-zone install before shipping.

Quick test plan for a contributor

If someone with a multi-zone install wants to validate before doing the larger UI work:

  1. Check /recent on their anantha for <N>/enabled == true for some <N> other than 1. That confirms the firmware is publishing for that zone.
  2. Compare <N>/program/.../, <N>/activities/... keys between zones to ensure shape parity.
  3. Optionally probe a write: publish a CarrierInfo{ConfigSettings: [{Name: "zones/2/hold/hold", ConfigType: CT_BOOL, BoolValue: false}]} to spBv1.0/WallCtrl/NCMD/<clientID> and observe whether zone 2's hold state changes.

A single-zone install can also do (1) and (2) by checking that all 8 zones' schemas are present even when 7 are disabled - confirms the protocol carries everything regardless of activation.

Related: DCMD/<id>/zones is the protocol-correct alternative

Carrier's Sparkplug B mapping has a sub-device topic spBv1.0/WallCtrl/DCMD/<thingName>/zones that the thermostat subscribes to on every CONNECT. Per Sparkplug semantics this is the "preferred" route for sub-device-targeted commands - it's what would carry zone writes if a Sparkplug-spec-compliant SCADA host were the source. anantha currently uses NCMD instead and the firmware accepts that route, so multi-zone work doesn't need to adopt DCMD.

DCMD experimentation could be a separate follow-up; it's not on the critical path for multi-zone.

Reference data

The DBIRTH-level decode of zones (1050 ConfigSettings, full per-zone shape across all 8 zones) was captured locally and saved as a reference. If useful for the maintainer, the relevant protoprint output can be shared. The wire-level confirmation of zones 2-8 carrying full schema even when disabled is the key finding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions