// Copyright (C) 2026 Fredrik Öhrström (gpl-3.0-or-later)
driver {
name = apatorna1
meter_type = WaterMeter
default_fields = name,id,total_m3,timestamp
manufacturer = Apator
detect {
mvt = APA,14,07
}
fields {
field {
name = packed_total_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 04FB00
}
}
field {
name = meter_datetime_b0
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB01
}
}
field {
name = meter_datetime_b1
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB02
}
}
field {
name = meter_datetime_b2
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB03
}
}
field {
name = meter_datetime_b3
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB04
}
}
field {
name = historic_age_low_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB05
}
}
field {
name = historic_age_meta_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB06
}
}
field {
name = historic_delta_b0_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB07
}
}
field {
name = historic_delta_b1_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB08
}
}
field {
name = historic_delta_b2_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB09
}
}
field {
name = frame_status_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB10
}
}
field {
name = current_status_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB11
}
}
field {
name = historic_status_raw
quantity = Dimensionless
attributes = HIDE
vif_scaling = None
dif_signedness = Unsigned
match {
difvifkey = 01FB12
}
}
field {
name = packed_total_multiplier
quantity = Dimensionless
attributes = HIDE
calculate = '((((packed_total_raw_counter >> 4counter) % 4counter) == 0counter) * 1counter) +
((((packed_total_raw_counter >> 4counter) % 4counter) == 1counter) * 10counter) +
((((packed_total_raw_counter >> 4counter) % 4counter) == 2counter) * 100counter) +
((((packed_total_raw_counter >> 4counter) % 4counter) == 3counter) * 1000counter)'
}
field {
name = total
quantity = Volume
info = 'The total water consumption recorded by this meter.'
calculate = '((((packed_total_raw_counter >> 8counter) * 16counter) + (packed_total_raw_counter % 16counter)) * packed_total_multiplier_counter / 1000counter) * 1m3'
}
field {
name = meter
quantity = PointInTime
info = 'Date and time reported inside the encrypted manufacturer payload.'
display_unit = datetime
calculate = "'2000-01-01 00:00:00' +
(((meter_datetime_b3_counter << 24counter) +
(meter_datetime_b2_counter << 16counter) +
(meter_datetime_b1_counter << 8counter) +
meter_datetime_b0_counter) * 1s)"
}
field {
name = historic_age
quantity = Time
info = 'Age of the last historical reading carried in the telegram.'
display_unit = h
calculate = '((((historic_age_meta_raw_counter >> 2counter) * 256counter) + historic_age_low_raw_counter) * 1h)'
}
field {
name = historic
quantity = Volume
info = 'The last historical water consumption recorded by this meter.'
calculate = 'total_m3 - (((((historic_delta_b2_raw_counter << 18counter) +
(historic_delta_b1_raw_counter << 10counter) +
(historic_delta_b0_raw_counter << 2counter) +
(historic_age_meta_raw_counter % 4counter)) * packed_total_multiplier_counter) / 1000counter) * 1m3)'
}
field {
name = historic
quantity = PointInTime
info = 'Date and time of the last historical reading carried in the telegram.'
display_unit = datetime
calculate = 'meter_datetime - historic_age_h'
}
field {
name = frame_status
quantity = Text
info = 'Status flags stored in the unencrypted manufacturer payload byte.'
match {
difvifkey = 01FB10
}
lookup {
name = FRAME_STATUS_FLAGS
map_type = BitToString
mask_bits = 0xff
default_message = OK
map {
name = SUMMER_TIME
value = 0x01
test = Set
}
map {
name = HISTORICAL_INDICATION
value = 0x02
test = Set
}
map {
name = MAX_SPEED_EXCEEDED
value = 0x04
test = Set
}
map {
name = DEVICE_ACCESS_ERROR
value = 0x08
test = Set
}
map {
name = TWO_WAY_COM_BLOCK
value = 0x10
test = Set
}
map {
name = INSTALL_FRAME
value = 0x20
test = Set
}
map {
name = WRONG_TEMPERATURE
value = 0x40
test = Set
}
map {
name = SERVICE_EVENT
value = 0x80
test = Set
}
}
}
field {
name = current_status
quantity = Text
info = 'Current alarm and tamper flags from the decrypted payload.'
match {
difvifkey = 01FB11
}
lookup {
name = CURRENT_STATUS_FLAGS
map_type = BitToString
mask_bits = 0xff
default_message = OK
map {
name = MINIMUM_FLOW
value = 0x80
test = Set
}
map {
name = MAXIMUM_FLOW
value = 0x40
test = Set
}
map {
name = REVERSE_FLOW
value = 0x20
test = Set
}
map {
name = MEASUREMENT_UNCHANGED
value = 0x10
test = Set
}
map {
name = LEAK
value = 0x08
test = Set
}
map {
name = DEVICE_DISCONNECTED
value = 0x04
test = Set
}
map {
name = MAGNETIC_FIELD
value = 0x02
test = Set
}
map {
name = BATTERY_LOW
value = 0x01
test = Set
}
}
}
field {
name = historic_status
quantity = Text
info = 'Historic alarm and tamper flags from the decrypted payload.'
match {
difvifkey = 01FB12
}
lookup {
name = HISTORIC_STATUS_FLAGS
map_type = BitToString
mask_bits = 0xff
default_message = OK
map {
name = MINIMUM_FLOW
value = 0x80
test = Set
}
map {
name = MAXIMUM_FLOW
value = 0x40
test = Set
}
map {
name = REVERSE_FLOW
value = 0x20
test = Set
}
map {
name = MEASUREMENT_UNCHANGED
value = 0x10
test = Set
}
map {
name = LEAK
value = 0x08
test = Set
}
map {
name = DEVICE_DISCONNECTED
value = 0x04
test = Set
}
map {
name = MAGNETIC_FIELD
value = 0x02
test = Set
}
map {
name = BATTERY_LIFETIME_THRESHOLD_EXCEEDED
value = 0x01
test = Set
}
}
}
field {
name = mfct_header_data
quantity = Text
attributes = HIDE
match_entire_payload = true
ixml = "decode = tpl_acc, frame_status, rest.
tpl_acc = -byte.
frame_status = byte, @DV_frame_status_raw.
rest = byte*.
-hex = ['A'-'F';'0'-'9'].
-byte = hex, hex.
DV_frame_status_raw>dvk = +'01FB10'."
}
field {
name = mfct_specific_data
quantity = Text
attributes = HIDE
match_entire_payload = true
transform_payload = tpl_aes_cbc_iv,2,16,0
ixml = "decode = -pad, packed_total, meter_datetime, historic_payload, current_status, historic_status.
packed_total = quad, @DV_packed_total_raw.
meter_datetime = dt_b0, dt_b1, dt_b2, dt_b3.
dt_b0 = byte, @DV_meter_datetime_b0.
dt_b1 = byte, @DV_meter_datetime_b1.
dt_b2 = byte, @DV_meter_datetime_b2.
dt_b3 = byte, @DV_meter_datetime_b3.
historic_payload = historic_age_low,
historic_age_meta,
historic_delta_b0,
historic_delta_b1,
historic_delta_b2.
historic_age_low = byte, @DV_historic_age_low_raw.
historic_age_meta = byte, @DV_historic_age_meta_raw.
historic_delta_b0 = byte, @DV_historic_delta_b0_raw.
historic_delta_b1 = byte, @DV_historic_delta_b1_raw.
historic_delta_b2 = byte, @DV_historic_delta_b2_raw.
current_status = byte, @DV_current_status_raw.
historic_status = byte, @DV_historic_status_raw.
-hex = ['A'-'F';'0'-'9'].
-byte = hex, hex.
-quad = byte, byte, byte, byte.
-pad = byte.
DV_packed_total_raw>dvk = +'04FB00'.
DV_meter_datetime_b0>dvk = +'01FB01'.
DV_meter_datetime_b1>dvk = +'01FB02'.
DV_meter_datetime_b2>dvk = +'01FB03'.
DV_meter_datetime_b3>dvk = +'01FB04'.
DV_historic_age_low_raw>dvk = +'01FB05'.
DV_historic_age_meta_raw>dvk = +'01FB06'.
DV_historic_delta_b0_raw>dvk = +'01FB07'.
DV_historic_delta_b1_raw>dvk = +'01FB08'.
DV_historic_delta_b2_raw>dvk = +'01FB09'.
DV_current_status_raw>dvk = +'01FB11'.
DV_historic_status_raw>dvk = +'01FB12'."
}
}
tests {
test {
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A0B000266A705474DDB80D9A0EB9AE2EF29D96
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":300.619,"historic_datetime":"2024-02-29 22:22","historic_age_h":545,"meter_datetime":"2024-03-23 15:22","total_m3":345.312,"current_status":"OK","frame_status":"OK","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;345.312;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 1 before dip'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A07F018D43B54E718E8718D5D2C8F939194847
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:23","historic_age_h":351,"meter_datetime":"2024-10-15 12:23","total_m3":687.361,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"OK","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;687.361;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 1 dip'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A17D01416562108AB98A0FE92FC06AA8122176
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:22","historic_age_h":351,"meter_datetime":"2024-10-15 12:22","total_m3":687.104,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"OK","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;687.104;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 1 recovery'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A174013655E9B2B8085E2F256908D7498C402C
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:21","historic_age_h":351,"meter_datetime":"2024-10-15 12:21","total_m3":687.357,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"OK","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;687.357;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 2 before dip'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A0FF019E3790584B0876226B94ADC34FFABDED
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:04","historic_age_h":375,"meter_datetime":"2024-10-16 12:04","total_m3":689.403,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;689.403;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 2 dip'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A0020134813C00486734067DB0111C30188137
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:04","historic_age_h":375,"meter_datetime":"2024-10-16 12:04","total_m3":689.152,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;689.152;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 2 recovery'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A00C019334727003B3DA268CF11117D85B6D71
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:06","historic_age_h":375,"meter_datetime":"2024-10-16 12:06","total_m3":689.415,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;689.415;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 3 before dip'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A0080174159B7E36D8E6421E3AED55C76BA54A
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:18","historic_age_h":473,"meter_datetime":"2024-10-20 14:18","total_m3":696.575,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;696.575;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 3 dip'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A10C010AEF3C1D41BE7F7A33B9EB469924397C
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:18","historic_age_h":473,"meter_datetime":"2024-10-20 14:18","total_m3":696.32,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;696.32;1111-11-11 11:11.11'
}
test {
comment = 'Issue 1215 cluster 3 recovery'
args = 'ApNa1 apatorna1 04913581 00000000000000000000000000000000'
telegram = 1C440106813591041407A0130197DAF2A1C02F9B77B14ACD51CB20C41E
json = '{"_":"telegram","media":"water","meter":"apatorna1","name":"ApNa1","id":"04913581","historic_m3":662.325,"historic_datetime":"2024-09-30 21:20","historic_age_h":473,"meter_datetime":"2024-10-20 14:20","total_m3":696.576,"current_status":"OK","frame_status":"SUMMER_TIME","historic_status":"MINIMUM_FLOW","timestamp":"1111-11-11T11:11:11Z"}'
fields = 'ApNa1;04913581;696.576;1111-11-11 11:11.11'
}
}
}