move firmware into subfolder
Some checks failed
ESP-IDF Build / build (esp32, latest) (push) Failing after 40s
ESP-IDF Build / build (esp32, release-v5.4) (push) Failing after 20s
ESP-IDF Build / build (esp32, release-v5.5) (push) Failing after 18s
ESP-IDF Build / build (esp32c3, latest) (push) Failing after 17s
ESP-IDF Build / build (esp32c3, release-v5.4) (push) Failing after 15s
ESP-IDF Build / build (esp32c3, release-v5.5) (push) Failing after 15s
ESP-IDF Build / build (esp32c5, latest) (push) Failing after 16s
ESP-IDF Build / build (esp32c5, release-v5.4) (push) Failing after 17s
ESP-IDF Build / build (esp32c5, release-v5.5) (push) Failing after 17s
ESP-IDF Build / build (esp32c6, latest) (push) Failing after 16s
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 17s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 16s
ESP-IDF Build / build (esp32h2, latest) (push) Failing after 14s
ESP-IDF Build / build (esp32h2, release-v5.4) (push) Failing after 16s
ESP-IDF Build / build (esp32h2, release-v5.5) (push) Failing after 16s
ESP-IDF Build / build (esp32p4, latest) (push) Failing after 17s
ESP-IDF Build / build (esp32p4, release-v5.4) (push) Failing after 16s
ESP-IDF Build / build (esp32p4, release-v5.5) (push) Failing after 16s
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 16s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 16s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 16s

Signed-off-by: Peter Siegmund <mars3142@users.noreply.github.com>
This commit is contained in:
2025-08-21 00:02:56 +02:00
parent b340cf4492
commit 197a1611f5
49 changed files with 48 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
idf_component_register(SRCS
"beacon.c"
"light.c"
"outdoor.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
esp_driver_gptimer
)

View File

@@ -0,0 +1,155 @@
#include "beacon.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "light.h"
#include "sdkconfig.h"
#include "semaphore.h"
static const char *TAG = "beacon";
static SemaphoreHandle_t timer_semaphore;
gptimer_handle_t gptimer = NULL;
static const uint32_t value = 200;
static bool IRAM_ATTR beacon_timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata,
void *userCtx)
{
BaseType_t high_task_wakeup = pdFALSE;
xSemaphoreGiveFromISR(timer_semaphore, &high_task_wakeup);
if (high_task_wakeup)
{
portYIELD_FROM_ISR();
}
return true;
}
static void beacon_timer_event_task(void *arg)
{
while (true)
{
if (xSemaphoreTake(timer_semaphore, portMAX_DELAY))
{
LedMatrix_t led_matrix = get_led_matrix();
static bool level = false;
level = !level;
if (led_matrix.led_strip)
{
for (uint32_t i = 0; i < led_matrix.size; i++)
{
led_strip_set_pixel(led_matrix.led_strip, i, 0, (level) ? value : 0, 0);
}
led_strip_refresh(led_matrix.led_strip);
}
ESP_LOGD(TAG, "Timer Event, LED now %s", level ? "ON" : "OFF");
}
}
}
esp_err_t beacon_start(void)
{
if (gptimer == NULL)
{
ESP_LOGE(TAG, "GPTimer not initialized");
return ESP_ERR_INVALID_STATE;
}
esp_err_t ret = gptimer_start(gptimer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to start gptimer: %s", esp_err_to_name(ret));
}
else
{
ESP_LOGI(TAG, "GPTimer started.");
}
return ret;
}
esp_err_t beacon_stop(void)
{
if (gptimer == NULL)
{
ESP_LOGE(TAG, "GPTimer not initialized");
return ESP_ERR_INVALID_STATE;
}
esp_err_t ret = gptimer_stop(gptimer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to stop gptimer: %s", esp_err_to_name(ret));
}
else
{
ESP_LOGI(TAG, "GPTimer stopped.");
}
return ret;
}
esp_err_t beacon_init(void)
{
esp_err_t ret = ESP_OK;
timer_semaphore = xSemaphoreCreateBinary();
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, .direction = GPTIMER_COUNT_UP, .resolution_hz = 1000000};
ret = gptimer_new_timer(&timer_config, &gptimer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to create new gptimer: %s", esp_err_to_name(ret));
goto exit;
}
gptimer_event_callbacks_t callbacks = {.on_alarm = beacon_timer_callback};
ret = gptimer_register_event_callbacks(gptimer, &callbacks, NULL);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to register timer callbacks: %s", esp_err_to_name(ret));
goto cleanupTimer;
}
ret = gptimer_enable(gptimer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to enable gptimer: %s", esp_err_to_name(ret));
goto cleanupTimer;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 2000000, .reload_count = 0, .flags.auto_reload_on_alarm = true};
ret = gptimer_set_alarm_action(gptimer, &alarm_config);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set gptimer alarm action: %s", esp_err_to_name(ret));
goto cleanupEnabledTimer;
}
BaseType_t task_created = xTaskCreate(beacon_timer_event_task, "beacon_timer_event_task", 4096, NULL, 10, NULL);
if (task_created != pdPASS)
{
ESP_LOGE(TAG, "Failed to create timer event task");
ret = ESP_ERR_NO_MEM;
goto cleanupEnabledTimer;
}
ESP_LOGI(TAG, "Beacon module initialized.");
goto exit;
cleanupEnabledTimer:
if (gptimer)
{
gptimer_disable(gptimer);
}
cleanupTimer:
if (gptimer)
{
gptimer_del_timer(gptimer);
gptimer = NULL;
}
exit:
return ret;
}

