mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-06-15 04:14:28 +02:00
Add provisioning task, refactor provisioning
This commit is contained in:
parent
2dd5b05891
commit
b179ce6884
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
|
Loading…
Reference in New Issue
Block a user