Initial commit

This commit is contained in:
2026-01-20 01:39:06 +01:00
commit 6f308ad590
34 changed files with 2068 additions and 0 deletions

182
src/main.cpp Normal file
View File

@@ -0,0 +1,182 @@
#include <Arduino.h>
#include "config.h"
#include "data_model.h"
#include "json_codec.h"
#include "compressor.h"
#include "lora_transport.h"
#include "meter_driver.h"
#include "power_manager.h"
#include "time_manager.h"
#include "wifi_manager.h"
#include "mqtt_client.h"
#include "web_server.h"
#include "display_ui.h"
#include "test_mode.h"
static DeviceRole g_role = DeviceRole::Sender;
static uint16_t g_short_id = 0;
static char g_device_id[16] = "";
static SenderStatus g_sender_statuses[NUM_SENDERS];
static bool g_ap_mode = false;
static WifiMqttConfig g_cfg;
static uint32_t g_last_timesync_ms = 0;
static void init_sender_statuses() {
for (uint8_t i = 0; i < NUM_SENDERS; ++i) {
g_sender_statuses[i] = {};
g_sender_statuses[i].has_data = false;
g_sender_statuses[i].last_update_ts_utc = 0;
g_sender_statuses[i].last_data.short_id = EXPECTED_SENDER_IDS[i];
snprintf(g_sender_statuses[i].last_data.device_id, sizeof(g_sender_statuses[i].last_data.device_id), "dd3-%04X", EXPECTED_SENDER_IDS[i]);
}
}
void setup() {
Serial.begin(115200);
delay(200);
g_role = detect_role();
init_device_ids(g_short_id, g_device_id, sizeof(g_device_id));
lora_init();
display_init();
display_set_role(g_role);
display_set_self_ids(g_short_id, g_device_id);
if (g_role == DeviceRole::Sender) {
power_sender_init();
meter_init();
} else {
power_receiver_init();
wifi_manager_init();
init_sender_statuses();
display_set_sender_statuses(g_sender_statuses, NUM_SENDERS);
bool has_cfg = wifi_load_config(g_cfg);
if (has_cfg && wifi_connect_sta(g_cfg)) {
g_ap_mode = false;
time_receiver_init(g_cfg.ntp_server_1.c_str(), g_cfg.ntp_server_2.c_str());
mqtt_init(g_cfg, g_device_id);
web_server_set_config(g_cfg);
web_server_begin_sta(g_sender_statuses, NUM_SENDERS);
} else {
g_ap_mode = true;
char ap_ssid[32];
snprintf(ap_ssid, sizeof(ap_ssid), "DD3-Bridge-%04X", g_short_id);
wifi_start_ap(ap_ssid, "changeme123");
if (g_cfg.ntp_server_1.isEmpty()) {
g_cfg.ntp_server_1 = "pool.ntp.org";
}
if (g_cfg.ntp_server_2.isEmpty()) {
g_cfg.ntp_server_2 = "time.nist.gov";
}
web_server_set_config(g_cfg);
web_server_begin_ap(g_sender_statuses, NUM_SENDERS);
}
}
}
static void sender_cycle() {
MeterData data = {};
data.short_id = g_short_id;
strncpy(data.device_id, g_device_id, sizeof(data.device_id));
bool meter_ok = meter_read(data);
read_battery(data);
uint32_t now_utc = time_get_utc();
data.ts_utc = now_utc > 0 ? now_utc : millis() / 1000;
data.valid = meter_ok;
display_set_last_meter(data);
display_set_last_read(meter_ok, data.ts_utc);
String json;
bool json_ok = meterDataToJson(data, json);
bool tx_ok = false;
if (json_ok) {
uint8_t compressed[LORA_MAX_PAYLOAD];
size_t compressed_len = 0;
if (compressBuffer(reinterpret_cast<const uint8_t *>(json.c_str()), json.length(), compressed, sizeof(compressed), compressed_len)) {
LoraPacket pkt = {};
pkt.protocol_version = PROTOCOL_VERSION;
pkt.role = DeviceRole::Sender;
pkt.device_id_short = g_short_id;
pkt.payload_type = PayloadType::MeterData;
pkt.payload_len = compressed_len;
memcpy(pkt.payload, compressed, compressed_len);
tx_ok = lora_send(pkt);
}
}
display_set_last_tx(tx_ok, data.ts_utc);
display_tick();
LoraPacket rx = {};
if (lora_receive(rx, 200) && rx.protocol_version == PROTOCOL_VERSION && rx.payload_type == PayloadType::TimeSync) {
time_handle_timesync_payload(rx.payload, rx.payload_len);
}
delay(50);
go_to_deep_sleep(SENDER_WAKE_INTERVAL_SEC);
}
static void receiver_loop() {
LoraPacket pkt = {};
if (lora_receive(pkt, 0) && pkt.protocol_version == PROTOCOL_VERSION && pkt.payload_type == PayloadType::MeterData) {
uint8_t decompressed[256];
size_t decompressed_len = 0;
if (decompressBuffer(pkt.payload, pkt.payload_len, decompressed, sizeof(decompressed), decompressed_len)) {
decompressed[decompressed_len] = '\0';
MeterData data = {};
if (jsonToMeterData(String(reinterpret_cast<const char *>(decompressed)), data)) {
for (uint8_t i = 0; i < NUM_SENDERS; ++i) {
if (pkt.device_id_short == EXPECTED_SENDER_IDS[i]) {
data.short_id = pkt.device_id_short;
g_sender_statuses[i].last_data = data;
g_sender_statuses[i].last_update_ts_utc = data.ts_utc;
g_sender_statuses[i].has_data = true;
mqtt_publish_state(data);
break;
}
}
}
}
}
if (!g_ap_mode && millis() - g_last_timesync_ms > TIME_SYNC_INTERVAL_SEC * 1000UL) {
g_last_timesync_ms = millis();
time_send_timesync(g_short_id);
}
mqtt_loop();
web_server_loop();
display_set_receiver_status(g_ap_mode, wifi_is_connected() ? wifi_get_ssid().c_str() : "AP", mqtt_is_connected());
display_tick();
}
void loop() {
#ifdef ENABLE_TEST_MODE
if (g_role == DeviceRole::Sender) {
test_sender_loop(g_short_id, g_device_id);
display_tick();
delay(50);
} else {
test_receiver_loop(g_sender_statuses, NUM_SENDERS);
mqtt_loop();
web_server_loop();
display_set_receiver_status(g_ap_mode, wifi_is_connected() ? wifi_get_ssid().c_str() : "AP", mqtt_is_connected());
display_tick();
delay(50);
}
return;
#endif
if (g_role == DeviceRole::Sender) {
sender_cycle();
} else {
receiver_loop();
}
}