feat: implement ESP32 power outage monitor with MQTT and OTA support
- WiFi manager with captive portal for configuration - MQTTS client with TLS support using EMQX Cloud - NVS storage for persistent configuration - Power monitoring with 30-second heartbeat messages - OTA firmware updates via MQTT - NTP time synchronization (Kenya timezone EAT-3) - Automatic reconnection handling - Rollback protection for OTA updates Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
This commit is contained in:
@@ -3,3 +3,6 @@
|
||||
|
||||
# ESP-IDF default build directory name
|
||||
build/
|
||||
|
||||
# ESP-IDF default directory for project dependencies
|
||||
managed_components/
|
||||
|
||||
+1
-5
@@ -1,8 +1,4 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(hello_world)
|
||||
project(power_outage_monitor)
|
||||
|
||||
@@ -1,58 +1,115 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- |
|
||||
# ESP32 Power Outage Monitor
|
||||
|
||||
# Hello World Example
|
||||
An ESP-IDF based IoT application for ESP32 that monitors power outages by sending periodic heartbeat messages to an MQTT broker.
|
||||
|
||||
Starts a FreeRTOS task to print "Hello World".
|
||||
## Hardware Requirements
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
- ESP32-C3 (or compatible ESP32 variant)
|
||||
- USB cable for power and programming
|
||||
- Internet connectivity via Wi-Fi
|
||||
|
||||
## How to use example
|
||||
## Architecture
|
||||
|
||||
Follow detailed instructions provided specifically for this example.
|
||||
### Data Model
|
||||
|
||||
Select the instructions depending on Espressif chip installed on your development board:
|
||||
Messages published to MQTT follow this JSON schema:
|
||||
|
||||
- [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html)
|
||||
- [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html)
|
||||
|
||||
## Example folder contents
|
||||
|
||||
The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main).
|
||||
|
||||
ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both).
|
||||
|
||||
Below is short explanation of remaining files in the project folder.
|
||||
|
||||
```
|
||||
├── CMakeLists.txt
|
||||
├── pytest_hello_world.py Python script used for automated testing
|
||||
├── main
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── hello_world_main.c
|
||||
└── README.md This is the file you are currently reading
|
||||
```json
|
||||
{
|
||||
"device_id": "ESP32-AABBCCDDEEFF",
|
||||
"power_status": true,
|
||||
"latitude": 40.7128,
|
||||
"longitude": -74.006,
|
||||
"timestamp": "2025-12-17T10:30:45Z",
|
||||
"metadata": {
|
||||
"firmware_version": "1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide.
|
||||
## Building and Flashing
|
||||
|
||||
## Troubleshooting
|
||||
### Prerequisites
|
||||
|
||||
- Program upload failure
|
||||
- Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
|
||||
- The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
|
||||
1. **Install ESP-IDF v5.5.1 or later**:
|
||||
|
||||
## Technical support and feedback
|
||||
Follow the official guide: <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/>
|
||||
|
||||
Please use the following feedback channels:
|
||||
2. **Set up ESP-IDF environment** (required before every build):
|
||||
|
||||
- For technical queries, go to the [esp32.com](https://esp32.com/) forum
|
||||
- For a feature request or bug report, create a [GitHub issue](https://github.com/espressif/esp-idf/issues)
|
||||
```bash
|
||||
. $HOME/esp/esp-idf/export.sh
|
||||
```
|
||||
|
||||
We will get back to you as soon as possible.
|
||||
|
||||
## Getting Started
|
||||
### Quick Build (Recommended)
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32
|
||||
idf.py menuconfig
|
||||
idf.py build
|
||||
```
|
||||
|
||||
### Manual Build
|
||||
|
||||
If you prefer manual steps:
|
||||
|
||||
1. **Set target**:
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c3
|
||||
```
|
||||
|
||||
2. **Build**:
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
The `sdkconfig.defaults` file automatically configures:
|
||||
- Custom partition table (partitions.csv)
|
||||
- 2MB flash size
|
||||
- OTA rollback support
|
||||
- Optimized settings
|
||||
|
||||
### Flash
|
||||
|
||||
```bash
|
||||
idf.py -p /dev/ttyUSB0 flash monitor
|
||||
```
|
||||
|
||||
Replace `/dev/ttyUSB0` with your serial port:
|
||||
|
||||
- Linux: `/dev/ttyUSB0` or `/dev/ttyACM0`
|
||||
- macOS: `/dev/cu.usbserial-*`
|
||||
- Windows: `COM3`, `COM4`, etc.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### First Boot
|
||||
|
||||
When the device boots for the first time (no Wi-Fi credentials stored):
|
||||
|
||||
1. The device starts in **Access Point mode**
|
||||
2. Connect to Wi-Fi network: `PowerMonitor-Setup`
|
||||
3. Password: `12345678`
|
||||
4. Open browser and navigate to: `http://192.168.4.1`
|
||||
5. Fill in the configuration form:
|
||||
- **Wi-Fi SSID**: Your Wi-Fi network name
|
||||
- **Wi-Fi Password**: Your Wi-Fi password
|
||||
- **MQTT Broker URL**: e.g., `mqtt://broker.hivemq.com`
|
||||
- **MQTT Port**: Default `1883`
|
||||
- **MQTT Topic**: e.g., `power/monitor`
|
||||
- **MQTT Username/Password**: Optional
|
||||
- **Device ID**: Unique identifier for this device
|
||||
- **Latitude/Longitude**: GPS coordinates
|
||||
6. Click **Save and Restart**
|
||||
|
||||
The device will restart and connect to your Wi-Fi network.
|
||||
|
||||
### Subsequent Boots
|
||||
|
||||
On every boot after initial configuration:
|
||||
|
||||
1. Device loads credentials from NVS
|
||||
2. Connects to Wi-Fi network
|
||||
3. Connects to MQTT broker
|
||||
4. Sends "power restored" event
|
||||
5. Starts sending periodic heartbeats (every 30 seconds)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
dependencies:
|
||||
espressif/mqtt:
|
||||
component_hash: ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.0
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.1
|
||||
direct_dependencies:
|
||||
- espressif/mqtt
|
||||
- idf
|
||||
manifest_hash: d11c86bf1e65d8286be5092ea284c0f288cf083564f6cbe51df18abb3bc2cf71
|
||||
target: esp32c3
|
||||
version: 2.0.0
|
||||
+16
-3
@@ -1,3 +1,16 @@
|
||||
idf_component_register(SRCS "hello_world_main.c"
|
||||
PRIV_REQUIRES spi_flash
|
||||
INCLUDE_DIRS "")
|
||||
idf_component_register(SRCS "power_outage_main.c"
|
||||
"nvs_storage.c"
|
||||
"wifi_manager.c"
|
||||
"mqtt_client.c"
|
||||
"power_monitor.c"
|
||||
"ota_manager.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES nvs_flash
|
||||
esp_wifi
|
||||
esp_netif
|
||||
esp_http_server
|
||||
esp_event
|
||||
mqtt
|
||||
json
|
||||
app_update
|
||||
esp_https_ota)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define FIRMWARE_VERSION "1.2.2"
|
||||
|
||||
#define WIFI_AP_SSID "PowerMonitor-Setup"
|
||||
#define WIFI_AP_PASSWORD "12345678"
|
||||
#define WIFI_AP_CHANNEL 1
|
||||
#define WIFI_AP_MAX_CONNECTIONS 4
|
||||
#define WIFI_RECONNECT_DELAY_MS 5000
|
||||
|
||||
#define MQTT_DEFAULT_BROKER "mqtts://qdadf0a9.ala.eu-central-1.emqxsl.com"
|
||||
#define MQTT_DEFAULT_PORT 8883
|
||||
#define MQTT_DEFAULT_TOPIC "tujuepawa/power/monitor"
|
||||
#define MQTT_USERNAME "clad-citric-criteria"
|
||||
#define MQTT_PASSWORD "uhLFkhbj4ge5wrnQQ2MA"
|
||||
#define MQTT_QOS 2
|
||||
#define MQTT_RETAIN 1
|
||||
#define MQTT_USE_TLS 1
|
||||
|
||||
#define HEARTBEAT_INTERVAL_MS 10000
|
||||
#define POWER_CHECK_INTERVAL_MS 1000
|
||||
|
||||
#define TIMEZONE "EAT-3"
|
||||
|
||||
#define DEFAULT_LATITUDE 0.0
|
||||
#define DEFAULT_LONGITUDE 0.0
|
||||
|
||||
#define NVS_NAMESPACE "power_monitor"
|
||||
#define NVS_WIFI_SSID "wifi_ssid"
|
||||
#define NVS_WIFI_PASSWORD "wifi_pass"
|
||||
#define NVS_MQTT_BROKER "mqtt_broker"
|
||||
#define NVS_MQTT_PORT "mqtt_port"
|
||||
#define NVS_MQTT_TOPIC "mqtt_topic"
|
||||
#define NVS_MQTT_USERNAME "mqtt_user"
|
||||
#define NVS_MQTT_PASSWORD "mqtt_pass"
|
||||
#define NVS_DEVICE_ID "device_id"
|
||||
#define NVS_LATITUDE "latitude"
|
||||
#define NVS_LONGITUDE "longitude"
|
||||
|
||||
#define OTA_RECV_TIMEOUT_MS 5000
|
||||
#define OTA_TOPIC "power/monitor/ota"
|
||||
|
||||
#define MAX_SSID_LEN 32
|
||||
#define MAX_PASSWORD_LEN 64
|
||||
#define MAX_BROKER_LEN 128
|
||||
#define MAX_TOPIC_LEN 64
|
||||
#define MAX_DEVICE_ID_LEN 32
|
||||
#define MAX_JSON_LEN 512
|
||||
|
||||
#define HTTP_SERVER_PORT 80
|
||||
#define HTTP_MAX_URI_LEN 256
|
||||
#define HTTP_MAX_RESP_LEN 8192
|
||||
|
||||
#endif // CONFIG_H
|
||||
+38
-36
@@ -4,49 +4,51 @@
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
printf("Hello world!\n");
|
||||
void app_main(void) {
|
||||
printf("Hello world!\n");
|
||||
|
||||
/* Print chip information */
|
||||
esp_chip_info_t chip_info;
|
||||
uint32_t flash_size;
|
||||
esp_chip_info(&chip_info);
|
||||
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
|
||||
CONFIG_IDF_TARGET,
|
||||
chip_info.cores,
|
||||
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
|
||||
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
|
||||
/* Print chip information */
|
||||
esp_chip_info_t chip_info;
|
||||
uint32_t flash_size;
|
||||
esp_chip_info(&chip_info);
|
||||
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ", CONFIG_IDF_TARGET,
|
||||
chip_info.cores,
|
||||
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
|
||||
(chip_info.features & CHIP_FEATURE_IEEE802154)
|
||||
? ", 802.15.4 (Zigbee/Thread)"
|
||||
: "");
|
||||
|
||||
unsigned major_rev = chip_info.revision / 100;
|
||||
unsigned minor_rev = chip_info.revision % 100;
|
||||
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
|
||||
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||
printf("Get flash size failed");
|
||||
return;
|
||||
}
|
||||
unsigned major_rev = chip_info.revision / 100;
|
||||
unsigned minor_rev = chip_info.revision % 100;
|
||||
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
|
||||
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||
printf("Get flash size failed");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
|
||||
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
|
||||
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
|
||||
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded"
|
||||
: "external");
|
||||
|
||||
printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
|
||||
printf("Minimum free heap size: %" PRIu32 " bytes\n",
|
||||
esp_get_minimum_free_heap_size());
|
||||
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
printf("Restarting in %d seconds...\n", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
printf("Restarting now.\n");
|
||||
fflush(stdout);
|
||||
esp_restart();
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
printf("Restarting in %d seconds...\n", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
printf("Restarting now.\n");
|
||||
fflush(stdout);
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: ">=4.1.0"
|
||||
# # Put list of dependencies here
|
||||
# # For components maintained by Espressif:
|
||||
# component: "~1.0.0"
|
||||
# # For 3rd party components:
|
||||
# username/component: ">=1.0.0,<2.0.0"
|
||||
# username2/component2:
|
||||
# version: "~1.0.0"
|
||||
# # For transient dependencies `public` flag can be set.
|
||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||
# # All dependencies of `main` are public by default.
|
||||
# public: true
|
||||
espressif/mqtt: "*"
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* MQTT TLS/SSL Certificates
|
||||
* EMQX Cloud CA Certificate (DigiCert Global Root G2)
|
||||
*/
|
||||
|
||||
#ifndef MQTT_CERTS_H
|
||||
#define MQTT_CERTS_H
|
||||
|
||||
/* DigiCert Global Root G2 - Used by EMQX Cloud */
|
||||
static const char *mqtt_server_ca_cert =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
|
||||
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
|
||||
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
|
||||
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
|
||||
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
|
||||
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
|
||||
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
|
||||
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
|
||||
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
|
||||
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
|
||||
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
|
||||
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
|
||||
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
|
||||
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
|
||||
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
|
||||
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
|
||||
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
|
||||
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
|
||||
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
|
||||
"MrY=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
#endif // MQTT_CERTS_H
|
||||
@@ -0,0 +1,217 @@
|
||||
#include "mqtt_client.h"
|
||||
#include "cJSON.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mqtt_certs.h"
|
||||
#include "nvs_storage.h"
|
||||
#include "ota_manager.h"
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
static const char *TAG = "MQTT_CLIENT";
|
||||
|
||||
static esp_mqtt_client_handle_t client = NULL;
|
||||
static bool is_connected = false;
|
||||
static char mqtt_topic[MAX_TOPIC_LEN] = {0};
|
||||
|
||||
static void ota_update_task(void *pvParameter) {
|
||||
char *url = (char *)pvParameter;
|
||||
ESP_LOGI(TAG, "Starting OTA update from: %s", url);
|
||||
|
||||
esp_err_t ret = ota_start_update(url);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "OTA update failed");
|
||||
}
|
||||
|
||||
free(url);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
||||
int32_t event_id, void *event_data) {
|
||||
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
|
||||
|
||||
switch ((esp_mqtt_event_id_t)event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT connected");
|
||||
is_connected = true;
|
||||
|
||||
char device_id[MAX_DEVICE_ID_LEN] = {0};
|
||||
if (nvs_load_device_id(device_id, sizeof(device_id)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to load device ID, cannot subscribe to topics");
|
||||
break;
|
||||
}
|
||||
|
||||
char topic[128];
|
||||
snprintf(topic, sizeof(topic), "power/monitor/%s/#", device_id);
|
||||
esp_mqtt_client_subscribe(client, topic, 0);
|
||||
|
||||
char ota_topic[128];
|
||||
snprintf(ota_topic, sizeof(ota_topic), "power/monitor/%s/ota", device_id);
|
||||
esp_mqtt_client_subscribe(client, ota_topic, 1);
|
||||
ESP_LOGI(TAG, "Subscribed to OTA updates: %s", ota_topic);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "MQTT disconnected");
|
||||
is_connected = false;
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGE(TAG, "MQTT error");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "MQTT message received");
|
||||
ESP_LOGI(TAG, "TOPIC=%.*s", event->topic_len, event->topic);
|
||||
ESP_LOGI(TAG, "DATA=%.*s", event->data_len, event->data);
|
||||
|
||||
// Create null-terminated topic buffer for safe string comparison
|
||||
if (event->topic_len > 0 && event->topic_len < 128) {
|
||||
char topic_buf[128];
|
||||
memcpy(topic_buf, event->topic, event->topic_len);
|
||||
topic_buf[event->topic_len] = '\0';
|
||||
|
||||
if (strstr(topic_buf, "/ota") != NULL) {
|
||||
char url[256] = {0};
|
||||
int len = event->data_len < 255 ? event->data_len : 255;
|
||||
memcpy(url, event->data, len);
|
||||
url[len] = '\0';
|
||||
|
||||
ESP_LOGI(TAG, "OTA update requested: %s", url);
|
||||
|
||||
char *url_copy = strdup(url);
|
||||
if (url_copy != NULL) {
|
||||
BaseType_t task_result = xTaskCreate(ota_update_task, "ota_update",
|
||||
8192, url_copy, 5, NULL);
|
||||
if (task_result != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create OTA task");
|
||||
free(url_copy);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for OTA URL");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t mqtt_client_init(void) {
|
||||
char broker[MAX_BROKER_LEN] = {0};
|
||||
uint16_t port = MQTT_DEFAULT_PORT;
|
||||
|
||||
// Use hardcoded broker, port, topic, and credentials from config.h
|
||||
strcpy(broker, MQTT_DEFAULT_BROKER);
|
||||
port = MQTT_DEFAULT_PORT;
|
||||
strcpy(mqtt_topic, MQTT_DEFAULT_TOPIC);
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to MQTT broker: %s:%d, topic: %s", broker, port,
|
||||
mqtt_topic);
|
||||
|
||||
esp_mqtt_client_config_t mqtt_cfg = {
|
||||
.broker.address.uri = broker,
|
||||
.broker.address.port = port,
|
||||
.network.disable_auto_reconnect = false,
|
||||
.credentials.set_null_client_id = false,
|
||||
};
|
||||
|
||||
if (port == 8883 || strstr(broker, "mqtts://") != NULL) {
|
||||
ESP_LOGI(TAG, "Enabling MQTTS with TLS/SSL");
|
||||
mqtt_cfg.broker.verification.certificate = mqtt_server_ca_cert;
|
||||
mqtt_cfg.broker.verification.skip_cert_common_name_check = false;
|
||||
}
|
||||
|
||||
// Use hardcoded credentials from config.h
|
||||
mqtt_cfg.credentials.username = MQTT_USERNAME;
|
||||
mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;
|
||||
ESP_LOGI(TAG, "Using MQTT authentication with username: %s", MQTT_USERNAME);
|
||||
|
||||
client = esp_mqtt_client_init(&mqtt_cfg);
|
||||
if (client == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to initialize MQTT client");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler,
|
||||
NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mqtt_client_start(void) {
|
||||
if (client == NULL) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_mqtt_client_start(client);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mqtt_client_stop(void) {
|
||||
if (client == NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_mqtt_client_stop(client);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mqtt_publish_power_status(bool power_on, const char *device_id,
|
||||
float latitude, float longitude) {
|
||||
if (!is_connected) {
|
||||
ESP_LOGW(TAG, "MQTT not connected, cannot publish");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
time_t now;
|
||||
struct tm timeinfo;
|
||||
time(&now);
|
||||
|
||||
gmtime_r(&now, &timeinfo);
|
||||
|
||||
char timestamp[32];
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &timeinfo);
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "device_id", device_id);
|
||||
cJSON_AddBoolToObject(root, "power_status", power_on);
|
||||
cJSON_AddNumberToObject(root, "latitude", latitude);
|
||||
cJSON_AddNumberToObject(root, "longitude", longitude);
|
||||
cJSON_AddStringToObject(root, "timestamp", timestamp);
|
||||
|
||||
cJSON *metadata = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(metadata, "firmware_version", FIRMWARE_VERSION);
|
||||
cJSON_AddItemToObject(root, "metadata", metadata);
|
||||
|
||||
char *json_str = cJSON_PrintUnformatted(root);
|
||||
|
||||
if (json_str == NULL) {
|
||||
cJSON_Delete(root);
|
||||
ESP_LOGE(TAG, "Failed to create JSON");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Publishing: %s", json_str);
|
||||
|
||||
int msg_id = esp_mqtt_client_publish(client, mqtt_topic, json_str,
|
||||
strlen(json_str), MQTT_QOS, MQTT_RETAIN);
|
||||
|
||||
cJSON_free(json_str);
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (msg_id < 0) {
|
||||
ESP_LOGE(TAG, "Failed to publish message");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool mqtt_is_connected(void) { return is_connected; }
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef MQTT_LIB_H
|
||||
#define MQTT_LIB_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Initialize MQTT client */
|
||||
esp_err_t mqtt_client_init(void);
|
||||
|
||||
/* Start MQTT client */
|
||||
esp_err_t mqtt_client_start(void);
|
||||
|
||||
/* Stop MQTT client */
|
||||
esp_err_t mqtt_client_stop(void);
|
||||
|
||||
/* Publish power status message */
|
||||
esp_err_t mqtt_publish_power_status(bool power_on, const char *device_id,
|
||||
float latitude, float longitude);
|
||||
|
||||
/* Check if MQTT is connected */
|
||||
bool mqtt_is_connected(void);
|
||||
|
||||
#endif // MQTT_LIB_H
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "nvs_storage.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "NVS_STORAGE";
|
||||
|
||||
esp_err_t nvs_storage_init(void) {
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_LOGW(TAG, "Erasing NVS flash and reinitializing");
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_save_wifi_credentials(const char *ssid, const char *password) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_str(handle, NVS_WIFI_SSID, ssid);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save SSID: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_str(handle, NVS_WIFI_PASSWORD, password);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save password: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_wifi_credentials(char *ssid, size_t ssid_len, char *password,
|
||||
size_t pass_len) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t required_size = ssid_len;
|
||||
ret = nvs_get_str(handle, NVS_WIFI_SSID, ssid, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "SSID buffer too small, required: %zu, provided: %zu",
|
||||
required_size, ssid_len);
|
||||
}
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
required_size = pass_len;
|
||||
ret = nvs_get_str(handle, NVS_WIFI_PASSWORD, password, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "Password buffer too small, required: %zu, provided: %zu",
|
||||
required_size, pass_len);
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool nvs_has_wifi_credentials(void) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
ret = nvs_get_str(handle, NVS_WIFI_SSID, NULL, &len);
|
||||
nvs_close(handle);
|
||||
|
||||
return (ret == ESP_OK && len > 0);
|
||||
}
|
||||
|
||||
esp_err_t nvs_save_mqtt_config(const char *broker, uint16_t port,
|
||||
const char *topic, const char *username,
|
||||
const char *password) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_str(handle, NVS_MQTT_BROKER, broker);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save MQTT broker: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_u16(handle, NVS_MQTT_PORT, port);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save MQTT port: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_str(handle, NVS_MQTT_TOPIC, topic);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save MQTT topic: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (username && strlen(username) > 0) {
|
||||
ret = nvs_set_str(handle, NVS_MQTT_USERNAME, username);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save MQTT username: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (password && strlen(password) > 0) {
|
||||
ret = nvs_set_str(handle, NVS_MQTT_PASSWORD, password);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save MQTT password: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_mqtt_broker(char *broker, size_t len) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t required_size = len;
|
||||
ret = nvs_get_str(handle, NVS_MQTT_BROKER, broker, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "Broker buffer too small, required: %zu, provided: %zu",
|
||||
required_size, len);
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_mqtt_port(uint16_t *port) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_get_u16(handle, NVS_MQTT_PORT, port);
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_mqtt_topic(char *topic, size_t len) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t required_size = len;
|
||||
ret = nvs_get_str(handle, NVS_MQTT_TOPIC, topic, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "Topic buffer too small, required: %zu, provided: %zu",
|
||||
required_size, len);
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_mqtt_username(char *username, size_t len) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t required_size = len;
|
||||
ret = nvs_get_str(handle, NVS_MQTT_USERNAME, username, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "Username buffer too small, required: %zu, provided: %zu",
|
||||
required_size, len);
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_mqtt_password(char *password, size_t len) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t required_size = len;
|
||||
ret = nvs_get_str(handle, NVS_MQTT_PASSWORD, password, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "Password buffer too small, required: %zu, provided: %zu",
|
||||
required_size, len);
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_save_device_id(const char *device_id) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_str(handle, NVS_DEVICE_ID, device_id);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save device ID: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_device_id(char *device_id, size_t len) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t required_size = len;
|
||||
ret = nvs_get_str(handle, NVS_DEVICE_ID, device_id, &required_size);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
ESP_LOGE(TAG, "Device ID buffer too small, required: %zu, provided: %zu",
|
||||
required_size, len);
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_save_location(float latitude, float longitude) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_blob(handle, NVS_LATITUDE, &latitude, sizeof(float));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save latitude: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_blob(handle, NVS_LONGITUDE, &longitude, sizeof(float));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save longitude: %s", esp_err_to_name(ret));
|
||||
nvs_close(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_load_location(float *latitude, float *longitude) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t size = sizeof(float);
|
||||
ret = nvs_get_blob(handle, NVS_LATITUDE, latitude, &size);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
size = sizeof(float);
|
||||
ret = nvs_get_blob(handle, NVS_LONGITUDE, longitude, &size);
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t nvs_erase(void) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_erase_all(handle);
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef NVS_STORAGE_H
|
||||
#define NVS_STORAGE_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Initialize NVS */
|
||||
esp_err_t nvs_storage_init(void);
|
||||
|
||||
/* Wi-Fi Credentials */
|
||||
esp_err_t nvs_save_wifi_credentials(const char *ssid, const char *password);
|
||||
esp_err_t nvs_load_wifi_credentials(char *ssid, size_t ssid_len, char *password,
|
||||
size_t pass_len);
|
||||
bool nvs_has_wifi_credentials(void);
|
||||
|
||||
/* MQTT Configuration */
|
||||
esp_err_t nvs_save_mqtt_config(const char *broker, uint16_t port,
|
||||
const char *topic, const char *username,
|
||||
const char *password);
|
||||
esp_err_t nvs_load_mqtt_broker(char *broker, size_t len);
|
||||
esp_err_t nvs_load_mqtt_port(uint16_t *port);
|
||||
esp_err_t nvs_load_mqtt_topic(char *topic, size_t len);
|
||||
esp_err_t nvs_load_mqtt_username(char *username, size_t len);
|
||||
esp_err_t nvs_load_mqtt_password(char *password, size_t len);
|
||||
|
||||
/* Device Configuration */
|
||||
esp_err_t nvs_save_device_id(const char *device_id);
|
||||
esp_err_t nvs_load_device_id(char *device_id, size_t len);
|
||||
esp_err_t nvs_save_location(float latitude, float longitude);
|
||||
esp_err_t nvs_load_location(float *latitude, float *longitude);
|
||||
|
||||
/* Erase all settings */
|
||||
esp_err_t nvs_erase(void);
|
||||
|
||||
#endif // NVS_STORAGE_H
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* OTA Server Certificate for gist.rodneyosodo.com
|
||||
*/
|
||||
|
||||
#ifndef OTA_CERTS_H
|
||||
#define OTA_CERTS_H
|
||||
|
||||
static const char ota_server_cert_pem[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDsDCCA1WgAwIBAgIQVJFJzsk2fvYO6u4tfCnjNDAKBggqhkjOPQQDAjA7MQsw\n"
|
||||
"CQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQwwCgYD\n"
|
||||
"VQQDEwNXRTEwHhcNMjUxMTAxMDYyMjM3WhcNMjYwMTMwMDcyMDU1WjAaMRgwFgYD\n"
|
||||
"VQQDEw9yb2RuZXlvc29kby5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASn\n"
|
||||
"jiG/+LOnHpIYT78fvoELyqtyJqffXHkchyl5J0TEw/0clPlnSgaPxGBE2z9+xKC2\n"
|
||||
"kz9cM3slRlBdWMkCK3Ego4ICWjCCAlYwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM\n"
|
||||
"MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAZu/CV+AbCUx0Ej\n"
|
||||
"lSopJ+RsayXAMB8GA1UdIwQYMBaAFJB3kjVnxP+ozKnme9mAeXvMk/k4MF4GCCsG\n"
|
||||
"AQUFBwEBBFIwUDAnBggrBgEFBQcwAYYbaHR0cDovL28ucGtpLmdvb2cvcy93ZTEv\n"
|
||||
"VkpFMCUGCCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93ZTEuY3J0MC0GA1Ud\n"
|
||||
"EQQmMCSCD3JvZG5leW9zb2RvLmNvbYIRKi5yb2RuZXlvc29kby5jb20wEwYDVR0g\n"
|
||||
"BAwwCjAIBgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2MucGtpLmdv\n"
|
||||
"b2cvd2UxL2J0dmQ2Nlo5dVFZLmNybDCCAQMGCisGAQQB1nkCBAIEgfQEgfEA7wB2\n"
|
||||
"AA5XlLzzrqk+MxssmQez95Dfm8I9cTIl3SGpJaxhxU4hAAABmj5L+gsAAAQDAEcw\n"
|
||||
"RQIhAOKqqnExy3qGupBgBsuFwdMG+m3CpV20nI8bshlQITASAiBEy+DB052tglIx\n"
|
||||
"80Z7IVd2jWaocNGXt7VcinavsGo8NAB1ANFuqaVoB35mNaA/N6XdvAOlPEESFNSI\n"
|
||||
"GPXpMbMjy5UEAAABmj5L+xEAAAQDAEYwRAIgL9Tmpw2dyD+BaP3N0wicEzPkYK2q\n"
|
||||
"15zfv+cx9h5rVCkCIAdL9OLitGYoUhLJrWDDdQwwB1AbnRHgOvfxQtrzyDXgMAoG\n"
|
||||
"CCqGSM49BAMCA0kAMEYCIQCKISJTQ/mwjgTzzRST1821dl9UJBSob/MZmqICXYm+\n"
|
||||
"DwIhAKB7PAG6aZpUrTXnxDCiIhiErcZr7F7Xuq8BiGEDwRKf\n"
|
||||
"-----END CERTIFICATE-----\n"
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICnzCCAiWgAwIBAgIQf/MZd5csIkp2FV0TttaF4zAKBggqhkjOPQQDAzBHMQsw\n"
|
||||
"CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n"
|
||||
"MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIwMTQw\n"
|
||||
"MDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZp\n"
|
||||
"Y2VzMQwwCgYDVQQDEwNXRTEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARvzTr+\n"
|
||||
"Z1dHTCEDhUDCR127WEcPQMFcF4XGGTfn1XzthkubgdnXGhOlCgP4mMTG6J7/EFmP\n"
|
||||
"LCaY9eYmJbsPAvpWo4H+MIH7MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggr\n"
|
||||
"BgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU\n"
|
||||
"kHeSNWfE/6jMqeZ72YB5e8yT+TgwHwYDVR0jBBgwFoAUgEzW63T/STaj1dj8tT7F\n"
|
||||
"avCUHYwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAChhhodHRwOi8vaS5wa2ku\n"
|
||||
"Z29vZy9yNC5jcnQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2MucGtpLmdvb2cv\n"
|
||||
"ci9yNC5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwCgYIKoZIzj0EAwMDaAAwZQIx\n"
|
||||
"AOcCq1HW90OVznX+0RGU1cxAQXomvtgM8zItPZCuFQ8jSBJSjz5keROv9aYsAm5V\n"
|
||||
"sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN\n"
|
||||
"7uJW\n"
|
||||
"-----END CERTIFICATE-----\n"
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDejCCAmKgAwIBAgIQf+UwvzMTQ77dghYQST2KGzANBgkqhkiG9w0BAQsFADBX\n"
|
||||
"MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE\n"
|
||||
"CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIzMTEx\n"
|
||||
"NTAzNDMyMVoXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT\n"
|
||||
"GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFI0\n"
|
||||
"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE83Rzp2iLYK5DuDXFgTB7S0md+8Fhzube\n"
|
||||
"Rr1r1WEYNa5A3XP3iZEwWus87oV8okB2O6nGuEfYKueSkWpz6bFyOZ8pn6KY019e\n"
|
||||
"WIZlD6GEZQbR3IvJx3PIjGov5cSr0R2Ko4H/MIH8MA4GA1UdDwEB/wQEAwIBhjAd\n"
|
||||
"BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd\n"
|
||||
"BgNVHQ4EFgQUgEzW63T/STaj1dj8tT7FavCUHYwwHwYDVR0jBBgwFoAUYHtmGkUN\n"
|
||||
"l8qJUC99BM00qP/8/UswNgYIKwYBBQUHAQEEKjAoMCYGCCsGAQUFBzAChhpodHRw\n"
|
||||
"Oi8vaS5wa2kuZ29vZy9nc3IxLmNydDAtBgNVHR8EJjAkMCKgIKAehhxodHRwOi8v\n"
|
||||
"Yy5wa2kuZ29vZy9yL2dzcjEuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMA0GCSqG\n"
|
||||
"SIb3DQEBCwUAA4IBAQAYQrsPBtYDh5bjP2OBDwmkoWhIDDkic574y04tfzHpn+cJ\n"
|
||||
"odI2D4SseesQ6bDrarZ7C30ddLibZatoKiws3UL9xnELz4ct92vID24FfVbiI1hY\n"
|
||||
"+SW6FoVHkNeWIP0GCbaM4C6uVdF5dTUsMVs/ZbzNnIdCp5Gxmx5ejvEau8otR/Cs\n"
|
||||
"kGN+hr/W5GvT1tMBjgWKZ1i4//emhA1JG1BbPzoLJQvyEotc03lXjTaCzv8mEbep\n"
|
||||
"8RqZ7a2CPsgRbuvTPBwcOMBBmuFeU88+FSBX6+7iP0il8b4Z0QFqIwwMHfs/L6K1\n"
|
||||
"vepuoxtGzi4CZ68zJpiq1UvSqTbFJjtbD4seiMHl\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "ota_manager.h"
|
||||
#include "config.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_https_ota.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "ota_certs.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "OTA_MANAGER";
|
||||
|
||||
esp_err_t ota_manager_init(void) {
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
esp_app_desc_t running_app_info;
|
||||
|
||||
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Current firmware version: %s", running_app_info.version);
|
||||
ESP_LOGI(TAG, "Running partition: %s", running->label);
|
||||
}
|
||||
|
||||
esp_ota_img_states_t ota_state;
|
||||
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
|
||||
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||
ESP_LOGI(TAG, "OTA update successful, marking as valid");
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ota_start_update(const char *url) {
|
||||
ESP_LOGI(TAG, "Starting OTA update from: %s", url);
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = url,
|
||||
.timeout_ms = OTA_RECV_TIMEOUT_MS,
|
||||
.keep_alive_enable = true,
|
||||
};
|
||||
|
||||
if (strncmp(url, "https://", 8) == 0) {
|
||||
if (strstr(url, "gist.rodneyosodo.com") != NULL ||
|
||||
strstr(url, "rodneyosodo.com") != NULL) {
|
||||
ESP_LOGI(TAG, "Using rodneyosodo.com certificate");
|
||||
config.cert_pem = ota_server_cert_pem;
|
||||
config.skip_cert_common_name_check = false;
|
||||
} else {
|
||||
ESP_LOGE(
|
||||
TAG,
|
||||
"HTTPS URL from unknown domain - OTA update rejected for security");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
} else if (strncmp(url, "http://", 7) == 0) {
|
||||
ESP_LOGW(TAG, "Using HTTP (non-encrypted) for OTA update");
|
||||
// HTTP is allowed but not recommended
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid OTA URL scheme");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_https_ota_config_t ota_config = {
|
||||
.http_config = &config,
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_https_ota(&ota_config);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "OTA update successful, restarting...");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "OTA update failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef OTA_MANAGER_H
|
||||
#define OTA_MANAGER_H
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/* Initialize OTA manager */
|
||||
esp_err_t ota_manager_init(void);
|
||||
|
||||
/* Start OTA update from URL */
|
||||
esp_err_t ota_start_update(const char *url);
|
||||
|
||||
#endif // OTA_MANAGER_H
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "power_monitor.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "mqtt_lib.h"
|
||||
#include "nvs_storage.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "POWER_MONITOR";
|
||||
|
||||
static TaskHandle_t monitor_task_handle = NULL;
|
||||
static bool is_running = false;
|
||||
static char device_id[MAX_DEVICE_ID_LEN] = {0};
|
||||
static float latitude = DEFAULT_LATITUDE;
|
||||
static float longitude = DEFAULT_LONGITUDE;
|
||||
|
||||
static void power_monitor_task(void *pvParameters) {
|
||||
ESP_LOGI(TAG, "Power monitor task started");
|
||||
|
||||
while (is_running) {
|
||||
// Send heartbeat (power ON status)
|
||||
if (mqtt_is_connected()) {
|
||||
ESP_LOGI(TAG, "Sending heartbeat");
|
||||
mqtt_publish_power_status(true, device_id, latitude, longitude);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "MQTT not connected, skipping heartbeat");
|
||||
}
|
||||
|
||||
// Wait for next heartbeat interval
|
||||
vTaskDelay(pdMS_TO_TICKS(HEARTBEAT_INTERVAL_MS));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Power monitor task stopped");
|
||||
monitor_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_err_t power_monitor_init(void) {
|
||||
if (nvs_load_device_id(device_id, sizeof(device_id)) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "No device ID found, using MAC address");
|
||||
uint8_t mac[6];
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
snprintf(device_id, sizeof(device_id), "ESP32-%02X%02X%02X%02X%02X%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
nvs_save_device_id(device_id);
|
||||
}
|
||||
|
||||
if (nvs_load_location(&latitude, &longitude) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "No location found, using defaults");
|
||||
latitude = DEFAULT_LATITUDE;
|
||||
longitude = DEFAULT_LONGITUDE;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Power monitor initialized - Device: %s, Location: %.6f, %.6f",
|
||||
device_id, latitude, longitude);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t power_monitor_start(void) {
|
||||
if (is_running) {
|
||||
ESP_LOGW(TAG, "Power monitor already running");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
is_running = true;
|
||||
|
||||
xTaskCreate(power_monitor_task, "power_monitor", 4096, NULL, 5,
|
||||
&monitor_task_handle);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t power_monitor_stop(void) {
|
||||
is_running = false;
|
||||
|
||||
if (monitor_task_handle != NULL) {
|
||||
// Wait for task to finish
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t power_monitor_reload_config(void) {
|
||||
// Reload device ID
|
||||
if (nvs_load_device_id(device_id, sizeof(device_id)) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "No device ID found, using MAC address");
|
||||
uint8_t mac[6];
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
snprintf(device_id, sizeof(device_id), "ESP32-%02X%02X%02X%02X%02X%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
nvs_save_device_id(device_id);
|
||||
}
|
||||
|
||||
// Reload location
|
||||
if (nvs_load_location(&latitude, &longitude) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "No location found, using defaults");
|
||||
latitude = DEFAULT_LATITUDE;
|
||||
longitude = DEFAULT_LONGITUDE;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Configuration reloaded - Device: %s, Location: %.6f, %.6f",
|
||||
device_id, latitude, longitude);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t power_monitor_send_power_restored(void) {
|
||||
ESP_LOGI(TAG, "Sending power restored event");
|
||||
|
||||
// Wait a bit for MQTT to connect
|
||||
int retries = 0;
|
||||
while (!mqtt_is_connected() && retries < 50) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (mqtt_is_connected()) {
|
||||
return mqtt_publish_power_status(true, device_id, latitude, longitude);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "MQTT not connected, cannot send power restored event");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef POWER_MONITOR_H
|
||||
#define POWER_MONITOR_H
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/* Initialize power monitor */
|
||||
esp_err_t power_monitor_init(void);
|
||||
|
||||
/* Start monitoring and heartbeat */
|
||||
esp_err_t power_monitor_start(void);
|
||||
|
||||
/* Stop monitoring */
|
||||
esp_err_t power_monitor_stop(void);
|
||||
|
||||
/* Reload configuration from NVS */
|
||||
esp_err_t power_monitor_reload_config(void);
|
||||
|
||||
/* Send power restored event */
|
||||
esp_err_t power_monitor_send_power_restored(void);
|
||||
|
||||
#endif // POWER_MONITOR_H
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_sntp.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "mqtt_lib.h"
|
||||
#include "nvs_storage.h"
|
||||
#include "ota_manager.h"
|
||||
#include "power_monitor.h"
|
||||
#include "wifi_manager.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
static const char *TAG = "MAIN";
|
||||
|
||||
static void time_sync_notification_cb(struct timeval *tv) {
|
||||
ESP_LOGI(TAG, "Time synchronized with NTP server");
|
||||
}
|
||||
|
||||
static void initialize_sntp(void) {
|
||||
// Check if SNTP is already initialized
|
||||
if (esp_sntp_enabled()) {
|
||||
ESP_LOGI(TAG, "SNTP already initialized, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing SNTP");
|
||||
|
||||
setenv("TZ", TIMEZONE, 1);
|
||||
tzset();
|
||||
|
||||
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||
esp_sntp_setservername(0, "pool.ntp.org");
|
||||
esp_sntp_setservername(1, "time.google.com");
|
||||
esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb);
|
||||
esp_sntp_init();
|
||||
|
||||
time_t now = 0;
|
||||
struct tm timeinfo = {0};
|
||||
int retry = 0;
|
||||
const int retry_count = 15;
|
||||
|
||||
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET &&
|
||||
++retry < retry_count) {
|
||||
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry,
|
||||
retry_count);
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
time(&now);
|
||||
localtime_r(&now, &timeinfo);
|
||||
|
||||
if (timeinfo.tm_year < (2024 - 1900)) {
|
||||
ESP_LOGW(TAG, "Time not set yet, continuing anyway");
|
||||
} else {
|
||||
char strftime_buf[64];
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
|
||||
ESP_LOGI(TAG, "Current time: %s", strftime_buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_event_callback(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
if (event_base == WIFI_MANAGER_EVENT) {
|
||||
switch (event_id) {
|
||||
case WIFI_MANAGER_EVENT_STA_CONNECTED:
|
||||
ESP_LOGI(TAG, "Connected to WiFi, syncing time...");
|
||||
|
||||
initialize_sntp();
|
||||
|
||||
ESP_LOGI(TAG, "Starting MQTT");
|
||||
mqtt_client_start();
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
power_monitor_send_power_restored();
|
||||
|
||||
power_monitor_start();
|
||||
break;
|
||||
|
||||
case WIFI_MANAGER_EVENT_STA_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "WiFi disconnected");
|
||||
power_monitor_stop();
|
||||
mqtt_client_stop();
|
||||
break;
|
||||
|
||||
case WIFI_MANAGER_EVENT_AP_STARTED:
|
||||
ESP_LOGI(TAG, "Access Point started for configuration");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
|
||||
ESP_ERROR_CHECK(nvs_storage_init());
|
||||
|
||||
ESP_ERROR_CHECK(wifi_manager_init());
|
||||
ESP_ERROR_CHECK(mqtt_client_init());
|
||||
ESP_ERROR_CHECK(power_monitor_init());
|
||||
ESP_ERROR_CHECK(ota_manager_init());
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
WIFI_MANAGER_EVENT, ESP_EVENT_ANY_ID, &wifi_event_callback, NULL));
|
||||
|
||||
if (nvs_has_wifi_credentials()) {
|
||||
ESP_LOGI(TAG, "WiFi credentials found, connecting to network");
|
||||
ESP_ERROR_CHECK(wifi_manager_connect_sta());
|
||||
} else {
|
||||
ESP_LOGI(TAG,
|
||||
"No WiFi credentials, starting Access Point for configuration");
|
||||
ESP_LOGI(TAG, "Connect to '%s' with password '%s'", WIFI_AP_SSID,
|
||||
WIFI_AP_PASSWORD);
|
||||
ESP_LOGI(TAG, "Then open http://192.168.4.1 in your browser");
|
||||
ESP_ERROR_CHECK(wifi_manager_start_ap());
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "System initialized and running");
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
|
||||
if (wifi_manager_config_updated()) {
|
||||
ESP_LOGI(TAG, "Configuration updated, reconnecting to WiFi...");
|
||||
wifi_manager_clear_config_flag();
|
||||
|
||||
mqtt_client_stop();
|
||||
power_monitor_stop();
|
||||
|
||||
// Reload configuration (device ID, location, etc.)
|
||||
power_monitor_reload_config();
|
||||
|
||||
if (nvs_has_wifi_credentials()) {
|
||||
ESP_LOGI(TAG, "Connecting to WiFi with updated credentials");
|
||||
wifi_manager_connect_sta();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
#include "wifi_manager.h"
|
||||
#include "config.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "lwip/dns.h"
|
||||
#include "nvs_storage.h"
|
||||
#include "ota_manager.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "WIFI_MANAGER";
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(WIFI_MANAGER_EVENT);
|
||||
|
||||
static esp_netif_t *sta_netif = NULL;
|
||||
static esp_netif_t *ap_netif = NULL;
|
||||
static httpd_handle_t server = NULL;
|
||||
static bool is_connected = false;
|
||||
static int retry_count = 0;
|
||||
static const int MAX_RETRY = 5;
|
||||
static bool config_updated = false;
|
||||
|
||||
// Simplified HTML template without external dependencies for offline AP mode
|
||||
// operation
|
||||
static const char *config_page_template =
|
||||
"<!DOCTYPE html>"
|
||||
"<html><head><meta name='viewport' "
|
||||
"content='width=device-width,initial-scale=1'>"
|
||||
"<meta charset='UTF-8'>"
|
||||
"<title>Power Monitor Setup</title>"
|
||||
"<link rel='stylesheet' "
|
||||
"href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'/>"
|
||||
"<style>"
|
||||
"body{font-family:Arial,sans-serif;background:#f0f0f0;margin:0;padding:"
|
||||
"20px}"
|
||||
".container{max-width:600px;margin:0 "
|
||||
"auto;background:white;padding:30px;border-radius:10px;box-shadow:0 2px "
|
||||
"10px rgba(0,0,0,0.1)}"
|
||||
"h1{color:#333;text-align:center;margin-bottom:10px}"
|
||||
".status{text-align:center;color:#666;margin-bottom:20px;font-size:14px}"
|
||||
"h3{color:#555;border-bottom:2px solid "
|
||||
"#4CAF50;padding-bottom:5px;margin-top:20px}"
|
||||
"input,select{width:100%%;padding:12px;margin:8px 0;border:1px solid "
|
||||
"#ddd;border-radius:5px;box-sizing:border-box;font-size:14px}"
|
||||
"input:focus{border-color:#4CAF50;outline:none}"
|
||||
"button{width:100%%;padding:14px;background:#4CAF50;color:white;border:"
|
||||
"none;border-radius:5px;cursor:pointer;font-size:16px;margin-top:15px}"
|
||||
"button:hover{background:#45a049}"
|
||||
".info{background:#e3f2fd;padding:10px;border-radius:5px;margin:10px "
|
||||
"0;font-size:13px}"
|
||||
".current-value{color:#666;font-size:12px;margin-top:3px}"
|
||||
".coord-group{display:flex;gap:10px;margin-top:10px}"
|
||||
".coord-group>div{flex:1}"
|
||||
"#map{height:300px;width:100%%;border-radius:5px;margin:15px 0;border:1px "
|
||||
"solid #ddd}"
|
||||
".location-hint{color:#666;font-size:12px;margin-top:10px;text-align:"
|
||||
"center;background:#f8f9fa;padding:8px;border-radius:5px}"
|
||||
"label{display:block;font-size:12px;color:#666;margin-bottom:4px}"
|
||||
"</style></head><body>"
|
||||
"<div class='container'>"
|
||||
"<h1>Power Monitor</h1>"
|
||||
"<div class='status'>Configuration Portal</div>"
|
||||
"<div class='info'>Configure your device settings below. Changes are saved "
|
||||
"immediately.</div>"
|
||||
"<form action='/save' method='POST' id='configForm'>"
|
||||
"<h3>WiFi Configuration</h3>"
|
||||
"<input type='text' name='ssid' id='ssid' placeholder='WiFi SSID' "
|
||||
"value='%s' required>"
|
||||
"<input type='password' name='pass' placeholder='WiFi Password (leave "
|
||||
"blank to keep current)' value=''>"
|
||||
"<h3>Device Configuration</h3>"
|
||||
"<input type='text' name='device_id' placeholder='Device ID' value='%s' "
|
||||
"required>"
|
||||
"<h3>Location</h3>"
|
||||
"<div class='current-value'>Click on the map to set location, or enter "
|
||||
"coordinates manually</div>"
|
||||
"<div id='map'></div>"
|
||||
"<div class='coord-group'>"
|
||||
"<div>"
|
||||
"<label>Latitude (-90 to 90)</label>"
|
||||
"<input type='number' step='0.000001' name='latitude' id='latitude' "
|
||||
"placeholder='0.000000' value='%.6f' min='-90' max='90'>"
|
||||
"</div>"
|
||||
"<div>"
|
||||
"<label>Longitude (-180 to 180)</label>"
|
||||
"<input type='number' step='0.000001' name='longitude' id='longitude' "
|
||||
"placeholder='0.000000' value='%.6f' min='-180' max='180'>"
|
||||
"</div>"
|
||||
"</div>"
|
||||
"<div class='location-hint'>Current: <span id='coordDisplay'>%.6f, "
|
||||
"%.6f</span></div>"
|
||||
"<button type='submit'>Save Configuration</button>"
|
||||
"</form>"
|
||||
"<div id='message' "
|
||||
"style='margin-top:15px;padding:10px;border-radius:5px;display:none'></div>"
|
||||
"</div>"
|
||||
"<script src='https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'></script>"
|
||||
"<script>"
|
||||
"var lat=parseFloat(document.getElementById('latitude').value)||0;"
|
||||
"var lng=parseFloat(document.getElementById('longitude').value)||0;"
|
||||
"var map=L.map('map').setView([lat,lng],lat===0&&lng===0?2:13);"
|
||||
"L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/"
|
||||
"{y}.png',{attribution:'© OpenStreetMap'}).addTo(map);"
|
||||
"var marker=L.marker([lat,lng],{draggable:true}).addTo(map);"
|
||||
"function updateDisplay(){"
|
||||
"var lat=parseFloat(document.getElementById('latitude').value)||0;"
|
||||
"var lng=parseFloat(document.getElementById('longitude').value)||0;"
|
||||
"document.getElementById('coordDisplay').textContent=lat.toFixed(6)+', "
|
||||
"'+lng.toFixed(6);"
|
||||
"}"
|
||||
"function updateCoords(latlng){"
|
||||
"document.getElementById('latitude').value=latlng.lat.toFixed(6);"
|
||||
"document.getElementById('longitude').value=latlng.lng.toFixed(6);"
|
||||
"updateDisplay();"
|
||||
"}"
|
||||
"map.on('click',function(e){marker.setLatLng(e.latlng);updateCoords(e."
|
||||
"latlng);});"
|
||||
"marker.on('dragend',function(e){updateCoords(e.target.getLatLng());});"
|
||||
"document.getElementById('latitude').addEventListener('input',function(){"
|
||||
"var newLat=parseFloat(this.value)||0;"
|
||||
"var newLng=parseFloat(document.getElementById('longitude').value)||0;"
|
||||
"marker.setLatLng([newLat,newLng]);map.setView([newLat,newLng]);"
|
||||
"updateDisplay();"
|
||||
"});"
|
||||
"document.getElementById('longitude').addEventListener('input',function(){"
|
||||
"var newLat=parseFloat(document.getElementById('latitude').value)||0;"
|
||||
"var newLng=parseFloat(this.value)||0;"
|
||||
"marker.setLatLng([newLat,newLng]);map.setView([newLat,newLng]);"
|
||||
"updateDisplay();"
|
||||
"});"
|
||||
"document.getElementById('configForm').onsubmit=function(e){"
|
||||
"e.preventDefault();"
|
||||
"var formData=new FormData(this);"
|
||||
"var xhr=new XMLHttpRequest();"
|
||||
"xhr.open('POST','/save',true);"
|
||||
"xhr.onload=function(){"
|
||||
"var msg=document.getElementById('message');"
|
||||
"if(xhr.status===200){"
|
||||
"msg.style.background='#d4edda';msg.style.color='#155724';"
|
||||
"msg.innerHTML='Configuration saved successfully! Connecting to WiFi...';"
|
||||
"msg.style.display='block';"
|
||||
"setTimeout(function(){location.reload()},2000);"
|
||||
"}else{"
|
||||
"msg.style.background='#f8d7da';msg.style.color='#721c24';"
|
||||
"msg.innerHTML='Failed to save configuration. Please try again.';"
|
||||
"msg.style.display='block';"
|
||||
"}"
|
||||
"};"
|
||||
"xhr.send(new URLSearchParams(formData));"
|
||||
"};"
|
||||
"</script></body></html>";
|
||||
|
||||
static void parse_form_data(char *data, char *ssid, char *pass, char *device_id,
|
||||
float *latitude, float *longitude) {
|
||||
char *token = strtok(data, "&");
|
||||
|
||||
while (token != NULL) {
|
||||
char *key = token;
|
||||
char *value = strchr(token, '=');
|
||||
|
||||
if (value != NULL) {
|
||||
*value = '\0';
|
||||
value++;
|
||||
|
||||
char decoded[256] = {0};
|
||||
int j = 0;
|
||||
for (int i = 0; value[i] && j < 255; i++, j++) {
|
||||
if (value[i] == '+') {
|
||||
decoded[j] = ' ';
|
||||
} else if (value[i] == '%' && value[i + 1] && value[i + 2]) {
|
||||
char hex[3] = {value[i + 1], value[i + 2], '\0'};
|
||||
decoded[j] = (char)strtol(hex, NULL, 16);
|
||||
i += 2;
|
||||
} else {
|
||||
decoded[j] = value[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(key, "ssid") == 0)
|
||||
strcpy(ssid, decoded);
|
||||
else if (strcmp(key, "pass") == 0)
|
||||
strcpy(pass, decoded);
|
||||
else if (strcmp(key, "device_id") == 0)
|
||||
strcpy(device_id, decoded);
|
||||
else if (strcmp(key, "latitude") == 0)
|
||||
*latitude = atof(decoded);
|
||||
else if (strcmp(key, "longitude") == 0)
|
||||
*longitude = atof(decoded);
|
||||
}
|
||||
|
||||
token = strtok(NULL, "&");
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t config_get_handler(httpd_req_t *req) {
|
||||
char *response = malloc(HTTP_MAX_RESP_LEN);
|
||||
if (response == NULL) {
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
char ssid[MAX_SSID_LEN] = {0};
|
||||
char password[MAX_PASSWORD_LEN] = {0};
|
||||
char device_id[MAX_DEVICE_ID_LEN] = {0};
|
||||
float latitude = 0.0;
|
||||
float longitude = 0.0;
|
||||
|
||||
nvs_load_wifi_credentials(ssid, sizeof(ssid), password, sizeof(password));
|
||||
nvs_load_device_id(device_id, sizeof(device_id));
|
||||
nvs_load_location(&latitude, &longitude);
|
||||
|
||||
snprintf(response, HTTP_MAX_RESP_LEN, config_page_template, ssid, device_id,
|
||||
latitude, longitude, latitude, longitude);
|
||||
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
free(response);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t config_post_handler(httpd_req_t *req) {
|
||||
char buf[1024];
|
||||
int ret, remaining = req->content_len;
|
||||
|
||||
if (remaining >= sizeof(buf)) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too long");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ret = httpd_req_recv(req, buf, remaining);
|
||||
if (ret <= 0) {
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
||||
"Failed to receive data");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
buf[ret] = '\0';
|
||||
|
||||
char ssid[MAX_SSID_LEN] = {0};
|
||||
char password[MAX_PASSWORD_LEN] = {0};
|
||||
char device_id[MAX_DEVICE_ID_LEN] = {0};
|
||||
float latitude = DEFAULT_LATITUDE;
|
||||
float longitude = DEFAULT_LONGITUDE;
|
||||
|
||||
parse_form_data(buf, ssid, password, device_id, &latitude, &longitude);
|
||||
|
||||
ESP_LOGI(TAG, "Saving configuration - SSID: %s, Device ID: %s", ssid,
|
||||
device_id);
|
||||
|
||||
if (strlen(ssid) > 0) {
|
||||
if (strlen(password) > 0) {
|
||||
nvs_save_wifi_credentials(ssid, password);
|
||||
} else {
|
||||
char old_password[MAX_PASSWORD_LEN] = {0};
|
||||
nvs_load_wifi_credentials(ssid, sizeof(ssid), old_password,
|
||||
sizeof(old_password));
|
||||
nvs_save_wifi_credentials(ssid, old_password);
|
||||
}
|
||||
}
|
||||
|
||||
nvs_save_device_id(device_id);
|
||||
nvs_save_location(latitude, longitude);
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req,
|
||||
"{\"status\":\"success\",\"message\":\"Configuration saved, "
|
||||
"connecting to WiFi...\"}",
|
||||
HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
config_updated = true;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Single consolidated webserver initialization function
|
||||
// Uses direct initialization - no need for separate task as httpd library
|
||||
// handles threading internally
|
||||
static esp_err_t start_webserver(void) {
|
||||
if (server != NULL) {
|
||||
ESP_LOGI(TAG, "Web server already running");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = HTTP_SERVER_PORT;
|
||||
config.max_uri_handlers = 8;
|
||||
config.stack_size = 8192; // Increase stack size for larger HTML responses
|
||||
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
httpd_uri_t config_get = {
|
||||
.uri = "/", .method = HTTP_GET, .handler = config_get_handler};
|
||||
httpd_uri_t config_post = {
|
||||
.uri = "/save", .method = HTTP_POST, .handler = config_post_handler};
|
||||
|
||||
httpd_register_uri_handler(server, &config_get);
|
||||
httpd_register_uri_handler(server, &config_post);
|
||||
|
||||
ESP_LOGI(TAG, "Web server started successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Failed to start web server");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
if (event_base == WIFI_EVENT) {
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_STA_START:
|
||||
ESP_LOGI(TAG, "Station started, connecting...");
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_STA_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "Disconnected from AP");
|
||||
is_connected = false;
|
||||
|
||||
if (retry_count < MAX_RETRY) {
|
||||
ESP_LOGI(TAG, "Retrying connection... (%d/%d)", retry_count + 1,
|
||||
MAX_RETRY);
|
||||
vTaskDelay(pdMS_TO_TICKS(WIFI_RECONNECT_DELAY_MS));
|
||||
esp_wifi_connect();
|
||||
retry_count++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Max retries reached");
|
||||
}
|
||||
|
||||
esp_event_post(WIFI_MANAGER_EVENT, WIFI_MANAGER_EVENT_STA_DISCONNECTED,
|
||||
NULL, 0, portMAX_DELAY);
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_AP_START:
|
||||
ESP_LOGI(TAG, "Access Point started");
|
||||
esp_event_post(WIFI_MANAGER_EVENT, WIFI_MANAGER_EVENT_AP_STARTED, NULL, 0,
|
||||
portMAX_DELAY);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (event_base == IP_EVENT) {
|
||||
if (event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
ESP_LOGI(TAG, "Configuration portal accessible at: http://" IPSTR,
|
||||
IP2STR(&event->ip_info.ip));
|
||||
retry_count = 0;
|
||||
is_connected = true;
|
||||
|
||||
if (server == NULL) {
|
||||
start_webserver();
|
||||
}
|
||||
|
||||
esp_event_post(WIFI_MANAGER_EVENT, WIFI_MANAGER_EVENT_STA_CONNECTED, NULL,
|
||||
0, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t wifi_manager_init(void) {
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
sta_netif = esp_netif_create_default_wifi_sta();
|
||||
ap_netif = esp_netif_create_default_wifi_ap();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
&wifi_event_handler, NULL));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t wifi_manager_start_ap(void) {
|
||||
wifi_config_t wifi_config = {
|
||||
.ap = {.ssid = WIFI_AP_SSID,
|
||||
.password = WIFI_AP_PASSWORD,
|
||||
.ssid_len = strlen(WIFI_AP_SSID),
|
||||
.channel = WIFI_AP_CHANNEL,
|
||||
.max_connection = WIFI_AP_MAX_CONNECTIONS,
|
||||
.authmode = WIFI_AUTH_WPA2_PSK},
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
start_webserver();
|
||||
|
||||
ESP_LOGI(TAG, "AP started - SSID: %s", WIFI_AP_SSID);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t wifi_manager_connect_sta(void) {
|
||||
char ssid[MAX_SSID_LEN] = {0};
|
||||
char password[MAX_PASSWORD_LEN] = {0};
|
||||
|
||||
esp_err_t ret =
|
||||
nvs_load_wifi_credentials(ssid, sizeof(ssid), password, sizeof(password));
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to load WiFi credentials");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return wifi_manager_connect(ssid, password);
|
||||
}
|
||||
|
||||
esp_err_t wifi_manager_connect(const char *ssid, const char *password) {
|
||||
wifi_config_t wifi_config = {0};
|
||||
|
||||
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
|
||||
strncpy((char *)wifi_config.sta.password, password,
|
||||
sizeof(wifi_config.sta.password) - 1);
|
||||
|
||||
esp_wifi_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
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_LOGI(TAG, "Connecting to SSID: %s", ssid);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool wifi_manager_is_connected(void) { return is_connected; }
|
||||
|
||||
bool wifi_manager_config_updated(void) { return config_updated; }
|
||||
|
||||
void wifi_manager_clear_config_flag(void) { config_updated = false; }
|
||||
|
||||
esp_err_t wifi_manager_stop(void) {
|
||||
if (server) {
|
||||
httpd_stop(server);
|
||||
server = NULL;
|
||||
}
|
||||
|
||||
esp_wifi_stop();
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef WIFI_MANAGER_H
|
||||
#define WIFI_MANAGER_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
/* WiFi events */
|
||||
ESP_EVENT_DECLARE_BASE(WIFI_MANAGER_EVENT);
|
||||
|
||||
typedef enum {
|
||||
WIFI_MANAGER_EVENT_STA_CONNECTED,
|
||||
WIFI_MANAGER_EVENT_STA_DISCONNECTED,
|
||||
WIFI_MANAGER_EVENT_AP_STARTED,
|
||||
} wifi_manager_event_t;
|
||||
|
||||
/* Initialize WiFi manager */
|
||||
esp_err_t wifi_manager_init(void);
|
||||
|
||||
/* Start AP mode for configuration */
|
||||
esp_err_t wifi_manager_start_ap(void);
|
||||
|
||||
/* Connect to saved WiFi credentials */
|
||||
esp_err_t wifi_manager_connect_sta(void);
|
||||
|
||||
/* Connect to specific WiFi network */
|
||||
esp_err_t wifi_manager_connect(const char *ssid, const char *password);
|
||||
|
||||
/* Check if connected */
|
||||
bool wifi_manager_is_connected(void);
|
||||
|
||||
/* Check if configuration was updated */
|
||||
bool wifi_manager_config_updated(void);
|
||||
|
||||
/* Clear config updated flag */
|
||||
void wifi_manager_clear_config_flag(void);
|
||||
|
||||
/* Stop WiFi */
|
||||
esp_err_t wifi_manager_stop(void);
|
||||
|
||||
#endif // WIFI_MANAGER_H
|
||||
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, 0xd000, 0x2000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
ota_0, app, ota_0, 0x10000, 0xF0000,
|
||||
ota_1, app, ota_1, 0x100000, 0xF0000,
|
||||
|
@@ -0,0 +1,61 @@
|
||||
# Partition Table Configuration
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
|
||||
# Flash size
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
|
||||
|
||||
# OTA Configuration
|
||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||
CONFIG_BOOTLOADER_ROLLBACK_ENABLE=y
|
||||
|
||||
# Application Manager
|
||||
CONFIG_APP_COMPILE_TIME_DATE=y
|
||||
CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16
|
||||
|
||||
# Log levels
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
CONFIG_LOG_MAXIMUM_LEVEL_INFO=y
|
||||
|
||||
# FreeRTOS
|
||||
CONFIG_FREERTOS_HZ=100
|
||||
CONFIG_FREERTOS_USE_TRACE_FACILITY=n
|
||||
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=n
|
||||
|
||||
# ESP32C3 specific
|
||||
CONFIG_ESP32C3_DEFAULT_CPU_FREQ_160=y
|
||||
|
||||
# Wi-Fi
|
||||
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
|
||||
|
||||
# LWIP
|
||||
CONFIG_LWIP_MAX_SOCKETS=10
|
||||
CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
|
||||
|
||||
# System Event Task
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
|
||||
|
||||
# HTTP Server
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
CONFIG_HTTPD_MAX_URI_LEN=512
|
||||
CONFIG_HTTPD_STACK_SIZE=6144
|
||||
|
||||
# MQTT with TLS/SSL Support
|
||||
CONFIG_MQTT_PROTOCOL_311=y
|
||||
CONFIG_MQTT_TRANSPORT_SSL=y
|
||||
CONFIG_MQTT_TRANSPORT_WEBSOCKET=n
|
||||
CONFIG_MQTT_USE_CUSTOM_CONFIG=n
|
||||
|
||||
# mbedTLS for MQTT TLS
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y
|
||||
CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y
|
||||
|
||||
+1985
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user