View File

@@ -0,0 +1,17 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=5.4.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/led_strip: '~3.0.0'

View File

@@ -0,0 +1,39 @@
#pragma once
#include "esp_err.h"
/**
* @brief Initializes the beacon module.
*
* This function sets up the beacon module, configuring it for subsequent operations
* such as starting or stopping the broadcast functionality.
*
* @return
* - ESP_OK: Initialization completed successfully.
* - Error codes in case of failure, indicating the specific issue.
*/
esp_err_t beacon_init(void);
/**
* @brief Starts the beacon functionality for broadcasting signals.
*
* This function initiates the process required for starting the beacon.
* It ensures the beacon is prepared to transmit signals effectively.
*
* @return
* - ESP_OK: Beacon started successfully.
* - Error codes in case of failure, indicating the specific issue.
*/
esp_err_t beacon_start(void);
/**
* @brief Stops the beacon broadcasting functionality.
*
* This function terminates the ongoing broadcasting process of the beacon
* and releases any resources allocated during the operation.
*
* @return
* - ESP_OK: Broadcasting stopped successfully.
* - Error codes in case of failure, indicating the specific issue.
*/
esp_err_t beacon_stop(void);

View File

@@ -0,0 +1,27 @@
#pragma once
#include "beacon.h"
#include "led_strip.h"
#include "outdoor.h"
#include "esp_err.h"
typedef struct
{
led_strip_handle_t led_strip;
uint32_t size;
} LedMatrix_t;
LedMatrix_t get_led_matrix(void);
/**
* @brief Initializes the WLED module.
*
* This function configures the WLED module for operation, preparing it for subsequent
* usage such as enabling lighting effects or communication.
*
* @return
* - ESP_OK: Initialization completed successfully.
* - Error codes in case of failure, indicating the specific issue.
*/
esp_err_t wled_init(void);

View File

@@ -0,0 +1,9 @@
#pragma once
#include "esp_err.h"
esp_err_t outdoor_init(void);
esp_err_t outdoor_start(void);
esp_err_t outdoor_stop(void);

View File

@@ -0,0 +1,38 @@
#include "light.h"
#include "sdkconfig.h"
static LedMatrix_t led_matrix = {.size = 1};
LedMatrix_t get_led_matrix(void)
{
return led_matrix;
}
esp_err_t wled_init(void)
{
led_strip_config_t strip_config = {.strip_gpio_num = CONFIG_WLED_DIN_PIN,
.max_leds = led_matrix.size,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {
.invert_out = false,
}};
led_strip_rmt_config_t rmt_config = {.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 0,
.mem_block_symbols = 0,
.flags = {
.with_dma = CONFIG_WLED_DMA_USAGE,
}};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_matrix.led_strip));
for (uint32_t i = 0; i < led_matrix.size; i++)
{
led_strip_set_pixel(led_matrix.led_strip, i, 0, 0, 0);
}
led_strip_refresh(led_matrix.led_strip);
return ESP_OK;
}

View File

@@ -0,0 +1,16 @@
#include "outdoor.h"
esp_err_t outdoor_init(void)
{
return ESP_OK;
}
esp_err_t outdoor_start(void)
{
return ESP_OK;
}
esp_err_t outdoor_stop(void)
{
return ESP_OK;
}

View File

@@ -0,0 +1,5 @@
idf_component_register(SRCS "persistence.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
nvs_flash
)

View File

@@ -0,0 +1,12 @@
#pragma once
typedef enum
{
VALUE_TYPE_STRING,
VALUE_TYPE_INT32,
} persistence_value_type_t;
void persistence_init(const char *namespace_name);
void persistence_save(persistence_value_type_t value_type, const char *key, const void *value);
void *persistence_load(persistence_value_type_t value_type, const char *key, void *out);
void persistence_deinit();

