Heatmiser V3 Protocol & Library Documentation
A definitive reference and developer guide for communicating with Heatmiser V3 network thermostats over RS485 and UH1 bridges. Replicating the official heatmiser_v3_protocol_3.9.pdf specification with enhanced clarity, exact byte offsets, and interactive tools.
1. Overview & Architecture
The heatmiserv3 Python library provides a robust, low-level and high-level interface for controlling and reading telemetry from Heatmiser thermostats communicating via the Heatmiser Version 3 RS485 protocol.
Heatmiser thermostats communicate over a 2-wire, half-duplex RS485 serial bus operating at 4800 baud, 8 data bits, no parity, 1 stop bit. A central master node—typically a Heatmiser UH1 wiring centre or Ethernet/RS485 bridge—originates all bus sessions and forwards commands to individual thermostats (slaves) addressed [1, 32].
In modern home automation setups, the UH1 or RS485 converter connects either via a direct local serial device (e.g. /dev/ttyACM0) or a TCP socket server (e.g. socket://192.168.1.57:102). The heatmiserv3 library establishes a connection to this interface via HeatmiserUH1 and coordinates session arbitration, frame formatting, CRC calculation, and response parsing.
2. Protocol Capability Audit: PDF v3.9 vs. heatmiserv3 Library
Below is a comprehensive audit assessing every chapter and feature described in the 45-page heatmiser_v3_protocol_3.9.pdf against the current library implementation:
| PDF Chapter | Protocol Capability / Feature | Status in Library | Implementation Details & Notes |
|---|---|---|---|
| Sec 3.1 & 3.2 | RS485 Master Command Framing & Slave Verification | Fully Covered | _hm_form_message, _hm_form_message_crc, and _hm_verify_message_crc_uk handle framing, variable payloads, and 7-byte write confirmations. |
| Sec 3.3 | DT / DT-E / PRT / PRT-E DCB Configuration | Fully Covered | Full 0..147 DCB mapping in config.yml. High-level convenience helpers for target temp, floor max, sensor selection (modes 0–4), differential, temperature format, keypad lock, on/off state, holiday hours, temp hold, and RTC. |
| Sec 3.4 | PRT-HW (Programmable Thermostat + Hot Water) | Fully Covered | Full 0..292 DCB mapping in config.yml. Dedicated PRTHWThermostat class providing hot water boost (addr 41), hot water state (index 36), heating state (index 35), and sensor selection enforcement (Mode 0 built-in only). |
| Sec 3.5 | TM1 Timer & Countdown Mode Thermostat | Fully Covered | 100% covered via TM1Thermostat and validated with comprehensive automated test suite. Includes On/Off, Key lock, Holiday, Countdown (0–1800m), Override (0/1/2), RTC, and 5/2 & 7-day schedules. |
| Sec 3.6 | HC-EN V3 (Heating & Cooling with Dew Point / RH) | Partially Covered | Full DCB mapping registered in config.yml. Library can read DCB and map labels. Dedicated heating/cooling setters (dew point, cooling limit) are not yet wrapped in a specialized class. |
| Sec 3.7 & 3.8 | OTS (Outside Temp Sensor) & HTS (Humidity Sensor) | Not Covered | PDF defines read-only DCB for OTS (addr 81) and HTS (addrs 41–72). The library does not currently provide standalone sensor reader classes. |
| Sec 4 | Hot Water & Timer Boost via RS485 | Fully Covered | Fully supported via PRTHWThermostat.set_hot_water_boost() (addr 41), TM1Thermostat.set_countdown_timer() (addr 26), and HeatmiserThermostat.set_temp_hold() (addr 26). |
| Sec 5 & 6 | Wi-Fi Direct Thermostat Protocol (Port 8068 + PIN) | Not Covered | PDF describes single-zone Wi-Fi thermostats accepting 0x93 frames with 4-byte PINs over port 8068. The library currently targets RS485 / UH1 bridges. |
| Sec 7 | Multi-Link / Netmonitor (NM) Hub Protocol | Not Covered | PDF specifies multi-zone aggregator frames (0x98, 0x99, 0xA8) and history logging. The library focuses on direct thermostat communication. |
| Sec 8 & App A | CRC-16 CCITT (0xFFFF) Lookup Table Algorithm | Fully Covered | CRC16 implements the exact nibble-swapping bitwise lookup algorithm from the Heatmiser C reference code. |
3. Supported Thermostats & Model Identification
Every Heatmiser thermostat returns its Model ID at DCB Index 4 during a full DCB read:
| Model Name | Model ID (Dec / Hex) | Description & Heating Modes | DCB Lengths |
|---|---|---|---|
| DT | 0 (0x00) | Digital Thermostat, non-programmable, room heating only. | 36 bytes |
| DT-E | 1 (0x01) | Digital Thermostat with remote/floor sensor for electrical underfloor heating. | 36 bytes |
| PRT | 2 (0x02) | Programmable Room Thermostat, 4 comfort levels per day. | 64 bytes (5/2 mode) / 148 bytes (7-day mode) |
| PRT-E | 3 (0x03) | Programmable Room Thermostat with electrical floor sensor probe. | 64 bytes (5/2 mode) / 148 bytes (7-day mode) |
| PRT-HW | 4 (0x04) | Programmable Room Thermostat with independent Hot Water time clock channel. | 72 bytes (5/2 mode) / 293 bytes (7-day mode) |
| TM1 | 5 (0x05) | Time Clock & Countdown Timer (Heating timer or hot water). | 19 bytes (countdown) / 51 bytes (5/2) / 163 bytes (7-day) |
| HC-EN | 7 (0x07) | Heating & Cooling Thermostat with Relative Humidity and Dew Point sensors. | 68 bytes (5/2 mode) / 152 bytes (7-day mode) |
| OTS / HTS | Sensors | Outside Temperature Sensor (addr 81) & Humidity Sensor (addrs 41–72). | 11 bytes / 13 bytes |
4. How the Library Works
4.1 Master Command Frame Structure
Whenever a command is issued by the library, a 10-byte (read) or (10 + n)-byte (write) bytecode frame is assembled:
| Byte # | Field Name | Value / Expression | Description |
|---|---|---|---|
| 0 | Destination Address | 1 – 32 (or 0xFF) | Thermostat slave ID. 0xFF (255) is broadcast. |
| 1 | Frame Length | 10 (Read) or 10 + n (Write) | Total length of this frame including the 2-byte CRC. |
| 2 | Source Address | 0x81 (129) | Master controller address (typically 0x81). |
| 3 | Function Code | 0 = Read, 1 = Write | 0x00 to read from DCB, 0x01 to write parameters. |
| 4 – 5 | Start DCB Address | start & 0xFF, (start >> 8) & 0xFF | 16-bit parameter starting address in Little-Endian format. |
| 6 – 7 | Read / Write Length | len & 0xFF, (len >> 8) & 0xFF | Number of bytes. 0xFFFF requests the full DCB for read. |
| 8 .. (n+7) | Payload Data | n bytes | The configuration bytes (omitted on read frames). |
| n+8 .. n+9 | 16-bit CRC | crc_low, crc_high | CCITT-16 checksum computed across bytes 0 to (n+7). |
4.3 CRC-16 Checksum Algorithm (CCITT 0xFFFF)
The Heatmiser V3 protocol uses a 16-bit CRC algorithm (polynomial 0xA001, initial register state 0xFFFF) calculated across all frame bytes preceding the checksum. The calculated checksum is transmitted in Little-Endian format as two bytes: [Low Byte, High Byte].
In this repository, the CRC algorithm is implemented in heatmiserv3/heatmiser.py (lines 26–96) within the CRC16 class.
# heatmiserv3/heatmiser.py lines 26-96
class CRC16:
"""This is the CRC hashing mechanism used by the V3 protocol."""
LookupHigh = [
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1,
]
LookupLow = [
0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7,
0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF,
]
def __init__(self):
self.high = 0xFF
self.low = 0xFF
def extract_bits(self, val):
"""Extracts 4 bits, XORs message data, and does table lookups."""
thisval = (self.high >> 4) ^ val
self.high = ((self.high << 4) | (self.low >> 4)) & 0xFF
self.low = (self.low << 4) & 0xFF
self.high = (self.high ^ self.LookupHigh[thisval]) & 0xFF
self.low = (self.low ^ self.LookupLow[thisval]) & 0xFF
def update(self, val):
"""Updates the CRC value using bitwise operations."""
self.extract_bits(val >> 4) # High nibble first
self.extract_bits(val & 0x0F) # Low nibble
def run(self, message):
"""Calculates a CRC-16 checksum, returning [low, high]."""
for value in message:
self.update(value)
return [self.low, self.high]
/* Heatmiser V3 Protocol Specification - Appendix A */
static const unsigned char CRC16_LookupHigh[16] = {
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1
};
static const unsigned char CRC16_LookupLow[16] = {
0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7,
0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF
};
static unsigned char CRC16_High = 0xFF;
static unsigned char CRC16_Low = 0xFF;
void CRC16_Init(void) {
CRC16_High = 0xFF;
CRC16_Low = 0xFF;
}
void CRC16_Update4Bits(unsigned char val) {
unsigned char t = (CRC16_High >> 4) ^ val;
CRC16_High = (unsigned char)((CRC16_High << 4) | (CRC16_Low >> 4));
CRC16_Low = (unsigned char)(CRC16_Low << 4);
CRC16_High ^= CRC16_LookupHigh[t];
CRC16_Low ^= CRC16_LookupLow[t];
}
void CRC16_Update(unsigned char val) {
CRC16_Update4Bits(val >> 4); /* High nibble first */
CRC16_Update4Bits(val & 0x0F); /* Low nibble */
}
void CRC16_Calculate(const unsigned char *data, size_t len, unsigned char *out_low, unsigned char *out_high) {
CRC16_Init();
for (size_t i = 0; i < len; ++i) {
CRC16_Update(data[i]);
}
*out_low = CRC16_Low;
*out_high = CRC16_High;
}
const CRC16_LOOKUP_HIGH = [
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1
];
const CRC16_LOOKUP_LOW = [
0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7,
0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF
];
export function calculateCRC16(data: Uint8Array | number[]): [number, number] {
let high = 0xFF;
let low = 0xFF;
function update4Bits(val: number) {
const t = ((high >> 4) ^ val) & 0x0F;
high = (((high << 4) | (low >> 4)) ^ CRC16_LOOKUP_HIGH[t]) & 0xFF;
low = ((low << 4) ^ CRC16_LOOKUP_LOW[t]) & 0xFF;
}
for (let i = 0; i < data.length; i++) {
const byte = data[i];
update4Bits((byte >> 4) & 0x0F); // High nibble first
update4Bits(byte & 0x0F); // Low nibble
}
return [low, high]; // Returns [Low Byte, High Byte]
}
const CRC16_LOOKUP_HIGH: [u8; 16] = [
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1,
];
const CRC16_LOOKUP_LOW: [u8; 16] = [
0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7,
0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF,
];
pub struct HeatmiserCrc16 {
high: u8,
low: u8,
}
impl HeatmiserCrc16 {
pub fn new() -> Self {
Self { high: 0xFF, low: 0xFF }
}
fn update_4bits(&mut self, val: u8) {
let t = ((self.high >> 4) ^ val) as usize;
self.high = (self.high << 4) | (self.low >> 4);
self.low <<= 4;
self.high ^= CRC16_LOOKUP_HIGH[t];
self.low ^= CRC16_LOOKUP_LOW[t];
}
pub fn update(&mut self, val: u8) {
self.update_4bits(val >> 4);
self.update_4bits(val & 0x0F);
}
pub fn calculate(data: &[u8]) -> [u8; 2] {
let mut crc = Self::new();
for &b in data {
crc.update(b);
}
[crc.low, crc.high] // [Low Byte, High Byte]
}
}
package heatmiserv3
var (
crcLookupHigh = [16]byte{
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1,
}
crcLookupLow = [16]byte{
0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7,
0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF,
}
)
// CalculateCRC16 computes the Heatmiser V3 CRC-16 checksum returning (low, high).
func CalculateCRC16(data []byte) (byte, byte) {
high := byte(0xFF)
low := byte(0xFF)
update4Bits := func(val byte) {
t := (high >> 4) ^ val
high = (high << 4) | (low >> 4)
low = low << 4
high ^= crcLookupHigh[t]
low ^= crcLookupLow[t]
}
for _, b := range data {
update4Bits(b >> 4) // High nibble first
update4Bits(b & 0x0F) // Low nibble
}
return low, high
}
Enter arbitrary hex or decimal bytes (space-separated, comma-separated, or 0x-prefixed) or choose a protocol preset. The algorithm executes live in your browser and verifies parity against the C Reference and Python implementations.
🔍 View Detailed Step-by-Step Nibble Trace
5. Thermostat DCB Register Tables
The tables below replicate the exact register tables from the official Heatmiser V3.9 protocol specification, augmented with explicit Access Rights, Endianness Rules, and Value Scaling.
DT / DT-E / PRT / PRT-E Register Table (Section 3.3)
DT/DT-E stops at index 35 (36 bytes). PRT in 5/2 mode is 64 bytes; in 7-day mode is 148 bytes.
| Unique Addr | DCB Index | Parameter Name | Access | Format & Endianness | Default | Protocol Description & Notes |
|---|---|---|---|---|---|---|
| 0 | 0–1 | DCB Length | R | 2B Big-Endian | 64 / 148 | Total byte length of the DCB. |
| 2 | 2 | Vendor ID | R | 1 Byte | 0 | 00 = Heatmiser, 01 = OEM. |
| 3 | 3 | Version & Status | R | 1 Byte (Bitmask) | 10 | Bits 0–6 = Version (10 = v1.0). Bit 7 = Floor limit state (1 = in floor limit). |
| 4 | 4 | Model ID | R | 1 Byte | 2 | 00 = DT, 01 = DT-E, 02 = PRT, 03 = PRT-E. |
| 5 | 5 | Temperature Format | R/W | 1 Byte | 0 | 00 = °C, 01 = °F. |
| 6 | 6 | Switch Differential | R/W | 1 Byte | 1 | Hysteresis between switching: [1, 3] °C. |
| 7 | 7 | Frost Protect Mode | R | 1 Byte | 1 | Frost protection state: 0 = Disabled, 1 = Enabled. |
| 8 | 8–9 | Calibration Offset | R/W | 2B Big-Endian | 0 | Temperature calibration offset. |
| 10 | 10 | Output Delay | R/W | 1 Byte | 0 | Switching delay in minutes: [0, 15]. |
| 11 | 11 | Thermostat Address | R | 1 Byte | 1 | Comms network address: [1, 32]. |
| 12 | 12 | Key Limit | R/W | 1 Byte | 0 | Up/down temperature adjustment limit: [0, 10] °C. |
| 13 | 13 | Sensor Selection | R/W | 1 Byte | 0 | 00=Built-in, 01=Remote air, 02=Floor, 03=Built-in+Floor, 04=Remote+Floor. |
| 14 | 14 | Optimum Start | R/W | 1 Byte | 0 | Pre-heat anticipation: [0, 3] hours. |
| 15 | 15 | Rate of Change | R/W | 1 Byte | 20 | Time in minutes to raise temperature by 1°C. |
| 16 | 16 | Program Mode | R/W | 1 Byte | 0 | 00 = 5/2 Day mode, 01 = 7-Day mode. |
| 17 | 17 | Frost Protect Temp | R/W | 1 Byte | 12 | Frost target temperature: [7, 17] °C. |
| 18 | 18 | Set Room Temp | R/W | 1 Byte | 16 | Target temperature setpoint: [5, 35] °C. |
| 19 | 19 | Floor Max Limit | R/W | 1 Byte | 28 | Maximum floor safety temperature: [20, 45] °C. |
| 20 | 20 | Floor Limit Enable | R/W | 1 Byte | 1 | 00 = Disabled, 01 = Enabled. |
| 21 | 21 | On / Off State | R/W | 1 Byte | 0 | 00 = Off, 01 = On. |
| 22 | 22 | Key Lock | R/W | 1 Byte | 0 | 00 = Unlocked, 01 = Locked. |
| 23 | 23 | Run Mode | R/W | 1 Byte | 0 | 00 = Heating (Normal), 01 = Frost Protection. |
| 24 | 24–25 | Holiday Hours | R/W | 2B Read:BE / Write:LE | 0 | Caution: Read is High byte ahead; Write is Low byte ahead! |
| 32 | 26–27 | Temp Hold Mins | R/W | 2B Read:BE / Write:LE | 0 | Hold override duration. Write 0 to cancel. |
| 34 | 28–29 | Remote Air Temp | R | 2B Big-Endian | 0xFFFF | Actual temp is value / 10. Returns 0xFFFF if no sensor. |
| 36 | 30–31 | Floor Temp | R | 2B Big-Endian | 0xFFFF | Actual temp is value / 10. Returns 0xFFFF if no sensor. |
| 38 | 32–33 | Built-In Air Temp | R | 2B Big-Endian | — | Actual temp is value / 10. |
| 40 | 34 | Error Code | R | 1 Byte | 0 | E0 = Built-in error, E1 = Floor sensor error, E2 = Remote air error. |
| 41 | 35 | Heating State | R | 1 Byte | 0 | 00 = No heating active, 01 = Heating currently calling. |
| 43 | 36–39 | Clock (RTC) | R/W | 4 Bytes | — | [Week (1..7), Hour (0..23), Min (0..59), Sec (0..59)]. |
| 47 | 40–51 | Weekday Comfort | R/W | 12 Bytes | — | 4 periods * 3 bytes (Hour, Min, Temp). Temp = 24 cancels period. |
| 59 | 52–63 | Weekend Comfort | R/W | 12 Bytes | — | 4 periods * 3 bytes (Hour, Min, Temp). |
| 103.. | 64–147 | 7-Day Comfort | R/W | 84 Bytes | — | Mon–Sun comfort levels (12 bytes per day). Unique addrs 103, 115, 127, 139, 151, 163, 175. |
TM1 Timer & Countdown Register Table (Section 3.5)
Countdown mode finishes at index 18 (19 bytes); 5/2 mode is 51 bytes; 7-day mode is 163 bytes.
| Unique Addr | DCB Index | Parameter Name | Access | Format & Endianness | Default | Protocol Description & Notes |
|---|---|---|---|---|---|---|
| 0 | 0–1 | DCB Length | R | 2B Big-Endian | 19 / 51 / 163 | Total length of TM1 DCB based on program mode. |
| 2 | 2 | Vendor ID | R | 1 Byte | 0 | 00 = Heatmiser, 01 = OEM. |
| 3 | 3 | Version | R | 1 Byte | 10 | 10 = VER 1.0. |
| 4 | 4 | Model ID | R | 1 Byte | 5 | TM1 = 0x05. |
| 11 | 5 | Address | R | 1 Byte | 1 | Slave address [1, 32]. |
| 16 | 6 | Program Mode | R/W | 1 Byte | 0 | 00 = 5/2 mode, 01 = 7-day mode, 02 = Countdown timer mode. |
| — | 7 | Reserved | R | 1 Byte | 0 | Not currently used. |
| 21 | 8 | On / Off State | R/W | 1 Byte | 0 | 00 = Off, 01 = On. |
| 22 | 9 | Key Lock | R/W | 1 Byte | 0 | 00 = Key Unlock, 01 = Key Lock. |
| 24 | 10–11 | Holiday Hours | R/W | 2B Read:BE / Write:LE | 0 | Write 0 to cancel holiday. Low byte first when writing. |
| 26 | 12–13 | Countdown Minutes | R/W | 2B Read:BE / Write:LE | 0 | Countdown duration: [0, 1800] minutes. Low byte first when writing. |
| 42 | 14 | Timer State / Override | R/W | 1 Byte | 0 | Read: 00=Off, 01=On.Write: 00=Auto (run program), 01=Override On, 02=Override Off. |
| 43 | 15–18 | Clock (RTC) | R/W | 4 Bytes | — | [Week (1..7), Hour (0..23), Min (0..59), Sec (0..59)]. |
| 71 | 19–34 | Weekday Switching | R/W | 16 Bytes | — | 4 on/off switching pairs: Time 1–4 On H/M, Off H/M. Hour 24 = Cancelled. |
| 87 | 35–50 | Weekend Switching | R/W | 16 Bytes | — | 4 on/off switching pairs: Time 1–4 On H/M, Off H/M. |
| 187..283 | 51–162 | 7-Day Switching | R/W | 112 Bytes | — | Monday (187), Tuesday (203), Wednesday (219), Thursday (235), Friday (251), Saturday (267), Sunday (283). 16 bytes each. |
PRT-HW (Heating & Hot Water) Register Table (Section 3.4)
Model ID = 4 (0x04). Contains both 12-byte heating comfort levels and 16-byte hot water timer switching periods.
| Unique Addr | DCB Index | Parameter Name | Access | Format | Default | Protocol Description & Notes |
|---|---|---|---|---|---|---|
| 0..35 | 0..35 | Standard Telemetry | R | Var | — | Same registers as PRT (DCB len, Model 04, temp format, sensor, current temp). |
| 42 | 36 | Current Hot Water State | R/W | 1 Byte | 0 | Read: 00=Off, 01=On.Write: 00=Auto program, 01=Override ON, 02=Override OFF. |
| 43 | 37–40 | Clock (RTC) | R/W | 4 Bytes | — | Week 1..7, Hour 0..23, Min 0..59, Sec 0..59. |
| 47 | 41–52 | Weekday Heating Comfort | R/W | 12 Bytes | — | 4 periods * 3 bytes (Hour, Min, Temp). Hour 24 cancels. |
| 59 | 53–64 | Weekend Heating Comfort | R/W | 12 Bytes | — | 4 periods * 3 bytes (Hour, Min, Temp). |
| 71 | 65–80 | Weekday Hot Water Timer | R/W | 16 Bytes | — | 4 on/off pairs (Time 1..4 On H/M, Off H/M). Hour 24 cancels. |
| 87 | 81–96 | Weekend Hot Water Timer | R/W | 16 Bytes | — | 4 on/off pairs (Time 1..4 On H/M, Off H/M). |
| 103..175 | 97–180 | 7-Day Heating Comfort | R/W | 84 Bytes | — | 7 days * 12 bytes = 84 bytes. |
| 187..283 | 181–292 | 7-Day Hot Water Timer | R/W | 112 Bytes | — | 7 days * 16 bytes = 112 bytes. |
HC-EN V3 (Heating & Cooling) Register Table (Section 3.6)
Model ID = 7 (0x07). 68 bytes in 5/2 mode, 152 bytes in 7-day mode.
| Unique Addr | DCB Index | Parameter Name | Access | Format | Default | Protocol Description & Notes |
|---|---|---|---|---|---|---|
| 4 | 4 | Model ID | R | 1 Byte | 7 | HC-EN = 0x07. |
| 29 | 13 | Cooling Enable | R/W | 1 Byte | 1 | 00 = Disabled, 01 = Enabled. |
| — | 14 | Dew Point Differential | R/W | 1 Byte | 3 | Range: [0, 10] °C. |
| — | 15 | Dew Point Time Limit | R/W | 1 Byte | 0 | Minutes: [0, 60]. |
| 27 | 24 | Cooling Setpoint | R/W | 1 Byte | 24 | Cooling setpoint temperature. |
| 23 | 28 | Run Mode | R/W | 1 Byte | 0 | 00 = Heating, 01 = Frost, 02 = Cooling. |
| 30 | 36 | Relative Humidity | R | 1 Byte | — | Relative Humidity RH% reading from built-in/remote sensor. |
| 28 | 37 | Dew Point Temp | R | 1 Byte | — | Calculated dew point temperature. |
| 41 | 38 | Output State Bitmask | R | 1 Byte (Bitmask) | — | bit0=Heat On, bit1=Heat Floor Limit, bit4=Cool On, bit5=Cool Floor Limit, bit6=Dew Point Reached, bit7=Dew Point Exceeded. |
Wi-Fi System Variants (Section 6)
Single-zone Wi-Fi thermostats communicate over direct TCP sockets on port 8068 with PIN authentication.
| Unique Addr | DCB Index | Parameter Name | Access | Format | Description |
|---|---|---|---|---|---|
| 31 | 7 | Away Mode | R/W | 1 Byte | 00 = Normal mode, 01 = Away mode. |
| 24 | 10–14 | Holiday Return Date | R/W | 5 Bytes | [Year, Month, Day, Hour, Min]. Write single byte 00 to cancel. |
| 24 | 15 | Holiday Status | R | 1 Byte | 00 = Not in holiday, 01 = In holiday mode. |
6. Visual Test Harness: Real-World DCB Dynamics
Writing register values to a Heatmiser thermostat over RS485 does not merely alter memory bytes in the Device Control Block (DCB)—it orchestrates physical relays, drives safety interlocks, updates LCD annunciators, and modulates HVAC heating loops in the real world.
In Split View (desktop default), the Virtual Thermostat Digital Twin is rendered adjacent to this guide on the right side of the screen. You can interact with tactile faceplate buttons, adjust physical environment temperatures, observe relay contacts opening and closing, and test safety cutouts in real time.
6.1 Heating Relay Switching & Differential Hysteresis
The central heating relay (connected across Terminals 1 & 2) switches 230V mains or dry-contact signals to central heating motorized zone valves and boiler call-for-heat terminals:
- Call for Heat: When the ambient temperature drops below
Target Setpoint - Switching Differential(DCB Addr 18 and Addr 27), the microcontroller fires Relay 1/2. The physical circuit closes, the boiler fires, and the 🔥 flame icon illuminates. - Setpoint Reached: When the ambient temperature reaches or exceeds the target setpoint, Relay 1/2 opens. The boiler shuts down and the flame icon extinguishes.
- Hysteresis Protection: The 1°C, 2°C, or 3°C differential (DCB Addr 27) prevents rapid boiler short-cycling, extending compressor and burner life.
6.2 Underfloor Heating Overheat Thermal Safety Interlock
When configured for Mode 3 (Built-in Air + Floor Limit Sensor), the thermostat regulates room comfort using air temperature while continuously polling the NTC floor probe embedded in the floor screed:
- Safety Cutout Threshold: If the floor temperature reaches or exceeds
Floor Max Limit(DCB Addr 29, typically 28°C–35°C), the firmware trips an Emergency Thermal Safety Cutout. - Forced Relay Disengagement: The heating relay is forcibly opened immediately, even if the room is cold and demanding heat.
- Real-World Protection: This interlock protects engineered wood flooring from buckling, adhesive breakdown, and cement screed cracking. The LCD flashes
FLR HIor a warning badge.
6.3 Domestic Hot Water Dual-Channel Control (PRT-HW)
The PRT-HW model features an independent secondary relay across Terminals 3 & 4 dedicated to domestic hot water (DHW) cylinder valves:
- Manual Hot Water Boost: Writing
01to DCB Addr 41 (Unique Address 41) closes Terminals 3 & 4, energizing the cylinder valve and illuminating the 🚿 tap icon. - Holiday Suppression: When Holiday Mode is engaged, hot water heating is suppressed for the holiday duration to conserve energy while the property is unoccupied.
6.4 TM1 Time Clock & Countdown Timer Operations
The TM1 operates in three distinct programming modes (DCB Addr 16: 5/2 day, 7 day, or Countdown Timer):
- Countdown Boost: Setting Countdown Minutes (DCB Addr 26–27, transmitted Little-Endian) energizes the relay and displays the remaining minutes alongside the ⏱️ timer icon. When the counter expires (0 minutes), the relay disengages automatically.
- Timer Override: DCB Addr 42 allows software overrides (
0= Auto Program,1= Constant ON,2= Constant OFF).
6.5 Commercial Anti-Tamper Keypad Lockout
In public buildings, rental properties, and commercial installations, keypad locking prevents unauthorized temperature manipulation:
- Lock Level 1: Full keypad lock (DCB Addr 22 =
1). All tactile front panel buttons are disabled, displaying a padlock 🔒 icon. - Tamper Alarm: Attempting to press buttons on the physical faceplate causes the display to flash a red
LOCtamper warning and prevents changes. - Lock Level 2: Partial lock allowing setpoint adjustment within configured temperature limits.
6.6 Holiday Mode Setback vs. Sub-Zero Frost Protection
Energy conservation and freeze prevention are governed by two safety state machines:
- Holiday Mode: Setting holiday hours (DCB Addr 24–25) suspends normal scheduled comfort levels and locks the target to the Frost Protect Temperature (DCB Addr 17, default 12°C). The 🧳 suitcase icon is displayed.
- Standby Freeze Protection: Even when the user turns the thermostat completely OFF / Standby (DCB Addr 21 =
0), an emergency freeze-protection monitor remains active. If the room drops $\le 12^\circ\text{C}$, Relay 1/2 closes and the ❄️ snowflake icon illuminates to prevent burst water pipes.
6.7 One-Click Scenario Demonstration Presets
Click any scenario below to automatically configure the simulated environment, mutate the thermostat DCB registers, and observe real-world hardware actuation:
7. Interactive Bytecode Frame Inspector
Use the tool below to generate, inspect, and verify exact RS485 bytecode packets according to Section 3.1:
Explanation will appear here.
8. Protocol Gotchas & Clarifications
The official Heatmiser specification contains subtleties that frequently cause integration bugs. Here are critical clarifications:
Page 4 states: "For those with 2 bytes parameters, when reading, high 8 bits are sent first while for writing the low 8 bit should be sent first".
For Holiday Hours (Address 24) and Countdown Minutes (Address 26):
• When reading from DCB: High byte is at index N, low byte is at index N+1 (Big-Endian).
• When writing command frames: Low byte is sent first, followed by high byte (Little-Endian).
In the PDF tables, there are two address columns:
• Unique Address: The network address used in write command frames (Bytes 4–5).
• Index in DCB: The zero-indexed byte position in the received read reply payload. These often differ! For instance, on TM1, Program Mode is Unique Address 16, but lives at DCB Index 6.
In both heating comfort levels and timer switching periods, an Hour value of 24 (0x18) denotes that this switching interval is cancelled/disabled, not midnight (which is Hour 00).
Remote air temperature, floor temperature, and built-in temperature values are returned as 16-bit integers multiplied by 10. A value of 215 represents 21.5°C. If a sensor is disconnected or absent, the value returns 0xFFFF (65535).
9. Python API Specification & Developer Guide
The heatmiserv3 package provides an object-oriented Python interface adhering strictly to the Heatmiser V3 RS485 Protocol 3.9 specification. All convenience methods generate validated CCITT-16 checksummed frames, enforce hardware boundaries, and handle bi-directional telemetry conversion.
Connection manager and serial transport bridge for Heatmiser UH1 wiring centres and RS485 bus nodes. Supports direct serial ports (e.g. /dev/ttyUSB0, COM3) or TCP-to-serial network bridges (e.g. Moxa, USR-TCP232). Manages connection retry and automatic socket re-opening on bus disruptions.
ipaddress(str, optional) – IP address of TCP-to-RS485 bridge.port(str | int, optional) – TCP port number of the bridge (e.g."102"or8000).dev(str, optional) – Local POSIX serial device path or Windows COM port name.
Primary model class representing Heatmiser digital heating thermostats: DT, DT-E, PRT, and PRT-E. Automatically retrieves and parses the initial DCB telemetry on initialization.
True upon successful write dispatch, or False if an argument is invalid or out of specification range.
0 or False puts unit in Off / Frost Setback state. 1 or True restores normal heating operation.
0 (Off / Frost setback) or 1 (On / Normal).
1 / True) or unlocks (0 / False) front-panel physical buttons to prevent manual tampering.
0 (Unlocked) or 1 (Locked).
0, "C", "Celsius" for °C; or 1, "F", "Fahrenheit" for °F.
0..4 or descriptive strings:
0or"built in": Built-in air sensor1or"remote air": Remote air sensor probe2or"floor": Floor sensor probe only3or"built-in+floor": Built-in air with floor overheat protection limit4or"remote+floor": Remote air with floor overheat protection limit
"Built in air sensor", "Remote + floor").
0..65535). Automatically formats as 2-byte little-endian payload on write. Writing 0 cancels holiday mode.
1..1800 minutes. If temperature is specified (5–35°C), simultaneously writes the temporary comfort setpoint. Writing 0 minutes cancels active hold.
week is 1–7 (1=Monday .. 7=Sunday). Hours 0–23, Minutes 0–59, Seconds 0–59.
{"week": int, "hour": int, "min": int, "sec": int}.
Specialized class for the PRT-HW (Programmable Thermostat with Hot Water control, Model ID 0x04). Adds dedicated hot water boost management and enforces hardware constraints.
set_sensor_selection() with external or floor modes is safely refused and returns False.
1 or True engages Boost On immediately. 0 or False returns channel to programmed schedule (Auto).
0 (Off) or 1 (On).
0 (Off) or 1 (Heating active).
Model class for the TM1 Time Clock & Countdown Programmer (Model ID 0x05). Provides timer channel overrides, countdown timing (0–1800m), and 5/2 & 7-day multi-period switching schedules.
0 = Auto (follows schedule), 1 = Override ON, 2 = Override OFF.
[T1_On_H, T1_On_M, T1_Off_H, T1_Off_M, ...]. Cancelled periods are designated by setting Hour to 24.
Python Automation Examples
from heatmiserv3 import heatmiser, connection
# Connect via serial bridge or direct RS485 adapter
uh1 = connection.HeatmiserUH1(ipaddress="192.168.1.57", port="102")
# Instantiate PRT programmable thermostat at address 1
prt = heatmiser.HeatmiserThermostat(address=1, model="prt", uh1=uh1)
# Configure device operating mode & regulation
prt.set_on_off(1) # Turn heating system ON
prt.set_temperature_format("C") # Use Celsius
prt.set_differential(1) # 1°C switching differential
prt.set_target_temp(21) # Target comfort: 21°C
prt.set_floormax_temp(28) # Floor safety cutout: 28°C
# Switch to Remote Air + Floor sensing mode (Mode 4)
prt.set_sensor_selection(4)
# Set 3-hour temporary comfort boost to 23°C
prt.set_temp_hold(minutes=180, temperature=23)
from heatmiserv3 import heatmiser, connection
uh1 = connection.HeatmiserUH1(dev="/dev/ttyUSB0")
# Instantiate PRT-HW thermostat at address 2
prt_hw = heatmiser.PRTHWThermostat(address=2, uh1=uh1)
# Read dual channel state
print("Heating Active:", prt_hw.get_heating_state()) # 1 or 0
print("Hot Water Active:", prt_hw.get_hot_water_state()) # 1 or 0
# Engage hot water boost
prt_hw.set_hot_water_boost(1)
# Reset boost back to scheduled auto mode
prt_hw.set_hot_water_boost(0)
from heatmiserv3 import heatmiser, connection
uh1 = connection.HeatmiserUH1(ipaddress="192.168.1.57", port="102")
tm1 = heatmiser.TM1Thermostat(address=3, uh1=uh1)
# 2-hour countdown timer
tm1.set_countdown_timer(120)
# Set 7-day vacation setback (168 hours)
tm1.set_holiday_hours(168)
# Configure 5/2 Weekday switching schedule (4 pairs: On H/M, Off H/M)
weekday_schedule = [
6, 30, 8, 30, # Time 1: 06:30 On, 08:30 Off
17, 0, 22, 30, # Time 2: 17:00 On, 22:30 Off
24, 0, 24, 0, # Time 3: Cancelled (Hour 24)
24, 0, 24, 0, # Time 4: Cancelled (Hour 24)
]
tm1.set_weekday_schedule(weekday_schedule)