Skip to content

Packet Trace: Alarms and Warnings

This document describes how to query and parse alarm/warning status from the ALPHA HWR pump.

Overview

The ALPHA HWR reports active alarms and warnings through Object 88 using the Active Query Response format. Unlike other telemetry that streams automatically via notifications, alarm/warning status must be queried explicitly.

Query Format

Alarms and warnings use the standard Class 10 READ operation (OpSpec 0x03):

[Start] [Length] [Src] [Dest] [Class] [OpSpec] [Reg-H] [Reg-M] [Reg-L] [CRC]

Query Alarms (Object 88, Sub 0)

Register address: 0x580000

27 07 E7 F8 0A 03 58 00 00 XX XX

Breakdown: - 27: Request start byte - 07: Length (7 bytes total) - E7: Source (Client) - F8: Destination (Pump) - 0A: Class 10 - 03: OpSpec (READ) - 58 00 00: Register (Obj=88, Sub=0 for alarms) - XX XX: CRC16

Query Warnings (Object 88, Sub 11)

Register address: 0x58000B

27 07 E7 F8 0A 03 58 00 0B XX XX

Breakdown: - Same as alarms query - 58 00 0B: Register (Obj=88, Sub=11 for warnings)

Response Format

The pump responds with OpSpec 0x09 (Active Query Response), using the same format as other register-read responses (0x30, 0x2B, 0x14):

[Start] [Length] [Dest] [Src] [Class] [OpSpec] [Seq-H] [Seq-L] [ID-H] [ID-L] [Res-H] [Res-L] [DataLen] [Data...] [CRC]

No Active Alarms Response

Validated on real hardware (ESPHome implementation):

24 0D F8 E7 0A 09 00 02 3A 01 00 00 02 00 00 DC 50

Breakdown: - 24: Response start byte - 0D: Length (13 bytes) - F8: Destination (Client) - E7: Source (Pump) - 0A: Class 10 - 09: OpSpec (Active Query Response) - 00 02: Sequence number - 3A 01: ID field (depends on register queried) - 00 00: Reserved - 02: DataLen (2 bytes of data) - 00 00: Data (uint16 = 0x0000, meaning "no alarms") - DC 50: CRC16

Interpretation: The data value 0x0000 means there are no active alarms or warnings. This is the normal/healthy state.

Active Alarms Response

Example with alarm codes 42 and 7:

24 11 F8 E7 0A 09 00 03 58 00 00 00 06 00 2A 00 07 00 00 XX XX

Breakdown: - 24: Response start byte - 11: Length (17 bytes) - F8: Destination (Client) - E7: Source (Pump) - 0A: Class 10 - 09: OpSpec (Active Query Response) - 00 03: Sequence number - 58 00: ID field (Obj=88, Sub=0 for alarms) - 00 00: Reserved - 06: DataLen (6 bytes of data) - 00 2A: Alarm code 42 (uint16 big-endian) - 00 07: Alarm code 7 (uint16 big-endian) - 00 00: Terminating zero (filtered out) - XX XX: CRC16

Interpretation: This packet indicates two active alarms with codes 42 and 7.

Active Warnings Response

Same format as alarms, but ID field reflects Object 88, Sub 11:

24 0F F8 E7 0A 09 00 04 58 0B 00 00 04 00 05 00 00 XX XX

Breakdown: - 58 0B: ID field (Obj=88, Sub=11 for warnings) - 04: DataLen (4 bytes) - 00 05: Warning code 5 - 00 00: Terminating zero (filtered out)

Data Format

Payload Structure

The data section contains an array of uint16 values in big-endian format:

[Code1-H] [Code1-L] [Code2-H] [Code2-L] ... [0x00] [0x00]

Important Notes: - Each alarm/warning code is a 2-byte unsigned integer - Byte order is big-endian (high byte first) - A code value of 0x0000 means "no alarm/warning" - Zero codes should be filtered out when parsing - The array is typically terminated with a zero value

Example Parsing (Python)

import struct

def parse_alarm_response(packet: bytes) -> list[int]:
    """Parse alarm/warning codes from OpSpec 0x09 response."""
    if len(packet) < 13:
        return []

    # Extract data length at offset 12
    data_len = packet[12]

    # Parse uint16 array starting at offset 13
    codes = []
    offset = 13
    while offset + 2 <= len(packet) - 2 and offset < 13 + data_len:
        code = struct.unpack(">H", packet[offset:offset+2])[0]
        if code != 0:  # Filter out zero codes
            codes.append(code)
        offset += 2

    return codes

# Example: No alarms
packet = bytes.fromhex("24 0D F8 E7 0A 09 00 02 3A 01 00 00 02 00 00 DC 50")
print(parse_alarm_response(packet))  # Output: []

# Example: Active alarms 42 and 7
# Final two bytes are CRC placeholders for this example and are ignored
packet = bytes.fromhex("24 11 F8 E7 0A 09 00 03 58 00 00 00 06 00 2A 00 07 00 00 00 00")
print(parse_alarm_response(packet))  # Output: [42, 7]

OpSpec 0x09 Details

OpSpec Byte Structure

The OpSpec byte 0x09 can be decoded as follows:

0x09 = 0b00001001
       │││    └─┴─ Bits 0-4: Data length indicator (9)
       ││└──────── Bit 5: Reserved
       │└───────── Bit 6: Error flag (0 = success)
       └────────── Bit 7: Reserved

Active Query Response Format

OpSpec 0x09 uses the Active Query Response layout, which is shared with other register-read responses:

  • OpSpec 0x30: Motor state response
  • OpSpec 0x2B: Flow/pressure response
  • OpSpec 0x14: Temperature response
  • OpSpec 0x09: Alarm/warning response

All these OpSpecs use the 7-byte header before data:

[OpSpec] [Seq-H] [Seq-L] [ID-H] [ID-L] [Res-H] [Res-L] [DataLen] [Data...]

The key difference is that OpSpec 0x09 contains uint16 codes rather than IEEE 754 floats.

Integration Notes

Polling Frequency

Unlike streaming telemetry, alarms/warnings must be polled:

  • Recommended interval: 5-10 seconds
  • Minimum interval: 1 second (avoid overwhelming the pump)
  • On-demand: Query after pump mode changes or errors

Error Handling

  • If the pump doesn't respond, retry after 2-3 seconds
  • If you receive malformed data, log the raw packet hex for debugging
  • Empty response (DataLen=0) should be treated as "no data available"

Home Assistant Integration

Example ESPHome text sensor configuration:

text_sensor:
  - platform: ble_client
    name: "Active Alarms"
    id: active_alarms
    # Updated when alarm query response received

  - platform: ble_client
    name: "Active Warnings"
    id: active_warnings
    # Updated when warning query response received

Historical Context

Initial Confusion (Now Resolved)

Early Python implementations incorrectly expected OpSpec 0x13 for alarm responses. This was based on speculation before hardware validation.

Hardware validation (ESPHome, Feb 2025) confirmed that the pump actually responds with OpSpec 0x09, using the standard Active Query Response format.

Key Discovery

The breakthrough came from analyzing 100+ real packet captures showing consistent OpSpec 0x09 responses with data value 0x0000, which was initially misinterpreted as "not supported." In reality, 0x0000 simply means "no active alarms."

References

Validation

This documentation is based on:

  • 100+ real packet captures from ALPHA HWR pump (Feb 2025)
  • Successful ESPHome C++ implementation (tested on ESP32-C3)
  • Cross-validation between Python and ESPHome implementations
  • Protocol documentation at https://eman.github.io/alpha-hwr/reimplementation/