View File

@@ -0,0 +1,114 @@
#include "persistence.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "nvs_flash.h"
static const char *TAG = "persistence";
static nvs_handle_t persistence_handle;
static SemaphoreHandle_t persistence_mutex;
void persistence_init(const char *namespace_name)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(nvs_open(namespace_name, NVS_READWRITE, &persistence_handle));
persistence_mutex = xSemaphoreCreateMutex();
if (persistence_mutex == NULL)
{
ESP_LOGE(TAG, "Failed to create mutex");
}
}
void persistence_save(persistence_value_type_t value_type, const char *key, const void *value)
{
if (persistence_mutex != NULL)
{
if (xSemaphoreTake(persistence_mutex, portMAX_DELAY) == pdTRUE)
{
esp_err_t err = ESP_ERR_INVALID_ARG;
switch (value_type)
{
case VALUE_TYPE_STRING:
err = nvs_set_str(persistence_handle, key, (char *)value);
break;
case VALUE_TYPE_INT32:
err = nvs_set_i32(persistence_handle, key, *(int32_t *)value);
break;
default:
ESP_LOGE(TAG, "Unsupported value type");
break;
}
if (err == ESP_OK)
{
ESP_ERROR_CHECK(nvs_commit(persistence_handle));
}
else
{
ESP_LOGE(TAG, "Error saving key %s: %s", key, esp_err_to_name(err));
}
xSemaphoreGive(persistence_mutex);
}
}
}
void *persistence_load(persistence_value_type_t value_type, const char *key, void *out)
{
if (persistence_mutex != NULL)
{
if (xSemaphoreTake(persistence_mutex, portMAX_DELAY) == pdTRUE)
{
esp_err_t err = ESP_ERR_INVALID_ARG;
switch (value_type)
{
case VALUE_TYPE_STRING:
err = nvs_get_str(persistence_handle, key, (char *)out, NULL);
break;
case VALUE_TYPE_INT32:
err = nvs_get_i32(persistence_handle, key, (int32_t *)out);
break;
default:
ESP_LOGE(TAG, "Unsupported value type");
break;
}
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Error loading key %s: %s", key, esp_err_to_name(err));
}
xSemaphoreGive(persistence_mutex);
}
}
return out;
}
void persistence_deinit()
{
if (persistence_mutex != NULL)
{
vSemaphoreDelete(persistence_mutex);
persistence_mutex = NULL;
}
nvs_close(persistence_handle);
}

View File

@@ -0,0 +1,11 @@
idf_component_register(SRCS
"capability_service.c"
"device_service.c"
"led_service.c"
"remote_control.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
bt
esp_app_format
storage
)

View File

