ttn-esp32/src/TheThingsNetwork.cpp
2018-07-17 22:07:35 +02:00

253 lines
6.2 KiB
C++

/*******************************************************************************
* Copyright (c) 2018 Manuel Bleichenbacher
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This the hardware abstraction layer to run LMIC in on ESP32 using ESP-iDF.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "TheThingsNetwork.h"
#include "esp_log.h"
#include "oslmic.h"
#include "hal.h"
#include "hal_esp32.h"
#include "lmic.h"
static const char *TAG = "ttn";
static TheThingsNetwork* ttnInstance;
static uint8_t devEui[8];
static uint8_t appEui[8];
static uint8_t appKey[16];
static QueueHandle_t result_queue;
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()
{
ASSERT(ttnInstance == NULL);
ttnInstance = this;
hal_initCriticalSection();
}
TheThingsNetwork::~TheThingsNetwork()
{
// nothing to do
}
void TheThingsNetwork::configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
{
lmic_pins.spi_host = spi_host;
lmic_pins.nss = nss;
lmic_pins.rxtx = rxtx;
lmic_pins.rst = rst;
lmic_pins.dio0 = dio0;
lmic_pins.dio1 = dio1;
os_init();
reset();
result_queue = xQueueCreate(12, sizeof(int));
assert(result_queue != NULL);
hal_startBgTask();
}
void TheThingsNetwork::reset()
{
hal_enterCriticalSection();
LMIC_reset();
hal_leaveCriticalSection();
}
bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const char *appKey)
{
return decodeKeys(devEui, appEui, appKey);
}
bool TheThingsNetwork::decodeKeys(const char *devEui, const char *appEui, const char *appKey)
{
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;
}
return true;
}
bool TheThingsNetwork::join(const char *devEui, const char *appEui, const char *appKey)
{
if (!decodeKeys(devEui, appEui, appKey))
return false;
return join();
}
bool TheThingsNetwork::join()
{
hal_enterCriticalSection();
LMIC_startJoining();
hal_wakeUp();
hal_leaveCriticalSection();
int result = 0;
while (true)
{
xQueueReceive(result_queue, &result, portMAX_DELAY);
if (result != EV_JOINING)
break;
}
return result == EV_JOINED;
}
ttn_response_t TheThingsNetwork::sendBytes(const uint8_t *payload, size_t length, port_t port, bool confirm)
{
hal_enterCriticalSection();
if (LMIC.opmode & OP_TXRXPEND)
{
hal_leaveCriticalSection();
return TTN_ERROR_SEND_COMMAND_FAILED;
}
LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm);
hal_wakeUp();
hal_leaveCriticalSection();
int result = 0;
xQueueReceive(result_queue, &result, portMAX_DELAY);
return result == EV_TXCOMPLETE ? TTN_SUCCESSFUL_TRANSMISSION : TTN_ERROR_SEND_COMMAND_FAILED;
}
// --- LMIC functions ---
#if CONFIG_LOG_DEFAULT_LEVEL >= 3
static const char *eventNames[] = {
NULL,
"EV_SCAN_TIMEOUT", "EV_BEACON_FOUND",
"EV_BEACON_MISSED", "EV_BEACON_TRACKED", "EV_JOINING",
"EV_JOINED", "EV_RFU1", "EV_JOIN_FAILED", "EV_REJOIN_FAILED",
"EV_TXCOMPLETE", "EV_LOST_TSYNC", "EV_RESET",
"EV_RXCOMPLETE", "EV_LINK_DEAD", "EV_LINK_ALIVE"
};
#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]);
#endif
if (ev == EV_TXCOMPLETE) {
if (LMIC.txrxFlags & TXRX_ACK)
ESP_LOGI(TAG, "Received ack\n");
if (LMIC.dataLen)
ESP_LOGI(TAG, "Received %d bytes of payload\n", LMIC.dataLen);
}
int result = ev;
xQueueSend(result_queue, &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--;
}
}