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.gzAdd main folder to ASM_client_pump
ASM_client_pump/main/CMakeLists.txt | 6 + ASM_client_pump/main/ClientPump.cpp | 234 +++++++++++++++++++++++++++++++ ASM_client_pump/main/ClientPump.h | 59 ++++++++ ASM_client_pump/main/OLD.client_pump.ino | 76 ++++++++++ ASM_client_pump/main/asm.S | 23 +++ ASM_client_pump/main/globals.cpp | 1 + ASM_client_pump/main/globals.h | 94 +++++++++++++ ASM_client_pump/main/main.cpp | 84 +++++++++++ 8 files changed, 577 insertions(+) diff --git a/ASM_client_pump/main/CMakeLists.txt b/ASM_client_pump/main/CMakeLists.txt new file mode 100644 index 0000000..a5c71d4 --- /dev/null +++ b/ASM_client_pump/main/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "main.cpp" "ClientPump.cpp" "globals.cpp" "asm.S" + INCLUDE_DIRS "." + REQUIRES esp_timer freertos esp_system esp_wifi esp_event nvs_flash + REQUIRES vfs esp_driver_uart + ) diff --git a/ASM_client_pump/main/ClientPump.cpp b/ASM_client_pump/main/ClientPump.cpp new file mode 100644 index 0000000..b1fa672 --- /dev/null +++ b/ASM_client_pump/main/ClientPump.cpp @@ -0,0 +1,234 @@ +#include "ClientPump.h" + +#include "nvs_flash.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_log.h" + +#include <esp_now.h> +#include <esp_sleep.h> +#include <sys/time.h> + + +// ---------------------------------------------------------------------------- +// Time handling +// ---------------------------------------------------------------------------- + +int64_t get_CurrentUnix() { + return (int64_t)time(nullptr); +} + +void setup_Timezone() { + setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); + tzset(); +} + +void set_LocalTime(long long unix_now) { + struct timeval tv; + tv.tv_sec = unix_now; + tv.tv_usec = 0; + + settimeofday(&tv, nullptr); +} + +// ---------------------------------------------------------------------------- +// WiFi handling +// ---------------------------------------------------------------------------- +int32_t get_WiFiChannel(const char* target_ssid) { + wifi_scan_config_t scan_config = {}; + ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true)); // blocking scan + + uint16_t ap_count = 0; + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count)); + if (ap_count == 0) return 0; + + wifi_ap_record_t *ap_list = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * ap_count); + if (!ap_list) return 0; + + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_list)); + + int32_t channel = 0; + for (int i = 0; i < ap_count; i++) { + if (strcmp((char*)ap_list[i].ssid, target_ssid) == 0) { + channel = ap_list[i].primary; + break; + } + } + + free(ap_list); + return channel; +} + +void init_WiFi() { + // Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // Initialize TCP/IP stack and default event loop + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Initialize Wi-Fi driver + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + // Scan and set channel + int32_t channel = get_WiFiChannel(WIFI_SSID); + if (channel > 0) { + esp_wifi_set_promiscuous(true); + ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE)); + esp_wifi_set_promiscuous(false); + } +} + + +// ---------------------------------------------------------------------------- +// ESP-NOW handling +// ---------------------------------------------------------------------------- + +void on_DataRecv(const esp_now_recv_info *info, const uint8_t *data, int len) { + static const char *TAG = "on_DataRecv"; + + if (!info || !data || len <= 0) return; + if (memcmp(info->src_addr, PEER_SENSOR, 6) != 0) return; + + received_starting_message = true; + + PumpConfig_s pump_config; + char msg[64]; + long long unix_now = 0; + memcpy(msg, data, len); + msg[len] = '\0'; + + ESP_LOGI(TAG, "Received from server: %s", msg); + + if (strncmp(msg, "p:", 2) == 0) { + + int n = sscanf(msg, "p:%lld:%31[^:]:%d:%d:%lld", + &unix_now, + pump_config.pump_name, + &pump_config.pumping_delay_sec, + &pump_config.pumping_duration, + &pump_config.next_message_unix); + + if (n == 5) { + for (size_t i = 0; i < PUMP_COUNT; i++) { + if (strcmp(pump_configs[i].pump_name, pump_config.pump_name) != 0) + continue; + + // --- Response to sender --- + // register peer + uint8_t primary; + wifi_second_chan_t second; + esp_wifi_get_channel(&primary, &second); + + esp_now_peer_info_t peer = {}; + memcpy(peer.peer_addr, info->src_addr, 6); + peer.channel = primary; + peer.encrypt = false; + + if (!esp_now_is_peer_exist(info->src_addr)) { + esp_err_t add_status = esp_now_add_peer(&peer); + if (add_status != ESP_OK) { + ESP_LOGE(TAG, "Failed to add peer before sending ACK, err=%d", add_status); + return; + } + } + // send acknowledgment + const char ack_msg[] = "thanks"; + esp_err_t result = esp_now_send(info->src_addr, (const uint8_t *)ack_msg, sizeof(ack_msg)); + if (result == ESP_OK) + ESP_LOGI(TAG, "Sent acknowledgment: thanks"); + else + ESP_LOGE(TAG, "Failed to send acknowledgment, err=%d", result); + + // --- Copy data from message --- + // make sure we keep old data before memcpy + pump_config.pumping_started_at_unix = pump_configs[i].pumping_started_at_unix; + pump_config.pumping_status = pump_configs[i].pumping_status; + + + memcpy(&pump_configs[i], &pump_config, sizeof(PumpConfig_s)); + next_message_unix = pump_config.next_message_unix; + + ESP_LOGI(TAG, "unix_now: %lld", unix_now); + ESP_LOGI(TAG, "pump_name: %s", pump_configs[i].pump_name); + ESP_LOGI(TAG, "pumping_delay_sec: %d", pump_configs[i].pumping_delay_sec); + ESP_LOGI(TAG, "pumping_duration: %d", pump_configs[i].pumping_duration); + ESP_LOGI(TAG, "next_message_unix: %lld", pump_configs[i].next_message_unix); + + if (pump_configs[i].pumping_duration > 0) + pump_configs[i].pumping_status = START; + else + received_starting_message = false; + + // --- Update local time --- + set_LocalTime(unix_now); + + return; + } + } else { + ESP_LOGE(TAG, "Failed to parse message"); + } + } + received_starting_message = false; +} + + +void init_EspNow() { + if (esp_now_init() != ESP_OK) { + ESP_LOGE("init_EspNow", "ESP NOW init failed"); + while (true); + } + esp_now_register_recv_cb(on_DataRecv); +} + + +// ---------------------------------------------------------------------------- +// Deep sleep scheduler +// ---------------------------------------------------------------------------- + +void sleep_UntilNextSend(int64_t next_read) { + static const char *TAG = "sleep_UntilNextSend"; + + ESP_LOGI(TAG, "Next read: %lld", next_read); + + int64_t now_sec = get_CurrentUnix(); + + int64_t delta_sec = next_read - now_sec; + if (delta_sec <= 0) delta_sec = 0; + + uint32_t min_delay_ms = delta_sec > UINT32_MAX / 1000 + ? UINT32_MAX + : (uint32_t)(delta_sec * 1000); + + + // Prevent sleeping if next send is within g_min_sleep_time + if (min_delay_ms < g_min_sleep_time) { + ESP_LOGI(TAG, "Delaying for %u ms (no deep sleep)", min_delay_ms); + if (!received_starting_message) + vTaskDelay(pdMS_TO_TICKS(min_delay_ms)); + return; + } + + uint32_t prep_time_ms = g_setup_duration + g_wakeup_earlier_pump; + uint32_t sleep_time_ms = (min_delay_ms > prep_time_ms) + ? (min_delay_ms - prep_time_ms) + : 0; + + + esp_sleep_enable_timer_wakeup((uint64_t)sleep_time_ms * 1000ULL); + if (!received_starting_message) { + ESP_LOGI(TAG, "Sleeping for %u ms (setup margin: %u ms)", + sleep_time_ms, prep_time_ms); + esp_deep_sleep_start(); + } +} + diff --git a/ASM_client_pump/main/ClientPump.h b/ASM_client_pump/main/ClientPump.h new file mode 100644 index 0000000..2362b6d --- /dev/null +++ b/ASM_client_pump/main/ClientPump.h @@ -0,0 +1,59 @@ +#ifndef CLIENTPUMP_H +#define CLIENTPUMP_H + + +#include "globals.h" +#include "secrets.h" + + +extern long long next_message_unix; +extern bool received_starting_message; + +enum PumpSatatus_e { + INACTIVE, + START, + RUNNING, + FINISHED +}; + +struct PumpConfig_s { + char pump_name[32]; + int pumping_delay_sec; + int pumping_duration; + long long next_message_unix; + long long pumping_started_at_unix; + PumpSatatus_e pumping_status; +}; + +#define PUMP_COUNT 2 +extern PumpConfig_s pump_configs[PUMP_COUNT]; + +// ---------------------------------------------------------------------------- +// Time handling +// ---------------------------------------------------------------------------- + +int64_t get_CurrentUnix(); +void setup_Timezone(); + +// ---------------------------------------------------------------------------- +// WiFi handling +// ---------------------------------------------------------------------------- + +void init_WiFi(); + + +// ---------------------------------------------------------------------------- +// ESP-NOW handling +// ---------------------------------------------------------------------------- + +void init_EspNow(); + + +// ---------------------------------------------------------------------------- +// Deep sleep scheduler +// ---------------------------------------------------------------------------- + +void sleep_UntilNextSend(int64_t next_read); + + +#endif diff --git a/ASM_client_pump/main/OLD.client_pump.ino b/ASM_client_pump/main/OLD.client_pump.ino new file mode 100644 index 0000000..2b8f923 --- /dev/null +++ b/ASM_client_pump/main/OLD.client_pump.ino @@ -0,0 +1,76 @@ +#include "ClientPump.h" + +#include "esp_system.h" +extern "C" esp_err_t esp_base_mac_addr_set(const uint8_t mac[6]); + + + + +long long next_message_unix = 0; +bool received_starting_message = false; + +PumpConfig_s pump_configs[PUMP_COUNT] = { + {"pump_1", 0, 0, 0, 0, INACTIVE}, + {"pump_2", 0, 0, 0, 0, INACTIVE}, +}; + + + +void start_Pump(PumpConfig_s& pump_data) { + DBG_PRINTF("Starting Pump %s\n", pump_data.pump_name); + pump_data.pumping_started_at_unix = get_CurrentUnix(); + pump_data.pumping_status = RUNNING; + // whatever we have to do to make the pump running +} + +void stop_Pump(PumpConfig_s& pump_data) { + DBG_PRINTF("Stopped Pump %s\n", pump_data.pump_name); + pump_data.pumping_status = INACTIVE; + // whatever we have to do to stop the pump +} + +void setup() { + uint32_t setup_start = millis(); + + DBG_BEGIN(); + + esp_base_mac_addr_set(PEER_PUMP); + + setup_Timezone(); + init_WiFi(); + init_EspNow(); + + next_message_unix = 0; + received_starting_message = false; + + g_setup_duration = millis() - setup_start; + DBG_PRINTF("Setup duration: %u ms\n", g_setup_duration); +} + + +void loop() { + bool pumping = false; + for (size_t i = 0; i < PUMP_COUNT; i++) { + PumpConfig_s& cfg = pump_configs[i]; + + if (START == cfg.pumping_status) { + + pumping = true; + received_starting_message = false; + start_Pump(cfg); + + } else + if (RUNNING == cfg.pumping_status) { + + long long stop_time = cfg.pumping_started_at_unix + cfg.pumping_duration; + long long now = get_CurrentUnix(); + // maybe we should add a safety here so we dont pump longer than n seconds,... + if (stop_time <= now) + stop_Pump(cfg); + else pumping = true; + + } + } + if (!pumping && next_message_unix != 0 && !received_starting_message) + sleep_UntilNextSend(next_message_unix); +} diff --git a/ASM_client_pump/main/asm.S b/ASM_client_pump/main/asm.S new file mode 100644 index 0000000..f87847b --- /dev/null +++ b/ASM_client_pump/main/asm.S @@ -0,0 +1,23 @@ +.section .text +.global set_gpio_output_level +.equ REG_GPIO_BASE, 0x60004000 + +set_gpio_output_level: + li t2,REG_GPIO_BASE //Basisadresse laden + li t1, 1 + sll t1,t1, a0 //Bitamske für GPIO a0 + + //GPIO als Output aktivieren(GPIO_ENABLE_W1TS_REG,Offset 0x20) + + sw t1,0x20(t2) + + //Pegel Setzen + beq a1,x0,gpio_low //Wenn a1==0 -> LOW + +gpio_high: + sw t1,0x8(t2) //GPIO HIGH + ret + +gpio_low: + sw t1,0xC(t2) //GPIO LOW + ret \ No newline at end of file diff --git a/ASM_client_pump/main/globals.cpp b/ASM_client_pump/main/globals.cpp new file mode 100644 index 0000000..e61d53a --- /dev/null +++ b/ASM_client_pump/main/globals.cpp @@ -0,0 +1 @@ +../../globals.cpp \ No newline at end of file diff --git a/ASM_client_pump/main/globals.h b/ASM_client_pump/main/globals.h new file mode 100644 index 0000000..894d085 --- /dev/null +++ b/ASM_client_pump/main/globals.h @@ -0,0 +1,94 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_attr.h" +#include <stdint.h> +#include <stdbool.h> + + +// Network Time +#define NTP_SERVER "pool.ntp.org" + +// Size for buffering data +#define BUFFER_SIZE 50 +#define TABLE_SIZE 12 +#define VALUE_SIZE 16 +#define JSON_PER_ENTRY (29 + (TABLE_SIZE - 1) + (VALUE_SIZE - 1) + 10) +#define JSON_BUFFER_SIZE (BUFFER_SIZE * JSON_PER_ENTRY + 2) + +#define SENSOR_CONFIG_COUNT 2 + +#define millis() (esp_timer_get_time() / 1000ULL) + +// Global variables +extern uint32_t g_setup_duration; +extern uint32_t g_wakeup_earlier_server; +extern uint32_t g_wakeup_earlier_sensor; +extern uint32_t g_wakeup_earlier_pump; + +// Structs +struct SensorConfigServer_s { + char sensor_name[32]; + bool success; + bool is_active; + int pin; + int min_moisture; + char notification_name[32]; + int pumping_delay_sec; + int pumping_duration; + int delay_ms; + bool use_time; + uint16_t times[8]; + uint8_t time_count; +}; + +struct SensorConfig_s { + char sensor_name[32]; + bool is_active; + int pin; + int min_moisture; + char notification_name[32]; + int pumping_delay_sec; + int pumping_duration; + uint8_t pump_mac[6]; + int delay_ms; + bool use_time; + uint16_t times[8]; + uint8_t time_count; + int64_t last_sensor_read; + int64_t next_sensor_read; +}; + +struct PumpInformation_s { + char notifiaction_name[32]; + int pumping_delay_sec; + int pumping_duration; + int64_t next_message; +}; + +struct SensorEntry_s { + char table[TABLE_SIZE]; + char value[VALUE_SIZE]; + int64_t time; + bool pump_reached; +}; + +// Extern variables +extern SensorEntry_s g_buffer[BUFFER_SIZE]; +extern uint8_t g_buffer_count; +#define MAX_PENDING_SENSOR_CONFIGS 4 +extern char g_pending_sensor_configs[MAX_PENDING_SENSOR_CONFIGS][32]; +extern uint8_t g_pending_sensor_configs_count; +extern bool g_received_ok_from_server; + +extern const uint32_t g_min_sleep_time; +extern bool all_init_configs_received; +extern const uint8_t PEER_SERVER[]; +extern const uint8_t PEER_SENSOR[]; +extern const uint8_t PEER_PUMP[]; +extern RTC_DATA_ATTR SensorConfig_s sensor_configs[]; +extern uint8_t config_request_attempts_max; + +#endif diff --git a/ASM_client_pump/main/main.cpp b/ASM_client_pump/main/main.cpp new file mode 100644 index 0000000..9bd3a44 --- /dev/null +++ b/ASM_client_pump/main/main.cpp @@ -0,0 +1,84 @@ +#include "ClientPump.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +extern void set_gpio_output_levl(int gpio_num,int level); +extern "C" esp_err_t esp_base_mac_addr_set(const uint8_t mac[6]); + +long long next_message_unix = 0; +bool received_starting_message = false; + +PumpConfig_s pump_configs[PUMP_COUNT] = { + {"pump_1", 0, 0, 0, 0, INACTIVE}, + {"pump_2", 0, 0, 0, 0, INACTIVE}, +}; + +void start_Pump(PumpConfig_s& pump_data) { + ESP_LOGI("start_Pump", "Starting Pump %s", pump_data.pump_name); + pump_data.pumping_started_at_unix = get_CurrentUnix(); + pump_data.pumping_status = RUNNING; + set_gpio_output_level(8,1); + set_gpio_output_level(10,1); +} + +void stop_Pump(PumpConfig_s& pump_data) { + ESP_LOGI("stop_Pump", "Stopped Pump %s", pump_data.pump_name); + pump_data.pumping_status = INACTIVE; + set_gpio_output_level(10,0); + set_gpio_output_level(8,0); +} + +void pump_loop(void*) { + while (true) { + bool pumping = false; + + for (size_t i = 0; i < PUMP_COUNT; i++) { + PumpConfig_s& cfg = pump_configs[i]; + + if (START == cfg.pumping_status) { + pumping = true; + received_starting_message = false; + start_Pump(cfg); + } + else if (RUNNING == cfg.pumping_status) { + long long stop_time = cfg.pumping_started_at_unix + cfg.pumping_duration; + long long now = get_CurrentUnix(); + + if (stop_time <= now) + stop_Pump(cfg); + else + pumping = true; + } + } + + if (!pumping && next_message_unix != 0 && !received_starting_message) + sleep_UntilNextSend(next_message_unix); + + // give other tasks CPU time + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +extern "C" void app_main(void) { + static const char *TAG = "app_main"; + + uint32_t setup_start = esp_timer_get_time() / 1000ULL; + + ESP_LOGI(TAG, "Initializing ClientPump..."); + + esp_base_mac_addr_set(PEER_PUMP); + + setup_Timezone(); + init_WiFi(); + init_EspNow(); + + next_message_unix = 0; + received_starting_message = false; + + uint32_t setup_duration = (esp_timer_get_time() / 1000ULL) - setup_start; + ESP_LOGI(TAG, "Setup duration: %u ms", setup_duration); + + xTaskCreate(pump_loop, "pump_loop", 4096, nullptr, 5, nullptr); +}