@@ -0,0 +1,156 @@
#include "capability_service.h"
#include "esp_log.h"
#include "storage.h"
#include <string.h>
#include <stdlib.h> // For malloc, free
#include "host/ble_hs.h" // For ble_hs_mbuf_from_flat, ble_att_mtu
#include "host/ble_uuid.h" // For BLE_ATT_MTU_DFLT (often included via ble_hs.h)
#include "nimble/nimble_port.h" // For os_mbuf related functions
static const char *TAG_CS = "capability_service";
#define CAPA_READ_CHUNK_SIZE 200 // Maximale Bytes pro Lesevorgang aus dem Storage
int capa_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
esp_err_t storage_status = storage_init();
if (storage_status != ESP_OK)
{
ESP_LOGE(TAG_CS, "Failed to initialize storage: %s", esp_err_to_name(storage_status));
const char *err_msg = "Error: Storage init failed";
os_mbuf_append(ctxt->om, err_msg, strlen(err_msg));
// storage_uninit() sollte hier nicht aufgerufen werden, da die Initialisierung fehlschlug.
return 0;
}
char *content = "";
os_mbuf_append(ctxt->om, content, strlen(content));
const char *filename = "/storage/capability.json"; // Die zu lesende Datei
char read_buffer[CAPA_READ_CHUNK_SIZE];
ssize_t bytes_read;
int os_err;
ESP_LOGI(TAG_CS, "Reading capabilities from %s", filename);
// Schleife, um die Datei in Chunks zu lesen und an den mbuf anzuhängen
while ((bytes_read = storage_read(filename, read_buffer, sizeof(read_buffer))) > 0)
{
ESP_LOGD(TAG_CS, "Read %zd bytes from storage", bytes_read);
// Den gelesenen Chunk an den BLE-Antwortpuffer anhängen
os_err = os_mbuf_append(ctxt->om, read_buffer, bytes_read);
if (os_err != 0)
{
ESP_LOGE(TAG_CS, "Failed to append to mbuf (error %d). May be out of space.", os_err);
// Der mbuf könnte voll sein. Stoppe das Anhängen.
// Bereits angehängte Daten werden gesendet.
break;
}
}
// Fehlerbehandlung oder EOF
if (bytes_read < 0)
{
ESP_LOGE(TAG_CS, "Error reading from storage (file: %s, error_code: %zd)", filename, bytes_read);
// Wenn noch nichts angehängt wurde, sende eine Fehlermeldung.
if (ctxt->om->om_len == 0)
{
const char *err_msg = "Error: Failed to read capability data";
// Hier könnten spezifischere Fehlermeldungen basierend auf bytes_read eingefügt werden
os_mbuf_append(ctxt->om, err_msg, strlen(err_msg));
}
}
else
{ // bytes_read == 0, bedeutet EOF (Ende der Datei)
ESP_LOGI(TAG_CS, "Successfully read and appended all data from %s to mbuf.", filename);
}
storage_uninit();
return 0;
}
int capa_char_1979_user_desc(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
const char *desc = "Capabilities of the device";
os_mbuf_append(ctxt->om, desc, strlen(desc));
return 0;
}
void capa_notify_data(uint16_t conn_handle, uint16_t char_val_handle)
{
esp_err_t storage_status = storage_init();
if (storage_status != ESP_OK)
{
ESP_LOGE(TAG_CS, "Notify: Failed to initialize storage: %s", esp_err_to_name(storage_status));
return;
}
const char *filename = "/storage/capability.json";
uint16_t mtu = ble_att_mtu(conn_handle);
if (mtu == 0) { // Should not happen for an active connection, fallback
ESP_LOGW(TAG_CS, "Notify: ble_att_mtu returned 0, using default MTU %d.", BLE_ATT_MTU_DFLT);
mtu = BLE_ATT_MTU_DFLT;
}
// Max payload for notification is MTU - 3 (1 byte opcode for Notification, 2 bytes attribute handle)
size_t notify_chunk_size = (mtu > 3) ? (mtu - 3) : (BLE_ATT_MTU_DFLT - 3); // Ensure mtu > 3
// Further cap by CAPA_READ_CHUNK_SIZE if it's smaller and meant as an upper limit for any single read op
if (notify_chunk_size > CAPA_READ_CHUNK_SIZE) {
notify_chunk_size = CAPA_READ_CHUNK_SIZE;
}
if (notify_chunk_size == 0) { // Safety check
ESP_LOGE(TAG_CS, "Notify: Calculated notify_chunk_size is 0. Aborting.");
storage_uninit();
return;
}
char *read_buffer = malloc(notify_chunk_size);
if (!read_buffer)
{
ESP_LOGE(TAG_CS, "Notify: Failed to allocate read_buffer (%zu bytes)", notify_chunk_size);
storage_uninit();
return;
}
ESP_LOGI(TAG_CS, "Notify: Reading capabilities from %s to notify conn %u, attr %u (chunk size %zu, MTU %u)",
filename, conn_handle, char_val_handle, notify_chunk_size, mtu);
FILE *fp = fopen(filename, "r");
if (!fp)
{
ESP_LOGE(TAG_CS, "Notify: Failed to open %s for reading.", filename);
free(read_buffer);
storage_uninit();
return;
}
ssize_t bytes_read;
while ((bytes_read = fread(read_buffer, 1, notify_chunk_size, fp)) > 0)
{
ESP_LOGD(TAG_CS, "Notify: Read %zd bytes from storage for notification", bytes_read);
struct os_mbuf *om = ble_hs_mbuf_from_flat(read_buffer, bytes_read);
if (!om) {
ESP_LOGE(TAG_CS, "Notify: Failed to allocate mbuf for notification. Stopping.");
break; // Stop sending if mbuf allocation fails
}
int rc = ble_gatts_notify_custom(conn_handle, char_val_handle, om);
if (rc != 0) {
ESP_LOGE(TAG_CS, "Notify: Error sending notification (rc=%d). Stopping.", rc);
// ble_gatts_notify_custom frees 'om' on success or if it takes ownership even on some errors.
// If it returns an error where 'om' is not freed, we might need os_mbuf_free_chain(om);
// For now, assume NimBLE handles 'om' correctly in error cases like BLE_HS_ENOMEM.
break; // Stop if notification fails
}
ESP_LOGD(TAG_CS, "Notify: Sent %zd bytes successfully.", bytes_read);
}
if (ferror(fp)) { ESP_LOGE(TAG_CS, "Notify: File read error from %s.", filename); }
fclose(fp);
free(read_buffer);
storage_uninit();
ESP_LOGI(TAG_CS, "Notify: Finished sending capability data for conn %u.", conn_handle);
}

