diff --git a/.vscode/settings.json b/.vscode/settings.json index 28b96ed..050206a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,8 @@ "config.h": "c", "sdkconfig.h": "c", "oslmic.h": "c", - "hal_esp32.h": "c" + "hal_esp32.h": "c", + "esp_log.h": "c", + "nvs_flash.h": "c" } } \ No newline at end of file diff --git a/examples/mac_address/Makefile b/examples/mac_address/Makefile new file mode 100644 index 0000000..58130c9 --- /dev/null +++ b/examples/mac_address/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := mac_address + +include $(IDF_PATH)/make/project.mk diff --git a/examples/mac_address/main/component.mk b/examples/mac_address/main/component.mk new file mode 100644 index 0000000..e69de29 diff --git a/examples/mac_address/main/main.cpp b/examples/mac_address/main/main.cpp new file mode 100644 index 0000000..b8a45db --- /dev/null +++ b/examples/mac_address/main/main.cpp @@ -0,0 +1,96 @@ +/******************************************************************************* + * + * 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 + * + * Sample program showing how to provision the keys from the built-in MAC. + *******************************************************************************/ + +#include "freertos/FreeRTOS.h" +#include "esp_event.h" +#include "nvs_flash.h" + +#include "TheThingsNetwork.h" + +// NOTE: +// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'. +// Go to Components / The Things Network, select the appropriate values and save. + +// Copy the below two lines from the bottom +// of your device's overview page in the TTN console. +const char *appEui = "????????????????"; +const char *appKey = "????????????????????????????????"; + +// Pins and other resources +#define TTN_SPI_HOST HSPI_HOST +#define TTN_SPI_DMA_CHAN 1 +#define TTN_PIN_SPI_SCLK 5 +#define TTN_PIN_SPI_MOSI 27 +#define TTN_PIN_SPI_MISO 19 +#define TTN_PIN_NSS 18 +#define TTN_PIN_RXTX TTN_NOT_CONNECTED +#define TTN_PIN_RST TTN_NOT_CONNECTED +#define TTN_PIN_DIO0 26 +#define TTN_PIN_DIO1 33 + +static TheThingsNetwork ttn; + +const unsigned TX_INTERVAL = 30; +static uint8_t msgData[] = "Hello, world"; + + +void sendMessages(void* pvParameter) +{ + while (1) { + printf("Sending message...\n"); + TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1); + printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n"); + + vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); + } +} + +extern "C" void app_main(void) +{ + esp_err_t err; + // Initialize the GPIO ISR handler service + err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + ESP_ERROR_CHECK(err); + + // Initialize the NVS (non-volatile storage) for saving and restoring the keys + err = nvs_flash_init(); + ESP_ERROR_CHECK(err); + + // Initialize SPI bus + spi_bus_config_t spi_bus_config; + spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO; + spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI; + spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK; + spi_bus_config.quadwp_io_num = -1; + spi_bus_config.quadhd_io_num = -1; + spi_bus_config.max_transfer_sz = 0; + err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN); + ESP_ERROR_CHECK(err); + + // Configure the SX127x pins + ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1); + + ttn.provisionWithMAC(appEui, appKey); + + ttn.startProvisioningTask(); + + printf("Joining...\n"); + if (ttn.join()) + { + printf("Joined.\n"); + xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL); + } + else + { + printf("Join failed. Goodbye\n"); + } +} diff --git a/include/TheThingsNetwork.h b/include/TheThingsNetwork.h index 291ca8c..1c4b126 100644 --- a/include/TheThingsNetwork.h +++ b/include/TheThingsNetwork.h @@ -104,6 +104,32 @@ public: */ bool provision(const char *devEui, const char *appEui, const char *appKey); + /** + * @brief Sets the information needed to activate the device via OTAA, using the MAC to generate the device EUI + * and without actually activating. + * + * The generated device EUI and the provided app EUI and app key are saved in non-volatile memory. Before + * this function is called, 'nvs_flash_init' must have been called once. + * + * The device EUI is generated by retrieving the ESP32's WiFi MAC address and expanding it into a device EUI + * by adding FFFE in the middle. So the MAC address A0:B1:C2:01:02:03 becomes the EUI A0:B1:C2:FF:FE:01:02:03. + * The TTN console requires the device EUI in MSB format, so it has to be reversed and entered as + * "03 02 01 FE FF C2 B1 A0". + * + * Generating the device EUI from the MAC address allows to flash the same app EUI and app key to a batch of + * devices. However, using the same app key for multiple devices is insecure. Only use this approach if + * it is okay for that the LoRa communication of your application can easily be intercepted and that + * forged data can be injected. + * + * Call join() without arguments to activate. + * + * @param appEui Application EUI of the device (16 character string with hexadecimal data) + * @param appKey App Key of the device (32 character string with hexadecimal data) + * @return true if the provisioning was successful + * @return false if the provisioning failed + */ + bool provisionWithMAC(const char *appEui, const char *appKey); + /** * @brief Start task that listens on configured UART for provisioning commands. * diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp index 6eff020..8a4a005 100644 --- a/src/TheThingsNetwork.cpp +++ b/src/TheThingsNetwork.cpp @@ -86,6 +86,14 @@ bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const c return provisioning_save_keys(); } +bool TheThingsNetwork::provisionWithMAC(const char *appEui, const char *appKey) +{ + if (!provisioning_from_mac(appEui, appKey)) + return false; + + return provisioning_save_keys(); +} + void TheThingsNetwork::startProvisioningTask() { #if !defined(CONFIG_TTN_PROVISION_UART_NONE) diff --git a/src/provisioning.c b/src/provisioning.c index aeedb7c..cb3958d 100644 --- a/src/provisioning.c +++ b/src/provisioning.c @@ -14,6 +14,7 @@ #include "driver/uart.h" #include "esp_event.h" #include "esp_log.h" +#include "esp_system.h" #include "nvs_flash.h" #include "provisioning.h" #include "lmic.h" @@ -28,6 +29,7 @@ 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 bool provisioning_decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key); static void provisioning_task(void* pvParameter); static void provisioning_add_line_data(int numBytes); static void provisioning_detect_line_end(int start_at); @@ -195,6 +197,7 @@ void provisioning_process_line() // Expected format: // AT+PROV? // AT+PROV=hex16-hex16-hex32 + // AT+MAC? if (strcmp(line_buf, "AT+PROV?") == 0) { uint8_t binbuf[8]; @@ -233,6 +236,22 @@ void provisioning_process_line() is_ok = false; } } + else if (strcmp(line_buf, "AT+MAC?") == 0) + { + uint8_t mac[6]; + char hexbuf[12]; + + esp_err_t err = esp_efuse_mac_get_default(mac); + ESP_ERROR_CHECK(err); + + bin_to_hex_str(mac, 6, hexbuf); + for (int i = 0; i < 12; i += 2) { + if (i > 0) + uart_write_bytes(UART_NUM, ":", 1); + uart_write_bytes(UART_NUM, hexbuf + i, 2); + } + uart_write_bytes(UART_NUM, "\r\n", 2); + } else if (strcmp(line_buf, "AT+PROVQ") == 0) { quit_task = true; @@ -276,18 +295,42 @@ bool provisioning_have_keys() } bool provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key) +{ + return provisioning_decode(true, dev_eui, app_eui, app_key); +} + +bool provisioning_from_mac(const char *app_eui, const char *app_key) +{ + uint8_t mac[6]; + esp_err_t err = esp_efuse_mac_get_default(mac); + ESP_ERROR_CHECK(err); + + global_dev_eui[0] = mac[0]; + global_dev_eui[1] = mac[1]; + global_dev_eui[2] = mac[2]; + global_dev_eui[3] = 0xff; + global_dev_eui[4] = 0xfe; + global_dev_eui[5] = mac[3]; + global_dev_eui[6] = mac[4]; + global_dev_eui[7] = mac[5]; + + return provisioning_decode(false, NULL, app_eui, app_key); +} + +bool provisioning_decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key) { uint8_t buf_dev_eui[8]; uint8_t buf_app_eui[8]; uint8_t buf_app_key[16]; - if (strlen(dev_eui) != 16 || !hex_str_to_bin(dev_eui, buf_dev_eui, 8)) + if (incl_dev_eui && (strlen(dev_eui) != 16 || !hex_str_to_bin(dev_eui, buf_dev_eui, 8))) { ESP_LOGW(TAG, "Invalid device EUI: %s", dev_eui); return false; } - swap_bytes(buf_dev_eui, 8); + if (incl_dev_eui) + swap_bytes(buf_dev_eui, 8); if (strlen(app_eui) != 16 || !hex_str_to_bin(app_eui, buf_app_eui, 8)) { @@ -303,7 +346,8 @@ bool provisioning_decode_keys(const char *dev_eui, const char *app_eui, const ch return false; } - memcpy(global_dev_eui, buf_dev_eui, sizeof(global_dev_eui)); + if (incl_dev_eui) + memcpy(global_dev_eui, buf_dev_eui, sizeof(global_dev_eui)); memcpy(global_app_eui, buf_app_eui, sizeof(global_app_eui)); memcpy(global_app_key, buf_app_key, sizeof(global_app_key)); diff --git a/src/provisioning.h b/src/provisioning.h index 3d1900f..fa9fe12 100644 --- a/src/provisioning.h +++ b/src/provisioning.h @@ -24,6 +24,7 @@ extern "C" { 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_from_mac(const char *app_eui, const char *app_key); bool provisioning_save_keys(); bool provisioning_restore_keys(bool silent);