mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-06-15 04:14:28 +02:00
435 lines
11 KiB
C
435 lines
11 KiB
C
/*******************************************************************************
|
|
*
|
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
|
*
|
|
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
|
*
|
|
* Licensed under MIT License
|
|
* https://opensource.org/licenses/MIT
|
|
*
|
|
* High-level C API for ttn-esp32.
|
|
*******************************************************************************/
|
|
|
|
#include "lmic/lmic.h"
|
|
#include "ttn.h"
|
|
#include "ttn_provisioning.h"
|
|
#include "ttn_logging.h"
|
|
#include "hal/hal_esp32.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "esp_event.h"
|
|
#include "esp_log.h"
|
|
|
|
|
|
#define TAG "ttn"
|
|
|
|
|
|
/**
|
|
* @brief Reason the user code is waiting
|
|
*/
|
|
typedef enum
|
|
{
|
|
TTN_WAITING_NONE,
|
|
TTN_WAITING_FOR_JOIN,
|
|
TTN_WAITING_FOR_TRANSMISSION
|
|
} ttn_waiting_reason_t;
|
|
|
|
/**
|
|
* @brief Event type
|
|
*/
|
|
typedef enum {
|
|
TTN_EVENT_NONE,
|
|
TTN_EVNT_JOIN_COMPLETED,
|
|
TTN_EVENT_JOIN_FAILED,
|
|
TTN_EVENT_MESSAGE_RECEIVED,
|
|
TTN_EVENT_TRANSMISSION_COMPLETED,
|
|
TTN_EVENT_TRANSMISSION_FAILED
|
|
} ttn_event_t;
|
|
|
|
/**
|
|
* @brief Event message sent from LMIC task to waiting client task
|
|
*/
|
|
typedef struct {
|
|
ttn_event_t event;
|
|
uint8_t port;
|
|
const uint8_t* message;
|
|
size_t message_size;
|
|
} ttn_lmic_event_t;
|
|
|
|
static QueueHandle_t lmic_event_queue;
|
|
static ttn_message_cb message_callback;
|
|
static ttn_waiting_reason_t waiting_reason = TTN_WAITING_NONE;
|
|
static ttn_rf_settings_t last_rf_settings[4];
|
|
static ttn_rx_tx_window_t current_rx_tx_window;
|
|
static int subband = 1;
|
|
|
|
static bool join_core(void);
|
|
static void event_callback(void* user_data, ev_t event);
|
|
static void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size);
|
|
static void message_transmitted_callback(void *user_data, int success);
|
|
static void save_rf_settings(ttn_rf_settings_t* rf_settings);
|
|
static void clear_rf_settings(ttn_rf_settings_t* rf_settings);
|
|
|
|
|
|
void ttn_init(void)
|
|
{
|
|
#if defined(TTN_IS_DISABLED)
|
|
ESP_LOGE(TAG, "TTN is disabled. Configure a frequency plan using 'make menuconfig'");
|
|
ASSERT(0);
|
|
#endif
|
|
|
|
message_callback = NULL;
|
|
hal_esp32_init_critical_section();
|
|
}
|
|
|
|
void ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
|
|
{
|
|
hal_esp32_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1);
|
|
|
|
#if LMIC_ENABLE_event_logging
|
|
ttn_log_init();
|
|
#endif
|
|
|
|
LMIC_registerEventCb(event_callback, NULL);
|
|
LMIC_registerRxMessageCb(message_received_callback, NULL);
|
|
|
|
os_init_ex(NULL);
|
|
ttn_reset();
|
|
|
|
lmic_event_queue = xQueueCreate(4, sizeof(ttn_lmic_event_t));
|
|
ASSERT(lmic_event_queue != NULL);
|
|
hal_esp32_start_lmic_task();
|
|
}
|
|
|
|
void ttn_set_subband(int band)
|
|
{
|
|
subband = band;
|
|
}
|
|
|
|
void ttn_reset(void)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_reset();
|
|
LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100);
|
|
waiting_reason = TTN_WAITING_NONE;
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
|
|
void ttn_shutdown(void)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_shutdown();
|
|
hal_esp32_stop_lmic_task();
|
|
waiting_reason = TTN_WAITING_NONE;
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
|
|
void ttn_startup(void)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_reset();
|
|
hal_esp32_start_lmic_task();
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
|
|
bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key)
|
|
{
|
|
if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key))
|
|
return false;
|
|
|
|
return ttn_provisioning_save_keys();
|
|
}
|
|
|
|
bool ttn_provision_with_mac(const char *app_eui, const char *app_key)
|
|
{
|
|
if (!ttn_provisioning_from_mac(app_eui, app_key))
|
|
return false;
|
|
|
|
return ttn_provisioning_save_keys();
|
|
}
|
|
|
|
|
|
void ttn_start_provisioning_task(void)
|
|
{
|
|
#if defined(TTN_HAS_AT_COMMANDS)
|
|
ttn_provisioning_start_task();
|
|
#else
|
|
ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'");
|
|
ASSERT(0);
|
|
esp_restart();
|
|
#endif
|
|
}
|
|
|
|
void ttn_wait_for_provisioning(void)
|
|
{
|
|
#if defined(TTN_HAS_AT_COMMANDS)
|
|
if (ttn_is_provisioned())
|
|
{
|
|
ESP_LOGI(TAG, "Device is already provisioned");
|
|
return;
|
|
}
|
|
|
|
while (!ttn_provisioning_have_keys())
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
|
|
ESP_LOGI(TAG, "Device successfully provisioned");
|
|
#else
|
|
ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'");
|
|
ASSERT(0);
|
|
esp_restart();
|
|
#endif
|
|
}
|
|
|
|
bool ttn_join(const char *dev_eui, const char *app_eui, const char *app_key)
|
|
{
|
|
if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key))
|
|
return false;
|
|
|
|
return join_core();
|
|
}
|
|
|
|
bool ttn_join_provisioned(void)
|
|
{
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
if (!ttn_provisioning_restore_keys(false))
|
|
return false;
|
|
}
|
|
|
|
return join_core();
|
|
}
|
|
|
|
bool join_core()
|
|
{
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
|
return false;
|
|
}
|
|
|
|
hal_esp32_enter_critical_section();
|
|
xQueueReset(lmic_event_queue);
|
|
waiting_reason = TTN_WAITING_FOR_JOIN;
|
|
|
|
#if defined(CFG_us915) || defined(CFG_au915)
|
|
if (subband != 0)
|
|
LMIC_selectSubBand(subband - 1);
|
|
#endif
|
|
|
|
LMIC_startJoining();
|
|
hal_esp32_wake_up();
|
|
hal_esp32_leave_critical_section();
|
|
|
|
ttn_lmic_event_t event;
|
|
xQueueReceive(lmic_event_queue, &event, portMAX_DELAY);
|
|
return event.event == TTN_EVNT_JOIN_COMPLETED;
|
|
}
|
|
|
|
ttn_response_code_t ttn_transmit_message(const uint8_t *payload, size_t length, ttn_port_t port, bool confirm)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
if (waiting_reason != TTN_WAITING_NONE || (LMIC.opmode & OP_TXRXPEND) != 0)
|
|
{
|
|
hal_esp32_leave_critical_section();
|
|
return TTN_ERROR_TRANSMISSION_FAILED;
|
|
}
|
|
|
|
waiting_reason = TTN_WAITING_FOR_TRANSMISSION;
|
|
LMIC.client.txMessageCb = message_transmitted_callback;
|
|
LMIC.client.txMessageUserData = NULL;
|
|
LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm);
|
|
hal_esp32_wake_up();
|
|
hal_esp32_leave_critical_section();
|
|
|
|
while (true)
|
|
{
|
|
ttn_lmic_event_t result;
|
|
xQueueReceive(lmic_event_queue, &result, portMAX_DELAY);
|
|
|
|
switch (result.event)
|
|
{
|
|
case TTN_EVENT_MESSAGE_RECEIVED:
|
|
if (message_callback != NULL)
|
|
message_callback(result.message, result.message_size, result.port);
|
|
break;
|
|
|
|
case TTN_EVENT_TRANSMISSION_COMPLETED:
|
|
return TTN_SUCCESSFUL_TRANSMISSION;
|
|
|
|
case TTN_EVENT_TRANSMISSION_FAILED:
|
|
return TTN_ERROR_TRANSMISSION_FAILED;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ttn_on_message(ttn_message_cb callback)
|
|
{
|
|
message_callback = callback;
|
|
}
|
|
|
|
|
|
bool ttn_is_provisioned(void)
|
|
{
|
|
if (ttn_provisioning_have_keys())
|
|
return true;
|
|
|
|
ttn_provisioning_restore_keys(true);
|
|
|
|
return ttn_provisioning_have_keys();
|
|
}
|
|
|
|
void ttn_set_rssi_cal(int8_t rssi_cal)
|
|
{
|
|
hal_esp32_set_rssi_cal(rssi_cal);
|
|
}
|
|
|
|
bool ttn_adr_enabled(void)
|
|
{
|
|
return LMIC.adrEnabled != 0;
|
|
}
|
|
|
|
void ttn_set_adr_nabled(bool enabled)
|
|
{
|
|
LMIC_setAdrMode(enabled);
|
|
}
|
|
|
|
ttn_rf_settings_t ttn_getrf_settings(ttn_rx_tx_window_t window)
|
|
{
|
|
int index = ((int)window) & 0x03;
|
|
return last_rf_settings[index];
|
|
}
|
|
|
|
ttn_rf_settings_t ttn_tx_settings(void)
|
|
{
|
|
return last_rf_settings[TTN_WINDOW_TX];
|
|
}
|
|
|
|
ttn_rf_settings_t ttn_rx1_settings(void)
|
|
{
|
|
return last_rf_settings[TTN_WINDOW_RX1];
|
|
}
|
|
|
|
ttn_rf_settings_t ttn_rx2_settings(void)
|
|
{
|
|
return last_rf_settings[TTN_WINDOW_RX2];
|
|
}
|
|
|
|
ttn_rx_tx_window_t ttn_rx_tx_window(void)
|
|
{
|
|
return current_rx_tx_window;
|
|
}
|
|
|
|
int ttn_rssi(void)
|
|
{
|
|
return LMIC.rssi;
|
|
}
|
|
|
|
|
|
// --- Callbacks ---
|
|
|
|
#if CONFIG_LOG_DEFAULT_LEVEL >= 3 || LMIC_ENABLE_event_logging
|
|
static const char *event_names[] = { LMIC_EVENT_NAME_TABLE__INIT };
|
|
#endif
|
|
|
|
|
|
// Called by LMIC when an LMIC event (join, join failed, reset etc.) occurs
|
|
void event_callback(void* user_data, ev_t event)
|
|
{
|
|
// update monitoring information
|
|
switch(event)
|
|
{
|
|
case EV_TXSTART:
|
|
current_rx_tx_window = TTN_WINDOW_TX;
|
|
save_rf_settings(&last_rf_settings[TTN_WINDOW_TX]);
|
|
clear_rf_settings(&last_rf_settings[TTN_WINDOW_RX1]);
|
|
clear_rf_settings(&last_rf_settings[TTN_WINDOW_RX2]);
|
|
break;
|
|
|
|
case EV_RXSTART:
|
|
if (current_rx_tx_window != TTN_WINDOW_RX1)
|
|
{
|
|
current_rx_tx_window = TTN_WINDOW_RX1;
|
|
save_rf_settings(&last_rf_settings[TTN_WINDOW_RX1]);
|
|
}
|
|
else
|
|
{
|
|
current_rx_tx_window = TTN_WINDOW_RX2;
|
|
save_rf_settings(&last_rf_settings[TTN_WINDOW_RX2]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
current_rx_tx_window = TTN_WINDOW_IDLE;
|
|
break;
|
|
};
|
|
|
|
#if LMIC_ENABLE_event_logging
|
|
ttn_log_event(event, event_names[event], 0);
|
|
#elif CONFIG_LOG_DEFAULT_LEVEL >= 3
|
|
ESP_LOGI(TAG, "event %s", event_names[event]);
|
|
#endif
|
|
|
|
ttn_event_t ttn_event = TTN_EVENT_NONE;
|
|
|
|
if (waiting_reason == TTN_WAITING_FOR_JOIN)
|
|
{
|
|
if (event == EV_JOINED)
|
|
{
|
|
ttn_event = TTN_EVNT_JOIN_COMPLETED;
|
|
}
|
|
else if (event == EV_REJOIN_FAILED || event == EV_RESET)
|
|
{
|
|
ttn_event = TTN_EVENT_JOIN_FAILED;
|
|
}
|
|
}
|
|
|
|
if (ttn_event == TTN_EVENT_NONE)
|
|
return;
|
|
|
|
ttn_lmic_event_t result = {
|
|
.event = ttn_event
|
|
};
|
|
waiting_reason = TTN_WAITING_NONE;
|
|
xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
// Called by LMIC when a message has been received
|
|
void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size)
|
|
{
|
|
ttn_lmic_event_t result = {
|
|
.event = TTN_EVENT_MESSAGE_RECEIVED,
|
|
.port = port,
|
|
.message = message,
|
|
.message_size = message_size
|
|
};
|
|
xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
// Called by LMIC when a message has been transmitted (or the transmission failed)
|
|
void message_transmitted_callback(void *user_data, int success)
|
|
{
|
|
waiting_reason = TTN_WAITING_NONE;
|
|
ttn_lmic_event_t result = {
|
|
.event = success ? TTN_EVENT_TRANSMISSION_COMPLETED : TTN_EVENT_TRANSMISSION_FAILED
|
|
};
|
|
xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
|
|
// --- Helpers
|
|
|
|
|
|
void save_rf_settings(ttn_rf_settings_t* rf_settings)
|
|
{
|
|
rf_settings->spreading_factor = (ttn_spreading_factor_t)(getSf(LMIC.rps) + 1);
|
|
rf_settings->bandwidth = (ttn_bandwidth_t)(getBw(LMIC.rps) + 1);
|
|
rf_settings->frequency = LMIC.freq;
|
|
}
|
|
|
|
void clear_rf_settings(ttn_rf_settings_t* rf_settings)
|
|
{
|
|
memset(rf_settings, 0, sizeof(*rf_settings));
|
|
}
|