// Copyright (C) 2020-2026 Fredrik Öhrström (gpl-3.0-or-later)
driver {
    name           = rfmtx1
    meter_type     = WaterMeter
    default_fields = name,id,total_m3,meter_datetime,timestamp
    detect {
        mvt = BMT,05,07
    }
    fields {
        // RFM-TX1 (tpl-cfg 1006) uses proprietary payload layout. The ixml rule below
        // reconstructs pseudo DV entries so the rest of the driver can decode like normal fields.
        field {
            name               = payload_decoder
            quantity           = Text
            attributes         = HIDE,REQUIRED
            match_entire_frame = true
            ixml               = "decode = skip11, key_byte, skip3, skip2, data_raw_bytes, skip7, sec_bytes, min_bytes, hour_bytes, day_bytes, mon_bytes, year_bytes, rest.
                                  -hex = ['A'-'F';'0'-'9'].
                                  -byte = hex, hex.
                                  -skip11 = byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte.
                                  -skip3 = byte, byte, byte.
                                  -skip2 = byte, byte.
                                  -skip7 = byte, byte, byte, byte, byte, byte, byte.
                                  -rest = byte*.
                                  key_byte = byte, @DV_key_raw.
                                  data_raw_bytes = byte, byte, byte, byte, @DV_data_raw.
                                  sec_bytes  = byte, @DV_sec_raw.
                                  min_bytes  = byte, @DV_min_raw.
                                  hour_bytes = byte, @DV_hour_raw.
                                  day_bytes  = byte, @DV_day_raw.
                                  mon_bytes  = byte, @DV_mon_raw.
                                  year_bytes = byte, @DV_year_raw.
                                  DV_key_raw>dvk   = +'01FF10'.
                                  DV_data_raw>dvk  = +'04FF11'.
                                  DV_sec_raw>dvk   = +'01FF13'.
                                  DV_min_raw>dvk   = +'01FF14'.
                                  DV_hour_raw>dvk  = +'01FF15'.
                                  DV_day_raw>dvk   = +'01FF16'.
                                  DV_mon_raw>dvk   = +'01FF17'.
                                  DV_year_raw>dvk  = +'01FF18'."
        }
        field {
            name           = key_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF10
            }
        }
        field {
            name           = data_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 04FF11
            }
        }
        field {
            name           = sec_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF13
            }
        }
        field {
            name           = min_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF14
            }
        }
        field {
            name           = hour_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF15
            }
        }
        field {
            name           = day_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF16
            }
        }
        field {
            name           = mon_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF17
            }
        }
        field {
            name           = year_raw
            quantity       = Dimensionless
            attributes     = HIDE
            vif_scaling    = None
            dif_signedness = Unsigned
            match {
                difvifkey = 01FF18
            }
        }
        field {
            name       = row
            quantity   = Dimensionless
            attributes = HIDE
            // Row selector used by all t0..t3 lookup formulas.
            // key_raw is the telegram key byte; low nibble gives a row in [0..15].
            calculate = 'key_raw_counter & 15counter'
        }
        // t0..t3 are row-indexed lookup values. Each formula returns exactly one constant
        // for the selected row (all other terms evaluate to 0).
        // Keep each ((row==N)*value) term wrapped in parentheses because the formula parser
        // does not apply standard operator precedence between + and *.
        field {
            name       = t0
            quantity   = Dimensionless
            attributes = HIDE
            // LUT value for d0, where encrypted byte0 is (data_raw_counter & 255counter)
            // and d0 is computed below as byte0 ^ key_raw_counter ^ t0_counter.
            calculate = '((row_counter == 0counter)*122counter) + ((row_counter == 1counter)*112counter) + ((row_counter == 2counter)*185counter) + ((row_counter == 3counter)*121counter) + ((row_counter == 4counter)*7counter) + ((row_counter == 5counter)*50counter) + ((row_counter == 6counter)*156counter) + ((row_counter == 7counter)*180counter) + ((row_counter == 8counter)*10counter) + ((row_counter == 9counter)*240counter) + ((row_counter == 10counter)*22counter) + ((row_counter == 11counter)*12counter) + ((row_counter == 12counter)*74counter) + ((row_counter == 13counter)*119counter) + ((row_counter == 14counter)*189counter) + ((row_counter == 15counter)*57counter)'
        }
        field {
            name       = t1
            quantity   = Dimensionless
            attributes = HIDE
            // LUT value for d1, with byte1 = ((data_raw_counter >> 8counter) & 255counter)
            // and d1 computed below as byte1 ^ key_raw_counter ^ t1_counter.
            calculate = '((row_counter == 0counter)*16counter) + ((row_counter == 1counter)*19counter) + ((row_counter == 2counter)*11counter) + ((row_counter == 3counter)*7counter) + ((row_counter == 4counter)*154counter) + ((row_counter == 5counter)*161counter) + ((row_counter == 6counter)*126counter) + ((row_counter == 7counter)*196counter) + ((row_counter == 8counter)*206counter) + ((row_counter == 9counter)*5counter) + ((row_counter == 10counter)*152counter) + ((row_counter == 11counter)*201counter) + ((row_counter == 12counter)*72counter) + ((row_counter == 13counter)*46counter) + ((row_counter == 14counter)*120counter) + ((row_counter == 15counter)*111counter)'
        }
        field {
            name       = t2
            quantity   = Dimensionless
            attributes = HIDE
            // LUT value for d2, with byte2 = ((data_raw_counter >> 16counter) & 255counter)
            // and d2 computed below as byte2 ^ key_raw_counter ^ t2_counter.
            calculate = '((row_counter == 0counter)*26counter) + ((row_counter == 1counter)*34counter) + ((row_counter == 2counter)*142counter) + ((row_counter == 3counter)*74counter) + ((row_counter == 4counter)*203counter) + ((row_counter == 5counter)*57counter) + ((row_counter == 6counter)*96counter) + ((row_counter == 7counter)*128counter) + ((row_counter == 8counter)*25counter) + ((row_counter == 9counter)*165counter) + ((row_counter == 10counter)*17counter) + ((row_counter == 11counter)*125counter) + ((row_counter == 12counter)*228counter) + ((row_counter == 13counter)*138counter) + ((row_counter == 14counter)*87counter) + ((row_counter == 15counter)*40counter)'
        }
        field {
            name       = t3
            quantity   = Dimensionless
            attributes = HIDE
            // LUT value for d3, with byte3 = ((data_raw_counter >> 24counter) & 255counter)
            // and d3 computed below as byte3 ^ key_raw_counter ^ t3_counter.
            calculate = '((row_counter == 0counter)*10counter) + ((row_counter == 1counter)*19counter) + ((row_counter == 2counter)*153counter) + ((row_counter == 3counter)*22counter) + ((row_counter == 4counter)*105counter) + ((row_counter == 5counter)*14counter) + ((row_counter == 6counter)*153counter) + ((row_counter == 7counter)*163counter) + ((row_counter == 8counter)*3counter) + ((row_counter == 9counter)*134counter) + ((row_counter == 10counter)*94counter) + ((row_counter == 11counter)*162counter) + ((row_counter == 12counter)*31counter) + ((row_counter == 13counter)*232counter) + ((row_counter == 14counter)*140counter) + ((row_counter == 15counter)*5counter)'
        }
        field {
            name       = d0
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '(data_raw_counter & 255counter) ^ key_raw_counter ^ t0_counter'
        }
        field {
            name       = d1
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((data_raw_counter >> 8counter) & 255counter) ^ key_raw_counter ^ t1_counter'
        }
        field {
            name       = d2
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((data_raw_counter >> 16counter) & 255counter) ^ key_raw_counter ^ t2_counter'
        }
        field {
            name       = d3
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((data_raw_counter >> 24counter) & 255counter) ^ key_raw_counter ^ t3_counter'
        }
        field {
            name       = sec
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((sec_raw_counter >> 4counter) * 10counter) + (sec_raw_counter % 16counter)'
        }
        field {
            name       = min
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((min_raw_counter >> 4counter) * 10counter) + (min_raw_counter % 16counter)'
        }
        field {
            name       = hour
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((hour_raw_counter >> 4counter) * 10counter) + (hour_raw_counter % 16counter)'
        }
        field {
            name       = day
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((day_raw_counter >> 4counter) * 10counter) + (day_raw_counter % 16counter)'
        }
        field {
            name       = mon
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((mon_raw_counter >> 4counter) * 10counter) + (mon_raw_counter % 16counter)'
        }
        field {
            name       = year
            quantity   = Dimensionless
            attributes = HIDE
            calculate  = '((year_raw_counter >> 4counter) * 10counter) + (year_raw_counter % 16counter)'
        }
        field {
            name     = total
            quantity = Volume
            info     = 'The total water consumption recorded by this meter.'
            // d0..d3 are decrypted bytes; each byte stores two BCD digits. Rebuild the
            // 8-digit reading and scale from liters to m3.
            calculate = '((((d0_counter >> 4counter) * 10counter) + (d0_counter % 16counter)) + (100counter * (((d1_counter >> 4counter) * 10counter) + (d1_counter % 16counter))) + (10000counter * (((d2_counter >> 4counter) * 10counter) + (d2_counter % 16counter))) + (1000000counter * (((d3_counter >> 4counter) * 10counter) + (d3_counter % 16counter)))) / 1000counter * 1m3'
        }
        field {
            name         = meter
            quantity     = PointInTime
            display_unit = datetime
            info         = 'Date time when meter sent this telegram.'
            // Meter datetime bytes are plain BCD (yy MM dd HH mm ss) in telegram payload.
            // Build local meter time from component fields, matching behavior confirmed in issue #101.
            calculate = "'2000-01-01 00:00:00' +
                         (((year_counter * 12counter) + mon_counter - 1counter) * 1month) +
                         ((day_counter - 1counter) * 24h) +
                         ((((hour_counter * 3600counter) + (min_counter * 60counter) + sec_counter)) * 1s)"
        }
    }
    tests {
        test {
            args     = 'Wasser rfmtx1 74737271 NOKEY'
            telegram = 4644B4097172737405077AA5000610_1115F78184AB0F1D1E200000005904103103208047004A4800E73C00193E00453F003E4000E64000E74100F442000144001545005B460000
            json     = '{"_":"telegram","media":"water","meter":"rfmtx1","name":"Wasser","id":"74737271","total_m3":188.56,"meter_datetime":"2020-03-31 10:04","timestamp":"1111-11-11T11:11:11Z"}'
            fields   = 'Wasser;74737271;188.56;2020-03-31 10:04;1111-11-11 11:11.11'
        }
    }
}