Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions components/wendy_ble_prov/include/wendy_ble_prov.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ typedef enum {
/* ── Callbacks from BLE to main ────────────────────────────────────── */

typedef struct {
/**
* Called when the BLE stack is up and advertising has started. If no
* device name was provided at init, the MAC-derived name is available
* at this point via wendy_ble_prov_get_device_name().
*/
void (*on_ble_up)(void);

/**
* Called (from BLE task context) when a client has written SSID +
* password + command=0x01. The main thread should call
Expand All @@ -37,9 +44,12 @@ typedef struct {

/**
* Initialize BLE provisioning: starts NimBLE, registers GATT services,
* and begins advertising as "Wendy-XXXX".
* and begins advertising as "wendy-XXXX".
*
* @param device_name Name to use for BLE advertising. If NULL, a default
* name is generated as "<prefix>-XXYY" from the BT MAC.
*/
esp_err_t wendy_ble_prov_init(const wendy_ble_prov_callbacks_t *callbacks);
esp_err_t wendy_ble_prov_init(const char *device_name, const wendy_ble_prov_callbacks_t *callbacks);

/**
* Update the status characteristic and optionally the IP address.
Expand All @@ -57,12 +67,12 @@ void wendy_ble_prov_set_status(wendy_ble_prov_status_t status, const char *ip_ad
bool wendy_ble_prov_nimble_ready(void);

/**
* Returns the advertising device name (e.g. "Wendy-EC3A") once the
* Returns the advertising device name (e.g. "wendy-ec3a") once the
* NimBLE host has synced with the controller and a real address is
* available; returns NULL before that. Callers should treat the
* NULL case as "not yet known" and fall back to a generic name.
*
* This is the canonical Wendy-XXXX identity. wendy_wifi uses it for
* This is the canonical wendy-XXXX identity. wendy_wifi uses it for
* mDNS so the BLE name and the LAN-side service name always match,
* even when the BT controller is remote (e.g. ESP-Hosted on the P4).
*/
Expand Down
50 changes: 32 additions & 18 deletions components/wendy_ble_prov/src/wendy_ble_prov.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "wendy_ble_prov.h"

#include <stdatomic.h>
#include <string.h>
#include <stdio.h>

Expand Down Expand Up @@ -70,12 +71,14 @@ static int s_status_val_len = 1;
/* Device name (built at init) */
static char s_device_name[32];

/* True once build_device_name() has populated s_device_name from a real
* controller address. Until then s_device_name holds a placeholder
* (e.g. "Wendy-INIT") and external readers should treat the name as
* not yet available. volatile because it's written by the NimBLE host
* task and read by other init paths (mDNS hostname pickup, etc.). */
static volatile bool s_device_name_resolved = false;
/* True if the caller didn't supply an explicit name; name will be generated from MAC address */
static bool s_generate_mac_derived_device_name = false;

/* True while s_device_name holds a placeholder (e.g. "wendy-init").
* Cleared once the real name is set — either from the caller or derived
* from the BT MAC in build_device_name(). atomic_bool because it's written
* by the NimBLE host task and read by other init paths. */
static atomic_bool s_temp_device_name = false;

/* Handle for status characteristic (for notifications) */
static uint16_t s_status_chr_val_handle;
Expand Down Expand Up @@ -302,15 +305,15 @@ static void build_device_name(void)
if (rc != 0) {
/* Fallback: use a fixed suffix */
snprintf(s_device_name, sizeof(s_device_name),
"%s-0000", CONFIG_WENDY_BLE_PROV_DEVICE_PREFIX);
"%s-0000", CONFIG_WENDY_DEVICE_NAME_DEFAULT_PREFIX);
return;
}

/* Use last 2 bytes of MAC for suffix */
snprintf(s_device_name, sizeof(s_device_name),
"%s-%02X%02X", CONFIG_WENDY_BLE_PROV_DEVICE_PREFIX,
"%s-%02x%02x", CONFIG_WENDY_DEVICE_NAME_DEFAULT_PREFIX,
addr[1], addr[0]);
s_device_name_resolved = true;
s_temp_device_name = false;
}

