mirror of
				https://github.com/manuelbl/ttn-esp32.git
				synced 2025-10-31 10:40:35 +01:00 
			
		
		
		
	Add provisioning task, refactor provisioning
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,5 @@ | ||||
| build/ | ||||
| sdkconfig | ||||
| sdkconfig.old | ||||
| dev_keys.txt | ||||
| ttn-esp32 | ||||
|  | ||||
							
								
								
									
										8
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| { | ||||
|     "files.associations": { | ||||
|         "provisioning.h": "c", | ||||
|         "config.h": "c", | ||||
|         "sdkconfig.h": "c", | ||||
|         "oslmic.h": "c" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										71
									
								
								Kconfig
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								Kconfig
									
									
									
									
									
								
							| @ -68,4 +68,75 @@ config TTN_BG_TASK_PRIO | ||||
|         the LoRaWAN radio chip. It needs a high priority as the timing is crucial. | ||||
|         Higher numbers indicate higher priority. | ||||
|  | ||||
|  | ||||
| choice TTN_PROVISION_UART | ||||
|     prompt "UART for provisioning" | ||||
|     default TTN_PROVISION_UART_DEFAULT | ||||
|     help | ||||
|         Select whether to use UART for listening for provisioning commands. | ||||
|          | ||||
|         - Default is to use UART0 on pins GPIO1(TX) and GPIO3(RX). | ||||
|         - If "Custom" is selected, UART0 or UART1 can be chosen, | ||||
|           and any pins can be selected. | ||||
|         - If "None" is selected, the feature is not available. | ||||
|  | ||||
| config TTN_PROVISION_UART_DEFAULT | ||||
|     bool "Default: UART0, TX=GPIO1, RX=GPIO3, 115,200 baud" | ||||
| config TTN_PROVISION_UART_CUSTOM | ||||
|     bool "Custom" | ||||
| config TTN_PROVISION_UART_NONE | ||||
|     bool "None" | ||||
| endchoice | ||||
|  | ||||
| choice TTN_PROVISION_UART_INIT | ||||
|     prompt "Initialize UART" | ||||
|     default TTN_PROVISION_UART_INIT_NO | ||||
|     depends on !TTN_PROVISION_UART_NONE | ||||
|     help | ||||
|         Select whether to initialize the UART, i.e. set the baud rate, the RX and TX | ||||
|         pins. If the UART is shared with other features (e.g. the console), it | ||||
|         should not be initialized.  | ||||
|  | ||||
| config TTN_PROVISION_UART_INIT_NO | ||||
|     bool "No" | ||||
| config TTN_PROVISION_UART_INIT_YES | ||||
|     bool "Yes" | ||||
| endchoice | ||||
|  | ||||
| choice TTN_PROVISION_UART_NUM | ||||
|     prompt "UART peripheral for provisioning (0-1)" | ||||
|     depends on TTN_PROVISION_UART_CUSTOM | ||||
|     default TTN_PROVISION_UART_CUSTOM_NUM_0 | ||||
|  | ||||
| config TTN_PROVISION_UART_CUSTOM_NUM_0 | ||||
|     bool "UART0" | ||||
| config TTN_PROVISION_UART_CUSTOM_NUM_1 | ||||
|     bool "UART1" | ||||
| endchoice | ||||
|  | ||||
| config TTN_PROVISION_UART_NUM | ||||
|     int | ||||
|     default 0 if TTN_PROVISION_UART_DEFAULT || TTN_PROVISION_UART_NONE | ||||
|     default 0 if TTN_PROVISION_UART_CUSTOM_NUM_0 | ||||
|     default 1 if TTN_PROVISION_UART_CUSTOM_NUM_1 | ||||
|  | ||||
| config TTN_PROVISION_UART_TX_GPIO | ||||
|     int "Provisioning UART TX on GPIO#" | ||||
|     depends on TTN_PROVISION_UART_CUSTOM && TTN_PROVISION_UART_INIT_YES | ||||
|     range 0 33 | ||||
|     default 19 | ||||
|  | ||||
| config TTN_PROVISION_UART_RX_GPIO | ||||
|     int "Provisioning UART RX on GPIO#" | ||||
|     depends on TTN_PROVISION_UART_CUSTOM && TTN_PROVISION_UART_INIT_YES | ||||
|     range 0 39 | ||||
|     default 21 | ||||
|  | ||||
| config TTN_PROVISION_UART_BAUDRATE | ||||
|     int "Provisioning UART baud rate" | ||||
|     depends on TTN_PROVISION_UART_CUSTOM && TTN_PROVISION_UART_INIT_YES | ||||
|     default 115200 | ||||
|     range 1200 4000000 | ||||
|  | ||||
|  | ||||
| endmenu | ||||
|  | ||||
| @ -104,6 +104,13 @@ public: | ||||
|      */ | ||||
|     bool provision(const char *devEui, const char *appEui, const char *appKey); | ||||
|  | ||||
|     /** | ||||
|      * @brief Start task that listens on configured UART for provisioning commands. | ||||
|      *  | ||||
|      * Run 'make menuconfig' to configure it. | ||||
|      */ | ||||
|     void startProvisioningTask(); | ||||
|  | ||||
|      /** | ||||
|      * @brief Activate the device via OTAA. | ||||
|      *  | ||||
| @ -176,12 +183,8 @@ public: | ||||
|  | ||||
| private: | ||||
|     TTNMessageCallback messageCallback; | ||||
|     bool haveKeys; | ||||
|  | ||||
|     bool joinCore(); | ||||
|     bool decodeKeys(const char *devEui, const char *appEui, const char *appKey); | ||||
|     bool saveKeys(); | ||||
|     bool restoreKeys(bool silent); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
| @ -12,13 +12,13 @@ | ||||
|  | ||||
| #include "freertos/FreeRTOS.h" | ||||
| #include "esp_event.h" | ||||
| #include "nvs_flash.h" | ||||
| #include "TheThingsNetwork.h" | ||||
| #include "esp_log.h" | ||||
| #include "oslmic.h" | ||||
| #include "TheThingsNetwork.h" | ||||
| #include "hal.h" | ||||
| #include "hal_esp32.h" | ||||
| #include "lmic.h" | ||||
| #include "provisioning.h" | ||||
|  | ||||
|  | ||||
| enum ClientAction | ||||
| { | ||||
| @ -28,29 +28,14 @@ enum ClientAction | ||||
| }; | ||||
|  | ||||
| static const char *TAG = "ttn"; | ||||
| static const char *NVS_FLASH_PARTITION = "ttn"; | ||||
| static const char *NVS_FLASH_KEY_DEV_EUI = "devEui"; | ||||
| static const char *NVS_FLASH_KEY_APP_EUI = "appEui"; | ||||
| static const char *NVS_FLASH_KEY_APP_KEY = "appKey"; | ||||
|  | ||||
| static TheThingsNetwork* ttnInstance; | ||||
| static uint8_t devEui[8]; | ||||
| static uint8_t appEui[8]; | ||||
| static uint8_t appKey[16]; | ||||
| static QueueHandle_t resultQueue; | ||||
| static ClientAction clientAction = eActionUnrelated; | ||||
|  | ||||
|  | ||||
| static bool readNvsValue(nvs_handle handle, const char* key, uint8_t* data, size_t expectedLength, bool silent); | ||||
| static bool writeNvsValue(nvs_handle handle, const char* key, const uint8_t* data, size_t len); | ||||
| static bool hexStringToBin(const char *hex, uint8_t *buf, int len); | ||||
| static int hexTupleToByte(const char *hex); | ||||
| static int hexDigitToVal(char ch); | ||||
| static void swapByteOrder(uint8_t* buf, int len); | ||||
|  | ||||
|  | ||||
| TheThingsNetwork::TheThingsNetwork() | ||||
|     : messageCallback(NULL), haveKeys(false) | ||||
|     : messageCallback(NULL) | ||||
| { | ||||
|     ASSERT(ttnInstance == NULL); | ||||
|     ttnInstance = this; | ||||
| @ -89,45 +74,23 @@ void TheThingsNetwork::reset() | ||||
|  | ||||
| bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const char *appKey) | ||||
| { | ||||
|     if (!decodeKeys(devEui, appEui, appKey)) | ||||
|     if (!provisioning_decode_keys(devEui, appEui, appKey)) | ||||
|         return false; | ||||
|      | ||||
|     return saveKeys(); | ||||
|     return provisioning_save_keys(); | ||||
| } | ||||
|  | ||||
| bool TheThingsNetwork::decodeKeys(const char *devEui, const char *appEui, const char *appKey) | ||||
| void TheThingsNetwork::startProvisioningTask() | ||||
| { | ||||
|     haveKeys = false; | ||||
|  | ||||
|     if (strlen(devEui) != 16 || !hexStringToBin(devEui, ::devEui, 8)) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Invalid device EUI: %s", devEui); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     swapByteOrder(::devEui, 8); | ||||
|  | ||||
|     if (strlen(appEui) != 16 || !hexStringToBin(appEui, ::appEui, 8)) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Invalid application EUI: %s", appEui); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     swapByteOrder(::appEui, 8); | ||||
|  | ||||
|     if (strlen(appKey) != 32 || !hexStringToBin(appKey, ::appKey, 16)) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Invalid application key: %s", appEui); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     haveKeys = true; | ||||
|     return true; | ||||
| #if !defined(CONFIG_TTN_PROVISION_UART_NONE) | ||||
|     provisioning_start_task(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
|  | ||||
| bool TheThingsNetwork::join(const char *devEui, const char *appEui, const char *appKey) | ||||
| { | ||||
|     if (!decodeKeys(devEui, appEui, appKey)) | ||||
|     if (!provisioning_decode_keys(devEui, appEui, appKey)) | ||||
|         return false; | ||||
|      | ||||
|     return joinCore(); | ||||
| @ -135,9 +98,9 @@ bool TheThingsNetwork::join(const char *devEui, const char *appEui, const char * | ||||
|  | ||||
| bool TheThingsNetwork::join() | ||||
| { | ||||
|     if (!haveKeys) | ||||
|     if (!provisioning_have_keys()) | ||||
|     { | ||||
|         if (!restoreKeys(false)) | ||||
|         if (!provisioning_restore_keys(false)) | ||||
|             return false; | ||||
|     } | ||||
|  | ||||
| @ -146,7 +109,7 @@ bool TheThingsNetwork::join() | ||||
|  | ||||
| bool TheThingsNetwork::joinCore() | ||||
| { | ||||
|     if (!haveKeys) | ||||
|     if (!provisioning_have_keys()) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Device EUI, App EUI and/or App key have not been provided"); | ||||
|         return false; | ||||
| @ -205,118 +168,13 @@ void TheThingsNetwork::onMessage(TTNMessageCallback callback) | ||||
|     messageCallback = callback; | ||||
| } | ||||
|  | ||||
| bool TheThingsNetwork::saveKeys() | ||||
| { | ||||
|     bool result = false; | ||||
|  | ||||
|     nvs_handle handle = 0; | ||||
|     esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READWRITE, &handle); | ||||
|     if (res == ESP_ERR_NVS_NOT_INITIALIZED) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first."); | ||||
|         goto done; | ||||
|     } | ||||
|     ESP_ERROR_CHECK(res); | ||||
|     if (res != ESP_OK) | ||||
|         goto done; | ||||
|  | ||||
|     if (!writeNvsValue(handle, NVS_FLASH_KEY_DEV_EUI, ::devEui, sizeof(::devEui))) | ||||
|         goto done; | ||||
|          | ||||
|     if (!writeNvsValue(handle, NVS_FLASH_KEY_APP_EUI, ::appEui, sizeof(::appEui))) | ||||
|         goto done; | ||||
|          | ||||
|     if (!writeNvsValue(handle, NVS_FLASH_KEY_APP_KEY, ::appKey, sizeof(::appKey))) | ||||
|         goto done; | ||||
|  | ||||
|     res = nvs_commit(handle); | ||||
|     ESP_ERROR_CHECK(res); | ||||
|      | ||||
|     result = true; | ||||
|     ESP_LOGI(TAG, "Dev and app EUI and app key saved in NVS storage"); | ||||
|  | ||||
| done: | ||||
|     nvs_close(handle); | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| bool TheThingsNetwork::restoreKeys(bool silent) | ||||
| { | ||||
|     haveKeys = false; | ||||
|      | ||||
|     nvs_handle handle = 0; | ||||
|     esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READONLY, &handle); | ||||
|     if (res == ESP_ERR_NVS_NOT_FOUND) | ||||
|         return false; // partition does not exist yet | ||||
|     if (res == ESP_ERR_NVS_NOT_INITIALIZED) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first."); | ||||
|         goto done; | ||||
|     } | ||||
|     ESP_ERROR_CHECK(res); | ||||
|     if (res != ESP_OK) | ||||
|         goto done; | ||||
|  | ||||
|     if (!readNvsValue(handle, NVS_FLASH_KEY_DEV_EUI, ::devEui, sizeof(::devEui), silent)) | ||||
|         goto done; | ||||
|  | ||||
|     if (!readNvsValue(handle, NVS_FLASH_KEY_APP_EUI, ::appEui, sizeof(::appEui), silent)) | ||||
|         goto done; | ||||
|  | ||||
|     if (!readNvsValue(handle, NVS_FLASH_KEY_APP_KEY, ::appKey, sizeof(::appKey), silent)) | ||||
|         goto done; | ||||
|  | ||||
|     haveKeys = true; | ||||
|     ESP_LOGI(TAG, "Dev and app EUI and app key have been restored from NVS storage"); | ||||
|  | ||||
| done: | ||||
|     nvs_close(handle); | ||||
|     return haveKeys; | ||||
| } | ||||
|  | ||||
| bool TheThingsNetwork::isProvisioned() | ||||
| { | ||||
|     if (haveKeys) | ||||
|     if (provisioning_have_keys()) | ||||
|         return true; | ||||
|      | ||||
|     return restoreKeys(true); | ||||
| } | ||||
|  | ||||
| bool readNvsValue(nvs_handle handle, const char* key, uint8_t* data, size_t expectedLength, bool silent) | ||||
| { | ||||
|     size_t size = expectedLength; | ||||
|     esp_err_t res = nvs_get_blob(handle, key, data, &size); | ||||
|     if (res == ESP_OK && size == expectedLength) | ||||
|         return true; | ||||
|  | ||||
|     if (res == ESP_OK && size != expectedLength) | ||||
|     { | ||||
|         if (!silent) | ||||
|             ESP_LOGW(TAG, "Invalid size of NVS data for %s", key); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (res == ESP_ERR_NVS_NOT_FOUND) | ||||
|     { | ||||
|         if (!silent) | ||||
|             ESP_LOGW(TAG, "No NVS data found for %s", key); | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     ESP_ERROR_CHECK(res); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool writeNvsValue(nvs_handle handle, const char* key, const uint8_t* data, size_t len) | ||||
| { | ||||
|     uint8_t buf[16]; | ||||
|     if (readNvsValue(handle, key, buf, len, true) && memcmp(buf, data, len) == 0) | ||||
|         return true; // unchanged | ||||
|      | ||||
|     esp_err_t res = nvs_set_blob(handle, key, data, len); | ||||
|     ESP_ERROR_CHECK(res); | ||||
|  | ||||
|     return res == ESP_OK; | ||||
|     return provisioning_restore_keys(true); | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -333,29 +191,6 @@ static const char *eventNames[] = { | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| // This EUI must be in little-endian format, so least-significant-byte first. | ||||
| // When copying an EUI from ttnctl output, this means to reverse the bytes. | ||||
| // For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70. | ||||
| // The order is swapped in TheThingsNetwork::provision(). | ||||
| void os_getArtEui (u1_t* buf) | ||||
| { | ||||
|     memcpy(buf, appEui, 8); | ||||
| } | ||||
|  | ||||
| // This should also be in little endian format, see above. | ||||
| void os_getDevEui (u1_t* buf) | ||||
| { | ||||
|     memcpy(buf, devEui, 8); | ||||
| } | ||||
|  | ||||
| // This key should be in big endian format (or, since it is not really a number | ||||
| // but a block of memory, endianness does not really apply). In practice, a key | ||||
| // taken from ttnctl can be copied as-is. | ||||
| void os_getDevKey (u1_t* buf) | ||||
| { | ||||
|     memcpy(buf, appKey, 16); | ||||
| } | ||||
|  | ||||
| void onEvent (ev_t ev) { | ||||
|     #if CONFIG_LOG_DEFAULT_LEVEL >= 3 | ||||
|         ESP_LOGI(TAG, "event %s", eventNames[ev]); | ||||
| @ -385,56 +220,3 @@ void onEvent (ev_t ev) { | ||||
|     clientAction = eActionUnrelated; | ||||
|     xQueueSend(resultQueue, &result, 100 / portTICK_PERIOD_MS); | ||||
| } | ||||
|  | ||||
|  | ||||
| // --- Helper functions --- | ||||
|  | ||||
| bool hexStringToBin(const char *hex, uint8_t *buf, int len) | ||||
| { | ||||
|     const char* ptr = hex; | ||||
|     for (int i = 0; i < len; i++) | ||||
|     { | ||||
|         int val = hexTupleToByte(ptr); | ||||
|         if (val < 0) | ||||
|             return false; | ||||
|         buf[i] = val; | ||||
|         ptr += 2; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| int hexTupleToByte(const char *hex) | ||||
| { | ||||
|     int nibble1 = hexDigitToVal(hex[0]); | ||||
|     if (nibble1 < 0) | ||||
|         return -1; | ||||
|     int nibble2 = hexDigitToVal(hex[1]); | ||||
|     if (nibble2 < 0) | ||||
|         return -1; | ||||
|     return (nibble1 << 4) | nibble2; | ||||
| } | ||||
|  | ||||
| int hexDigitToVal(char ch) | ||||
| { | ||||
|     if (ch >= '0' && ch <= '9') | ||||
|         return ch - '0'; | ||||
|     if (ch >= 'A' && ch <= 'F') | ||||
|         return ch + 10 - 'A'; | ||||
|     if (ch >= 'a' && ch <= 'f') | ||||
|         return ch + 10 - 'a'; | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| void swapByteOrder(uint8_t* buf, int len) | ||||
| { | ||||
|     uint8_t* p1 = buf; | ||||
|     uint8_t* p2 = buf + len - 1; | ||||
|     while (p1 < p2) | ||||
|     { | ||||
|         uint8_t t = *p1; | ||||
|         *p1 = *p2; | ||||
|         *p2 = t; | ||||
|         p1++; | ||||
|         p2--; | ||||
|     } | ||||
| } | ||||
| @ -28,6 +28,9 @@ | ||||
| #ifndef _hal_hpp_ | ||||
| #define _hal_hpp_ | ||||
|  | ||||
| #include "oslmic.h" | ||||
|  | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										453
									
								
								src/provisioning.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										453
									
								
								src/provisioning.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,453 @@ | ||||
| /******************************************************************************* | ||||
|  *  | ||||
|  * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x | ||||
|  *  | ||||
|  * Copyright (c) 2018 Manuel Bleichenbacher | ||||
|  *  | ||||
|  * Licensed under MIT License | ||||
|  * https://opensource.org/licenses/MIT | ||||
|  * | ||||
|  * Task listening on a UART port for provisioning commands. | ||||
|  *******************************************************************************/ | ||||
|  | ||||
| #include "freertos/FreeRTOS.h" | ||||
| #include "driver/uart.h" | ||||
| #include "esp_event.h" | ||||
| #include "esp_log.h" | ||||
| #include "nvs_flash.h" | ||||
| #include "provisioning.h" | ||||
|  | ||||
| #define UART_NUM CONFIG_TTN_PROVISION_UART_NUM | ||||
| #define MAX_LINE_LENGTH 128 | ||||
|  | ||||
| static const char *TAG = "ttn_prov"; | ||||
| static const char *NVS_FLASH_PARTITION = "ttn"; | ||||
| static const char *NVS_FLASH_KEY_DEV_EUI = "devEui"; | ||||
| static const char *NVS_FLASH_KEY_APP_EUI = "appEui"; | ||||
| static const char *NVS_FLASH_KEY_APP_KEY = "appKey"; | ||||
|  | ||||
| static void provisioning_task(void* pvParameter); | ||||
| static void provisioning_add_line_data(int numBytes); | ||||
| static void provisioning_detect_line_end(int start_at); | ||||
| static void provisioning_process_line(); | ||||
| static bool read_nvs_value(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent); | ||||
| static bool write_nvs_value(nvs_handle handle, const char* key, const uint8_t* data, size_t len); | ||||
| static bool hex_str_to_bin(const char *hex, uint8_t *buf, int len); | ||||
| static int hex_tuple_to_byte(const char *hex); | ||||
| static int hex_digit_to_val(char ch); | ||||
| static void bin_to_hex_str(const uint8_t* buf, int len, char* hex); | ||||
| static char val_to_hex_digit(int val); | ||||
| static void swap_bytes(uint8_t* buf, int len); | ||||
|  | ||||
|  | ||||
| static QueueHandle_t uart_queue = NULL; | ||||
| static char* line_buf; | ||||
| static int line_length; | ||||
| static uint8_t last_line_end_char = 0; | ||||
| static uint8_t global_dev_eui[8]; | ||||
| static uint8_t global_app_eui[8]; | ||||
| static uint8_t global_app_key[16]; | ||||
| static bool have_keys = false; | ||||
|  | ||||
|  | ||||
| #if defined(CONFIG_TTN_PROVISION_UART_INIT_YES) | ||||
| static void provisioning_init_uart(); | ||||
| #endif | ||||
|  | ||||
|  | ||||
| // --- LMIC callbacks | ||||
|  | ||||
| // This EUI must be in little-endian format, so least-significant-byte first. | ||||
| // When copying an EUI from ttnctl output, this means to reverse the bytes. | ||||
| // For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70. | ||||
| // The order is swapped in provisioning_decode_keys(). | ||||
| void os_getArtEui (u1_t* buf) | ||||
| { | ||||
|     memcpy(buf, global_app_eui, 8); | ||||
| } | ||||
|  | ||||
| // This should also be in little endian format, see above. | ||||
| void os_getDevEui (u1_t* buf) | ||||
| { | ||||
|     memcpy(buf, global_dev_eui, 8); | ||||
| } | ||||
|  | ||||
| // This key should be in big endian format (or, since it is not really a number | ||||
| // but a block of memory, endianness does not really apply). In practice, a key | ||||
| // taken from ttnctl can be copied as-is. | ||||
| void os_getDevKey (u1_t* buf) | ||||
| { | ||||
|     memcpy(buf, global_app_key, 16); | ||||
| } | ||||
|  | ||||
|  | ||||
| // --- Provisioning task | ||||
|  | ||||
| void provisioning_start_task() | ||||
| { | ||||
| #if defined(CONFIG_TTN_PROVISION_UART_INIT_YES) | ||||
|     provisioning_init_uart(); | ||||
| #endif | ||||
|  | ||||
|     esp_err_t err = uart_driver_install(UART_NUM, 2048, 2048, 20, &uart_queue, 0); | ||||
|     ESP_ERROR_CHECK(err); | ||||
|  | ||||
|     xTaskCreate(provisioning_task, "provisioning", 2048, NULL, 1, NULL); | ||||
| } | ||||
|  | ||||
| void provisioning_task(void* pvParameter) | ||||
| { | ||||
|     line_buf = (char*)malloc(MAX_LINE_LENGTH + 1); | ||||
|     line_length = 0; | ||||
|  | ||||
|     uart_event_t event; | ||||
|  | ||||
|     ESP_LOGI(TAG, "Provisioning task started"); | ||||
|  | ||||
|     while (true) | ||||
|     { | ||||
|         if (!xQueueReceive(uart_queue, &event, portMAX_DELAY)) | ||||
|             continue; | ||||
|  | ||||
|         switch (event.type) | ||||
|         { | ||||
|             case UART_DATA: | ||||
|                 provisioning_add_line_data(event.size); | ||||
|                 break; | ||||
|  | ||||
|             case UART_FIFO_OVF: | ||||
|             case UART_BUFFER_FULL: | ||||
|                 uart_flush_input(UART_NUM); | ||||
|                 xQueueReset(uart_queue); | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void provisioning_add_line_data(int numBytes) | ||||
| { | ||||
|     int n; | ||||
| top: | ||||
|     n = numBytes; | ||||
|     if (line_length + n > MAX_LINE_LENGTH) | ||||
|         n = MAX_LINE_LENGTH - line_length; | ||||
|      | ||||
|     uart_read_bytes(UART_NUM, (uint8_t*)line_buf + line_length, n, portMAX_DELAY); | ||||
|     int start_at = line_length; | ||||
|     line_length += n; | ||||
|  | ||||
|     provisioning_detect_line_end(start_at); | ||||
|  | ||||
|     if (n < numBytes) | ||||
|     { | ||||
|         numBytes -= n; | ||||
|         goto top; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void provisioning_detect_line_end(int start_at) | ||||
| { | ||||
| top: | ||||
|     for (int p = start_at; p < line_length; p++) | ||||
|     { | ||||
|         char ch = line_buf[p]; | ||||
|         if (ch == 0x0d || ch == 0x0a) | ||||
|         { | ||||
|             if (p > 0) | ||||
|                 uart_write_bytes(UART_NUM, line_buf + start_at, line_length - start_at - 1); | ||||
|             if (p > 0 || ch == 0x0d || last_line_end_char == 0x0a) | ||||
|                 uart_write_bytes(UART_NUM, "\r\n", 2); | ||||
|  | ||||
|             line_buf[p] = 0; | ||||
|             last_line_end_char = ch; | ||||
|  | ||||
|             if (p > 0) | ||||
|                 provisioning_process_line(); | ||||
|  | ||||
|             memcpy(line_buf, line_buf + p + 1, line_length - p - 1); | ||||
|             line_length -= p + 1; | ||||
|             start_at = 0; | ||||
|             goto top; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (line_length > 0) | ||||
|         uart_write_bytes(UART_NUM, line_buf + start_at, line_length - start_at); | ||||
|  | ||||
|     if (line_length == MAX_LINE_LENGTH) | ||||
|         line_length = 0; // Line too long; flush it | ||||
| } | ||||
|  | ||||
| void provisioning_process_line() | ||||
| { | ||||
|     bool is_ok = true; | ||||
|     // Expected format: | ||||
|     // AT+PROV? | ||||
|     // AT+PROV=hex16-hex16-hex32 | ||||
|     if (strcmp(line_buf, "AT+PROV?") == 0) | ||||
|     { | ||||
|         uint8_t binbuf[8]; | ||||
|         char hexbuf[16]; | ||||
|  | ||||
|         memcpy(binbuf, global_dev_eui, 8); | ||||
|         swap_bytes(binbuf, 8); | ||||
|         bin_to_hex_str(binbuf, 8, hexbuf); | ||||
|         uart_write_bytes(UART_NUM, hexbuf, 16); | ||||
|         uart_write_bytes(UART_NUM, "-", 1); | ||||
|  | ||||
|         memcpy(binbuf, global_app_eui, 8); | ||||
|         swap_bytes(binbuf, 8); | ||||
|         bin_to_hex_str(binbuf, 8, hexbuf); | ||||
|         uart_write_bytes(UART_NUM, hexbuf, 16); | ||||
|  | ||||
|         uart_write_bytes(UART_NUM, "-00000000000000000000000000000000\r\n", 35); | ||||
|     } | ||||
|     else if (strncmp(line_buf, "AT+PROV=", 8) == 0) | ||||
|     { | ||||
|         if (strlen(line_buf) == 74 && line_buf[24] == '-' && line_buf[41] == '-') | ||||
|         { | ||||
|             line_buf[24] = 0; | ||||
|             line_buf[41] = 0; | ||||
|             is_ok = provisioning_decode_keys(line_buf + 8, line_buf + 25, line_buf + 42); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             is_ok = false; | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         is_ok = false; | ||||
|     } | ||||
|  | ||||
|     uart_write_bytes(UART_NUM, is_ok ? "OK\r\n" : "ERROR\r\n", is_ok ? 4 : 7); | ||||
| } | ||||
|  | ||||
| #if defined(CONFIG_TTN_PROVISION_UART_INIT_YES) | ||||
|  | ||||
| void provisioning_init_uart() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
| // --- Key handling | ||||
|  | ||||
| bool provisioning_have_keys() | ||||
| { | ||||
|     return have_keys; | ||||
| } | ||||
|  | ||||
| bool provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key) | ||||
| { | ||||
|     have_keys = false; | ||||
|  | ||||
|     if (strlen(dev_eui) != 16 || !hex_str_to_bin(dev_eui, global_dev_eui, 8)) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Invalid device EUI: %s", dev_eui); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     swap_bytes(global_dev_eui, 8); | ||||
|  | ||||
|     if (strlen(app_eui) != 16 || !hex_str_to_bin(app_eui, global_app_eui, 8)) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Invalid application EUI: %s", app_eui); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     swap_bytes(global_app_eui, 8); | ||||
|  | ||||
|     if (strlen(app_key) != 32 || !hex_str_to_bin(app_key, global_app_key, 16)) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "Invalid application key: %s", app_key); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     have_keys = true; | ||||
|     return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| // --- Non-volatile storage | ||||
|  | ||||
| bool provisioning_save_keys() | ||||
| { | ||||
|     bool result = false; | ||||
|  | ||||
|     nvs_handle handle = 0; | ||||
|     esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READWRITE, &handle); | ||||
|     if (res == ESP_ERR_NVS_NOT_INITIALIZED) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first."); | ||||
|         goto done; | ||||
|     } | ||||
|     ESP_ERROR_CHECK(res); | ||||
|     if (res != ESP_OK) | ||||
|         goto done; | ||||
|  | ||||
|     if (!write_nvs_value(handle, NVS_FLASH_KEY_DEV_EUI, global_dev_eui, sizeof(global_dev_eui))) | ||||
|         goto done; | ||||
|          | ||||
|     if (!write_nvs_value(handle, NVS_FLASH_KEY_APP_EUI, global_app_eui, sizeof(global_app_eui))) | ||||
|         goto done; | ||||
|          | ||||
|     if (!write_nvs_value(handle, NVS_FLASH_KEY_APP_KEY, global_app_key, sizeof(global_app_key))) | ||||
|         goto done; | ||||
|  | ||||
|     res = nvs_commit(handle); | ||||
|     ESP_ERROR_CHECK(res); | ||||
|      | ||||
|     result = true; | ||||
|     ESP_LOGI(TAG, "Dev and app EUI and app key saved in NVS storage"); | ||||
|  | ||||
| done: | ||||
|     nvs_close(handle); | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| bool provisioning_restore_keys(bool silent) | ||||
| { | ||||
|     have_keys = false; | ||||
|      | ||||
|     nvs_handle handle = 0; | ||||
|     esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READONLY, &handle); | ||||
|     if (res == ESP_ERR_NVS_NOT_FOUND) | ||||
|         return false; // partition does not exist yet | ||||
|     if (res == ESP_ERR_NVS_NOT_INITIALIZED) | ||||
|     { | ||||
|         ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first."); | ||||
|         goto done; | ||||
|     } | ||||
|     ESP_ERROR_CHECK(res); | ||||
|     if (res != ESP_OK) | ||||
|         goto done; | ||||
|  | ||||
|     if (!read_nvs_value(handle, NVS_FLASH_KEY_DEV_EUI, global_dev_eui, sizeof(global_dev_eui), silent)) | ||||
|         goto done; | ||||
|  | ||||
|     if (!read_nvs_value(handle, NVS_FLASH_KEY_APP_EUI, global_app_eui, sizeof(global_app_eui), silent)) | ||||
|         goto done; | ||||
|  | ||||
|     if (!read_nvs_value(handle, NVS_FLASH_KEY_APP_KEY, global_app_key, sizeof(global_app_key), silent)) | ||||
|         goto done; | ||||
|  | ||||
|     have_keys = true; | ||||
|     ESP_LOGI(TAG, "Dev and app EUI and app key have been restored from NVS storage"); | ||||
|  | ||||
| done: | ||||
|     nvs_close(handle); | ||||
|     return have_keys; | ||||
| } | ||||
|  | ||||
| bool read_nvs_value(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent) | ||||
| { | ||||
|     size_t size = expected_length; | ||||
|     esp_err_t res = nvs_get_blob(handle, key, data, &size); | ||||
|     if (res == ESP_OK && size == expected_length) | ||||
|         return true; | ||||
|  | ||||
|     if (res == ESP_OK && size != expected_length) | ||||
|     { | ||||
|         if (!silent) | ||||
|             ESP_LOGW(TAG, "Invalid size of NVS data for %s", key); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (res == ESP_ERR_NVS_NOT_FOUND) | ||||
|     { | ||||
|         if (!silent) | ||||
|             ESP_LOGW(TAG, "No NVS data found for %s", key); | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     ESP_ERROR_CHECK(res); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool write_nvs_value(nvs_handle handle, const char* key, const uint8_t* data, size_t len) | ||||
| { | ||||
|     uint8_t buf[16]; | ||||
|     if (read_nvs_value(handle, key, buf, len, true) && memcmp(buf, data, len) == 0) | ||||
|         return true; // unchanged | ||||
|      | ||||
|     esp_err_t res = nvs_set_blob(handle, key, data, len); | ||||
|     ESP_ERROR_CHECK(res); | ||||
|  | ||||
|     return res == ESP_OK; | ||||
| } | ||||
|  | ||||
|  | ||||
| // --- Helper functions --- | ||||
|  | ||||
| bool hex_str_to_bin(const char *hex, uint8_t *buf, int len) | ||||
| { | ||||
|     const char* ptr = hex; | ||||
|     for (int i = 0; i < len; i++) | ||||
|     { | ||||
|         int val = hex_tuple_to_byte(ptr); | ||||
|         if (val < 0) | ||||
|             return false; | ||||
|         buf[i] = val; | ||||
|         ptr += 2; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| int hex_tuple_to_byte(const char *hex) | ||||
| { | ||||
|     int nibble1 = hex_digit_to_val(hex[0]); | ||||
|     if (nibble1 < 0) | ||||
|         return -1; | ||||
|     int nibble2 = hex_digit_to_val(hex[1]); | ||||
|     if (nibble2 < 0) | ||||
|         return -1; | ||||
|     return (nibble1 << 4) | nibble2; | ||||
| } | ||||
|  | ||||
| int hex_digit_to_val(char ch) | ||||
| { | ||||
|     if (ch >= '0' && ch <= '9') | ||||
|         return ch - '0'; | ||||
|     if (ch >= 'A' && ch <= 'F') | ||||
|         return ch + 10 - 'A'; | ||||
|     if (ch >= 'a' && ch <= 'f') | ||||
|         return ch + 10 - 'a'; | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| void bin_to_hex_str(const uint8_t* buf, int len, char* hex) | ||||
| { | ||||
|     for (int i = 0; i < len; i++) | ||||
|     { | ||||
|         uint8_t b = buf[i]; | ||||
|         *hex = val_to_hex_digit((b & 0xf0) >> 4); | ||||
|         hex++; | ||||
|         *hex = val_to_hex_digit(b & 0x0f); | ||||
|         hex++; | ||||
|     } | ||||
| } | ||||
|  | ||||
| char val_to_hex_digit(int val) | ||||
| { | ||||
|     return "0123456789ABCDEF"[val]; | ||||
| } | ||||
|  | ||||
| void swap_bytes(uint8_t* buf, int len) | ||||
| { | ||||
|     uint8_t* p1 = buf; | ||||
|     uint8_t* p2 = buf + len - 1; | ||||
|     while (p1 < p2) | ||||
|     { | ||||
|         uint8_t t = *p1; | ||||
|         *p1 = *p2; | ||||
|         *p2 = t; | ||||
|         p1++; | ||||
|         p2--; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/provisioning.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/provisioning.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| /******************************************************************************* | ||||
|  *  | ||||
|  * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x | ||||
|  *  | ||||
|  * Copyright (c) 2018 Manuel Bleichenbacher | ||||
|  *  | ||||
|  * Licensed under MIT License | ||||
|  * https://opensource.org/licenses/MIT | ||||
|  * | ||||
|  * Task listening on a UART port for provisioning commands. | ||||
|  *******************************************************************************/ | ||||
|  | ||||
| #ifndef _provision_task_h_ | ||||
| #define _provision_task_h_ | ||||
|  | ||||
| #include "oslmic.h" | ||||
|  | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
|  | ||||
| void provisioning_start_task(); | ||||
| bool provisioning_have_keys(); | ||||
| bool provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key); | ||||
| bool provisioning_save_keys(); | ||||
| bool provisioning_restore_keys(bool silent); | ||||
|  | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user