#include #include #include #include "esp_sleep.h" #include "driver/gpio.h" #include "driver/rtc_io.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "ulp_lp_core.h" #include "ulp_main.h" #include "lp_core_uart.h" #include "esp_wifi.h" #include "esp_http_client.h" #include "nvs_flash.h" #include "esp_log.h" #include "led_strip.h" #include "nvs.h" #define WIFI_SSID CONFIG_WIFI_SSID #define WIFI_PASSWORD CONFIG_WIFI_PASSWORD #define METRICS_SERVER_URL CONFIG_METRICS_SERVER_URL #define WIFI_CONNECT_TIMEOUT_S 10 #define LED_GPIO 8 #define LED_BRIGHTNESS 2 // Very low intensity (0-255) #define NVS_NAMESPACE "storage" #define NVS_KEY_TOTAL_COUNT "total_count" static led_strip_handle_t led_strip; extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start"); extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end"); static void init_ulp_program(void); static bool connectToWifi(void); static bool sendApiRequest(uint32_t count); static void sendMetrics(uint32_t count); static void init_led(void); static void flash_led_green(void); static void flash_led_blue(void); static void flash_led_red(void); static void flash_led_yellow(void); static uint32_t nvs_read_total_count(void); static void nvs_write_total_count(uint32_t count); // ── WiFi ────────────────────────────────────────────────────────────────────── static bool connectToWifi(void) { // Suppress WiFi component logs esp_log_level_set("wifi", ESP_LOG_ERROR); esp_log_level_set("wifi_init", ESP_LOG_ERROR); printf("Connecting to WiFi"); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASSWORD, }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_connect()); int attempts = 0; wifi_ap_record_t ap_info; while (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK && attempts++ < WIFI_CONNECT_TIMEOUT_S) { printf("."); fflush(stdout); flash_led_blue(); vTaskDelay(pdMS_TO_TICKS(1000)); } if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) { printf(" FAILED!\n"); flash_led_red(); return false; } printf(" OK!\n"); return true; } // ── HTTP ────────────────────────────────────────────────────────────────────── static bool sendApiRequest(uint32_t count) { printf("Sending API request..."); char post_data[64]; snprintf(post_data, sizeof(post_data), "instance=a&pulses=%"PRIu32, count); esp_http_client_config_t config = { .url = METRICS_SERVER_URL, .method = HTTP_METHOD_POST, .timeout_ms = 3000, }; esp_http_client_handle_t client = esp_http_client_init(&config); esp_http_client_set_header(client, "Content-Type", "application/x-www-form-urlencoded"); esp_err_t err = esp_http_client_open(client, strlen(post_data)); if (err != ESP_OK) { printf(" FAIL - Failed to open connection: %d\n", err); esp_http_client_cleanup(client); return false; } int write_len = esp_http_client_write(client, post_data, strlen(post_data)); if (write_len < 0) { printf(" FAIL - Write failed\n"); esp_http_client_cleanup(client); return false; } // Fetch headers to get status code int content_length = esp_http_client_fetch_headers(client); int status_code = esp_http_client_get_status_code(client); // Read and discard response body if (content_length > 0) { char buffer[64]; int read_len; while ((read_len = esp_http_client_read(client, buffer, sizeof(buffer))) > 0) { // Discard response data } } esp_http_client_close(client); esp_http_client_cleanup(client); if (status_code == 200) { printf(" OK\n"); return true; } printf(" FAIL - code: %d\n", status_code); return false; } // ── Send ────────────────────────────────────────────────────────────────────── static void sendMetrics(uint32_t count) { if (connectToWifi()) { if (sendApiRequest(count)) { flash_led_green(); } else { flash_led_red(); } } ESP_ERROR_CHECK(esp_wifi_stop()); ESP_ERROR_CHECK(esp_wifi_deinit()); } // ── LED ─────────────────────────────────────────────────────────────────────── static void init_led(void) { led_strip_config_t strip_config = { .strip_gpio_num = LED_GPIO, .max_leds = 1, .led_pixel_format = LED_PIXEL_FORMAT_GRB, .led_model = LED_MODEL_WS2812, .flags.invert_out = false, }; led_strip_rmt_config_t rmt_config = { .clk_src = RMT_CLK_SRC_DEFAULT, .resolution_hz = 10 * 1000 * 1000, // 10MHz .flags.with_dma = false, }; ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); led_strip_clear(led_strip); } static void flash_led_green(void) { // Flash green once at low intensity // GRB format: Green, Red, Blue led_strip_set_pixel(led_strip, 0, LED_BRIGHTNESS, 0, 0); // Green at low brightness led_strip_refresh(led_strip); vTaskDelay(pdMS_TO_TICKS(200)); led_strip_clear(led_strip); } static void flash_led_blue(void) { // Flash blue once at low intensity led_strip_set_pixel(led_strip, 0, 0, 0, LED_BRIGHTNESS); // Blue at low brightness led_strip_refresh(led_strip); vTaskDelay(pdMS_TO_TICKS(200)); led_strip_clear(led_strip); } static void flash_led_red(void) { // Flash red once at low intensity led_strip_set_pixel(led_strip, 0, 0, LED_BRIGHTNESS, 0); // Red at low brightness led_strip_refresh(led_strip); vTaskDelay(pdMS_TO_TICKS(200)); led_strip_clear(led_strip); } static void flash_led_yellow(void) { // Flash yellow once at low intensity // GRB format: Green + Red = Yellow led_strip_set_pixel(led_strip, 0, LED_BRIGHTNESS, LED_BRIGHTNESS, 0); // Yellow at low brightness led_strip_refresh(led_strip); vTaskDelay(pdMS_TO_TICKS(200)); led_strip_clear(led_strip); } // ── NVS ─────────────────────────────────────────────────────────────────────── static uint32_t nvs_read_total_count(void) { nvs_handle_t nvs_handle; esp_err_t err; err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); if (err != ESP_OK) { printf("NVS namespace not found, starting count from 0\n"); return 0; } uint32_t count = 0; err = nvs_get_u32(nvs_handle, NVS_KEY_TOTAL_COUNT, &count); nvs_close(nvs_handle); if (err != ESP_OK) { printf("Total count not found in NVS, starting from 0\n"); return 0; } printf("Read total count from NVS: %"PRIu32"\n", count); return count; } static void nvs_write_total_count(uint32_t count) { nvs_handle_t nvs_handle; esp_err_t err; err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); if (err != ESP_OK) { printf("Error opening NVS for writing: %d\n", err); return; } err = nvs_set_u32(nvs_handle, NVS_KEY_TOTAL_COUNT, count); if (err != ESP_OK) { printf("Error writing total count to NVS: %d\n", err); nvs_close(nvs_handle); return; } err = nvs_commit(nvs_handle); if (err != ESP_OK) { printf("Error committing NVS: %d\n", err); } else { printf("Saved total count to NVS: %"PRIu32"\n", count); } nvs_close(nvs_handle); } void app_main(void) { /* If user is using USB-serial-jtag then idf monitor needs some time to * re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection * before we print anything. Otherwise the chip will go back to sleep again before the user * has time to monitor any output. */ vTaskDelay(pdMS_TO_TICKS(1000)); /* Initialize and flash LED yellow to indicate boot */ init_led(); flash_led_yellow(); /* Initialize NVS for WiFi */ 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 network interface */ ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* Only initialize GPIO on first boot, not after ULP wakeup */ if (!(esp_sleep_get_wakeup_causes() & BIT(ESP_SLEEP_WAKEUP_ULP))) { rtc_gpio_init(CONFIG_PULSE_COUNT_PIN); rtc_gpio_set_direction(CONFIG_PULSE_COUNT_PIN, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_dis(CONFIG_PULSE_COUNT_PIN); rtc_gpio_pullup_en(CONFIG_PULSE_COUNT_PIN); // Enable pull-up for reed switch } printf("ULP will wake up processor after every %d pulses\n", CONFIG_PULSE_COUNT_WAKEUP_LIMIT); uint64_t wakeup_causes = esp_sleep_get_wakeup_causes(); if (wakeup_causes & BIT(ESP_SLEEP_WAKEUP_ULP)) { printf("ULP woke up the main CPU!\n"); printf("Count: %"PRIu32"\n", ulp_pulse_count); /* Save count to NVS and send to API */ nvs_write_total_count(ulp_pulse_count); sendMetrics(ulp_pulse_count); } else if (wakeup_causes & BIT(ESP_SLEEP_WAKEUP_TIMER)) { printf("Timer woke up the main CPU!\n"); printf("Current count: %"PRIu32"\n", ulp_pulse_count); /* Send current count without resetting */ sendMetrics(ulp_pulse_count); } else { printf("Not a ULP/timer wakeup, initializing ULP program\n"); init_ulp_program(); /* Set ULP count to value in the NVS after loading binary (binary load resets it to 0) */ uint32_t saved_count = nvs_read_total_count(); printf("Restored count from NVS: %"PRIu32"\n", saved_count); ulp_pulse_count = saved_count; } printf("Zzzz\n"); vTaskDelay(pdMS_TO_TICKS(100)); ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup()); /* Wake every 15 minutes to send current count */ ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(15 * 60 * 1000000ULL)); // 15 minutes in microseconds esp_deep_sleep_start(); } static void init_ulp_program(void) { lp_core_uart_cfg_t uart_cfg = LP_CORE_UART_DEFAULT_CONFIG(); ESP_ERROR_CHECK(lp_core_uart_init(&uart_cfg)); esp_err_t err = ulp_lp_core_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start)); ESP_ERROR_CHECK(err); /* Start the program */ ulp_lp_core_cfg_t cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, .lp_timer_sleep_duration_us = 100000, // Wake every 100ms to keep LP core active }; err = ulp_lp_core_run(&cfg); ESP_ERROR_CHECK(err); /* Give ULP time to start */ vTaskDelay(pdMS_TO_TICKS(1000)); }