cmd/gateway runs the bus stack and serves HTTP endpoints for GraphQL, GraphQL subscriptions, and MCP. It can optionally enable a passive broadcast listener and advertise the GraphQL endpoint over mDNS.
helianthus-ebusgoandhelianthus-ebusregbuild and include unit tests.helianthus-ebusgatewaybuilds as a Go module and serves:- GraphQL queries/mutations:
/graphql - GraphQL subscriptions (SSE/WS):
/graphql/subscriptions - Projection snapshot endpoint:
/snapshot - MCP endpoint:
/mcp - Projection browser (read-only projection explorer):
/ui - Portal shell + versioned API:
/portaland/portal/api/v1
- GraphQL queries/mutations:
- mDNS advertisement for the GraphQL endpoint (see mDNS Discovery below).
cmd/gatewaycan optionally enable a passive broadcast listener (separate connection) for energy broadcasts.
cmd/gateway selects the eBUS transport backend via CLI flags:
| Flag | Purpose | Notes |
|---|---|---|
-transport |
Backend protocol | enh, ens (alias of enh), udp-plain, tcp-plain, ebusd-tcp, or adapter-direct |
-network |
Dial network | unix, tcp, or udp |
-address |
Socket path or host:port | Example: /var/run/ebusd/ebusd.socket or 127.0.0.1:8888 |
-source-addr |
Initiator/source address used by scan + semantic reads | Hex (0xF0), decimal, 0x00, or auto |
-read-timeout |
Read timeout | Default 5s |
-write-timeout |
Write timeout | Default 5s |
-dial-timeout |
Connect timeout | Default 5s |
# ENH over TCP adapter (enhanced adapter protocol, ebusd-style port 9999)
go run ./cmd/gateway \
-transport enh \
-network tcp \
-address 203.0.113.10:9999
# ENS alias over TCP (same framing as ENH for network endpoints)
# Note: `ens` is an alias for `enh` (Enhanced protocol). Both values are accepted.
go run ./cmd/gateway \
-transport ens \
-network tcp \
-address 203.0.113.10:9999 \
-source-addr auto
# Raw UDP byte stream (software arbitration required above transport)
go run ./cmd/gateway \
-transport udp-plain \
-network udp \
-address 203.0.113.10:9999 \
-source-addr auto
# Raw TCP byte stream (software arbitration required above transport)
go run ./cmd/gateway \
-transport tcp-plain \
-network tcp \
-address 203.0.113.10:9999 \
-source-addr auto
# ebusd command backend over unix socket
go run ./cmd/gateway \
-transport ebusd-tcp \
-network unix \
-address /var/run/ebusd/ebusd.socket
# adapter-direct (gateway-embedded adapter multiplexer, no external proxy)
go run ./cmd/gateway \
-transport adapter-direct \
-network tcp \
-address 203.0.113.10:9999 \
-source-addr autoFor ebusd command syntax and response framing details, see protocols/ebusd-tcp.md.
-source-addr auto(or-source-addr 0x00) enables gentle-join behavior with proxy-mediated ENS/ENH/UDP-plain/TCP-plain flows.- In gentle-join mode, Helianthus asks the proxy to select a free initiator dynamically instead of pinning a fixed address.
-source-addr 0x31should be avoided whenebusdis also active, because0x31isebusd's common default initiator.- With
-transport ebusd-tcp, source selection only affects ebusd command parameters; the on-wire initiator is still ebusd's own bus identity.
Terminology note:
protocols/enh.md/protocols/ens.mddescribe ebusd’s enhanced adapter protocol and the meaning of theenh:/ens:prefixes.- In Helianthus gateway CLI,
-transport ensis accepted as a compatibility alias for-transport enh. - Raw ESC/SYN wire symbols (
0xA9/0xAA) are decoded in the bus/protocol layer.
For UDP-PLAIN adapters, run Helianthus behind a proxy with a single southbound owner. Do not connect multiple independent clients directly to the adapter endpoint.
Recommended topology:
adapter (udp-plain) <-single southbound-> helianthus-ebus-adapter-proxy <-northbound-> gateway / ebusd / tools
Rationale:
- prevents cross-client request/response mismatch on raw byte streams,
- centralizes bounded retry/backoff and collision signaling,
- keeps a consistent bus view for all northbound consumers.
When -transport ebusd-tcp is selected, the gateway uses ebusd's text command channel (typically port 8888) and executes request/response traffic via hex commands.
- The ebusd command backend is request/response oriented; it is not a continuous bus sniff stream.
- Broadcast sends (
DST=0xFE) may return textual completion (for exampledone ...) with no hex payload. - The optional passive broadcast listener (
-broadcast) expects stream-style frame input; this is generally not useful with ebusd command-mode connections.
ERR:lines with timeout/no-answer wording are treated as timeouts.- Other
ERR:lines (or malformed hex/usage responses) are treated as invalid payload errors. dump enabled/dump disabledbanners are treated as non-semantic noise and ignored.- If multiple lines are returned, parsers should use the first valid hex payload line and ignore later trailing noise.
- If an
ERR:line appears before the actual hex payload in the same command response burst, parsers keep a short follow-up window to catch the valid hex line. - Empty/no non-empty response lines are treated as timeout conditions.
- Runtime transport setup clamps
ebusd-tcpread/write deadlines to at leastscan-request-timeout(default floor400ms) to reduce cross-command stream desynchronization.
Gateway startup scan tries to reduce bus load by using ebusd's known target list when available:
- If gateway itself runs with
-transport ebusd-tcpovertcp, it asks that endpoint forscan result. - In
ebusd-tcpmode, gateway preloads registry devices directly from thatscan result(address/manufacturer/device ID/HW/SW/SN) before active probing. - For non-
ebusd-tcptransports, it also tries a local fallback ebusd endpoint at127.0.0.1:8888. - If neither returns targets, gateway falls back to the full default address scan.
- If direct scan requests time out for all narrowed targets, gateway imports device metadata
from the same ebusd
scan resultoutput as a discovery fallback.
Important startup contract:
scan resultpreload/narrowing is opportunistic bus-load reduction, not proof that semantic startup is ready to close.- Preloaded inventory, product identity, and imported metadata are useful hints, but they do not by themselves prove that startup already has a coherent Vaillant/B524 controller root.
- If narrowed/preloaded inventory does not yield a coherent B524/controller root, gateway must perform one bounded full-range discovery retry before concluding startup scan.
- If preload already yields a coherent root, no broadened retry is required.
When semantic B524 reads time out in ebusd-tcp mode, zone inventory/name/state can be recovered
from ebusd's grab result all cache (passive snapshot), so climate entities can still appear without
forcing additional bus traffic.
Successful hydration from this path is classified as live semantic source for startup phase accounting
(it is not treated as persistent stale cache preload).
Runtime read/write traffic still uses the configured gateway transport.
Passive observe-first support is narrower than active startup support.
ebusd-tcpremainsunsupported_or_misconfiguredfor passive observe-first. Seeprotocols/ebusd-tcp.md.- Direct adapter-class
enh/ensendpoints overtcp/:9999remainunsupported_or_misconfiguredfor passive observe-first, including equivalent hostname forms that resolve to the same adapter listener. Seeprotocols/enh.mdandprotocols/ens.md. - Proxy-like
enh/ensendpoints on other ports remain passive-capable for observe-first, whether they are reached over local loopback or remote northbound addresses.
Operational meaning:
- a coherent B524/controller root still proves active semantic startup readiness;
- it does not by itself prove that passive observe-first is supported for the chosen transport topology;
- validation cases that use direct adapter-class
enh/ensendpoints should expect explicit passive unavailability, not endlesswarming_up/socket_lossstates; - validation cases that use proxy-like
enh/ensendpoints on non-adapter ports should continue to expect passive-capable behavior.
The bounded P03 proof contract and the rollout decision remain intentionally
family-scoped. The proof evidence proves only the bounded
proxy-single-client / passive_mode=required / ens / no-ebusd family used by
canonical P03; it does not justify a broad default flip.
- Bounded proof evidence:
Project-Helianthus/helianthus-ebusgateway#400 - Canonical bounded proof artifact (outside this docs repo; from a
Project-Helianthus/helianthus-ebusgatewaycheckout):helianthus-ebusgateway/results-matrix-ha/20260315T070147Z-gw15-proof-p03-canonical-rerun/index.json - Canonical non-promotion decision:
Project-Helianthus/helianthus-ebusgateway#439
Operational meaning:
- record-only remains the canonical default for any family or deployment topology that is not individually proven in the bounded passive proof scope;
- the proven bounded
P03family may continue to serve proof artifacts, but that does not widen default promotion to direct adapter-classenh/ens,ebusd-tcp, or any broader deployment family; and - any later default-flip claim must be backed by a separate proven deployment family.
- Gateway reads/writes semantic startup cache at
-semantic-cache-path(default./semantic_cache.json). - Writes are atomic (temp file + rename).
- Runtime semantic cache persistence writes happen for live semantic publications.
- Legacy v1 -> v2 migration can also rewrite the cache during startup load (
semantic_cache_migrated), before any live publish. - Cache load failures are fail-safe:
- missing file →
semantic_cache_miss - malformed/unknown schema/read error →
semantic_cache_invalid - runtime continues with live polling and no crash.
- missing file →
- Legacy v1 cache payloads are migrated to v2 at load time (
semantic_cache_migrated).
Operator recovery for corrupted cache:
- Stop gateway/add-on.
- Move or delete the cache file at
-semantic-cache-path. - Restart gateway; runtime should log
semantic_cache_missand rebuild cache from live data.
When -mdns is enabled (default), the gateway advertises its GraphQL endpoint via DNS-SD:
- Service type:
_helianthus-graphql._tcp(domainlocal.) - Instance name:
-mdns-instance(defaulthelianthus) - Port: the HTTP listener port
- TXT records:
path: HTTP path for GraphQL (default-mdns-pathor-graphql-path, e.g./graphql)version: discovery schema version (default1)transport: endpoint transport (defaulthttp)instance_guid: stable installation GUID published when-instance-guidis configured
When Home Assistant consumes this advertisement, it must verify the same GUID through GraphQL
gatewayIdentity.instanceGuid before treating the discovered endpoint as the same installation.
# In each repo root:
go test ./...