|
| 1 | +/** @file |
| 2 | + Badger ORION water meter support. |
| 3 | +
|
| 4 | + Copyright (C) 2022 Nicko van Someren |
| 5 | +
|
| 6 | + This program is free software; you can redistribute it and/or modify |
| 7 | + it under the terms of the GNU General Public License as published by |
| 8 | + the Free Software Foundation; either version 2 of the License, or |
| 9 | + (at your option) any later version. |
| 10 | +*/ |
| 11 | + |
| 12 | +#include "decoder.h" |
| 13 | + |
| 14 | +/** @fn static int badger_orion_decode(r_device *decoder, bitbuffer_t *bitbuffer) |
| 15 | +Badger ORION water meter. |
| 16 | +
|
| 17 | +S.a. https://fccid.io/GIF2006B |
| 18 | +
|
| 19 | +For the single-frequency models the center frequency is 916.45MHz. The bit rate is |
| 20 | +100KHz, so the sample rate should be at least 1.2MHz; using 1.6MHz may work better |
| 21 | +when the signal is weak or noisy. |
| 22 | +
|
| 23 | +The low-level encoding is much the same as M-Bus mode T, but the payload differs. |
| 24 | +
|
| 25 | +The specification sheet states that "The endpoint broadcasts its unique endpoint |
| 26 | +serial number, current meter reading and applicable status indicators" and also that |
| 27 | +status reports include "Premise Leak Detection", "Cut-Wire Indication", "Reverse Flow |
| 28 | +Indication", "No Usage Indication" and "Encoder Error", but the specific flag values |
| 29 | +are not known. |
| 30 | +
|
| 31 | +The data is preceded by several sync bytes of 01010101, followed by the ten bit |
| 32 | +preamble of 0000 1111 01. This is followed by 10 bytes encoded using a 4:6 NRZ |
| 33 | +encoding. This code treats 6 bits of the sync sequence as part of a 16 bit preamble. |
| 34 | +
|
| 35 | +Once the data has been decoded with the NRZ 6:4 decoding, it has the following format: |
| 36 | +- Device ID: 3 bytes, little-endian. Typically utility provider's number, mod 2^24 or mod 10^7. |
| 37 | +- Device flags: 1 byte. Fields not known |
| 38 | +- Meter reading: 3 bytes, little-endian. Value in gallons for meters with 1-gallon resolution. |
| 39 | +- Status flags: 1 byte. Fields not known |
| 40 | +- CRC: 2 bytes, crc16, polynomial 0x3D65 |
| 41 | +
|
| 42 | +*/ |
| 43 | + |
| 44 | +// Mapping from 6 bits to 4 bits. "3of6" coding used for Mode T |
| 45 | +static uint8_t badger_decode_3of6(uint8_t byte) |
| 46 | +{ |
| 47 | + uint8_t out = 0xFF; // Error |
| 48 | + //fprintf(stderr,"Decode %0d\n", byte); |
| 49 | + switch(byte) { |
| 50 | + case 22: out = 0x0; break; // 0x16 |
| 51 | + case 13: out = 0x1; break; // 0x0D |
| 52 | + case 14: out = 0x2; break; // 0x0E |
| 53 | + case 11: out = 0x3; break; // 0x0B |
| 54 | + case 28: out = 0x4; break; // 0x1C |
| 55 | + case 25: out = 0x5; break; // 0x19 |
| 56 | + case 26: out = 0x6; break; // 0x1A |
| 57 | + case 19: out = 0x7; break; // 0x13 |
| 58 | + case 44: out = 0x8; break; // 0x2C |
| 59 | + case 37: out = 0x9; break; // 0x25 |
| 60 | + case 38: out = 0xA; break; // 0x26 |
| 61 | + case 35: out = 0xB; break; // 0x23 |
| 62 | + case 52: out = 0xC; break; // 0x34 |
| 63 | + case 49: out = 0xD; break; // 0x31 |
| 64 | + case 50: out = 0xE; break; // 0x32 |
| 65 | + case 41: out = 0xF; break; // 0x29 |
| 66 | + default: break; // Error |
| 67 | + } |
| 68 | + return out; |
| 69 | +} |
| 70 | + |
| 71 | +// Decode the DC-free 4:6 encoding |
| 72 | +static int badger_decode_3of6_buffer(uint8_t const *bits, unsigned bit_offset, uint8_t *output) |
| 73 | +{ |
| 74 | + for (unsigned n=0; n<10; ++n) { |
| 75 | + uint8_t nibble_h = badger_decode_3of6(bitrow_get_byte(bits, n*12+bit_offset) >> 2); |
| 76 | + uint8_t nibble_l = badger_decode_3of6(bitrow_get_byte(bits, n*12+bit_offset+6) >> 2); |
| 77 | + if ((nibble_h | nibble_l) > 15) { |
| 78 | + return 1; |
| 79 | + } |
| 80 | + output[n] = (nibble_h << 4) | nibble_l; |
| 81 | + } |
| 82 | + return 0; |
| 83 | +} |
| 84 | + |
| 85 | +static int badger_orion_decode(r_device *decoder, bitbuffer_t *bitbuffer) |
| 86 | +{ |
| 87 | + static uint8_t const preamble_pattern[] = {0x54, 0x3D}; |
| 88 | + |
| 89 | + uint8_t data_in[10] = {0}; // Data from Physical layer decoded to bytes |
| 90 | + data_t *data; |
| 91 | + |
| 92 | + // Validate package length |
| 93 | + // The minimum preamble is 16 bits and the payload is 10 4:6 encoded bytes. |
| 94 | + // There is often a long preamble and a 64 or more bits of tail, so the maximum likely length is longer |
| 95 | + if (bitbuffer->bits_per_row[0] < (16 + 12 * 10) || bitbuffer->bits_per_row[0] > (128 + 16 + 12 * 10 + 96)) { |
| 96 | + return DECODE_ABORT_LENGTH; |
| 97 | + } |
| 98 | + |
| 99 | + // Find the preamble |
| 100 | + unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); |
| 101 | + if (bit_offset + 12 * 10 >= bitbuffer->bits_per_row[0]) { |
| 102 | + return DECODE_ABORT_EARLY; |
| 103 | + } |
| 104 | + |
| 105 | + decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, "Preamble found at: %u", bit_offset); |
| 106 | + bit_offset += sizeof(preamble_pattern) * 8; // skip preamble |
| 107 | + |
| 108 | + if (badger_decode_3of6_buffer(bitbuffer->bb[0], bit_offset, data_in) < 0) { |
| 109 | + return DECODE_FAIL_MIC; |
| 110 | + } |
| 111 | + |
| 112 | + uint16_t crc_read = (((uint16_t)data_in[8] << 8) | data_in[9]); |
| 113 | + uint16_t crc_calc = ~crc16(data_in, 8, 0x3D65, 0); |
| 114 | + if (crc_calc != crc_read) { |
| 115 | + decoder_logf(decoder, 1, __func__, |
| 116 | + "Badger ORION: CRC error: Calculated 0x%0X, Read 0x%0X", |
| 117 | + (unsigned)crc_calc, (unsigned) crc_read); |
| 118 | + return DECODE_FAIL_MIC; |
| 119 | + } |
| 120 | + |
| 121 | + uint32_t device_id = data_in[0] | (data_in[1] << 8) | (data_in[2] << 16); |
| 122 | + uint8_t flags_1 = data_in[3]; |
| 123 | + uint32_t volume = data_in[4] | (data_in[5] << 8) | (data_in[6] << 16); |
| 124 | + uint8_t flags_2 = data_in[7]; |
| 125 | + |
| 126 | + /* clang-format off */ |
| 127 | + data = data_make( |
| 128 | + "model", "", DATA_STRING, "Badger-ORION", |
| 129 | + "id", "ID", DATA_INT, device_id, |
| 130 | + "flags_1", "Flags-1", DATA_INT, flags_1, |
| 131 | + "volume_gal", "Volume", DATA_INT, volume, |
| 132 | + "flags_2", "Flags-2", DATA_INT, flags_2, |
| 133 | + "mic", "Integrity", DATA_STRING, "CRC", |
| 134 | + NULL); |
| 135 | + /* clang-format on */ |
| 136 | + |
| 137 | + decoder_output_data(decoder, data); |
| 138 | + return 0; |
| 139 | +} |
| 140 | + |
| 141 | +// Note: At this time the exact meaning of the flags is not known. |
| 142 | +static char *badger_output_fields[] = { |
| 143 | + "model", |
| 144 | + "id", |
| 145 | + "flags_1", |
| 146 | + "volume_gal", |
| 147 | + "flags_2", |
| 148 | + "mic", |
| 149 | + NULL, |
| 150 | +}; |
| 151 | + |
| 152 | +// Badger ORION water meter, |
| 153 | +// Frequency 916.45 MHz, Bitrate 100 kbps, Modulation NRZ FSK |
| 154 | +r_device badger_orion = { |
| 155 | + .name = "Badger ORION water meter, 100kbps (-f 916450000 -s 1200000)", // Minimum samplerate = 1.2 MHz (12 samples of 100kb/s) |
| 156 | + .modulation = FSK_PULSE_PCM, |
| 157 | + .short_width = 10, // Bit rate: 100 kb/s |
| 158 | + .long_width = 10, // NRZ encoding (bit width = pulse width) |
| 159 | + .reset_limit = 1000, // |
| 160 | + .decode_fn = &badger_orion_decode, |
| 161 | + .fields = badger_output_fields, |
| 162 | +}; |
0 commit comments