Opto 22 groov I/O
The groov_io driver controls and monitors an Opto 22 groov EPIC or groov RIO
modular I/O controller over its local REST API. Each digital channel on the
controller's I/O modules becomes a GEM zone: a digital output you turn on and off,
or a digital input whose contact state GEM watches and reflects live. Analog channels
can be read and written on demand from a macro or the Commands page. It is the
general-purpose industrial-I/O front end for jobs where GEM needs to throw a real
contact or read a hard wired signal — driving a lighting contactor, latching a lock or
gate, switching a power circuit, or monitoring a dry-contact input, flow switch, or
level sensor.
The controller talks to the groov box over HTTPS on port 443, authenticating
with an API key header. It polls the digital modules you declare and writes channel
state with short REST calls under the controller's /api/v1/io/local I/O tree.
Unlike GEM's plug-and-play relay boards, this driver has no discovery. You must tell
it which module slots are populated and what type each one is, using the Module Types
attribute, before it will connect. There is no zone auto-creation either — you create
one zone per channel by hand and address it moduleId:channel. See
Setup below.
How modules, channels, and zones map
One groov EPIC/RIO controller is one GEM device. Inside it, I/O is organized as
modules (the physical slots in the rack) and channels (the individual points on
each module). GEM addresses a single point as moduleId:channel — both 0-based:
0:4— module 0, channel 41:0— module 1, channel 0
Every point you want to control or monitor becomes a zone bound to the device, with
its Address set to that moduleId:channel string. The Module Types attribute on
the device declares which module slots exist and whether each is a digital or analog
module, so the driver knows which ones to poll.
- Digital output channels accept
on/off(and theopen/closealiases).onandcloseclose the contact (drive the point true);offandopenopen it (false). - Digital input channels are read-only. The driver polls them and writes the result to
the bound zone's state as
on(contact true) oroff(contact false). - Analog channels are read and written on demand with the
get_analog_value/set_analog_valuecommands. They are not polled — GEM does not track analog values in the background (see What is and isn't polled).
Prerequisites
- A groov EPIC or groov RIO controller on the same LAN as GEM, reachable at a known IP (use a DHCP reservation — the driver addresses the controller by IP).
- The groov REST API enabled on the controller. Turn it on in the controller's groov Manage interface.
- An API key with read + write scope, generated in groov Manage. GEM sends it on every
request; a key with the wrong scope or a revoked key returns
401 / unauthorized. - Outbound HTTPS from the GEM controller to the groov box on port 443 (or whatever port you have the REST API listening on).
- Know your module layout — which slot holds which kind of module (digital in, digital
out, analog in, analog out) and the channel count on each. You will enter this as the
Module Types map. If you are unsure, add the device with a best-guess map, then run the
get_module_typescommand against it to read the controller's actual module list back.
Setup
-
Add the device. Go to System → Devices → Add Device on the Devices page and choose the driver Opto 22 groov I/O. Enter the Controller IP Address and the API Key. Leave HTTPS Port at
443unless the REST API is on a different port. -
Fill in Module Types. This attribute is required — the driver refuses to connect without it. Enter a JSON object mapping each module id (as a string) to its type:
{"0": "digital_output", "1": "digital_input"}Supported types are
digital_input,digital_output,analog_input, andanalog_output. Save and enable the device. -
Create a zone per channel. On the Zones page, add a zone bound to the groov device and set its Address to
moduleId:channel(e.g.0:4). The address field hint shows the same format. Pick the subsystem that matches what the point drives — the subsystem list pre-selects a sensible default (Lights, Power, Garages, or a generic relay subsystem) from the driver's hints, but you choose the right home. -
Set Normally Closed where needed. On a zone wired to a normally-closed contact, toggle Normally Closed so the polled state reads the way you expect (a resting-closed contact reports as
off, an opened one ason). This affects the reported state only — see Digital control and monitoring.
If you are not certain which slot is which, run the get_module_types command against the
device — it queries the controller's local rack and returns the module type list, which you
can transcribe into the Module Types JSON. get_channel_names and get_module_config
(both take a module_id arg) return the per-channel names and configuration for a module,
which helps you confirm channel numbering before you assign zone addresses.
Attributes
Device
| Attribute | UI label | Required | Type | Default | Notes |
|---|---|---|---|---|---|
ip | Controller IP Address | yes | string | — | Controller LAN IP (DHCP reservation recommended). |
api_key | API Key | yes | string (secure) | — | groov Manage API key with read + write scope. Stored encrypted. |
module_types | Module Types | yes | json | — | JSON map of module id → type (digital_input, digital_output, analog_input, analog_output). Required — the driver will not connect without it. |
port | HTTPS Port | no | int | 443 | Port the groov REST API listens on. |
status_interval | Status Poll Interval (ms) | no | int | 1000 | How often the driver polls declared digital modules. Raise it to cut traffic; lower it for snappier input feedback. |
Zone
Each zone carries an Address in the form moduleId:channel (e.g. 0:4). Enter it on
the Zones page — the field hint shows the same format.
| Attribute | UI label | Type | Default | Notes |
|---|---|---|---|---|
normally_closed | Normally Closed | bool | false | Invert the reported digital state for a normally-closed contact (closed reads off, open reads on). Does not change the direction of on / off commands. |
Commands
Send these from the Commands page, a macro, or a control. When you send a verb to a
zone, GEM supplies the zone's Address as the address argument for you — you rarely
type it by hand.
Digital control
| Command | Args | Description |
|---|---|---|
on | address | Drive the channel true (close the contact). |
off | address | Drive the channel false (open the contact). |
close | address | Alias of on — drive the channel true. |
open | address | Alias of off — drive the channel false. |
set_digital_state | address, state | Set an explicit state. state accepts true / false (or on / off). |
get_digital_state | address | Read one digital channel's state now. |
Analog
| Command | Args | Description |
|---|---|---|
get_analog_value | address | Read one analog channel's value. |
set_analog_value | address, value | Write an analog output. value is sent as a floating-point number. |
get_module_analog_values | module_id | Read all analog values on a module at once. |
Diagnostics and discovery
| Command | Args | Description |
|---|---|---|
get_module_types | — | Return the controller's local module list — use it to build or verify Module Types. |
get_module_config | module_id | Return one module's configuration. |
get_channel_names | module_id | Return the channel names for a module. |
get_module_digital_values | module_id | Read all digital channel states on a module — this is the call the background poller uses. |
address is the moduleId:channel string; module_id is just the module number. If you
call an analog write without a value, or a digital write without a state, the driver
rejects it with a "missing value" / "missing state" error.
Digital control and monitoring
Commands are direct. on and close set the channel true (contact closed); off and
open set it false (contact open). That mapping is fixed and does not depend on the
zone's subsystem, and it is not flipped by the Normally Closed toggle — Normally
Closed only affects how the polled state is reported.
Monitoring is by polling. For every module you declare as digital_input or
digital_output in Module Types, the driver reads that module's channel states every
Status Poll Interval (default 1000 ms) and writes each channel's state onto the zone
whose address matches moduleId:channel:
- Contact true → zone state
on; contact false → zone stateoff. - With Normally Closed enabled on that zone, the reading inverts: true →
off, false →on. - The zone updates only when the state actually changes, so a static input doesn't churn.
Some of GEM's relay drivers re-word a relay's state to fit its subsystem (a contact reads
as "open" under Gates but "on" under Lights). This driver does not do that — a monitored
groov channel always reports plain on / off, whatever subsystem the zone lives in. If
you need the semantic open/closed/locked wording, that is a property of relay drivers that
opt into it; groov I/O reports the raw digital state. See
Relay semantics for the wider picture.
What is and isn't polled
- Digital input / digital output modules are polled on the status interval and keep their zones' state live.
- Analog modules are not polled. You can read and write analog channels on demand with
get_analog_value/set_analog_value(andget_module_analog_values), but GEM does not track analog values in the background, so an analog input's value is only as fresh as the last time you queried it. Declaring a module asanalog_input/analog_outputin Module Types is still correct — it just doesn't start a poll.
Known quirks
- Module Types is mandatory. With it unset the driver logs a missing-
module_typeswarning and never connects — there is no auto-discovery of the rack layout. - Channels are 0-based. Addresses are
moduleId:channelwith both indexes starting at 0, and the channel must exist on that physical module or the write goes nowhere. - The self-signed controller certificate is accepted. The driver connects to the groov box's HTTPS API without validating its certificate, so treat the LAN segment between GEM and the controller as semi-trusted.
- Normally Closed inverts reporting, not commands. It changes how a monitored channel's
state is reported; it does not reverse the direction of an
on/offcommand. - Only the local rack is addressed. The driver targets the controller's local I/O
(
/api/v1/io/local); remote/expansion racks addressed by a different device name are not exposed through the standard zone verbs.
Troubleshooting
| Symptom | Check |
|---|---|
| Driver logs "missing module_types" and never connects | Set the Module Types JSON on the device — it maps each module id to digital_input / digital_output / analog_input / analog_output. |
Commands return 401 / unauthorized | The API key is for the wrong scope or has been revoked. Mint a new key in groov Manage with read + write access and update the API Key attribute. |
| Zone state doesn't flip | The Address must be moduleId:channel (e.g. 0:4), both 0-based, and the channel must exist on that physical module. Confirm the module is declared in Module Types as a digital type — analog modules are not polled. |
| Input reads inverted | Toggle Normally Closed on that zone. |
| Can't tell which slot is which | Run get_module_types against the device to read the controller's module list, and get_channel_names / get_module_config (with a module_id) to confirm channel numbering. |
| Analog value looks stale | Analog channels aren't polled — read them on demand with get_analog_value (or get_module_analog_values) each time you need a current reading. |
Related
- Devices — adding the controller and setting its attributes.
- Zones — addresses, subsystems, and per-verb command routing.
- Subsystems — where the I/O zones live.
- Commands — how
on/offverbs route to a device. - Relay semantics — how digital/relay states map to on/off across the system.