/* ── NimBLE host sync callback ─────────────────────────────────────── */
Expand All @@ -319,12 +322,17 @@ static void prov_on_sync(void)
{
ble_hs_util_ensure_addr(0);

/* Now that the host is synced we can read the real BT address */
build_device_name();
if (s_generate_mac_derived_device_name) {
build_device_name();
}
ble_svc_gap_device_name_set(s_device_name);

ESP_LOGI(TAG, "BLE host synced, starting advertising");
start_advertising();

if (s_callbacks.on_ble_up) {
s_callbacks.on_ble_up();
}
}

static void ble_host_task(void *param)
Expand All @@ -335,14 +343,23 @@ static void ble_host_task(void *param)

/* ── Public API ─────────────────────────────────────────────────────── */

esp_err_t wendy_ble_prov_init(const wendy_ble_prov_callbacks_t *callbacks)
esp_err_t wendy_ble_prov_init(const char *device_name, const wendy_ble_prov_callbacks_t *callbacks)
{
if (s_nimble_ready) return ESP_OK;

if (callbacks) {
s_callbacks = *callbacks;
}

if (device_name) {
strlcpy(s_device_name, device_name, sizeof(s_device_name));
} else {
s_generate_mac_derived_device_name = true;
s_temp_device_name = true;
snprintf(s_device_name, sizeof(s_device_name),
"%s-init", CONFIG_WENDY_DEVICE_NAME_DEFAULT_PREFIX);
}

/* Initialize NimBLE */
esp_err_t err = nimble_port_init();
if (err != ESP_OK) {
Expand Down Expand Up @@ -380,10 +397,6 @@ esp_err_t wendy_ble_prov_init(const wendy_ble_prov_callbacks_t *callbacks)
}
#endif

/* Build device name — will use fallback until host syncs and we
* get the real address in prov_on_sync, but we set GAP name here */
snprintf(s_device_name, sizeof(s_device_name),
"%s-INIT", CONFIG_WENDY_BLE_PROV_DEVICE_PREFIX);
ble_svc_gap_device_name_set(s_device_name);

/* Start the NimBLE host task */
Expand Down Expand Up @@ -432,13 +445,14 @@ bool wendy_ble_prov_nimble_ready(void)

const char *wendy_ble_prov_get_device_name(void)
{
return s_device_name_resolved ? s_device_name : NULL;
return s_temp_device_name ? NULL : s_device_name;
}

#else /* BLE prov not enabled */

esp_err_t wendy_ble_prov_init(const wendy_ble_prov_callbacks_t *callbacks)
esp_err_t wendy_ble_prov_init(const char *device_name, const wendy_ble_prov_callbacks_t *callbacks)
{
(void)device_name;
(void)callbacks;
return ESP_ERR_NOT_SUPPORTED;
}
Expand Down
2 changes: 2 additions & 0 deletions components/wendy_conf/include/wendy_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ struct wendy_conf_span wendy_conf_get_cloud_host(void);
struct wendy_conf_span wendy_conf_get_private_key(void);
struct wendy_conf_span wendy_conf_get_certificate(void);
struct wendy_conf_span wendy_conf_get_chain_of_trust(void);

void wendy_conf_copy_span(char *dest, size_t dest_size, struct wendy_conf_span src);
12 changes: 12 additions & 0 deletions components/wendy_conf/src/wendy_conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,15 @@ struct wendy_conf_span wendy_conf_get_chain_of_trust(void)
{
return s_chain_der;
}

void wendy_conf_copy_span(char *dest, size_t dest_size, struct wendy_conf_span src)
{
if (src.size == 0 || !src.data) {
if (dest_size > 0)
dest[0] = '\0';
return;
}
size_t copy_size = src.size < dest_size - 1 ? src.size : dest_size - 1;
memcpy(dest, src.data, copy_size);
dest[copy_size] = '\0';
}
Comment on lines +188 to +198
2 changes: 1 addition & 1 deletion components/wendy_server/include/wendy_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
#ifndef WENDY_SERVER_H
#define WENDY_SERVER_H

void wendy_server_start(void);
void wendy_server_start(const char *device_name);

#endif
24 changes: 19 additions & 5 deletions components/wendy_server/src/wendy_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


Expand Down Expand Up @@ -50,6 +51,7 @@ extern const uint8_t default_key_der_start[] asm("_binary_default_key_der_start
extern const uint8_t default_key_der_end[] asm("_binary_default_key_der_end");

static struct wendy_server_link _links[WENDY_SERVER_MAX_LINKS];
static char *_device_name;


//--- internal functions ---//
Expand Down Expand Up @@ -178,13 +180,24 @@ static void _server_task(void *arg)
ESP_LOGI(TAG, "listening on port %d", WENDY_SERVER_PORT);
if (trusted) {
ESP_LOGI(TAG, "full mTLS enabled: only authenticated clients will be accepted");
} else {
ESP_LOGI(TAG, "mTLS disabled: any client will be accepted");
}

mdns_txt_item_t txt_item = {
.key = "mtls",
.value = "true"
mdns_txt_item_t txt_items[] = {
{
.key = "name",
.value = _device_name
},
{
.key = "mtls",
.value = trusted ? "true" : "false"
}
};
esp_err_t mdns_err = mdns_service_add(NULL, "_wendy-lite", "_tcp", WENDY_SERVER_PORT, &txt_item, trusted ? 1 : 0);

esp_err_t mdns_err = mdns_service_add(_device_name, "_wendy-lite", "_tcp", WENDY_SERVER_PORT, txt_items, sizeof(txt_items) / sizeof(txt_items[0]));
free(_device_name);
_device_name = NULL;
if (mdns_err != ESP_OK) {
ESP_LOGE(TAG, "mDNS service add failed: %s", esp_err_to_name(mdns_err));
} else {
Expand Down Expand Up @@ -282,7 +295,8 @@ static void _server_task(void *arg)

//--- public functions ---//

void wendy_server_start(void)
void wendy_server_start(const char *device_name)
{
_device_name = device_name ? strdup(device_name) : NULL;
xTaskCreate(_server_task, "wendy_server", WENDY_SERVER_TASK_STACK, NULL, WENDY_SERVER_TASK_PRIO, NULL);
}
2 changes: 1 addition & 1 deletion components/wendy_wifi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ endif()
idf_component_register(
SRCS ${srcs}
INCLUDE_DIRS "include"
REQUIRES esp_wifi esp_netif esp_event mdns lwip freertos nvs_flash esp_partition wendy_cloud_prov wendy_ble_prov wendy_com wendy_server wendy_cloud wendy_conf
REQUIRES esp_wifi esp_netif esp_event mdns lwip freertos nvs_flash esp_partition wendy_cloud_prov wendy_com wendy_server wendy_cloud wendy_conf
)
2 changes: 1 addition & 1 deletion components/wendy_wifi/include/wendy_wifi.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extern "C" {
*
* On success, registers mDNS service and starts UDP listener.
*/
esp_err_t wendy_wifi_init(void);
esp_err_t wendy_wifi_init(const char *device_name);

/**
* Connect to WiFi with given credentials. Persists to NVS on success,
Expand Down
55 changes: 26 additions & 29 deletions components/wendy_wifi/src/wendy_wifi.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@

#include <ctype.h>

#if CONFIG_WENDY_BLE_PROV
#include "wendy_ble_prov.h"
#endif

#include "lwip/sockets.h"
#include "lwip/netdb.h"

Expand All @@ -44,6 +40,7 @@ static EventGroupHandle_t s_wifi_events;
static bool s_infra_initialized = false;
static bool s_connected = false;
static bool s_services_started = false;
static char s_device_name[CONFIG_WENDY_DEVICE_NAME_BUF_SIZE];

#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
Expand Down Expand Up @@ -190,31 +187,29 @@ static esp_err_t save_nvs_creds(const char *ssid, const char *password)

static esp_err_t start_mdns_service(void)
{
/* Mirror the BLE advertising name so a device shows the same
* Wendy-XXXX identity over BLE and mDNS. wendy_ble_prov is the
* single source of truth — it can read the controller's address
* whether the controller is native (C6) or remote-over-VHCI (P4),
* whereas esp_read_mac(ESP_MAC_BT) only works for native BT and
* silently returned zeros on the P4. */
char device_name[32];
char hostname[32];
const char *ble_name = NULL;
#if CONFIG_WENDY_BLE_PROV
ble_name = wendy_ble_prov_get_device_name();
#endif
if (ble_name && ble_name[0]) {
strlcpy(device_name, ble_name, sizeof(device_name));
size_t i = 0;
for (; i < sizeof(hostname) - 1 && ble_name[i]; i++) {
hostname[i] = (char)tolower((unsigned char)ble_name[i]);
char hostname[CONFIG_WENDY_DEVICE_NAME_BUF_SIZE];
size_t out = 0;
// On WendyOS, we prefix the hostname with "wendyos-".
// Here, we want to mimic this behavior, but want to use a different prefix.
// This prefix should not be longer than "wendyos-" in order to not
// overconstrain the hostname length. So we choose "wendylt-".
const char prefix[] = "wendylt-";
assert(sizeof(prefix) < sizeof(hostname));
memcpy(hostname + out, prefix, sizeof(prefix) - 1);
out += sizeof(prefix) - 1;
for (size_t in = 0; s_device_name[in] && out < sizeof(hostname) - 1; in++) {
Comment on lines +196 to +200
unsigned char c = (unsigned char)s_device_name[in];
if (c >= 'A' && c <= 'Z') {
hostname[out++] = (char)(c + 32);
} else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') {
hostname[out++] = (char)c;
} else if (c == '_' || c == '.') {
hostname[out++] = '-';
}
hostname[i] = '\0';
} else {
/* BLE prov disabled or NimBLE host not yet synced — use a
* deterministic fallback. */
snprintf(device_name, sizeof(device_name), "Wendy-0000");
snprintf(hostname, sizeof(hostname), "wendy-0000");
/* drop everything else */
}
hostname[out] = '\0';
ESP_LOGI(TAG, "mDNS hostname: '%s'", hostname);

esp_err_t err = mdns_init();
if (err != ESP_OK) {
Expand Down Expand Up @@ -254,7 +249,7 @@ static void start_services(void)
}

// Initialize the mTLS server accessible via the local network
wendy_server_start();
wendy_server_start(s_device_name);

// Initialize the mTLS server accessible via the cloud
#if CONFIG_WENDY_CLOUD_ENABLED
Expand All @@ -271,8 +266,10 @@ static void start_services(void)

/* ── Public API ─────────────────────────────────────────────────────── */

esp_err_t wendy_wifi_init(void)
esp_err_t wendy_wifi_init(const char *device_name)
{
strlcpy(s_device_name, device_name ? device_name : CONFIG_WENDY_DEVICE_NAME_DEFAULT_PREFIX "-0000", sizeof(s_device_name));

Comment on lines +269 to +272
esp_err_t err = wifi_infra_init();
if (err != ESP_OK) return err;

Expand Down
23 changes: 16 additions & 7 deletions main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ menu "Wendy MCU Firmware Configuration"
int "Firmware version patch"
default 0

config WENDY_DEVICE_NAME_BUF_SIZE
int "Device name buffer size (bytes, including null terminator)"
default 64
help
Size of the buffer used to hold the device name (BLE advertising
name, mDNS hostname, etc.), including the null terminator.
We use 64 because this corresponds to the DNS name standard.

config WENDY_DEVICE_NAME_DEFAULT_PREFIX
string "Default device name prefix"
default "wendy"
help
Prefix used when no device name has been provisioned. The
actual name is formed as "<prefix>-XXXX" where XXXX is
derived from the last 2 bytes of the BT MAC address.

config WENDY_P4_BOARD_NAME
string "ESP32-P4 board name"
default ""
Expand Down Expand Up @@ -178,13 +194,6 @@ menu "Wendy MCU Firmware Configuration"
that allows clients to write WiFi credentials. Credentials
are stored in NVS and survive reboots.

config WENDY_BLE_PROV_DEVICE_PREFIX
string "BLE device name prefix"
depends on WENDY_BLE_PROV
default "Wendy"
help
The BLE advertising name will be "<prefix>-XXXX" where XXXX
is derived from the last 2 bytes of the BT MAC address.
endmenu

menu "Cloud Provisioning"
Expand Down
Loading
Loading