From e07c277a59d81e2f1cad5fc50b56b279cdf411a5 Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 19:58:48 +0200 Subject: [PATCH 01/28] refactor: refactor utils.cpp into seperate files --- node/src/drivers/power.cpp | 35 +++ node/src/{ => drivers}/sensor_logic.cpp | 0 node/src/drivers/storage.cpp | 49 +++ node/src/experimental/experimental.cpp | 109 +++++++ node/src/experimental/init_handler.cpp | 111 +++++++ node/src/network/config_handler.cpp | 39 +++ node/src/{ => network}/encode_payload.cpp | 0 node/src/utils.cpp | 363 ---------------------- node/src/utils/error_handler.cpp | 50 +++ 9 files changed, 393 insertions(+), 363 deletions(-) create mode 100644 node/src/drivers/power.cpp rename node/src/{ => drivers}/sensor_logic.cpp (100%) create mode 100644 node/src/drivers/storage.cpp create mode 100644 node/src/experimental/experimental.cpp create mode 100644 node/src/experimental/init_handler.cpp create mode 100644 node/src/network/config_handler.cpp rename node/src/{ => network}/encode_payload.cpp (100%) delete mode 100644 node/src/utils.cpp create mode 100644 node/src/utils/error_handler.cpp diff --git a/node/src/drivers/power.cpp b/node/src/drivers/power.cpp new file mode 100644 index 0000000..16e3185 --- /dev/null +++ b/node/src/drivers/power.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "debug_macros.h" +#include "sensor_logic.h" +#include "utils.h" +#include "protocol.h" + +Preferences prefs; +extern SX1262 radio; + +void power_down_radios() { + WiFi.mode(WIFI_OFF); + btStop(); + esp_wifi_stop(); + esp_bt_controller_disable(); +} + +bool sleep_particle_sensor() { + digitalWrite(PS_SET_PIN, LOW); + return true; +} + +bool wake_particle_sensor() { + pinMode(PS_SET_PIN, OUTPUT); + digitalWrite(PS_SET_PIN, HIGH); + return true; +} \ No newline at end of file diff --git a/node/src/sensor_logic.cpp b/node/src/drivers/sensor_logic.cpp similarity index 100% rename from node/src/sensor_logic.cpp rename to node/src/drivers/sensor_logic.cpp diff --git a/node/src/drivers/storage.cpp b/node/src/drivers/storage.cpp new file mode 100644 index 0000000..65e93c2 --- /dev/null +++ b/node/src/drivers/storage.cpp @@ -0,0 +1,49 @@ + +static void write_nvs(const char* key, uint8_t data_in) { + prefs.begin("mints", false); + if (!prefs.putUChar(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); + prefs.end(); +} + +static void write_nvs(const char* key, uint16_t data_in) { + prefs.begin("mints", false); + if (!prefs.putUShort(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); + prefs.end(); +} + +static void write_nvs(const char* key, uint32_t data_in) { + prefs.begin("mints", false); + if (!prefs.putULong(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); + prefs.end(); +} + +static void read_nvs(const char* key, uint8_t &data_out) { + prefs.begin("mints", true); + data_out = prefs.getUChar(key, 0); + prefs.end(); +} + +static void read_nvs(const char* key, uint16_t &data_out) { + prefs.begin("mints", true); + data_out = prefs.getUShort(key, 0); + prefs.end(); +} + +static void read_nvs(const char* key, uint32_t &data_out) { + prefs.begin("mints", true); + data_out = prefs.getULong(key, 0); + prefs.end(); +} + +void add_to_nvs(uint8_t boot_count, uint8_t pm1, uint8_t pm25, uint16_t noise) { + char key[16]; + + snprintf(key, sizeof(key), "i_%u_p1", boot_count); + write_nvs(key, pm1); + + snprintf(key, sizeof(key), "i_%u_p2", boot_count); + write_nvs(key, pm25); + + snprintf(key, sizeof(key), "i_%u_n", boot_count); + write_nvs(key, noise); +} diff --git a/node/src/experimental/experimental.cpp b/node/src/experimental/experimental.cpp new file mode 100644 index 0000000..7e32483 --- /dev/null +++ b/node/src/experimental/experimental.cpp @@ -0,0 +1,109 @@ +// static void start_config_msg(uint32_t time_ms) { +// msg_config_t header; + +// memcpy(config_tx_buffer, &header, sizeof(msg_config_t)); +// cursor = sizeof(msg_config_t); +// } + +// static void add_tlv(ConfigTag tag, size_t val) { +// tx_buffer[cursor++] = tag; +// tx_buffer[cursor++] = sizeof(val); +// memcpy(&tx_buffer[cursor], &val, sizeof(val)); +// cursor += sizeof(val); +// } + +// static void add_tlv(ConfigTag tag, uint8_t val) { +// tx_buffer[cursor++] = tag; +// tx_buffer[cursor++] = sizeof(uint8_t); +// tx_buffer[cursor++] = val; +// } + +// void send_update() { +// start_config_msg(millis()); + +// // example +// add_tlv(TAG_LORA_FREQ, 433.5f); +// add_tlv(TAG_LORA_SF, (uint8_t)12); +// add_tlv(TAG_NODE_ID, (uint8_t)0x05); + +// int state = radio.transmit(tx_buffer, cursor); +// } + +// static void handle_config_msg(size_t len) { +// if (len < sizeof(msg_config_t)) return; + +// msg_config_t* header = (msg_config_t*)config_rx_buffer; + +// size_t cursor = sizeof(msg_config_t); + +// while (cursor < len) { +// uint8_t tag = config_rx_buffer[cursor++]; +// uint8_t length = config_rx_buffer[cursor++]; +// uint8_t* value_ptr = &config_rx_buffer[cursor]; + +// switch (tag) { +// case TAG_LORA_FREQ: +// memcpy(&FREQUENCY, value_ptr, sizeof(FREQUENCY)); +// break; +// case TAG_LORA_BW: +// memcpy(&BANDWIDTH, value_ptr, sizeof(BANDWIDTH)); +// break; +// case TAG_LORA_SF: +// memcpy(&SPREADING_FACTOR, value_ptr, sizeof(SPREADING_FACTOR)); +// break; +// case TAG_LORA_CR: +// memcpy(&CODING_RATE, value_ptr, sizeof(CODING_RATE)); +// break; +// case TAG_LORA_SYNC: +// memcpy(&SYNC_WORD, value_ptr, sizeof(SYNC_WORD)); +// break; +// case TAG_LORA_PWR: +// memcpy(&POWER, value_ptr, sizeof(POWER)); +// break; +// case TAG_LORA_PREAMBLE: +// memcpy(&PREAMBLE_LEN, value_ptr, sizeof(PREAMBLE_LEN)); +// break; +// case TAG_LORA_GAIN: +// memcpy(&GAIN, value_ptr, sizeof(GAIN)); +// break; + +// case TAG_CPU_FREQ: +// memcpy(&CPU_FREQ_MHZ, value_ptr, sizeof(CPU_FREQ_MHZ)); +// break; +// case TAG_SLEEP_TIME: +// memcpy(&WAKEUP_INTERVAL_S, value_ptr, sizeof(WAKEUP_INTERVAL_S)); +// break; +// case TAG_MEASURE_WINDOW: +// memcpy(&MEASUREMENT_WINDOW_S, value_ptr, sizeof(MEASUREMENT_WINDOW_S)); +// break; + +// case TAG_PS_HEAT_UP: +// memcpy(&PS_HEAT_UP_TIME_S, value_ptr, sizeof(PS_HEAT_UP_TIME_S)); +// break; +// case TAG_PS_SAMPLE_TIME: +// memcpy(&PS_SAMPLE_TIME_mS, value_ptr, sizeof(PS_SAMPLE_TIME_mS)); +// break; +// case TAG_PS_TARGET: +// memcpy(&PS_TARGET_SAMPLES, value_ptr, sizeof(PS_TARGET_SAMPLES)); +// break; +// case TAG_NS_SAMPLE_TIME: +// memcpy(&NS_SAMPLE_TIME_mS, value_ptr, sizeof(NS_SAMPLE_TIME_mS)); +// break; + +// case TAG_NODE_ID: +// memcpy(&node_id, value_ptr, sizeof(node_id)); +// break; +// case TAG_MAX_RETRIES: +// memcpy(&MAX_TX_RETRIES, value_ptr, sizeof(MAX_TX_RETRIES)); +// break; +// case TAG_BUFFER_THRESH: +// memcpy(&BUFFERING_THRESHOLD, value_ptr, sizeof(BUFFERING_THRESHOLD)); +// break; + +// default: +// DEBUG_PRINTLN("Unknown config tag") +// break; +// } +// cursor += length; +// } +// } diff --git a/node/src/experimental/init_handler.cpp b/node/src/experimental/init_handler.cpp new file mode 100644 index 0000000..7d7f88a --- /dev/null +++ b/node/src/experimental/init_handler.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "debug_macros.h" +#include "sensor_logic.h" +#include "utils.h" +#include "protocol.h" + +Preferences prefs; +extern SX1262 radio; + +bool standby_mode() { + msg_clearance_t clearance; + int16_t state = radio.receive((uint8_t*)&clearance, sizeof(clearance)); + + uint32_t _ = millis(); + uint32_t gateway_time_stamp = clearance.time_stamp; + uint8_t type = clearance.type; + + if (type == MSG_TYPE_CLEARANCE && + !error_handler(state, false, UNDEFINED_ERROR, "LoRa_init failed to receive standby clearance from gateway")) { + + // ACKs the standby clearance + msg_ack_t msg_clearance_ack; + msg_clearance_ack.node_id = node_id; + msg_clearance_ack.ack_for = MSG_TYPE_CLEARANCE; + + state = radio.transmit((uint8_t*)&msg_clearance_ack, sizeof(msg_ack_t)); + + if (!error_handler(state, false, UNDEFINED_ERROR, "Failed to ack standby clearance")) { + radio.sleep(); + + //// Initialises the boot count + uint32_t offset = millis() - _; + uint32_t now_ms = clearance.time_stamp + offset; + uint32_t measurement_windows_ms = MEASUREMENT_WINDOW_S * S_TO_mS; + uint32_t time_past_in_window_ms = now_ms % measurement_windows_ms; + uint32_t time_to_sleep_us = (measurement_windows_ms - time_past_in_window_ms) * 1000ULL; + + boot_count = floor(now_ms / measurement_windows_ms); + DEBUG_PRINT("[STANDBY] Init boot_count set to: "); + DEBUG_PRINTLN(boot_count); + + DEBUG_PRINT("[STANDBY] Will sleep for (seconds): "); + DEBUG_PRINTLN(time_to_sleep_us / S_TO_uS); + + //// Sleeps till the closest window + esp_sleep_enable_timer_wakeup(time_to_sleep_us); + esp_deep_sleep_start(); + // wont be reached + } + } else { + return false; + } +} + +void initialise_node() { + int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); + error_handler(state, false, UNDEFINED_ERROR, "[INIT] LoRa_init initialisation"); + + uint8_t id_attempts = 0; + + while (needs_initialisation && id_attempts < MAX_ID_ATTEMPTS) { + DEBUG_PRINTLN("[INIT] Sends join request to the gateway"); + + //// Sends a join request to the gateway + msg_req_t msg_join_req; + msg_join_req.type = MSG_TYPE_JOIN_REQ; + msg_join_req.node_id = 0x00; + + state = radio.transmit((uint8_t*)&msg_join_req, sizeof(msg_join_req)); + + id_attempts++; + + DEBUG_PRINT("[INIT] Join request attempt nr.: "); + DEBUG_PRINTLN(id_attempts); + + if (!error_handler(state, false, UNDEFINED_ERROR, "[ERROR] LoRa_init failed to send join msg to the gateway")) { + //// Waits the for the join ack from the gateway + msg_ack_t msg_join_ack; + state = radio.receive((uint8_t*)&msg_join_ack, sizeof(msg_join_ack)); + + if (msg_join_ack.ack_for == MSG_TYPE_JOIN_REQ && + !error_handler(state, false, UNDEFINED_ERROR, "[ERROR] LoRa_init failed to receive join ack from the gateway")) { + + // Assigns the ID + node_id = msg_join_ack.node_id; + DEBUG_PRINT("[INIT] Assigned Node ID: "); + DEBUG_PRINTLN(node_id); + + // ACK the gateway + // msg_join_ack.node_id = node_id; + // msg_join_ack.ack_for = MSG_TYPE_JOIN_REQ; + // radio.transmit(msg_join_ack, sizeof(msg_join_ack)); + + needs_initialisation = false; + + //// Enters standby mode + standby_mode(); + } + } + } + DEBUG_PRINTLN("[ERROR] Max amount of id attempts reached. Reboot node."); +} diff --git a/node/src/network/config_handler.cpp b/node/src/network/config_handler.cpp new file mode 100644 index 0000000..8aaa7b0 --- /dev/null +++ b/node/src/network/config_handler.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "debug_macros.h" +#include "sensor_logic.h" +#include "utils.h" +#include "protocol.h" + +Preferences prefs; +extern SX1262 radio; + +void config_mode() { + int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); + error_handler(state, false, UNDEFINED_ERROR, "[config] radio initialisation"); + + state = radio.receive(config_rx_buffer, sizeof(config_rx_buffer)); + + if (state == RADIOLIB_ERR_NONE) { + if (config_rx_buffer[0] == MSG_TYPE_CONFIG) { + size_t len = radio.getPacketLength(); + + // handle_config_msg(len); + + // ACKs the gateway + msg_ack_t msg_ack; + msg_ack.node_id = node_id; + msg_ack.ack_for = MSG_TYPE_CONFIG; + state = radio.transmit((uint8_t*)&msg_ack, sizeof(payload_t)); + } + radio.sleep(); + } +} diff --git a/node/src/encode_payload.cpp b/node/src/network/encode_payload.cpp similarity index 100% rename from node/src/encode_payload.cpp rename to node/src/network/encode_payload.cpp diff --git a/node/src/utils.cpp b/node/src/utils.cpp deleted file mode 100644 index d93ac81..0000000 --- a/node/src/utils.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "debug_macros.h" -#include "sensor_logic.h" -#include "utils.h" -#include "protocol.h" - -Preferences prefs; -extern SX1262 radio; - -bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message) { - // RADIOLIB_ERR_NONE is def. as 0; - if (state != RADIOLIB_ERR_NONE) { - DEBUG_PRINT("[ERROR] "); - DEBUG_PRINT(message); - DEBUG_PRINT(" Code: "); - DEBUG_PRINTLN(state); - - if (inform_gateway) { - int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); - - if (state != RADIOLIB_ERR_NONE) { - DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio init failed."); - return true; - } - msg_error_t msg_error; - msg_error.node_id = node_id; - msg_error.error_id = error_code; - - state = radio.transmit((uint8_t*)&msg_error, sizeof(msg_error_t)); - - if (state != RADIOLIB_ERR_NONE) { - DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio transmit failed."); - return true; - } - } - - return true; - } else { - return false; - } -} - -void power_down_radios() { - WiFi.mode(WIFI_OFF); - btStop(); - esp_wifi_stop(); - esp_bt_controller_disable(); -} - -bool standby_mode() { - msg_clearance_t clearance; - int16_t state = radio.receive((uint8_t*)&clearance, sizeof(clearance)); - - uint32_t _ = millis(); - uint32_t gateway_time_stamp = clearance.time_stamp; - uint8_t type = clearance.type; - - if (type == MSG_TYPE_CLEARANCE && - !error_handler(state, false, UNDEFINED_ERROR, "LoRa_init failed to receive standby clearance from gateway")) { - - // ACKs the standby clearance - msg_ack_t msg_clearance_ack; - msg_clearance_ack.node_id = node_id; - msg_clearance_ack.ack_for = MSG_TYPE_CLEARANCE; - - state = radio.transmit((uint8_t*)&msg_clearance_ack, sizeof(msg_ack_t)); - - if (!error_handler(state, false, UNDEFINED_ERROR, "Failed to ack standby clearance")) { - radio.sleep(); - - //// Initialises the boot count - uint32_t offset = millis() - _; - uint32_t now_ms = clearance.time_stamp + offset; - uint32_t measurement_windows_ms = MEASUREMENT_WINDOW_S * S_TO_mS; - uint32_t time_past_in_window_ms = now_ms % measurement_windows_ms; - uint32_t time_to_sleep_us = (measurement_windows_ms - time_past_in_window_ms) * 1000ULL; - - boot_count = floor(now_ms / measurement_windows_ms); - DEBUG_PRINT("[STANDBY] Init boot_count set to: "); - DEBUG_PRINTLN(boot_count); - - DEBUG_PRINT("[STANDBY] Will sleep for (seconds): "); - DEBUG_PRINTLN(time_to_sleep_us / S_TO_uS); - - //// Sleeps till the closest window - esp_sleep_enable_timer_wakeup(time_to_sleep_us); - esp_deep_sleep_start(); - // wont be reached - } - } else { - return false; - } -} - -void initialise_node() { - int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); - error_handler(state, false, UNDEFINED_ERROR, "[INIT] LoRa_init initialisation"); - - uint8_t id_attempts = 0; - - while (needs_initialisation && id_attempts < MAX_ID_ATTEMPTS) { - DEBUG_PRINTLN("[INIT] Sends join request to the gateway"); - - //// Sends a join request to the gateway - msg_req_t msg_join_req; - msg_join_req.type = MSG_TYPE_JOIN_REQ; - msg_join_req.node_id = 0x00; - - state = radio.transmit((uint8_t*)&msg_join_req, sizeof(msg_join_req)); - - id_attempts++; - - DEBUG_PRINT("[INIT] Join request attempt nr.: "); - DEBUG_PRINTLN(id_attempts); - - if (!error_handler(state, false, UNDEFINED_ERROR, "[ERROR] LoRa_init failed to send join msg to the gateway")) { - //// Waits the for the join ack from the gateway - msg_ack_t msg_join_ack; - state = radio.receive((uint8_t*)&msg_join_ack, sizeof(msg_join_ack)); - - if (msg_join_ack.ack_for == MSG_TYPE_JOIN_REQ && - !error_handler(state, false, UNDEFINED_ERROR, "[ERROR] LoRa_init failed to receive join ack from the gateway")) { - - // Assigns the ID - node_id = msg_join_ack.node_id; - DEBUG_PRINT("[INIT] Assigned Node ID: "); - DEBUG_PRINTLN(node_id); - - // ACK the gateway - // msg_join_ack.node_id = node_id; - // msg_join_ack.ack_for = MSG_TYPE_JOIN_REQ; - // radio.transmit(msg_join_ack, sizeof(msg_join_ack)); - - needs_initialisation = false; - - //// Enters standby mode - standby_mode(); - } - } - } - DEBUG_PRINTLN("[ERROR] Max amount of id attempts reached. Reboot node."); -} - -bool sleep_particle_sensor() { - digitalWrite(PS_SET_PIN, LOW); - return true; -} - -bool wake_particle_sensor() { - pinMode(PS_SET_PIN, OUTPUT); - digitalWrite(PS_SET_PIN, HIGH); - return true; -} - -// static void start_config_msg(uint32_t time_ms) { -// msg_config_t header; - -// memcpy(config_tx_buffer, &header, sizeof(msg_config_t)); -// cursor = sizeof(msg_config_t); -// } - -// static void add_tlv(ConfigTag tag, size_t val) { -// tx_buffer[cursor++] = tag; -// tx_buffer[cursor++] = sizeof(val); -// memcpy(&tx_buffer[cursor], &val, sizeof(val)); -// cursor += sizeof(val); -// } - -// static void add_tlv(ConfigTag tag, uint8_t val) { -// tx_buffer[cursor++] = tag; -// tx_buffer[cursor++] = sizeof(uint8_t); -// tx_buffer[cursor++] = val; -// } - -// void send_update() { -// start_config_msg(millis()); - -// // example -// add_tlv(TAG_LORA_FREQ, 433.5f); -// add_tlv(TAG_LORA_SF, (uint8_t)12); -// add_tlv(TAG_NODE_ID, (uint8_t)0x05); - -// int state = radio.transmit(tx_buffer, cursor); -// } - -// static void handle_config_msg(size_t len) { -// if (len < sizeof(msg_config_t)) return; - -// msg_config_t* header = (msg_config_t*)config_rx_buffer; - -// size_t cursor = sizeof(msg_config_t); - -// while (cursor < len) { -// uint8_t tag = config_rx_buffer[cursor++]; -// uint8_t length = config_rx_buffer[cursor++]; -// uint8_t* value_ptr = &config_rx_buffer[cursor]; - -// switch (tag) { -// case TAG_LORA_FREQ: -// memcpy(&FREQUENCY, value_ptr, sizeof(FREQUENCY)); -// break; -// case TAG_LORA_BW: -// memcpy(&BANDWIDTH, value_ptr, sizeof(BANDWIDTH)); -// break; -// case TAG_LORA_SF: -// memcpy(&SPREADING_FACTOR, value_ptr, sizeof(SPREADING_FACTOR)); -// break; -// case TAG_LORA_CR: -// memcpy(&CODING_RATE, value_ptr, sizeof(CODING_RATE)); -// break; -// case TAG_LORA_SYNC: -// memcpy(&SYNC_WORD, value_ptr, sizeof(SYNC_WORD)); -// break; -// case TAG_LORA_PWR: -// memcpy(&POWER, value_ptr, sizeof(POWER)); -// break; -// case TAG_LORA_PREAMBLE: -// memcpy(&PREAMBLE_LEN, value_ptr, sizeof(PREAMBLE_LEN)); -// break; -// case TAG_LORA_GAIN: -// memcpy(&GAIN, value_ptr, sizeof(GAIN)); -// break; - -// case TAG_CPU_FREQ: -// memcpy(&CPU_FREQ_MHZ, value_ptr, sizeof(CPU_FREQ_MHZ)); -// break; -// case TAG_SLEEP_TIME: -// memcpy(&WAKEUP_INTERVAL_S, value_ptr, sizeof(WAKEUP_INTERVAL_S)); -// break; -// case TAG_MEASURE_WINDOW: -// memcpy(&MEASUREMENT_WINDOW_S, value_ptr, sizeof(MEASUREMENT_WINDOW_S)); -// break; - -// case TAG_PS_HEAT_UP: -// memcpy(&PS_HEAT_UP_TIME_S, value_ptr, sizeof(PS_HEAT_UP_TIME_S)); -// break; -// case TAG_PS_SAMPLE_TIME: -// memcpy(&PS_SAMPLE_TIME_mS, value_ptr, sizeof(PS_SAMPLE_TIME_mS)); -// break; -// case TAG_PS_TARGET: -// memcpy(&PS_TARGET_SAMPLES, value_ptr, sizeof(PS_TARGET_SAMPLES)); -// break; -// case TAG_NS_SAMPLE_TIME: -// memcpy(&NS_SAMPLE_TIME_mS, value_ptr, sizeof(NS_SAMPLE_TIME_mS)); -// break; - -// case TAG_NODE_ID: -// memcpy(&node_id, value_ptr, sizeof(node_id)); -// break; -// case TAG_MAX_RETRIES: -// memcpy(&MAX_TX_RETRIES, value_ptr, sizeof(MAX_TX_RETRIES)); -// break; -// case TAG_BUFFER_THRESH: -// memcpy(&BUFFERING_THRESHOLD, value_ptr, sizeof(BUFFERING_THRESHOLD)); -// break; - -// default: -// DEBUG_PRINTLN("Unknown config tag") -// break; -// } -// cursor += length; -// } -// } - -void config_mode() { - int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); - error_handler(state, false, UNDEFINED_ERROR, "[config] radio initialisation"); - - state = radio.receive(config_rx_buffer, sizeof(config_rx_buffer)); - - if (state == RADIOLIB_ERR_NONE) { - if (config_rx_buffer[0] == MSG_TYPE_CONFIG) { - size_t len = radio.getPacketLength(); - - // handle_config_msg(len); - - // ACKs the gateway - msg_ack_t msg_ack; - msg_ack.node_id = node_id; - msg_ack.ack_for = MSG_TYPE_CONFIG; - state = radio.transmit((uint8_t*)&msg_ack, sizeof(payload_t)); - } - radio.sleep(); - } -} - -static void write_nvs(const char* key, uint8_t data_in) { - prefs.begin("mints", false); - if (!prefs.putUChar(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); - prefs.end(); -} - -static void write_nvs(const char* key, uint16_t data_in) { - prefs.begin("mints", false); - if (!prefs.putUShort(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); - prefs.end(); -} - -static void write_nvs(const char* key, uint32_t data_in) { - prefs.begin("mints", false); - if (!prefs.putULong(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); - prefs.end(); -} - -static void read_nvs(const char* key, uint8_t &data_out) { - prefs.begin("mints", true); - data_out = prefs.getUChar(key, 0); - prefs.end(); -} - -static void read_nvs(const char* key, uint16_t &data_out) { - prefs.begin("mints", true); - data_out = prefs.getUShort(key, 0); - prefs.end(); -} - -static void read_nvs(const char* key, uint32_t &data_out) { - prefs.begin("mints", true); - data_out = prefs.getULong(key, 0); - prefs.end(); -} - -void add_to_nvs(uint8_t boot_count, uint8_t pm1, uint8_t pm25, uint16_t noise) { - char key[16]; - - snprintf(key, sizeof(key), "i_%u_p1", boot_count); - write_nvs(key, pm1); - - snprintf(key, sizeof(key), "i_%u_p2", boot_count); - write_nvs(key, pm25); - - snprintf(key, sizeof(key), "i_%u_n", boot_count); - write_nvs(key, noise); -} - - - - -// void init_nvs() { -// // Initialises the flash storage at FIRST startup to match ram - -// // init preferences GLOBALLY -// // begin -// // end -// } - -// void set_config_variables() { -// // Updates the system variables based on values in nvs -// // use case: node has initialised, and updated system variables, before and is now being repowered (not woken from sleep) - -// // init preferences GLOBALLY -// // begin -// // end -// } \ No newline at end of file diff --git a/node/src/utils/error_handler.cpp b/node/src/utils/error_handler.cpp new file mode 100644 index 0000000..700f342 --- /dev/null +++ b/node/src/utils/error_handler.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "debug_macros.h" +#include "sensor_logic.h" +#include "utils.h" +#include "protocol.h" + +Preferences prefs; +extern SX1262 radio; + +bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message) { + // RADIOLIB_ERR_NONE is def. as 0; + if (state != RADIOLIB_ERR_NONE) { + DEBUG_PRINT("[ERROR] "); + DEBUG_PRINT(message); + DEBUG_PRINT(" Code: "); + DEBUG_PRINTLN(state); + + if (inform_gateway) { + int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); + + if (state != RADIOLIB_ERR_NONE) { + DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio init failed."); + return true; + } + msg_error_t msg_error; + msg_error.node_id = node_id; + msg_error.error_id = error_code; + + state = radio.transmit((uint8_t*)&msg_error, sizeof(msg_error_t)); + + if (state != RADIOLIB_ERR_NONE) { + DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio transmit failed."); + return true; + } + } + + return true; + } else { + return false; + } +} From 814b7fb0e912c89e336efb32377962323d208c8c Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 19:59:09 +0200 Subject: [PATCH 02/28] rem: remove main.cpp Deprecated --- node/src/main.cpp | 76 ----------------------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 node/src/main.cpp diff --git a/node/src/main.cpp b/node/src/main.cpp deleted file mode 100644 index 4704a12..0000000 --- a/node/src/main.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// #include -// #include -// #include -// #include -// #include -// #include -// #include - -// #include "config.h" -// #include "debug_macros.h" -// #include "encode_payload.h" -// #include "sensor_logic.h" -// #include "utils.h" - -// HM330X particle_sensor; -// uint8_t ps_sensor_buf[30]; -// ps_state_t ps_state; -// ps_result_t ps_result; - -// ns_state_t ns_state; -// ns_result_t ns_result; - -// RTC_DATA_ATTR payload_t payload; - -// SX1262 radio = new Module(PIN_NSS, PIN_DIO0, PIN_NRST, PIN_DIO1); - -// void setup() { -// power_down_radios(); -// setCpuFrequencyMhz(CPU_FREQ_MHZ); -// DEBUG_BEGIN(BAUD); - -// //// Node initialisation -// //if (needs_initialisation) initialise_node(); - -// // Initialise sensors -// if (particle_sensor.init()) error_handler(-1, true, PS_INIT_ERROR, "Particle sensor initialisation failed"); - -// //// Data collection -// sample_noise_sensor(); -// if (sleep_noise_sensor()) error_handler(-1, true, NS_SLEEP_ERROR, "Failed to put the noise sensor to sleep"); - -// sample_particle_sensor(); -// if (sleep_particle_sensor()) error_handler(-1, true, PS_SLEEP_ERROR, "Failed to put the particle sensor to sleep"); - -// //// Update RTC -// boot_count++; - -// //// TODO: Power down sensors -// //// updates the payload -// encode_payload(&payload, &ps_result, &ns_result); - -// //// send data -// if (buffering_counter <= (BUFFERING_THRESHOLD - 1)) { -// buffering_counter++; -// } else { -// srand((unsigned int)time(NULL) + node_id); -// delay((rand() % MAX_TX_DELAY_S) * S_TO_mS); - -// transmit_payload(&payload); -// buffering_counter = 0; -// memset(&payload, 0, sizeof(payload_t)); -// } - - -// //// Sleep -// DEBUG_PRINTLN("[END] Entering sleep"); -// radio.sleep(); -// // calculates the sleep time by subtracting the designated sleep time with the time it took to reach this line -// uint32_t sleep_time_us = (WAKEUP_INTERVAL_S * S_TO_uS) - (millis() * 1000UL); -// esp_sleep_enable_timer_wakeup(sleep_time_us); -// esp_deep_sleep_start(); -// } - -// void loop() { -// // keep empty -// } \ No newline at end of file From 95df037b59cbaafca0fd99506d743e528d9b6019 Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:07:36 +0200 Subject: [PATCH 03/28] refactor: refactor config.h into seperate files --- node/include/drivers/power.h | 27 ++++++++ node/include/{ => drivers}/sensor_logic.h | 2 +- node/include/drivers/storage.h | 14 +++++ node/include/experimental/experimental.h | 0 node/include/experimental/init_handler.h | 19 ++++++ node/include/network/config_handler.h | 11 ++++ node/include/{ => network}/encode_payload.h | 2 +- node/include/{ => network}/protocol.h | 2 +- node/include/utils.h | 68 --------------------- node/include/{ => utils}/debug_macros.h | 0 node/include/utils/error_handler.h | 17 ++++++ 11 files changed, 91 insertions(+), 71 deletions(-) create mode 100644 node/include/drivers/power.h rename node/include/{ => drivers}/sensor_logic.h (99%) create mode 100644 node/include/drivers/storage.h create mode 100644 node/include/experimental/experimental.h create mode 100644 node/include/experimental/init_handler.h create mode 100644 node/include/network/config_handler.h rename node/include/{ => network}/encode_payload.h (96%) rename node/include/{ => network}/protocol.h (99%) delete mode 100644 node/include/utils.h rename node/include/{ => utils}/debug_macros.h (100%) create mode 100644 node/include/utils/error_handler.h diff --git a/node/include/drivers/power.h b/node/include/drivers/power.h new file mode 100644 index 0000000..75cf83f --- /dev/null +++ b/node/include/drivers/power.h @@ -0,0 +1,27 @@ +#ifndef POWER_H +#define POWER_H + +/** + * @brief Disables all wireless communication. + */ +void power_down_radios(); + +/** + * @brief Sleeps the sensor. + * @return success + */ +bool sleep_particle_sensor(); + +/** + * @brief Sleeps the sensor. + * @return success + */ +bool sleep_noise_sensor(); + +/** + * @brief Wakes the sensor. + * @return success + */ +bool wake_particle_sensor(); + +#endif \ No newline at end of file diff --git a/node/include/sensor_logic.h b/node/include/drivers/sensor_logic.h similarity index 99% rename from node/include/sensor_logic.h rename to node/include/drivers/sensor_logic.h index 37273ce..ac1361c 100644 --- a/node/include/sensor_logic.h +++ b/node/include/drivers/sensor_logic.h @@ -1,7 +1,7 @@ #ifndef SENSOR_LOGIC_H #define SENSOR_LOGIC_H -#include +#include #include #include diff --git a/node/include/drivers/storage.h b/node/include/drivers/storage.h new file mode 100644 index 0000000..bb4be0e --- /dev/null +++ b/node/include/drivers/storage.h @@ -0,0 +1,14 @@ +#ifndef STORAGE_H +#define STORAGE_H + +#include + +/** + * @brief Saves a single set of sensor readings to flash using the boot count as the unique ID. + * @note pm1 => i_%u_p1 + * @note pm2,5 => i_%u_p2 + * @note noise => i_%u_n + */ +void add_to_nvs(uint8_t boot_count, uint8_t pm1, uint8_t pm25, uint16_t noise); + +#endif \ No newline at end of file diff --git a/node/include/experimental/experimental.h b/node/include/experimental/experimental.h new file mode 100644 index 0000000..e69de29 diff --git a/node/include/experimental/init_handler.h b/node/include/experimental/init_handler.h new file mode 100644 index 0000000..3af60a8 --- /dev/null +++ b/node/include/experimental/init_handler.h @@ -0,0 +1,19 @@ +#ifndef INIT_HANDLER_H +#define INIT_HANDLER_H + +/** + * @brief Initializes the LoRa radio and performs a handshake with the gateway + * to receive a unique node ID. + * @note Sets init_flag to false upon success. + * @note Is blocking. + */ +void initialise_node(); + +/** + * @brief Synchronizes with the gateway and sets a Deep Sleep timer until the next window. + * @return success + * @note Is blocking. + */ +bool standby_mode(); + +#endif \ No newline at end of file diff --git a/node/include/network/config_handler.h b/node/include/network/config_handler.h new file mode 100644 index 0000000..8f6b8d6 --- /dev/null +++ b/node/include/network/config_handler.h @@ -0,0 +1,11 @@ +#ifndef CONFIG_HANDLER_H +#define CONFIG_HANDLER_H + +/** + * @brief Sets the node to config mode - where it can receive updates to state RTC variables. + * @note Starts and sleeps the radio upon use. + * @note Acks the gateway upon received config message + */ +void config_mode(); + +#endif \ No newline at end of file diff --git a/node/include/encode_payload.h b/node/include/network/encode_payload.h similarity index 96% rename from node/include/encode_payload.h rename to node/include/network/encode_payload.h index 88659be..baa5b9b 100644 --- a/node/include/encode_payload.h +++ b/node/include/network/encode_payload.h @@ -1,7 +1,7 @@ #ifndef ENCODE_PAYLOAD_H #define ENCODE_PAYLOAD_H -#include +#include #include "sensor_logic.h" #include "protocol.h" diff --git a/node/include/protocol.h b/node/include/network/protocol.h similarity index 99% rename from node/include/protocol.h rename to node/include/network/protocol.h index 06eb510..f7bc5b3 100644 --- a/node/include/protocol.h +++ b/node/include/network/protocol.h @@ -1,7 +1,7 @@ #ifndef PROTOCOL_H #define PROTOCOL_H -#include +#include // Types of messages const uint8_t MSG_TYPE_ACK = 0xA0; diff --git a/node/include/utils.h b/node/include/utils.h deleted file mode 100644 index 739d0e2..0000000 --- a/node/include/utils.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H - -/** - * @brief Logic for handling radio status codes. - * @param state The return code from the radio library. - * @param message The error message to print if the state is not successful. - * @param inform_gateway True = sends a message to the gateway with the specified error_code. - * @param error_code the error code that gets sent to the gateway. - * @return true if an error occurred, false if successful. - * @note if inform_gateway is set to false, set error_code to UNDEFINED_ERROR - */ -bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message); - -/** - * @brief Disables all wireless communication. - */ -void power_down_radios(); - -/** - * @brief Initializes the LoRa radio and performs a handshake with the gateway - * to receive a unique node ID. - * @note Sets init_flag to false upon success. - * @note Is blocking. - */ -void initialise_node(); - -/** - * @brief Synchronizes with the gateway and sets a Deep Sleep timer until the next window. - * @return success - * @note Is blocking. - */ -bool standby_mode(); - -/** - * @brief Sleeps the sensor. - * @return success - */ -bool sleep_particle_sensor(); - -/** - * @brief Sleeps the sensor. - * @return success - */ -bool sleep_noise_sensor(); - -/** - * @brief Wakes the sensor. - * @return success - */ -bool wake_particle_sensor(); - -/** - * @brief Sets the node to config mode - where it can receive updates to state RTC variables. - * @note Starts and sleeps the radio upon use. - * @note Acks the gateway upon received config message - */ -void config_mode(); - -/** - * @brief Saves a single set of sensor readings to flash using the boot count as the unique ID. - * @note pm1 => i_%u_p1 - * @note pm2,5 => i_%u_p2 - * @note noise => i_%u_n - */ -void add_to_nvs(uint8_t boot_count, uint8_t pm1, uint8_t pm25, uint16_t noise); - -#endif \ No newline at end of file diff --git a/node/include/debug_macros.h b/node/include/utils/debug_macros.h similarity index 100% rename from node/include/debug_macros.h rename to node/include/utils/debug_macros.h diff --git a/node/include/utils/error_handler.h b/node/include/utils/error_handler.h new file mode 100644 index 0000000..5cd0d1c --- /dev/null +++ b/node/include/utils/error_handler.h @@ -0,0 +1,17 @@ +#ifndef ERROR_HANDLER_H +#define ERROR_HANDLER_H + +#include + +/** + * @brief Logic for handling radio status codes. + * @param state The return code from the radio library. + * @param message The error message to print if the state is not successful. + * @param inform_gateway True = sends a message to the gateway with the specified error_code. + * @param error_code the error code that gets sent to the gateway. + * @return true if an error occurred, false if successful. + * @note if inform_gateway is set to false, set error_code to UNDEFINED_ERROR + */ +bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message); + +#endif \ No newline at end of file From 86e4bfd92c851ced83e0373db1d0df16cc8d46bb Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:08:08 +0200 Subject: [PATCH 04/28] rem: remove lora_utilites/ --- .../LoRa-Ping-Pong-Test.ino | 170 ------------------ .../LoRa-Transmitter-Receiver-V2.ino | 96 ---------- .../LoRa-Transmitter-Receiver.ino | 115 ------------ .../LoRa Frequency Sniffing/Readme.txt | 3 - .../heltec_LoRa_sniffer.ino | 136 -------------- 5 files changed, 520 deletions(-) delete mode 100644 node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Ping-Pong-Test.ino delete mode 100644 node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver-V2.ino delete mode 100644 node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver.ino delete mode 100644 node/LoRa Utilities/LoRa Frequency Sniffing/Readme.txt delete mode 100644 node/LoRa Utilities/LoRa Frequency Sniffing/heltec_LoRa_sniffer.ino diff --git a/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Ping-Pong-Test.ino b/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Ping-Pong-Test.ino deleted file mode 100644 index 6586a23..0000000 --- a/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Ping-Pong-Test.ino +++ /dev/null @@ -1,170 +0,0 @@ -/* - RadioLib SX127x Ping-Pong Example - - This example is intended to run on two SX126x radios, - and send packets between the two. - - For default module settings, see the wiki page - https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx127xrfm9x---lora-modem - - For full API reference, see the GitHub Pages - https://jgromes.github.io/RadioLib/ -*/ -// include the library -#include - -// uncomment the following only on one -// of the nodes to initiate the pings -// #define INITIATING_NODE - -// SX1278 has the following connections: -// NSS pin: 10 -// DIO0 pin: 2 -// NRST pin: 9 -// DIO1 pin: 3 -SX1276 radio = new Module(18, 26, 14, 35); - -// or detect the pinout automatically using RadioBoards -// https://github.com/radiolib-org/RadioBoards -/* -#define RADIO_BOARD_AUTO -#include -Radio radio = new RadioModule(); -*/ - -// save transmission states between loops -int transmissionState = RADIOLIB_ERR_NONE; - -// flag to indicate transmission or reception state -bool transmitFlag = false; - -// flag to indicate that a packet was sent or received -volatile bool operationDone = false; - -// this function is called when a complete packet -// is transmitted or received by the module -// IMPORTANT: this function MUST be 'void' type -// and MUST NOT have any arguments! -#if defined(ESP8266) || defined(ESP32) -ICACHE_RAM_ATTR -#endif -void setFlag(void) -{ - // we sent or received packet, set the flag - operationDone = true; -} - -void setup() -{ - Serial.begin(115200); - - // initialize SX1278 with default settings - Serial.print(F("[SX1278] Initializing ... ")); - int state = radio.begin(868.1, 62.5, 12, 8, 0x12, 14, 8, 0); - if (state == RADIOLIB_ERR_NONE) - { - Serial.println(F("success!")); - } - else - { - Serial.print(F("failed, code ")); - Serial.println(state); - while (true) - { - delay(10); - } - } - - // set the function that will be called - // when new packet is received - radio.setDio0Action(setFlag, RISING); - -#if defined(INITIATING_NODE) - // send the first packet on this node - Serial.print(F("[SX1278] Sending first packet ... ")); - transmissionState = radio.startTransmit("Hello World!"); - transmitFlag = true; -#else - // start listening for LoRa packets on this node - Serial.print(F("[SX1278] Starting to listen ... ")); - state = radio.startReceive(); - if (state == RADIOLIB_ERR_NONE) - { - Serial.println(F("success!")); - } - else - { - Serial.print(F("failed, code ")); - Serial.println(state); - while (true) - { - delay(10); - } - } -#endif -} - -void loop() -{ - // check if the previous operation finished - if (operationDone) - { - // reset flag - operationDone = false; - - if (transmitFlag) - { - // the previous operation was transmission, listen for response - // print the result - if (transmissionState == RADIOLIB_ERR_NONE) - { - // packet was successfully sent - Serial.println(F("transmission finished!")); - } - else - { - Serial.print(F("failed, code ")); - Serial.println(transmissionState); - } - - // listen for response - radio.startReceive(); - transmitFlag = false; - } - else - { - // the previous operation was reception - // print data and send another packet - String str; - int state = radio.readData(str); - - if (state == RADIOLIB_ERR_NONE) - { - // packet was successfully received - Serial.println(F("[SX1278] Received packet!")); - - // print data of the packet - Serial.print(F("[SX1278] Data:\t\t")); - Serial.println(str); - - // print RSSI (Received Signal Strength Indicator) - Serial.print(F("[SX1278] RSSI:\t\t")); - Serial.print(radio.getRSSI()); - Serial.println(F(" dBm")); - - // print SNR (Signal-to-Noise Ratio) - Serial.print(F("[SX1278] SNR:\t\t")); - Serial.print(radio.getSNR()); - Serial.println(F(" dB")); - } - - // wait a second before transmitting again - delay(1000); - - // send another one - Serial.print(F("[SX1278] Sending another packet ... ")); - transmissionState = radio.startTransmit("Hello World!"); - transmitFlag = true; - } - } -} \ No newline at end of file diff --git a/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver-V2.ino b/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver-V2.ino deleted file mode 100644 index 9608012..0000000 --- a/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver-V2.ino +++ /dev/null @@ -1,96 +0,0 @@ -#include - -// För Heltec V2 pins, byt dessa sedan när vi byggt stationerna -// NSS: 18, DIO0: 26, RESET: 14, DIO1: 35 -SX1276 radio = new Module(18, 26, 14, 35); - -String msg = "Yippie, det funkade!"; // Meddelandet som skickas - -int receiveState = RADIOLIB_ERR_NONE; - -#define PRG_BTN 0 - -void transmitterMode(String message) -{ - Serial.println("Changing to transmitter mode."); - int transmitState = radio.transmit(message); - - Serial.println("Sending data..."); - - if (transmitState == RADIOLIB_ERR_NONE) - { - Serial.println(F("Message sent.")); - } - else - { - Serial.print(F("Failed to send: ")); - Serial.println(transmitState); - } -} - -int receiverModeNotification() -{ - Serial.println("Changing to receiver mode."); - // receiveState = radio.startReceive(); -} - -void receiverMode() -{ - String packet; - int state = radio.readData(packet); - - if (state == RADIOLIB_ERR_NONE) - { - Serial.print(F("Received message ")); - - Serial.println(packet); - - radio.startReceive(); - } -} - -void setup() -{ - Serial.begin(115200); - - pinMode(21, OUTPUT); - digitalWrite(21, LOW); - pinMode(PRG_BTN, INPUT_PULLUP); - - Serial.print(F("Initialising ")); - - // Carrier Freq, BW, SF, CR, SyncWord, Power(dBm), PreambleLength - int radioState = radio.begin(868.1, 125.0, 9, 7, 0x12, 17, 8); - - delay(500); - - if (radioState == RADIOLIB_ERR_NONE) - { - Serial.println(F("System ready!")); - radio.startReceive(); - } - else - { - Serial.print(F("Initialisation failed: ")); - Serial.println(radioState); - while (true) - ; - } -} - -void loop() -{ - if (digitalRead(PRG_BTN) == LOW) - { - transmitterMode(msg); - delay(300); - - while (digitalRead(PRG_BTN) == LOW) - ; - delay(100); - - receiveState = receiverModeNotification(); - } - - receiverMode(); -} \ No newline at end of file diff --git a/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver.ino b/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver.ino deleted file mode 100644 index 537d69a..0000000 --- a/node/LoRa Utilities/LoRa - Transmitter-Receiver/LoRa-Transmitter-Receiver/LoRa-Transmitter-Receiver.ino +++ /dev/null @@ -1,115 +0,0 @@ -#include -#include -#include // För OLED-skärmen - -// Heltec V2 Pin Definitions -#define SCK 5 -#define MISO 19 -#define MOSI 27 -#define SS 18 -#define RST 14 -#define DIO0 26 -#define PRG_BTN 0 - -long freqs[] = {868100000, 868300000, 868500000, 867100000, 867400000}; // Frequencies for Schweden (EU) -int set_frequency = freqs[0]; // Change between 0 to 4 for different frequencies. -char *user = "David"; // Change to the correct user -char *msg = "Hejsan!"; // Message to send -int mode = 0; // 0 is receiver mode, 1 is transmitter mode. Don't change. - -U8X8_SSD1306_128X64_NONAME_SW_I2C display(15, 4, 16); - -void transmitterMode() -{ - Serial.println("Changing to transmitter mode."); - display.drawString(0, 2, "LoRa Transmitter"); - display.drawString(0, 4, (String("Freq: ") + (set_frequency / 1000000.0) + " MHz").c_str()); - - delay(5000); - - display.clearLine(2); - display.clearLine(4); - - display.drawString(0, 0, "Sending..."); - delay(5000); - - display.clearLine(4); - display.drawString(0, 0, (String("Sent: ") + msg).c_str()); - - LoRa.beginPacket(); - LoRa.print(msg); - LoRa.print((String("Message sent by ") + user).c_str()); - LoRa.endPacket(); - delay(1000); - display.clearLine(0); -} - -void receiverMode() -{ - Serial.println("Changing to receiver mode."); - display.drawString(0, 2, "LoRa Receiver"); - display.drawString(0, 4, (String("Freq: ") + (set_frequency / 1000000) + " MHz").c_str()); -} - -void setup() -{ - Serial.begin(115200); - while (!Serial) - ; - pinMode(PRG_BTN, INPUT_PULLUP); - - display.begin(); - display.setFont(u8x8_font_chroma48medium8_r); - display.drawString(0, 0, "Init System"); - - SPI.begin(SCK, MISO, MOSI, SS); - LoRa.setPins(SS, RST, DIO0); - - delay(3000); - display.clearLine(0); - - if (!LoRa.begin(set_frequency)) - { - display.drawString(0, 2, "LoRa FAILED :()"); - while (1) - ; - } - - Serial.println("System ready."); - - receiverMode(); -} - -void loop() -{ - if (digitalRead(PRG_BTN) == LOW) - { // Button press, change mode - transmitterMode(); - - delay(300); - while (digitalRead(PRG_BTN) == LOW) - ; // Wait for button to be released - receiverMode(); - } - - int packetSize = LoRa.parsePacket(); - if (packetSize) - { - Serial.println("Received packet '"); - - display.clearLine(2); - display.clearLine(4); - display.drawString(0, 0, "Packet received!"); - - while (LoRa.available()) - { - Serial.print((char)LoRa.read()); - } - - Serial.print("' with RSSI "); // RSSI = Received Signal Strength Indicator - Serial.println(LoRa.packetRssi() + " dBm"); - - delay(1000); - display.clearLine(0); - } -} diff --git a/node/LoRa Utilities/LoRa Frequency Sniffing/Readme.txt b/node/LoRa Utilities/LoRa Frequency Sniffing/Readme.txt deleted file mode 100644 index 07c5a49..0000000 --- a/node/LoRa Utilities/LoRa Frequency Sniffing/Readme.txt +++ /dev/null @@ -1,3 +0,0 @@ -The Python file "monitor" is for reading the data and making a noise when LoRa packets are found. Change COM5 in the file to the port you are using. If you get "permission denied" error, make sure you are not uploading to the Arduino, using Serial Monitor, or anything else using the port. - -The Arduino file "heltec_LoRa_sniffer" is the actual sniffing code. Upload to arduino and change pins to fit your setup. \ No newline at end of file diff --git a/node/LoRa Utilities/LoRa Frequency Sniffing/heltec_LoRa_sniffer.ino b/node/LoRa Utilities/LoRa Frequency Sniffing/heltec_LoRa_sniffer.ino deleted file mode 100644 index febefa1..0000000 --- a/node/LoRa Utilities/LoRa Frequency Sniffing/heltec_LoRa_sniffer.ino +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include - -// Structure to hold our sensor data -typedef struct -{ - int pm1, pm25; - int noise_peak; -} __attribute__((packed)) payload_t; - -// Heltec V2 Pin Definitions -// NSS: 18, DIO0: 26, RST: 14, DIO1: 35 -SX1276 radio = new Module(18, 26, 14, 35); - -#define PRG_BTN 0 - -// Swedish Frequency List -float freqs[] = {868.1, 868.3, 868.5, 867.1, 867.4}; -int freqIndex = 0; -int maxFreqs = 5; - -// Display Pins for Heltec V2 (SDA: 15, SCL: 4, RST: 16) -U8X8_SSD1306_128X64_NONAME_SW_I2C display(15, 4, 16); - -void updateFrequency() -{ - int state = radio.setFrequency(freqs[freqIndex]); - - if (state == RADIOLIB_ERR_NONE) - { - display.clearLine(2); - display.setCursor(0, 2); - display.print("Freq: "); - display.print(freqs[freqIndex], 1); - display.print("MHz"); - - Serial.print("\n>>> Tuned to: "); - Serial.print(freqs[freqIndex]); - Serial.println(" MHz <<<"); - } - else - { - Serial.print("Freq set failed, code: "); - Serial.println(state); - } -} - -void setup() -{ - Serial.begin(115200); - pinMode(PRG_BTN, INPUT_PULLUP); - - display.begin(); - display.setFont(u8x8_font_chroma48medium8_r); - display.drawString(0, 0, "LoRa Sniffer"); - - Serial.print(F("[SX1276] Initializing ... ")); - - // Initialize: Freq, BW, SF, CR, SyncWord, Power, Preamble - int state = radio.begin(868.1, 125.0, 7, 8, 0x12, 10, 8); - - if (state == RADIOLIB_ERR_NONE) - { - Serial.println(F("success!")); - } - else - { - Serial.print(F("failed, code ")); - Serial.println(state); - display.drawString(0, 2, "Init Failed!"); - while (true) - ; - } - - updateFrequency(); -} - -void loop() -{ - // 1. Handle Button Press to switch frequencies - if (digitalRead(PRG_BTN) == LOW) - { - freqIndex = (freqIndex + 1) % maxFreqs; - updateFrequency(); - delay(300); // Debounce - while (digitalRead(PRG_BTN) == LOW) - ; // Wait for release - } - - // 2. Check for incoming packets - payload_t payload; - int state = radio.receive((uint8_t *)&payload, sizeof(payload_t)); - - if (state == RADIOLIB_ERR_NONE) - { - // Serial Output for Debugging - Serial.println("--- Packet Received ---"); - Serial.print("PM1: "); - Serial.println(payload.pm1); - Serial.print("PM2.5: "); - Serial.println(payload.pm25); - Serial.print("Noise: "); - Serial.println(payload.noise_peak); - Serial.print("RSSI: "); - Serial.print(radio.getRSSI()); - Serial.println(" dBm"); - Serial.println("-----------------------"); - - // Display Update - display.clearLine(4); - display.drawString(0, 4, "New Packet!"); - - // Print PM1, PM2.5, and Noise on Row 5 - // Format: "10:XX 25:XX N:XX" - char buffer[17]; - snprintf(buffer, sizeof(buffer), "10:%d 25:%d N:%d", payload.pm1, payload.pm25, payload.noise_peak); - display.clearLine(5); - display.drawString(0, 5, buffer); - - // Print RSSI and SNR on Row 6 - display.clearLine(6); - display.setCursor(0, 6); - display.print("R:"); - display.print((int)radio.getRSSI()); - display.print(" S:"); - display.print((int)radio.getSNR()); - } - else if (state == RADIOLIB_ERR_RX_TIMEOUT) - { - // Normal: No packet received in this polling window - } - else if (state == RADIOLIB_ERR_CRC_MISMATCH) - { - Serial.println("CRC error! (Corrupt packet)"); - } -} \ No newline at end of file From 28c3e0550afe39ce806f5d3ac75841f89a11e7b9 Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:12:23 +0200 Subject: [PATCH 05/28] feat: add node build script --- .gitignore | 1 + node/build.ps1 | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 node/build.ps1 diff --git a/.gitignore b/.gitignore index c87a66c..3215b82 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ database/node_modules/* database/src/node_modules/* gateway/src/secrets/* gateway/src/venv/* +node/build/* __pycache__ *.out *.exe diff --git a/node/build.ps1 b/node/build.ps1 new file mode 100644 index 0000000..9ac8712 --- /dev/null +++ b/node/build.ps1 @@ -0,0 +1,17 @@ +function build ($Src1, $Src2) { + if (-not (Test-Path ".\build")) { + New-Item -ItemType Directory -Path ".\build" -Force | Out-Null + } + + $Files = Get-ChildItem -Path "$Src1", "$Src2" -Recurse -File -ErrorAction SilentlyContinue + + foreach ($File in $Files) { + Copy-Item -Path $File.FullName -Destination ".\build" -Force -ErrorAction SilentlyContinue + } + + if (Test-Path ".\build\src.ino") { + Rename-Item -Path ".\build\src.ino" -NewName "build.ino" -Force -ErrorAction SilentlyContinue + } +} + +build ".\include" ".\src" \ No newline at end of file From b11a33ec73ba1d0164b08c584b9dc9c588ffdd11 Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:28:58 +0200 Subject: [PATCH 06/28] chore: update README in node/ --- node/README.md | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/node/README.md b/node/README.md index 72f7967..0e6b181 100644 --- a/node/README.md +++ b/node/README.md @@ -1,10 +1,33 @@ -This file is temporary and should be updated once the node has been initialised. +Updated 2026/05/24. -Updated 2026/04/20. +## Brief +The full workings of the node can be found within the authors' Bachelor thesis, linked in the main README. -# Node logic +config.h hosts all of the sytems changeable parameters. -## Initialisation -If the flag needs_initialisation has not been +The nodes core logic: +1. Wakeup +2. Disable peripherals +3. Sample the particle sensor +4. Sample the noise sensor +5. Encode the payload (add to the batch) + 6. Send the payload (when the batch is full) +7. Disable the radio +9. Sleep -## Main logic \ No newline at end of file +## Initialisation w/ Arduinos IDE +1. Navigate into node/ and execute the build script: + +```powershell +.\build.ps1 +``` + +2. Copy the path to build.ino within build/ +3. Within Arduinos IDE: + A. File + B. Open + C. Paste the path and open. +4. Now has all of the appropriate node files been opened within the IDE and the program can be flashed onto the microcontroller + +## Other +See the main README for the teams contact info. \ No newline at end of file From 96e378dbbff39044b2e36d3858b35f3215498afd Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:34:08 +0200 Subject: [PATCH 07/28] refactor: src.ino --- node/src/src.ino | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/node/src/src.ino b/node/src/src.ino index 4ce5c59..8a3e89c 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -26,31 +26,21 @@ SX1262 radio = new Module(PIN_NSS, PIN_DIO0, PIN_NRST, PIN_DIO1); void setup() { power_down_radios(); - //setCpuFrequencyMhz(CPU_FREQ_MHZ); - delay(5000); + delay(1000); + DEBUG_BEGIN(BAUD); delay(1000); - DEBUG_PRINTLN("[START] Entering"); - // Node initialisation - // if (needs_initialisation) initialise_node(); - - // Initialise sensors + DEBUG_PRINTLN("[START]"); + if (particle_sensor.init()) error_handler(-1, true, PS_INIT_ERROR, "Particle sensor initialisation failed"); - // Data collection sample_noise_sensor(); - // if (!sleep_noise_sensor()) error_handler(-1, true, NS_SLEEP_ERROR, "Failed to put the noise sensor to sleep"); - - sample_particle_sensor(); if (!sleep_particle_sensor()) error_handler(-1, true, PS_SLEEP_ERROR, "Failed to put the particle sensor to sleep"); - //// Update RTC boot_count++; - //// TODO: Power down sensors - //// updates the payload encode_payload(&payload, &ps_result, &ns_result); //// send data @@ -60,14 +50,13 @@ void setup() { srand((unsigned int)time(NULL) + node_id); delay((rand() % MAX_TX_DELAY_S) * S_TO_mS); - DEBUG_PRINTLN("[TRANSMIT] Transmitting payload"); + DEBUG_PRINTLN("[TRANSMIT] Transmitting payload"); transmit_payload(&payload); buffering_counter = 0; memset(&payload, 0, sizeof(payload_t)); } - //// Sleep - DEBUG_PRINTLN("[END] Entering sleep"); + DEBUG_PRINTLN("[END] Entering sleep"); radio.sleep(); uint32_t time_awake = millis() * 1000UL; From 82272839818e4aaf002c0523211c2831b7505abf Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:43:09 +0200 Subject: [PATCH 08/28] refactor: refactor sleep time calculation --- node/include/network/protocol.h | 2 ++ node/src/drivers/power.cpp | 14 +++++++++++++- node/src/src.ino | 18 +++++------------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/node/include/network/protocol.h b/node/include/network/protocol.h index f7bc5b3..a5973a9 100644 --- a/node/include/network/protocol.h +++ b/node/include/network/protocol.h @@ -11,11 +11,13 @@ const uint8_t MSG_TYPE_CLEARANCE = 0xD1; const uint8_t MSG_TYPE_ERROR = 0xE0; const uint8_t MSG_TYPE_CONFIG = 0xF0; +// Types of error messages to throw to the gateway const uint8_t UNDEFINED_ERROR = 0x00; const uint8_t PS_INIT_ERROR = 0x01; const uint8_t PS_SLEEP_ERROR = 0x02; const uint8_t NS_SLEEP_ERROR = 0x03; const uint8_t NVS_ERROR = 0x04; +const uint8_t SLEEPTIME_ERROR = 0x05; /** * @brief Structure for config updates diff --git a/node/src/drivers/power.cpp b/node/src/drivers/power.cpp index 16e3185..ee853ae 100644 --- a/node/src/drivers/power.cpp +++ b/node/src/drivers/power.cpp @@ -32,4 +32,16 @@ bool wake_particle_sensor() { pinMode(PS_SET_PIN, OUTPUT); digitalWrite(PS_SET_PIN, HIGH); return true; -} \ No newline at end of file +} + +void calculate_sleep_time() { + uint32_t time_awake = millis() * 1000UL; + uint32_t wakeup_interval = WAKEUP_INTERVAL_S * S_TO_uS; + uint32_t sleep_time = 900 * S_TO_uS; // default value + + if (wakeup_interval > time_awake) { + sleep_time = wakeup_interval - time_awake; + } else { + error_handler(-1, true, SLEEPTIME_ERROR, "wakeup interval too low (underflow). Defaulting the sleep time."); + } +} diff --git a/node/src/src.ino b/node/src/src.ino index 8a3e89c..e4b9c31 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -56,22 +56,14 @@ void setup() { memset(&payload, 0, sizeof(payload_t)); } - DEBUG_PRINTLN("[END] Entering sleep"); radio.sleep(); - uint32_t time_awake = millis() * 1000UL; - uint32_t wakeup_interval = WAKEUP_INTERVAL_S * S_TO_uS; - uint32_t sleep_time = 900 * S_TO_uS; //default value - dont change + sleep_time_ms = calculate_sleep_time(); + DEBUG_PRINT ("[END] Entering sleep for: "); + DEBUG_PRINT (sleep_time_ms); + DEBUG_PRINTLN(" ms"); - if (wakeup_interval > time_awake) { - sleep_time = wakeup_interval - time_awake; - DEBUG_PRINT("[SLEEP] Dynamic sleep time (s): "); - DEBUG_PRINTLN(sleep_time / S_TO_uS); - } else { - DEBUG_PRINTLN("[ERROR] WAKEUP_INTERVAL too low (underflow). Defaulting to ~900s"); - } - - esp_sleep_enable_timer_wakeup(sleep_time); + esp_sleep_enable_timer_wakeup(sleep_time_ms); esp_deep_sleep_start(); } From 9abe87c186904792d62b375e62c5c9bf89d919b7 Mon Sep 17 00:00:00 2001 From: TimSchonning Date: Sun, 24 May 2026 20:44:58 +0200 Subject: [PATCH 09/28] refactor: refactor tx delay --- node/src/network/delay.cpp | 15 +++++++++++++++ node/src/src.ino | 3 +-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 node/src/network/delay.cpp diff --git a/node/src/network/delay.cpp b/node/src/network/delay.cpp new file mode 100644 index 0000000..2ccfcdd --- /dev/null +++ b/node/src/network/delay.cpp @@ -0,0 +1,15 @@ +#include "config.h" +#include "debug_macros.h" +#include "encode_payload.h" +#include "sensor_logic.h" +#include "utils.h" + +#include +#include + +extern SX1262 radio; + +void random_tx_delay() { + srand((unsigned int)time(NULL) + node_id); + delay((rand() % MAX_TX_DELAY_S) * S_TO_mS); +} \ No newline at end of file diff --git a/node/src/src.ino b/node/src/src.ino index e4b9c31..083b2a8 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -47,8 +47,7 @@ void setup() { if (buffering_counter < (BUFFERING_THRESHOLD - 1)) { buffering_counter++; } else { - srand((unsigned int)time(NULL) + node_id); - delay((rand() % MAX_TX_DELAY_S) * S_TO_mS); + random_tx_delay(); DEBUG_PRINTLN("[TRANSMIT] Transmitting payload"); transmit_payload(&payload); From 8843752899787edc432b53e5508e604483974ec1 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 14:54:50 +0200 Subject: [PATCH 10/28] chore: rename LoRa pins --- node/include/config.h | 8 ++++---- node/src/src.ino | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/node/include/config.h b/node/include/config.h index 7ee8b7a..8d8b2f0 100644 --- a/node/include/config.h +++ b/node/include/config.h @@ -37,10 +37,10 @@ inline int8_t POWER = 14; inline uint16_t PREAMBLE_LEN = 8; // LoRa pins (check datasheet) -inline const int PIN_NSS = 21; -inline const int PIN_DIO0 = 5; -inline const int PIN_NRST = 7; -inline const int PIN_DIO1 = 6; +inline const int PIN_CS = 21; +inline const int PIN_DIO1 = 5; +inline const int PIN_RST = 7; +inline const int PIN_BSY = 6; //// Initialisation config inline const uint8_t MAX_ID_ATTEMPTS = 0; diff --git a/node/src/src.ino b/node/src/src.ino index 083b2a8..7a3921e 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -16,13 +16,11 @@ HM330X particle_sensor; uint8_t ps_sensor_buf[30]; ps_state_t ps_state; ps_result_t ps_result; - ns_state_t ns_state; ns_result_t ns_result; RTC_DATA_ATTR payload_t payload; - -SX1262 radio = new Module(PIN_NSS, PIN_DIO0, PIN_NRST, PIN_DIO1); +SX1262 radio = new Module(PIN_CS, PIN_DIO1, PIN_RST, PIN_BSY); void setup() { power_down_radios(); From 67b3921d31d2c0be7d5ca5e309f5b702f6f4fa04 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 15:04:40 +0200 Subject: [PATCH 11/28] chore(src): cleanup --- node/src/src.ino | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/node/src/src.ino b/node/src/src.ino index 7a3921e..159d406 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -31,24 +31,25 @@ void setup() { DEBUG_PRINTLN("[START]"); - if (particle_sensor.init()) error_handler(-1, true, PS_INIT_ERROR, "Particle sensor initialisation failed"); + if (particle_sensor.init()) error_handler(-1, true, PS_INIT_ERROR, "Particle sensor initialisation failed"); sample_noise_sensor(); sample_particle_sensor(); + if (!sleep_particle_sensor()) error_handler(-1, true, PS_SLEEP_ERROR, "Failed to put the particle sensor to sleep"); boot_count++; encode_payload(&payload, &ps_result, &ns_result); + buffering_counter++; // Amount of batched readings within the payload - //// send data - if (buffering_counter < (BUFFERING_THRESHOLD - 1)) { - buffering_counter++; - } else { + bool send_payload = !(buffering_counter < BUFFERING_THRESHOLD); + if (send_payload) { random_tx_delay(); DEBUG_PRINTLN("[TRANSMIT] Transmitting payload"); transmit_payload(&payload); + buffering_counter = 0; memset(&payload, 0, sizeof(payload_t)); } @@ -56,9 +57,7 @@ void setup() { radio.sleep(); sleep_time_ms = calculate_sleep_time(); - DEBUG_PRINT ("[END] Entering sleep for: "); - DEBUG_PRINT (sleep_time_ms); - DEBUG_PRINTLN(" ms"); + DEBUG_PRINT("[END] Entering sleep for: "); DEBUG_PRINT(sleep_time_ms); DEBUG_PRINTLN(" ms"); esp_sleep_enable_timer_wakeup(sleep_time_ms); esp_deep_sleep_start(); From 98bcc7c2ba6034627f70c530f38499afca3ca1c9 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 15:17:33 +0200 Subject: [PATCH 12/28] chore(error_handler): cleanup --- node/src/utils/error_handler.cpp | 48 ++++++++++---------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/node/src/utils/error_handler.cpp b/node/src/utils/error_handler.cpp index 700f342..3d5ec7f 100644 --- a/node/src/utils/error_handler.cpp +++ b/node/src/utils/error_handler.cpp @@ -1,11 +1,5 @@ #include -#include #include -#include -#include -#include -#include -#include #include "config.h" #include "debug_macros.h" @@ -13,38 +7,26 @@ #include "utils.h" #include "protocol.h" -Preferences prefs; extern SX1262 radio; bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message) { - // RADIOLIB_ERR_NONE is def. as 0; - if (state != RADIOLIB_ERR_NONE) { - DEBUG_PRINT("[ERROR] "); - DEBUG_PRINT(message); - DEBUG_PRINT(" Code: "); - DEBUG_PRINTLN(state); + if (state != RADIOLIB_ERR_NONE) return false; + + DEBUG_PRINT("[ERROR] "); DEBUG_PRINT(message); DEBUG_PRINT(". Code: "); DEBUG_PRINTLN(state); - if (inform_gateway) { - int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); - - if (state != RADIOLIB_ERR_NONE) { - DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio init failed."); - return true; - } - msg_error_t msg_error; - msg_error.node_id = node_id; - msg_error.error_id = error_code; - - state = radio.transmit((uint8_t*)&msg_error, sizeof(msg_error_t)); - - if (state != RADIOLIB_ERR_NONE) { - DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio transmit failed."); - return true; - } + if (inform_gateway) { + + if (radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN) != RADIOLIB_ERR_NONE) { + DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio initialisation failed."); + return true; } - return true; - } else { - return false; + msg_error_t msg_error{.node_id = node_id, .error_id = error_code}; + + if (radio.transmit((uint8_t*)&msg_error, sizeof(msg_error_t)) != RADIOLIB_ERR_NONE) { + DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio transmit failed."); + } } + + return true; } From e87f948eafa2b76c128cc0cd3b5bd6c0754f6b58 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 15:24:57 +0200 Subject: [PATCH 13/28] fix: error_handler to return false if no error --- node/src/utils/error_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/utils/error_handler.cpp b/node/src/utils/error_handler.cpp index 3d5ec7f..91f2661 100644 --- a/node/src/utils/error_handler.cpp +++ b/node/src/utils/error_handler.cpp @@ -10,7 +10,7 @@ extern SX1262 radio; bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message) { - if (state != RADIOLIB_ERR_NONE) return false; + if (state == RADIOLIB_ERR_NONE) return false; DEBUG_PRINT("[ERROR] "); DEBUG_PRINT(message); DEBUG_PRINT(". Code: "); DEBUG_PRINTLN(state); From 10c4b62346aa45cafab946b63a827a697017f1fd Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 15:36:42 +0200 Subject: [PATCH 14/28] chore(encode_payload): cleanup --- node/src/network/encode_payload.cpp | 39 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/node/src/network/encode_payload.cpp b/node/src/network/encode_payload.cpp index a985228..3df6f1e 100644 --- a/node/src/network/encode_payload.cpp +++ b/node/src/network/encode_payload.cpp @@ -4,7 +4,7 @@ #include "sensor_logic.h" #include "utils.h" -#include +#include #include extern SX1262 radio; @@ -20,8 +20,8 @@ bool encode_payload(payload_t* payload, ps_result_t* ps_result, ns_result_t* ns_ payload->readings[index] = ps_result->pm1; payload->readings[index + 1] = ps_result->pm25; - payload->readings[index + 2] = (uint8_t)((ns_result->noise_avg >> 8) & 0xFF); - payload->readings[index + 3] = (uint8_t)(ns_result->noise_avg & 0xFF); + payload->readings[index + 2] = (ns_result->noise_avg >> 8); + payload->readings[index + 3] = ns_result->noise_avg; add_to_nvs(boot_count, ps_result->pm1, ps_result->pm25, ns_result->noise_avg); @@ -29,30 +29,33 @@ bool encode_payload(payload_t* payload, ps_result_t* ps_result, ns_result_t* ns_ } bool transmit_payload(payload_t* payload) { - DEBUG_PRINTLN("[START] LoRa transmission"); - int16_t state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SYNC_WORD, POWER, PREAMBLE_LEN); - error_handler(state, false, UNDEFINED_ERROR, "LoRa initialisation"); + if (error_handler(state, false, UNDEFINED_ERROR, "LoRa initialisation")) return false; - uint8_t counter = 0; - while (counter < MAX_TX_RETRIES) { - uint8_t payload_size = 3 + BUFFERING_THRESHOLD * 4; + for (uint8_t counter = 0; counter < MAX_TX_RETRIES; counter++) { + // 1. Transmit the package. Abort upon fail. + uint16_t payload_size = 3 + BUFFERING_THRESHOLD * 4; state = radio.transmit((uint8_t*)payload, payload_size); - error_handler(state, false, UNDEFINED_ERROR, "LoRa payload transmission"); + if (error_handler(state, false, UNDEFINED_ERROR, "LoRa payload transmission")) return false; - DEBUG_PRINTLN("[SUCCESS] Payload sent, waiting for ACK"); + DEBUG_PRINTLN("[INFO] Payload sent. Awaiting ACK"); + // 2. Receive an ACK msg_ack_t payload_ack; state = radio.receive((uint8_t*)&payload_ack, sizeof(msg_ack_t)); - if (state == RADIOLIB_ERR_NONE) { - if (payload_ack.type == MSG_TYPE_ACK && - payload_ack.node_id == node_id && - payload_ack.ack_for == MSG_TYPE_PAYLOAD_UPLINK) { - return true; - } + // 3. Verify the ACK + if (state == RADIOLIB_ERR_NONE && + payload_ack.type == MSG_TYPE_ACK && + payload_ack.node_id == node_id && + payload_ack.ack_for == MSG_TYPE_PAYLOAD_UPLINK) { + DEBUG_PRINTLN("[INFO] ACK received."); + return true; } - counter++; + + DEBUG_PRINTLN("[WARNING] ACK missing or invalid. Retrying."); } + + DEBUG_PRINTLN("[ERROR] Max transmit payload retries reached. Transmission failed."); return false; } \ No newline at end of file From 4ef4df3ead08a5074fe498f29bfbb611ea4a9494 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 15:45:26 +0200 Subject: [PATCH 15/28] refactor: parameterize maximum random tx delay --- node/src/network/delay.cpp | 13 +++---------- node/src/src.ino | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/node/src/network/delay.cpp b/node/src/network/delay.cpp index 2ccfcdd..efe250a 100644 --- a/node/src/network/delay.cpp +++ b/node/src/network/delay.cpp @@ -1,15 +1,8 @@ #include "config.h" -#include "debug_macros.h" -#include "encode_payload.h" -#include "sensor_logic.h" -#include "utils.h" -#include -#include +#include -extern SX1262 radio; - -void random_tx_delay() { +void random_tx_delay(uint16_t max_delay) { srand((unsigned int)time(NULL) + node_id); - delay((rand() % MAX_TX_DELAY_S) * S_TO_mS); + delay((rand() % max_delay) * S_TO_mS); } \ No newline at end of file diff --git a/node/src/src.ino b/node/src/src.ino index 159d406..0a7872d 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -45,7 +45,7 @@ void setup() { bool send_payload = !(buffering_counter < BUFFERING_THRESHOLD); if (send_payload) { - random_tx_delay(); + random_tx_delay(MAX_TX_DELAY_S); DEBUG_PRINTLN("[TRANSMIT] Transmitting payload"); transmit_payload(&payload); From 9b27a7d94d7297449df66f528643fb1d237c8dc5 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 15:59:34 +0200 Subject: [PATCH 16/28] refactor: create master header file for centralized includes --- node/include/master.h | 37 ++++++++++++++++++++++++++ node/src/drivers/power.cpp | 15 +---------- node/src/drivers/sensor_logic.cpp | 7 +---- node/src/drivers/storage.cpp | 1 + node/src/experimental/init_handler.cpp | 15 +---------- node/src/network/config_handler.cpp | 15 +---------- node/src/network/delay.cpp | 4 +-- node/src/network/encode_payload.cpp | 10 ++----- node/src/src.ino | 14 +--------- node/src/utils/error_handler.cpp | 9 +------ 10 files changed, 47 insertions(+), 80 deletions(-) create mode 100644 node/include/master.h diff --git a/node/include/master.h b/node/include/master.h new file mode 100644 index 0000000..46d4f6b --- /dev/null +++ b/node/include/master.h @@ -0,0 +1,37 @@ +#ifndef MASTER_H +#define MASTER_H + +// Standard libraries +#include +#include +#include +#include +#include + +// Third party libraries +#include +#include +#include +#include +#include +#include +#include + +// Drivers +#include "power.h" +#include "sensor_logic.h" +#include "storage.h" + +// Network +#include "config_handler.h" +#include "encode_payload.h" +#include "protocol.h" + +// Utilities +#include "debug_macros.h" +#include "error_handler.h" + +// Configurations +#include "config.h" + +#endif \ No newline at end of file diff --git a/node/src/drivers/power.cpp b/node/src/drivers/power.cpp index ee853ae..d309f20 100644 --- a/node/src/drivers/power.cpp +++ b/node/src/drivers/power.cpp @@ -1,17 +1,4 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "debug_macros.h" -#include "sensor_logic.h" -#include "utils.h" -#include "protocol.h" +#include "master.h" Preferences prefs; extern SX1262 radio; diff --git a/node/src/drivers/sensor_logic.cpp b/node/src/drivers/sensor_logic.cpp index f5c48e1..c98caea 100644 --- a/node/src/drivers/sensor_logic.cpp +++ b/node/src/drivers/sensor_logic.cpp @@ -1,9 +1,4 @@ -#include - -#include "config.h" -#include "debug_macros.h" -#include "sensor_logic.h" -#include "utils.h" +#include "master.h" extern HM330X particle_sensor; extern uint8_t ps_sensor_buf[]; diff --git a/node/src/drivers/storage.cpp b/node/src/drivers/storage.cpp index 65e93c2..54e8e43 100644 --- a/node/src/drivers/storage.cpp +++ b/node/src/drivers/storage.cpp @@ -1,3 +1,4 @@ +#include "master.h" static void write_nvs(const char* key, uint8_t data_in) { prefs.begin("mints", false); diff --git a/node/src/experimental/init_handler.cpp b/node/src/experimental/init_handler.cpp index 7d7f88a..6f986e8 100644 --- a/node/src/experimental/init_handler.cpp +++ b/node/src/experimental/init_handler.cpp @@ -1,17 +1,4 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "debug_macros.h" -#include "sensor_logic.h" -#include "utils.h" -#include "protocol.h" +#include "master.h" Preferences prefs; extern SX1262 radio; diff --git a/node/src/network/config_handler.cpp b/node/src/network/config_handler.cpp index 8aaa7b0..e98d8fe 100644 --- a/node/src/network/config_handler.cpp +++ b/node/src/network/config_handler.cpp @@ -1,17 +1,4 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "debug_macros.h" -#include "sensor_logic.h" -#include "utils.h" -#include "protocol.h" +#include "master.h" Preferences prefs; extern SX1262 radio; diff --git a/node/src/network/delay.cpp b/node/src/network/delay.cpp index efe250a..0076db8 100644 --- a/node/src/network/delay.cpp +++ b/node/src/network/delay.cpp @@ -1,6 +1,4 @@ -#include "config.h" - -#include +#include "master.h" void random_tx_delay(uint16_t max_delay) { srand((unsigned int)time(NULL) + node_id); diff --git a/node/src/network/encode_payload.cpp b/node/src/network/encode_payload.cpp index 3df6f1e..e32bc3d 100644 --- a/node/src/network/encode_payload.cpp +++ b/node/src/network/encode_payload.cpp @@ -1,11 +1,4 @@ -#include "config.h" -#include "debug_macros.h" -#include "encode_payload.h" -#include "sensor_logic.h" -#include "utils.h" - -#include -#include +#include "master.h" extern SX1262 radio; @@ -54,6 +47,7 @@ bool transmit_payload(payload_t* payload) { } DEBUG_PRINTLN("[WARNING] ACK missing or invalid. Retrying."); + } DEBUG_PRINTLN("[ERROR] Max transmit payload retries reached. Transmission failed."); diff --git a/node/src/src.ino b/node/src/src.ino index 0a7872d..b7f59ba 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -1,16 +1,4 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "debug_macros.h" -#include "encode_payload.h" -#include "sensor_logic.h" -#include "utils.h" +#include "master.h" HM330X particle_sensor; uint8_t ps_sensor_buf[30]; diff --git a/node/src/utils/error_handler.cpp b/node/src/utils/error_handler.cpp index 91f2661..148cb1f 100644 --- a/node/src/utils/error_handler.cpp +++ b/node/src/utils/error_handler.cpp @@ -1,11 +1,4 @@ -#include -#include - -#include "config.h" -#include "debug_macros.h" -#include "sensor_logic.h" -#include "utils.h" -#include "protocol.h" +#include "master.h" extern SX1262 radio; From b7741fcc7eecc639aa4dea4e3ff5356d464d2d8f Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 16:01:34 +0200 Subject: [PATCH 17/28] feat: added random tx delay to payload retransmissions --- node/src/network/encode_payload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/network/encode_payload.cpp b/node/src/network/encode_payload.cpp index e32bc3d..5798618 100644 --- a/node/src/network/encode_payload.cpp +++ b/node/src/network/encode_payload.cpp @@ -47,7 +47,7 @@ bool transmit_payload(payload_t* payload) { } DEBUG_PRINTLN("[WARNING] ACK missing or invalid. Retrying."); - + random_tx_delay(5); } DEBUG_PRINTLN("[ERROR] Max transmit payload retries reached. Transmission failed."); From 9571da37058d05b1b2d2ebccb066e7d2e6245c3f Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 16:05:21 +0200 Subject: [PATCH 18/28] chore: moved config_handler to experimental/ --- node/include/{network => experimental}/config_handler.h | 0 node/src/{network => experimental}/config_handler.cpp | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename node/include/{network => experimental}/config_handler.h (100%) rename node/src/{network => experimental}/config_handler.cpp (100%) diff --git a/node/include/network/config_handler.h b/node/include/experimental/config_handler.h similarity index 100% rename from node/include/network/config_handler.h rename to node/include/experimental/config_handler.h diff --git a/node/src/network/config_handler.cpp b/node/src/experimental/config_handler.cpp similarity index 100% rename from node/src/network/config_handler.cpp rename to node/src/experimental/config_handler.cpp From 2cc8e5b6b40c31875414c70871d0c7ca418bdb98 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 16:23:06 +0200 Subject: [PATCH 19/28] refactor: simplify sensor logic --- node/include/drivers/sensor_logic.h | 22 +---- node/src/drivers/sensor_logic.cpp | 130 +++++++--------------------- 2 files changed, 33 insertions(+), 119 deletions(-) diff --git a/node/include/drivers/sensor_logic.h b/node/include/drivers/sensor_logic.h index ac1361c..1414935 100644 --- a/node/include/drivers/sensor_logic.h +++ b/node/include/drivers/sensor_logic.h @@ -5,17 +5,6 @@ #include #include -/** - * @brief Stores the running totals and timing state for PM averaging. - */ -typedef struct { - uint32_t start_time, last_sample_time; - uint8_t sample_count; - bool is_active; - - uint16_t sum_pm1, sum_pm25; -} ps_state_t; - /** * @brief Stores the final calculated averages. */ @@ -43,14 +32,7 @@ typedef struct { } ns_result_t; /** - * @brief Calculates the average PM concentration. - * @param sensor_buf: Buffer containing the readings of the sensor. (uint8_t sensor_buf[30];) - * @param state: the state/running totals. - * @param results: the accumulated totals. - * @param duration_ms: duration in ms the sampling runs for. - * @param target_samples: number of samples to take. - * @return is done - * + * @brief Records the PM concentration. * @note Sensor needs to heat up for about 30s * @note * HM330X Sensor Data Buffer Mapping (29 Bytes). @@ -61,7 +43,7 @@ typedef struct { * @note| 12-13 | PM2.5 (ATM) | PM2.5 Atmospheric environment (ug/m3) | * @note| 14-15 | PM10 (ATM) | PM10 Atmospheric environment (ug/m3) | */ -bool ps_parse(uint8_t* sensor_buf, ps_state_t* state, ps_result_t* result, uint16_t duration_ms, uint16_t target_samples); +void ps_parse(); /** * @brief Calculates the average peak to peak sound amplitude diff --git a/node/src/drivers/sensor_logic.cpp b/node/src/drivers/sensor_logic.cpp index c98caea..cb2de80 100644 --- a/node/src/drivers/sensor_logic.cpp +++ b/node/src/drivers/sensor_logic.cpp @@ -1,88 +1,38 @@ #include "master.h" -extern HM330X particle_sensor; -extern uint8_t ps_sensor_buf[]; -extern ps_state_t ps_state; +extern HM330X particle_sensor; +extern uint8_t ps_sensor_buf[]; +extern ps_state_t ps_state; extern ps_result_t ps_result; -extern ns_state_t ns_state; +extern ns_state_t ns_state; extern ns_result_t ns_result; -static uint8_t pm_average(uint8_t count, uint16_t input) { - uint16_t average_pm = (input + (count / 2)) / count; - if (average_pm > 255) { - return 255; - } - - return (uint8_t)average_pm; -} - -bool ps_parse(uint8_t* sensor_buf, ps_state_t* state, ps_result_t* result, uint16_t duration_ms, uint16_t target_samples) { - // uint16_t sample_interval = duration_ms / target_samples; - uint32_t now = millis(); - - if (!state->is_active) { - memset(state, 0, sizeof(ps_state_t)); - state->start_time = now; - state->is_active = true; - } - - if (target_samples != 1) { - DEBUG_PRINTLN("[WARNING] Target samples inside ps_parse != 1. Undefined behaviour."); - } - - // debug_print_raw_ps_data(); - - /* Sums the readings over the given time period */ - // if (state->sample_count < target_samples) { - // if (now - state->last_sample_time >= sample_interval) { - // if (particle_sensor.read_sensor_value(sensor_buf, 29) == NO_ERROR) { - - // state->sum_pm1 += ((uint16_t)sensor_buf[10] << 8) | sensor_buf[11]; - // state->sum_pm25 += ((uint16_t)sensor_buf[12] << 8) | sensor_buf[13]; - - // DEBUG_PRINTLN("PM1 SUM VALUE: "); - // DEBUG_PRINT(state->sum_pm1); - // DEBUG_PRINTLN("PM25 SUM VALUE: "); - // DEBUG_PRINT(state->sum_pm25); - - // state->sample_count++; - // state->last_sample_time = now; - // } else { - // DEBUG_PRINTLN("[ERROR] particle_sensor.read_sensor_value(sensor_buf, 29) returned an error"); - // } - // } - // return false; - // } - - // /* Calculates the averages */ - // result->pm1 = pm_average(state->sample_count, state->sum_pm1); - // result->pm25 = pm_average(state->sample_count, state->sum_pm25); - // state->is_active = false; +void ps_parse() { + memset(result, 0, sizeof(ps_result_t)); - uint16_t error_code = particle_sensor.read_sensor_value(sensor_buf, 29); + uint16_t error_code = particle_sensor.read_sensor_value(ps_sensor_buf, 29); if (error_code == NO_ERROR) { + ps_result->pm1 = ((uint16_t)ps_sensor_buf[10] << 8) | ps_sensor_buf[11]; + ps_result->pm25 = ((uint16_t)ps_sensor_buf[12] << 8) | ps_sensor_buf[13]; - result->pm1 += ((uint16_t)sensor_buf[10] << 8) | sensor_buf[11]; - result->pm25 += ((uint16_t)sensor_buf[12] << 8) | sensor_buf[13]; - - DEBUG_PRINT("PM1 SUM VALUE: "); - DEBUG_PRINTLN( result->pm1); - DEBUG_PRINT("PM25 SUM VALUE: "); - DEBUG_PRINTLN( result->pm25); + DEBUG_PRINT("Measured PM1 : "); DEBUG_PRINTLN(ps_result->pm1); + DEBUG_PRINT("Measured PM2.5: "); DEBUG_PRINTLN(ps_result->pm25); } else { - DEBUG_PRINT("[ERROR] particle_sensor.read_sensor_value(sensor_buf, 29) returned an error"); - DEBUG_PRINTLN(error_code); - DEBUG_PRINTLN("Writing 255 to both PM values"); - result->pm1 += 255; - result->pm25 += 255; + DEBUG_PRINT("[ERROR] read_sensor_value(sensor_buf, 29) returned error: "); DEBUG_PRINTLN(error_code); + DEBUG_PRINTLN("[INFO] Writing 254 to both PM values"); + ps_result->pm1 = 254; + ps_result->pm25 = 254; } +} - state->is_active = false; +void sample_particle_sensor() { + wake_particle_sensor(); + delay(PS_HEAT_UP_TIME_S * S_TO_mS); - return true; + ps_parse(); } bool ns_parse(int SENSOR_PIN, ns_state_t* state, ns_result_t* result, uint16_t duration_ms) { @@ -95,7 +45,7 @@ bool ns_parse(int SENSOR_PIN, ns_state_t* state, ns_result_t* result, uint16_t d state->is_active = true; } - /* Sums the readings over the given time period */ + // 1. Sums the readings over the given time period if (now - state->start_time < duration_ms) { uint16_t sample = analogRead(SENSOR_PIN); @@ -107,24 +57,21 @@ bool ns_parse(int SENSOR_PIN, ns_state_t* state, ns_result_t* result, uint16_t d return false; } - // Window finished. Adds the peak-to-peak value to the accumulator + // 2. Window finished + // Adds the peak-to-peak value to the accumulator if (state->signal_max <= state->signal_min) { - DEBUG_PRINTLN("[ERROR] ns_parse call failed to detect any sound"); + DEBUG_PRINTLN("[ERROR] ns_parse call failed to detect any sound"); // TODO: this requires some better handling state->total_noise_peak += 0; } else { - state->total_noise_peak += state->signal_max - state->signal_min; + uint16_t noise_peak = state->signal_max - state->signal_min + DEBUG_PRINT("Measured noise peak: "); DEBUG_PRINTLN(noise_peak); + state->total_noise_peak += noise_peak; } state->sample_count++; - #ifdef DEBUG_MODE - Serial.println(__func__); - Serial.println("Total noise peak: " + String(state->total_noise_peak)); - Serial.println(""); - #endif - - // Calculates the total average + // 3. Calculates the total average if (state->sample_count >= NS_TARGET_SAMPLES) { result->noise_avg = state->total_noise_peak / NS_TARGET_SAMPLES; @@ -138,28 +85,13 @@ bool ns_parse(int SENSOR_PIN, ns_state_t* state, ns_result_t* result, uint16_t d return true; } -void sample_particle_sensor() { - DEBUG_PRINTLN("[START] Particle sensor sampling"); - DEBUG_PRINT("Heating particle sensor for (ms): "); - DEBUG_PRINTLN(PS_HEAT_UP_TIME_S * S_TO_mS); - - wake_particle_sensor(); - - delay(PS_HEAT_UP_TIME_S * S_TO_mS); - - while (!ps_parse(ps_sensor_buf, &ps_state, &ps_result, PS_SAMPLE_TIME_mS - 1, PS_TARGET_SAMPLES)) { - // 1ms delay safe guard - delay(1); - } -} - void sample_noise_sensor() { - DEBUG_PRINTLN("[START] Noise sensor sampling"); + DEBUG_PRINTLN("[START] Noise sensor sampling"); // state safe guards - ns_state.is_active = false; + ns_state.is_active = false; ns_state.total_noise_peak = 0; - ns_state.sample_count = 0; + ns_state.sample_count = 0; for (int i = 0; i < NS_TARGET_SAMPLES; i++) { while (!ns_parse(NS_PIN, &ns_state, &ns_result, NS_SAMPLE_WINDOW_mS)) { From 9428203a3cd1365582a5d67df0d1dd3d65d40433 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Mon, 25 May 2026 17:04:52 +0200 Subject: [PATCH 20/28] fix: minor syntax and bug fixes --- node/build.ps1 | 4 ++++ node/include/drivers/power.h | 9 +++++++++ node/include/master.h | 4 +++- node/include/network/delay.h | 14 ++++++++++++++ node/include/network/protocol.h | 13 +++++++------ node/src/drivers/power.cpp | 5 +++-- node/src/drivers/sensor_logic.cpp | 19 +++++++++---------- node/src/drivers/storage.cpp | 2 ++ node/src/experimental/config_handler.cpp | 1 - node/src/experimental/init_handler.cpp | 1 - node/src/src.ino | 4 ++-- node/src/utils/error_handler.cpp | 4 +++- 12 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 node/include/network/delay.h diff --git a/node/build.ps1 b/node/build.ps1 index 9ac8712..26bf647 100644 --- a/node/build.ps1 +++ b/node/build.ps1 @@ -1,4 +1,8 @@ function build ($Src1, $Src2) { + if (Test-Path ".\build") { + Remove-Item -Path ".\build" -Recurse -Force -ErrorAction SilentlyContinue + } + if (-not (Test-Path ".\build")) { New-Item -ItemType Directory -Path ".\build" -Force | Out-Null } diff --git a/node/include/drivers/power.h b/node/include/drivers/power.h index 75cf83f..e4b68b1 100644 --- a/node/include/drivers/power.h +++ b/node/include/drivers/power.h @@ -1,6 +1,8 @@ #ifndef POWER_H #define POWER_H +#include "cstdint" + /** * @brief Disables all wireless communication. */ @@ -24,4 +26,11 @@ bool sleep_noise_sensor(); */ bool wake_particle_sensor(); +/** + * @brief Calculates the time needed to sleep till the next measurement window. + * @return sleep time in us. + * @note Defaults to 900 seconds in the case of integer underflow. + */ +uint32_t calculate_sleep_time(); + #endif \ No newline at end of file diff --git a/node/include/master.h b/node/include/master.h index 46d4f6b..aa831f4 100644 --- a/node/include/master.h +++ b/node/include/master.h @@ -23,7 +23,7 @@ #include "storage.h" // Network -#include "config_handler.h" +#include "delay.h" #include "encode_payload.h" #include "protocol.h" @@ -34,4 +34,6 @@ // Configurations #include "config.h" +extern Preferences prefs; + #endif \ No newline at end of file diff --git a/node/include/network/delay.h b/node/include/network/delay.h new file mode 100644 index 0000000..8468806 --- /dev/null +++ b/node/include/network/delay.h @@ -0,0 +1,14 @@ +#ifndef DELAY_H +#define DELAY_H + +#include +#include "sensor_logic.h" +#include "protocol.h" + +/** + * @brief Delays execution between 0 and max_delay seconds. + * @param max_delay: Max amount of delay. + */ +void random_tx_delay(uint16_t max_delay); + +#endif \ No newline at end of file diff --git a/node/include/network/protocol.h b/node/include/network/protocol.h index a5973a9..c4cf2f7 100644 --- a/node/include/network/protocol.h +++ b/node/include/network/protocol.h @@ -12,12 +12,13 @@ const uint8_t MSG_TYPE_ERROR = 0xE0; const uint8_t MSG_TYPE_CONFIG = 0xF0; // Types of error messages to throw to the gateway -const uint8_t UNDEFINED_ERROR = 0x00; -const uint8_t PS_INIT_ERROR = 0x01; -const uint8_t PS_SLEEP_ERROR = 0x02; -const uint8_t NS_SLEEP_ERROR = 0x03; -const uint8_t NVS_ERROR = 0x04; -const uint8_t SLEEPTIME_ERROR = 0x05; +const uint8_t UNDEFINED_ERROR = 0x00; +const uint8_t PS_INIT_ERROR = 0x01; +const uint8_t PS_SLEEP_ERROR = 0x02; +const uint8_t NS_SLEEP_ERROR = 0x03; +const uint8_t NVS_ERROR = 0x04; +const uint8_t SLEEPTIME_ERROR = 0x05; +const uint8_t HM3301_READ_ERROR = 0x06; /** * @brief Structure for config updates diff --git a/node/src/drivers/power.cpp b/node/src/drivers/power.cpp index d309f20..cf995e4 100644 --- a/node/src/drivers/power.cpp +++ b/node/src/drivers/power.cpp @@ -1,6 +1,5 @@ #include "master.h" -Preferences prefs; extern SX1262 radio; void power_down_radios() { @@ -21,7 +20,7 @@ bool wake_particle_sensor() { return true; } -void calculate_sleep_time() { +uint32_t calculate_sleep_time() { uint32_t time_awake = millis() * 1000UL; uint32_t wakeup_interval = WAKEUP_INTERVAL_S * S_TO_uS; uint32_t sleep_time = 900 * S_TO_uS; // default value @@ -31,4 +30,6 @@ void calculate_sleep_time() { } else { error_handler(-1, true, SLEEPTIME_ERROR, "wakeup interval too low (underflow). Defaulting the sleep time."); } + + return sleep_time; } diff --git a/node/src/drivers/sensor_logic.cpp b/node/src/drivers/sensor_logic.cpp index cb2de80..3f01e3a 100644 --- a/node/src/drivers/sensor_logic.cpp +++ b/node/src/drivers/sensor_logic.cpp @@ -2,29 +2,28 @@ extern HM330X particle_sensor; extern uint8_t ps_sensor_buf[]; -extern ps_state_t ps_state; extern ps_result_t ps_result; extern ns_state_t ns_state; extern ns_result_t ns_result; void ps_parse() { - memset(result, 0, sizeof(ps_result_t)); + memset(&ps_result, 0, sizeof(ps_result_t)); uint16_t error_code = particle_sensor.read_sensor_value(ps_sensor_buf, 29); if (error_code == NO_ERROR) { - ps_result->pm1 = ((uint16_t)ps_sensor_buf[10] << 8) | ps_sensor_buf[11]; - ps_result->pm25 = ((uint16_t)ps_sensor_buf[12] << 8) | ps_sensor_buf[13]; + ps_result.pm1 = ((uint16_t)ps_sensor_buf[10] << 8) | ps_sensor_buf[11]; + ps_result.pm25 = ((uint16_t)ps_sensor_buf[12] << 8) | ps_sensor_buf[13]; - DEBUG_PRINT("Measured PM1 : "); DEBUG_PRINTLN(ps_result->pm1); - DEBUG_PRINT("Measured PM2.5: "); DEBUG_PRINTLN(ps_result->pm25); + DEBUG_PRINT("Measured PM1 : "); DEBUG_PRINTLN(ps_result.pm1); + DEBUG_PRINT("Measured PM2.5: "); DEBUG_PRINTLN(ps_result.pm25); } else { - DEBUG_PRINT("[ERROR] read_sensor_value(sensor_buf, 29) returned error: "); DEBUG_PRINTLN(error_code); + error_handler(-1, true, HM3301_READ_ERROR, "read_sensor_value(sensor_buf, 29) returned error: "); DEBUG_PRINTLN(error_code); DEBUG_PRINTLN("[INFO] Writing 254 to both PM values"); - ps_result->pm1 = 254; - ps_result->pm25 = 254; + ps_result.pm1 = 254; + ps_result.pm25 = 254; } } @@ -64,7 +63,7 @@ bool ns_parse(int SENSOR_PIN, ns_state_t* state, ns_result_t* result, uint16_t d // TODO: this requires some better handling state->total_noise_peak += 0; } else { - uint16_t noise_peak = state->signal_max - state->signal_min + uint16_t noise_peak = state->signal_max - state->signal_min; DEBUG_PRINT("Measured noise peak: "); DEBUG_PRINTLN(noise_peak); state->total_noise_peak += noise_peak; } diff --git a/node/src/drivers/storage.cpp b/node/src/drivers/storage.cpp index 54e8e43..6c78a99 100644 --- a/node/src/drivers/storage.cpp +++ b/node/src/drivers/storage.cpp @@ -1,5 +1,7 @@ #include "master.h" +Preferences prefs; + static void write_nvs(const char* key, uint8_t data_in) { prefs.begin("mints", false); if (!prefs.putUChar(key, data_in)) error_handler(-1, true, NVS_ERROR, "Failed to write to nvs"); diff --git a/node/src/experimental/config_handler.cpp b/node/src/experimental/config_handler.cpp index e98d8fe..fc91e8f 100644 --- a/node/src/experimental/config_handler.cpp +++ b/node/src/experimental/config_handler.cpp @@ -1,6 +1,5 @@ #include "master.h" -Preferences prefs; extern SX1262 radio; void config_mode() { diff --git a/node/src/experimental/init_handler.cpp b/node/src/experimental/init_handler.cpp index 6f986e8..6924fa6 100644 --- a/node/src/experimental/init_handler.cpp +++ b/node/src/experimental/init_handler.cpp @@ -1,6 +1,5 @@ #include "master.h" -Preferences prefs; extern SX1262 radio; bool standby_mode() { diff --git a/node/src/src.ino b/node/src/src.ino index b7f59ba..68d0347 100644 --- a/node/src/src.ino +++ b/node/src/src.ino @@ -2,7 +2,7 @@ HM330X particle_sensor; uint8_t ps_sensor_buf[30]; -ps_state_t ps_state; + ps_result_t ps_result; ns_state_t ns_state; ns_result_t ns_result; @@ -44,7 +44,7 @@ void setup() { radio.sleep(); - sleep_time_ms = calculate_sleep_time(); + uint32_t sleep_time_ms = calculate_sleep_time(); DEBUG_PRINT("[END] Entering sleep for: "); DEBUG_PRINT(sleep_time_ms); DEBUG_PRINTLN(" ms"); esp_sleep_enable_timer_wakeup(sleep_time_ms); diff --git a/node/src/utils/error_handler.cpp b/node/src/utils/error_handler.cpp index 148cb1f..738e7c9 100644 --- a/node/src/utils/error_handler.cpp +++ b/node/src/utils/error_handler.cpp @@ -14,7 +14,9 @@ bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const return true; } - msg_error_t msg_error{.node_id = node_id, .error_id = error_code}; + msg_error_t msg_error; + msg_error.node_id = node_id; + msg_error.error_id = error_code; if (radio.transmit((uint8_t*)&msg_error, sizeof(msg_error_t)) != RADIOLIB_ERR_NONE) { DEBUG_PRINTLN("[ERROR] (error_handler) Cannot transmit because radio transmit failed."); From 6e30244822b8fb26583c850fdad10a288367eb4f Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Thu, 28 May 2026 16:13:05 +0200 Subject: [PATCH 21/28] chore: update build script Added print statements src.ino renamed to main.cpp --- node/build.ps1 | 11 +++++++++-- node/src/{src.ino => main.cpp} | 0 2 files changed, 9 insertions(+), 2 deletions(-) rename node/src/{src.ino => main.cpp} (100%) diff --git a/node/build.ps1 b/node/build.ps1 index 26bf647..b1fc8b3 100644 --- a/node/build.ps1 +++ b/node/build.ps1 @@ -1,21 +1,28 @@ function build ($Src1, $Src2) { if (Test-Path ".\build") { + Write-Host "Removing existing '.\build' directory..." -ForegroundColor Yellow Remove-Item -Path ".\build" -Recurse -Force -ErrorAction SilentlyContinue } if (-not (Test-Path ".\build")) { + Write-Host "Creating fresh '.\build' directory..." -ForegroundColor Green New-Item -ItemType Directory -Path ".\build" -Force | Out-Null } + Write-Host "Searching for files in '$Src1' and '$Src2'..." -ForegroundColor Gray $Files = Get-ChildItem -Path "$Src1", "$Src2" -Recurse -File -ErrorAction SilentlyContinue foreach ($File in $Files) { + Write-Host "Copying: $($File.Name) -> .\build\" -ForegroundColor DarkGray Copy-Item -Path $File.FullName -Destination ".\build" -Force -ErrorAction SilentlyContinue } - if (Test-Path ".\build\src.ino") { - Rename-Item -Path ".\build\src.ino" -NewName "build.ino" -Force -ErrorAction SilentlyContinue + if (Test-Path ".\build\main.cpp") { + Write-Host "Renaming 'main.cpp' to 'build.ino'..." -ForegroundColor Magenta + Rename-Item -Path ".\build\main.cpp" -NewName "build.ino" -Force -ErrorAction SilentlyContinue } + + Write-Host "--- Build complete ---" -ForegroundColor Cyan } build ".\include" ".\src" \ No newline at end of file diff --git a/node/src/src.ino b/node/src/main.cpp similarity index 100% rename from node/src/src.ino rename to node/src/main.cpp From 9d02c8a5bc3ca49a5c1f30dab00ed1ed2b9b1bdf Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Thu, 28 May 2026 16:38:00 +0200 Subject: [PATCH 22/28] chore: update node/readme --- node/README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/node/README.md b/node/README.md index 0e6b181..a1855b9 100644 --- a/node/README.md +++ b/node/README.md @@ -1,4 +1,4 @@ -Updated 2026/05/24. +Updated 2026/05/28. ## Brief The full workings of the node can be found within the authors' Bachelor thesis, linked in the main README. @@ -15,6 +15,23 @@ The nodes core logic: 7. Disable the radio 9. Sleep +## node/ structure + +The project is organized into modular directories separating definitions (`include/`) from execution logic: + +### Root Files +* **`main.cpp`**: The primary application entry point; handles system initialization and the core runtime loop. + +### Include Directory (`include/`) +Contains all header files: + +* **`config.h`**: Global configuration settings, pin definitions, constants, and system-wide parameters. +* **`master.h`**: Top-level header file including all other .h-files. +* **`drivers/`**: Interface definitions for peripheral hardware and sensor abstractions (e.g., air quality sensors). +* **`network/`**: Headers for managing connectivity, communication protocols, and data payloads. +* **`utils/`**: Helper macros, error utilities. +* **`experimental/`**: Sandbox interfaces and test setups for features currently under development. + ## Initialisation w/ Arduinos IDE 1. Navigate into node/ and execute the build script: From 5581d5d5e929c5f05757f45f54bf8b350f73a89d Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Thu, 28 May 2026 16:57:27 +0200 Subject: [PATCH 23/28] docs: update comments --- node/README.md | 2 ++ node/include/drivers/power.h | 3 ++- node/include/network/encode_payload.h | 8 ++++---- node/include/utils/debug_macros.h | 1 - node/include/utils/error_handler.h | 11 ++++++----- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/node/README.md b/node/README.md index a1855b9..4a5d296 100644 --- a/node/README.md +++ b/node/README.md @@ -33,6 +33,8 @@ Contains all header files: * **`experimental/`**: Sandbox interfaces and test setups for features currently under development. ## Initialisation w/ Arduinos IDE +0. Disable debug mode when in production. Can be done in include/utils/debug_macros.h + 1. Navigate into node/ and execute the build script: ```powershell diff --git a/node/include/drivers/power.h b/node/include/drivers/power.h index e4b68b1..016ae8e 100644 --- a/node/include/drivers/power.h +++ b/node/include/drivers/power.h @@ -4,7 +4,8 @@ #include "cstdint" /** - * @brief Disables all wireless communication. + * @brief Disables unused peripherals communication. + * @note saves power! */ void power_down_radios(); diff --git a/node/include/network/encode_payload.h b/node/include/network/encode_payload.h index baa5b9b..6dbd820 100644 --- a/node/include/network/encode_payload.h +++ b/node/include/network/encode_payload.h @@ -6,10 +6,10 @@ #include "protocol.h" /** - * @brief Returns the encoded payload. - * @param payload: the payload. - * @param ps_result: PM results. - * @param ns_result: Sound sensor results. + * @brief Encodes the payload. + * @param payload: the payload. + * @param ps_result: PM results to append to the payload. + * @param ns_result: Sound sensor results to append to the payload. * @return Success indicator */ bool encode_payload(payload_t* payload, ps_result_t* ps_result, ns_result_t* ns_result); diff --git a/node/include/utils/debug_macros.h b/node/include/utils/debug_macros.h index 089a48b..73263ac 100644 --- a/node/include/utils/debug_macros.h +++ b/node/include/utils/debug_macros.h @@ -1,7 +1,6 @@ #ifndef DEBUG_MACROS_H #define DEBUG_MACROS_H - #define DEBUG_MODE // Comment this to disable debug mode #ifdef DEBUG_MODE diff --git a/node/include/utils/error_handler.h b/node/include/utils/error_handler.h index 5cd0d1c..fe1fa41 100644 --- a/node/include/utils/error_handler.h +++ b/node/include/utils/error_handler.h @@ -4,12 +4,13 @@ #include /** - * @brief Logic for handling radio status codes. - * @param state The return code from the radio library. - * @param message The error message to print if the state is not successful. + * @brief Logic for handling error status codes. + * @param state The return error code. + * @param message The error message to print if the state is not successful. * @param inform_gateway True = sends a message to the gateway with the specified error_code. - * @param error_code the error code that gets sent to the gateway. - * @return true if an error occurred, false if successful. + * @param error_code the error code that gets sent to the gateway. + * @return true if an error occurred. + * @note function directly returns false if state is set to 0. * @note if inform_gateway is set to false, set error_code to UNDEFINED_ERROR */ bool error_handler(int16_t state, bool inform_gateway, uint8_t error_code, const char* message); From 208cd7d5faccac1d18e2229d7338acd31f33a1db Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Thu, 28 May 2026 17:19:32 +0200 Subject: [PATCH 24/28] fix(gateway): fix error parsing --- gateway/src/gatewayLogic.cpp | 30 +----------------------------- gateway/src/measurement.py | 4 ++-- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/gateway/src/gatewayLogic.cpp b/gateway/src/gatewayLogic.cpp index 1ad7233..2fa6c49 100644 --- a/gateway/src/gatewayLogic.cpp +++ b/gateway/src/gatewayLogic.cpp @@ -95,7 +95,7 @@ static void handlePacket(size_t payloadSize) { case MSG_TYPE_ERROR: { msg_error_t *error_msg = (msg_error_t *)packetBuffer; - std::cout << "[ERROR] Node-side node ID: " << error_msg->node_id << " error code: " << error_msg->error_code << std::endl; + std::cout << "[ERROR] Node-side node ID: " << (int)error_msg->node_id << " error code: " << (int)error_msg->error_code << std::endl; break; } @@ -105,34 +105,6 @@ static void handlePacket(size_t payloadSize) { } } -/* int main() { // TODO: Clear gateway simulation and add (modified) main loop from LoRa.cpp - LoRaInit(); - - while (true) { - int state = radio.receive(packetBuffer, sizeof(packetBuffer)); - size_t payloadSize = radio.getPacketLength(); - - switch (state) { - case RADIOLIB_ERR_NONE: - handlePacket(payloadSize); - break; - - case RADIOLIB_ERR_RX_TIMEOUT: - break; - - case RADIOLIB_ERR_CRC_MISMATCH: - std::cout << "CRC Error!" << std::endl; - break; - - default: - std::cout << "Unknown error: " << (int)state << std::endl; - break; - } - } - - return 0; -} */ - int main() { LoRaInit(); diff --git a/gateway/src/measurement.py b/gateway/src/measurement.py index a71921a..76964db 100644 --- a/gateway/src/measurement.py +++ b/gateway/src/measurement.py @@ -9,9 +9,9 @@ class MeasurementGroup(): readings = {} def __init__(self, batch_nr, station_id, pm1, pm25, noise): - self.station_id = station_id + self.station_id = station_id self.readings["PM2.5"] = pm25 - self.readings["PM1"] = pm1 + self.readings["PM1"] = pm1 self.readings["Noise"] = noise self.timestamp = (datetime.now(time_zone) - timedelta(seconds=(int(batch_nr) * NODE_SAMPLE_WINDOW_S))).replace(second=0, microsecond=0) \ No newline at end of file From 8bdb41ae47fd0ca0550a196e286981bad1da774e Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Thu, 28 May 2026 17:38:52 +0200 Subject: [PATCH 25/28] chore: move non-production ready header content to experimental/ --- node/include/drivers/power.h | 6 -- node/include/drivers/sensor_logic.h | 8 --- node/include/experimental/config_handler.h | 2 + node/include/experimental/experimental.h | 0 node/include/experimental/init_handler.h | 2 + node/include/experimental/protocol.h | 65 ++++++++++++++++++++++ node/include/network/protocol.h | 59 -------------------- 7 files changed, 69 insertions(+), 73 deletions(-) delete mode 100644 node/include/experimental/experimental.h create mode 100644 node/include/experimental/protocol.h diff --git a/node/include/drivers/power.h b/node/include/drivers/power.h index 016ae8e..1ea25af 100644 --- a/node/include/drivers/power.h +++ b/node/include/drivers/power.h @@ -15,12 +15,6 @@ void power_down_radios(); */ bool sleep_particle_sensor(); -/** - * @brief Sleeps the sensor. - * @return success - */ -bool sleep_noise_sensor(); - /** * @brief Wakes the sensor. * @return success diff --git a/node/include/drivers/sensor_logic.h b/node/include/drivers/sensor_logic.h index 1414935..574d988 100644 --- a/node/include/drivers/sensor_logic.h +++ b/node/include/drivers/sensor_logic.h @@ -34,14 +34,6 @@ typedef struct { /** * @brief Records the PM concentration. * @note Sensor needs to heat up for about 30s - * @note - * HM330X Sensor Data Buffer Mapping (29 Bytes). - * The sensor transmits a 29-byte packet via I2C. Data is sent as Big-Endian - * (High Byte followed by Low Byte) for each 16-bit measurement. - * @note| Index | Name | Description | - * @note| 10-11 | PM1.0 (ATM) | PM1.0 Atmospheric environment (ug/m3) | - * @note| 12-13 | PM2.5 (ATM) | PM2.5 Atmospheric environment (ug/m3) | - * @note| 14-15 | PM10 (ATM) | PM10 Atmospheric environment (ug/m3) | */ void ps_parse(); diff --git a/node/include/experimental/config_handler.h b/node/include/experimental/config_handler.h index 8f6b8d6..6add35e 100644 --- a/node/include/experimental/config_handler.h +++ b/node/include/experimental/config_handler.h @@ -1,6 +1,8 @@ #ifndef CONFIG_HANDLER_H #define CONFIG_HANDLER_H +/* EXPERIMENTAL - MOVE WHEN ADDED TO PRODUCTION */ + /** * @brief Sets the node to config mode - where it can receive updates to state RTC variables. * @note Starts and sleeps the radio upon use. diff --git a/node/include/experimental/experimental.h b/node/include/experimental/experimental.h deleted file mode 100644 index e69de29..0000000 diff --git a/node/include/experimental/init_handler.h b/node/include/experimental/init_handler.h index 3af60a8..84ac7f5 100644 --- a/node/include/experimental/init_handler.h +++ b/node/include/experimental/init_handler.h @@ -1,6 +1,8 @@ #ifndef INIT_HANDLER_H #define INIT_HANDLER_H +/* EXPERIMENTAL - MOVE WHEN ADDED TO PRODUCTION */ + /** * @brief Initializes the LoRa radio and performs a handshake with the gateway * to receive a unique node ID. diff --git a/node/include/experimental/protocol.h b/node/include/experimental/protocol.h new file mode 100644 index 0000000..92a9d9d --- /dev/null +++ b/node/include/experimental/protocol.h @@ -0,0 +1,65 @@ +#ifndef EXPERIMENTAL_PROTOCOL_H +#define EXPERIMENTAL_PROTOCOL_H + +/* EXPERIMENTAL - MOVE WHEN ADDED TO PRODUCTION */ + +/** + * @brief Structure for config updates + * @note Is __attribute__((packed) + * @note + */ +typedef struct __attribute__((packed)) { + uint8_t type = MSG_TYPE_CONFIG; +} msg_config_t; + +/** + * @brief Request message structure. + * @note Is __attribute__((packed) + */ +typedef struct __attribute__((packed)) { + uint8_t type; + uint8_t node_id; +} msg_req_t; + +/** + * @brief Structure for gateway synchronization clearance + * @note Will ever only be received from the gateway upon initialisation. + * @note Is __attribute__((packed) + */ +typedef struct __attribute__((packed)) { + uint8_t type; + uint32_t time_stamp; +} msg_clearance_t; + +/** + * @brief Contains all configurable variables and time stamp, + * @note is __attribute__((packed)) + */ +enum __attribute__((packed)) config_tag_t : uint8_t { + TAG_TIME_STAMP = 0x00, // uint32_t + + // LoRa Settings + TAG_LORA_FREQ = 0x01, // float (4 bytes) + TAG_LORA_BW = 0x02, // float (4 bytes) + TAG_LORA_SF = 0x03, // uint8_t + TAG_LORA_CR = 0x04, // uint8_t + TAG_LORA_SYNC = 0x05, // uint8_t + TAG_LORA_PWR = 0x06, // int8_t + TAG_LORA_PREAMBLE = 0x07, // uint16_t + TAG_LORA_GAIN = 0x08, // uint8_t + + // General Config + TAG_CPU_FREQ = 0x09, // uint8_t + TAG_SLEEP_TIME = 0x0A, // uint32_t + TAG_MEASURE_WINDOW = 0x0B, // uint8_t + + // Particle Config + TAG_PS_HEAT_UP = 0x0C, // uint8_t + TAG_PS_SAMPLE_TIME = 0x0D, // uint16_t + TAG_PS_TARGET = 0x0E, // uint16_t + TAG_NODE_ID = 0x0F, // uint8_t + TAG_TX_RETRIES = 0x10, // uint8_t + TAG_BUFFER_THRESH = 0x11 // uint8_t +}; + +#endif \ No newline at end of file diff --git a/node/include/network/protocol.h b/node/include/network/protocol.h index c4cf2f7..434023d 100644 --- a/node/include/network/protocol.h +++ b/node/include/network/protocol.h @@ -20,15 +20,6 @@ const uint8_t NVS_ERROR = 0x04; const uint8_t SLEEPTIME_ERROR = 0x05; const uint8_t HM3301_READ_ERROR = 0x06; -/** - * @brief Structure for config updates - * @note Is __attribute__((packed) - * @note - */ -typedef struct __attribute__((packed)) { - uint8_t type = MSG_TYPE_CONFIG; -} msg_config_t; - /** * @brief Stores the results as a sendable LoRa payload. * @note reading_id reflects the latest readings id. @@ -62,54 +53,4 @@ typedef struct __attribute__((packed)) { uint8_t error_id; } msg_error_t; -/** - * @brief Request message structure. - * @note Is __attribute__((packed) - */ -typedef struct __attribute__((packed)) { - uint8_t type; - uint8_t node_id; -} msg_req_t; - -/** - * @brief Structure for gateway synchronization clearance - * @note Will ever only be received from the gateway upon initialisation. - * @note Is __attribute__((packed) - */ -typedef struct __attribute__((packed)) { - uint8_t type; - uint32_t time_stamp; -} msg_clearance_t; - -/** - * @brief Contains all configurable variables and time stamp, - * @note is __attribute__((packed)) - */ -enum __attribute__((packed)) config_tag_t : uint8_t { - TAG_TIME_STAMP = 0x00, // uint32_t - - // LoRa Settings - TAG_LORA_FREQ = 0x01, // float (4 bytes) - TAG_LORA_BW = 0x02, // float (4 bytes) - TAG_LORA_SF = 0x03, // uint8_t - TAG_LORA_CR = 0x04, // uint8_t - TAG_LORA_SYNC = 0x05, // uint8_t - TAG_LORA_PWR = 0x06, // int8_t - TAG_LORA_PREAMBLE = 0x07, // uint16_t - TAG_LORA_GAIN = 0x08, // uint8_t - - // General Config - TAG_CPU_FREQ = 0x09, // uint8_t - TAG_SLEEP_TIME = 0x0A, // uint32_t - TAG_MEASURE_WINDOW = 0x0B, // uint8_t - - // Particle Config - TAG_PS_HEAT_UP = 0x0C, // uint8_t - TAG_PS_SAMPLE_TIME = 0x0D, // uint16_t - TAG_PS_TARGET = 0x0E, // uint16_t - TAG_NODE_ID = 0x0F, // uint8_t - TAG_TX_RETRIES = 0x10, // uint8_t - TAG_BUFFER_THRESH = 0x11 // uint8_t -}; - #endif \ No newline at end of file From 08b840024aa7c5a0e8aa8195e393290f1f10cc0f Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Fri, 29 May 2026 10:32:17 +0200 Subject: [PATCH 26/28] feat(gateway): add duplication protection Gateway now discards a packet if the latest received packet had the same node_id and reading_id combination. --- gateway/include/protocol.h | 12 +++--- gateway/src/gatewayLogic.cpp | 73 +++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/gateway/include/protocol.h b/gateway/include/protocol.h index 2b0ba20..ebaa03b 100644 --- a/gateway/include/protocol.h +++ b/gateway/include/protocol.h @@ -11,11 +11,13 @@ const uint8_t MSG_TYPE_CLEARANCE = 0xD1; const uint8_t MSG_TYPE_ERROR = 0xE0; const uint8_t MSG_TYPE_CONFIG = 0xF0; -const uint8_t UNDEFINED_ERROR = 0x00; -const uint8_t PS_INIT_ERROR = 0x01; -const uint8_t PS_SLEEP_ERROR = 0x02; -const uint8_t NS_SLEEP_ERROR = 0x03; -const uint8_t NVS_ERROR = 0x04; +const uint8_t UNDEFINED_ERROR = 0x00; +const uint8_t PS_INIT_ERROR = 0x01; +const uint8_t PS_SLEEP_ERROR = 0x02; +const uint8_t NS_SLEEP_ERROR = 0x03; +const uint8_t NVS_ERROR = 0x04; +const uint8_t SLEEPTIME_ERROR = 0x05; +const uint8_t HM3301_READ_ERROR = 0x06; /** * @brief Structure for config updates diff --git a/gateway/src/gatewayLogic.cpp b/gateway/src/gatewayLogic.cpp index 2fa6c49..e1cacea 100644 --- a/gateway/src/gatewayLogic.cpp +++ b/gateway/src/gatewayLogic.cpp @@ -27,15 +27,15 @@ SX1262 radio(mod); void LoRaInit() { int state = radio.begin(FREQ, BW, SF, CR, SYNC, PWR, PRE); - + if (state == RADIOLIB_ERR_NONE) { state = radio.setDio2AsRfSwitch(); } - + if (state != RADIOLIB_ERR_NONE) { std::cout << "Initialisation failed, error code: \n" - << "For error codes, see: https://jgromes.github.io/RadioLib/group__status__codes.html" - << (int)state << std::endl; // Maybe add some error handling? + << "For error codes, see: https://jgromes.github.io/RadioLib/group__status__codes.html" + << (int)state << std::endl; // Maybe add some error handling? } } @@ -47,16 +47,16 @@ void LoRaInit() { */ static void handleSensorReading(payload_t *packet, size_t payloadSize) { int payloadOverheadSize = 3; - int paylaodReadingSize = 4; - int numberOfReadings = (payloadSize - payloadOverheadSize) / paylaodReadingSize; - + int payloadReadingSize = 4; + int numberOfReadings = (payloadSize - payloadOverheadSize) / payloadReadingSize; + for (int i = 0; i < numberOfReadings; i++) { int set = i * 4; std::cout << (int)i << "," // is used to calculate the timestamps for each set - << (int)packet->node_id << "," - << (int)packet->readings[set + 0] << "," - << (int)packet->readings[set + 1] << "," - << (uint16_t) ((packet->readings[set + 2] << 8) | packet->readings[set + 3]) << std::endl; + << (int)packet->node_id << "," + << (int)packet->readings[set + 0] << "," + << (int)packet->readings[set + 1] << "," + << (uint16_t) ((packet->readings[set + 2] << 8) | packet->readings[set + 3]) << std::endl; std::cout.flush(); } } @@ -70,10 +70,34 @@ static void sendAck(uint8_t nodeID, uint8_t ackFor) { msg_ack_t msg_packet_ack; msg_packet_ack.node_id = nodeID; msg_packet_ack.ack_for = ackFor; - + radio.transmit((uint8_t *)&msg_packet_ack, sizeof(msg_ack_t)); } +/** + * @brief Checks if a packet already has been received + * @return true if a new node ID was seen + * @return true if a new node ID and packet ID combination was seen + * @return false else + * @note only stores the latest seen packet IDs + */ +std::vector> latestPackets; +static bool new_packet(uint8_t node_id, uint8_t reading_id) { + auto it = std::find_if(latestPackets.begin(), latestPackets.end(), + [node_id](const auto& pair) { return pair.first == node_id; }); + + // Node exists + if (it != latestPackets.end()) { + if (it->second == reading_id) return false; + + it->second = reading_id; + return true; + } + + latestPackets.push_back({node_id, reading_id}); + return true; +} + /** * @brief Main packet handler. * Reads the packet signature from the global buffer and routes to the @@ -81,27 +105,32 @@ static void sendAck(uint8_t nodeID, uint8_t ackFor) { */ static void handlePacket(size_t payloadSize) { uint8_t signature = packetBuffer[0]; - + switch (signature) { case MSG_TYPE_PAYLOAD_UPLINK: { - payload_t *packet = (payload_t *)packetBuffer; - handleSensorReading(packet, payloadSize); - sendAck(packet->node_id, MSG_TYPE_PAYLOAD_UPLINK); + payload_t *packet = (payload_t *)packetBuffer; + uint8_t node_id = (int)packet->node_id; + uint8_t reading_id = (int)packet->reading_id; + + if (new_packet(node_id, reading_id)) { + handleSensorReading(packet, payloadSize); + sendAck(packet->node_id, MSG_TYPE_PAYLOAD_UPLINK); + } break; } - + case MSG_TYPE_ACK: - break; - + break; + case MSG_TYPE_ERROR: { msg_error_t *error_msg = (msg_error_t *)packetBuffer; std::cout << "[ERROR] Node-side node ID: " << (int)error_msg->node_id << " error code: " << (int)error_msg->error_code << std::endl; break; } - + default: - std::cout << "Unknown packet signature: " << signature << std::endl; - break; + std::cout << "Unknown packet signature: " << signature << std::endl; + break; } } From 9d176b9966920da514c3a083f186023a442ded48 Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Fri, 29 May 2026 10:39:53 +0200 Subject: [PATCH 27/28] chore(gateway): add print statement --- gateway/src/gatewayLogic.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gateway/src/gatewayLogic.cpp b/gateway/src/gatewayLogic.cpp index e1cacea..80c6b06 100644 --- a/gateway/src/gatewayLogic.cpp +++ b/gateway/src/gatewayLogic.cpp @@ -115,6 +115,8 @@ static void handlePacket(size_t payloadSize) { if (new_packet(node_id, reading_id)) { handleSensorReading(packet, payloadSize); sendAck(packet->node_id, MSG_TYPE_PAYLOAD_UPLINK); + } else { + std::cout << "[INFO] Discard packet (duplicate). Node ID: " << node_id << ". Reading ID: " << reading_id << std::endl; } break; } From e21a8d4b9d56f1184cb6007832a2dbf050df8a9b Mon Sep 17 00:00:00 2001 From: TimSchonning <116971347+TimSchonning@users.noreply.github.com> Date: Fri, 29 May 2026 11:08:50 +0200 Subject: [PATCH 28/28] chore(gateway): add readme - not finalised Needs initialisation /w Raspberry pi info --- gateway/README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 gateway/README.md diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 0000000..8745a85 --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,42 @@ +Updated 2026/05/28. + +## Brief +The full workings of the node can be found within the authors' Bachelor thesis, linked in the main README. + +The gateways core logic: +1. Always-on listening +2. Receives and parses packet +3. Packet handling +If the packet is a node payload: +4. Check if received before - discard if true. +5. Send data to database +If the packet is an error message: +4. Log the error + +## gateway/ structure +The project is organized into modular directories separating definitions (`include/`) from execution logic: + +### Include Directory (`include/`) +Contains all header files: + +* **`config.h`**: Global configuration settings +* **`debug_macros.h`**: Helper macros. +* **`protocol.h`**: Protocol definitions. +* **`utils/`**: Helper utilities, error utilities. + +### Source Directory (`src/`) + +* **`experimental/`**: Sandbox interfaces and test setups for features currently under development. +* **`databaseConnection.py`**: Initializes the Firebase Admin SDK and provides a method to batch-upload grouped sensor measurements into a Firestore database. +* **`gatewayLogic.cpp`**: Manages the hardware-level LoRa transceiver to receive, filter, and validate incoming wireless sensor packets before outputting them as CSV strings. +* **`main.py`**: main executable. Executes the C++ LoRa gateway process, parses received sensor data packets, and uploads them to Firebase. +* **`measurement.py`**: Defines a class to group and timestamp sensor readings (PM2.5, PM1, and Noise) by station. +* **`run_gateway.sh`**: Bash script that pulls the latest project updates, compiles the gatewayLogic executable with the RadioLib library, and launches the main Python application. + +## Initialisation w/ Raspberry PI +0. TODO +1. TODO +2. TODO + +## Other +See the main README for the teams contact info. \ No newline at end of file