diff --git a/firmware/include/config.hpp b/firmware/include/config.hpp index 6890dfd..404859e 100644 --- a/firmware/include/config.hpp +++ b/firmware/include/config.hpp @@ -2,15 +2,70 @@ #include +#include +#include + namespace config { +namespace nvs { + +constexpr const char *NAMESPACE = "config"; + +} // namespace nvs + namespace device { -constexpr const char *NVS_NAMESPACE = "config"; -constexpr const char *DEVICE_ID_KEY = "device_id"; +constexpr const char *PROJECT_NAME = "bugbite"; +constexpr const char *ID_KEY = "device_id"; } // namespace device +namespace wifi { + +constexpr const char *SSID_KEY = "wifi_ssid"; +constexpr const char *PASSWORD_KEY = "wifi_password"; + +} // namespace wifi + +namespace mqtt { + +constexpr const char *HOST_KEY = "mqtt_host"; +constexpr const char *PORT_KEY = "mqtt_port"; +constexpr const char *USER_KEY = "mqtt_user"; +constexpr const char *PASSWORD_KEY = "mqtt_password"; + +} // namespace mqtt + +namespace ble { + +// 128-bit provisioning UUIDs (little-endian for NimBLE). +constexpr std::array SERVICE_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x01, 0x00, 0x00}; +constexpr std::array DEVICE_ID_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x08, 0x00, 0x00}; +constexpr std::array WIFI_SSID_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x02, 0x00, 0x00}; +constexpr std::array WIFI_PASSWORD_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x03, 0x00, 0x00}; +constexpr std::array MQTT_HOST_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x04, 0x00, 0x00}; +constexpr std::array MQTT_PORT_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x05, 0x00, 0x00}; +constexpr std::array MQTT_USER_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x06, 0x00, 0x00}; +constexpr std::array MQTT_PASSWORD_UUID = { + 0x3d, 0x4e, 0x5f, 0x6a, 0x7b, 0x8c, 0xb9, 0xae, + 0xbf, 0xc0, 0xd1, 0xe2, 0x00, 0x07, 0x00, 0x00}; + +} // namespace ble + namespace relay { constexpr gpio_num_t GPIO = GPIO_NUM_2; diff --git a/firmware/sdkconfig.defaults b/firmware/sdkconfig.defaults index b81fd37..9f5e576 100644 --- a/firmware/sdkconfig.defaults +++ b/firmware/sdkconfig.defaults @@ -16,3 +16,13 @@ CONFIG_FREERTOS_HZ=1000 # --- Logging ----------------------------------------------------------------- CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y + +# --- Bluetooth Low Energy (NimBLE host, peripheral only) --------------------- +# Used by the connectivity/provisioning module. ESP32-C3 is BLE-only; NimBLE is +# the lighter host. No security/bonding (plaintext link) for this MVP. +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n diff --git a/firmware/sdkconfig.esp32-c3-supermini b/firmware/sdkconfig.esp32-c3-supermini index ef379f6..af38d6f 100644 --- a/firmware/sdkconfig.esp32-c3-supermini +++ b/firmware/sdkconfig.esp32-c3-supermini @@ -551,15 +551,286 @@ CONFIG_APPTRACE_LOCK_ENABLE=y # # Bluetooth # -# CONFIG_BT_ENABLED is not set +CONFIG_BT_ENABLED=y +# CONFIG_BT_BLUEDROID_ENABLED is not set +CONFIG_BT_NIMBLE_ENABLED=y +# CONFIG_BT_CONTROLLER_ONLY is not set +CONFIG_BT_CONTROLLER_ENABLED=y +# CONFIG_BT_CONTROLLER_DISABLED is not set + +# +# NimBLE Options +# +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set +# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set +# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set +CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y +# CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG is not set +CONFIG_BT_NIMBLE_LOG_LEVEL=1 +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3 +CONFIG_BT_NIMBLE_MAX_BONDS=3 +CONFIG_BT_NIMBLE_MAX_CCCDS=8 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0 +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096 +# CONFIG_BT_NIMBLE_ROLE_CENTRAL is not set +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +# CONFIG_BT_NIMBLE_ROLE_OBSERVER is not set +CONFIG_BT_NIMBLE_GATT_SERVER=y +# CONFIG_BT_NIMBLE_NVS_PERSIST is not set +# CONFIG_BT_NIMBLE_SMP_ID_RESET is not set +CONFIG_BT_NIMBLE_SECURITY_ENABLE=y +CONFIG_BT_NIMBLE_SM_LEGACY=y +CONFIG_BT_NIMBLE_SM_SC=y +# CONFIG_BT_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_ENCRYPTION=y +CONFIG_BT_NIMBLE_SM_LVL=0 +CONFIG_BT_NIMBLE_SM_SC_ONLY=0 +CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y +# CONFIG_BT_NIMBLE_DEBUG is not set +# CONFIG_BT_NIMBLE_DYNAMIC_SERVICE is not set +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=256 +CONFIG_BT_NIMBLE_ATT_MAX_PREP_ENTRIES=64 +CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0 + +# +# Memory Settings +# +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=256 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=24 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=320 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_COUNT=30 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_DISCARD_COUNT=8 +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=1 +# end of Memory Settings + +CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4 +# CONFIG_BT_NIMBLE_HS_FLOW_CTRL is not set +CONFIG_BT_NIMBLE_RPA_TIMEOUT=900 +# CONFIG_BT_NIMBLE_MESH is not set +CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y +CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000 +CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=y +CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3 +# CONFIG_BT_NIMBLE_HANDLE_REPEAT_PAIRING_DELETION is not set +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y +# CONFIG_BT_NIMBLE_EXT_ADV is not set +# CONFIG_BT_NIMBLE_GATT_CACHING is not set +# CONFIG_BT_NIMBLE_INCL_SVC_DISCOVERY is not set +CONFIG_BT_NIMBLE_WHITELIST_SIZE=12 +# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set +# CONFIG_BT_NIMBLE_BLUFI_ENABLE is not set +CONFIG_BT_NIMBLE_USE_ESP_TIMER=y +CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y +# CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER is not set + +# +# BLE Services +# +CONFIG_BT_NIMBLE_PROX_SERVICE=y +CONFIG_BT_NIMBLE_ANS_SERVICE=y +CONFIG_BT_NIMBLE_CTS_SERVICE=y +CONFIG_BT_NIMBLE_HTP_SERVICE=y +CONFIG_BT_NIMBLE_IPSS_SERVICE=y +CONFIG_BT_NIMBLE_TPS_SERVICE=y +CONFIG_BT_NIMBLE_IAS_SERVICE=y +CONFIG_BT_NIMBLE_LLS_SERVICE=y +CONFIG_BT_NIMBLE_SPS_SERVICE=y +CONFIG_BT_NIMBLE_HR_SERVICE=y +# CONFIG_BT_NIMBLE_HID_SERVICE is not set +CONFIG_BT_NIMBLE_BAS_SERVICE=y +# CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY is not set +CONFIG_BT_NIMBLE_DIS_SERVICE=y +# CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME is not set +# CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER is not set +# CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION is not set +# CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION is not set +# CONFIG_BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION is not set +# CONFIG_BT_NIMBLE_SVC_DIS_SYSTEM_ID is not set +# CONFIG_BT_NIMBLE_SVC_DIS_PNP_ID is not set +# CONFIG_BT_NIMBLE_SVC_DIS_INCLUDED is not set +CONFIG_BT_NIMBLE_GAP_SERVICE=y + +# +# GAP Appearance write permissions +# +# CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE is not set +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM=0 +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC=0 +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN=0 +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR=0 +# end of GAP Appearance write permissions + +CONFIG_BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP=y +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP is not set +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_SUPP is not set +CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION=-1 + +# +# GAP device name write permissions +# +# CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE is not set +# end of GAP device name write permissions + +# +# PPCP settings +# +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL=0 +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL=0 +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY=0 +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0 +# end of PPCP settings + +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM=0 +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC=0 +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN=0 +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR=0 +# CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL is not set +# CONFIG_BT_NIMBLE_SVC_GAP_RPA_ONLY is not set +# end of BLE Services + +# CONFIG_BT_NIMBLE_VS_SUPPORT is not set +# CONFIG_BT_NIMBLE_ENC_ADV_DATA is not set +# CONFIG_BT_NIMBLE_HIGH_DUTY_ADV_ITVL is not set +# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set +# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set +# CONFIG_BT_NIMBLE_GATTC_PROC_PREEMPTION_PROTECT is not set + +# +# Host-controller Transport +# +CONFIG_UART_HW_FLOWCTRL_DISABLE=y +# CONFIG_UART_HW_FLOWCTRL_CTS_RTS is not set +CONFIG_BT_NIMBLE_HCI_UART_FLOW_CTRL=0 +CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=19 +CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport + +CONFIG_BT_NIMBLE_EATT_CHAN_NUM=0 +# CONFIG_BT_NIMBLE_SUBRATE is not set +# end of NimBLE Options + +# +# Controller Options +# +CONFIG_BT_CTRL_MODE_EFF=1 +CONFIG_BT_CTRL_BLE_MAX_ACT=6 +CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6 +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 +CONFIG_BT_CTRL_PINNED_TO_CORE=0 +CONFIG_BT_CTRL_HCI_MODE_VHCI=y +# CONFIG_BT_CTRL_HCI_MODE_UART_H4 is not set +CONFIG_BT_CTRL_HCI_TL=1 +CONFIG_BT_CTRL_ADV_DUP_FILT_MAX=30 +CONFIG_BT_BLE_CCA_MODE_NONE=y +# CONFIG_BT_BLE_CCA_MODE_HW is not set +# CONFIG_BT_BLE_CCA_MODE_SW is not set +CONFIG_BT_BLE_CCA_MODE=0 +CONFIG_BT_CTRL_HW_CCA_VAL=75 +CONFIG_BT_CTRL_HW_CCA_EFF=0 +CONFIG_BT_CTRL_CE_LENGTH_TYPE_ORIG=y +# CONFIG_BT_CTRL_CE_LENGTH_TYPE_CE is not set +# CONFIG_BT_CTRL_CE_LENGTH_TYPE_SD is not set +CONFIG_BT_CTRL_CE_LENGTH_TYPE_EFF=0 +CONFIG_BT_CTRL_TX_ANTENNA_INDEX_0=y +# CONFIG_BT_CTRL_TX_ANTENNA_INDEX_1 is not set +CONFIG_BT_CTRL_TX_ANTENNA_INDEX_EFF=0 +CONFIG_BT_CTRL_RX_ANTENNA_INDEX_0=y +# CONFIG_BT_CTRL_RX_ANTENNA_INDEX_1 is not set +CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0 +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N24 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N21 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N18 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N15 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N12 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N9 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N6 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N3 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 +CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 +CONFIG_BT_CTRL_BLE_SCAN_DUPL=y +CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DEVICE=y +# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA is not set +# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA_DEVICE is not set +CONFIG_BT_CTRL_SCAN_DUPL_TYPE=0 +CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE=100 +CONFIG_BT_CTRL_DUPL_SCAN_CACHE_REFRESH_PERIOD=0 +# CONFIG_BT_CTRL_BLE_MESH_SCAN_DUPL_EN is not set +# CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EFF=0 + +# +# MODEM SLEEP Options +# +# CONFIG_BT_CTRL_MODEM_SLEEP is not set +# end of MODEM SLEEP Options + +CONFIG_BT_CTRL_SLEEP_MODE_EFF=0 +CONFIG_BT_CTRL_SLEEP_CLOCK_EFF=0 +CONFIG_BT_CTRL_HCI_TL_EFF=1 +# CONFIG_BT_CTRL_AGC_RECORRECT_EN is not set +# CONFIG_BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set +# CONFIG_BT_BLE_ADV_DATA_LENGTH_ZERO_AUX is not set +CONFIG_BT_CTRL_CHAN_ASS_EN=y +CONFIG_BT_CTRL_LE_PING_EN=y + +# +# BLE disconnects when Instant Passed (0x28) occurs +# +# CONFIG_BT_CTRL_BLE_LLCP_CONN_UPDATE is not set +# CONFIG_BT_CTRL_BLE_LLCP_CHAN_MAP_UPDATE is not set +# CONFIG_BT_CTRL_BLE_LLCP_PHY_UPDATE is not set +# end of BLE disconnects when Instant Passed (0x28) occurs + +# CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY is not set +CONFIG_BT_CTRL_DTM_ENABLE=y +CONFIG_BT_CTRL_BLE_MASTER=y +# CONFIG_BT_CTRL_BLE_TEST is not set +CONFIG_BT_CTRL_BLE_SCAN=y +CONFIG_BT_CTRL_BLE_SECURITY_ENABLE=y +CONFIG_BT_CTRL_BLE_ADV=y +# CONFIG_BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set + +# +# Controller debug log Options (Experimental) +# +# end of Controller debug log Options (Experimental) +# end of Controller Options # # Common Options # +CONFIG_BT_ALARM_MAX_NUM=50 # CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set # end of Common Options + +# CONFIG_BT_HCI_LOG_DEBUG_EN is not set # end of Bluetooth +# CONFIG_BLE_MESH is not set + # # Console Library # @@ -668,7 +939,8 @@ CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y # Wireless Coexistence # CONFIG_ESP_COEX_ENABLED=y -# CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE is not set +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y +# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set # CONFIG_ESP_COEX_GPIO_DEBUG is not set # end of Wireless Coexistence @@ -1970,6 +2242,11 @@ CONFIG_WL_SECTOR_SIZE=4096 # CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +# CONFIG_WIFI_PROV_BLE_BONDING is not set +CONFIG_WIFI_PROV_BLE_SEC_CONN=y +# CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set +# CONFIG_WIFI_PROV_BLE_NOTIFY is not set +# CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager @@ -2012,8 +2289,46 @@ CONFIG_STACK_CHECK_NONE=y # CONFIG_ESP32_APPTRACE_DEST_TRAX is not set CONFIG_ESP32_APPTRACE_DEST_NONE=y CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y -# CONFIG_EXTERNAL_COEX_ENABLE is not set -# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_BLUEDROID_ENABLED is not set +CONFIG_NIMBLE_ENABLED=y +CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +CONFIG_NIMBLE_MAX_CONNECTIONS=3 +CONFIG_NIMBLE_MAX_BONDS=3 +CONFIG_NIMBLE_MAX_CCCDS=8 +CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0 +CONFIG_NIMBLE_PINNED_TO_CORE=0 +CONFIG_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 +# CONFIG_NIMBLE_ROLE_CENTRAL is not set +CONFIG_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_NIMBLE_ROLE_BROADCASTER=y +# CONFIG_NIMBLE_ROLE_OBSERVER is not set +# CONFIG_NIMBLE_NVS_PERSIST is not set +CONFIG_NIMBLE_SM_LEGACY=y +CONFIG_NIMBLE_SM_SC=y +# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_SM_SC_LVL=0 +# CONFIG_NIMBLE_DEBUG is not set +CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_NIMBLE_ATT_PREFERRED_MTU=256 +CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0 +CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24 +CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255 +CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 +CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 +# CONFIG_NIMBLE_HS_FLOW_CTRL is not set +CONFIG_NIMBLE_RPA_TIMEOUT=900 +# CONFIG_NIMBLE_MESH is not set +CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y +# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +CONFIG_SW_COEXIST_ENABLE=y +CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y +CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y # CONFIG_GPTIMER_ISR_IRAM_SAFE is not set # CONFIG_EVENT_LOOP_PROFILING is not set CONFIG_POST_EVENTS_FROM_ISR=y diff --git a/firmware/src/CMakeLists.txt b/firmware/src/CMakeLists.txt index 5182bfb..370a387 100644 --- a/firmware/src/CMakeLists.txt +++ b/firmware/src/CMakeLists.txt @@ -1,2 +1,2 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) -idf_component_register(SRCS ${app_sources}) +idf_component_register(SRCS ${app_sources} REQUIRES bt) diff --git a/firmware/src/setup.cpp b/firmware/src/build.cpp similarity index 68% rename from firmware/src/setup.cpp rename to firmware/src/build.cpp index 3acefc8..d739fc3 100644 --- a/firmware/src/setup.cpp +++ b/firmware/src/build.cpp @@ -1,21 +1,23 @@ -#include "setup.hpp" +#include "build.hpp" #include -static const char *TAG = "app/setup"; +static const char *TAG = "app/build"; -esp_err_t setup(flash_memory::FlashMemory &flashMemory, relay::Relay &loadRelay, - led::Led &builtinLed) { +esp_err_t beginPeripherals(flash_memory::FlashMemory &flashMemory, + relay::Relay &loadRelay, led::Led &builtinLed) { esp_err_t err = flashMemory.begin(); if (err != ESP_OK) { ESP_LOGE(TAG, "flash memory initialization failed: %s", esp_err_to_name(err)); + return err; } err = loadRelay.begin(); if (err != ESP_OK) { ESP_LOGE(TAG, "load relay initialization failed: %s", esp_err_to_name(err)); + return err; } @@ -23,10 +25,11 @@ esp_err_t setup(flash_memory::FlashMemory &flashMemory, relay::Relay &loadRelay, if (err != ESP_OK) { ESP_LOGE(TAG, "builtin led initialization failed: %s", esp_err_to_name(err)); + return err; } - ESP_LOGI(TAG, "setup completed"); + ESP_LOGI(TAG, "peripherals initialized"); return ESP_OK; } diff --git a/firmware/src/setup.hpp b/firmware/src/build.hpp similarity index 62% rename from firmware/src/setup.hpp rename to firmware/src/build.hpp index 2c0a2fa..388cd3d 100644 --- a/firmware/src/setup.hpp +++ b/firmware/src/build.hpp @@ -7,7 +7,7 @@ #include "peripherals/relay/relay.hpp" /** - * @brief Initializes every module instance of the application. + * @brief Initializes every peripheral instance of the application. * * Runs the begin() sequence in dependency order, binding each instance to its * configuration constants. The sequence aborts on the first failure. @@ -16,8 +16,8 @@ * modules rely on persistent storage. * @param loadRelay Load relay facade. * @param builtinLed Built-in LED facade. - * @return ESP_OK when every module initialized, or the error of the - * first failure. + * @return ESP_OK when every peripheral initialized, or the error of + * the first failure. */ -esp_err_t setup(flash_memory::FlashMemory &flashMemory, relay::Relay &loadRelay, - led::Led &builtinLed); +esp_err_t beginPeripherals(flash_memory::FlashMemory &flashMemory, + relay::Relay &loadRelay, led::Led &builtinLed); diff --git a/firmware/src/connectivity/config/domain/i_device_config.hpp b/firmware/src/connectivity/config/domain/i_device_config.hpp new file mode 100644 index 0000000..9171237 --- /dev/null +++ b/firmware/src/connectivity/config/domain/i_device_config.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +namespace connectivity::config { + +/** + * @brief Port for device identity information. + * + * Read-only access to the device identity: the device_id provisioned at the + * factory (resolved from storage) and the build-time project name. Consumers + * such as the provisioning module use it to derive the advertised name, and the + * future MQTT module will derive the client id from the device_id. + */ +class IDeviceConfig { + public: + virtual ~IDeviceConfig() = default; + + /** + * @brief Reads the device id. + * + * @param out Receives the device_id on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if the device has not + * been provisioned, or an ESP-IDF error code on failure. + */ + virtual esp_err_t getId(std::string &out) const = 0; + + /** + * @brief Reads the project name. + * + * @param out Receives the project name. + * @return ESP_OK on success. + */ + virtual esp_err_t getProjectName(std::string &out) const = 0; +}; + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/domain/i_mqtt_config.hpp b/firmware/src/connectivity/config/domain/i_mqtt_config.hpp new file mode 100644 index 0000000..7dd1500 --- /dev/null +++ b/firmware/src/connectivity/config/domain/i_mqtt_config.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include + +#include + +namespace connectivity::config { + +/** + * @brief Port for persistent MQTT configuration. + * + * Read/write access to the MQTT connection settings backing store. The + * provisioning module writes these values; the future MQTT module reads them + * to connect. Each getter resolves the value from storage; + * @c ESP_ERR_NVS_NOT_FOUND means the field has not been provisioned yet. + * + * The port is stored and exposed as a string, mirroring the underlying + * key-value storage. The MQTT module is expected to parse it to a numeric + * port when it consumes the value. + */ +class IMqttConfig { + public: + virtual ~IMqttConfig() = default; + + /** + * @brief Reads the configured broker host. + * + * @param out Receives the host on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if unset, or an + * ESP-IDF error code on failure. + */ + virtual esp_err_t getHost(std::string &out) const = 0; + + /** + * @brief Stores the broker host. + * + * @param value Host to persist. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t setHost(const std::string &value) = 0; + + /** + * @brief Reads the configured broker port. + * + * @param out Receives the port on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if unset, or an + * ESP-IDF error code on failure. + */ + virtual esp_err_t getPort(std::string &out) const = 0; + + /** + * @brief Stores the broker port. + * + * @param value Port to persist. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t setPort(const std::string &value) = 0; + + /** + * @brief Reads the configured user. + * + * @param out Receives the user on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if unset, or an + * ESP-IDF error code on failure. + */ + virtual esp_err_t getUser(std::string &out) const = 0; + + /** + * @brief Stores the user. + * + * @param value User to persist. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t setUser(const std::string &value) = 0; + + /** + * @brief Reads the configured password. + * + * @param out Receives the password on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if unset, or an + * ESP-IDF error code on failure. + */ + virtual esp_err_t getPassword(std::string &out) const = 0; + + /** + * @brief Stores the password. + * + * @param value Password to persist. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t setPassword(const std::string &value) = 0; +}; + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/domain/i_wifi_config.hpp b/firmware/src/connectivity/config/domain/i_wifi_config.hpp new file mode 100644 index 0000000..d13fd8f --- /dev/null +++ b/firmware/src/connectivity/config/domain/i_wifi_config.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include + +namespace connectivity::config { + +/** + * @brief Port for persistent WiFi configuration. + * + * Read/write access to the WiFi credentials backing store. The provisioning + * module writes these values; the future WiFi module reads them to connect. + * Each getter resolves the value from storage; @c ESP_ERR_NVS_NOT_FOUND means + * the field has not been provisioned yet. + */ +class IWifiConfig { + public: + virtual ~IWifiConfig() = default; + + /** + * @brief Reads the configured SSID. + * + * @param out Receives the SSID on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if unset, or an + * ESP-IDF error code on failure. + */ + virtual esp_err_t getSsid(std::string &out) const = 0; + + /** + * @brief Stores the SSID. + * + * @param value SSID to persist. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t setSsid(const std::string &value) = 0; + + /** + * @brief Reads the configured password. + * + * @param out Receives the password on success. + * @return ESP_OK on success, ESP_ERR_NVS_NOT_FOUND if unset, or an + * ESP-IDF error code on failure. + */ + virtual esp_err_t getPassword(std::string &out) const = 0; + + /** + * @brief Stores the password. + * + * @param value Password to persist. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t setPassword(const std::string &value) = 0; +}; + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/infrastructure/flash_device_config.cpp b/firmware/src/connectivity/config/infrastructure/flash_device_config.cpp new file mode 100644 index 0000000..9148954 --- /dev/null +++ b/firmware/src/connectivity/config/infrastructure/flash_device_config.cpp @@ -0,0 +1,19 @@ +#include "connectivity/config/infrastructure/flash_device_config.hpp" + +namespace connectivity::config { + +FlashDeviceConfig::FlashDeviceConfig(flash_memory::FlashService &flash, + const DeviceConfigParams ¶ms) + : _flash(flash), _params(params) {} + +esp_err_t FlashDeviceConfig::getId(std::string &out) const { + return _flash.read(_params.ns, _params.idKey, out); +} + +esp_err_t FlashDeviceConfig::getProjectName(std::string &out) const { + out = _params.projectName; + + return ESP_OK; +} + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp b/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp new file mode 100644 index 0000000..dbab64f --- /dev/null +++ b/firmware/src/connectivity/config/infrastructure/flash_device_config.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "connectivity/config/domain/i_device_config.hpp" +#include "peripherals/flash_memory/application/flash_service.hpp" + +namespace connectivity::config { + +/** + * @brief NVS coordinates and build-time values for the device identity. + * + * Injected at construction so the adapter holds no hard-coded configuration; + * main.cpp supplies the values from the central config header. + */ +struct DeviceConfigParams { + const char *ns; + const char *idKey; + const char *projectName; +}; + +/** + * @brief Flash-backed adapter for device identity. + * + * Implements @c IDeviceConfig. The device_id is read-through from NVS; the + * project name is a build-time constant supplied at construction. + */ +class FlashDeviceConfig : public IDeviceConfig { + public: + /** + * @brief Builds the adapter over a flash service. + * + * @param flash Reference to the flash service. Must outlive this instance. + * @param params NVS coordinates and the project name. + */ + FlashDeviceConfig(flash_memory::FlashService &flash, + const DeviceConfigParams ¶ms); + + esp_err_t getId(std::string &out) const override; + esp_err_t getProjectName(std::string &out) const override; + + private: + flash_memory::FlashService &_flash; + DeviceConfigParams _params; +}; + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.cpp b/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.cpp new file mode 100644 index 0000000..9757982 --- /dev/null +++ b/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.cpp @@ -0,0 +1,41 @@ +#include "connectivity/config/infrastructure/flash_mqtt_config.hpp" + +namespace connectivity::config { + +FlashMqttConfig::FlashMqttConfig(flash_memory::FlashService &flash, + const MqttConfigKeys &keys) + : _flash(flash), _keys(keys) {} + +esp_err_t FlashMqttConfig::getHost(std::string &out) const { + return _flash.read(_keys.ns, _keys.host, out); +} + +esp_err_t FlashMqttConfig::setHost(const std::string &value) { + return _flash.write(_keys.ns, _keys.host, value); +} + +esp_err_t FlashMqttConfig::getPort(std::string &out) const { + return _flash.read(_keys.ns, _keys.port, out); +} + +esp_err_t FlashMqttConfig::setPort(const std::string &value) { + return _flash.write(_keys.ns, _keys.port, value); +} + +esp_err_t FlashMqttConfig::getUser(std::string &out) const { + return _flash.read(_keys.ns, _keys.user, out); +} + +esp_err_t FlashMqttConfig::setUser(const std::string &value) { + return _flash.write(_keys.ns, _keys.user, value); +} + +esp_err_t FlashMqttConfig::getPassword(std::string &out) const { + return _flash.read(_keys.ns, _keys.password, out); +} + +esp_err_t FlashMqttConfig::setPassword(const std::string &value) { + return _flash.write(_keys.ns, _keys.password, value); +} + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp b/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp new file mode 100644 index 0000000..071d571 --- /dev/null +++ b/firmware/src/connectivity/config/infrastructure/flash_mqtt_config.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "connectivity/config/domain/i_mqtt_config.hpp" +#include "peripherals/flash_memory/application/flash_service.hpp" + +namespace connectivity::config { + +/** + * @brief NVS namespace and keys for the MQTT configuration fields. + * + * Injected at construction so the adapter holds no hard-coded configuration; + * main.cpp supplies the values from the central config header. + */ +struct MqttConfigKeys { + const char *ns; + const char *host; + const char *port; + const char *user; + const char *password; +}; + +/** + * @brief Flash-backed adapter for MQTT configuration. + * + * Implements @c IMqttConfig on top of the flash_memory service. Each operation + * is a direct read-through / write-through to NVS (no in-RAM cache), mapping + * each MQTT field to its injected NVS key. + */ +class FlashMqttConfig : public IMqttConfig { + public: + /** + * @brief Builds the adapter over a flash service. + * + * @param flash Reference to the flash service. Must outlive this instance. + * @param keys NVS namespace and keys for the MQTT fields. + */ + FlashMqttConfig(flash_memory::FlashService &flash, + const MqttConfigKeys &keys); + + esp_err_t getHost(std::string &out) const override; + esp_err_t setHost(const std::string &value) override; + + esp_err_t getPort(std::string &out) const override; + esp_err_t setPort(const std::string &value) override; + + esp_err_t getUser(std::string &out) const override; + esp_err_t setUser(const std::string &value) override; + + esp_err_t getPassword(std::string &out) const override; + esp_err_t setPassword(const std::string &value) override; + + private: + flash_memory::FlashService &_flash; + MqttConfigKeys _keys; +}; + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/infrastructure/flash_wifi_config.cpp b/firmware/src/connectivity/config/infrastructure/flash_wifi_config.cpp new file mode 100644 index 0000000..fcd644a --- /dev/null +++ b/firmware/src/connectivity/config/infrastructure/flash_wifi_config.cpp @@ -0,0 +1,25 @@ +#include "connectivity/config/infrastructure/flash_wifi_config.hpp" + +namespace connectivity::config { + +FlashWifiConfig::FlashWifiConfig(flash_memory::FlashService &flash, + const WifiConfigKeys &keys) + : _flash(flash), _keys(keys) {} + +esp_err_t FlashWifiConfig::getSsid(std::string &out) const { + return _flash.read(_keys.ns, _keys.ssid, out); +} + +esp_err_t FlashWifiConfig::setSsid(const std::string &value) { + return _flash.write(_keys.ns, _keys.ssid, value); +} + +esp_err_t FlashWifiConfig::getPassword(std::string &out) const { + return _flash.read(_keys.ns, _keys.password, out); +} + +esp_err_t FlashWifiConfig::setPassword(const std::string &value) { + return _flash.write(_keys.ns, _keys.password, value); +} + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp b/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp new file mode 100644 index 0000000..e3dc75e --- /dev/null +++ b/firmware/src/connectivity/config/infrastructure/flash_wifi_config.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "connectivity/config/domain/i_wifi_config.hpp" +#include "peripherals/flash_memory/application/flash_service.hpp" + +namespace connectivity::config { + +/** + * @brief NVS namespace and keys for the WiFi configuration fields. + * + * Injected at construction so the adapter holds no hard-coded configuration; + * main.cpp supplies the values from the central config header. + */ +struct WifiConfigKeys { + const char *ns; + const char *ssid; + const char *password; +}; + +/** + * @brief Flash-backed adapter for WiFi configuration. + * + * Implements @c IWifiConfig on top of the flash_memory service. Each operation + * is a direct read-through / write-through to NVS (no in-RAM cache), mapping + * each WiFi field to its injected NVS key. + */ +class FlashWifiConfig : public IWifiConfig { + public: + /** + * @brief Builds the adapter over a flash service. + * + * @param flash Reference to the flash service. Must outlive this instance. + * @param keys NVS namespace and keys for the WiFi fields. + */ + FlashWifiConfig(flash_memory::FlashService &flash, + const WifiConfigKeys &keys); + + esp_err_t getSsid(std::string &out) const override; + esp_err_t setSsid(const std::string &value) override; + + esp_err_t getPassword(std::string &out) const override; + esp_err_t setPassword(const std::string &value) override; + + private: + flash_memory::FlashService &_flash; + WifiConfigKeys _keys; +}; + +} // namespace connectivity::config diff --git a/firmware/src/connectivity/provisioning/application/provisioning_service.cpp b/firmware/src/connectivity/provisioning/application/provisioning_service.cpp new file mode 100644 index 0000000..e91737d --- /dev/null +++ b/firmware/src/connectivity/provisioning/application/provisioning_service.cpp @@ -0,0 +1,64 @@ +#include "connectivity/provisioning/application/provisioning_service.hpp" + +#include + +namespace connectivity::provisioning { + +namespace { + +// Wraps a getter so a not-yet-provisioned field reads back as an empty value +// instead of surfacing ESP_ERR_NVS_NOT_FOUND over BLE. +ReadHandler readThrough(std::function getter) { + return [getter](std::string &out) -> esp_err_t { + esp_err_t err = getter(out); + if (err == ESP_ERR_NVS_NOT_FOUND) { + out.clear(); + + return ESP_OK; + } + + return err; + }; +} + +} // namespace + +ProvisioningService::ProvisioningService(config::IDeviceConfig &device, + config::IWifiConfig &wifi, + config::IMqttConfig &mqtt, + const ProvisioningUuids &uuids) + : _device(device), _wifi(wifi), _mqtt(mqtt), _uuids(uuids) {} + +ServiceSpec ProvisioningService::buildService() const { + config::IDeviceConfig *device = &_device; + config::IWifiConfig *wifi = &_wifi; + config::IMqttConfig *mqtt = &_mqtt; + + ServiceSpec service; + service.uuid = _uuids.service; + service.characteristics = { + {_uuids.deviceId, "device_id", true, false, + readThrough([device](std::string &o) { return device->getId(o); }), + nullptr}, + {_uuids.wifiSsid, "wifi_ssid", true, true, + readThrough([wifi](std::string &o) { return wifi->getSsid(o); }), + [wifi](const std::string &v) { return wifi->setSsid(v); }}, + {_uuids.wifiPassword, "wifi_password", false, true, nullptr, + [wifi](const std::string &v) { return wifi->setPassword(v); }}, + {_uuids.mqttHost, "mqtt_host", true, true, + readThrough([mqtt](std::string &o) { return mqtt->getHost(o); }), + [mqtt](const std::string &v) { return mqtt->setHost(v); }}, + {_uuids.mqttPort, "mqtt_port", true, true, + readThrough([mqtt](std::string &o) { return mqtt->getPort(o); }), + [mqtt](const std::string &v) { return mqtt->setPort(v); }}, + {_uuids.mqttUser, "mqtt_user", true, true, + readThrough([mqtt](std::string &o) { return mqtt->getUser(o); }), + [mqtt](const std::string &v) { return mqtt->setUser(v); }}, + {_uuids.mqttPassword, "mqtt_password", false, true, nullptr, + [mqtt](const std::string &v) { return mqtt->setPassword(v); }}, + }; + + return service; +} + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/application/provisioning_service.hpp b/firmware/src/connectivity/provisioning/application/provisioning_service.hpp new file mode 100644 index 0000000..4555076 --- /dev/null +++ b/firmware/src/connectivity/provisioning/application/provisioning_service.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include "connectivity/config/domain/i_device_config.hpp" +#include "connectivity/config/domain/i_mqtt_config.hpp" +#include "connectivity/config/domain/i_wifi_config.hpp" +#include "connectivity/provisioning/domain/i_gatt_server.hpp" + +namespace connectivity::provisioning { + +/** + * @brief 128-bit UUIDs for the provisioning service and its characteristics. + * + * Injected at construction so the module holds no hard-coded protocol values; + * main.cpp supplies them from the central config header. + */ +struct ProvisioningUuids { + std::array service; + std::array deviceId; + std::array wifiSsid; + std::array wifiPassword; + std::array mqttHost; + std::array mqttPort; + std::array mqttUser; + std::array mqttPassword; +}; + +/** + * @brief Application service that defines the provisioning GATT contract. + * + * Builds the @c ServiceSpec exposing WiFi and MQTT parameters as + * characteristics, binding each one's read/write handlers to the configuration + * store. Read handlers are read-through to flash; write handlers are + * write-through to flash. Passwords are write-only at the BLE layer (no read + * handler), even though they remain readable from flash for the WiFi/MQTT + * modules. + */ +class ProvisioningService { + public: + /** + * @brief Builds the service over the configuration ports. + * + * @param device Device identity store (exposes device_id, read-only). + * @param wifi WiFi configuration store. Must outlive this instance. + * @param mqtt MQTT configuration store. Must outlive this instance. + * @param uuids UUIDs for the service and characteristics. + */ + ProvisioningService(config::IDeviceConfig &device, config::IWifiConfig &wifi, + config::IMqttConfig &mqtt, + const ProvisioningUuids &uuids); + + /** + * @brief Assembles the GATT service description. + * + * @return A @c ServiceSpec with the provisioning characteristics and their + * handlers wired to the configuration store. + */ + ServiceSpec buildService() const; + + private: + config::IDeviceConfig &_device; + config::IWifiConfig &_wifi; + config::IMqttConfig &_mqtt; + ProvisioningUuids _uuids; +}; + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/domain/i_gatt_server.hpp b/firmware/src/connectivity/provisioning/domain/i_gatt_server.hpp new file mode 100644 index 0000000..a77da0f --- /dev/null +++ b/firmware/src/connectivity/provisioning/domain/i_gatt_server.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace connectivity::provisioning { + +/// Produces the value returned on a characteristic read. +using ReadHandler = std::function; + +/// Consumes the value received on a characteristic write. +using WriteHandler = std::function; + +/** + * @brief Transport-agnostic description of a GATT characteristic. + * + * Carries its 128-bit UUID, a human-readable name (exposed as the 0x2901 user + * descriptor), the access flags and the handlers invoked on read/write. A + * handler is only used when the matching flag is set. + */ +struct CharacteristicSpec { + std::array uuid; + std::string name; + bool readable; + bool writable; + ReadHandler onRead; + WriteHandler onWrite; +}; + +/** + * @brief Transport-agnostic description of a GATT service. + */ +struct ServiceSpec { + std::array uuid; + std::vector characteristics; +}; + +/** + * @brief Port for a startable/stoppable GATT server. + * + * Keeps the BLE stack out of the application layer: the provisioning service + * builds a @c ServiceSpec and any adapter (NimBLE here) realizes it. + */ +class IGattServer { + public: + virtual ~IGattServer() = default; + + /** + * @brief Brings the server up and starts advertising. + * + * @param deviceName Advertised device name. + * @param service Service and characteristics to expose. + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t start(const std::string &deviceName, + const ServiceSpec &service) = 0; + + /** + * @brief Stops advertising and drops active connections. + * + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + virtual esp_err_t stop() = 0; +}; + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.cpp b/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.cpp new file mode 100644 index 0000000..2cb9f74 --- /dev/null +++ b/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.cpp @@ -0,0 +1,315 @@ +#include "connectivity/provisioning/infrastructure/nimble_gatt_server.hpp" + +#include + +#include +#include + +#include "connectivity/provisioning/tag.hpp" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + +namespace connectivity::provisioning { + +namespace { + +// There is a single provisioning server per device, so the NimBLE host state +// lives here at file scope. The GATT table is built once and must outlive the +// host: the C structures below are kept alive for the program's lifetime. +ServiceSpec g_service; +std::vector g_uuids; // [0]=service, [1..]=chars +std::vector g_chrDefs; // n + 1 (zero terminator) +std::vector> g_dscDefs; // 2 each (desc + term) +ble_gatt_svc_def g_svcDefs[2]; +const ble_uuid16_t g_userDescUuid = BLE_UUID16_INIT(0x2901); + +std::string g_deviceName; +bool g_hostInitialized = false; +bool g_advertising = false; +std::uint8_t g_ownAddrType = 0; +std::uint16_t g_connHandle = BLE_HS_CONN_HANDLE_NONE; + +ble_uuid128_t makeUuid128(const std::array &bytes) { + ble_uuid128_t uuid = {}; + uuid.u.type = BLE_UUID_TYPE_128; + std::memcpy(uuid.value, bytes.data(), bytes.size()); + + return uuid; +} + +// Dispatches a characteristic read/write to its spec handlers (arg points to +// the owning CharacteristicSpec). +int accessCb(std::uint16_t, std::uint16_t, struct ble_gatt_access_ctxt *ctxt, + void *arg) { + auto *spec = static_cast(arg); + + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: { + std::string value; + if (spec->onRead) { + esp_err_t err = spec->onRead(value); + if (err != ESP_OK) { + ESP_LOGE(TAG, "read '%s' failed: %s", spec->name.c_str(), + esp_err_to_name(err)); + + return BLE_ATT_ERR_UNLIKELY; + } + } + int rc = os_mbuf_append(ctxt->om, value.data(), value.size()); + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + case BLE_GATT_ACCESS_OP_WRITE_CHR: { + std::uint16_t len = OS_MBUF_PKTLEN(ctxt->om); + std::string value(len, '\0'); + std::uint16_t outLen = 0; + int rc = ble_hs_mbuf_to_flat(ctxt->om, value.data(), len, &outLen); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + value.resize(outLen); + if (spec->onWrite) { + esp_err_t err = spec->onWrite(value); + if (err != ESP_OK) { + ESP_LOGE(TAG, "write '%s' failed: %s", spec->name.c_str(), + esp_err_to_name(err)); + + return BLE_ATT_ERR_UNLIKELY; + } + } + ESP_LOGI(TAG, "characteristic '%s' updated (%u bytes)", + spec->name.c_str(), static_cast(outLen)); + + return 0; + } + default: + return BLE_ATT_ERR_UNLIKELY; + } +} + +// Serves the 0x2901 user description (the characteristic name in arg). +int dscCb(std::uint16_t, std::uint16_t, struct ble_gatt_access_ctxt *ctxt, + void *arg) { + if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { + const char *name = static_cast(arg); + int rc = os_mbuf_append(ctxt->om, name, std::strlen(name)); + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + return BLE_ATT_ERR_UNLIKELY; +} + +// Builds the NimBLE service/characteristic/descriptor tables from g_service. +// Pointers handed to NimBLE reference the file-scope containers, so they must +// not be reallocated afterwards. +void buildGattTable() { + const std::size_t n = g_service.characteristics.size(); + + g_uuids.clear(); + g_uuids.reserve(n + 1); + g_uuids.push_back(makeUuid128(g_service.uuid)); + for (std::size_t i = 0; i < n; ++i) { + g_uuids.push_back(makeUuid128(g_service.characteristics[i].uuid)); + } + + g_dscDefs.assign(n, std::vector(2, ble_gatt_dsc_def{})); + g_chrDefs.assign(n + 1, ble_gatt_chr_def{}); + for (std::size_t i = 0; i < n; ++i) { + CharacteristicSpec &c = g_service.characteristics[i]; + + g_dscDefs[i][0].uuid = &g_userDescUuid.u; + g_dscDefs[i][0].att_flags = BLE_ATT_F_READ; + g_dscDefs[i][0].access_cb = dscCb; + g_dscDefs[i][0].arg = const_cast(c.name.c_str()); + + ble_gatt_chr_flags flags = 0; + if (c.readable) { + flags |= BLE_GATT_CHR_F_READ; + } + if (c.writable) { + flags |= BLE_GATT_CHR_F_WRITE; + } + + g_chrDefs[i].uuid = &g_uuids[i + 1].u; + g_chrDefs[i].access_cb = accessCb; + g_chrDefs[i].arg = &c; + g_chrDefs[i].flags = flags; + g_chrDefs[i].descriptors = g_dscDefs[i].data(); + } + + g_svcDefs[0] = ble_gatt_svc_def{}; + g_svcDefs[0].type = BLE_GATT_SVC_TYPE_PRIMARY; + g_svcDefs[0].uuid = &g_uuids[0].u; + g_svcDefs[0].characteristics = g_chrDefs.data(); + g_svcDefs[1] = ble_gatt_svc_def{}; +} + +int gapEventCb(struct ble_gap_event *event, void *arg); + +void startAdvertising() { + struct ble_hs_adv_fields fields = {}; + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + + const char *name = ble_svc_gap_device_name(); + fields.name = reinterpret_cast(const_cast(name)); + fields.name_len = name ? std::strlen(name) : 0; + fields.name_is_complete = 1; + + int rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gap_adv_set_fields failed: rc=%d", rc); + + return; + } + + struct ble_gap_adv_params adv_params = {}; + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + + rc = ble_gap_adv_start(g_ownAddrType, nullptr, BLE_HS_FOREVER, &adv_params, + gapEventCb, nullptr); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gap_adv_start failed: rc=%d", rc); + + return; + } + + ESP_LOGI(TAG, "advertising as '%s'", name ? name : ""); +} + +int gapEventCb(struct ble_gap_event *event, void *) { + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status == 0) { + g_connHandle = event->connect.conn_handle; + ESP_LOGI(TAG, "client connected"); + } else if (g_advertising) { + startAdvertising(); + } + + return 0; + case BLE_GAP_EVENT_DISCONNECT: + g_connHandle = BLE_HS_CONN_HANDLE_NONE; + ESP_LOGI(TAG, "client disconnected"); + if (g_advertising) { + startAdvertising(); + } + + return 0; + case BLE_GAP_EVENT_ADV_COMPLETE: + if (g_advertising) { + startAdvertising(); + } + + return 0; + default: + return 0; + } +} + +void onSync() { + int rc = ble_hs_id_infer_auto(0, &g_ownAddrType); + if (rc != 0) { + ESP_LOGE(TAG, "ble_hs_id_infer_auto failed: rc=%d", rc); + + return; + } + + if (g_advertising) { + startAdvertising(); + } +} + +void onReset(int reason) { + ESP_LOGW(TAG, "nimble host reset; reason=%d", reason); +} + +void hostTask(void *) { + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +} // namespace + +esp_err_t NimbleGattServer::start(const std::string &deviceName, + const ServiceSpec &service) { + g_deviceName = deviceName; + g_advertising = true; + + if (g_hostInitialized) { + ble_svc_gap_device_name_set(g_deviceName.c_str()); + startAdvertising(); + + return ESP_OK; + } + + g_service = service; + + esp_err_t err = nimble_port_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed: %s", esp_err_to_name(err)); + + return err; + } + + ble_hs_cfg.sync_cb = onSync; + ble_hs_cfg.reset_cb = onReset; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + + buildGattTable(); + + int rc = ble_gatts_count_cfg(g_svcDefs); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gatts_count_cfg failed: rc=%d", rc); + + return ESP_FAIL; + } + + rc = ble_gatts_add_svcs(g_svcDefs); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gatts_add_svcs failed: rc=%d", rc); + + return ESP_FAIL; + } + + rc = ble_svc_gap_device_name_set(g_deviceName.c_str()); + if (rc != 0) { + ESP_LOGW(TAG, "ble_svc_gap_device_name_set failed: rc=%d", rc); + } + + nimble_port_freertos_init(hostTask); + g_hostInitialized = true; + ESP_LOGI(TAG, "nimble host started"); + + return ESP_OK; +} + +esp_err_t NimbleGattServer::stop() { + g_advertising = false; + + if (!g_hostInitialized) { + return ESP_OK; + } + + int rc = ble_gap_adv_stop(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + ESP_LOGW(TAG, "ble_gap_adv_stop failed: rc=%d", rc); + } + + if (g_connHandle != BLE_HS_CONN_HANDLE_NONE) { + ble_gap_terminate(g_connHandle, BLE_ERR_REM_USER_CONN_TERM); + } + + ESP_LOGI(TAG, "provisioning stopped"); + + return ESP_OK; +} + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp b/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp new file mode 100644 index 0000000..a7a4f2e --- /dev/null +++ b/firmware/src/connectivity/provisioning/infrastructure/nimble_gatt_server.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "connectivity/provisioning/domain/i_gatt_server.hpp" + +namespace connectivity::provisioning { + +/** + * @brief NimBLE adapter implementing the GATT server port. + * + * Realizes a @c ServiceSpec as a NimBLE GATT service and drives connectable, + * undirected advertising. There is a single provisioning server per device, so + * the NimBLE host state is owned at file scope in the implementation; this + * keeps the BLE stack headers out of the rest of the codebase. + * + * No security is configured (plaintext link): characteristics carry no + * encryption/authentication flags and no pairing/bonding is performed. + */ +class NimbleGattServer : public IGattServer { + public: + esp_err_t start(const std::string &deviceName, + const ServiceSpec &service) override; + esp_err_t stop() override; +}; + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/provisioning.cpp b/firmware/src/connectivity/provisioning/provisioning.cpp new file mode 100644 index 0000000..ac149e6 --- /dev/null +++ b/firmware/src/connectivity/provisioning/provisioning.cpp @@ -0,0 +1,41 @@ +#include "connectivity/provisioning/provisioning.hpp" + +#include + +#include "connectivity/provisioning/tag.hpp" + +namespace connectivity::provisioning { + +Provisioning::Provisioning(config::IDeviceConfig &device, + config::IWifiConfig &wifi, config::IMqttConfig &mqtt, + const ProvisioningUuids &uuids) + : _device(device), _service(device, wifi, mqtt, uuids) {} + +esp_err_t Provisioning::start() { + std::string deviceId; + esp_err_t err = _device.getId(deviceId); + if (err != ESP_OK) { + ESP_LOGE(TAG, "device_id unavailable (%s); cannot start provisioning", + esp_err_to_name(err)); + + return err; + } + + if (deviceId.empty()) { + ESP_LOGE(TAG, "device_id is empty; cannot start provisioning"); + + return ESP_ERR_INVALID_STATE; + } + + std::string projectName; + _device.getProjectName(projectName); + + std::string suffix = deviceId.substr(0, deviceId.find('-')); + std::string deviceName = projectName + "-" + suffix; + + return _server.start(deviceName, _service.buildService()); +} + +esp_err_t Provisioning::stop() { return _server.stop(); } + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/provisioning.hpp b/firmware/src/connectivity/provisioning/provisioning.hpp new file mode 100644 index 0000000..5c2d27b --- /dev/null +++ b/firmware/src/connectivity/provisioning/provisioning.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include + +#include "connectivity/config/domain/i_device_config.hpp" +#include "connectivity/config/domain/i_mqtt_config.hpp" +#include "connectivity/config/domain/i_wifi_config.hpp" +#include "connectivity/provisioning/application/provisioning_service.hpp" +#include "connectivity/provisioning/infrastructure/nimble_gatt_server.hpp" + +namespace connectivity::provisioning { + +/** + * @brief Module facade for BLE provisioning. + * + * Composition root of the provisioning module: owns the GATT server adapter and + * the application service, wiring the configuration ports and UUIDs together. + * Exposes a start/stop lifecycle so provisioning availability can be managed + * flexibly (e.g. provisioning windows, event-triggered activation) in the + * future; for now the application starts it once at boot. + */ +class Provisioning { + public: + /** + * @brief Builds the provisioning module. + * + * @param device Device identity store. Must outlive this instance. + * @param wifi WiFi configuration store. Must outlive this instance. + * @param mqtt MQTT configuration store. Must outlive this instance. + * @param uuids UUIDs for the service and characteristics. + */ + Provisioning(config::IDeviceConfig &device, config::IWifiConfig &wifi, + config::IMqttConfig &mqtt, const ProvisioningUuids &uuids); + + /** + * @brief Brings the BLE server up and starts advertising. + * + * Reads the device_id to build the advertised name (project name plus a short + * device_id suffix). Fails if the device has not been provisioned (no + * device_id), since the device cannot be identified over BLE in that case. + * + * @return ESP_OK on success, ESP_ERR_INVALID_STATE if the device_id is + * missing or empty, or an ESP-IDF error code on failure. + */ + esp_err_t start(); + + /** + * @brief Stops advertising and drops active connections. + * + * @return ESP_OK on success, or an ESP-IDF error code on failure. + */ + esp_err_t stop(); + + private: + config::IDeviceConfig &_device; + ProvisioningService _service; + NimbleGattServer _server; +}; + +} // namespace connectivity::provisioning diff --git a/firmware/src/connectivity/provisioning/tag.hpp b/firmware/src/connectivity/provisioning/tag.hpp new file mode 100644 index 0000000..3702f96 --- /dev/null +++ b/firmware/src/connectivity/provisioning/tag.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace connectivity::provisioning { + +constexpr const char *TAG = "connectivity/provisioning"; + +} // namespace connectivity::provisioning diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index a9d6b2a..51779c8 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -2,40 +2,57 @@ #include #include -#include - +#include "build.hpp" #include "config.hpp" +#include "connectivity/config/infrastructure/flash_device_config.hpp" +#include "connectivity/config/infrastructure/flash_mqtt_config.hpp" +#include "connectivity/config/infrastructure/flash_wifi_config.hpp" +#include "connectivity/provisioning/provisioning.hpp" #include "peripherals/flash_memory/flash_memory.hpp" #include "peripherals/led/led.hpp" #include "peripherals/relay/relay.hpp" -#include "setup.hpp" + +static const char *TAG = "app/main"; static flash_memory::FlashMemory flashMemory; +static connectivity::config::FlashDeviceConfig deviceConfig( + flashMemory, {config::nvs::NAMESPACE, config::device::ID_KEY, + config::device::PROJECT_NAME}); +static connectivity::config::FlashWifiConfig wifiConfig( + flashMemory, {config::nvs::NAMESPACE, config::wifi::SSID_KEY, + config::wifi::PASSWORD_KEY}); +static connectivity::config::FlashMqttConfig mqttConfig( + flashMemory, + {config::nvs::NAMESPACE, config::mqtt::HOST_KEY, config::mqtt::PORT_KEY, + config::mqtt::USER_KEY, config::mqtt::PASSWORD_KEY}); static relay::Relay loadRelay(config::relay::GPIO, config::relay::INVERTED); static led::Led builtinLed(config::builtin_led::GPIO, config::builtin_led::INVERTED); - -static const char *TAG = "app/main"; +static connectivity::provisioning::Provisioning provisioning( + deviceConfig, wifiConfig, mqttConfig, + {.service = config::ble::SERVICE_UUID, + .deviceId = config::ble::DEVICE_ID_UUID, + .wifiSsid = config::ble::WIFI_SSID_UUID, + .wifiPassword = config::ble::WIFI_PASSWORD_UUID, + .mqttHost = config::ble::MQTT_HOST_UUID, + .mqttPort = config::ble::MQTT_PORT_UUID, + .mqttUser = config::ble::MQTT_USER_UUID, + .mqttPassword = config::ble::MQTT_PASSWORD_UUID}); extern "C" void app_main() { - if (setup(flashMemory, loadRelay, builtinLed) != ESP_OK) { + if (beginPeripherals(flashMemory, loadRelay, builtinLed) != ESP_OK) { ESP_LOGE(TAG, "setup failed; halting"); + return; } - std::string deviceId; - esp_err_t err = flashMemory.read(config::device::NVS_NAMESPACE, - config::device::DEVICE_ID_KEY, deviceId); + esp_err_t err = provisioning.start(); if (err != ESP_OK) { - ESP_LOGW(TAG, "device_id unavailable (%s); provision the device", - esp_err_to_name(err)); - } else { - ESP_LOGI(TAG, "device_id: %s", deviceId.c_str()); + ESP_LOGE(TAG, "provisioning start failed: %s", esp_err_to_name(err)); } // Idle loop: keeps app_main alive until the real application logic is added. while (true) { - ESP_LOGI(TAG, "Device ID: %s", deviceId.c_str()); vTaskDelay(pdMS_TO_TICKS(1000)); } } diff --git a/firmware/src/peripherals/digital_actuator/application/digital_actuator_service.hpp b/firmware/src/peripherals/digital_actuator/application/digital_actuator_service.hpp index 38844c3..d12c032 100644 --- a/firmware/src/peripherals/digital_actuator/application/digital_actuator_service.hpp +++ b/firmware/src/peripherals/digital_actuator/application/digital_actuator_service.hpp @@ -20,19 +20,14 @@ class DigitalActuatorService { */ DigitalActuatorService(IDigitalActuator &driver); - virtual ~DigitalActuatorService() = default; - /** * @brief Drives the line to the given logical level. * - * Virtual so concrete peripherals (relay, led, ...) can add their own - * logging while reusing the base behavior. - * * @param level Logical level to apply. * @return ESP_OK on success, ESP_ERR_INVALID_STATE if the driver is not * initialized, or an ESP-IDF error code on failure. */ - virtual esp_err_t setLevel(Level level); + esp_err_t setLevel(Level level); /** * @brief Returns the current logical level.