View File

@@ -0,0 +1,29 @@
#include "include/device_service.h"
#include "esp_app_desc.h"
#include <stdio.h>
#include <string.h>
int device_model_number_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
char model_number_str[65];
const esp_app_desc_t *app_desc = esp_app_get_description();
if (app_desc->project_name[0] != '\0' && app_desc->version[0] != '\0')
{
snprintf(model_number_str, sizeof(model_number_str), "%s v%s", app_desc->project_name, app_desc->version);
}
else
{
snprintf(model_number_str, sizeof(model_number_str), "undefined");
}
os_mbuf_append(ctxt->om, model_number_str, strlen(model_number_str));
return 0;
}
int device_manufacturer_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
char *manufacturer = "mars3142";
os_mbuf_append(ctxt->om, manufacturer, strlen(manufacturer));
return 0;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "host/ble_hs.h"
#include <stdio.h>
/// Service Characteristics Callback
int capa_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
void capa_notify_data(uint16_t conn_handle, uint16_t char_val_handle);
/// Service Characteristics User Description
int capa_char_1979_user_desc(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);

View File

@@ -0,0 +1,7 @@
#pragma once
#include "host/ble_hs.h"
#include <stdio.h>
int device_model_number_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
int device_manufacturer_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);

View File

@@ -0,0 +1,12 @@
#pragma once
#include "host/ble_hs.h"
#include <stdio.h>
/// LED Service Characteristic Callbacks
int led_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
int led_capabilities_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
/// LED Service Characteristic User Description
int led_char_a000_user_desc(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
int led_char_dead_user_desc(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);

View File

@@ -0,0 +1,3 @@
#pragma once
void remote_control_init(void);

View File

@@ -0,0 +1,75 @@
#include "include/led_service.h"
static const char *TAG = "led_service";
/// Capabilities of Device
int led_capabilities_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
char *data = "To be implemented later";
os_mbuf_append(ctxt->om, data, strlen(data));
return 0;
}
// Write data to ESP32 defined as server
int led_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
const char *received_payload = (const char *)ctxt->om->om_data;
uint16_t payload_len = ctxt->om->om_len;
// Define command strings
const char CMD_LIGHT_ON[] = "LIGHT ON";
const char CMD_LIGHT_OFF[] = "LIGHT OFF";
const char CMD_FAN_ON[] = "FAN ON";
const char CMD_FAN_OFF[] = "FAN OFF";
if (payload_len == (sizeof(CMD_LIGHT_ON) - 1) && strncmp(received_payload, CMD_LIGHT_ON, payload_len) == 0)
{
ESP_LOGI(TAG, "LIGHT ON");
// for (int i = 0; i < led_matrix_get_size(); i++)
{
// led_matrix_set_pixel(i, 10, 10, 0);
}
}
else if (payload_len == (sizeof(CMD_LIGHT_OFF) - 1) && strncmp(received_payload, CMD_LIGHT_OFF, payload_len) == 0)
{
ESP_LOGI(TAG, "LIGHT OFF");
// for (int i = 0; i < led_matrix_get_size(); i++)
{
// led_matrix_set_pixel(i, 0, 0, 0);
}
}
else if (payload_len == (sizeof(CMD_FAN_ON) - 1) && strncmp(received_payload, CMD_FAN_ON, payload_len) == 0)
{
ESP_LOGI(TAG, "FAN ON");
// TODO: Implement action for FAN ON
}
else if (payload_len == (sizeof(CMD_FAN_OFF) - 1) && strncmp(received_payload, CMD_FAN_OFF, payload_len) == 0)
{
ESP_LOGI(TAG, "FAN OFF");
// TODO: Implement action for FAN OFF
}
else
{
char temp_buffer[payload_len + 1];
memcpy(temp_buffer, received_payload, payload_len);
temp_buffer[payload_len] = '\0';
ESP_LOGI(TAG, "Unknown command from client: %s", temp_buffer);
}
return 0;
}
int led_char_a000_user_desc(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
const char *desc = "Capabilities of Device";
os_mbuf_append(ctxt->om, desc, strlen(desc));
return 0;
}
int led_char_dead_user_desc(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
const char *desc = "Readable Data from Server";
os_mbuf_append(ctxt->om, desc, strlen(desc));
return 0;
}

View File

