Driver Development
A driver teaches GEM to talk to one type of equipment. GEM ships 100+ drivers; this page is the contract for writing another.
Base classes
Extend device_base.js, or one of the generic transports when your device speaks a common protocol:
generic_tcp.js— TCP socketsgeneric_http.js— HTTP/RESTgeneric_serial.js— serialgeneric_ir.js— IR
The generic transports handle connection management so your driver focuses on the protocol.
The lifecycle methods
| Method | Responsibility |
|---|---|
connect() | Establish the connection to the device. |
command(cmd) | Translate a GEM command (on, off, open, set_level, …) into the device's native protocol and send it. |
response(msg) | Parse incoming device data and turn it into attribute updates. |
disconnect() | Tear the connection down cleanly. |
Declaring commands and metadata
getCommands()— returns the command table (templates, args, arg options). This table is authoritative: on each boot GEM'sensureCommandsreconciles system command rows to match it, so the driver — not hand edits — owns command definitions. Integrators add new commands; they don't edit driver-managed ones.getMetaData()— returns driver hints consumed by the admin UI: device-type hints, a command-set hint (enabling zero-touch command-set creation), required-attribute schema, and a sample configuration. Always reportstatus: 'stable'.
Patterns to use, not reinvent
- Relay state — never hardcode contact meaning. Route through
relayToZoneState(relayState, zone)andzoneToRelayCommand(cmd, zone). See Relay Semantics. - Per-zone action overrides — exotic wiring (e.g. a shade on a pulse relay) can be retrofitted via a zone's
<verb>_actionattribute; the server intercepts and re-dispatches. Implement the verb natively when you can; overrides are the escape hatch. - Toggle emulation — for a state toggle the hardware lacks (e.g.
mute_toggle), declare it ingetCommands()for discoverability and let the server synthesize it from cached state, or short-circuit it in your owncommand().
State access
Read current values from the in-memory collections, not the database:
let device = GemServer.getInstance().devices[deviceId];
let val = device.some_attr;
See Architecture for why.
:::warning Don't write zone.source
zone.source is GEM's av_source foreign key, owned by AV-routing macros — a driver writing it triggers "invalid av source for change." A physical input number belongs in zone.input, never zone.source.
:::