Summary
The ControlByWeb X-406 is an Ethernet-attached industrial I/O module (relay + digital/analog I/O, 1-Wire sensor support) controllable over TCP/IP. The spec covers HTTP GET-based control of state.xml/state.json and customState.xml/customState.json endpoints, plus Modbus/TCP slave on port 502.
Transport
protocols:
- tcp # HTTP GET control plane
- tcp # Modbus/TCP slave (port 502, separate socket pool)
addressing:
port: 80 # default HTTP; customState/state files served from same port
base_url: "http://{device-ip}/"
auth:
type: none # inferred: source documents "No Password" path as the default; auth (HTTP Basic) is optional per device setup
Traits
- powerable # inferred from relay on/off commands
- routable # inferred from per-relay select/control commands (relay1..relayN)
- queryable # inferred from state.xml/json GET endpoints and Modbus read functions
- levelable # inferred from register X set-value commands (analog-ish writable values)
Actions
# HTTP GET control - state.xml / state.json endpoints (TCP/80, default)
- id: get_state_xml
label: Get State (XML)
kind: query
command: "GET /state.xml HTTP/1.1\r\n\r\n"
params: []
notes: >
Returns <datavalues> with relayX, digitalInputX, analogInputX, vin,
registerX, onTimeX, totalOnTimeX, countX, frequencyX, oneWireSensorX,
utcTime, timezoneOffset, serialNumber. Only I/O assigned a Local I/O
Number appears.
- id: get_state_json
label: Get State (JSON)
kind: query
command: "GET /state.json HTTP/1.1\r\n\r\n"
params: []
notes: >
JSON equivalent of state.xml. Also includes lat, long, minRecRefresh
when relevant. Same I/O filtering rule as state.xml.
- id: get_custom_state_xml
label: Get Custom State (XML)
kind: query
command: "GET /customState.xml HTTP/1.1\r\n\r\n"
params: []
notes: Tag names use user-configurable Camel-case labels (e.g. myRegister1).
- id: get_custom_state_json
label: Get Custom State (JSON)
kind: query
command: "GET /customState.json HTTP/1.1\r\n\r\n"
params: []
- id: set_register_via_state_xml
label: Set Register (state.xml)
kind: action
command: "GET /state.xml?register1={value} HTTP/1.1\r\n\r\n"
params:
- name: value
type: number
description: Numeric value (e.g. 10.5)
- id: set_relay_off_xml
label: Set Relay OFF (state.xml)
kind: action
command: "GET /state.xml?relay{relay}=0 HTTP/1.1\r\n\r\n"
params:
- name: relay
type: integer
description: Relay number (1-based)
- id: set_relay_on_xml
label: Set Relay ON (state.xml)
kind: action
command: "GET /state.xml?relay{relay}=1 HTTP/1.1\r\n\r\n"
params:
- name: relay
type: integer
description: Relay number (1-based)
- id: pulse_relay_xml
label: Pulse Relay (state.xml, preset duration)
kind: action
command: "GET /state.xml?relay{relay}=2 HTTP/1.1\r\n\r\n"
params:
- name: relay
type: integer
description: Relay number (1-based)
notes: Pulse width = Pulse Duration from relay setup page.
- id: pulse_relay_custom_duration_json
label: Pulse Relay custom duration (state.json)
kind: action
command: "GET /state.json?pulseTime{relay}={seconds}&relay{relay}=2 HTTP/1.1\r\n\r\n"
params:
- name: relay
type: integer
description: Relay number (1-based)
- name: seconds
type: number
description: Pulse duration in seconds. MUST precede relay{N}=2 in query string.
notes: pulseTime argument must come before relay{N}=2 in query string.
- id: set_on_time_xml
label: Set onTime counter (state.xml)
kind: action
command: "GET /state.xml?onTime{io}=0 HTTP/1.1\r\n\r\n" # UNRESOLVED: param template uses {io}
params:
- name: io
type: integer
description: I/O number (1-based)
- id: set_total_on_time_xml
label: Set totalOnTime counter (state.xml)
kind: action
command: "GET /state.xml?totalOnTime{io}=0 HTTP/1.1\r\n\r\n"
params:
- name: io
type: integer
description: I/O number (1-based)
- id: set_counter_xml
label: Set count counter (state.xml)
kind: action
command: "GET /state.xml?count{io}=0 HTTP/1.1\r\n\r\n"
params:
- name: io
type: integer
description: I/O number (1-based)
- id: multi_command_xml
label: Combined XML/JSON multi-command
kind: action
command: "GET /state.xml?relay1=1&relay2=0 HTTP/1.1\r\n\r\n"
params:
- name: relay1_value
type: integer
description: Value for relay1 (0=off, 1=on, 2=pulse)
- name: relay2_value
type: integer
description: Value for relay2 (0=off, 1=on, 2=pulse)
- id: erase_log_txt
label: Erase log.txt
kind: action
command: "GET /log.txt?erase=1 HTTP/1.1\r\n\r\n"
params: []
notes: Requires setup password if User account enabled.
- id: get_log_txt
label: Read log.txt
kind: query
command: "GET /log.txt HTTP/1.1\r\n\r\n"
params: []
- id: erase_syslog_txt
label: Erase syslog.txt
kind: action
command: "GET /syslog.txt?erase=1 HTTP/1.1\r\n\r\n"
params: []
notes: Requires setup username + password.
- id: get_syslog_txt
label: Read syslog.txt
kind: query
command: "GET /syslog.txt HTTP/1.1\r\n\r\n"
params: []
# Modbus/TCP (slave on port 502, two simultaneous sockets; per-IO address map is device-specific)
- id: modbus_read_coils
label: Modbus Read Coils (FC 01)
kind: query
command: "Modbus FC 0x01 (Read Coils)"
params:
- name: start_address
type: integer
description: Refer to device Modbus Address Table (Setup > Advanced Network)
- name: coil_quantity
type: integer
description: Multiple outputs may be read; sum of start+qty <= max coil count
notes: Bit 0 of response = state of start_address coil. 1=ON, 0=OFF. Error code 0x81, exception 0x02.
- id: modbus_read_discrete_inputs
label: Modbus Read Discrete Inputs (FC 02)
kind: query
command: "Modbus FC 0x02 (Read Discrete Inputs)"
params:
- name: start_address
type: integer
description: Refer to device Modbus map
- name: input_quantity
type: integer
notes: Reads digital inputs + digital I/O configured as inputs. Error code 0x82, exception 0x02.
- id: modbus_read_holding_registers
label: Modbus Read Holding Registers (FC 03)
kind: query
command: "Modbus FC 0x03 (Read Holding Registers)"
params:
- name: start_address
type: integer
description: Analog inputs, registers, counters, vin; 32-bit floats read as 2-register pairs
- name: register_quantity
type: integer
description: Must be divisible by 2
notes: IEEE 754 float, little- or big-endian per Advanced Network setting. NaN (0xFFFFFFFF) if sensor missing.
- id: modbus_write_single_coil
label: Modbus Write Single Coil (FC 05)
kind: action
command: "Modbus FC 0x05 (Write Single Coil), value 0x00=OFF, 0xFF=ON"
params:
- name: start_address
type: integer
- name: value
type: integer
enum: [0, 255]
description: 0x00=OFF, 0xFF=ON
notes: Response mirrors request. Error code 0x85.
- id: modbus_write_multiple_coils
label: Modbus Write Multiple Coils (FC 15)
kind: action
command: "Modbus FC 0x0F (Write Multiple Coils), 0x0000=OFF, 0xFFFF=ON"
params:
- name: start_address
type: integer
- name: quantity
type: integer
- name: value_bytes
type: string
description: Byte(s) of bitmask, e.g. 0xFFFF / 0xF0
notes: Byte count = quantity/8. Error code 0x8F.
- id: modbus_write_multiple_registers
label: Modbus Write Multiple Registers (FC 16)
kind: action
command: "Modbus FC 0x10 (Write Multiple Registers), IEEE 754 float"
params:
- name: start_address
type: integer
- name: register_quantity
type: integer
description: Must be divisible by 2
- name: float_value
type: number
description: e.g. 81.25 → 0x800042A2 little-endian, 0x42A28000 big-endian
notes: Acknowledged with register quantity written. Error code 0x90.
- id: modbus_pulse_relay_register
label: Modbus Pulse Relay via holding-register pair
kind: action
command: "Modbus FC 0x10 (Write Multiple Registers) at addresses 512-513, 32-bit float seconds"
params:
- name: seconds
type: number
description: Pulse duration as 32-bit IEEE 754 float
notes: Per source - relay1 pulse duration address 512-513 (PLC 40513-40514).
Feedbacks
- id: relay_state
type: enum
values: [off, on, pulse]
source: "<relayX>0=off, 1=on, 2=pulse</relayX>"
- id: digital_input_state
type: enum
values: [off, on]
source: "<digitalInputX>0=off (no voltage), 1=on (voltage applied)</digitalInputX>"
- id: digital_io_state
type: enum
values: [off, on]
source: "<digitalIOX> when configured as input or output</digitalIOX>"
- id: analog_input_value
type: number
source: "<analogInputX>Value of analog input X</analogInputX>"
- id: vin_value
type: number
source: "<vin>Scaled internal Vin measurement</vin>"
- id: register_value
type: number
source: "<registerX>Value of the register X</registerX>"
- id: on_time_value
type: number
source: "<onTimeX>seconds since input last came on</onTimeX>"
- id: total_on_time_value
type: number
source: "<totalOnTimeX>total seconds input has been on</totalOnTimeX>"
- id: count_value
type: number
source: "<countX>count value associated with input X</countX>"
- id: frequency_value
type: number
source: "<frequencyX>frequency associated with input X</frequencyX>"
- id: one_wire_sensor_value
type: string
source: "<oneWireSensorX>77.3=sensor value, 77.3 F=with units, x.x=read error</oneWireSensorX>"
- id: utc_time
type: integer
source: "<utcTime>seconds since 1970-01-01</utcTime>"
- id: timezone_offset
type: integer
source: "<timezoneOffset>offset to apply to utcTime for local time</timezoneOffset>"
- id: serial_number
type: string
source: "<serialNumber>XX:XX:XX:XX:XX:XX</serialNumber>"
Variables
- id: register1
type: number
settable: true
source: "register1 (state.xml?register1=10.5)"
description: General-purpose writable numeric register.
- id: registerN
type: number
settable: true
description: Additional registers as configured. Names mirror user config in customState.
- id: onTime_io
type: number
settable: true
source: "state.xml?onTime{io}=N"
- id: totalOnTime_io
type: number
settable: true
source: "state.xml?totalOnTime{io}=N"
- id: count_io
type: number
settable: true
source: "state.xml?count{io}=N"
# UNRESOLVED: register/index ranges per X-406 model not stated in this excerpt;
# source refers to "the Modbus Address Table" and "Local I/O Number" assignment
# without enumerating how many relays/digital I/O/analog inputs the X-406 itself ships.
Events
# UNRESOLVED: source describes unsolicited MQTT publishes and SNMP traps/notifications
# at a feature level ("when a relay changes state", "when sensor value reached",
# "when supply voltage is out of range") but does not enumerate the on-wire payload
# format. Documented as features only; no per-event spec entries can be authored
# without fabricating payload structure.
Macros
# UNRESOLVED: source does not describe multi-step client-side sequences; pulse
# commands and combined query-string commands are atomic from the client's POV.
Safety
confirmation_required_for: []
interlocks: []
# UNRESOLVED: source contains no explicit safety warnings, interlock procedures,
# or power-on sequencing requirements. X-406 is a generic I/O module; relay
# control is unsupervised from the device's perspective.
Notes
- HTTP control plane is plain TCP/80 by default;
state.xmlandstate.jsonmirror each other (XML is canonical, JSON is convenience form). pulseTime{N}MUST appear in the query string beforerelay{N}=2or it is ignored.- Auth is optional: if the device's User account is enabled, requests must include
Authorization: Basic <base64(user:pass)>where the default usernone:webrelayencodes tobm9uZTp3ZWJyZWxheQ==. - Modbus/TCP is disabled whenever the User account is enabled (Modbus has no password mechanism); two simultaneous TCP sockets; connection times out after 50 s of idle — send a periodic read to keep it open.
- Modbus error responses set the high bit of the function code (e.g. 0x01 → 0x81); common exception is 0x02 (bad address/qty combo).
- 32-bit sensor values span two 16-bit Modbus registers and must be read/written in even pairs.
- Modbus address map is device-specific — generated dynamically from configured I/O; consult Setup > Advanced Network > "View Modbus Address Table" rather than hard-coding offsets. The source's worked examples use illustrative addresses only.
- SNMP v1/v2c community strings default to
webrelayfor both read and write; v3 uses USM (auth + privacy protocols). Source does not enumerate per-OID MIB structure — generate from device. - MQTT supports v3.1.1 and Sparkplug B; payload tokens listed in source are placeholders like
${relay1},${vin},${dateTime}for use in publish templates, not standalone commands. - ControlByWeb.Cloud DAT URL form:
https://api.controlbyweb.cloud/{dat-url}/state.json?relay1=1&relay2=1— useful when port-forwarding is unavailable.
Provenance
source_domains:
- controlbyweb.com
source_urls:
- https://controlbyweb.com/wp-content/uploads/2025/05/cbw-integration-and-protocols-manual.pdf
- https://controlbyweb.com/wp-content/uploads/2025/05/400-series-users-manual.pdf
- https://controlbyweb.com/support/cbw-integration-manual/
retrieved_at: 2026-06-30T15:31:43.563Z
last_checked_at: 2026-07-07T12:27:14.039Z
Verification Summary
verdict: verified
checked_at: 2026-07-07T12:27:14.039Z
matched_actions: 24
action_count: 24
confidence: medium
summary: "All 24 actions matched literally in source; HTTP GET endpoints, Modbus function codes, and transport parameters (port 80, no auth default) verified; full coverage. (7 unresolved item(s) noted in Known Gaps.)"
Known Gaps
- "SNMP, MQTT/Sparkplug B, and ControlByWeb.Cloud DAT URL payload details are described at a feature level in source but lack per-action opcode granularity; left as Notes only."
- "param template uses {io}"
- "register/index ranges per X-406 model not stated in this excerpt;"
- "source describes unsolicited MQTT publishes and SNMP traps/notifications"
- "source does not describe multi-step client-side sequences; pulse"
- "source contains no explicit safety warnings, interlock procedures,"
- "per-OID SNMP MIB structure, MQTT topic/payload schemas, Sparkplug B metric definitions, Remote Services TCP V1 connection-string format (beyond the \"send state.xml, expect 3-char ACK within 10s\" note) — source describes these as features without machine-protocol-grade detail."
From the AI4AV catalog (https://ai4av.net) · ODbL-1.0