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

37
include/README Normal file
View File

@@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

6
include/compressor.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <Arduino.h>
bool compressBuffer(const uint8_t *in, size_t in_len, uint8_t *out, size_t out_max, size_t &out_len);
bool decompressBuffer(const uint8_t *in, size_t in_len, uint8_t *out, size_t out_max, size_t &out_len);

58
include/config.h Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <Arduino.h>
enum class DeviceRole : uint8_t {
Sender = 0,
Receiver = 1
};
enum class PayloadType : uint8_t {
MeterData = 0,
TestCode = 1,
TimeSync = 2
};
constexpr uint8_t PROTOCOL_VERSION = 1;
// Pin definitions
constexpr uint8_t PIN_LORA_SCK = 5;
constexpr uint8_t PIN_LORA_MISO = 19;
constexpr uint8_t PIN_LORA_MOSI = 27;
constexpr uint8_t PIN_LORA_NSS = 18;
constexpr uint8_t PIN_LORA_RST = 23;
constexpr uint8_t PIN_LORA_DIO0 = 26;
constexpr uint8_t PIN_OLED_SDA = 21;
constexpr uint8_t PIN_OLED_SCL = 22;
constexpr uint8_t PIN_OLED_RST = 16;
constexpr uint8_t OLED_I2C_ADDR = 0x3C;
constexpr uint8_t OLED_WIDTH = 128;
constexpr uint8_t OLED_HEIGHT = 64;
constexpr uint8_t PIN_BAT_ADC = 35;
constexpr uint8_t PIN_ROLE = 13;
constexpr uint8_t PIN_OLED_CTRL = 14;
constexpr uint8_t PIN_METER_RX = 34;
// LoRa settings
constexpr long LORA_FREQUENCY = 433E6;
constexpr uint8_t LORA_SPREADING_FACTOR = 12;
constexpr long LORA_BANDWIDTH = 125E3;
constexpr uint8_t LORA_CODING_RATE = 5;
constexpr uint8_t LORA_SYNC_WORD = 0x34;
// Timing
constexpr uint32_t SENDER_WAKE_INTERVAL_SEC = 30;
constexpr uint32_t TIME_SYNC_INTERVAL_SEC = 60;
constexpr uint32_t OLED_PAGE_INTERVAL_MS = 10000;
constexpr uint32_t OLED_AUTO_OFF_MS = 10UL * 60UL * 1000UL;
constexpr uint8_t NUM_SENDERS = 1;
inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = {
0xF19C
};
DeviceRole detect_role();

24
include/data_model.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <Arduino.h>
struct MeterData {
uint32_t ts_utc;
uint16_t short_id;
char device_id[16];
float energy_total_kwh;
float phase_power_w[3];
float total_power_w;
float phase_voltage_v[3];
float battery_voltage_v;
uint8_t battery_percent;
bool valid;
};
struct SenderStatus {
MeterData last_data;
uint32_t last_update_ts_utc;
bool has_data;
};
void init_device_ids(uint16_t &short_id, char *device_id, size_t device_id_len);

19
include/display_ui.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <Arduino.h>
#include "config.h"
#include "data_model.h"
void display_init();
void display_set_role(DeviceRole role);
void display_set_self_ids(uint16_t short_id, const char *device_id);
void display_set_sender_statuses(const SenderStatus *statuses, uint8_t count);
void display_set_last_meter(const MeterData &data);
void display_set_last_read(bool ok, uint32_t ts_utc);
void display_set_last_tx(bool ok, uint32_t ts_utc);
void display_set_receiver_status(bool ap_mode, const char *ssid, bool mqtt_ok);
void display_tick();
#ifdef ENABLE_TEST_MODE
void display_set_test_code(const char *code);
void display_set_test_code_for_sender(uint8_t index, const char *code);
#endif

7
include/json_codec.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include <Arduino.h>
#include "data_model.h"
bool meterDataToJson(const MeterData &data, String &out_json);
bool jsonToMeterData(const String &json, MeterData &data);

19
include/lora_transport.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <Arduino.h>
#include "config.h"
constexpr size_t LORA_MAX_PAYLOAD = 200;
struct LoraPacket {
uint8_t protocol_version;
DeviceRole role;
uint16_t device_id_short;
PayloadType payload_type;
uint8_t payload[LORA_MAX_PAYLOAD];
size_t payload_len;
};
void lora_init();
bool lora_send(const LoraPacket &pkt);
bool lora_receive(LoraPacket &pkt, uint32_t timeout_ms);

7
include/meter_driver.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include <Arduino.h>
#include "data_model.h"
void meter_init();
bool meter_read(MeterData &data);

13
include/mqtt_client.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
#include "data_model.h"
#include "wifi_manager.h"
void mqtt_init(const WifiMqttConfig &config, const char *device_id);
void mqtt_loop();
bool mqtt_is_connected();
bool mqtt_publish_state(const MeterData &data);
#ifdef ENABLE_TEST_MODE
bool mqtt_publish_test(const char *device_id, const String &payload);
#endif

9
include/power_manager.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <Arduino.h>
#include "data_model.h"
void power_sender_init();
void power_receiver_init();
void read_battery(MeterData &data);
void go_to_deep_sleep(uint32_t seconds);

9
include/test_mode.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <Arduino.h>
#include "data_model.h"
#ifdef ENABLE_TEST_MODE
void test_sender_loop(uint16_t short_id, const char *device_id);
void test_receiver_loop(SenderStatus *statuses, uint8_t count);
#endif

12
include/time_manager.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <Arduino.h>
#include "lora_transport.h"
void time_receiver_init(const char *ntp_server_1, const char *ntp_server_2);
uint32_t time_get_utc();
bool time_is_synced();
void time_set_utc(uint32_t epoch);
void time_send_timesync(uint16_t device_id_short);
bool time_handle_timesync_payload(const uint8_t *payload, size_t len);
void time_get_local_hhmm(char *out, size_t out_len);

10
include/web_server.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <Arduino.h>
#include "data_model.h"
#include "wifi_manager.h"
void web_server_begin_ap(const SenderStatus *statuses, uint8_t count);
void web_server_begin_sta(const SenderStatus *statuses, uint8_t count);
void web_server_set_config(const WifiMqttConfig &config);
void web_server_loop();

25
include/wifi_manager.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <Arduino.h>
#include <Preferences.h>
struct WifiMqttConfig {
String ssid;
String password;
String mqtt_host;
uint16_t mqtt_port;
String mqtt_user;
String mqtt_pass;
String ntp_server_1;
String ntp_server_2;
bool valid;
};
void wifi_manager_init();
bool wifi_load_config(WifiMqttConfig &config);
bool wifi_save_config(const WifiMqttConfig &config);
bool wifi_connect_sta(const WifiMqttConfig &config, uint32_t timeout_ms = 10000);
void wifi_start_ap(const char *ap_ssid, const char *ap_pass);
bool wifi_is_connected();
String wifi_get_ssid();