@@ -0,0 +1,251 @@
#include <stdio.h>
#include <string.h>
#include "capability_service.h"
#include "esp_event.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "host/ble_hs.h"
#include "host/ble_sm.h"
#include "host/ble_uuid.h"
#include "include/device_service.h"
#include "include/led_service.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "sdkconfig.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
static const char *TAG = "remote_control";
static const ble_uuid16_t device_service_uuid = BLE_UUID16_INIT(0x180A);
static const ble_uuid128_t capability_service_uuid =
BLE_UUID128_INIT(0x91, 0xB6, 0xCA, 0x95, 0xB2, 0xC6, 0x7B, 0x90, 0x31, 0x45, 0x77, 0xE6, 0x67, 0x10, 0x68, 0xB9);
static const ble_uuid16_t led_service_uuid = BLE_UUID16_INIT(0x1007);
uint8_t ble_addr_type;
// Handle for the capability characteristic value
static uint16_t g_capa_char_val_handle;
static void ble_app_advertise(void);
static struct ble_gatt_dsc_def char_0xA000_descs[] = {{
.uuid = BLE_UUID16_DECLARE(0x2901),
.att_flags = BLE_ATT_F_READ,
.access_cb = led_char_a000_user_desc,
},
{0}};
static struct ble_gatt_dsc_def char_0xDEAD_descs[] = {{
.uuid = BLE_UUID16_DECLARE(0x2901),
.att_flags = BLE_ATT_F_WRITE,
.access_cb = led_char_dead_user_desc,
},
{0}};
static struct ble_gatt_dsc_def char_0x1979_desc[] = {{
.uuid = BLE_UUID16_DECLARE(0x2901),
.att_flags = BLE_ATT_F_READ,
.access_cb = capa_char_1979_user_desc,
},
{0}};
// Array of pointers to other service definitions
static const struct ble_gatt_svc_def gatt_svcs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &device_service_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]){{.uuid = BLE_UUID16_DECLARE(0x2A24),
.flags = BLE_GATT_CHR_F_READ,
.access_cb = device_model_number_read},
{.uuid = BLE_UUID16_DECLARE(0x2A29),
.flags = BLE_GATT_CHR_F_READ,
.access_cb = device_manufacturer_read},
{0}},
},
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &capability_service_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]){{
.uuid = BLE_UUID16_DECLARE(0x1979),
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.access_cb = capa_read,
.val_handle = &g_capa_char_val_handle,
.descriptors = char_0x1979_desc,
},
{0}},
},
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &led_service_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]){{
.uuid = BLE_UUID16_DECLARE(0xA000),
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.access_cb = led_capabilities_read,
.descriptors = char_0xA000_descs,
},
{
.uuid = BLE_UUID16_DECLARE(0xDEAD),
.flags = BLE_GATT_CHR_F_WRITE,
.access_cb = led_write,
.descriptors = char_0xDEAD_descs,
},
{0}},
},
{0}};
// BLE event handling
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type)
{
case BLE_GAP_EVENT_CONNECT:
ESP_LOGI(TAG, "BLE GAP EVENT CONNECT %s", event->connect.status == 0 ? "OK!" : "FAILED!");
if (event->connect.status != 0)
{
// Re-advertise if connection failed
ble_app_advertise();
}
break;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(TAG, "BLE GAP EVENT DISCONNECTED");
// Re-advertise after disconnection
ble_app_advertise();
break;
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI(TAG, "BLE GAP EVENT ADV COMPLETE");
// Re-advertise to continue accepting new clients
ble_app_advertise();
break;
case BLE_GAP_EVENT_SUBSCRIBE:
ESP_LOGI(TAG,
"BLE GAP EVENT SUBSCRIBE conn_handle=%d attr_handle=%d reason=%d "
"prev_notify=%d cur_notify=%d prev_indicate=%d cur_indicate=%d",
event->subscribe.conn_handle, event->subscribe.attr_handle, event->subscribe.reason,
event->subscribe.prev_notify, event->subscribe.cur_notify, event->subscribe.prev_indicate,
event->subscribe.cur_indicate);
// Check if subscription is for the capability characteristic's CCCD.
// The CCCD handle is typically the characteristic value handle + 1.
// g_capa_char_val_handle stores the handle of the characteristic value itself.
if (event->subscribe.attr_handle == g_capa_char_val_handle + 1)
{
if (event->subscribe.cur_notify)
{
ESP_LOGI(TAG, "Client subscribed to capability notifications. Sending data...");
// Call the function to send capability data via notifications
capa_notify_data(event->subscribe.conn_handle, g_capa_char_val_handle);
}
else
{
ESP_LOGI(TAG, "Client unsubscribed from capability notifications.");
}
}
break;
default:
break;
}
return 0;
}
// Define the BLE connection
static void ble_app_advertise(void)
{
int ret;
// GAP - advertising definition
struct ble_hs_adv_fields fields;
memset(&fields, 0, sizeof(fields));
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.uuids128 = (ble_uuid128_t[]){capability_service_uuid};
fields.num_uuids128 = 1;
fields.uuids128_is_complete = 1;
ret = ble_gap_adv_set_fields(&fields);
if (ret != 0)
{
ESP_LOGE(TAG, "Failed to set advertising data (err: %d)", ret);
return;
}
// GAP - device connectivity definition
struct ble_gap_adv_params adv_params;
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // connectable or non-connectable
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable
ret = ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL);
if (ret != 0)
{
ESP_LOGE(TAG, "Advertising failed to start (err %d)", ret);
return;
}
// --- Configure Scan Response Data (SCAN_RSP) ---
struct ble_hs_adv_fields scan_rsp_fields;
memset(&scan_rsp_fields, 0, sizeof(scan_rsp_fields));
// Get the device name
const char *device_name;
device_name = ble_svc_gap_device_name();
scan_rsp_fields.name = (uint8_t *)device_name;
scan_rsp_fields.name_len = strlen(device_name);
scan_rsp_fields.name_is_complete = 1;
// Optionally, add TX power level to scan response
scan_rsp_fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
scan_rsp_fields.tx_pwr_lvl_is_present = 1;
ret = ble_gap_adv_rsp_set_fields(&scan_rsp_fields);
if (ret != 0)
{
ESP_LOGE(TAG, "Error setting scan response data; rc=%d", ret);
return;
}
}
// The application
static void ble_app_on_sync(void)
{
uint8_t ble_addr[6] = {0};
int ret = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, ble_addr, NULL);
if (ret != 0)
{
ESP_LOGE(TAG, "Failed to get BLE MAC address (err: %d)", ret);
return;
}
char formatted_name[32];
snprintf(formatted_name, sizeof(formatted_name), "Lighthouse %02X%02X", ble_addr[4], ble_addr[5]);
ble_svc_gap_device_name_set(formatted_name);
// Start Advertising
ble_hs_id_infer_auto(0, &ble_addr_type); // Determines the best address type automatically
ble_app_advertise();
}
// The infinite task
static void host_task(void *param)
{
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
}
void remote_control_init(void)
{
nimble_port_init();
ble_svc_gap_init();
ble_svc_gatt_init();
ble_gatts_count_cfg(gatt_svcs);
ble_gatts_add_svcs(gatt_svcs);
// Callback für Synchronisation
ble_hs_cfg.sync_cb = ble_app_on_sync;
nimble_port_freertos_init(host_task); // Start BLE-Host-Task
}

