mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2024-09-28 20:03:58 +02:00
253 lines
6.2 KiB
C++
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--;
|
|
}
|
|
} |