Skip to content

0418: System Fault Log

brucemiranda edited this page Nov 23, 2022 · 18 revisions

When a fault occurs, the controller will send an I packet:

071  I --- 01:145038 --:------ 01:145038 0418 022 000000B00606040000001714359AFFFFFF700012E296
071  I --- 01:145038 --:------ 01:145038 0418 022 000000B00AFA05000000BB1375FCFFFFFF7000000002

The controller will respond to an RQ with an RP of the corresponding log entry - this is for entry #7 (entry numbers start from 0):

095 RQ --- 18:013393 01:145038 --:------ 0418 003 000006
045 RP --- 01:145038 18:056026 --:------ 0418 022 000006B006FA05000000C413A587FFFFFF70001CB388

The controller does not send a packet when the log is cleared.

Payload decode:
Unknown [0:2] fixed value 0x00
Entry Type [2:4] 00=Fault, 0x40=Restore
Entry Number [4:6] Fault log entry number
Unknown [6:8] fixed value 0xB0
Error Type [8:10] 0x04=Battery Low, 0x06=Comms Fault, ...
zone_idx/domain_id [10:12] (0-0xA, 0xFC etc)
device class [12:14] 0x04=Actuator, 0x01=Sensor, 0x00=Controller
unknown [14:18] fixed value 0x0000
datetime [18:30]
unknown [30:38] fixed value 0xFFFF7000
device_id [38:44]

Python Code

FAULT_STATE = {
    "00": "Fault  ",
    "40": "Restore",
    "C0": "Unknown (C0)"
}
FAULT_DEVICE_CLASS = {
    "00": "Controller?",
    "01": "Sensor",
    "04": "Actuator",
    "05": "DhwSensor?"
    "06": "Remote Gateway"
}
FAULT_TYPE = {
    "04": "BatteryLow",
    "06": "CommsFault",
    "0A": "SensorError"
}

@parser_decorator
def parser_0418(payload, msg) -> Optional[dict]:  # system_fault
    def _timestamp(seqx):
        """In the controller UI: YYYY-MM-DD HH:MM"""
        _seqx = int(seqx, 16)
        return dt(
            year=(_seqx & 0b1111111 << 24) >> 24,
            month=(_seqx & 0b1111 << 36) >> 36,
            day=(_seqx & 0b11111 << 31) >> 31,
            hour=(_seqx & 0b11111 << 19) >> 19,
            minute=(_seqx & 0b111111 << 13) >> 13,
            second=(_seqx & 0b111111 << 7) >> 7,
        ).strftime("%Y-%m-%d %H:%M:%S")
    
    if payload == "000000B0000000000000000000007FFFFF7000000000":
        return {"log_idx": None}  # a null log entry, (or: payload[38:] == "000000")
    
    if msg:
        assert msg.verb in [" I", "RP"]
    assert len(payload) / 2 == 22
    
    assert payload[:2] == "00"  # unknown_0
    assert payload[2:4] in list(FAULT_STATE)
    assert int(payload[4:6], 16) <= 63
    assert payload[6:8] == "B0"  # unknown_1
    assert payload[8:10] in list(FAULT_TYPE)
    assert int(payload[10:12], 16) <= 11 or payload[10:12] in ["FA"]
    assert payload[12:14] in list(FAULT_DEVICE_CLASS)
    assert payload[14:18] == "0000"  # unknown_2
    assert payload[28:30] in ["7F", "FF"]  # last bit in dt field
    assert payload[30:38] == "FFFF7000"  # unknown_3
    
    return {
        "state": FAULT_STATE.get(payload[2:4], payload[2:4]),
        "timestamp": _timestamp(payload[18:30]),
        "fault_type": FAULT_TYPE.get(payload[8:10], payload[8:10]),
        "zone_idx" if int(payload[10:12], 16) <= 11 else "domain_id": payload[10:12],
        "device_class": FAULT_DEVICE_CLASS.get(payload[12:14], payload[12:14]),
        "device_id": dev_hex_to_id(payload[38:]),  # is "00:000001/2 for CTL?
        "log_idx": int(payload[4:6], 16),
    }

Sample C Code

nFaultYear=(nFaultDateTime & 0b1111111UL << 24) >> 24;
nFaultMonth=(nFaultDateTime & 0b1111UL << 36) >> 36;
nFaultDay=(nFaultDateTime & 0b11111UL << 31) >> 31;
nFaultHour=(nFaultDateTime & 0b11111UL << 19) >> 19;
nFaultMinute=(nFaultDateTime & 0b111111UL << 13) >> 13;
nFaultSecond=(nFaultDateTime & 0b111111UL << 7) >> 7;
Clone this wiki locally