Relay Features
When writing blueprints for devices controlled via an Enapter relay module (such as the ENP-RL6), there are two important behaviors to understand that are easy to get wrong.
Connection String Addresses a Single Channel
The ENP-RL6 is a six-channel relay module. When your blueprint controls a device through a relay, the connection URI in your configuration points to one specific relay channel — not to the ENP-RL6 as a whole.
The ENP-RL6 exposes its channels as individual digital outputs: port://do-1 through port://do-6. Each channel is addressed independently, and your blueprint's relay_channel parameter should contain the URI of just the channel it needs.
configuration:
relay:
display_name: Relay
description: Relay channel for this device.
parameters:
relay_channel:
display_name: Relay Channel
type: string
format: connection_uri
required: true
description: "URI for the relay channel controlling this device (e.g. port://do-1)."
This per-channel addressing means a single ENP-RL6 can serve multiple blueprints at the same time — each blueprint controls its own channel independently. No exclusive ownership of the entire module is needed.
When creating a relay client in Lua, pass the configured channel URI directly to relay.new():
local config, err = configuration.read('relay')
-- relay.new() opens a connection to a single channel, not the whole ENP-RL6
local relay_client, r_err = relay.new(config.relay_channel)
Relay State Persists Across Script Restarts
The relay module maintains the physical state of its channels independently from the Lua script. When the Lua script is restarted — for example, after uploading a new blueprint version or after a UCM reboot — all Lua variables are reset to their initial values. The relay, however, stays in whatever state it was in before the restart.
This mismatch is a common source of bugs. Consider a blueprint that tracks device status in a Lua variable:
-- WARNING: this variable is reset to false on every restart,
-- but the relay may already be closed.
local is_running = false
function start_command(ctx)
relay_client:close()
is_running = true
end
function stop_command(ctx)
relay_client:open()
is_running = false
end
function send_telemetry()
-- Bug: always reports 'stopped' after a restart, even if the relay is closed
enapter.send_telemetry({
status = is_running and 'running' or 'stopped',
})
end
After a restart, is_running will be false even if the relay is still closed. The UI will show the device as stopped when it is actually running.
The fix is to always read the relay state directly from hardware instead of maintaining it in a variable:
function send_telemetry()
-- Read the actual hardware state on every telemetry cycle
local is_running = relay_client:is_closed()
enapter.send_telemetry({
status = is_running and 'running' or 'stopped',
})
end
relay_client:is_closed() returns the real hardware state of the relay channel. Reading it each cycle ensures telemetry is always accurate, regardless of how many times the script has restarted or which script instance last changed the relay.
The same principle applies to any logic that branches on relay state. If your blueprint needs to know whether the relay is open or closed — for example, to guard a command or choose between two telemetry values — always query relay_client:is_closed() at the point you need the answer, rather than relying on a variable that was set in a previous execution.