Relay Semantics
Relay-driven hardware (gates, garage doors, locks, lighting contactors) reports and accepts a raw contact state — open or closed. What that contact means depends on the equipment and the subsystem. GEM centralizes this translation so drivers never hardcode it.
The translation helpers
Two server helpers map between raw relay state and logical zone state:
relayToZoneState(relayState, zone)— turn a contact reading into a zone state (e.g. "this gate is open").zoneToRelayCommand(cmd, zone)— turn a zone command into the relay action that achieves it.
Drivers route relay handling through these helpers rather than reasoning about contacts themselves.
Default mappings by subsystem
The default assumes a normally-open relay (relay closed = the active/energized state). From there the meaning is subsystem-specific:
| Subsystem name | Relay closed → | Relay open → |
|---|---|---|
gates, garages, lifts | open | closed |
doors, locks | unlocked | locked |
shades | open | closed |
lights, fans, fire, power, water | on | off |
security | armed | disarmed |
The default is chosen by the subsystem's name, with a plural fallback: GEM looks up the name as-is, then with an s appended. So a subsystem named gate resolves to the gates row, light to lights, and so on — singular or plural both work.
A subsystem whose name is not one of the rows above (for example climate, cameras, or any custom name you create) has no built-in relay mapping. For relay-driven hardware placed under such a subsystem the raw contact state passes through unchanged (closed/open), so you must supply an explicit relay_states override (below) for the zone to read as on/off, open/closed, etc.
Override precedence
A site can override the default per zone or per subsystem. Precedence, most specific first:
- Zone
relay_statesattribute (JSON) - Subsystem
relay_statesattribute - Built-in subsystem defaults (above)
The relay_states attribute is a JSON object keyed by the two physical contact states, with the semantic zone state you want each to produce as the value. Enter it in the Attribute editor on the zone (or subsystem) exactly like this:
{ "closed": "open", "open": "closed" }
For example, to make a gate wired normally-closed read correctly — contact closed means the gate is closed — set the zone's relay_states to { "closed": "closed", "open": "open" }.
The normally_closed shortcut
A normally_closed boolean zone attribute is also honored as a quick inversion that stacks on top of whichever mapping won (zone override, subsystem override, or default): when true it flips the relay-closed and relay-open results. normally_open is the default and a no-op — only normally_closed inverts the contact interpretation. Set normally_closed = true on a zone instead of hand-writing a relay_states JSON when all you need is to invert a recognized subsystem's default.
Driving relays from commands
The inverse helper zoneToRelayCommand turns a zone command into the physical relay action — it returns open or close. It understands the verbs open, close, on, off, lock, unlock, arm, and disarm, normalizing each to the matching zone state before looking it up in the same mapping. A command that doesn't correspond to either mapped state is passed through unchanged.
If you're writing a relay driver, always go through relayToZoneState / zoneToRelayCommand. Hardcoding "closed = on" breaks the moment a site wires a gate normally-closed or maps a contact differently. See Driver Development.