// Copyright (C) 2026 Fredrik Öhrström (gpl-3.0-or-later)
driver {
name = qheatv2
meter_type = HeatMeter
default_fields = name,id,status,total_kwh,timestamp
manufacturer = Qundis
detect {
mvt = QDS,23,04
mvt = QDS,23,37
mvt = QDS,3e,04
mvt = QDS,3e,37
mvt = QDS,46,04
mvt = QDS,46,37
mvt = QDS,47,04
mvt = QDS,47,37
mvt = QDS,48,04
mvt = QDS,48,37
mvt = LSE,23,04
mvt = LSE,23,37
mvt = LSE,3e,04
mvt = LSE,3e,37
mvt = LSE,46,04
mvt = LSE,46,37
mvt = LSE,47,04
mvt = LSE,47,37
mvt = LSE,48,04
mvt = LSE,48,37
}
library {
use = meter_datetime
use = enhanced_id
}
fields {
field {
name = status
quantity = Text
info = 'Status and error flags'
attributes = STATUS,INCLUDE_TPL_STATUS
match {
measurement_type = Instantaneous
vif_range = ErrorFlags
}
lookup {
/* !!! The status bits are uncertain !!!
The documentation links to some intranet site, Qundis Error Codes Specification v1.7
at https://base/svn/sys/Meter/Gen/G5-5/PUBL/Gen55_SysSpec_Error-Codes_EN_v1.6_any.pdf
Here is a table that might apply:
https://www.manualslib.com/manual/2046543/Qundis-Q-Heat-5-5-Us.html?page=5 */
name = ERROR_FLAGS
map_type = BitToString
mask_bits = 0xffff
default_message = OK
map {
name = NO_FLOW
// F0
value = 0x0001
test = Set
}
map {
name = SUPPLY_SENSOR_INTERRUPTED
// F1
value = 0x0002
test = Set
}
map {
name = RETURN_SENSOR_INTERRUPTED
// F2
value = 0x0004
test = Set
}
map {
name = TEMPERATURE_ELECTRONICS_ERROR
// F3
value = 0x0008
test = Set
}
map {
name = BATTERY_VOLTAGE_ERROR
// F4
value = 0x0010
test = Set
}
map {
name = SHORT_CIRCUIT_SUPPLY_SENSOR
// F5
value = 0x0020
test = Set
}
map {
name = SHORT_CIRCUIT_RETURN_SENSOR
// F6
value = 0x0040
test = Set
}
map {
name = MEMORY_ERROR
// F7
value = 0x0080
test = Set
}
map {
name = SABOTAGE
// F8 - F1,2,3,5,6 longer than 8 hours, latching error, no more measurements performed.
value = 0x0100
test = Set
}
map {
name = ELECTRONICS_ERROR
// F9
value = 0x0200
test = Set
}
}
}
field {
name = total
quantity = Energy
match {
measurement_type = Instantaneous
vif_range = AnyEnergyVIF
}
}
field {
name = target
quantity = Energy
match {
measurement_type = Instantaneous
vif_range = AnyEnergyVIF
storage_nr = 17
}
}
field {
name = target
quantity = PointInTime
display_unit = date
match {
measurement_type = Instantaneous
vif_range = Date
storage_nr = 17
}
}
field {
name = target_year
quantity = Energy
match {
measurement_type = Instantaneous
vif_range = AnyEnergyVIF
storage_nr = 1
}
}
field {
name = target_year
quantity = PointInTime
display_unit = date
match {
measurement_type = Instantaneous
vif_range = Date
storage_nr = 1
}
}
field {
name = device_error
quantity = PointInTime
display_unit = date
match {
measurement_type = AtError
vif_range = Date
}
}
field {
name = mfct_specific_data
quantity = Text
attributes = HIDE
match {
difvifkey = 0DFF5F
}
// Siemens/Qundis WalkByDataSet block: DIF=0D (variable-len), VIF=FF (mfct), VIFE=5F, 53 bytes.
// 9-byte proprietary header (skipped as quad, quad, byte):
// [0] 0x00 reserved
// [1] 0x82 wbdsId: Type 2 WalkByDataSet
// [2] counter rolling transmission counter (increments each send)
// [3] 0x00 reserved
// [4] 0x00 reserved
// [5] flags device-specific status/flags (format unknown)
// [6] 0x00/01 extra flag
// [7] 0x07 constant (possibly number of data records)
// [8] 0xC0 meter type: 0xB0=HCA, 0xC0=heat, 0xC1=warm/cold water
// Byte [9] is the VIF (0x05/06/0D/0E = energy variants), which is matched below.
ixml = "decode = quad, quad, byte,
(energy_100wh |
energy_1kwh |
energy_100kj |
energy_1mj).
energy_100wh = '05', error_date, total_100wh, target_year_date, target_year_100wh, target_date, target_100wh, d05_1, d05_2, d05_3, d05_4, d05_5, d05_6, d05_7, d05_8, d05_9, d05_10, d05_11, d05_12, -pad.
energy_1kwh = '06', error_date, total_1kwh, target_year_date, target_year_1kwh, target_date, target_1kwh, d06_1, d06_2, d06_3, d06_4, d06_5, d06_6, d06_7, d06_8, d06_9, d06_10, d06_11, d06_12, -pad.
energy_100kj = '0D', error_date, total_100kj, target_year_date, target_year_100kj, target_date, target_100kj, d0d_1, d0d_2, d0d_3, d0d_4, d0d_5, d0d_6, d0d_7, d0d_8, d0d_9, d0d_10, d0d_11, d0d_12, -pad.
energy_1mj = '0E', error_date, total_1mj, target_year_date, target_year_1mj, target_date, target_1mj, d0e_1, d0e_2, d0e_3, d0e_4, d0e_5, d0e_6, d0e_7, d0e_8, d0e_9, d0e_10, d0e_11, d0e_12, -pad.
error_date = word, @DV_426C.
target_year_date = word, @DV_426C.
target_date = word, @DV_C2086C.
total_100wh = quad, @DV_0C05.
total_1kwh = quad, @DV_0C06.
total_100kj = quad, @DV_0C0D.
total_1mj = quad, @DV_0C0E.
target_year_100wh = quad, @DV_4C05.
target_year_1kwh = quad, @DV_4C06.
target_year_100kj = quad, @DV_4C0D.
target_year_1mj = quad, @DV_4C0E.
target_100wh = quad, @DV_CC0805.
target_1kwh = quad, @DV_CC0806.
target_100kj = quad, @DV_CC080D.
target_1mj = quad, @DV_CC080E.
d05_1 = word, @DV_820105.
d05_2 = word, @DV_C20105.
d05_3 = word, @DV_820205.
d05_4 = word, @DV_C20205.
d05_5 = word, @DV_820305.
d05_6 = word, @DV_C20305.
d05_7 = word, @DV_820405.
d05_8 = word, @DV_C20405.
d05_9 = word, @DV_820505.
d05_10 = word, @DV_C20505.
d05_11 = word, @DV_820605.
d05_12 = word, @DV_C20605.
d06_1 = word, @DV_820106.
d06_2 = word, @DV_C20106.
d06_3 = word, @DV_820206.
d06_4 = word, @DV_C20206.
d06_5 = word, @DV_820306.
d06_6 = word, @DV_C20306.
d06_7 = word, @DV_820406.
d06_8 = word, @DV_C20406.
d06_9 = word, @DV_820506.
d06_10 = word, @DV_C20506.
d06_11 = word, @DV_820606.
d06_12 = word, @DV_C20606.
d0d_1 = word, @DV_82010D.
d0d_2 = word, @DV_C2010D.
d0d_3 = word, @DV_82020D.
d0d_4 = word, @DV_C2020D.
d0d_5 = word, @DV_82030D.
d0d_6 = word, @DV_C2030D.
d0d_7 = word, @DV_82040D.
d0d_8 = word, @DV_C2040D.
d0d_9 = word, @DV_82050D.
d0d_10 = word, @DV_C2050D.
d0d_11 = word, @DV_82060D.
d0d_12 = word, @DV_C2060D.
d0e_1 = word, @DV_82010E.
d0e_2 = word, @DV_C2010E.
d0e_3 = word, @DV_82020E.
d0e_4 = word, @DV_C2020E.
d0e_5 = word, @DV_82030E.
d0e_6 = word, @DV_C2030E.
d0e_7 = word, @DV_82040E.
d0e_8 = word, @DV_C2040E.
d0e_9 = word, @DV_82050E.
d0e_10 = word, @DV_C2050E.
d0e_11 = word, @DV_82060E.
d0e_12 = word, @DV_C2060E.
-pad = byte.
-hex = ['A'-'F';'0'-'9'].
-byte = hex, hex.
-word = byte, byte.
-triplet = byte, byte, byte.
-quad = byte, byte, byte, byte.
DV_0C05>dvk = +'0C05'.
DV_0C06>dvk = +'0C06'.
DV_0C0D>dvk = +'0C0D'.
DV_0C0E>dvk = +'0C0E'.
DV_4C05>dvk = +'4C05'.
DV_4C06>dvk = +'4C06'.
DV_4C0D>dvk = +'4C0D'.
DV_4C0E>dvk = +'4C0E'.
DV_CC0805>dvk = +'CC0805'.
DV_CC0806>dvk = +'CC0806'.
DV_CC080D>dvk = +'CC080D'.
DV_CC080E>dvk = +'CC080E'.
DV_426C>dvk = +'426C'.
DV_C2086C>dvk = +'C2086C'.
DV_820105>dvk = +'820105'.
DV_C20105>dvk = +'C20105'.
DV_820205>dvk = +'820205'.
DV_C20205>dvk = +'C20205'.
DV_820305>dvk = +'820305'.
DV_C20305>dvk = +'C20305'.
DV_820405>dvk = +'820405'.
DV_C20405>dvk = +'C20405'.
DV_820505>dvk = +'820505'.
DV_C20505>dvk = +'C20505'.
DV_820605>dvk = +'820605'.
DV_C20605>dvk = +'C20605'.
DV_820106>dvk = +'820106'.
DV_C20106>dvk = +'C20106'.
DV_820206>dvk = +'820206'.
DV_C20206>dvk = +'C20206'.
DV_820306>dvk = +'820306'.
DV_C20306>dvk = +'C20306'.
DV_820406>dvk = +'820406'.
DV_C20406>dvk = +'C20406'.
DV_820506>dvk = +'820506'.
DV_C20506>dvk = +'C20506'.
DV_820606>dvk = +'820606'.
DV_C20606>dvk = +'C20606'.
DV_82010D>dvk = +'82010D'.
DV_C2010D>dvk = +'C2010D'.
DV_82020D>dvk = +'82020D'.
DV_C2020D>dvk = +'C2020D'.
DV_82030D>dvk = +'82030D'.
DV_C2030D>dvk = +'C2030D'.
DV_82040D>dvk = +'82040D'.
DV_C2040D>dvk = +'C2040D'.
DV_82050D>dvk = +'82050D'.
DV_C2050D>dvk = +'C2050D'.
DV_82060D>dvk = +'82060D'.
DV_C2060D>dvk = +'C2060D'.
DV_82010E>dvk = +'82010E'.
DV_C2010E>dvk = +'C2010E'.
DV_82020E>dvk = +'82020E'.
DV_C2020E>dvk = +'C2020E'.
DV_82030E>dvk = +'82030E'.
DV_C2030E>dvk = +'C2030E'.
DV_82040E>dvk = +'82040E'.
DV_C2040E>dvk = +'C2040E'.
DV_82050E>dvk = +'82050E'.
DV_C2050E>dvk = +'C2050E'.
DV_82060E>dvk = +'82060E'.
DV_C2060E>dvk = +'C2060E'."
}
field {
name = 'energy_delta_{storage_counter}'
quantity = Energy
info = 'Monthly energy delta.'
vif_scaling = Auto
dif_signedness = Signed
match {
measurement_type = Instantaneous
vif_range = AnyEnergyVIF
storage_nr = 2,13
}
}
}
tests {
test {
args = 'QHeato qheatv2 67228058 NOKEY'
telegram = 3C449344957002372337725880226793442304DC0000200C05043900004C0500000000426C9F2CCC080551070000C2086CBE29326CFFFF046D280DB62A
json = '{"_": "telegram","device_error_date": "2128-03-31","id": "67228058","media": "heat","meter": "qheatv2","meter_datetime": "2021-10-22 13:40","name": "QHeato","status": "OK","target_date": "2021-09-30","target_kwh": 75.1,"target_year_date": "2020-12-31","target_year_kwh": 0,"timestamp": "1111-11-11T11:11:11Z","total_kwh": 390.4}'
fields = 'QHeato;67228058;OK;390.4;1111-11-11 11:11.11'
}
test {
args = 'QQ1 qheatv2 68204641 NOKEY'
telegram = 58449344414620684737780779414620689344470c0dff5f3500825a00000e0007c00dffff310803001f3c036800003e34310803000080008000800080008000800000931a92293128190c00002f02fd170000046d000f3235
json = '{"_": "telegram","energy_delta_10_kwh":295.611111,"energy_delta_11_kwh":285.805556,"energy_delta_12_kwh":86.027778,"energy_delta_13_kwh":0,"energy_delta_2_kwh":-910.222222,"energy_delta_3_kwh":-910.222222,"energy_delta_4_kwh":-910.222222,"energy_delta_5_kwh":-910.222222,"energy_delta_6_kwh":-910.222222,"energy_delta_7_kwh":-910.222222,"energy_delta_8_kwh":0,"energy_delta_9_kwh":188.972222,"enhanced_id": "0C47449368204641","id": "68204641","media": "radio converter (meter side)","meter": "qheatv2","meter_datetime": "2025-05-18 15:00","name": "QQ1","status": "OK","target_date": "2025-04-30","target_kwh":856.416667,"target_year_date": "2024-12-31","target_year_kwh":188.972222,"timestamp": "1111-11-11T11:11:11Z","total_kwh": 856.416667}'
fields = 'QQ1;68204641;OK;856.416667;1111-11-11 11:11.11'
}
test {
args = 'QHeat55r qheatv2 68267443 NOKEY'
comment = 'Q AMR telegram sent every 7.5 minutes'
comment = 'Q Walk by telegram sent every 112 seconds'
telegram = 414493444374266848377243742668934448042A000020_0C06140000004C0600000000426CFFFFCC080600000000C2086C3C3202FD170000326CFFFF046D190F3533
json = '{"_": "telegram","device_error_date": "2128-03-31","id": "68267443","media": "heat","meter": "qheatv2","meter_datetime": "2025-03-21 15:25","name": "QHeat55r","status": "OK","target_date": "2025-02-28","target_kwh": 0,"target_year_date": "2128-03-31","target_year_kwh": 0,"timestamp": "1111-11-11T11:11:11Z","total_kwh": 14}'
fields = 'QHeat55r;68267443;OK;14;1111-11-11 11:11.11'
}
test {
args = 'QHeat55r qheatv2 68267443 NOKEY'
comment = 'Q Walk by telegram sent every 112 seconds'
telegram = 5844934443742668483778_077943742668934448040DFF5F3500822A0000610107C006FFFF14000000FFFF000000003C32000000000080008000800080008000800080008000800080008000002F02FD170000046D1B0F3533
json = '{"_": "telegram","energy_delta_10_kwh":-32768,"energy_delta_11_kwh":-32768,"energy_delta_12_kwh":-32768,"energy_delta_13_kwh":0,"energy_delta_2_kwh":-32768,"energy_delta_3_kwh":-32768,"energy_delta_4_kwh":-32768,"energy_delta_5_kwh":-32768,"energy_delta_6_kwh":-32768,"energy_delta_7_kwh":-32768,"energy_delta_8_kwh":-32768,"energy_delta_9_kwh":-32768,"enhanced_id": "0448449368267443","id": "68267443","media": "radio converter (meter side)","meter": "qheatv2","meter_datetime": "2025-03-21 15:27","name": "QHeat55r","status": "OK","target_date": "2025-02-28","target_kwh": 0,"target_year_date": "2128-03-31","target_year_kwh": 0,"timestamp": "1111-11-11T11:11:11Z","total_kwh": 14}'
fields = 'QHeat55r;68267443;OK;14;1111-11-11 11:11.11'
}
test {
args = 'QQA qheatv2 67958979 NOKEY'
telegram = 58449344798995673E377807797989956793443E040DFF5F3500827D0000810007C006FFFF531100003F3C490900005F318010000083003C0022000B0000000000000000000A002B00510083002F02FD170000046D16085832
json = '{"_": "telegram","energy_delta_10_kwh":10,"energy_delta_11_kwh":43,"energy_delta_12_kwh":81,"energy_delta_13_kwh":131,"energy_delta_2_kwh":131,"energy_delta_3_kwh":60,"energy_delta_4_kwh":34,"energy_delta_5_kwh":11,"energy_delta_6_kwh":0,"energy_delta_7_kwh":0,"energy_delta_8_kwh":0,"energy_delta_9_kwh":0,"enhanced_id": "043E449367958979","id": "67958979","media": "radio converter (meter side)","meter": "qheatv2","meter_datetime": "2026-02-24 08:22","name": "QQA","status": "OK","target_date": "2026-01-31","target_kwh":1080,"target_year_date": "2025-12-31","target_year_kwh":949,"timestamp": "1111-11-11T11:11:11Z","total_kwh": 1153}'
fields = 'QQA;67958979;OK;1153;1111-11-11 11:11.11'
}
test {
args = 'QQB qheatv2 67985890 NOKEY'
telegram = 41449344905898673e37729058986793443e04120000200c06809300004c0678820000426c3f3ccc080636900000c2086c5f3102fd170000326cffff046d1d095832
json = '{"_": "telegram","device_error_date": "2128-03-31","id": "67985890","media": "heat","meter": "qheatv2","meter_datetime": "2026-02-24 09:29","name": "QQB","status": "OK","target_date": "2026-01-31","target_kwh": 9036,"target_year_date": "2025-12-31","target_year_kwh": 8278,"timestamp": "1111-11-11T11:11:11Z","total_kwh": 9380}'
fields = 'QQB;67985890;OK;9380;1111-11-11 11:11.11'
}
}
}