|
| 1 | +/* |
| 2 | + * meter_decode.c - Pure C meter byte decoding for Modbus energy meters |
| 3 | + * |
| 4 | + * Extracted from meter.cpp combineBytes() and decodeMeasurement(). |
| 5 | + * No platform dependencies. |
| 6 | + */ |
| 7 | + |
| 8 | +#include "meter_decode.h" |
| 9 | +#include <string.h> |
| 10 | + |
| 11 | +/* Power-of-10 lookup table (matches firmware's pow_10[]) */ |
| 12 | +static const unsigned long pow10_table[10] = { |
| 13 | + 1, 10, 100, 1000, 10000, 100000, |
| 14 | + 1000000, 10000000, 100000000, 1000000000 |
| 15 | +}; |
| 16 | + |
| 17 | +uint8_t meter_register_size(meter_datatype_t datatype) |
| 18 | +{ |
| 19 | + return (datatype == METER_DATATYPE_INT16) ? 2 : 4; |
| 20 | +} |
| 21 | + |
| 22 | +void meter_combine_bytes(void *out, const uint8_t *buf, uint8_t pos, |
| 23 | + uint8_t endianness, meter_datatype_t datatype) |
| 24 | +{ |
| 25 | + if (!out || !buf) return; |
| 26 | + |
| 27 | + /* Target is little-endian (ESP32, x86, ARM) */ |
| 28 | + char *p = (char *)out; |
| 29 | + |
| 30 | + switch (endianness) { |
| 31 | + case ENDIANNESS_LBF_LWF: /* low byte first, low word first (LE) */ |
| 32 | + *p++ = (char)buf[pos + 0]; |
| 33 | + *p++ = (char)buf[pos + 1]; |
| 34 | + if (datatype != METER_DATATYPE_INT16) { |
| 35 | + *p++ = (char)buf[pos + 2]; |
| 36 | + *p = (char)buf[pos + 3]; |
| 37 | + } |
| 38 | + break; |
| 39 | + |
| 40 | + case ENDIANNESS_LBF_HWF: /* low byte first, high word first */ |
| 41 | + if (datatype != METER_DATATYPE_INT16) { |
| 42 | + *p++ = (char)buf[pos + 2]; |
| 43 | + *p++ = (char)buf[pos + 3]; |
| 44 | + } |
| 45 | + *p++ = (char)buf[pos + 0]; |
| 46 | + *p = (char)buf[pos + 1]; |
| 47 | + break; |
| 48 | + |
| 49 | + case ENDIANNESS_HBF_LWF: /* high byte first, low word first */ |
| 50 | + *p++ = (char)buf[pos + 1]; |
| 51 | + *p++ = (char)buf[pos + 0]; |
| 52 | + if (datatype != METER_DATATYPE_INT16) { |
| 53 | + *p++ = (char)buf[pos + 3]; |
| 54 | + *p = (char)buf[pos + 2]; |
| 55 | + } |
| 56 | + break; |
| 57 | + |
| 58 | + case ENDIANNESS_HBF_HWF: /* high byte first, high word first (BE) */ |
| 59 | + if (datatype != METER_DATATYPE_INT16) { |
| 60 | + *p++ = (char)buf[pos + 3]; |
| 61 | + *p++ = (char)buf[pos + 2]; |
| 62 | + } |
| 63 | + *p++ = (char)buf[pos + 1]; |
| 64 | + *p = (char)buf[pos + 0]; |
| 65 | + break; |
| 66 | + |
| 67 | + default: |
| 68 | + break; |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +meter_reading_t meter_decode_value(const uint8_t *buf, uint8_t index, |
| 73 | + uint8_t endianness, meter_datatype_t datatype, |
| 74 | + int8_t divisor) |
| 75 | +{ |
| 76 | + meter_reading_t result = {0, 0}; |
| 77 | + |
| 78 | + if (!buf) return result; |
| 79 | + if (datatype >= METER_DATATYPE_MAX) return result; |
| 80 | + |
| 81 | + /* Validate divisor range for pow10 lookup */ |
| 82 | + int8_t abs_div = (divisor >= 0) ? divisor : (int8_t)(-divisor); |
| 83 | + if (abs_div >= 10) return result; |
| 84 | + |
| 85 | + uint8_t reg_size = meter_register_size(datatype); |
| 86 | + uint8_t pos = index * reg_size; |
| 87 | + |
| 88 | + if (datatype == METER_DATATYPE_FLOAT32) { |
| 89 | + float f_combined = 0.0f; |
| 90 | + meter_combine_bytes(&f_combined, buf, pos, endianness, datatype); |
| 91 | + if (divisor >= 0) { |
| 92 | + result.value = (int32_t)(f_combined / (int32_t)pow10_table[divisor]); |
| 93 | + } else { |
| 94 | + result.value = (int32_t)(f_combined * (int32_t)pow10_table[-divisor]); |
| 95 | + } |
| 96 | + } else { |
| 97 | + int32_t i_combined = 0; |
| 98 | + meter_combine_bytes(&i_combined, buf, pos, endianness, datatype); |
| 99 | + if (datatype == METER_DATATYPE_INT16) { |
| 100 | + /* Sign extend 16-bit into 32-bit */ |
| 101 | + i_combined = (int32_t)((int16_t)i_combined); |
| 102 | + } |
| 103 | + if (divisor >= 0) { |
| 104 | + result.value = i_combined / (int32_t)pow10_table[divisor]; |
| 105 | + } else { |
| 106 | + result.value = i_combined * (int32_t)pow10_table[-divisor]; |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + result.valid = 1; |
| 111 | + return result; |
| 112 | +} |
0 commit comments