mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-05-11 12:24:29 +02:00
563 lines
13 KiB
C
563 lines
13 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 "ttn.h"
|
|
#include "esp_event.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "hal/hal_esp32.h"
|
|
#include "lmic/lmic.h"
|
|
#include "ttn_logging.h"
|
|
#include "ttn_provisioning.h"
|
|
#include "ttn_nvs.h"
|
|
#include "ttn_rtc.h"
|
|
|
|
#define TAG "ttn"
|
|
|
|
#define DEFAULT_MAX_TX_POWER -1000
|
|
|
|
/**
|
|
* @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 bool is_started;
|
|
static bool has_joined;
|
|
static QueueHandle_t lmic_event_queue;
|
|
static ttn_message_cb message_callback;
|
|
static ttn_waiting_reason_t waiting_reason;
|
|
static ttn_rf_settings_t last_rf_settings[4];
|
|
static ttn_rx_tx_window_t current_rx_tx_window;
|
|
static int subband = 2;
|
|
static ttn_data_rate_t join_data_rate = TTN_DR_JOIN_DEFAULT;
|
|
static int max_tx_power = DEFAULT_MAX_TX_POWER;
|
|
|
|
static void start(void);
|
|
static void stop(void);
|
|
static bool join_core(void);
|
|
static void config_rf_params(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
|
|
}
|
|
|
|
void ttn_set_subband(int band)
|
|
{
|
|
subband = band;
|
|
}
|
|
|
|
void start(void)
|
|
{
|
|
if (is_started)
|
|
return;
|
|
|
|
LMIC_registerEventCb(event_callback, NULL);
|
|
LMIC_registerRxMessageCb(message_received_callback, NULL);
|
|
|
|
os_init_ex(NULL);
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_reset();
|
|
LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100);
|
|
waiting_reason = TTN_WAITING_NONE;
|
|
hal_esp32_leave_critical_section();
|
|
|
|
lmic_event_queue = xQueueCreate(4, sizeof(ttn_lmic_event_t));
|
|
ASSERT(lmic_event_queue != NULL);
|
|
hal_esp32_start_lmic_task();
|
|
is_started = true;
|
|
}
|
|
|
|
void stop(void)
|
|
{
|
|
if (!is_started)
|
|
return;
|
|
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_shutdown();
|
|
hal_esp32_stop_lmic_task();
|
|
waiting_reason = TTN_WAITING_NONE;
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
|
|
void ttn_shutdown(void)
|
|
{
|
|
stop();
|
|
}
|
|
|
|
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_transiently(const char *dev_eui, const char *app_eui, const char *app_key)
|
|
{
|
|
return ttn_provisioning_decode_keys(dev_eui, app_eui, app_key);
|
|
}
|
|
|
|
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_with_keys(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(void)
|
|
{
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
if (!ttn_provisioning_restore_keys(false))
|
|
return false;
|
|
}
|
|
|
|
return join_core();
|
|
}
|
|
|
|
bool ttn_resume_after_deep_sleep(void)
|
|
{
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
if (!ttn_provisioning_restore_keys(false))
|
|
return false;
|
|
}
|
|
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
|
return false;
|
|
}
|
|
|
|
start();
|
|
|
|
if (!ttn_rtc_restore())
|
|
return false;
|
|
|
|
has_joined = true;
|
|
return true;
|
|
}
|
|
|
|
bool ttn_resume_after_power_off(int off_duration)
|
|
{
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
if (!ttn_provisioning_restore_keys(false))
|
|
return false;
|
|
}
|
|
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
|
return false;
|
|
}
|
|
|
|
start();
|
|
|
|
if (!ttn_nvs_restore(off_duration))
|
|
return false;
|
|
|
|
has_joined = true;
|
|
return true;
|
|
}
|
|
|
|
// Called immediately before sending join request message
|
|
void config_rf_params(void)
|
|
{
|
|
#if defined(CFG_us915) || defined(CFG_au915)
|
|
if (subband != 0)
|
|
LMIC_selectSubBand(subband - 1);
|
|
#endif
|
|
|
|
if (join_data_rate != TTN_DR_JOIN_DEFAULT || max_tx_power != DEFAULT_MAX_TX_POWER)
|
|
{
|
|
dr_t dr = join_data_rate == TTN_DR_JOIN_DEFAULT ? LMIC.datarate : (dr_t)join_data_rate;
|
|
s1_t txpow = max_tx_power == DEFAULT_MAX_TX_POWER ? LMIC.adrTxPow : max_tx_power;
|
|
LMIC_setDrTxpow(dr, txpow);
|
|
}
|
|
}
|
|
|
|
bool join_core(void)
|
|
{
|
|
if (!ttn_provisioning_have_keys())
|
|
{
|
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
|
return false;
|
|
}
|
|
|
|
start();
|
|
|
|
has_joined = true;
|
|
hal_esp32_enter_critical_section();
|
|
xQueueReset(lmic_event_queue);
|
|
waiting_reason = TTN_WAITING_FOR_JOIN;
|
|
|
|
config_rf_params();
|
|
LMIC_startJoining();
|
|
|
|
hal_esp32_wake_up();
|
|
hal_esp32_leave_critical_section();
|
|
|
|
ttn_lmic_event_t event;
|
|
xQueueReceive(lmic_event_queue, &event, portMAX_DELAY);
|
|
has_joined = event.event == TTN_EVNT_JOIN_COMPLETED;
|
|
return has_joined;
|
|
}
|
|
|
|
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_prepare_for_deep_sleep(void)
|
|
{
|
|
ttn_rtc_save();
|
|
stop();
|
|
}
|
|
|
|
void ttn_prepare_for_power_off(void)
|
|
{
|
|
ttn_nvs_save();
|
|
stop();
|
|
}
|
|
|
|
void ttn_wait_for_idle(void)
|
|
{
|
|
while (true)
|
|
{
|
|
TickType_t ticks_to_wait = ttn_busy_duration();
|
|
if (ticks_to_wait == 0)
|
|
return;
|
|
vTaskDelay(ticks_to_wait);
|
|
}
|
|
}
|
|
|
|
TickType_t ttn_busy_duration(void)
|
|
{
|
|
TickType_t duration = hal_esp32_get_timer_duration();
|
|
if (duration != 0)
|
|
return duration; // busy or timer scheduled
|
|
|
|
if ((LMIC.opmode & (OP_JOINING | OP_TXDATA | OP_POLL | OP_TXRXPEND)) != 0)
|
|
return pdMS_TO_TICKS(100); // pending action
|
|
|
|
return 0; // idle
|
|
}
|
|
|
|
|
|
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_enabled(bool enabled)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_setAdrMode(enabled);
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
|
|
void ttn_set_data_rate(ttn_data_rate_t data_rate)
|
|
{
|
|
join_data_rate = data_rate;
|
|
|
|
if (has_joined)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_setDrTxpow(data_rate, LMIC.adrTxPow);
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
}
|
|
|
|
void ttn_set_max_tx_pow(int tx_pow)
|
|
{
|
|
max_tx_power = tx_pow;
|
|
|
|
if (has_joined)
|
|
{
|
|
hal_esp32_enter_critical_section();
|
|
LMIC_setDrTxpow(LMIC.datarate, tx_pow);
|
|
hal_esp32_leave_critical_section();
|
|
}
|
|
}
|
|
|
|
ttn_rf_settings_t ttn_get_rf_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));
|
|
}
|