Skip to content

3220: OpenTherm Message

David Bonnes edited this page Feb 18, 2020 · 24 revisions

OpenTherm protocol-compliant messages are regularly exchanged between the controller and the OpenTherm Bridge (R8810A1018).

060 RQ --- 01:187666 10:138822 --:------ 3220 005 0000120000
058 RP --- 10:138822 01:187666 --:------ 3220 005 00401201EE
060 RQ --- 01:187666 10:138822 --:------ 3220 005 0080130000
058 RP --- 10:138822 01:187666 --:------ 3220 005 00C0192300

Such payloads comply with the OpenTherm protocol, and are not discussed in detail here, see instead: https://www.domoticaforum.eu/uploaded/Ard%20M/Opentherm%20Protocol%20v2-2.pdf

Payload Structure

All OpenTherm payloads are 5 bytes long; the first byte is ignored as the OpenTherm protocol specifies a 4 byte message.

segment size contents
unused [0:2] Always 0x00
msg_type [2:4] Message type, includes parity check
data_id [4:6] Message identifier
data_value [6:10] One 16-bit, or two 8-bit values

The msg_type will indicate the direction of the message (master-to-slave, or slave-to-master) and whether it is a read/write request, or a response to such. It includes a 1-bit parity check of the remaining message.

The most significant bit is the parity bit, and the next 3 bits are used for the message type. The 4 least significant bits should be 0 (this is the specification for OT v2.2, but is unknown for later versions of the protocol).

Note: in this context, the evohome controller is the master, and the OpenTherm bridge is the slave (and bridge also functions as a master to the boiler, but evohome is blind to this).

The data_id (and msg_type) will indicate how to process the data_value. Note the value field could be one 16-bit data structure, or two 8-bit data structures called HB (high byte) and LB (low byte).

The data could be signed or unsigned integers, floats, or bit masks: refer to the protocol reference for more detail. For many messages, such as Read-Data requests, the data_value segment is not used, and will be 0000.

Data IDs

From v2.2 of the OT specification, the following data IDs must be supported: 0, 1, 3, 14, 17 & 25; these messages include status, setpoint, temperature and modulation level data (note that this is the case, even if the corresponding data is not normally available for that system).

In addition, the following has been seen with the evohome OpenTherm bridge: 5, 15, 18-19, 26, 28, 56-57, 115-123, 127 (there is good evidence that other data IDs are supported). The following has also been seen, but are not included in the v2.2 specification: 113, 114 (these may be boiler-specific, or part of a later specification).

Sample Code

In Python:

def parser_3220(payload, msg) -> Optional[dict]:  # opentherm_msg
    assert len(payload) / 2 == 5
    assert payload[:2] == "00"

    # these are OpenTherm-specific assertions, first check parity bit
    assert int(payload[2:4], 16) // 0x80 == parity(int(payload[2:], 16) & 0x7FFFFFFF)

    ot_msg_type = int(payload[2:4], 16) & 0x70
    assert ot_msg_type in OPENTHERM_MSG_TYPES  # is it a known msg type

    assert int(payload[2:4], 16) & 0x0F == 0  # valid for v2.2 of the protocol

    ot_msg_id = int(payload[4:6], 16)
    assert str(ot_msg_id) in OPENTHERM_MESSAGES

    return {
        "msg_type": ot_msg_type,
        "data_id": ot_msg_id,
        "data_value": ot_msg_value(payload[6:], ot_msg_id),
        "description": OPENTHERM_MESSAGES[ot_msg_id],
    }

Note that the detail of ot_msg_value() is not included here.

Other Python Code

OPENTHERM_MSG_TYPES = {
      0: "Read-Data",       # 0b.000....
     16: "Write-Data",      # 0b.001....
     32: "Invalid-Data",    # 0b.010....
     48: "-reserved-",      # 0b.011....
     64: "Read-Ack",        # 0b.100....
     80: "Write-Ack",       # 0b.101....
     96: "Data-Invalid",    # 0b.110....
    112: "Unknown-DataId",  # 0b.111....
}

def parity(x: int) -> int:
    shiftamount = 1
    while x >> shiftamount:
        x ^= x >> shiftamount
        shiftamount <<= 1
    return x & 1

Related Packets

Clone this wiki locally