#include "json_codec.h" #include #include #include "power_manager.h" static float round2(float value) { if (isnan(value)) { return value; } return roundf(value * 100.0f) / 100.0f; } static const char *short_id_from_device_id(const char *device_id) { if (!device_id) { return ""; } size_t len = strlen(device_id); if (len >= 4) { return device_id + (len - 4); } return device_id; } static void format_float_2(char *buf, size_t buf_len, float value) { if (!buf || buf_len == 0) { return; } if (isnan(value)) { snprintf(buf, buf_len, "null"); return; } snprintf(buf, buf_len, "%.2f", round2(value)); } bool meterDataToJson(const MeterData &data, String &out_json) { StaticJsonDocument<256> doc; doc["id"] = short_id_from_device_id(data.device_id); doc["ts"] = data.ts_utc; char buf[16]; format_float_2(buf, sizeof(buf), data.energy_total_kwh); doc["e_kwh"] = serialized(buf); format_float_2(buf, sizeof(buf), data.total_power_w); doc["p_w"] = serialized(buf); format_float_2(buf, sizeof(buf), data.phase_power_w[0]); doc["p1_w"] = serialized(buf); format_float_2(buf, sizeof(buf), data.phase_power_w[1]); doc["p2_w"] = serialized(buf); format_float_2(buf, sizeof(buf), data.phase_power_w[2]); doc["p3_w"] = serialized(buf); format_float_2(buf, sizeof(buf), data.phase_voltage_v[0]); doc["v1_v"] = serialized(buf); format_float_2(buf, sizeof(buf), data.phase_voltage_v[1]); doc["v2_v"] = serialized(buf); format_float_2(buf, sizeof(buf), data.phase_voltage_v[2]); doc["v3_v"] = serialized(buf); format_float_2(buf, sizeof(buf), data.battery_voltage_v); doc["bat_v"] = serialized(buf); doc["bat_pct"] = data.battery_percent; if (data.link_valid) { doc["rssi"] = data.link_rssi_dbm; doc["snr"] = data.link_snr_db; } if (data.err_meter_read > 0) { doc["err_m"] = data.err_meter_read; } if (data.err_decode > 0) { doc["err_d"] = data.err_decode; } if (data.err_lora_tx > 0) { doc["err_tx"] = data.err_lora_tx; } if (data.last_error != FaultType::None) { doc["err_last"] = static_cast(data.last_error); } out_json = ""; size_t len = serializeJson(doc, out_json); return len > 0 && len < 256; } static float read_float_or_legacy(JsonDocument &doc, const char *key, const char *legacy_key) { if (doc[key].isNull()) { return doc[legacy_key] | NAN; } return doc[key] | NAN; } bool jsonToMeterData(const String &json, MeterData &data) { StaticJsonDocument<256> doc; DeserializationError err = deserializeJson(doc, json); if (err) { return false; } const char *id = doc["id"] | ""; if (strlen(id) == 4) { snprintf(data.device_id, sizeof(data.device_id), "dd3-%s", id); } else { strncpy(data.device_id, id, sizeof(data.device_id)); } data.device_id[sizeof(data.device_id) - 1] = '\0'; data.ts_utc = doc["ts"] | 0; data.energy_total_kwh = read_float_or_legacy(doc, "e_kwh", "energy_kwh"); data.total_power_w = read_float_or_legacy(doc, "p_w", "p_total_w"); data.phase_power_w[0] = doc["p1_w"] | NAN; data.phase_power_w[1] = doc["p2_w"] | NAN; data.phase_power_w[2] = doc["p3_w"] | NAN; data.phase_voltage_v[0] = doc["v1_v"] | NAN; data.phase_voltage_v[1] = doc["v2_v"] | NAN; data.phase_voltage_v[2] = doc["v3_v"] | NAN; data.battery_voltage_v = doc["bat_v"] | NAN; if (doc["bat_pct"].isNull() && !isnan(data.battery_voltage_v)) { data.battery_percent = battery_percent_from_voltage(data.battery_voltage_v); } else { data.battery_percent = doc["bat_pct"] | 0; } data.valid = true; data.link_valid = false; data.link_rssi_dbm = 0; data.link_snr_db = NAN; data.err_meter_read = doc["err_m"] | 0; data.err_decode = doc["err_d"] | 0; data.err_lora_tx = doc["err_tx"] | 0; data.last_error = static_cast(doc["err_last"] | 0); if (strlen(data.device_id) >= 8) { const char *suffix = data.device_id + strlen(data.device_id) - 4; data.short_id = static_cast(strtoul(suffix, nullptr, 16)); } return true; } bool meterBatchToJson(const MeterData *samples, size_t count, String &out_json, const FaultCounters *faults, FaultType last_error) { if (!samples || count == 0) { return false; } DynamicJsonDocument doc(8192); doc["id"] = short_id_from_device_id(samples[count - 1].device_id); doc["bat_v"] = round2(samples[count - 1].battery_voltage_v); doc["bat_pct"] = samples[count - 1].battery_percent; if (faults) { if (faults->meter_read_fail > 0) { doc["err_m"] = faults->meter_read_fail; } if (faults->lora_tx_fail > 0) { doc["err_tx"] = faults->lora_tx_fail; } } if (last_error != FaultType::None) { doc["err_last"] = static_cast(last_error); } JsonArray arr = doc.createNestedArray("s"); for (size_t i = 0; i < count; ++i) { JsonArray row = arr.createNestedArray(); row.add(samples[i].ts_utc); row.add(round2(samples[i].energy_total_kwh)); row.add(round2(samples[i].total_power_w)); row.add(round2(samples[i].phase_power_w[0])); row.add(round2(samples[i].phase_power_w[1])); row.add(round2(samples[i].phase_power_w[2])); row.add(round2(samples[i].phase_voltage_v[0])); row.add(round2(samples[i].phase_voltage_v[1])); row.add(round2(samples[i].phase_voltage_v[2])); row.add(samples[i].valid ? 1 : 0); } out_json = ""; size_t len = serializeJson(doc, out_json); return len > 0; } bool jsonToMeterBatch(const String &json, MeterData *out_samples, size_t max_count, size_t &out_count) { out_count = 0; if (!out_samples || max_count == 0) { return false; } DynamicJsonDocument doc(8192); DeserializationError err = deserializeJson(doc, json); if (err) { return false; } JsonArray arr = doc["s"].as(); if (arr.isNull()) { return false; } const char *id = doc["id"] | ""; float bat_v = doc["bat_v"] | NAN; uint8_t bat_pct = doc["bat_pct"] | 0; uint32_t err_m = doc["err_m"] | 0; uint32_t err_tx = doc["err_tx"] | 0; FaultType last_error = static_cast(doc["err_last"] | 0); size_t idx = 0; for (JsonArray row : arr) { if (idx >= max_count) { break; } MeterData &data = out_samples[idx]; data = {}; if (strlen(id) == 4) { snprintf(data.device_id, sizeof(data.device_id), "dd3-%s", id); } else { strncpy(data.device_id, id, sizeof(data.device_id)); } data.device_id[sizeof(data.device_id) - 1] = '\0'; data.ts_utc = row[0] | 0; data.energy_total_kwh = row[1] | NAN; data.total_power_w = row[2] | NAN; data.phase_power_w[0] = row[3] | NAN; data.phase_power_w[1] = row[4] | NAN; data.phase_power_w[2] = row[5] | NAN; data.phase_voltage_v[0] = row[6] | NAN; data.phase_voltage_v[1] = row[7] | NAN; data.phase_voltage_v[2] = row[8] | NAN; data.valid = (row[9] | 1) != 0; data.battery_voltage_v = bat_v; if (doc["bat_pct"].isNull() && !isnan(bat_v)) { data.battery_percent = battery_percent_from_voltage(bat_v); } else { data.battery_percent = bat_pct; } data.link_valid = false; data.link_rssi_dbm = 0; data.link_snr_db = NAN; data.err_meter_read = err_m; data.err_decode = 0; data.err_lora_tx = err_tx; data.last_error = last_error; if (strlen(data.device_id) >= 8) { const char *suffix = data.device_id + strlen(data.device_id) - 4; data.short_id = static_cast(strtoul(suffix, nullptr, 16)); } idx++; } out_count = idx; return idx > 0; }