// Copyright (C) 2026 Fredrik Öhrström (gpl-3.0-or-later)
driver {
name = apatoreitn
meter_type = HeatCostAllocationMeter
default_fields = name,id,current_hca,previous_hca,current_date,season_start_date,esb_date,temp_room_avg_c,temp_room_prev_avg_c,timestamp
manufacturer = Apator
detect {
mvt = APA,04,08
mvt = APT,04,08
}
fields {
field {
name = current
quantity = HCA
info = 'Energy consumption so far in this billing period.'
match {
difvifkey = 026E
}
}
field {
name = previous
quantity = HCA
info = 'Energy consumption in previous billing period.'
match {
difvifkey = 426E
}
}
field {
name = temp_room_avg_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 02FB01
}
}
field {
name = temp_room_prev_avg_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 02FB02
}
}
field {
name = current_date_raw
quantity = Dimensionless
attributes = HIDE
match {
difvifkey = 02FD3A
}
}
field {
name = season_start_date_lo
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB00
}
}
field {
name = esb_date_raw
quantity = Dimensionless
attributes = HIDE
null_value = 0
match {
difvifkey = 8200FD3A
}
}
field {
name = season_start_date_raw
quantity = Dimensionless
attributes = HIDE
calculate = '(160counter * 256counter) + season_start_date_lo_counter'
}
field {
name = current_day
quantity = Dimensionless
attributes = HIDE
calculate = 'current_date_raw_counter % 32counter'
}
field {
name = current_month
quantity = Dimensionless
attributes = HIDE
calculate = '(current_date_raw_counter >> 5counter) % 16counter'
}
field {
name = current_year
quantity = Dimensionless
attributes = HIDE
calculate = '(current_date_raw_counter >> 9counter) % 32counter'
}
field {
name = season_start_day
quantity = Dimensionless
attributes = HIDE
calculate = 'season_start_date_raw_counter % 32counter'
}
field {
name = season_start_month
quantity = Dimensionless
attributes = HIDE
calculate = '(season_start_date_raw_counter >> 5counter) % 16counter'
}
field {
name = season_start_year
quantity = Dimensionless
attributes = HIDE
calculate = '(season_start_date_raw_counter >> 9counter) % 32counter'
}
field {
name = esb_day
quantity = Dimensionless
attributes = HIDE
calculate = 'esb_date_raw_counter % 32counter'
}
field {
name = esb_month
quantity = Dimensionless
attributes = HIDE
calculate = '(esb_date_raw_counter >> 5counter) % 16counter'
}
field {
name = esb_year
quantity = Dimensionless
attributes = HIDE
calculate = '(esb_date_raw_counter >> 9counter) % 32counter'
}
field {
name = temp_room_avg
quantity = Temperature
info = 'Average room temperature in current season.'
calculate = '((temp_room_avg_raw_counter >> 8counter) + ((temp_room_avg_raw_counter % 256counter) / 256counter)) * 1c'
}
field {
name = temp_room_prev_avg
quantity = Temperature
info = 'Average room temperature in previous season.'
calculate = '((temp_room_prev_avg_raw_counter >> 8counter) + ((temp_room_prev_avg_raw_counter % 256counter) / 256counter)) * 1c'
}
field {
name = current
quantity = PointInTime
display_unit = date
info = 'Current date, as reported by meter.'
calculate = "'2000-01-01 00:00:00' +
(((current_year_counter * 12counter) + current_month_counter - 1counter) * 1month) +
((current_day_counter - 1counter) * 24h)"
}
field {
name = season_start
quantity = PointInTime
display_unit = date
info = 'Season start date.'
calculate = "'2000-01-01 00:00:00' +
(((season_start_year_counter * 12counter) + season_start_month_counter - 1counter) * 1month) +
((season_start_day_counter - 1counter) * 24h)"
}
field {
name = esb
quantity = PointInTime
display_unit = date
null_string = ''
info = 'Electronic seal protection break date.'
calculate = "'2000-01-01 00:00:00' +
(((esb_year_counter * 12counter) + esb_month_counter - 1counter) * 1month) +
((esb_day_counter - 1counter) * 24h)"
}
field {
name = status
quantity = Text
info = 'Seal integrity status: OK = intact, SEAL_BROKEN = ESB date is set.'
match {
difvifkey = 8200FD3A
}
lookup {
name = STATUS
map_type = BitToString
mask_bits = 0xffff
default_message = OK
map {
name = SEAL_BROKEN
value = 0xffff
test = Set
}
}
}
field {
name = mfct_specific_data
quantity = Text
attributes = HIDE
match_entire_payload = true
ixml = "decode = frame_direct | frame_direct_with_a0 | frame_b6.
frame_direct = SeasonStartDateLo,
pad,
PrevHCA,
ESBDate,
CurrHCA,
CurrentDate,
TempPrevAvg,
TempAvg.
frame_direct_with_a0 = -'A0', frame_direct.
frame_b6 = frame_b6_00 | frame_b6_01 | frame_b6_02 | frame_b6_03 |
frame_b6_04 | frame_b6_05 | frame_b6_06 | frame_b6_07 |
frame_b6_08 | frame_b6_09 | frame_b6_0A | frame_b6_0B |
frame_b6_0C | frame_b6_0D | frame_b6_0E | frame_b6_0F.
frame_b6_00 = -'00', -'A0', frame_direct.
frame_b6_01 = -'01', byte, -'A0', frame_direct.
frame_b6_02 = -'02', byte, byte, -'A0', frame_direct.
frame_b6_03 = -'03', byte, byte, byte, -'A0', frame_direct.
frame_b6_04 = -'04', byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_05 = -'05', byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_06 = -'06', byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_07 = -'07', byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_08 = -'08', byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_09 = -'09', byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_0A = -'0A', byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_0B = -'0B', byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_0C = -'0C', byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_0D = -'0D', byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_0E = -'0E', byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
frame_b6_0F = -'0F', byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, -'A0', frame_direct.
pad = byte, byte.
SeasonStartDateLo = byte, @DV_season_start_date_lo.
PrevHCA = word, @DV_previous_hca.
ESBDate = word, @DV_esb_date_raw.
CurrHCA = word, @DV_current_hca.
CurrentDate = word, @DV_current_date_raw.
TempPrevAvg = word, @DV_temp_room_prev_avg.
TempAvg = word, @DV_temp_room_avg.
-hex = ['A'-'F';'0'-'9'].
-byte = hex, hex.
-word = byte, byte.
DV_current_hca>dvk = +'026E'.
DV_previous_hca>dvk = +'426E'.
DV_current_date_raw>dvk = +'02FD3A'.
DV_season_start_date_lo>dvk = +'01FB00'.
DV_esb_date_raw>dvk = +'8200FD3A'.
DV_temp_room_avg>dvk = +'02FB01'.
DV_temp_room_prev_avg>dvk = +'02FB02'."
}
}
tests {
test {
args = 'HCA1 apatoreitn 37373731 NOKEY'
telegram = 19440186313737370408A0A1000059001C270100322DE413B415
json = '{"_":"telegram","media":"heat cost allocation","meter":"apatoreitn","name":"HCA1","id":"37373731","current_hca":1,"previous_hca":89,"current_date":"2022-09-18","season_start_date":"2016-05-01","esb_date":"2019-08-28","status":"SEAL_BROKEN","temp_room_avg_c":21.703125,"temp_room_prev_avg_c":19.890625,"timestamp":"1111-11-11T11:11:11Z"}'
fields = 'HCA1;37373731;1;89;2022-09-18;2016-05-01;2019-08-28;21.703125;19.890625;1111-11-11 11:11.11'
}
test {
args = 'HCA2 apatoreitn 37373732 NOKEY'
telegram = 25441486323737370408B60AFFFFF5450186F41B9D58A0A100007809000000001F2D6416C819
json = '{"_":"telegram","media":"heat cost allocation","meter":"apatoreitn","name":"HCA2","id":"37373732","current_hca":0,"previous_hca":2424,"current_date":"2022-08-31","season_start_date":"2016-05-01","esb_date":null,"status":"OK","temp_room_avg_c":25.78125,"temp_room_prev_avg_c":22.390625,"timestamp":"1111-11-11T11:11:11Z"}'
fields = 'HCA2;37373732;0;2424;2022-08-31;2016-05-01;null;25.78125;22.390625;1111-11-11 11:11.11'
}
test {
args = 'HCA3 apatoreitn 37373733 NOKEY'
telegram = 29441486333737370408B60EFFFFF1460186EC1B934EE91BA57BA0A1000059009C250100322DE413B415
json = '{"_":"telegram","media":"heat cost allocation","meter":"apatoreitn","name":"HCA3","id":"37373733","current_hca":1,"previous_hca":89,"current_date":"2022-09-18","season_start_date":"2016-05-01","esb_date":"2018-12-28","status":"SEAL_BROKEN","temp_room_avg_c":21.703125,"temp_room_prev_avg_c":19.890625,"timestamp":"1111-11-11T11:11:11Z"}'
fields = 'HCA3;37373733;1;89;2022-09-18;2016-05-01;2018-12-28;21.703125;19.890625;1111-11-11 11:11.11'
}
}
}