Skip to content

Transport Addresses

A transport address declares how to read and/or write an attribute value on a device in its native protocol, as declared in the transport field at driver level.

Access modes

Field When to use
read The address for reading the attribute value
write The address for writing/updating the attribute value
read_write The read and write address are identical — shorthand for declaring both

read_write and read/write are mutually exclusive on a given attribute. If read_write is present, it takes precedence.


Templating

Address strings and fields support ${key} interpolation. Keys are resolved in this order:

env:
  BASE_PATH: "http://192.168.1.1/api"

device_config:
  - name: ip
read: "GET ${BASE_PATH}/status"   # from env
read: "GET ${ip}/api/v1/status"   # from device_config

${value} is a reserved keyword available only in write address payloads. It is replaced at runtime by the actual value being written by the client.

write:
  method: POST
  path: "${ip}/api/v1/setpoint"
  body:
    value: ${value}

Address reference per protocol

HTTP

Both read and write addresses accept either a compact string or an object.

String format"<METHOD> <path>" — use when no body is needed:

read: "GET ${ip}/api/v1/status"
write: "POST ${ip}/attribute?value=${value}"

Object format — use when a body is required or for explicitness:

Field Required Description
method yes HTTP method: GET, POST, PUT, PATCH, DELETE
path yes URL of the endpoint
body no Request body — a dict (sent as JSON on write, form data on read) or a raw str (sent as-is)
read:
  method: GET
  path: "${ip}/api/v1/status"

write:
  method: POST
  path: "${ip}/api/v1/setpoint"
  body:
    value: ${value}

write:
  method: POST
  path: "${ip}/api/v1/setpoint"
  body: "raw string payload"

MQTT

Read and write addresses each have a topic to subscribe to or publish on.

Read — listen-only (string): subscribe to a topic and consume whatever arrives, no outgoing trigger.

read: agrid/${device_id}/snapshot

Read — with request trigger (object): subscribe to topic and publish a trigger message on request.topic before waiting for a response.

read:
  topic: agrid/${device_id}/snapshot
  request:
    topic: agrid/${device_id}/get/snapshot
    message:
      input: request

Write: publish message to topic.

write:
  topic: agrid/${device_id}/set/setpoint
  message:
    value: ${value}

Webhook

Read addresses have a topic matched exactly (no wildcards) against the path of incoming pushes on POST /transports/{transport_id}/ingress/{topic}. The string form is the whole topic; templating from device_config lets each device subscribe to its own path.

read: ${room_id}/snapshot

The transport is ingress-only: there is no request field (nothing can be solicited), on-demand reads fail, and write addresses are not supported. Polling is disabled automatically (declaring polling_enabled: true is a validation error). Declare healthcheck.expected_push_interval so device health tracks push silence — see Healthcheck.


Modbus TCP

Addresses are compact strings: <type><instance> or <type><instance>:<count> for values that span multiple registers. An object format is also accepted (type, instance, count) but the string notation is preferred.

Type Code Access Data types
Coil C read / write bool
Discrete Input DI read only bool
Input Register IR read only int, float, str
Holding Register HR read / write int, float, str

IR and HR support a register count suffix (:2 for 32-bit values). Use byte_convert to specify how multi-register values are decoded — see Codecs.

read: IR0:2       # Input Register 0, 2 registers (32-bit)
read_write: HR0:2 # Holding Register 0, 2 registers
read_write: C0    # Coil 0

read_write:
  type: IR
  instance: 4

M-Bus

Addresses are strings of the form <primary_address>/<record_index>. The meter's M-Bus primary address (0250) and the zero-based index of the record to read from its response. An object format (primary_address, record_index) is also accepted. M-Bus is read only.

read: "1/0"   # meter at primary address 1, first record
read: "1/2"   # meter at primary address 1, third record

read:
  primary_address: 1
  record_index: 0

BACnet

Addresses reference a BACnet object by type and instance. The device_instance is provided via device_config and resolved at runtime.

Object types can be written in full (analog-value) or as initials (AV).

Type Initials Access
analog-input AI read only
analog-value AV read / write
binary-input BI read only
binary-value BV read / write
multistate-input MI read only
multistate-value MV read / write

An optional BACnet write priority (P5P16) can be appended to the address (priorities below 5 are not allowed by design).

read: "AI 0"          # Analog Input 0 — read only
read_write: "AV 1"    # Analog Value 1
read_write: "BV 0 P8" # Binary Value 0, write priority 8

read:
  object_type: analog-value
  object_instance: 4
  property_name: present-value

KNX

The address is a KNX group address string in three-level notation (main/middle/sub). Supports ${key} templating from device_config.

Unlike MQTT, there is no request field. A GroupValueRead telegram is always sent automatically on every read — no configuration is needed. On write, a GroupValueWrite telegram is sent with no payload configured in the address.

read: "${ga_main}/${ga_middle}/4"
read_write: "${ga_main}/${ga_middle}/1"

read:
  topic: "${ga_main}/${ga_middle}/4"     # object form

OPC-UA

The address is a NodeId, in the protocol's own string notation: ns=<index>;<type>=<value>. <type> is one of i (numeric), s (string), g (GUID) or b (opaque, base64). ns= may be omitted and defaults to ns=0 (OPC-UA's reserved namespace, not a gridone default).

read: "ns=2;s=Chiller.SupplyTemp"   # string identifier
read: "ns=4;i=1042"                 # numeric identifier
read: "ns=1;g=09087e75-8e5e-499b-954f-f2a9603db28a"  # GUID
read: "ns=1;b=M/RbKBsRVkePCePcx24oRA=="               # opaque, base64
read: "s=ServerStatus"              # ns omitted -> ns=0

An object form is also accepted, with the identifier type as the key and ns optional:

read:
  ns: 2
  s: Chiller.SupplyTemp

read_write:
  s: Chiller.Setpoint   # ns omitted -> ns=0

Whitespace around ; and = is tolerated: "ns = 2 ; s = Chiller.SupplyTemp" parses the same as the compact form.

A NodeId that resolves to an ExtensionObject decodes to a dict — use json_pointer to extract a scalar from it, same as a JSON payload over HTTP or MQTT:

read: "ns=4;i=1042"
codecs:
  - json_pointer: /status/code