esp_garden git · master
Distributed ESP sensor and actuator system (garden)
C++ 70.7% Markdown 18% C 10.7%git clone https://git.christianimmanuel.de/embedded/esp_garden.gitwget https://git.christianimmanuel.de/embedded/esp_garden/archive/esp_garden.tar.gzCleanup
client_sensor/ClientSensor.cpp | 609 ++++++++++++++++++++++++++++++++++++++ client_sensor/ClientSensor.h | 65 +++++ client_sensor/client_sensor.ino | 628 +--------------------------------------- globals.cpp | 18 ++ globals.h | 14 + 5 files changed, 709 insertions(+), 625 deletions(-) diff --git a/client_sensor/ClientSensor.cpp b/client_sensor/ClientSensor.cpp new file mode 100644 index 0000000..99e59ac --- /dev/null +++ b/client_sensor/ClientSensor.cpp @@ -0,0 +1,609 @@ +#include "ClientSensor.h" +#include "secrets.h" + +volatile bool rtc_synced_with_server = false; +volatile bool ack_received = false; +volatile bool send_process_complete = false; +volatile bool send_ack_process_complete = false; + + +// ---------------------------------------------------------------------------- +// Helper +// ---------------------------------------------------------------------------- +void printMac(const uint8_t* mac) { + for (size_t i = 0; i < 6; i++) { + if (i > 0) DBG_PRINT(":"); + DBG_PRINTF("%02X", mac[i]); + } + DBG_PRINTLN(); +} + +// ---------------------------------------------------------------------------- +// WiFi handling +// ---------------------------------------------------------------------------- +int32_t get_WiFiChannel() { + DBG_PRINTLN(ssid); + int32_t n = WiFi.scanNetworks(); + if (n <= 0) return 0; + for (uint8_t i = 0; i < n; i++) { + if (!strcmp(ssid, WiFi.SSID(i).c_str())) return WiFi.channel(i); + } + return 0; +} + +void init_WiFi() { + WiFi.mode(WIFI_MODE_STA); + int32_t channel = get_WiFiChannel(); + if (channel > 0) { + esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + esp_wifi_set_promiscuous(false); + } +} + + +// ---------------------------------------------------------------------------- +// Peers +// ---------------------------------------------------------------------------- + +uint8_t config_request_attempts = 0; + +void printSensorConfig(const SensorConfig_s& cfg) { + DBG_PRINTF("SensorConfig for '%s'\n", cfg.sensor_name); + DBG_PRINTF(" is_active: %s\n", cfg.is_active ? "true" : "false"); + DBG_PRINTF(" pin: %d\n", cfg.pin); + DBG_PRINTF(" min_moisture: %d\n", cfg.min_moisture); + DBG_PRINTF(" notification_delay_ms: %d\n", cfg.notification_delay_ms); + DBG_PRINTF(" notification_name: %s\n", cfg.notification_name); + DBG_PRINTF(" pump_mac: %s\n", (char*)cfg.pump_mac); + DBG_PRINTF(" delay_ms: %d\n", cfg.delay_ms); + DBG_PRINTF(" use_time: %s\n", cfg.use_time ? "true" : "false"); + DBG_PRINTF(" time_count: %u\n", cfg.time_count); + + if (cfg.time_count > 0) { + DBG_PRINT(" times: "); + for (uint8_t i = 0; i < cfg.time_count; i++) { + DBG_PRINTF("%u", cfg.times[i]); + if (i < cfg.time_count - 1) + DBG_PRINT("|"); + } + DBG_PRINTLN(""); + } +} + +// ---------------------------------------------------------------------------- +// Config to flash +// ---------------------------------------------------------------------------- + +#include <Preferences.h> + +Preferences prefs; + +bool load_SensorConfigs() { + DBG_PRINTLN("Loading sensor configs from NVS..."); + + prefs.begin("sensors", true); + size_t stored_size = prefs.getBytesLength("configs"); + DBG_PRINTF("Stored size: %u bytes\n", (unsigned)stored_size); + bool ok = false; + size_t sc_size = SENSOR_CONFIG_COUNT * sizeof(SensorConfig_s); + if (stored_size == sc_size) { + prefs.getBytes("configs", sensor_configs, sc_size); + ok = true; + DBG_PRINTLN("Config read successfully."); + } else DBG_PRINTLN("No valid stored config found."); + prefs.end(); + + return ok; +} + +void save_SensorConfigs() { + DBG_PRINTLN("Saving sensor configs to NVS..."); + + prefs.begin("sensors", false); + prefs.putBytes("configs", sensor_configs, SENSOR_CONFIG_COUNT * sizeof(SensorConfig_s)); + prefs.end(); + + DBG_PRINTLN("Save complete."); +} + +void restore_SensorConfigs() { + DBG_PRINTLN("Trying to load old config!"); + + bool restored = load_SensorConfigs(); + if (restored) { + DBG_PRINTLN("Sensor configs restored from flash."); + } else { + DBG_PRINTLN("No stored configs found, using defaults."); + for (size_t i = 0; i < SENSOR_CONFIG_COUNT; i++) + memcpy(sensor_configs[i].pump_mac, PEER_PUMP, MAC_ADDR_LEN); + } +} + + +// ---------------------------------------------------------------------------- +// RTC variables +// ---------------------------------------------------------------------------- +RTC_DATA_ATTR uint64_t last_sent_array[SENSOR_CONFIG_COUNT] = {0}; + +// ---------------------------------------------------------------------------- +// ESP-NOW handling +// ---------------------------------------------------------------------------- + +void on_DataSent(const wifi_tx_info_t* info, esp_now_send_status_t status) { + DBG_PRINT("Packet to "); + for (int i = 0; i < 6; i++) { + DBG_PRINTF("%02X", info->des_addr[i]); + if (i < 5) DBG_PRINT(":"); + } + + if (status == ESP_NOW_SEND_SUCCESS) + DBG_PRINTLN(" queued successfully (waiting for ACK)"); + else + DBG_PRINTLN(" failed to queue!"); +} + +void on_DataRecv(const esp_now_recv_info *info, const uint8_t *data, int len) { + if (!info || !data || len <= 0) return; + + char msg[256]; + memcpy(msg, data, len); + msg[len] = '\0'; + + DBG_PRINTF("Received from server: %s\n", msg); + + if (strncmp(msg, "time:", 5) == 0) { + time_t server_unix = strtoul(msg + 5, nullptr, 10); + struct timeval tv = { .tv_sec = server_unix, .tv_usec = 0 }; + settimeofday(&tv, nullptr); + rtc_synced_with_server = true; + DBG_PRINTLN("System time updated from server."); + } + + DBG_PRINTLN("Setting send_process_complete to TRUE"); + send_process_complete = true; +} + + +void on_DataRecvAck(const esp_now_recv_info *info, const uint8_t *data, int len) { + if (!info || !data || len <= 0) return; + + char msg[256]; + int copylen = len < (int)sizeof(msg) - 1 ? len : (int)sizeof(msg) - 1; + memcpy(msg, data, copylen); + msg[copylen] = '\0'; + + DBG_PRINTF("ACK received: %s\n", msg); + + const char *time_ptr = strstr(msg, "time:"); + if (time_ptr) { + time_t server_unix = strtoul(time_ptr + 5, nullptr, 10); + struct timeval tv = { .tv_sec = server_unix, .tv_usec = 0 }; + settimeofday(&tv, nullptr); + rtc_synced_with_server = true; + ack_received = true; + DBG_PRINTLN("System time updated from server (ACK)."); + } + + const char *cfg_ptr = msg; + while ((cfg_ptr = strstr(cfg_ptr, "cfg:")) != nullptr) { + cfg_ptr += 4; + const char *end = strchr(cfg_ptr, ','); + size_t len_cfg = end ? (size_t)(end - cfg_ptr) : strlen(cfg_ptr); + if (len_cfg >= sizeof(g_pending_sensor_configs[0])) len_cfg = sizeof(g_pending_sensor_configs[0]) - 1; + + char new_cfg[32]; + memcpy(new_cfg, cfg_ptr, len_cfg); + new_cfg[len_cfg] = '\0'; + + bool exists = false; + for (uint8_t i = 0; i < g_pending_sensor_configs_count; i++) { + if (strcmp(g_pending_sensor_configs[i], new_cfg) == 0) { + exists = true; + break; + } + } + + if (!exists && g_pending_sensor_configs_count < 4) { + strncpy(g_pending_sensor_configs[g_pending_sensor_configs_count], new_cfg, sizeof(g_pending_sensor_configs[0]) - 1); + g_pending_sensor_configs[g_pending_sensor_configs_count][sizeof(g_pending_sensor_configs[0]) - 1] = '\0'; + g_pending_sensor_configs_count++; + DBG_PRINTF("Added new config: %s\n", new_cfg); + } + + if (!end) break; + cfg_ptr = end; + } + + DBG_PRINTLN("Setting send_ack_process_complete to TRUE"); + send_ack_process_complete = true; +} + +void add_Peer(const uint8_t* addr) { + esp_now_peer_info_t peer{}; + memcpy(peer.peer_addr, addr, 6); + peer.ifidx = WIFI_IF_STA; + peer.channel = 0; + peer.encrypt = false; + if (!esp_now_is_peer_exist(addr)) esp_now_add_peer(&peer); +} + +void ensure_Peers() { + for (size_t i=0; i<SENSOR_CONFIG_COUNT; i++) add_Peer(sensor_configs[i].pump_mac); + add_Peer(PEER_SERVER); +} + +void init_EspNow() { + if (esp_now_init() != ESP_OK) { + DBG_PRINTLN("ESP NOW init failed"); + while (true); + } + esp_now_register_send_cb(on_DataSent); + ensure_Peers(); +} + +bool send_Message(const uint8_t* peer, const char* message) { + if (!esp_now_is_peer_exist(peer)) { + DBG_PRINTLN("ERROR: peer doese not exist!"); + return false; + } + esp_err_t result = esp_now_send(peer, (uint8_t*)message, strlen(message)); + return result == ESP_OK; +} + +bool send_And_wait_Ack(const uint8_t* peer, const char* message, uint32_t timeout_ms = 5000) { + send_ack_process_complete = false; + ack_received = false; + + esp_now_register_recv_cb(on_DataRecvAck); + + if (!send_Message(peer, message)) { + DBG_PRINTLN("Failed to queue message for sending!"); + return false; + } + + unsigned long start = millis(); + while (!ack_received && millis() - start < timeout_ms) { + delay(10); + } + + if (!ack_received) { + DBG_PRINT("No ACK received from "); + printMac(peer); + return false; + } + + return true; +} + +// ---------------------------------------------------------------------------- +// Time handling +// ---------------------------------------------------------------------------- +uint64_t get_CurrentUnixMs() { + time_t now_sec; + time(&now_sec); + struct timeval tv; + gettimeofday(&tv, nullptr); + return (uint64_t)tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; +} + +bool request_TimeSync(uint32_t timeout_ms) { + const uint8_t* server_mac = PEER_SERVER; + unsigned long start = millis(); + esp_now_register_recv_cb(on_DataRecv); + send_Message(server_mac, "time?"); + while (millis() - start < timeout_ms) { + if (rtc_synced_with_server) return true; + delay(50); + } + return false; +} + +void setup_Timezone() { + setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); + tzset(); +} + +void get_LocalTime(time_t unix_time, int *hour, int *minute) { + struct tm tm_time; + localtime_r(&unix_time, &tm_time); + *hour = tm_time.tm_hour; + *minute = tm_time.tm_min; +} + +uint16_t get_CurrentMinutes() { + uint64_t t = get_CurrentUnixMs(); + if (t == 0) return 0; + time_t sec = t / 1000; + int hour, minute; + get_LocalTime(sec, &hour, &minute); + return hour * 60 + minute; +} + +// ---------------------------------------------------------------------------- +// Deep sleep scheduler +// ---------------------------------------------------------------------------- +void sleep_UntilNextSend() { + uint64_t ms_now = get_CurrentUnixMs(); + uint32_t min_delay_ms = UINT32_MAX; + + for (size_t i = 0; i < SENSOR_CONFIG_COUNT; i++) { + SensorConfig_s & p = sensor_configs[i]; + if (!p.is_active) continue; + + if (p.use_time) { + uint16_t min_current = get_CurrentMinutes(); + for (size_t j = 0; j < p.time_count; j++) { + int16_t min_delta = (int16_t)p.times[j] - (int16_t)min_current; + if (min_delta <= 0) min_delta += 24 * 60; // wrap to next day + uint32_t delta_ms = min_delta * 60 * 1000; + if (delta_ms < min_delay_ms) min_delay_ms = delta_ms; + } + } else { + uint32_t delta_ms = (p.delay_ms > (ms_now - last_sent_array[i])) + ? (p.delay_ms - (ms_now - last_sent_array[i])) + : 0; + if (delta_ms < min_delay_ms) min_delay_ms = delta_ms; + } + } + + if (min_delay_ms == UINT32_MAX) return; + + // Prevent sleeping if next send is within 1 minute + if (min_delay_ms < min_sleep_time) { + DBG_PRINTF("Delaying for %u ms\n", min_delay_ms); + DBG_PRINTF("######################\n\n"); + delay(min_delay_ms); + return; + } + + DBG_PRINTF("Sleeping for %u ms\n", min_delay_ms); + esp_sleep_enable_timer_wakeup((uint64_t)min_delay_ms * 1000ULL); + // set server_time for the case we dont get a new connection. + // idk why its 1000 to big,.. + DBG_PRINTF("######################\n\n"); + esp_deep_sleep_start(); +} + +// ---------------------------------------------------------------------------- +// Request sensor config from server via ESP-NOW +// ---------------------------------------------------------------------------- +volatile +bool sensor_config_received = false; +char sensor_config_message[256] = { 0 }; + +void on_DataRecvSensorConfig(const esp_now_recv_info* info, const uint8_t* data, int len) { + if (!info || !data || len <= 0) return; + if (memcmp(info->src_addr, PEER_SERVER, 6) != 0) return; + + int copylen = len < (int)sizeof(sensor_config_message) - 1 ? len : (int)sizeof(sensor_config_message) - 1; + memcpy(sensor_config_message, data, copylen); + sensor_config_message[copylen] = '\0'; + + if (strncmp(sensor_config_message, "cfg:", 4) != 0) return; + + sensor_config_received = true; +} + +bool request_SensorConfig(const char* sensor_name) { + const uint8_t* server_mac = PEER_SERVER; + if (!esp_now_is_peer_exist(server_mac)) add_Peer(server_mac); + + sensor_config_received = false; + memset(sensor_config_message, 0, sizeof(sensor_config_message)); + + esp_now_register_recv_cb(on_DataRecvSensorConfig); + + char msg[64]; + snprintf(msg, sizeof(msg), "get_config:%s", sensor_name); + + if (!send_Message(server_mac, msg)) { + DBG_PRINTLN("Failed to send get_config request"); + return false; + } + + unsigned long start = millis(); + const uint32_t max_wait = 10000; + + while (!sensor_config_received && millis() - start < max_wait) { + delay(50); + yield(); + } + + if (!sensor_config_received) { + DBG_PRINTLN("No config reply received (timeout)"); + return false; + } + + if (strncmp(sensor_config_message, "cfg:", 4) != 0) { + DBG_PRINTF("Invalid config response: %s\n", sensor_config_message); + return false; + } + + char* ptr = sensor_config_message + 4; + char* tokens[20]; + uint8_t count_token = 0; + char* tok = strtok(ptr, ","); + while (tok && count_token < 20) { + tokens[count_token++] = tok; + tok = strtok(nullptr, ","); + } + + if (count_token == 0) return false; + + DBG_PRINTF("Config received for %s:\n", tokens[0]); + for (uint8_t i = 1; i < count_token; i++) { + DBG_PRINTF(" %u: %s\n", i, tokens[i]); + } + + + if (count_token < 9) { + DBG_PRINTLN("Incomplete config data"); + return false; + } + + SensorConfig_s *cfg = NULL; + size_t config_id = 0; + for (size_t i = 0; i < SENSOR_CONFIG_COUNT; i++) { + if (strcmp(sensor_configs[i].sensor_name, tokens[0]) == 0) { + config_id = i; + cfg = &sensor_configs[i]; + break; + } + } + + if (!cfg) { + DBG_PRINTF("Unknown sensor name in config: %s\n", tokens[0]); + return false; + } + + // basic fields + strncpy(cfg->sensor_name, tokens[0], sizeof(cfg->sensor_name)-1); + cfg->sensor_name[sizeof(cfg->sensor_name)-1] = '\0'; + cfg->is_active = atoi(tokens[1]) != 0; + cfg->pin = atoi(tokens[2]); + cfg->min_moisture = atoi(tokens[3]); + cfg->notification_delay_ms = atoi(tokens[4]); + strncpy(cfg->notification_name, tokens[5], sizeof(cfg->notification_name)-1); + cfg->notification_name[sizeof(cfg->notification_name)-1] = '\0'; + strncpy((char*)cfg->pump_mac, tokens[6], sizeof(cfg->pump_mac)-1); + cfg->pump_mac[sizeof(cfg->pump_mac)-1] = '\0'; + cfg->delay_ms = atoi(tokens[7]); + cfg->use_time = atoi(tokens[8]) != 0; + + // times + cfg->time_count = 0; + if (cfg->use_time && count_token > 9 && strncmp(tokens[9], "times=", 6) == 0) { + char *time_str = tokens[9] + 6; + char *time_tok = strtok(time_str, "|"); + while (time_tok && cfg->time_count < 8) { + cfg->times[cfg->time_count++] = (uint16_t)atoi(time_tok); + time_tok = strtok(NULL, "|"); + } + } + + config_request_attempts = 0; + return true; +} + +void process_PendingSensorConfigs() { + config_request_attempts = 0; + DBG_PRINTF("NEW CONFIGS: %d\n", g_pending_sensor_configs_count); + + size_t config_id = 0; + bool save_new_configs = false; + while (config_id < g_pending_sensor_configs_count) { + DBG_PRINTF("SENSOR: %s\n", g_pending_sensor_configs[config_id]); + + bool success = false; + for (config_request_attempts = 0; + config_request_attempts < config_request_attempts_max; + config_request_attempts++) { + + DBG_PRINTF("Requesting config attempt %u...\n", config_request_attempts + 1); + if (request_SensorConfig(g_pending_sensor_configs[config_id])) { + success = true; + break; + } + delay(2000); + } + + if (success) { + DBG_PRINTF("Config %s processed successfully, removing.\n", + g_pending_sensor_configs[config_id]); + if (config_id >= g_pending_sensor_configs_count) return; + for (size_t i = config_id; i < g_pending_sensor_configs_count - 1; i++) + strcpy(g_pending_sensor_configs[i], g_pending_sensor_configs[i + 1]); + g_pending_sensor_configs_count--; + save_new_configs = true; + } else { + DBG_PRINTLN("Config request failed repeatedly, aborting loop."); + break; + } + } + + if (save_new_configs) save_SensorConfigs(); +} + +void get_AllSensorConfigs() { + g_pending_sensor_configs_count = 0; + + for (size_t i = 0; i < SENSOR_CONFIG_COUNT; ++i) { + strncpy( + g_pending_sensor_configs[g_pending_sensor_configs_count], + sensor_configs[i].sensor_name, + sizeof(g_pending_sensor_configs[0]) - 1 + ); + + g_pending_sensor_configs[g_pending_sensor_configs_count][sizeof(g_pending_sensor_configs[0]) - 1] = '\0'; + + ++g_pending_sensor_configs_count; + } + + + process_PendingSensorConfigs(); + if (g_pending_sensor_configs_count == 0) + all_init_configs_received = true; +} + +// ---------------------------------------------------------------------------- +// Send Data to peers +// ---------------------------------------------------------------------------- + +void send_DataToPeer(size_t config_id, uint32_t value) { + uint64_t ms_now = get_CurrentUnixMs(); + + SensorConfig_s& p = sensor_configs[config_id]; + + DBG_PRINTF("SENSOR: %s\n", p.sensor_name); + printSensorConfig(p); + if (!p.is_active) { + DBG_PRINTLN("Sensor is inactive!"); + return; + } + + bool should_send = false; + + if (p.use_time) { + uint16_t min_current = get_CurrentMinutes(); + for (size_t j = 0; j < p.time_count; j++) { + if (min_current == p.times[j] && + ms_now - last_sent_array[config_id] > 60000) { + should_send = true; + break; + } + } + } else { + if (ms_now - last_sent_array[config_id] >= p.delay_ms) should_send = true; + } + + if (should_send) { + char message[32]; + snprintf(message, sizeof(message), "%s:%u", p.sensor_name, value); + + DBG_PRINT("Sending to peer: "); + printMac(p.pump_mac); + send_ack_process_complete = false; + if (!send_And_wait_Ack(p.pump_mac, message)) { + DBG_PRINTLN("Delivery failed or peer offline!"); + send_ack_process_complete = true; + } else { + last_sent_array[config_id] = get_CurrentUnixMs(); + value++; + } + + while (!send_ack_process_complete) delay(100); + + DBG_PRINT("Sending to peer: "); + printMac(PEER_SERVER); + send_ack_process_complete = false; + if (!send_And_wait_Ack(PEER_SERVER, message)) { + DBG_PRINTLN("Failed to get ack from server!"); + send_ack_process_complete = true; + } + while (!send_ack_process_complete) delay(100); + + delay(5000); + } +} + diff --git a/client_sensor/ClientSensor.h b/client_sensor/ClientSensor.h new file mode 100644 index 0000000..7130a98 --- /dev/null +++ b/client_sensor/ClientSensor.h @@ -0,0 +1,65 @@ +#ifndef CLIENTSENSOR_H +#define CLIENTSENSOR_H + +#include "globals.h" + +#include <Arduino.h> +#include <WiFi.h> +#include <esp_wifi.h> +#include <esp_now.h> +#include <esp_sleep.h> +#include <sys/time.h> + + +// ---------------------------------------------------------------------------- +// WiFi handling +// ---------------------------------------------------------------------------- +void init_WiFi(); + + +// ---------------------------------------------------------------------------- +// Peers +// ---------------------------------------------------------------------------- + +// ---------------------------------------------------------------------------- +// Config to flash +// ---------------------------------------------------------------------------- + +void restore_SensorConfigs(); + + +// ---------------------------------------------------------------------------- +// ESP-NOW handling +// ---------------------------------------------------------------------------- + +void ensure_Peers(); +void init_EspNow(); + +// ---------------------------------------------------------------------------- +// Time handling +// ---------------------------------------------------------------------------- + +uint64_t get_CurrentUnixMs(); +bool request_TimeSync(uint32_t timeout_ms = 3000); +void setup_Timezone(); + +// ---------------------------------------------------------------------------- +// Deep sleep scheduler +// ---------------------------------------------------------------------------- +void sleep_UntilNextSend(); + +// ---------------------------------------------------------------------------- +// Request sensor config from server via ESP-NOW +// ---------------------------------------------------------------------------- + +void get_AllSensorConfigs(); +void process_PendingSensorConfigs(); + +// ---------------------------------------------------------------------------- +// Send Data to peers +// ---------------------------------------------------------------------------- + +void send_DataToPeer(size_t config_id, uint32_t value); + + +#endif diff --git a/client_sensor/client_sensor.ino b/client_sensor/client_sensor.ino index 37709f7..4155e53 100644 --- a/client_sensor/client_sensor.ino +++ b/client_sensor/client_sensor.ino @@ -1,632 +1,10 @@ -#include "secrets.h" -#include "globals.h" +#include "ClientSensor.h" -#include <Arduino.h> -#include <WiFi.h> -#include <esp_wifi.h> -#include <esp_now.h> -#include <esp_sleep.h> -#include <sys/time.h> - -volatile bool rtc_synced_with_server = false; -volatile bool ack_received = false; -volatile bool send_process_complete = false; -volatile bool send_ack_process_complete = false; - -const uint32_t min_sleep_time = 60000; - - -// ---------------------------------------------------------------------------- -// Helper -// ---------------------------------------------------------------------------- -void print_Mac(const uint8_t* mac) { - for (size_t i = 0; i < 6; i++) { - if (i > 0) DBG_PRINT(":"); - DBG_PRINTF("%02X", mac[i]); - } - DBG_PRINTLN(); -} - -// ---------------------------------------------------------------------------- -// WiFi handling -// ---------------------------------------------------------------------------- -int32_t get_WiFiChannel() { - DBG_PRINTLN(ssid); - int32_t n = WiFi.scanNetworks(); - if (n <= 0) return 0; - for (uint8_t i = 0; i < n; i++) { - if (!strcmp(ssid, WiFi.SSID(i).c_str())) return WiFi.channel(i); - } - return 0; -} - -void init_WiFi() { - WiFi.mode(WIFI_MODE_STA); - int32_t channel = get_WiFiChannel(); - if (channel > 0) { - esp_wifi_set_promiscuous(true); - esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); - esp_wifi_set_promiscuous(false); - } -} - - -// ---------------------------------------------------------------------------- -// Peers -// ---------------------------------------------------------------------------- -constexpr uint8_t PEER_SERVER[] = {0x58,0xcf,0x79,0x1e,0xed,0x18}; -constexpr uint8_t PEER_PUMP[] = {0x51,0xc1,0x79,0x1e,0xed,0x18}; -RTC_DATA_ATTR SensorConfig_s sensor_configs[] = { - {"moisture_1", true, 11, 10, 0, "pump_1", {0}, 90000, true, {465, 466}, 2}, - {"moisture_2", true, 11, 10, 0, "pump_2", {0}, 120000, false, {0}, 0} -}; - -constexpr size_t num_sensor_configs = sizeof(sensor_configs)/sizeof(SensorConfig_s); - -uint8_t config_request_attempts = 0; -uint8_t config_request_attempts_max = 3; - -void printSensorConfig(const SensorConfig_s& cfg) -{ - DBG_PRINTF("SensorConfig for '%s'\n", cfg.sensor_name); - DBG_PRINTF(" is_active: %s\n", cfg.is_active ? "true" : "false"); - DBG_PRINTF(" pin: %d\n", cfg.pin); - DBG_PRINTF(" min_moisture: %d\n", cfg.min_moisture); - DBG_PRINTF(" notification_delay_ms: %d\n", cfg.notification_delay_ms); - DBG_PRINTF(" notification_name: %s\n", cfg.notification_name); - DBG_PRINTF(" pump_mac: %s\n", (char*)cfg.pump_mac); - DBG_PRINTF(" delay_ms: %d\n", cfg.delay_ms); - DBG_PRINTF(" use_time: %s\n", cfg.use_time ? "true" : "false"); - DBG_PRINTF(" time_count: %u\n", cfg.time_count); - - if (cfg.time_count > 0) { - DBG_PRINT(" times: "); - for (uint8_t i = 0; i < cfg.time_count; i++) { - DBG_PRINTF("%u", cfg.times[i]); - if (i < cfg.time_count - 1) - DBG_PRINT("|"); - } - DBG_PRINTLN(""); - } -} - -// ---------------------------------------------------------------------------- -// Config to flash -// ---------------------------------------------------------------------------- - -#include <Preferences.h> - -Preferences prefs; - -bool load_SensorConfigs() { - DBG_PRINTLN("Loading sensor configs from NVS..."); - - prefs.begin("sensors", true); - size_t stored_size = prefs.getBytesLength("configs"); - DBG_PRINTF("Stored size: %u bytes\n", (unsigned)stored_size); - bool ok = false; - if (stored_size == sizeof(sensor_configs)) { - prefs.getBytes("configs", sensor_configs, sizeof(sensor_configs)); - ok = true; - DBG_PRINTLN("Config read successfully."); - } else DBG_PRINTLN("No valid stored config found."); - prefs.end(); - - return ok; -} - -void save_SensorConfigs() { - DBG_PRINTLN("Saving sensor configs to NVS..."); - - prefs.begin("sensors", false); - prefs.putBytes("configs", sensor_configs, sizeof(sensor_configs)); - prefs.end(); - - DBG_PRINTLN("Save complete."); -} - -void restore_SensorConfigs() { - DBG_PRINTLN("Trying to load old config!"); - - bool restored = load_SensorConfigs(); - if (restored) { - DBG_PRINTLN("Sensor configs restored from flash."); - } else { - DBG_PRINTLN("No stored configs found, using defaults."); - for (size_t i = 0; i < num_sensor_configs; i++) - memcpy(sensor_configs[i].pump_mac, PEER_PUMP, sizeof(PEER_PUMP)); - } -} - - -// ---------------------------------------------------------------------------- -// RTC variables -// ---------------------------------------------------------------------------- -RTC_DATA_ATTR uint64_t last_sent_array[num_sensor_configs] = {0}; // this is going to be a sensor read value! +// RTC_DATA_ATTR is not required than! RTC_DATA_ATTR uint32_t value_to_send = 0; -// ---------------------------------------------------------------------------- -// Time handling -// ---------------------------------------------------------------------------- -uint64_t get_CurrentUnixMs() { - time_t now_sec; - time(&now_sec); - struct timeval tv; - gettimeofday(&tv, nullptr); - return (uint64_t)tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; -} - -bool request_TimeSync(uint32_t timeout_ms = 3000) { - const uint8_t* server_mac = PEER_SERVER; - unsigned long start = millis(); - esp_now_register_recv_cb(on_DataRecv); - send_Message(server_mac, "time?"); - while (millis() - start < timeout_ms) { - if (rtc_synced_with_server) return true; - delay(50); - } - return false; -} - -void setup_Timezone() { - setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); - tzset(); -} - -void get_LocalTime(time_t unix_time, int *hour, int *minute) { - struct tm tm_time; - localtime_r(&unix_time, &tm_time); - *hour = tm_time.tm_hour; - *minute = tm_time.tm_min; -} - -uint16_t get_CurrentMinutes() { - uint64_t t = get_CurrentUnixMs(); - if (t == 0) return 0; - time_t sec = t / 1000; - int hour, minute; - get_LocalTime(sec, &hour, &minute); - return hour * 60 + minute; -} - -// ---------------------------------------------------------------------------- -// ESP-NOW handling -// ---------------------------------------------------------------------------- -void on_DataSent(const wifi_tx_info_t* info, esp_now_send_status_t status) { - DBG_PRINT("Packet to "); - for (int i = 0; i < 6; i++) { - DBG_PRINTF("%02X", info->des_addr[i]); - if (i < 5) DBG_PRINT(":"); - } - - if (status == ESP_NOW_SEND_SUCCESS) - DBG_PRINTLN(" queued successfully (waiting for ACK)"); - else - DBG_PRINTLN(" failed to queue!"); -} - -void on_DataRecv(const esp_now_recv_info *info, const uint8_t *data, int len) { - if (!info || !data || len <= 0) return; - - char msg[256]; - memcpy(msg, data, len); - msg[len] = '\0'; - - DBG_PRINTF("Received from server: %s\n", msg); - - if (strncmp(msg, "time:", 5) == 0) { - time_t server_unix = strtoul(msg + 5, nullptr, 10); - struct timeval tv = { .tv_sec = server_unix, .tv_usec = 0 }; - settimeofday(&tv, nullptr); - rtc_synced_with_server = true; - DBG_PRINTLN("System time updated from server."); - } - - DBG_PRINTLN("Setting send_process_complete to TRUE"); - send_process_complete = true; -} - - -void on_DataRecvAck(const esp_now_recv_info *info, const uint8_t *data, int len) { - if (!info || !data || len <= 0) return; - - char msg[256]; - int copylen = len < (int)sizeof(msg) - 1 ? len : (int)sizeof(msg) - 1; - memcpy(msg, data, copylen); - msg[copylen] = '\0'; - - DBG_PRINTF("ACK received: %s\n", msg); - - const char *time_ptr = strstr(msg, "time:"); - if (time_ptr) { - time_t server_unix = strtoul(time_ptr + 5, nullptr, 10); - struct timeval tv = { .tv_sec = server_unix, .tv_usec = 0 }; - settimeofday(&tv, nullptr); - rtc_synced_with_server = true; - ack_received = true; - DBG_PRINTLN("System time updated from server (ACK)."); - } - - const char *cfg_ptr = msg; - while ((cfg_ptr = strstr(cfg_ptr, "cfg:")) != nullptr) { - cfg_ptr += 4; - const char *end = strchr(cfg_ptr, ','); - size_t len_cfg = end ? (size_t)(end - cfg_ptr) : strlen(cfg_ptr); - if (len_cfg >= sizeof(g_pending_sensor_configs[0])) len_cfg = sizeof(g_pending_sensor_configs[0]) - 1; - - char new_cfg[32]; - memcpy(new_cfg, cfg_ptr, len_cfg); - new_cfg[len_cfg] = '\0'; - - bool exists = false; - for (uint8_t i = 0; i < g_pending_sensor_configs_count; i++) { - if (strcmp(g_pending_sensor_configs[i], new_cfg) == 0) { - exists = true; - break; - } - } - - if (!exists && g_pending_sensor_configs_count < 4) { - strncpy(g_pending_sensor_configs[g_pending_sensor_configs_count], new_cfg, sizeof(g_pending_sensor_configs[0]) - 1); - g_pending_sensor_configs[g_pending_sensor_configs_count][sizeof(g_pending_sensor_configs[0]) - 1] = '\0'; - g_pending_sensor_configs_count++; - DBG_PRINTF("Added new config: %s\n", new_cfg); - } - - if (!end) break; - cfg_ptr = end; - } - - DBG_PRINTLN("Setting send_ack_process_complete to TRUE"); - send_ack_process_complete = true; -} - -void add_Peer(const uint8_t* addr) { - esp_now_peer_info_t peer{}; - memcpy(peer.peer_addr, addr, 6); - peer.ifidx = WIFI_IF_STA; - peer.channel = 0; - peer.encrypt = false; - if (!esp_now_is_peer_exist(addr)) esp_now_add_peer(&peer); -} - -void ensure_Peers() { - for (size_t i=0; i<num_sensor_configs; i++) add_Peer(sensor_configs[i].pump_mac); - add_Peer(PEER_SERVER); -} - -void init_EspNow() { - if (esp_now_init() != ESP_OK) { - DBG_PRINTLN("ESP NOW init failed"); - while (true); - } - esp_now_register_send_cb(on_DataSent); - ensure_Peers(); -} - -bool send_Message(const uint8_t* peer, const char* message) { - if (!esp_now_is_peer_exist(peer)) { - DBG_PRINTLN("ERROR: peer doese not exist!"); - return false; - } - esp_err_t result = esp_now_send(peer, (uint8_t*)message, strlen(message)); - return result == ESP_OK; -} - -bool send_And_wait_Ack(const uint8_t* peer, const char* message, uint32_t timeout_ms = 5000) { - send_ack_process_complete = false; - ack_received = false; - - esp_now_register_recv_cb(on_DataRecvAck); - - if (!send_Message(peer, message)) { - DBG_PRINTLN("Failed to queue message for sending!"); - return false; - } - - unsigned long start = millis(); - while (!ack_received && millis() - start < timeout_ms) { - delay(10); - } - - if (!ack_received) { - DBG_PRINT("No ACK received from "); - print_Mac(peer); - return false; - } - - return true; -} - -// ---------------------------------------------------------------------------- -// Deep sleep scheduler -// ---------------------------------------------------------------------------- -void sleep_UntilNextSend() { - uint64_t ms_now = get_CurrentUnixMs(); - uint32_t min_delay_ms = UINT32_MAX; - - for (size_t i = 0; i < num_sensor_configs; i++) { - SensorConfig_s & p = sensor_configs[i]; - if (!p.is_active) continue; - - if (p.use_time) { - uint16_t min_current = get_CurrentMinutes(); - for (size_t j = 0; j < p.time_count; j++) { - int16_t min_delta = (int16_t)p.times[j] - (int16_t)min_current; - if (min_delta <= 0) min_delta += 24 * 60; // wrap to next day - uint32_t delta_ms = min_delta * 60 * 1000; - if (delta_ms < min_delay_ms) min_delay_ms = delta_ms; - } - } else { - uint32_t delta_ms = (p.delay_ms > (ms_now - last_sent_array[i])) - ? (p.delay_ms - (ms_now - last_sent_array[i])) - : 0; - if (delta_ms < min_delay_ms) min_delay_ms = delta_ms; - } - } - - if (min_delay_ms == UINT32_MAX) return; - - // Prevent sleeping if next send is within 1 minute - if (min_delay_ms < min_sleep_time) { - DBG_PRINTF("Delaying for %u ms\n", min_delay_ms); - DBG_PRINTF("######################\n\n"); - delay(min_delay_ms); - return; - } - - DBG_PRINTF("Sleeping for %u ms\n", min_delay_ms); - esp_sleep_enable_timer_wakeup((uint64_t)min_delay_ms * 1000ULL); - // set server_time for the case we dont get a new connection. - // idk why its 1000 to big,.. - DBG_PRINTF("######################\n\n"); - esp_deep_sleep_start(); -} - -// ---------------------------------------------------------------------------- -// Request sensor config from server via ESP-NOW -// ---------------------------------------------------------------------------- -RTC_DATA_ATTR bool all_init_configs_received = false; -volatile bool sensor_config_received = false; -char sensor_config_message[256] = {0}; - -void on_DataRecvSensorConfig(const esp_now_recv_info* info, const uint8_t* data, int len) { - if (!info || !data || len <= 0) return; - if (memcmp(info->src_addr, PEER_SERVER, 6) != 0) return; - - int copylen = len < (int)sizeof(sensor_config_message) - 1 ? len : (int)sizeof(sensor_config_message) - 1; - memcpy(sensor_config_message, data, copylen); - sensor_config_message[copylen] = '\0'; - - if (strncmp(sensor_config_message, "cfg:", 4) != 0) return; - - sensor_config_received = true; -} - -bool request_SensorConfig(const char* sensor_name) { - const uint8_t* server_mac = PEER_SERVER; - if (!esp_now_is_peer_exist(server_mac)) add_Peer(server_mac); - - sensor_config_received = false; - memset(sensor_config_message, 0, sizeof(sensor_config_message)); - - esp_now_register_recv_cb(on_DataRecvSensorConfig); - - char msg[64]; - snprintf(msg, sizeof(msg), "get_config:%s", sensor_name); - - if (!send_Message(server_mac, msg)) { - DBG_PRINTLN("Failed to send get_config request"); - return false; - } - - unsigned long start = millis(); - const uint32_t max_wait = 10000; - - while (!sensor_config_received && millis() - start < max_wait) { - delay(50); - yield(); - } - - if (!sensor_config_received) { - DBG_PRINTLN("No config reply received (timeout)"); - return false; - } - - if (strncmp(sensor_config_message, "cfg:", 4) != 0) { - DBG_PRINTF("Invalid config response: %s\n", sensor_config_message); - return false; - } - - char* ptr = sensor_config_message + 4; - char* tokens[20]; - uint8_t count_token = 0; - char* tok = strtok(ptr, ","); - while (tok && count_token < 20) { - tokens[count_token++] = tok; - tok = strtok(nullptr, ","); - } - - if (count_token == 0) return false; - - DBG_PRINTF("Config received for %s:\n", tokens[0]); - for (uint8_t i = 1; i < count_token; i++) { - DBG_PRINTF(" %u: %s\n", i, tokens[i]); - } - - - if (count_token < 9) { - DBG_PRINTLN("Incomplete config data"); - return false; - } - - SensorConfig_s *cfg = NULL; - size_t config_id = 0; - for (size_t i = 0; i < sizeof(sensor_configs) / sizeof(sensor_configs[0]); i++) { - if (strcmp(sensor_configs[i].sensor_name, tokens[0]) == 0) { - config_id = i; - cfg = &sensor_configs[i]; - break; - } - } - - if (!cfg) { - DBG_PRINTF("Unknown sensor name in config: %s\n", tokens[0]); - return false; - } - - // basic fields - strncpy(cfg->sensor_name, tokens[0], sizeof(cfg->sensor_name)-1); - cfg->sensor_name[sizeof(cfg->sensor_name)-1] = '\0'; - cfg->is_active = atoi(tokens[1]) != 0; - cfg->pin = atoi(tokens[2]); - cfg->min_moisture = atoi(tokens[3]); - cfg->notification_delay_ms = atoi(tokens[4]); - strncpy(cfg->notification_name, tokens[5], sizeof(cfg->notification_name)-1); - cfg->notification_name[sizeof(cfg->notification_name)-1] = '\0'; - strncpy((char*)cfg->pump_mac, tokens[6], sizeof(cfg->pump_mac)-1); - cfg->pump_mac[sizeof(cfg->pump_mac)-1] = '\0'; - cfg->delay_ms = atoi(tokens[7]); - cfg->use_time = atoi(tokens[8]) != 0; - - // times - cfg->time_count = 0; - if (cfg->use_time && count_token > 9 && strncmp(tokens[9], "times=", 6) == 0) { - char *time_str = tokens[9] + 6; - char *time_tok = strtok(time_str, "|"); - while (time_tok && cfg->time_count < 8) { - cfg->times[cfg->time_count++] = (uint16_t)atoi(time_tok); - time_tok = strtok(NULL, "|"); - } - } - - config_request_attempts = 0; - return true; -} - -void process_PendingSensorConfigs() { - config_request_attempts = 0; - DBG_PRINTF("NEW CONFIGS: %d\n", g_pending_sensor_configs_count); - - size_t config_id = 0; - bool save_new_configs = false; - while (config_id < g_pending_sensor_configs_count) { - DBG_PRINTF("SENSOR: %s\n", g_pending_sensor_configs[config_id]); - - bool success = false; - for (config_request_attempts = 0; - config_request_attempts < config_request_attempts_max; - config_request_attempts++) { - - DBG_PRINTF("Requesting config attempt %u...\n", config_request_attempts + 1); - if (request_SensorConfig(g_pending_sensor_configs[config_id])) { - success = true; - break; - } - delay(2000); - } - - if (success) { - DBG_PRINTF("Config %s processed successfully, removing.\n", - g_pending_sensor_configs[config_id]); - if (config_id >= g_pending_sensor_configs_count) return; - for (size_t i = config_id; i < g_pending_sensor_configs_count - 1; i++) - strcpy(g_pending_sensor_configs[i], g_pending_sensor_configs[i + 1]); - g_pending_sensor_configs_count--; - save_new_configs = true; - } else { - DBG_PRINTLN("Config request failed repeatedly, aborting loop."); - break; - } - } - - if (save_new_configs) save_SensorConfigs(); -} - -void get_AllSensorConfigs() { - g_pending_sensor_configs_count = 0; - - for (size_t i = 0; i < num_sensor_configs; ++i) { - strncpy( - g_pending_sensor_configs[g_pending_sensor_configs_count], - sensor_configs[i].sensor_name, - sizeof(g_pending_sensor_configs[0]) - 1 - ); - - g_pending_sensor_configs[g_pending_sensor_configs_count][sizeof(g_pending_sensor_configs[0]) - 1] = '\0'; - - ++g_pending_sensor_configs_count; - } - - - process_PendingSensorConfigs(); - if (g_pending_sensor_configs_count == 0) - all_init_configs_received = true; -} - -// ---------------------------------------------------------------------------- -// Send Data to peers -// ---------------------------------------------------------------------------- - -void send_DataToPeer(size_t config_id, uint32_t value) { - uint64_t ms_now = get_CurrentUnixMs(); - - SensorConfig_s& p = sensor_configs[config_id]; - - DBG_PRINTF("SENSOR: %s\n", p.sensor_name); - printSensorConfig(p); - if (!p.is_active) { - DBG_PRINTLN("Sensor is inactive!"); - return; - } - - bool should_send = false; - - if (p.use_time) { - uint16_t min_current = get_CurrentMinutes(); - for (size_t j = 0; j < p.time_count; j++) { - if (min_current == p.times[j] && - ms_now - last_sent_array[config_id] > 60000) { - should_send = true; - break; - } - } - } else { - if (ms_now - last_sent_array[config_id] >= p.delay_ms) should_send = true; - } - - if (should_send) { - char message[32]; - snprintf(message, sizeof(message), "%s:%u", p.sensor_name, value); - - DBG_PRINT("Sending to peer: "); - print_Mac(p.pump_mac); - send_ack_process_complete = false; - if (!send_And_wait_Ack(p.pump_mac, message)) { - DBG_PRINTLN("Delivery failed or peer offline!"); - send_ack_process_complete = true; - } else { - last_sent_array[config_id] = get_CurrentUnixMs(); - value++; - } - - while (!send_ack_process_complete) delay(100); - - DBG_PRINT("Sending to peer: "); - print_Mac(PEER_SERVER); - send_ack_process_complete = false; - if (!send_And_wait_Ack(PEER_SERVER, message)) { - DBG_PRINTLN("Failed to get ack from server!"); - send_ack_process_complete = true; - } - while (!send_ack_process_complete) delay(100); - - delay(5000); - } -} - // ---------------------------------------------------------------------------- // Setup // ---------------------------------------------------------------------------- @@ -660,7 +38,7 @@ void loop() { ensure_Peers(); DBG_PRINTF("## Unix time: %llu\n", (unsigned long long)get_CurrentUnixMs() / 1000); - for (size_t config_id = 0; config_id < num_sensor_configs; config_id++) { + for (size_t config_id = 0; config_id < SENSOR_CONFIG_COUNT; config_id++) { send_DataToPeer(config_id, value_to_send); } diff --git a/globals.cpp b/globals.cpp index a91d74e..034bf82 100644 --- a/globals.cpp +++ b/globals.cpp @@ -8,3 +8,21 @@ char g_pending_sensor_configs[4][32] = {}; uint8_t g_pending_sensor_configs_count = 0; bool g_received_ok_from_server = false; + + + + + +// Client sensor stuff +const uint32_t min_sleep_time = 60000; +RTC_DATA_ATTR bool all_init_configs_received = false; + +constexpr uint8_t PEER_SERVER[] = {0x58,0xcf,0x79,0x1e,0xed,0x18}; +constexpr uint8_t PEER_PUMP[] = {0x51,0xc1,0x79,0x1e,0xed,0x18}; + +RTC_DATA_ATTR SensorConfig_s sensor_configs[] = { + {"moisture_1", true, 11, 10, 0, "pump_1", {0}, 90000, true, {465, 466}, 2}, + {"moisture_2", true, 11, 10, 0, "pump_2", {0}, 120000, false, {0}, 0} +}; + +uint8_t config_request_attempts_max = 3; diff --git a/globals.h b/globals.h index 2f1be4c..6752685 100644 --- a/globals.h +++ b/globals.h @@ -31,6 +31,8 @@ #define JSON_PER_ENTRY (29 + (TABLE_SIZE-1) + (VALUE_SIZE-1) + 10) #define JSON_BUFFER_SIZE (BUFFER_SIZE * JSON_PER_ENTRY + 2) +#define MAC_ADDR_LEN 6 + struct SensorConfigServer_s { char sensor_name[32]; @@ -81,3 +83,15 @@ extern uint32_t g_time_now; extern char g_pending_sensor_configs[4][32]; extern uint8_t g_pending_sensor_configs_count; extern bool g_received_ok_from_server; + + + + + +extern const uint32_t min_sleep_time; +extern RTC_DATA_ATTR bool all_init_configs_received; +extern const uint8_t PEER_SERVER[]; +extern const uint8_t PEER_PUMP[]; +#define SENSOR_CONFIG_COUNT 2 +extern RTC_DATA_ATTR SensorConfig_s sensor_configs[]; +extern uint8_t config_request_attempts_max;