code enhancements
- add MQTT - add ESP32-C6 - fix simulation Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
release:
|
release:
|
||||||
idf.py -B build-release -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" fullclean build
|
idf.py -B build-release -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" -DIDF_TARGET=esp32c6 fullclean build size
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ idf_component_register(SRCS
|
|||||||
simulator
|
simulator
|
||||||
persistence-manager
|
persistence-manager
|
||||||
message-manager
|
message-manager
|
||||||
simulator
|
my_mqtt_client
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ dependencies:
|
|||||||
idf:
|
idf:
|
||||||
version: '>=5.0.0'
|
version: '>=5.0.0'
|
||||||
espressif/mdns:
|
espressif/mdns:
|
||||||
version: '*'
|
version: ^1.10.1
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ esp_err_t api_capabilities_get_handler(httpd_req_t *req)
|
|||||||
// Thread only available for esp32c6 or esp32h2
|
// Thread only available for esp32c6 or esp32h2
|
||||||
bool thread = false;
|
bool thread = false;
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
|
#if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
|
||||||
thread = true;
|
thread = false;
|
||||||
#endif
|
#endif
|
||||||
cJSON *json = cJSON_CreateObject();
|
cJSON *json = cJSON_CreateObject();
|
||||||
cJSON_AddBoolToObject(json, "thread", thread);
|
cJSON_AddBoolToObject(json, "thread", thread);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <esp_http_server.h>
|
#include <esp_http_server.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <mdns.h>
|
#include <mdns.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const char *TAG = "api_server";
|
static const char *TAG = "api_server";
|
||||||
@@ -55,7 +56,7 @@ static esp_err_t start_webserver(void)
|
|||||||
config.server_port = s_config.port;
|
config.server_port = s_config.port;
|
||||||
config.lru_purge_enable = true;
|
config.lru_purge_enable = true;
|
||||||
config.max_uri_handlers = 32;
|
config.max_uri_handlers = 32;
|
||||||
config.max_open_sockets = 7;
|
config.max_open_sockets = (CONFIG_LWIP_MAX_SOCKETS - 3);
|
||||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Starting HTTP server on port %d", config.server_port);
|
ESP_LOGI(TAG, "Starting HTTP server on port %d", config.server_port);
|
||||||
@@ -175,6 +176,7 @@ esp_err_t api_server_ws_broadcast_status(bool on, const char *mode, const char *
|
|||||||
"{\"type\":\"status\",\"on\":%s,\"mode\":\"%s\",\"schema\":\"%s\","
|
"{\"type\":\"status\",\"on\":%s,\"mode\":\"%s\",\"schema\":\"%s\","
|
||||||
"\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}}",
|
"\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}}",
|
||||||
on ? "true" : "false", mode, schema, r, g, b);
|
on ? "true" : "false", mode, schema, r, g, b);
|
||||||
|
|
||||||
return api_server_ws_broadcast(buffer);
|
return api_server_ws_broadcast(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include "message_manager.h"
|
#include "my_mqtt_client.h"
|
||||||
#include <esp_http_server.h>
|
#include <esp_http_server.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
|
#include <message_manager.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const char *TAG = "websocket_handler";
|
static const char *TAG = "websocket_handler";
|
||||||
@@ -267,6 +268,8 @@ esp_err_t websocket_broadcast(httpd_handle_t server, const char *message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mqtt_publish(message);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
## IDF Component Manager Manifest File
|
|
||||||
dependencies:
|
dependencies:
|
||||||
## Required IDF version
|
|
||||||
idf:
|
idf:
|
||||||
version: '>=4.1.0'
|
version: '>=5.0.0'
|
||||||
|
espressif/ble_conn_mgr: '^0.1.6'
|
||||||
espressif/ble_conn_mgr: '^0.1.3'
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
espressif/led_strip: '~3.0.1'
|
idf:
|
||||||
|
version: '>=5.0.0'
|
||||||
|
espressif/led_strip: '~3.0.3'
|
||||||
|
|||||||
@@ -103,15 +103,6 @@ static void message_manager_task(void *param)
|
|||||||
message_listeners[i](&msg);
|
message_listeners[i](&msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t mac[6];
|
|
||||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
|
||||||
const esp_app_desc_t *app_desc = esp_app_get_description();
|
|
||||||
char topic[60];
|
|
||||||
snprintf(topic, sizeof(topic), "device/%s/%02x%02x", app_desc->project_name, mac[4], mac[5]);
|
|
||||||
|
|
||||||
char *data = "{\"key\":\"value\"}";
|
|
||||||
mqtt_client_publish(topic, data, strlen(data), 0, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
|
idf:
|
||||||
|
version: '>=5.0.0'
|
||||||
espressif/mqtt: ^1.0.0
|
espressif/mqtt: ^1.0.0
|
||||||
|
espressif/cjson: "*"
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C"
|
||||||
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void mqtt_client_start(void);
|
void mqtt_client_start(void);
|
||||||
void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain);
|
void mqtt_publish(const char *message);
|
||||||
|
void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
#include "mqtt_client.h"
|
#include "mqtt_client.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#include <cJSON.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#define DEVICE_TOPIC_MAX_LEN 60
|
||||||
|
|
||||||
static const char *TAG = "mqtt_client";
|
static const char *TAG = "mqtt_client";
|
||||||
static esp_mqtt_client_handle_t client = NULL;
|
static esp_mqtt_client_handle_t client = NULL;
|
||||||
|
|
||||||
@@ -108,12 +114,123 @@ void mqtt_client_start(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void get_device_topic(char *topic, size_t topic_len)
|
||||||
|
{
|
||||||
|
uint8_t mac[6];
|
||||||
|
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||||
|
const esp_app_desc_t *app_desc = esp_app_get_description();
|
||||||
|
snprintf(topic, topic_len, "device/%s/%02x%02x", app_desc->project_name, mac[4], mac[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqtt_publish(const char *message)
|
||||||
|
{
|
||||||
|
// Uptime in ms
|
||||||
|
int64_t uptime_ms = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
// UTC time as ISO8601
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
struct tm tm_utc;
|
||||||
|
gmtime_r(&tv.tv_sec, &tm_utc);
|
||||||
|
char timestamp[32];
|
||||||
|
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &tm_utc);
|
||||||
|
|
||||||
|
// Firmware version
|
||||||
|
const esp_app_desc_t *app_desc = esp_app_get_description();
|
||||||
|
const char *firmware = app_desc->version;
|
||||||
|
|
||||||
|
// Reset reason
|
||||||
|
esp_reset_reason_t reset_reason = esp_reset_reason();
|
||||||
|
const char *reset_reason_str = "UNKNOWN";
|
||||||
|
switch (reset_reason)
|
||||||
|
{
|
||||||
|
case ESP_RST_POWERON:
|
||||||
|
reset_reason_str = "POWERON";
|
||||||
|
break;
|
||||||
|
case ESP_RST_EXT:
|
||||||
|
reset_reason_str = "EXT";
|
||||||
|
break;
|
||||||
|
case ESP_RST_SW:
|
||||||
|
reset_reason_str = "SW";
|
||||||
|
break;
|
||||||
|
case ESP_RST_PANIC:
|
||||||
|
reset_reason_str = "PANIC";
|
||||||
|
break;
|
||||||
|
case ESP_RST_INT_WDT:
|
||||||
|
reset_reason_str = "INT_WDT";
|
||||||
|
break;
|
||||||
|
case ESP_RST_TASK_WDT:
|
||||||
|
reset_reason_str = "TASK_WDT";
|
||||||
|
break;
|
||||||
|
case ESP_RST_WDT:
|
||||||
|
reset_reason_str = "WDT";
|
||||||
|
break;
|
||||||
|
case ESP_RST_DEEPSLEEP:
|
||||||
|
reset_reason_str = "DEEPSLEEP";
|
||||||
|
break;
|
||||||
|
case ESP_RST_BROWNOUT:
|
||||||
|
reset_reason_str = "BROWNOUT";
|
||||||
|
break;
|
||||||
|
case ESP_RST_SDIO:
|
||||||
|
reset_reason_str = "SDIO";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create JSON object
|
||||||
|
uint8_t mac[6];
|
||||||
|
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||||
|
cJSON *root = cJSON_CreateObject();
|
||||||
|
char mac_str[18];
|
||||||
|
snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
cJSON_AddStringToObject(root, "device_id", mac_str);
|
||||||
|
cJSON_AddNumberToObject(root, "uptime", uptime_ms);
|
||||||
|
cJSON_AddStringToObject(root, "timestamp", timestamp);
|
||||||
|
cJSON_AddStringToObject(root, "firmware", firmware);
|
||||||
|
cJSON_AddStringToObject(root, "reset_reason", reset_reason_str);
|
||||||
|
|
||||||
|
// Insert message as JSON object if possible
|
||||||
|
char topic_with_type[128];
|
||||||
|
strncpy(topic_with_type, "", sizeof(topic_with_type));
|
||||||
|
topic_with_type[sizeof(topic_with_type) - 1] = '\0';
|
||||||
|
|
||||||
|
cJSON *msg_obj = cJSON_Parse(message);
|
||||||
|
if (msg_obj)
|
||||||
|
{
|
||||||
|
cJSON *type_item = cJSON_DetachItemFromObject(msg_obj, "type");
|
||||||
|
if (type_item && cJSON_IsString(type_item))
|
||||||
|
{
|
||||||
|
// Extend topic
|
||||||
|
strncat(topic_with_type, type_item->valuestring, sizeof(topic_with_type) - strlen(topic_with_type) - 1);
|
||||||
|
}
|
||||||
|
cJSON_AddItemToObject(root, "message", msg_obj);
|
||||||
|
cJSON_Delete(type_item); // Free memory
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cJSON_AddStringToObject(root, "message", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish JSON via MQTT
|
||||||
|
char *json_str = cJSON_PrintUnformatted(root);
|
||||||
|
mqtt_client_publish(topic_with_type, json_str, strlen(json_str), 0, true);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
free(json_str);
|
||||||
|
}
|
||||||
|
|
||||||
void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain)
|
void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain)
|
||||||
{
|
{
|
||||||
if (client)
|
if (client)
|
||||||
{
|
{
|
||||||
int msg_id = esp_mqtt_client_publish(client, topic, data, len, qos, retain);
|
char base_topic[DEVICE_TOPIC_MAX_LEN];
|
||||||
ESP_LOGI(TAG, "Publish: topic=%s, msg_id=%d, qos=%d, retain=%d, len=%d", topic, msg_id, qos, retain, (int)len);
|
get_device_topic(base_topic, sizeof(base_topic));
|
||||||
|
char full_topic[DEVICE_TOPIC_MAX_LEN + 64];
|
||||||
|
snprintf(full_topic, sizeof(full_topic), "%s/%s", base_topic, topic);
|
||||||
|
|
||||||
|
int msg_id = esp_mqtt_client_publish(client, full_topic, data, len, qos, retain);
|
||||||
|
ESP_LOGV(TAG, "Publish: topic=%s, msg_id=%d, qos=%d, retain=%d, len=%d", full_topic, msg_id, qos, retain,
|
||||||
|
(int)len);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "led_strip_ws2812.h"
|
#include "led_strip_ws2812.h"
|
||||||
#include "message_manager.h"
|
#include "message_manager.h"
|
||||||
#include "persistence_manager.h"
|
#include "persistence_manager.h"
|
||||||
|
#include "simulator.h"
|
||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
@@ -15,28 +16,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const char *TAG = "simulator";
|
// Type definitions
|
||||||
static char *time = NULL;
|
|
||||||
|
|
||||||
static char *time_to_string(int hhmm)
|
|
||||||
{
|
|
||||||
static char buffer[20];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%02d:%02d", hhmm / 100, hhmm % 100);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TaskHandle_t simulation_task_handle = NULL;
|
|
||||||
static SemaphoreHandle_t simulation_mutex = NULL;
|
|
||||||
|
|
||||||
static void ensure_mutex_initialized(void)
|
|
||||||
{
|
|
||||||
if (simulation_mutex == NULL)
|
|
||||||
{
|
|
||||||
simulation_mutex = xSemaphoreCreateMutex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The struct is extended with a 'next' pointer to form a linked list.
|
|
||||||
typedef struct light_item_node_t
|
typedef struct light_item_node_t
|
||||||
{
|
{
|
||||||
char time[5];
|
char time[5];
|
||||||
@@ -46,23 +26,42 @@ typedef struct light_item_node_t
|
|||||||
struct light_item_node_t *next;
|
struct light_item_node_t *next;
|
||||||
} light_item_node_t;
|
} light_item_node_t;
|
||||||
|
|
||||||
// Global pointers for the head and tail of the list.
|
|
||||||
static light_item_node_t *head = NULL;
|
|
||||||
static light_item_node_t *tail = NULL;
|
|
||||||
|
|
||||||
// Interpolation mode selection
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
INTERPOLATION_RGB,
|
INTERPOLATION_RGB,
|
||||||
INTERPOLATION_HSV
|
INTERPOLATION_HSV
|
||||||
} interpolation_mode_t;
|
} interpolation_mode_t;
|
||||||
|
|
||||||
// You can change this to test different interpolation methods
|
// Constants and global variables
|
||||||
|
static const char *TAG = "simulator";
|
||||||
|
static char *time = NULL;
|
||||||
|
static TaskHandle_t simulation_task_handle = NULL;
|
||||||
|
static SemaphoreHandle_t simulation_mutex = NULL;
|
||||||
|
static light_item_node_t *head = NULL;
|
||||||
static const interpolation_mode_t interpolation_mode = INTERPOLATION_RGB;
|
static const interpolation_mode_t interpolation_mode = INTERPOLATION_RGB;
|
||||||
|
|
||||||
char *get_time(void)
|
// Helper function: converts hhmm format to minutes of the day
|
||||||
|
static int hhmm_to_minutes(const char time[5])
|
||||||
{
|
{
|
||||||
return time;
|
int t = atoi(time);
|
||||||
|
return (t / 100) * 60 + (t % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function: converts int hhmm to string
|
||||||
|
static char *time_to_string(int hhmm)
|
||||||
|
{
|
||||||
|
static char buffer[20];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02d:%02d", hhmm / 100, hhmm % 100);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function: ensures mutex is initialized
|
||||||
|
static void ensure_mutex_initialized(void)
|
||||||
|
{
|
||||||
|
if (simulation_mutex == NULL)
|
||||||
|
{
|
||||||
|
simulation_mutex = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main interpolation function that selects the appropriate method
|
// Main interpolation function that selects the appropriate method
|
||||||
@@ -78,10 +77,11 @@ static rgb_t interpolate_color(rgb_t start, rgb_t end, float factor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Linked list management
|
||||||
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white,
|
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white,
|
||||||
uint8_t brightness, uint8_t saturation)
|
uint8_t brightness, uint8_t saturation)
|
||||||
{
|
{
|
||||||
// Allocate memory for a new node in PSRAM.
|
// Allocate memory for new node in PSRAM.
|
||||||
light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_DEFAULT);
|
light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_DEFAULT);
|
||||||
if (new_node == NULL)
|
if (new_node == NULL)
|
||||||
{
|
{
|
||||||
@@ -106,18 +106,22 @@ esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t
|
|||||||
new_node->blue = (uint8_t)(color.blue * brightness_factor);
|
new_node->blue = (uint8_t)(color.blue * brightness_factor);
|
||||||
new_node->next = NULL;
|
new_node->next = NULL;
|
||||||
|
|
||||||
// Append the new node to the end of the list.
|
// Insert sorted: find the correct position
|
||||||
if (head == NULL)
|
if (head == NULL || hhmm_to_minutes(new_node->time) < hhmm_to_minutes(head->time))
|
||||||
{
|
{
|
||||||
// If the list is empty, the new node becomes both head and tail.
|
// New head
|
||||||
|
new_node->next = head;
|
||||||
head = new_node;
|
head = new_node;
|
||||||
tail = new_node;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Otherwise, append the new node to the end and update tail.
|
light_item_node_t *prev = head;
|
||||||
tail->next = new_node;
|
while (prev->next != NULL && hhmm_to_minutes(prev->next->time) < hhmm_to_minutes(new_node->time))
|
||||||
tail = new_node;
|
{
|
||||||
|
prev = prev->next;
|
||||||
|
}
|
||||||
|
new_node->next = prev->next;
|
||||||
|
prev->next = new_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -136,7 +140,6 @@ void cleanup_light_items(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
head = NULL;
|
head = NULL;
|
||||||
tail = NULL;
|
|
||||||
ESP_LOGI(TAG, "Cleaned up all light items.");
|
ESP_LOGI(TAG, "Cleaned up all light items.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +156,8 @@ static void initialize_light_items(void)
|
|||||||
load_file(filename);
|
load_file(filename);
|
||||||
persistence_manager_deinit(&persistence);
|
persistence_manager_deinit(&persistence);
|
||||||
|
|
||||||
|
// The list is now sorted because add_light_item inserts sorted
|
||||||
|
|
||||||
if (head == NULL)
|
if (head == NULL)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "Light schedule is empty. Simulation will not run.");
|
ESP_LOGW(TAG, "Light schedule is empty. Simulation will not run.");
|
||||||
@@ -199,43 +204,7 @@ static light_item_node_t *find_best_light_item_for_time(int hhmm)
|
|||||||
return best_item;
|
return best_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
static light_item_node_t *find_next_light_item_for_time(int hhmm)
|
// Messaging
|
||||||
{
|
|
||||||
light_item_node_t *current = head;
|
|
||||||
light_item_node_t *next_item = NULL;
|
|
||||||
int next_time = 9999; // Initialize with a value larger than any possible time
|
|
||||||
|
|
||||||
// First pass: find the soonest time after hhmm
|
|
||||||
while (current != NULL)
|
|
||||||
{
|
|
||||||
int current_time = atoi(current->time);
|
|
||||||
if (current_time > hhmm && current_time < next_time)
|
|
||||||
{
|
|
||||||
next_time = current_time;
|
|
||||||
next_item = current;
|
|
||||||
}
|
|
||||||
current = current->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no item is found for the rest of the day, wrap around to the beginning of the next day
|
|
||||||
if (next_item == NULL)
|
|
||||||
{
|
|
||||||
current = head;
|
|
||||||
next_time = 9999;
|
|
||||||
while (current != NULL)
|
|
||||||
{
|
|
||||||
int current_time = atoi(current->time);
|
|
||||||
if (current_time < next_time)
|
|
||||||
{
|
|
||||||
next_time = current_time;
|
|
||||||
next_item = current;
|
|
||||||
}
|
|
||||||
current = current->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return next_item;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void send_simulation_message(const char *time, rgb_t color)
|
static void send_simulation_message(const char *time, rgb_t color)
|
||||||
{
|
{
|
||||||
message_t msg = {};
|
message_t msg = {};
|
||||||
@@ -247,6 +216,12 @@ static void send_simulation_message(const char *time, rgb_t color)
|
|||||||
message_manager_post(&msg, pdMS_TO_TICKS(100));
|
message_manager_post(&msg, pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
char *get_time(void)
|
||||||
|
{
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
void start_simulate_day(void)
|
void start_simulate_day(void)
|
||||||
{
|
{
|
||||||
initialize_light_items();
|
initialize_light_items();
|
||||||
@@ -308,49 +283,55 @@ void simulate_cycle(void *args)
|
|||||||
time = time_to_string(hhmm);
|
time = time_to_string(hhmm);
|
||||||
|
|
||||||
light_item_node_t *current_item = find_best_light_item_for_time(hhmm);
|
light_item_node_t *current_item = find_best_light_item_for_time(hhmm);
|
||||||
light_item_node_t *next_item = find_next_light_item_for_time(hhmm);
|
light_item_node_t *next_item = NULL;
|
||||||
|
|
||||||
if (current_item != NULL)
|
if (current_item != NULL)
|
||||||
{
|
{
|
||||||
rgb_t color = {0, 0, 0};
|
rgb_t color = {0, 0, 0};
|
||||||
|
|
||||||
// Use head as fallback if next_item is NULL
|
// Cyclic interpolation: if current_item is the tail element, set next_item to head
|
||||||
next_item = next_item ? next_item : head;
|
if (current_item->next == NULL && head != NULL)
|
||||||
|
{
|
||||||
|
next_item = head;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
next_item = current_item->next;
|
||||||
|
}
|
||||||
|
|
||||||
if (next_item != NULL)
|
if (next_item != NULL)
|
||||||
{
|
{
|
||||||
int current_item_time_min = (atoi(current_item->time) / 100) * 60 + (atoi(current_item->time) % 100);
|
int current_minutes = hhmm_to_minutes(current_item->time);
|
||||||
int next_item_time_min = (atoi(next_item->time) / 100) * 60 + (atoi(next_item->time) % 100);
|
int next_minutes = hhmm_to_minutes(next_item->time);
|
||||||
|
|
||||||
if (next_item_time_min < current_item_time_min)
|
// Cyclic transition: if next_minutes < current_minutes, add day length
|
||||||
|
if (next_minutes < current_minutes)
|
||||||
{
|
{
|
||||||
next_item_time_min += total_minutes_in_day;
|
next_minutes += total_minutes_in_day;
|
||||||
}
|
}
|
||||||
|
|
||||||
int minutes_since_current_item_start = current_minute_of_day - current_item_time_min;
|
int minutes_since_current = current_minute_of_day - current_minutes;
|
||||||
if (minutes_since_current_item_start < 0)
|
if (minutes_since_current < 0)
|
||||||
{
|
{
|
||||||
minutes_since_current_item_start += total_minutes_in_day;
|
minutes_since_current += total_minutes_in_day;
|
||||||
}
|
}
|
||||||
|
|
||||||
int interval_duration = next_item_time_min - current_item_time_min;
|
int interval = next_minutes - current_minutes;
|
||||||
if (interval_duration == 0)
|
if (interval == 0)
|
||||||
{
|
{
|
||||||
interval_duration = 1;
|
interval = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
float interpolation_factor = (float)minutes_since_current_item_start / (float)interval_duration;
|
float factor = (float)minutes_since_current / (float)interval;
|
||||||
|
|
||||||
// Prepare colors for interpolation
|
|
||||||
rgb_t start_rgb = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
|
rgb_t start_rgb = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
|
||||||
rgb_t end_rgb = {.red = next_item->red, .green = next_item->green, .blue = next_item->blue};
|
rgb_t end_rgb = {.red = next_item->red, .green = next_item->green, .blue = next_item->blue};
|
||||||
|
|
||||||
// Use the interpolation function
|
color = interpolate_color(start_rgb, end_rgb, factor);
|
||||||
color = interpolate_color(start_rgb, end_rgb, interpolation_factor);
|
|
||||||
led_strip_update(LED_STATE_SIMULATION, color);
|
led_strip_update(LED_STATE_SIMULATION, color);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No next_item and no head, use only current
|
|
||||||
color = (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
|
color = (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
|
||||||
led_strip_update(LED_STATE_SIMULATION, color);
|
led_strip_update(LED_STATE_SIMULATION, color);
|
||||||
}
|
}
|
||||||
@@ -402,7 +383,7 @@ void stop_simulation_task(void)
|
|||||||
simulation_task_handle = NULL;
|
simulation_task_handle = NULL;
|
||||||
xSemaphoreGive(simulation_mutex);
|
xSemaphoreGive(simulation_mutex);
|
||||||
|
|
||||||
// Prüfe ob der Task noch existiert bevor er gelöscht wird
|
// Check if the task still exists before deleting it
|
||||||
eTaskState state = eTaskGetState(handle_to_delete);
|
eTaskState state = eTaskGetState(handle_to_delete);
|
||||||
if (state != eDeleted && state != eInvalid)
|
if (state != eDeleted && state != eInvalid)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ idf_component_register(SRCS
|
|||||||
src/app_task.cpp
|
src/app_task.cpp
|
||||||
src/button_handling.c
|
src/button_handling.c
|
||||||
src/i2c_checker.c
|
src/i2c_checker.c
|
||||||
|
src/u8g2_mqtt.cpp
|
||||||
src/hal/u8g2_esp32_hal.c
|
src/hal/u8g2_esp32_hal.c
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
PRIV_REQUIRES
|
PRIV_REQUIRES
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ dependencies:
|
|||||||
git: https://github.com/olikraus/u8g2.git
|
git: https://github.com/olikraus/u8g2.git
|
||||||
# u8g2_hal:
|
# u8g2_hal:
|
||||||
# git: https://github.com/mkfrey/u8g2-hal-esp-idf.git
|
# git: https://github.com/mkfrey/u8g2-hal-esp-idf.git
|
||||||
espressif/button: ^4.1.4
|
espressif/button: ^4.1.6
|
||||||
espressif/esp_insights: ^1.2.7
|
espressif/esp_insights: ^1.3.2
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
extern QueueHandle_t display_mqtt_queue;
|
||||||
|
|
||||||
|
void u8g2_mqtt_task(void *pvParameters);
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "my_mqtt_client.h"
|
#include "my_mqtt_client.h"
|
||||||
#include "persistence_manager.h"
|
#include "persistence_manager.h"
|
||||||
#include "simulator.h"
|
#include "simulator.h"
|
||||||
|
#include "u8g2_mqtt.h"
|
||||||
#include "ui/ClockScreenSaver.h"
|
#include "ui/ClockScreenSaver.h"
|
||||||
#include "ui/ScreenSaver.h"
|
#include "ui/ScreenSaver.h"
|
||||||
#include "ui/SplashScreen.h"
|
#include "ui/SplashScreen.h"
|
||||||
@@ -33,6 +34,7 @@ u8g2_t u8g2;
|
|||||||
uint8_t last_value = 0;
|
uint8_t last_value = 0;
|
||||||
menu_options_t options;
|
menu_options_t options;
|
||||||
uint8_t received_signal;
|
uint8_t received_signal;
|
||||||
|
uint64_t last_mqtt_sync = 0;
|
||||||
|
|
||||||
std::shared_ptr<Widget> m_widget;
|
std::shared_ptr<Widget> m_widget;
|
||||||
std::vector<std::shared_ptr<Widget>> m_history;
|
std::vector<std::shared_ptr<Widget>> m_history;
|
||||||
@@ -148,27 +150,27 @@ static void handle_button(uint8_t button)
|
|||||||
{
|
{
|
||||||
switch (button)
|
switch (button)
|
||||||
{
|
{
|
||||||
case 1:
|
case CONFIG_BUTTON_UP:
|
||||||
m_widget->OnButtonClicked(ButtonType::UP);
|
m_widget->OnButtonClicked(ButtonType::UP);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case CONFIG_BUTTON_LEFT:
|
||||||
m_widget->OnButtonClicked(ButtonType::LEFT);
|
m_widget->OnButtonClicked(ButtonType::LEFT);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 5:
|
case CONFIG_BUTTON_RIGHT:
|
||||||
m_widget->OnButtonClicked(ButtonType::RIGHT);
|
m_widget->OnButtonClicked(ButtonType::RIGHT);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case CONFIG_BUTTON_DOWN:
|
||||||
m_widget->OnButtonClicked(ButtonType::DOWN);
|
m_widget->OnButtonClicked(ButtonType::DOWN);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 16:
|
case CONFIG_BUTTON_BACK:
|
||||||
m_widget->OnButtonClicked(ButtonType::BACK);
|
m_widget->OnButtonClicked(ButtonType::BACK);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 18:
|
case CONFIG_BUTTON_SELECT:
|
||||||
m_widget->OnButtonClicked(ButtonType::SELECT);
|
m_widget->OnButtonClicked(ButtonType::SELECT);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -259,6 +261,10 @@ void app_task(void *args)
|
|||||||
|
|
||||||
start_simulation();
|
start_simulation();
|
||||||
|
|
||||||
|
display_mqtt_queue = xQueueCreate(1, 1024);
|
||||||
|
|
||||||
|
xTaskCreatePinnedToCore(u8g2_mqtt_task, "mqtt_disp", 4096, nullptr, 5, nullptr, tskNO_AFFINITY);
|
||||||
|
|
||||||
auto oldTime = esp_timer_get_time();
|
auto oldTime = esp_timer_get_time();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@@ -279,6 +285,15 @@ void app_task(void *args)
|
|||||||
m_inactivityTracker->update(deltaMs);
|
m_inactivityTracker->update(deltaMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MQTT
|
||||||
|
auto now = esp_timer_get_time();
|
||||||
|
if (now - last_mqtt_sync > 1000000)
|
||||||
|
{
|
||||||
|
uint8_t *u8g2_buf = u8g2_GetBufferPtr(&u8g2);
|
||||||
|
xQueueOverwrite(display_mqtt_queue, u8g2_buf);
|
||||||
|
last_mqtt_sync = now;
|
||||||
|
}
|
||||||
|
|
||||||
u8g2_SendBuffer(&u8g2);
|
u8g2_SendBuffer(&u8g2);
|
||||||
|
|
||||||
if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE)
|
if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "persistence_manager.h"
|
#include "persistence_manager.h"
|
||||||
#include "wifi_manager.h"
|
#include "wifi_manager.h"
|
||||||
#include <ble_manager.h>
|
#include <ble_manager.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
#include <esp_event.h>
|
#include <esp_event.h>
|
||||||
#include <esp_insights.h>
|
#include <esp_insights.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
@@ -13,9 +14,31 @@
|
|||||||
#include <nvs_flash.h>
|
#include <nvs_flash.h>
|
||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
|
|
||||||
|
#define WIFI_ENABLE GPIO_NUM_3
|
||||||
|
#define WIFI_ANT_CONFIG GPIO_NUM_14
|
||||||
|
|
||||||
__BEGIN_DECLS
|
__BEGIN_DECLS
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||||
|
// GPIO für WiFi Enable konfigurieren
|
||||||
|
gpio_config_t io_conf = {.pin_bit_mask = (1ULL << WIFI_ENABLE),
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE};
|
||||||
|
gpio_config(&io_conf);
|
||||||
|
gpio_set_level(WIFI_ENABLE, 0); // LOW
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
|
||||||
|
// GPIO for WiFi antenna configuration
|
||||||
|
io_conf.pin_bit_mask = (1ULL << WIFI_ANT_CONFIG);
|
||||||
|
gpio_config(&io_conf);
|
||||||
|
gpio_set_level(WIFI_ANT_CONFIG, 1); // HIGH
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialize NVS
|
// Initialize NVS
|
||||||
esp_err_t err = nvs_flash_init();
|
esp_err_t err = nvs_flash_init();
|
||||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#include "u8g2_mqtt.h"
|
||||||
|
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include <my_mqtt_client.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <u8g2.h>
|
||||||
|
|
||||||
|
#define BUFFER_SIZE (128 * 64 / 8)
|
||||||
|
QueueHandle_t display_mqtt_queue = nullptr;
|
||||||
|
|
||||||
|
void u8g2_mqtt_task(void *pvParameters)
|
||||||
|
{
|
||||||
|
static uint8_t current_buffer[BUFFER_SIZE];
|
||||||
|
static uint8_t previous_buffer[BUFFER_SIZE] = {0};
|
||||||
|
static uint8_t mqtt_payload[BUFFER_SIZE * 2 + 1];
|
||||||
|
|
||||||
|
uint64_t last_keyframe_time = 0;
|
||||||
|
const uint64_t KEYFRAME_INTERVAL_US = 5000000; // 5 seconds in microseconds
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Blocks without CPU load until app_task provides a frame
|
||||||
|
if (xQueueReceive(display_mqtt_queue, current_buffer, portMAX_DELAY) == pdTRUE)
|
||||||
|
{
|
||||||
|
int payload_size = 0;
|
||||||
|
uint64_t current_time = esp_timer_get_time();
|
||||||
|
|
||||||
|
// Time-based I-frame decision (or on initial start)
|
||||||
|
bool is_keyframe = (current_time - last_keyframe_time >= KEYFRAME_INTERVAL_US) || (last_keyframe_time == 0);
|
||||||
|
|
||||||
|
if (is_keyframe)
|
||||||
|
{
|
||||||
|
mqtt_payload[payload_size++] = 0x01; // Header: I-frame
|
||||||
|
last_keyframe_time = current_time;
|
||||||
|
|
||||||
|
for (int i = 0; i < BUFFER_SIZE;)
|
||||||
|
{
|
||||||
|
uint8_t count = 1;
|
||||||
|
while (i + count < BUFFER_SIZE && current_buffer[i] == current_buffer[i + count] && count < 255)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
mqtt_payload[payload_size++] = count;
|
||||||
|
mqtt_payload[payload_size++] = current_buffer[i];
|
||||||
|
i += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mqtt_payload[payload_size++] = 0x00; // Header: P-frame (Diff)
|
||||||
|
uint8_t xor_buffer[BUFFER_SIZE];
|
||||||
|
|
||||||
|
for (int i = 0; i < BUFFER_SIZE; i++)
|
||||||
|
{
|
||||||
|
xor_buffer[i] = current_buffer[i] ^ previous_buffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < BUFFER_SIZE;)
|
||||||
|
{
|
||||||
|
uint8_t count = 1;
|
||||||
|
while (i + count < BUFFER_SIZE && xor_buffer[i] == xor_buffer[i + count] && count < 255)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
mqtt_payload[payload_size++] = count;
|
||||||
|
mqtt_payload[payload_size++] = xor_buffer[i];
|
||||||
|
i += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- MQTT SEND ---
|
||||||
|
mqtt_client_publish("stream", (char *)mqtt_payload, payload_size, 0, false);
|
||||||
|
|
||||||
|
memcpy(previous_buffer, current_buffer, BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,24 @@ CONFIG_IDF_TARGET="esp32c6"
|
|||||||
#
|
#
|
||||||
# Display Settings
|
# Display Settings
|
||||||
#
|
#
|
||||||
CONFIG_DISPLAY_SDA_PIN=9
|
CONFIG_DISPLAY_SDA_PIN=22
|
||||||
CONFIG_DISPLAY_SCL_PIN=8
|
CONFIG_DISPLAY_SCL_PIN=23
|
||||||
# end of Display Settings
|
# end of Display Settings
|
||||||
|
|
||||||
#
|
#
|
||||||
# Button Configuration
|
# Button Configuration
|
||||||
#
|
#
|
||||||
CONFIG_BUTTON_UP=7
|
CONFIG_BUTTON_UP=18
|
||||||
CONFIG_BUTTON_DOWN=4
|
CONFIG_BUTTON_DOWN=17
|
||||||
CONFIG_BUTTON_LEFT=6
|
CONFIG_BUTTON_LEFT=20
|
||||||
CONFIG_BUTTON_RIGHT=5
|
CONFIG_BUTTON_RIGHT=19
|
||||||
CONFIG_BUTTON_SELECT=19
|
CONFIG_BUTTON_SELECT=1
|
||||||
CONFIG_BUTTON_BACK=20
|
CONFIG_BUTTON_BACK=2
|
||||||
# end of Button Configuration
|
# end of Button Configuration
|
||||||
|
|
||||||
|
CONFIG_WLED_DIN_PIN=21
|
||||||
|
CONFIG_STATUS_WLED_PIN=16
|
||||||
|
|
||||||
|
CONFIG_API_SERVER_HOSTNAME="system-control"
|
||||||
|
|
||||||
|
CONFIG_LWIP_MAX_SOCKETS=20
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
# default ESP target
|
# default ESP target
|
||||||
CONFIG_IDF_TARGET="esp32s3"
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
|
|
||||||
|
CONFIG_API_SERVER_HOSTNAME="system-client"
|
||||||
|
|||||||
@@ -3,4 +3,6 @@ CONFIG_APP_REPRODUCIBLE_BUILD=y
|
|||||||
|
|
||||||
# Compiler options
|
# Compiler options
|
||||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||||
|
# CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
|
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
|
||||||
|
CONFIG_DEBUG_INFO=n
|
||||||
|
|||||||
@@ -46,8 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-primary" id="connect-btn" onclick="saveWifi()" data-i18n="captive.connect"
|
<button class="btn btn-primary" id="connect-btn" onclick="saveWifi()" data-i18n="captive.connect">
|
||||||
disabled>
|
|
||||||
💾 Verbinden
|
💾 Verbinden
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user