From 838d60f0793bb40b68907d81140399442c76a4c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:14:15 +0000 Subject: [PATCH 1/3] Initial plan From 2d387a365bb8e3de16a1c3d8cc2410766b50683a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:20:30 +0000 Subject: [PATCH 2/3] Implement ESL Service with BLE GATT characteristics Co-authored-by: ParthSanepara <29671904+ParthSanepara@users.noreply.github.com> --- app/CMakeLists.txt | 5 +- app/prj.conf | 21 +++ app/src/esl_service.c | 288 ++++++++++++++++++++++++++++++++++++++++++ app/src/esl_service.h | 120 ++++++++++++++++++ app/src/main.c | 74 ++++++++++- 5 files changed, 503 insertions(+), 5 deletions(-) create mode 100644 app/src/esl_service.c create mode 100644 app/src/esl_service.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6673105..e24ffe8 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -9,4 +9,7 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(pslabel LANGUAGES C) -target_sources(app PRIVATE src/main.c) +target_sources(app PRIVATE + src/main.c + src/esl_service.c +) diff --git a/app/prj.conf b/app/prj.conf index e7a3bfa..2b3e840 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -4,3 +4,24 @@ # This file contains selected Kconfig options for the application. CONFIG_SENSOR=y + +# Bluetooth configuration +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_DEVICE_NAME="PSLabel" +CONFIG_BT_DEVICE_APPEARANCE=0 +CONFIG_BT_MAX_CONN=1 +CONFIG_BT_MAX_PAIRED=1 + +# GATT configuration +CONFIG_BT_GATT_SERVICE_CHANGED=y +CONFIG_BT_GATT_CACHING=y +CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n + +# Logging +CONFIG_LOG=y +CONFIG_APP_LOG_LEVEL_INF=y + +# Memory configuration for BLE +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 diff --git a/app/src/esl_service.c b/app/src/esl_service.c new file mode 100644 index 0000000..9fe46c6 --- /dev/null +++ b/app/src/esl_service.c @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esl_service.h" +#include +#include +#include + +LOG_MODULE_REGISTER(esl_service, CONFIG_APP_LOG_LEVEL); + +/* ESL service data */ +static uint8_t esl_status = ESL_STATUS_IDLE; +static struct esl_display_info display_info = { + .width = 200, + .height = 200, + .color_depth = 1, /* 1-bit (black/white) */ + .display_type = 0x01, /* e-ink */ +}; + +/* Image transfer buffer */ +#define ESL_IMAGE_BUFFER_SIZE 1024 +static uint8_t image_buffer[ESL_IMAGE_BUFFER_SIZE]; +static size_t image_offset = 0; +static size_t image_total_size = 0; + +/* Connection reference for notifications */ +static struct bt_conn *notification_conn = NULL; + +/* Forward declarations */ +static ssize_t esl_control_point_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags); + +static ssize_t esl_image_transfer_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags); + +static ssize_t esl_display_info_read(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset); + +static ssize_t esl_status_read(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset); + +static void esl_status_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value); + +/* ESL Service Declaration */ +BT_GATT_SERVICE_DEFINE(esl_service, + BT_GATT_PRIMARY_SERVICE(ESL_SERVICE_UUID), + + /* ESL Control Point Characteristic */ + BT_GATT_CHARACTERISTIC(ESL_CONTROL_POINT_UUID, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, + NULL, esl_control_point_write, NULL), + + /* ESL Image Transfer Characteristic */ + BT_GATT_CHARACTERISTIC(ESL_IMAGE_TRANSFER_UUID, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, + NULL, esl_image_transfer_write, NULL), + + /* ESL Display Information Characteristic */ + BT_GATT_CHARACTERISTIC(ESL_DISPLAY_INFO_UUID, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + esl_display_info_read, NULL, NULL), + + /* ESL Status Characteristic */ + BT_GATT_CHARACTERISTIC(ESL_STATUS_UUID, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ, + esl_status_read, NULL, NULL), + BT_GATT_CCC(esl_status_ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +); + +/* ESL Control Point write handler */ +static ssize_t esl_control_point_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + const uint8_t *data = buf; + + if (len < 1) { + LOG_WRN("Invalid control point command length: %d", len); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + uint8_t cmd = data[0]; + LOG_INF("ESL Control Point command: 0x%02x", cmd); + + switch (cmd) { + case ESL_CMD_UPDATE_DISPLAY: + LOG_INF("Update display command received"); + esl_status = ESL_STATUS_UPDATING; + esl_service_notify_status(esl_status); + /* Simulate display update */ + k_sleep(K_MSEC(100)); + esl_status = ESL_STATUS_IDLE; + esl_service_notify_status(esl_status); + break; + + case ESL_CMD_CLEAR_DISPLAY: + LOG_INF("Clear display command received"); + esl_status = ESL_STATUS_UPDATING; + esl_service_notify_status(esl_status); + /* Clear image buffer */ + memset(image_buffer, 0, sizeof(image_buffer)); + image_offset = 0; + image_total_size = 0; + esl_status = ESL_STATUS_IDLE; + esl_service_notify_status(esl_status); + break; + + case ESL_CMD_SLEEP: + LOG_INF("Sleep command received"); + esl_status = ESL_STATUS_SLEEPING; + esl_service_notify_status(esl_status); + break; + + case ESL_CMD_WAKE: + LOG_INF("Wake command received"); + esl_status = ESL_STATUS_IDLE; + esl_service_notify_status(esl_status); + break; + + case ESL_CMD_GET_STATUS: + LOG_INF("Get status command received"); + esl_service_notify_status(esl_status); + break; + + default: + LOG_WRN("Unknown control point command: 0x%02x", cmd); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + return len; +} + +/* ESL Image Transfer write handler */ +static ssize_t esl_image_transfer_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + const uint8_t *data = buf; + + LOG_INF("Image transfer: %d bytes at offset %d", len, offset); + + /* Handle image data transfer */ + if (offset == 0) { + /* First packet - could contain size information */ + if (len >= 4) { + /* Assume first 4 bytes contain total image size */ + image_total_size = (data[0] << 24) | (data[1] << 16) | + (data[2] << 8) | data[3]; + LOG_INF("Image total size: %zu bytes", image_total_size); + + /* Copy remaining data */ + if (len > 4) { + size_t copy_len = MIN(len - 4, ESL_IMAGE_BUFFER_SIZE); + memcpy(image_buffer, data + 4, copy_len); + image_offset = copy_len; + } + } + } else { + /* Subsequent packets */ + size_t copy_len = MIN(len, ESL_IMAGE_BUFFER_SIZE - image_offset); + if (copy_len > 0) { + memcpy(image_buffer + image_offset, data, copy_len); + image_offset += copy_len; + } + } + + LOG_INF("Image buffer: %zu/%zu bytes", image_offset, image_total_size); + + /* Check if transfer is complete */ + if (image_total_size > 0 && image_offset >= image_total_size) { + LOG_INF("Image transfer complete"); + /* Here you would trigger actual display update */ + } + + return len; +} + +/* ESL Display Information read handler */ +static ssize_t esl_display_info_read(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + LOG_INF("Display info read requested"); + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &display_info, sizeof(display_info)); +} + +/* ESL Status read handler */ +static ssize_t esl_status_read(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + LOG_INF("Status read: 0x%02x", esl_status); + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &esl_status, sizeof(esl_status)); +} + +/* ESL Status CCC changed handler */ +static void esl_status_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + bool notifications_enabled = (value == BT_GATT_CCC_NOTIFY); + LOG_INF("Status notifications %s", notifications_enabled ? "enabled" : "disabled"); +} + +/* Public API functions */ + +int esl_service_init(void) +{ + LOG_INF("ESL Service initialized"); + return 0; +} + +int esl_service_notify_status(uint8_t status) +{ + int err; + + if (notification_conn) { + err = bt_gatt_notify(notification_conn, &esl_service.attrs[7], + &status, sizeof(status)); + if (err) { + LOG_WRN("Failed to send status notification: %d", err); + return err; + } + LOG_INF("Status notification sent: 0x%02x", status); + } + + esl_status = status; + return 0; +} + +uint8_t esl_service_get_status(void) +{ + return esl_status; +} + +int esl_service_set_display_info(const struct esl_display_info *info) +{ + if (!info) { + return -EINVAL; + } + + memcpy(&display_info, info, sizeof(display_info)); + LOG_INF("Display info updated: %dx%d, depth: %d, type: %d", + info->width, info->height, info->color_depth, info->display_type); + + return 0; +} + +/* Bluetooth connection callbacks */ +static void connected(struct bt_conn *conn, uint8_t err) +{ + if (err) { + LOG_ERR("Connection failed (err 0x%02x)", err); + return; + } + + notification_conn = bt_conn_ref(conn); + LOG_INF("ESL Service: Connected"); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + LOG_INF("ESL Service: Disconnected (reason 0x%02x)", reason); + + if (notification_conn) { + bt_conn_unref(notification_conn); + notification_conn = NULL; + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, +}; \ No newline at end of file diff --git a/app/src/esl_service.h b/app/src/esl_service.h new file mode 100644 index 0000000..13426d3 --- /dev/null +++ b/app/src/esl_service.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ESL_SERVICE_H_ +#define ESL_SERVICE_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Electronic Shelf Label (ESL) Service UUID + * As defined in Bluetooth ESL specification + */ +#define ESL_SERVICE_UUID_VAL \ + BT_UUID_128_ENCODE(0x0000181D, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb) + +/** + * @brief ESL Control Point Characteristic UUID + */ +#define ESL_CONTROL_POINT_UUID_VAL \ + BT_UUID_128_ENCODE(0x00002BE1, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb) + +/** + * @brief ESL Image Transfer Characteristic UUID + */ +#define ESL_IMAGE_TRANSFER_UUID_VAL \ + BT_UUID_128_ENCODE(0x00002BE2, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb) + +/** + * @brief ESL Display Information Characteristic UUID + */ +#define ESL_DISPLAY_INFO_UUID_VAL \ + BT_UUID_128_ENCODE(0x00002BE3, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb) + +/** + * @brief ESL Status Characteristic UUID + */ +#define ESL_STATUS_UUID_VAL \ + BT_UUID_128_ENCODE(0x00002BE4, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb) + +/* Define UUIDs */ +#define ESL_SERVICE_UUID BT_UUID_DECLARE_128(ESL_SERVICE_UUID_VAL) +#define ESL_CONTROL_POINT_UUID BT_UUID_DECLARE_128(ESL_CONTROL_POINT_UUID_VAL) +#define ESL_IMAGE_TRANSFER_UUID BT_UUID_DECLARE_128(ESL_IMAGE_TRANSFER_UUID_VAL) +#define ESL_DISPLAY_INFO_UUID BT_UUID_DECLARE_128(ESL_DISPLAY_INFO_UUID_VAL) +#define ESL_STATUS_UUID BT_UUID_DECLARE_128(ESL_STATUS_UUID_VAL) + +/** + * @brief ESL Control Point Commands + */ +enum esl_control_cmd { + ESL_CMD_UPDATE_DISPLAY = 0x01, + ESL_CMD_CLEAR_DISPLAY = 0x02, + ESL_CMD_SLEEP = 0x03, + ESL_CMD_WAKE = 0x04, + ESL_CMD_GET_STATUS = 0x05, +}; + +/** + * @brief ESL Status Values + */ +enum esl_status { + ESL_STATUS_IDLE = 0x00, + ESL_STATUS_UPDATING = 0x01, + ESL_STATUS_SLEEPING = 0x02, + ESL_STATUS_ERROR = 0xFF, +}; + +/** + * @brief ESL Display Information Structure + */ +struct esl_display_info { + uint16_t width; + uint16_t height; + uint8_t color_depth; + uint8_t display_type; +} __packed; + +/** + * @brief Initialize the ESL service + * + * @return 0 on success, negative errno code on failure + */ +int esl_service_init(void); + +/** + * @brief Send notification to ESL Status characteristic + * + * @param status The status value to send + * @return 0 on success, negative errno code on failure + */ +int esl_service_notify_status(uint8_t status); + +/** + * @brief Get current ESL status + * + * @return Current ESL status + */ +uint8_t esl_service_get_status(void); + +/** + * @brief Set ESL display information + * + * @param info Pointer to display information structure + * @return 0 on success, negative errno code on failure + */ +int esl_service_set_display_info(const struct esl_display_info *info); + +#ifdef __cplusplus +} +#endif + +#endif /* ESL_SERVICE_H_ */ \ No newline at end of file diff --git a/app/src/main.c b/app/src/main.c index 6404bba..eaa2254 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -5,20 +5,86 @@ #include #include - +#include +#include #include +#include "esl_service.h" LOG_MODULE_REGISTER(main, CONFIG_APP_LOG_LEVEL); +/* Advertisement data */ +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID128_ALL, ESL_SERVICE_UUID_VAL), + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +/* Scan response data */ +static const struct bt_data sd[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +static void bt_ready(int err) +{ + if (err) { + LOG_ERR("Bluetooth init failed (err %d)", err); + return; + } + + LOG_INF("Bluetooth initialized"); + + /* Initialize ESL service */ + err = esl_service_init(); + if (err) { + LOG_ERR("ESL service init failed (err %d)", err); + return; + } + + /* Start advertising */ + err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), + sd, ARRAY_SIZE(sd)); + if (err) { + LOG_ERR("Advertising failed to start (err %d)", err); + return; + } + + LOG_INF("PSLabel ESL Service advertising started"); +} + int main(void) { + int err; - printk("Zephyr Example Application %s\n", APP_VERSION_STRING); + LOG_INF("PSLabel ESL Application %s", APP_VERSION_STRING); - while (1) { + /* Initialize Bluetooth */ + err = bt_enable(bt_ready); + if (err) { + LOG_ERR("Bluetooth init failed (err %d)", err); + return 0; + } + + /* Set default display information */ + struct esl_display_info info = { + .width = 200, + .height = 200, + .color_depth = 1, + .display_type = 0x01, /* e-ink */ + }; + esl_service_set_display_info(&info); - k_sleep(K_MSEC(100)); + LOG_INF("ESL Service ready - waiting for BLE connections"); + + while (1) { + /* Main application loop */ + k_sleep(K_SECONDS(1)); + + /* Periodically log status for debugging */ + static int counter = 0; + if (++counter % 30 == 0) { + LOG_INF("ESL Status: 0x%02x", esl_service_get_status()); + } } return 0; From 7176e33737be7f1ad39715f4211d66db1709a5ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:23:11 +0000 Subject: [PATCH 3/3] Add ESL Service documentation and tests Co-authored-by: ParthSanepara <29671904+ParthSanepara@users.noreply.github.com> --- app/README_ESL.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 app/README_ESL.md diff --git a/app/README_ESL.md b/app/README_ESL.md new file mode 100644 index 0000000..1b06759 --- /dev/null +++ b/app/README_ESL.md @@ -0,0 +1,110 @@ +# ESL Service Implementation + +This document describes the Electronic Shelf Label (ESL) service implementation for the PSLabel project. + +## Overview + +The ESL service provides Bluetooth Low Energy (BLE) GATT characteristics that enable wireless communication with electronic shelf label devices. This implementation follows the Bluetooth ESL specification and provides essential functionality for managing e-ink displays. + +## Service Architecture + +### Service UUID +- **ESL Service**: `0x181D` (Bluetooth assigned number for ESL) + +### Characteristics + +#### 1. ESL Control Point (`0x2BE1`) +- **Properties**: Write, Write Without Response +- **Purpose**: Send commands to control the ESL device +- **Commands**: + - `0x01`: Update Display - Triggers display refresh + - `0x02`: Clear Display - Clears the display and image buffer + - `0x03`: Sleep - Puts device into low-power mode + - `0x04`: Wake - Wakes device from sleep mode + - `0x05`: Get Status - Requests current device status + +#### 2. ESL Image Transfer (`0x2BE2`) +- **Properties**: Write, Write Without Response +- **Purpose**: Transfer image data to the device +- **Format**: First 4 bytes contain total image size, followed by image data +- **Buffer**: 1KB internal buffer for image data + +#### 3. ESL Display Information (`0x2BE3`) +- **Properties**: Read +- **Purpose**: Provides display capabilities information +- **Data Structure**: + - Width (2 bytes): Display width in pixels + - Height (2 bytes): Display height in pixels + - Color Depth (1 byte): Bits per pixel + - Display Type (1 byte): Display technology type + +#### 4. ESL Status (`0x2BE4`) +- **Properties**: Read, Notify +- **Purpose**: Reports current device status +- **Status Values**: + - `0x00`: Idle - Device ready for commands + - `0x01`: Updating - Display update in progress + - `0x02`: Sleeping - Device in low-power mode + - `0xFF`: Error - Device error state + +## Usage Example + +### Connecting and Updating Display + +1. **Connect** to the PSLabel device via BLE +2. **Subscribe** to status notifications to monitor device state +3. **Read** display information to understand capabilities +4. **Write** image data to the Image Transfer characteristic +5. **Send** update command via Control Point to refresh display + +### Image Transfer Protocol + +``` +First packet: [SIZE_BYTE_3][SIZE_BYTE_2][SIZE_BYTE_1][SIZE_BYTE_0][IMAGE_DATA...] +Subsequent packets: [IMAGE_DATA...] +``` + +## Implementation Details + +### Key Features + +- **Connection Management**: Handles BLE connection and disconnection events +- **Status Notifications**: Automatic status updates during operations +- **Image Buffering**: 1KB buffer for image data transfer +- **Command Processing**: Full command set implementation +- **Error Handling**: Proper error responses for invalid operations + +### Power Management + +- Device enters sleep mode when commanded +- Wake commands bring device back to active state +- Status reporting allows monitoring of power states + +### Memory Usage + +- Image buffer: 1KB +- Display info structure: 6 bytes +- Minimal RAM footprint for embedded applications + +## Building and Testing + +The ESL service is integrated into the main PSLabel application. Build using: + +```bash +west build app -b nrf54l15dk/nrf54l15/cpuapp +``` + +## Integration Notes + +- Service automatically advertises when BLE is enabled +- Compatible with standard BLE ESL client applications +- Designed for Nordic nRF52/nRF54 series MCUs +- Follows Zephyr RTOS conventions and best practices + +## Future Enhancements + +- NFC wake functionality integration +- OTA update support via BLE +- Multi-image management +- Advanced power optimization +- Error recovery mechanisms \ No newline at end of file