diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5f670ca --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 + +updates: + - package-ecosystem: gitsubmodule + schedule: + interval: "daily" + directory: "." diff --git a/.github/workflows/validate-component.yml b/.github/workflows/validate-component.yml index 77ca7b7..a21b817 100644 --- a/.github/workflows/validate-component.yml +++ b/.github/workflows/validate-component.yml @@ -16,10 +16,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.12' - name: Install IDF Component Manager - run: pip install 'idf-component-manager==1.5.2' + run: pip install idf-component-manager - name: Validate and Pack Component run: compote component pack --name Notecard diff --git a/README.md b/README.md index 394baeb..04b6a76 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,11 @@ Component config ---> Notecard Configuration ## Thread Safety The component automatically provides thread-safe access to the Notecard in multi-threaded FreeRTOS applications. +This is specific to the ESP-IDF implementation of `malloc` as referenced in their [documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/mem_alloc.html#thread-safety). The underlying [note-c](https://github.com/blues/note-c) library protects the Notecard from concurrent access using internal mutexes, so no additional locking is required for normal use. +> Note: Due to the nature of note-c and how it protects the Notecard from concurrent access, if you application has sensitive timing requirements, your Notecard operations may need to be handled in their own task. Consider using a message queue to offload data to a Notecard send/receive task. + ### I2C Bus Sharing If you have other I2C peripherals on the same bus as the Notecard, register your I2C mutex with `note-c` using `NoteSetFnI2CMutex()` to minimize the time spent under lock. diff --git a/examples/basic_usage_i2c/README.md b/examples/basic_usage_i2c/README.md index 6e7f7ef..5db7969 100644 --- a/examples/basic_usage_i2c/README.md +++ b/examples/basic_usage_i2c/README.md @@ -5,9 +5,10 @@ This example demonstrates basic Notecard usage with ESP-IDF in a standard ESP-ID - Initialize the Notecard with I2C communication using the new I2C master API - Configure Notehub connection - Use FreeRTOS tasks for sensor data collection -- Read built-in sensors from the Notecard (temperature and voltage) +- Read built-in sensors from the Notecard (temperature) - Send sensor data to Notehub using a dedicated task -- Handle JSON requests and responses with proper thread safety +- Handle JSON requests and responses +- Properly deinitialize the Notecard on cleanup ## Hardware Requirements diff --git a/examples/basic_usage_i2c/main/main.c b/examples/basic_usage_i2c/main/main.c index 102fa49..e4cbb69 100644 --- a/examples/basic_usage_i2c/main/main.c +++ b/examples/basic_usage_i2c/main/main.c @@ -22,7 +22,6 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/semphr.h" #include "esp_log.h" #include "esp_system.h" #include "esp_chip_info.h" @@ -30,15 +29,11 @@ #include "driver/i2c_master.h" #include "notecard.h" -// Platform function declaration for I2C scan -extern esp_err_t notecard_platform_i2c_scan(uint8_t start_addr, uint8_t end_addr, uint8_t *found_devices, uint8_t max_devices, uint8_t *device_count); - static const char *TAG = "notecard_basic"; // Task handles static TaskHandle_t notecard_task_handle = NULL; static bool notecard_initialized = false; -static SemaphoreHandle_t notecard_mutex = NULL; // Task stack sizes #define NOTECARD_TASK_STACK_SIZE (4 * 1024) @@ -74,10 +69,6 @@ static esp_err_t notecard_configure_hub(void) // Create hub.set request J *req = NoteNewRequest("hub.set"); - if (req == NULL) { - ESP_LOGE(TAG, "Failed to create hub.set request"); - return ESP_ERR_NO_MEM; - } // Set Notehub product ID JAddStringToObject(req, "product", CONFIG_NOTEHUB_PRODUCT_UID); @@ -108,54 +99,34 @@ static void notecard_sensor_task(void *pvParameters) const TickType_t reading_interval = pdMS_TO_TICKS(15000); // 15 seconds while (eventCounter < max_readings) { - if (xSemaphoreTake(notecard_mutex, portMAX_DELAY) == pdTRUE) { - eventCounter++; - - ESP_LOGI(TAG, "Reading sensors (sample %d/%d)...", eventCounter, max_readings); - - // Read temperature from Notecard's built-in sensor - double temperature = 0; - J *rsp = NoteRequestResponse(NoteNewRequest("card.temp")); - if (rsp != NULL) { - temperature = JGetNumber(rsp, "value"); - NoteDeleteResponse(rsp); - } else { - ESP_LOGW(TAG, "Failed to read temperature"); - } - - // Read voltage from Notecard's V+ pin - double voltage = 0; - rsp = NoteRequestResponse(NoteNewRequest("card.voltage")); - if (rsp != NULL) { - voltage = JGetNumber(rsp, "value"); - NoteDeleteResponse(rsp); - } else { - ESP_LOGW(TAG, "Failed to read voltage"); - } - - // Send data to Notehub - J *req = NoteNewRequest("note.add"); - if (req != NULL) { - JAddBoolToObject(req, "sync", true); - J *body = JAddObjectToObject(req, "body"); - if (body != NULL) { - JAddNumberToObject(body, "temp", temperature); - JAddNumberToObject(body, "voltage", voltage); - JAddNumberToObject(body, "count", eventCounter); - } - - bool success = NoteRequest(req); - if (success) { - ESP_LOGI(TAG, "Sample %d sent: temp=%.2f°C, voltage=%.2fV", - eventCounter, temperature, voltage); - } else { - ESP_LOGE(TAG, "Failed to send sample %d", eventCounter); - } - } else { - ESP_LOGE(TAG, "Failed to create note request"); - } - - xSemaphoreGive(notecard_mutex); + eventCounter++; + + ESP_LOGI(TAG, "Reading sensors (sample %d/%d)...", eventCounter, max_readings); + + // Read temperature from Notecard's built-in sensor + double temperature = 0; + J *rsp = NoteRequestResponse(NoteNewRequest("card.temp")); + if (rsp != NULL) { + temperature = JGetNumber(rsp, "value"); + NoteDeleteResponse(rsp); + } else { + ESP_LOGW(TAG, "Failed to read temperature"); + } + + // Send data to Notehub + J *req = NoteNewRequest("note.add"); + JAddBoolToObject(req, "sync", true); + + J *body = JAddObjectToObject(req, "body"); + JAddNumberToObject(body, "temp", temperature); + JAddNumberToObject(body, "count", eventCounter); + + bool success = NoteRequest(req); + if (success) { + ESP_LOGI(TAG, "Sample %d sent: temp=%.2f°C", + eventCounter, temperature); + } else { + ESP_LOGE(TAG, "Failed to send sample %d", eventCounter); } // Wait before next reading @@ -181,27 +152,14 @@ void app_main(void) CONFIG_IDF_TARGET, chip_info.revision, chip_info.cores); ESP_LOGI(TAG, "Free heap: %u bytes", esp_get_free_heap_size()); - // Create mutex for Notecard access - notecard_mutex = xSemaphoreCreateMutex(); - if (notecard_mutex == NULL) { - ESP_LOGE(TAG, "Failed to create Notecard mutex"); - return; - } - // Initialize Notecard hardware and configure for Notehub - // Use mutex to protect note-c global state during initialization - esp_err_t ret = ESP_FAIL; - if (xSemaphoreTake(notecard_mutex, portMAX_DELAY) == pdTRUE) { - ret = notecard_hardware_init(); - if (ret == ESP_OK) { - ret = notecard_configure_hub(); - } - xSemaphoreGive(notecard_mutex); + esp_err_t ret = notecard_hardware_init(); + if (ret == ESP_OK) { + ret = notecard_configure_hub(); } if (ret != ESP_OK) { ESP_LOGE(TAG, "Notecard initialization failed"); - vSemaphoreDelete(notecard_mutex); return; } @@ -240,10 +198,8 @@ void app_main(void) (notecard_task_handle != NULL) ? "yes" : "no"); } - // Cleanup - if (notecard_mutex) { - vSemaphoreDelete(notecard_mutex); - } + // Cleanup - deinitialize Notecard + notecard_deinit(); ESP_LOGI(TAG, "Application ended gracefully"); } diff --git a/examples/basic_usage_uart/README.md b/examples/basic_usage_uart/README.md index 6233192..1720e7c 100644 --- a/examples/basic_usage_uart/README.md +++ b/examples/basic_usage_uart/README.md @@ -5,9 +5,10 @@ This example demonstrates basic Notecard usage with ESP-IDF using UART communica - Initialize the Notecard with UART communication - Configure Notehub connection - Use FreeRTOS tasks for sensor data collection -- Read built-in sensors from the Notecard (temperature and voltage) +- Read built-in sensors from the Notecard (temperature) - Send sensor data to Notehub using a dedicated task -- Handle JSON requests and responses with proper thread safety +- Handle JSON requests and responses +- Properly deinitialize the Notecard on cleanup ## Hardware Requirements diff --git a/examples/basic_usage_uart/main/main.c b/examples/basic_usage_uart/main/main.c index 5632396..f586977 100644 --- a/examples/basic_usage_uart/main/main.c +++ b/examples/basic_usage_uart/main/main.c @@ -22,7 +22,6 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/semphr.h" #include "esp_log.h" #include "esp_system.h" #include "esp_chip_info.h" @@ -44,7 +43,6 @@ static const char *TAG = "notecard_uart"; // Task handles static TaskHandle_t notecard_task_handle = NULL; static bool notecard_initialized = false; -static SemaphoreHandle_t notecard_mutex = NULL; // Task stack sizes #define NOTECARD_TASK_STACK_SIZE (4 * 1024) @@ -82,10 +80,6 @@ static esp_err_t notecard_configure_hub(void) // Create hub.set request J *req = NoteNewRequest("hub.set"); - if (req == NULL) { - ESP_LOGE(TAG, "Failed to create hub.set request"); - return ESP_ERR_NO_MEM; - } // Set Notehub product ID JAddStringToObject(req, "product", CONFIG_NOTEHUB_PRODUCT_UID); @@ -116,54 +110,34 @@ static void notecard_sensor_task(void *pvParameters) const TickType_t reading_interval = pdMS_TO_TICKS(15000); // 15 seconds while (eventCounter < max_readings) { - if (xSemaphoreTake(notecard_mutex, portMAX_DELAY) == pdTRUE) { - eventCounter++; - - ESP_LOGI(TAG, "Reading sensors (sample %d/%d)...", eventCounter, max_readings); - - // Read temperature from Notecard's built-in sensor - double temperature = 0; - J *rsp = NoteRequestResponse(NoteNewRequest("card.temp")); - if (rsp != NULL) { - temperature = JGetNumber(rsp, "value"); - NoteDeleteResponse(rsp); - } else { - ESP_LOGW(TAG, "Failed to read temperature"); - } - - // Read voltage from Notecard's V+ pin - double voltage = 0; - rsp = NoteRequestResponse(NoteNewRequest("card.voltage")); - if (rsp != NULL) { - voltage = JGetNumber(rsp, "value"); - NoteDeleteResponse(rsp); - } else { - ESP_LOGW(TAG, "Failed to read voltage"); - } - - // Send data to Notehub - J *req = NoteNewRequest("note.add"); - if (req != NULL) { - JAddBoolToObject(req, "sync", true); - J *body = JAddObjectToObject(req, "body"); - if (body != NULL) { - JAddNumberToObject(body, "temp", temperature); - JAddNumberToObject(body, "voltage", voltage); - JAddNumberToObject(body, "count", eventCounter); - } - - bool success = NoteRequest(req); - if (success) { - ESP_LOGI(TAG, "Sample %d sent: temp=%.2f°C, voltage=%.2fV", - eventCounter, temperature, voltage); - } else { - ESP_LOGE(TAG, "Failed to send sample %d", eventCounter); - } - } else { - ESP_LOGE(TAG, "Failed to create note request"); - } - - xSemaphoreGive(notecard_mutex); + eventCounter++; + + ESP_LOGI(TAG, "Reading sensors (sample %d/%d)...", eventCounter, max_readings); + + // Read temperature from Notecard's built-in sensor + double temperature = 0; + J *rsp = NoteRequestResponse(NoteNewRequest("card.temp")); + if (rsp != NULL) { + temperature = JGetNumber(rsp, "value"); + NoteDeleteResponse(rsp); + } else { + ESP_LOGW(TAG, "Failed to read temperature"); + } + + // Send data to Notehub + J *req = NoteNewRequest("note.add"); + JAddBoolToObject(req, "sync", true); + + J *body = JAddObjectToObject(req, "body"); + JAddNumberToObject(body, "temp", temperature); + JAddNumberToObject(body, "count", eventCounter); + + bool success = NoteRequest(req); + if (success) { + ESP_LOGI(TAG, "Sample %d sent: temp=%.2f°C", + eventCounter, temperature); + } else { + ESP_LOGE(TAG, "Failed to send sample %d", eventCounter); } // Wait before next reading @@ -189,27 +163,14 @@ void app_main(void) CONFIG_IDF_TARGET, chip_info.revision, chip_info.cores); ESP_LOGI(TAG, "Free heap: %u bytes", esp_get_free_heap_size()); - // Create mutex for Notecard access - notecard_mutex = xSemaphoreCreateMutex(); - if (notecard_mutex == NULL) { - ESP_LOGE(TAG, "Failed to create Notecard mutex"); - return; - } - // Initialize Notecard hardware and configure for Notehub - // Use mutex to protect note-c global state during initialization - esp_err_t ret = ESP_FAIL; - if (xSemaphoreTake(notecard_mutex, portMAX_DELAY) == pdTRUE) { - ret = notecard_hardware_init(); - if (ret == ESP_OK) { - ret = notecard_configure_hub(); - } - xSemaphoreGive(notecard_mutex); + esp_err_t ret = notecard_hardware_init(); + if (ret == ESP_OK) { + ret = notecard_configure_hub(); } if (ret != ESP_OK) { ESP_LOGE(TAG, "Notecard initialization failed"); - vSemaphoreDelete(notecard_mutex); return; } @@ -248,10 +209,8 @@ void app_main(void) (notecard_task_handle != NULL) ? "yes" : "no"); } - // Cleanup - if (notecard_mutex) { - vSemaphoreDelete(notecard_mutex); - } + // Cleanup - deinitialize Notecard + notecard_deinit(); ESP_LOGI(TAG, "Application ended gracefully"); } diff --git a/src/notecard_platform.c b/src/notecard_platform.c index d10723a..eadcc2a 100644 --- a/src/notecard_platform.c +++ b/src/notecard_platform.c @@ -22,6 +22,7 @@ static notecard_uart_config_t g_uart_config; static bool g_i2c_initialized = false; static bool g_uart_initialized = false; static i2c_master_bus_handle_t g_i2c_bus_handle = NULL; +static i2c_master_dev_handle_t g_i2c_dev_handle = NULL; // FreeRTOS mutex handles for thread-safe access #ifdef CONFIG_NOTECARD_I2C_MUTEX @@ -126,9 +127,30 @@ esp_err_t notecard_platform_i2c_init(const notecard_i2c_config_t *config) return ret; } + // Create I2C device configuration once during initialization + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = config->address, + .scl_speed_hz = config->frequency, + }; + + ret = i2c_master_bus_add_device(g_i2c_bus_handle, &dev_cfg, &g_i2c_dev_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret)); + i2c_del_master_bus(g_i2c_bus_handle); + g_i2c_bus_handle = NULL; +#ifdef CONFIG_NOTECARD_I2C_MUTEX + vSemaphoreDelete(g_i2c_mutex); + g_i2c_mutex = NULL; +#endif + vSemaphoreDelete(g_notecard_mutex); + g_notecard_mutex = NULL; + return ret; + } + g_i2c_initialized = true; - ESP_LOGI(TAG, "I2C initialized on port %d (SDA:%d, SCL:%d, %uHz)", - config->port, config->sda_pin, config->scl_pin, config->frequency); + ESP_LOGI(TAG, "I2C initialized on port %d (SDA:%d, SCL:%d, %uHz, addr:0x%02X)", + config->port, config->sda_pin, config->scl_pin, config->frequency, config->address); return ESP_OK; } @@ -139,6 +161,17 @@ esp_err_t notecard_platform_i2c_deinit(void) return ESP_OK; } + // Remove the I2C device first + if (g_i2c_dev_handle != NULL) { + esp_err_t ret = i2c_master_bus_rm_device(g_i2c_dev_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "I2C device removal failed: %s", esp_err_to_name(ret)); + return ret; + } + g_i2c_dev_handle = NULL; + } + + // Then delete the master bus esp_err_t ret = i2c_del_master_bus(g_i2c_bus_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "I2C master bus deletion failed: %s", esp_err_to_name(ret)); @@ -183,7 +216,7 @@ bool notecard_platform_i2c_reset(uint16_t device_address) const char *notecard_platform_i2c_transmit(uint16_t device_address, uint8_t *buffer, uint16_t size) { - if (!g_i2c_initialized || !g_i2c_bus_handle) { + if (!g_i2c_initialized || !g_i2c_dev_handle) { ESP_LOGE(TAG, "I2C not initialized"); return "i2c not initialized"; } @@ -197,27 +230,12 @@ const char *notecard_platform_i2c_transmit(uint16_t device_address, uint8_t *buf return "chunk too large"; } - // Create device configuration for this transmission - i2c_device_config_t dev_cfg = { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = device_address, - .scl_speed_hz = g_i2c_config.frequency, - }; - - i2c_master_dev_handle_t dev_handle; - esp_err_t ret = i2c_master_bus_add_device(g_i2c_bus_handle, &dev_cfg, &dev_handle); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret)); - return esp_err_to_name(ret); - } - // Notecard Serial-over-I2C protocol: prepend size byte to data uint8_t send_buffer[size + 1]; send_buffer[0] = (uint8_t)(size & 0xFF); memcpy(&send_buffer[1], buffer, size); - ret = i2c_master_transmit(dev_handle, send_buffer, size + 1, I2C_TIMEOUT_MS); - i2c_master_bus_rm_device(dev_handle); + esp_err_t ret = i2c_master_transmit(g_i2c_dev_handle, send_buffer, size + 1, I2C_TIMEOUT_MS); if (ret != ESP_OK) { ESP_LOGE(TAG, "I2C transmit failed: %s", esp_err_to_name(ret)); @@ -231,36 +249,20 @@ const char *notecard_platform_i2c_transmit(uint16_t device_address, uint8_t *buf const char *notecard_platform_i2c_receive(uint16_t device_address, uint8_t *buffer, uint16_t size, uint32_t *available) { - if (!g_i2c_initialized || !g_i2c_bus_handle) { + if (!g_i2c_initialized || !g_i2c_dev_handle) { ESP_LOGE(TAG, "I2C not initialized for receive"); return "I2C not initialized"; } - // Create device configuration for this transmission - i2c_device_config_t dev_cfg = { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = device_address, - .scl_speed_hz = g_i2c_config.frequency, - }; - - i2c_master_dev_handle_t dev_handle; - esp_err_t ret = i2c_master_bus_add_device(g_i2c_bus_handle, &dev_cfg, &dev_handle); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret)); - return esp_err_to_name(ret); - } - if (size > 255) { ESP_LOGE(TAG, "Requested size %u exceeds maximum 255", size); - i2c_master_bus_rm_device(dev_handle); return "size too large"; } // Send read request [0, requested_size] uint8_t read_request[2] = {0, (uint8_t)(size & 0xFF)}; - ret = i2c_master_transmit(dev_handle, read_request, 2, I2C_TIMEOUT_MS); + esp_err_t ret = i2c_master_transmit(g_i2c_dev_handle, read_request, 2, I2C_TIMEOUT_MS); if (ret != ESP_OK) { - i2c_master_bus_rm_device(dev_handle); ESP_LOGE(TAG, "I2C read request failed: %s", esp_err_to_name(ret)); return "I2C read request failed"; } @@ -270,8 +272,7 @@ const char *notecard_platform_i2c_receive(uint16_t device_address, uint8_t *buff vTaskDelay(pdMS_TO_TICKS(25)); - ret = i2c_master_receive(dev_handle, receive_buffer, response_size, I2C_TIMEOUT_MS); - i2c_master_bus_rm_device(dev_handle); + ret = i2c_master_receive(g_i2c_dev_handle, receive_buffer, response_size, I2C_TIMEOUT_MS); if (ret != ESP_OK) { ESP_LOGE(TAG, "I2C receive failed: %s", esp_err_to_name(ret));