View File

@@ -0,0 +1,6 @@
idf_component_register(SRCS
"storage.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
spiffs
)

View File

@@ -0,0 +1,55 @@
#pragma once
#include "esp_err.h"
#include <sys/types.h> // For ssize_t
/**
* @brief Initializes the SPIFFS filesystem.
* This function should be called once before any file operations.
* @return ESP_OK on success, error code otherwise.
*/
esp_err_t storage_init(void);
/**
* @brief Unregisters the SPIFFS filesystem.
* This function should be called once when file operations are finished.
*/
void storage_uninit(void);
/**
* @brief Reads a chunk of data from a file on SPIFFS.
* This function maintains internal state (file pointer) to allow reading
* the same file chunk by chunk across multiple calls.
*
* @param filename The path to the file to read (e.g., "/storage/my_file.txt").
* Must be the same filename for consecutive calls to read the same file.
* @param buffer Buffer to store the read data. Must be large enough for max_bytes.
* @param max_bytes The maximum number of bytes to read in this call. Must be > 0.
* @return The number of bytes read on success (>= 0).
* Returns 0 when the end of the file is reached.
* Returns a negative value on error:
* -1: Invalid input parameters (filename, buffer is NULL, or max_bytes is 0).
* -2: Failed to open the file (first call for this filename).
* -3: Filename mismatch with the currently open file (subsequent call).
* -4: Read error occurred.
*/
ssize_t storage_read(const char *filename, char *buffer, size_t max_bytes);
/**
* @brief Reads a chunk of data from a file on SPIFFS, starting at a specific offset.
* This function is stateless regarding an internally managed file pointer for sequential reads;
* it opens, seeks, reads, and closes the file on each call.
*
* @param filename The path to the file to read (e.g., "/storage/my_file.txt").
* @param buffer Buffer to store the read data. Must be large enough for nbytes.
* @param offset The offset in the file to start reading from.
* @param nbytes The maximum number of bytes to read in this call. Must be > 0.
* @return The number of bytes read on success (>= 0).
* Returns 0 when the end of the file is reached from the given offset.
* Returns a negative value on error:
* -1: Invalid input parameters (filename, buffer is NULL, nbytes is 0, or offset is negative).
* -2: Failed to open the file.
* -5: Seek error occurred.
* -4: Read error occurred.
*/
ssize_t storage_read_at(const char *filename, char *buffer, off_t offset, size_t nbytes);

View File

@@ -0,0 +1,139 @@
#include "storage.h"
#include "esp_log.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "esp_spiffs.h"
static const char *TAG = "storage";
static FILE *s_current_file = NULL;
static char s_current_filename[256] = {0}; // Buffer to store the current filename
esp_err_t storage_init(void)
{
ESP_LOGI(TAG, "Initializing Storage");
esp_vfs_spiffs_conf_t conf = {
.base_path = "/storage", // Path where the filesystem will be mounted
.partition_label = "storage", // Partition label (must match partitions.csv)
.max_files = 5, // Maximum number of files that can be open at the same time
.format_if_mount_failed = true // Format partition if mount fails
};
// Initialize and mount SPIFFS
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(TAG, "Failed to mount or format filesystem");
}
else if (ret == ESP_ERR_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
}
else
{
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
}
return ret;
}
ESP_LOGI(TAG, "SPIFFS mounted");
return ESP_OK;
}
void storage_uninit(void)
{
if (s_current_file != NULL)
{
// Log before clearing s_current_filename, using its value.
ESP_LOGW(TAG, "File '%s' was still open during uninit. Closing it.", s_current_filename);
fclose(s_current_file); // Close the file
s_current_file = NULL;
s_current_filename[0] = '\0';
}
esp_vfs_spiffs_unregister("storage");
ESP_LOGI(TAG, "SPIFFS unmounted");
}
ssize_t storage_read(const char *filename, char *buffer, size_t max_bytes)
{
// Input validation
if (filename == NULL || filename[0] == '\0' || buffer == NULL || max_bytes == 0)
{
ESP_LOGE(TAG, "Invalid input parameters for storage_read");
return -1;
}
// If no file is currently open, open the requested one
if (s_current_file == NULL)
{
s_current_file = fopen(filename, "r");
if (s_current_file == NULL)
{
ESP_LOGE(TAG, "Failed to open file for reading: %s", filename);
s_current_filename[0] = '\0'; // Clear filename as open failed
return -2; // Failed to open file
}
// Store the filename for subsequent calls
strncpy(s_current_filename, filename, sizeof(s_current_filename) - 1);
s_current_filename[sizeof(s_current_filename) - 1] = '\0';
ESP_LOGI(TAG, "Opened file: %s", s_current_filename);
}
else
{
// If a file is open, ensure it's the same file requested
if (strcmp(filename, s_current_filename) != 0)
{
ESP_LOGE(TAG, "Filename mismatch. Expected '%s', got '%s'. Close the current file first.",
s_current_filename, filename);
// Note: File remains open. Caller might need a separate close function
// or ensure read loop finishes (returns 0 or error) before changing filename.
return -3; // Filename mismatch
}
}
// Read a chunk
size_t bytes_read = fread(buffer, 1, max_bytes, s_current_file);
// Check for read errors
if (ferror(s_current_file))
{
ESP_LOGE(TAG, "Error reading file: %s", s_current_filename);
fclose(s_current_file);
s_current_file = NULL;
s_current_filename[0] = '\0';
return -4; // Read error
}
// If fread returns 0 and it's not an error, it means EOF or an empty file.
// The file should be closed only when no more bytes can be read (i.e., bytes_read == 0).
if (bytes_read == 0) // Indicates EOF or empty file (and no ferror)
{
// Log before clearing s_current_filename.
// feof(s_current_file) should be true if end of file was actually reached.
if (feof(s_current_file))
{
ESP_LOGI(TAG, "EOF reached for file: %s. Closing file.", s_current_filename);
}
else
{
ESP_LOGI(TAG, "Read 0 bytes from file: %s (possibly empty or already at EOF). Closing file.",
s_current_filename);
}
fclose(s_current_file);
s_current_file = NULL;
s_current_filename[0] = '\0';
// Return 0 as per contract: "Returns 0 when the end of the file is reached."
}
// If bytes_read > 0, return the number of bytes read.
// The file remains open (even if EOF was hit during this read and bytes_read < max_bytes).
// The next call to storage_read for this file will then result in fread returning 0,
// which will then hit the (bytes_read == 0) condition above and close the file.
return bytes_read;
}