mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 01:24:04 +00:00
TTN stack version selector does change the RX1 delay so ESPEasy can be used as TTNv3 node.
1393 lines
35 KiB
C++
1393 lines
35 KiB
C++
#include "rn2xx3_handler.h"
|
||
|
||
#include "rn2xx3_helper.h"
|
||
#include "rn2xx3_received_types.h"
|
||
|
||
|
||
rn2xx3_handler::rn2xx3_handler(Stream& serial) : _serial(serial)
|
||
{
|
||
clearSerialBuffer();
|
||
}
|
||
|
||
String rn2xx3_handler::sendRawCommand(const String& command)
|
||
{
|
||
unsigned long timer = millis();
|
||
|
||
if (!prepare_raw_command(command)) {
|
||
setLastError(F("sendRawCommand: Prepare fail"));
|
||
return "";
|
||
}
|
||
|
||
if (wait_command_finished() == RN_state::timeout) {
|
||
String log = F("sendRawCommand timeout: ");
|
||
log += command;
|
||
setLastError(log);
|
||
}
|
||
String ret = get_received_data();
|
||
|
||
if (_extensive_debug) {
|
||
String log = command;
|
||
log += '(';
|
||
log += String(millis() - timer);
|
||
log += ')';
|
||
log += F(" max length: ");
|
||
log += _max_received_length;
|
||
setLastError(log);
|
||
}
|
||
|
||
ret.trim();
|
||
return ret;
|
||
}
|
||
|
||
bool rn2xx3_handler::prepare_raw_command(const String& command)
|
||
{
|
||
if (!command_finished()) {
|
||
// Handling of another command has not finished.
|
||
return false;
|
||
}
|
||
_sendData = command;
|
||
_processing_cmd = Active_cmd::other;
|
||
_busy_count = 0;
|
||
_retry_count = 0;
|
||
set_state(RN_state::command_set_to_send);
|
||
|
||
// Set state may set command_finished to true if no _sendData is set.
|
||
return !command_finished();
|
||
}
|
||
|
||
bool rn2xx3_handler::prepare_tx_command(const String& command, const String& data, bool shouldEncode, uint8_t port) {
|
||
int estimatedSize = command.length() + 4; // port + space
|
||
|
||
estimatedSize += shouldEncode ? 2 * data.length() : data.length();
|
||
String tmpCommand;
|
||
tmpCommand.reserve(estimatedSize);
|
||
tmpCommand = command;
|
||
|
||
if (command.endsWith(F("cnf "))) {
|
||
// No port was given in the command, so add the port.
|
||
tmpCommand += String(port);
|
||
tmpCommand += ' ';
|
||
}
|
||
|
||
if (shouldEncode)
|
||
{
|
||
tmpCommand += rn2xx3_helper::base16encode(data);
|
||
}
|
||
else
|
||
{
|
||
tmpCommand += data;
|
||
}
|
||
|
||
if (!prepare_raw_command(tmpCommand)) {
|
||
return false;
|
||
}
|
||
_processing_cmd = Active_cmd::TX;
|
||
return true;
|
||
}
|
||
|
||
bool rn2xx3_handler::prepare_join(bool useOTAA) {
|
||
updateStatus();
|
||
|
||
if (!prepare_raw_command(useOTAA ? F("mac join otaa") : F("mac join abp"))) {
|
||
return false;
|
||
}
|
||
_processing_cmd = Active_cmd::join;
|
||
Status.Joined = false;
|
||
return true;
|
||
}
|
||
|
||
rn2xx3_handler::RN_state rn2xx3_handler::async_loop()
|
||
{
|
||
if (_state != RN_state::must_pause) {
|
||
if (!command_finished() && time_out_reached()) {
|
||
set_state(RN_state::timeout);
|
||
}
|
||
}
|
||
|
||
|
||
switch (get_state()) {
|
||
case RN_state::idle:
|
||
|
||
// Noting to do.
|
||
break;
|
||
case RN_state::command_set_to_send:
|
||
{
|
||
++_retry_count;
|
||
|
||
// retransmit/retry a maximum of 10 times
|
||
// N.B. this also applies when no_free_ch was received.
|
||
if (_retry_count > 10) {
|
||
set_state(RN_state::max_attempt_reached);
|
||
} else {
|
||
_receivedData = "";
|
||
clearSerialBuffer();
|
||
|
||
// Write the commmand
|
||
_serial.print(get_send_data());
|
||
_serial.println();
|
||
|
||
set_state(RN_state::wait_for_reply);
|
||
}
|
||
break;
|
||
}
|
||
case RN_state::must_pause:
|
||
{
|
||
// Do not call writes for a while.
|
||
if (time_out_reached()) {
|
||
set_state(RN_state::command_set_to_send);
|
||
}
|
||
break;
|
||
}
|
||
case RN_state::wait_for_reply:
|
||
case RN_state::wait_for_reply_rx2:
|
||
{
|
||
if (read_line()) {
|
||
switch (_state) {
|
||
case RN_state::wait_for_reply:
|
||
set_state(RN_state::reply_received);
|
||
break;
|
||
case RN_state::wait_for_reply_rx2:
|
||
set_state(RN_state::reply_received_rx2);
|
||
break;
|
||
default:
|
||
|
||
// Only process data when in the wait for reply state
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (_invalid_char_read) {
|
||
set_state(RN_state::invalid_char_read);
|
||
}
|
||
break;
|
||
}
|
||
case RN_state::reply_received:
|
||
case RN_state::reply_received_rx2:
|
||
{
|
||
handle_reply_received();
|
||
break;
|
||
}
|
||
case RN_state::must_perform_init:
|
||
break;
|
||
|
||
case RN_state::timeout:
|
||
sendWakeSequence();
|
||
break;
|
||
|
||
case RN_state::max_attempt_reached:
|
||
case RN_state::error:
|
||
case RN_state::duty_cycle_exceeded:
|
||
case RN_state::invalid_char_read:
|
||
|
||
break;
|
||
|
||
case RN_state::tx_success:
|
||
case RN_state::tx_success_with_rx:
|
||
case RN_state::reply_received_finished:
|
||
case RN_state::join_accepted:
|
||
break;
|
||
|
||
// Do not use default: here, so the compiler warns when a new state is not yet implemented here.
|
||
// default:
|
||
// break;
|
||
}
|
||
return get_state();
|
||
}
|
||
|
||
rn2xx3_handler::RN_state rn2xx3_handler::wait_command_finished(unsigned long timeout)
|
||
{
|
||
// Still use a timeout to prevent endless loops, although the state machine should always obey the set timeouts.
|
||
unsigned long start_timer = millis();
|
||
|
||
while ((millis() - start_timer) < timeout) {
|
||
async_loop();
|
||
|
||
if (command_finished()) { return get_state(); }
|
||
delay(10);
|
||
}
|
||
return get_state();
|
||
}
|
||
|
||
rn2xx3_handler::RN_state rn2xx3_handler::wait_command_accepted(unsigned long timeout)
|
||
{
|
||
// Still use a timeout to prevent endless loops, although the state machine should always obey the set timeouts.
|
||
unsigned long start_timer = millis();
|
||
|
||
while ((millis() - start_timer) < timeout) {
|
||
async_loop();
|
||
|
||
if (command_finished() || (get_state() == RN_state::wait_for_reply_rx2)) {
|
||
return get_state();
|
||
}
|
||
delay(10);
|
||
}
|
||
return get_state();
|
||
}
|
||
|
||
bool rn2xx3_handler::command_finished() const
|
||
{
|
||
return _processing_cmd == Active_cmd::none;
|
||
}
|
||
|
||
bool rn2xx3_handler::init()
|
||
{
|
||
if (!check_set_keys())
|
||
{
|
||
// FIXME TD-er: Do we need to set the state here to idle ???
|
||
// or maybe introduce a new "not_started" ???
|
||
setLastError(F("Not all keys are set"));
|
||
return false;
|
||
}
|
||
|
||
bool mustInit =
|
||
get_state() == RN_state::must_perform_init ||
|
||
!Status.Joined;
|
||
|
||
if (!mustInit) {
|
||
// What should be returned? The joined state or whether there has been a join performed?
|
||
return false;
|
||
}
|
||
|
||
if (!resetModule()) { return false; }
|
||
|
||
// We set both sets of keys, as some reports on older firmware suggest the save
|
||
// may not be successful after a factory reset if not all fields are set.
|
||
|
||
// Set OTAA keys
|
||
sendMacSet(F("deveui"), _deveui);
|
||
sendMacSet(F("appeui"), _appeui);
|
||
sendMacSet(F("appkey"), _appkey);
|
||
|
||
// Set ABP keys
|
||
sendMacSet(F("nwkskey"), _nwkskey);
|
||
sendMacSet(F("appskey"), _appskey);
|
||
sendMacSet(F("devaddr"), _devaddr);
|
||
|
||
// Set max. allowed power.
|
||
// 868 MHz EU : 1 -> 14 dBm
|
||
// 900 MHz US/AU: 5 -> 20 dBm
|
||
setTXoutputPower(_moduleType == RN2xx3_datatypes::Model::RN2903 ? 5 : 1);
|
||
setSF(_sf);
|
||
|
||
// TTN does not yet support Adaptive Data Rate.
|
||
// Using it is also only necessary in limited situations.
|
||
// Therefore disable it by default.
|
||
setAdaptiveDataRate(false);
|
||
|
||
// Switch off automatic replies, because this library can not
|
||
// handle more than one mac_rx per tx. See RN2483 datasheet,
|
||
// 2.4.8.14, page 27 and the scenario on page 19.
|
||
setAutomaticReply(false);
|
||
|
||
// Semtech and TTN both use a non default RX2 window freq and SF.
|
||
// Maybe we should not specify this for other networks.
|
||
// if (_moduleType == RN2xx3_datatypes::Model::RN2483)
|
||
// {
|
||
// set2ndRecvWindow(3, 869525000);
|
||
// }
|
||
// Disabled for now because an OTAA join seems to work fine without.
|
||
|
||
if (_asyncMode) {
|
||
return prepare_join(_otaa);
|
||
}
|
||
return wait_command_accepted() == RN_state::join_accepted;
|
||
}
|
||
|
||
bool rn2xx3_handler::initOTAA(const String& AppEUI, const String& AppKey, const String& DevEUI)
|
||
{
|
||
// If the Device EUI was given as a parameter, use it
|
||
// otherwise use the Hardware EUI.
|
||
if (rn2xx3_helper::isHexStr_of_length(DevEUI, 16))
|
||
{
|
||
_deveui = DevEUI;
|
||
}
|
||
else
|
||
{
|
||
String addr = sendRawCommand(F("sys get hweui"));
|
||
|
||
if (rn2xx3_helper::isHexStr_of_length(addr, 16))
|
||
{
|
||
_deveui = addr;
|
||
}
|
||
}
|
||
|
||
if (!rn2xx3_helper::isHexStr_of_length(AppEUI, 16) ||
|
||
!rn2xx3_helper::isHexStr_of_length(AppKey, 32) ||
|
||
!rn2xx3_helper::isHexStr_of_length(_deveui, 16))
|
||
{
|
||
// No valid config
|
||
setLastError(F("InitOTAA: Not all keys are valid."));
|
||
return false;
|
||
}
|
||
_appeui = AppEUI;
|
||
_appkey = AppKey;
|
||
_otaa = true;
|
||
return init();
|
||
}
|
||
|
||
bool rn2xx3_handler::initOTAA(uint8_t *AppEUI, uint8_t *AppKey, uint8_t *DevEUI)
|
||
{
|
||
if ((AppEUI == nullptr) || (AppKey == nullptr)) {
|
||
return false;
|
||
}
|
||
|
||
String app_eui;
|
||
String dev_eui;
|
||
String app_key;
|
||
char buff[3];
|
||
|
||
for (uint8_t i = 0; i < 8; i++)
|
||
{
|
||
sprintf_P(buff, PSTR("%02X"), AppEUI[i]);
|
||
app_eui += String(buff);
|
||
}
|
||
|
||
if (DevEUI == nullptr)
|
||
{
|
||
dev_eui = '0';
|
||
} else {
|
||
for (uint8_t i = 0; i < 8; i++)
|
||
{
|
||
sprintf_P(buff, PSTR("%02X"), DevEUI[i]);
|
||
dev_eui += String(buff);
|
||
}
|
||
}
|
||
|
||
for (uint8_t i = 0; i < 16; i++)
|
||
{
|
||
sprintf_P(buff, PSTR("%02X"), AppKey[i]);
|
||
app_key += String(buff);
|
||
}
|
||
|
||
return initOTAA(app_eui, app_key, dev_eui);
|
||
}
|
||
|
||
bool rn2xx3_handler::initABP(const String& devAddr, const String& AppSKey, const String& NwkSKey)
|
||
{
|
||
_devaddr = devAddr;
|
||
_appskey = AppSKey;
|
||
_nwkskey = NwkSKey;
|
||
_otaa = false;
|
||
return init();
|
||
}
|
||
|
||
RN2xx3_datatypes::TX_return_type rn2xx3_handler::txCommand(const String& command, const String& data, bool shouldEncode, uint8_t port)
|
||
{
|
||
if (get_state() == RN_state::must_perform_init) {
|
||
init();
|
||
}
|
||
|
||
if (!prepare_tx_command(command, data, shouldEncode, port)) {
|
||
return RN2xx3_datatypes::TX_return_type::TX_FAIL;
|
||
}
|
||
|
||
if (_asyncMode) {
|
||
// Unlikely the state will be other than an error or wait_for_reply_rx2
|
||
switch (wait_command_accepted()) {
|
||
case RN_state::wait_for_reply_rx2:
|
||
case RN_state::tx_success:
|
||
case RN_state::tx_success_with_rx:
|
||
return RN2xx3_datatypes::TX_return_type::TX_SUCCESS;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
} else {
|
||
switch (wait_command_finished()) {
|
||
case RN_state::tx_success:
|
||
return RN2xx3_datatypes::TX_return_type::TX_SUCCESS;
|
||
case RN_state::tx_success_with_rx:
|
||
return RN2xx3_datatypes::TX_return_type::TX_WITH_RX;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
return RN2xx3_datatypes::TX_return_type::TX_FAIL;
|
||
}
|
||
|
||
bool rn2xx3_handler::setSF(uint8_t sf)
|
||
{
|
||
if ((sf >= 7) && (sf <= 12))
|
||
{
|
||
int dr = -1;
|
||
|
||
switch (_fp)
|
||
{
|
||
case RN2xx3_datatypes::Freq_plan::TTN_EU:
|
||
case RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU:
|
||
case RN2xx3_datatypes::Freq_plan::DEFAULT_EU:
|
||
|
||
// case TTN_FP_EU868:
|
||
// case TTN_FP_IN865_867:
|
||
// case TTN_FP_AS920_923:
|
||
// case TTN_FP_AS923_925:
|
||
// case TTN_FP_KR920_923:
|
||
dr = 12 - sf;
|
||
break;
|
||
case RN2xx3_datatypes::Freq_plan::TTN_US:
|
||
|
||
// case TTN_FP_US915:
|
||
// case TTN_FP_AU915:
|
||
dr = 10 - sf;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (dr >= 0)
|
||
{
|
||
_sf = sf;
|
||
_dr = dr;
|
||
return setDR(dr);
|
||
}
|
||
}
|
||
setLastError(F("error in setSF"));
|
||
return false;
|
||
}
|
||
|
||
bool rn2xx3_handler::setDR(int dr)
|
||
{
|
||
if ((dr >= 0) && (dr <= 7))
|
||
{
|
||
return sendMacSet(F("dr"), String(dr));
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void rn2xx3_handler::setAsyncMode(bool enabled) {
|
||
_asyncMode = enabled;
|
||
}
|
||
|
||
bool rn2xx3_handler::getAsyncMode() const {
|
||
return _asyncMode;
|
||
}
|
||
|
||
bool rn2xx3_handler::useOTAA() const {
|
||
return _otaa;
|
||
}
|
||
|
||
void rn2xx3_handler::setLastUsedJoinMode(bool isOTAA) {
|
||
if (_otaa != isOTAA) {
|
||
Status.Joined = false;
|
||
_otaa = isOTAA;
|
||
}
|
||
}
|
||
|
||
bool rn2xx3_handler::setFrequencyPlan(RN2xx3_datatypes::Freq_plan fp)
|
||
{
|
||
bool returnValue = false;
|
||
|
||
switch (fp)
|
||
{
|
||
case RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU:
|
||
{
|
||
if (_moduleType == RN2xx3_datatypes::Model::RN2483)
|
||
{
|
||
// mac set rx2 <dataRate> <frequency>
|
||
// set2ndRecvWindow(5, 868100000); //use this for "strict" one channel gateways
|
||
set2ndRecvWindow(3, 869525000); // use for "non-strict" one channel gateways
|
||
setChannelDutyCycle(0, 99); // 1% duty cycle for this channel
|
||
setChannelDutyCycle(1, 65535); // almost never use this channel
|
||
setChannelDutyCycle(2, 65535); // almost never use this channel
|
||
|
||
for (uint8_t ch = 3; ch < 8; ch++)
|
||
{
|
||
setChannelEnabled(ch, false);
|
||
}
|
||
returnValue = true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_datatypes::Freq_plan::TTN_EU:
|
||
{
|
||
if (_moduleType == RN2xx3_datatypes::Model::RN2483)
|
||
{
|
||
/*
|
||
* The <dutyCycle> value that needs to be configured can be
|
||
* obtained from the actual duty cycle X (in percentage)
|
||
* using the following formula: <dutyCycle> = (100/X) – 1
|
||
*
|
||
* 10% -> 9
|
||
* 1% -> 99
|
||
* 0.33% -> 299
|
||
* 8 channels, total of 1% duty cycle:
|
||
* 0.125% per channel -> 799
|
||
*
|
||
* Most of the RN2xx3_datatypes::Freq_plan::TTN_EU frequency plan was copied from:
|
||
* https://github.com/TheThingsNetwork/arduino-device-lib
|
||
*/
|
||
|
||
uint32_t freq = 867100000;
|
||
|
||
for (uint8_t ch = 0; ch < 8; ch++)
|
||
{
|
||
setChannelDutyCycle(ch, 799); // All channels
|
||
|
||
if (ch == 1)
|
||
{
|
||
setChannelDataRateRange(ch, 0, 6);
|
||
}
|
||
else if (ch > 2)
|
||
{
|
||
setChannelDataRateRange(ch, 0, 5);
|
||
setChannelFrequency(ch, freq);
|
||
freq = freq + 200000;
|
||
}
|
||
setChannelEnabled(ch, true); // frequency, data rate and duty cycle must be set first.
|
||
}
|
||
|
||
// RX window 2
|
||
set2ndRecvWindow(3, 869525000);
|
||
|
||
returnValue = true;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_datatypes::Freq_plan::TTN_US:
|
||
{
|
||
/*
|
||
* Most of the RN2xx3_datatypes::Freq_plan::TTN_US frequency plan was copied from:
|
||
* https://github.com/TheThingsNetwork/arduino-device-lib
|
||
*/
|
||
if (_moduleType == RN2xx3_datatypes::Model::RN2903)
|
||
{
|
||
for (int channel = 0; channel < 72; channel++)
|
||
{
|
||
bool enabled = (channel >= 8 && channel < 16);
|
||
setChannelEnabled(channel, enabled);
|
||
}
|
||
returnValue = true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_datatypes::Freq_plan::DEFAULT_EU:
|
||
{
|
||
if (_moduleType == RN2xx3_datatypes::Model::RN2483)
|
||
{
|
||
for (int channel = 0; channel < 8; channel++)
|
||
{
|
||
if (channel < 3) {
|
||
// fix duty cycle - 1% = 0.33% per channel
|
||
setChannelDutyCycle(channel, 799);
|
||
setChannelEnabled(channel, true);
|
||
} else {
|
||
// disable non-default channels
|
||
setChannelEnabled(channel, false);
|
||
}
|
||
}
|
||
returnValue = true;
|
||
}
|
||
|
||
break;
|
||
}
|
||
default:
|
||
{
|
||
// set default channels 868.1, 868.3 and 868.5?
|
||
return false; // well we didn't do anything, so yes, false
|
||
}
|
||
}
|
||
_fp = fp;
|
||
|
||
return returnValue;
|
||
}
|
||
|
||
bool rn2xx3_handler::setTTNstack(RN2xx3_datatypes::TTN_stack_version version)
|
||
{
|
||
switch(version) {
|
||
case RN2xx3_datatypes::TTN_stack_version::TTN_v2:
|
||
_rxdelay1 = 1000;
|
||
_rxdelay2 = 2000;
|
||
break;
|
||
case RN2xx3_datatypes::TTN_stack_version::TTN_v3:
|
||
_rxdelay1 = 5000;
|
||
_rxdelay2 = 6000;
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
sendMacSet(F("rxdelay1"), String(_rxdelay1));
|
||
return true;
|
||
}
|
||
|
||
RN2xx3_datatypes::Model rn2xx3_handler::configureModuleType()
|
||
{
|
||
RN2xx3_datatypes::Firmware firmware;
|
||
|
||
_moduleType = RN2xx3_datatypes::parseVersion(sysver(), firmware);
|
||
return _moduleType;
|
||
}
|
||
|
||
bool rn2xx3_handler::resetModule()
|
||
{
|
||
// reset the module - this will clear all keys set previously
|
||
String result;
|
||
|
||
switch (configureModuleType())
|
||
{
|
||
case RN2xx3_datatypes::Model::RN2903:
|
||
result = sendRawCommand(F("mac reset"));
|
||
break;
|
||
case RN2xx3_datatypes::Model::RN2483:
|
||
result = sendRawCommand(F("mac reset 868"));
|
||
break;
|
||
default:
|
||
|
||
// we shouldn't go forward with the init
|
||
setLastError(F("error in reset"));
|
||
return false;
|
||
}
|
||
|
||
// setLastError(F("success resetmodule"));
|
||
return true;
|
||
|
||
// return RN2xx3_received_types::determineReceivedDataType(result) == ok;
|
||
}
|
||
|
||
const String& rn2xx3_handler::get_send_data() const {
|
||
return _sendData;
|
||
}
|
||
|
||
const String& rn2xx3_handler::get_received_data() const {
|
||
return _receivedData;
|
||
}
|
||
|
||
const String& rn2xx3_handler::get_received_data(unsigned long& duration) const {
|
||
duration = millis() - _start_prep;
|
||
return _receivedData;
|
||
}
|
||
|
||
const String& rn2xx3_handler::get_rx_message() const {
|
||
return _rxMessenge;
|
||
}
|
||
|
||
String rn2xx3_handler::peekLastError() const
|
||
{
|
||
return _lastError;
|
||
}
|
||
|
||
String rn2xx3_handler::getLastError()
|
||
{
|
||
String res = _lastError;
|
||
|
||
_lastError = "";
|
||
return res;
|
||
}
|
||
|
||
void rn2xx3_handler::setLastError(const String& error)
|
||
{
|
||
if (_extensive_debug) {
|
||
_lastError += '\n';
|
||
_lastError += String(millis());
|
||
_lastError += F(" : ");
|
||
_lastError += error;
|
||
} else {
|
||
_lastError = error;
|
||
}
|
||
}
|
||
|
||
rn2xx3_handler::RN_state rn2xx3_handler::get_state() const {
|
||
return _state;
|
||
}
|
||
|
||
uint8_t rn2xx3_handler::get_busy_count() const {
|
||
return _busy_count;
|
||
}
|
||
|
||
String rn2xx3_handler::sysver() {
|
||
String ver = sendRawCommand(F("sys get ver"));
|
||
|
||
ver.trim();
|
||
return ver;
|
||
}
|
||
|
||
bool rn2xx3_handler::getRxDelayValues(uint32_t& rxdelay1,
|
||
uint32_t& rxdelay2)
|
||
{
|
||
rxdelay1 = _rxdelay1;
|
||
rxdelay2 = _rxdelay2;
|
||
return _rxdelay1 != 0 && _rxdelay2 != 0;
|
||
}
|
||
|
||
float rn2xx3_handler::getLoRaAirTime(uint8_t pl) const
|
||
{
|
||
uint8_t sf = _sf; // Spreading factor 7 - 12
|
||
uint16_t bw = 125; // Bandwidth 125 kHz default for LoRaWAN. 250 kHz also supported.
|
||
uint8_t cr = 1; // Code Rate 4 / (CR + 4) = 4/5. 4/5 default for LoRaWAN
|
||
uint8_t n_preamble = 8; // Preamble length Default for frame = 8, beacon = 10
|
||
bool header = true; // Explicit header Default on for LoRaWAN
|
||
bool crc = true; // CRC Default on for LoRaWAN
|
||
|
||
if (sf > 12) {
|
||
sf = 12;
|
||
} else if (sf < 7) {
|
||
sf = 7;
|
||
}
|
||
|
||
if (cr > 4) {
|
||
cr = 4;
|
||
} else if (cr < 1) {
|
||
cr = 1;
|
||
}
|
||
|
||
// Symbols in frame
|
||
int payload_length = 8;
|
||
{
|
||
int beta_offset = 28;
|
||
|
||
if (crc) { beta_offset += 16; }
|
||
|
||
if (!header) { beta_offset -= 20; }
|
||
float beta_f = 8.0f * pl - 4.0f * sf + beta_offset;
|
||
bool lowDataRateOptimization = (bw == 125 && sf >= 11);
|
||
|
||
if (lowDataRateOptimization) {
|
||
beta_f = beta_f / (4.0f * (sf - 2));
|
||
} else {
|
||
beta_f = beta_f / (4.0f * sf);
|
||
}
|
||
int beta = static_cast<int>(beta_f + 1.0f); // ceil
|
||
|
||
if (beta > 0) {
|
||
payload_length += (beta * (cr + 4));
|
||
}
|
||
}
|
||
|
||
// t_symbol and t_air in msec
|
||
float t_symbol = static_cast<float>(1 << sf) / bw;
|
||
float t_air = ((n_preamble + 4.25f) + payload_length) * t_symbol;
|
||
return t_air;
|
||
}
|
||
|
||
void rn2xx3_handler::set_state(rn2xx3_handler::RN_state state) {
|
||
const bool was_processing_cmd = _processing_cmd != Active_cmd::none;
|
||
|
||
_state = state;
|
||
|
||
switch (state) {
|
||
case RN_state::wait_for_reply:
|
||
case RN_state::wait_for_reply_rx2:
|
||
{
|
||
// We will wait for data, so make sure the receiving buffer is empty.
|
||
_receivedData = "";
|
||
|
||
if (state == RN_state::wait_for_reply_rx2)
|
||
{
|
||
// Enough time to wait for:
|
||
// Transmit Time On Air + receive_delay2 + receiving RX2 packet.
|
||
switch (_processing_cmd) {
|
||
case Active_cmd::join:
|
||
set_timeout(10000); // Do take a bit more time for a join.
|
||
break;
|
||
case Active_cmd::TX:
|
||
set_timeout(_rxdelay2 + 3000); // 55 bytes @EU868 data rate of SF12/125kHz = 2,957.31 milliseconds
|
||
break;
|
||
default:
|
||
|
||
// Other commands do not use RX2
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case RN_state::reply_received:
|
||
case RN_state::reply_received_rx2:
|
||
|
||
// Nothing to set here, as we will now inspect the received data and not communicate with the module.
|
||
break;
|
||
case RN_state::command_set_to_send:
|
||
|
||
if (_sendData.length() == 0) {
|
||
set_state(RN_state::idle);
|
||
} else {
|
||
_start_prep = millis();
|
||
|
||
set_timeout(1500); // Roughly 1100 msec needed for mac save
|
||
// Almost all other commands reply in 20 - 100 msec.
|
||
}
|
||
|
||
break;
|
||
case RN_state::must_pause:
|
||
set_timeout(1000);
|
||
break;
|
||
|
||
case RN_state::invalid_char_read:
|
||
|
||
if (_processing_cmd == Active_cmd::other) {
|
||
// Must retry to run the command again.
|
||
set_state(RN_state::command_set_to_send);
|
||
} else {
|
||
_processing_cmd = Active_cmd::none;
|
||
}
|
||
break;
|
||
|
||
case RN_state::idle:
|
||
|
||
// ToDo: Add support for sleep mode.
|
||
// Clear the strings to free up some memory.
|
||
_processing_cmd = Active_cmd::none;
|
||
_sendData = "";
|
||
_receivedData = "";
|
||
_rxMessenge = "";
|
||
_lastError = "";
|
||
break;
|
||
case RN_state::timeout:
|
||
case RN_state::max_attempt_reached:
|
||
case RN_state::error:
|
||
case RN_state::must_perform_init:
|
||
case RN_state::duty_cycle_exceeded:
|
||
|
||
// We cannot continue from this error
|
||
_processing_cmd = Active_cmd::none;
|
||
break;
|
||
case RN_state::tx_success:
|
||
case RN_state::tx_success_with_rx:
|
||
case RN_state::reply_received_finished:
|
||
_processing_cmd = Active_cmd::none;
|
||
break;
|
||
|
||
case RN_state::join_accepted:
|
||
Status.Joined = true;
|
||
saveUpdatedStatus();
|
||
_processing_cmd = Active_cmd::none;
|
||
break;
|
||
|
||
// Do not use default: here, so the compiler warns when a new state is not yet implemented here.
|
||
// default:
|
||
// break;
|
||
}
|
||
|
||
if (was_processing_cmd && (_processing_cmd == Active_cmd::none)) {
|
||
_start = 0;
|
||
_invalid_char_read = false;
|
||
_busy_count = 0;
|
||
_retry_count = 0;
|
||
}
|
||
}
|
||
|
||
bool rn2xx3_handler::read_line()
|
||
{
|
||
int available = _serial.available();
|
||
while (available > 0) {
|
||
int c = _serial.read();
|
||
--available;
|
||
|
||
if (c >= 0) {
|
||
const char character = static_cast<char>(c & 0xFF);
|
||
|
||
if (!rn2xx3_helper::valid_char(character)) {
|
||
_invalid_char_read = true;
|
||
return false;
|
||
}
|
||
|
||
_receivedData += character;
|
||
|
||
if (character == '\n') {
|
||
if (_receivedData.length() > _max_received_length) {
|
||
_max_received_length = _receivedData.length();
|
||
}
|
||
return true;
|
||
}
|
||
if (available == 0) {
|
||
available = _serial.available();
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void rn2xx3_handler::set_timeout(unsigned long timeout)
|
||
{
|
||
_timeout = timeout;
|
||
_start = millis();
|
||
}
|
||
|
||
bool rn2xx3_handler::time_out_reached() const
|
||
{
|
||
return (millis() - _start) >= _timeout;
|
||
}
|
||
|
||
void rn2xx3_handler::clearSerialBuffer()
|
||
{
|
||
while (_serial.available()) {
|
||
_serial.read();
|
||
}
|
||
}
|
||
|
||
bool rn2xx3_handler::updateStatus()
|
||
{
|
||
if (!Status.modelVersionSet()) {
|
||
Status.setModelVersion(sysver());
|
||
}
|
||
|
||
const String status_str = sendRawCommand(F("mac get status"));
|
||
|
||
// pre 1.0.1 firmware revisions only used 16 bits.
|
||
// Newer firmware revisions use 32 bits.
|
||
if (!(rn2xx3_helper::isHexStr_of_length(status_str, 4) ||
|
||
rn2xx3_helper::isHexStr_of_length(status_str, 8))) {
|
||
String error = F("mac get status : No valid hex string \"");
|
||
error += status_str;
|
||
error += '\"';
|
||
setLastError(error);
|
||
return false;
|
||
}
|
||
uint32_t status_value = strtoul(status_str.c_str(), 0, 16);
|
||
Status.decode(status_value);
|
||
|
||
if ((_rxdelay1 == 0) || (_rxdelay2 == 0) || Status.SecondReceiveWindowParamUpdated)
|
||
{
|
||
readUIntMacGet(F("rxdelay1"), _rxdelay1);
|
||
readUIntMacGet(F("rxdelay2"), _rxdelay2);
|
||
Status.SecondReceiveWindowParamUpdated = false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool rn2xx3_handler::saveUpdatedStatus()
|
||
{
|
||
// Only save to the eeprom when really needed.
|
||
// No need to store the current config when there is no active connection.
|
||
// Todo: Must keep track of last saved counters and decide to update when current counter differs more than set threshold.
|
||
bool saved = false;
|
||
|
||
if (updateStatus())
|
||
{
|
||
if (Status.Joined && !Status.RejoinNeeded && Status.saveSettingsNeeded())
|
||
{
|
||
saved = RN2xx3_received_types::determineReceivedDataType(sendRawCommand(F("mac save"))) == RN2xx3_received_types::ok;
|
||
Status.clearSaveSettingsNeeded();
|
||
updateStatus();
|
||
}
|
||
}
|
||
return saved;
|
||
}
|
||
|
||
void rn2xx3_handler::handle_reply_received() {
|
||
const RN2xx3_received_types::received_t received_datatype = RN2xx3_received_types::determineReceivedDataType(_receivedData);
|
||
|
||
// Check if the reply is unexpected, so log the command + reply
|
||
bool mustLogAsError = _extensive_debug;
|
||
|
||
switch (received_datatype) {
|
||
case RN2xx3_received_types::ok:
|
||
case RN2xx3_received_types::UNKNOWN: // Many get-commands just return a value, so that will be of type UNKNOWN
|
||
case RN2xx3_received_types::accepted:
|
||
case RN2xx3_received_types::mac_tx_ok:
|
||
case RN2xx3_received_types::mac_rx:
|
||
case RN2xx3_received_types::radio_rx:
|
||
case RN2xx3_received_types::radio_tx_ok:
|
||
break;
|
||
|
||
default:
|
||
mustLogAsError = true;
|
||
break;
|
||
}
|
||
|
||
if (mustLogAsError) {
|
||
String error;
|
||
error.reserve(_sendData.length() + _receivedData.length() + 4);
|
||
|
||
if (_processing_cmd == Active_cmd::TX) {
|
||
// TX commands are a lot longer, so do not include complete command
|
||
error += F("mac tx");
|
||
} else {
|
||
error += _sendData;
|
||
}
|
||
error += F(" -> ");
|
||
error += _receivedData;
|
||
setLastError(error);
|
||
}
|
||
|
||
switch (received_datatype) {
|
||
case RN2xx3_received_types::UNKNOWN:
|
||
|
||
// A reply which is not part of standard replies, so it can be a requested value.
|
||
// Command is now finished.
|
||
set_state(RN_state::reply_received_finished);
|
||
break;
|
||
case RN2xx3_received_types::ok:
|
||
{
|
||
const bool expect_rx2 =
|
||
(_processing_cmd == Active_cmd::TX) ||
|
||
(_processing_cmd == Active_cmd::join);
|
||
|
||
if ((get_state() == RN_state::reply_received) && expect_rx2) {
|
||
// "mac tx" and "join otaa" commands may receive a second response if the first one was "ok"
|
||
set_state(RN_state::wait_for_reply_rx2);
|
||
} else {
|
||
set_state(RN_state::reply_received_finished);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::invalid_param:
|
||
{
|
||
// parameters (<type> <portno> <data>) are not valid
|
||
// should not happen if we typed the commands correctly
|
||
set_state(RN_state::error);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::not_joined:
|
||
{
|
||
// the network is not joined
|
||
Status.Joined = false;
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::no_free_ch:
|
||
{
|
||
// all channels are busy
|
||
// probably duty cycle limits exceeded.
|
||
// User must retry.
|
||
set_state(RN_state::duty_cycle_exceeded);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::silent:
|
||
{
|
||
// the module is in a Silent Immediately state
|
||
// This is enforced by the network.
|
||
// To enable:
|
||
// sendRawCommand(F("mac forceENABLE"));
|
||
// N.B. One has to think about why this has happened.
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::frame_counter_err_rejoin_needed:
|
||
{
|
||
// the frame counter rolled over
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::busy:
|
||
{
|
||
// MAC state is not in an Idle state
|
||
_busy_count++;
|
||
|
||
// Not sure if this is wise. At low data rates with large packets
|
||
// this can perhaps cause transmissions at more than 1% duty cycle.
|
||
// Need to calculate the correct constant value.
|
||
// But it is wise to have this check and re-init in case the
|
||
// lorawan stack in the RN2xx3 hangs.
|
||
if (_busy_count >= 10)
|
||
{
|
||
set_state(RN_state::must_perform_init);
|
||
}
|
||
else
|
||
{
|
||
delay(100);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::mac_paused:
|
||
{
|
||
// MAC was paused and not resumed back
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::invalid_data_len:
|
||
{
|
||
if (_state == RN_state::reply_received)
|
||
{
|
||
// application payload length is greater than the maximum application payload length corresponding to the current data rate
|
||
}
|
||
else
|
||
{
|
||
// application payload length is greater than the maximum application payload length corresponding to the current data rate.
|
||
// This can occur after an earlier uplink attempt if retransmission back-off has reduced the data rate.
|
||
}
|
||
set_state(RN_state::error);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::mac_tx_ok:
|
||
{
|
||
// if uplink transmission was successful and no downlink data was received back from the server
|
||
// SUCCESS!!
|
||
set_state(RN_state::tx_success);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::mac_rx:
|
||
{
|
||
// mac_rx <portno> <data>
|
||
// transmission was successful
|
||
// <portno>: port number, from 1 to 223
|
||
// <data>: hexadecimal value that was received from theserver
|
||
// example: mac_rx 1 54657374696E6720313233
|
||
_rxMessenge = _receivedData.substring(_receivedData.indexOf(' ', 7) + 1);
|
||
set_state(RN_state::tx_success_with_rx);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::mac_err:
|
||
{
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::radio_err:
|
||
{
|
||
// transmission was unsuccessful, ACK not received back from the server
|
||
// This should never happen. If it does, something major is wrong.
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
|
||
case RN2xx3_received_types::accepted:
|
||
set_state(RN_state::join_accepted);
|
||
break;
|
||
|
||
|
||
case RN2xx3_received_types::denied:
|
||
case RN2xx3_received_types::keys_not_init:
|
||
set_state(RN_state::error);
|
||
break;
|
||
|
||
case RN2xx3_received_types::radio_rx:
|
||
case RN2xx3_received_types::radio_tx_ok:
|
||
|
||
// FIXME TD-er: Not sure what to do here.
|
||
break;
|
||
|
||
|
||
/*
|
||
default:
|
||
{
|
||
// unknown response after mac tx command
|
||
set_state(RN_state::must_perform_init);
|
||
break;
|
||
}
|
||
*/
|
||
}
|
||
}
|
||
|
||
int rn2xx3_handler::readIntValue(const String& command)
|
||
{
|
||
String value = sendRawCommand(command);
|
||
|
||
value.trim();
|
||
return value.toInt();
|
||
}
|
||
|
||
bool rn2xx3_handler::readUIntMacGet(const String& param, uint32_t& value)
|
||
{
|
||
String command;
|
||
|
||
command.reserve(8 + param.length());
|
||
command = F("mac get ");
|
||
command += param;
|
||
String value_str = sendRawCommand(command);
|
||
|
||
if (value_str.length() == 0)
|
||
{
|
||
return false;
|
||
}
|
||
value = strtoul(value_str.c_str(), 0, 10);
|
||
return true;
|
||
}
|
||
|
||
bool rn2xx3_handler::sendMacSet(const String& param, const String& value)
|
||
{
|
||
String command;
|
||
|
||
command.reserve(10 + param.length() + value.length());
|
||
command = F("mac set ");
|
||
command += param;
|
||
command += ' ';
|
||
command += value;
|
||
|
||
if (_extensive_debug) {
|
||
setLastError(command);
|
||
}
|
||
|
||
return RN2xx3_received_types::determineReceivedDataType(sendRawCommand(command)) == RN2xx3_received_types::ok;
|
||
}
|
||
|
||
bool rn2xx3_handler::sendMacSetEnabled(const String& param, bool enabled)
|
||
{
|
||
return sendMacSet(param, enabled ? F("on") : F("off"));
|
||
}
|
||
|
||
bool rn2xx3_handler::sendMacSetCh(const String& param, unsigned int channel, const String& value)
|
||
{
|
||
String command;
|
||
|
||
command.reserve(20);
|
||
command = param;
|
||
command += ' ';
|
||
command += channel;
|
||
command += ' ';
|
||
command += value;
|
||
return sendMacSet(F("ch"), command);
|
||
}
|
||
|
||
bool rn2xx3_handler::sendMacSetCh(const String& param, unsigned int channel, uint32_t value)
|
||
{
|
||
return sendMacSetCh(param, channel, String(value));
|
||
}
|
||
|
||
bool rn2xx3_handler::setChannelDutyCycle(unsigned int channel, unsigned int dutyCycle)
|
||
{
|
||
return sendMacSetCh(F("dcycle"), channel, dutyCycle);
|
||
}
|
||
|
||
bool rn2xx3_handler::setChannelFrequency(unsigned int channel, uint32_t frequency)
|
||
{
|
||
return sendMacSetCh(F("freq"), channel, frequency);
|
||
}
|
||
|
||
bool rn2xx3_handler::setChannelDataRateRange(unsigned int channel, unsigned int minRange, unsigned int maxRange)
|
||
{
|
||
String value;
|
||
|
||
value = String(minRange);
|
||
value += ' ';
|
||
value += String(maxRange);
|
||
return sendMacSetCh(F("drrange"), channel, value);
|
||
}
|
||
|
||
bool rn2xx3_handler::setChannelEnabled(unsigned int channel, bool enabled)
|
||
{
|
||
return sendMacSetCh(F("status"), channel, enabled ? F("on") : F("off"));
|
||
}
|
||
|
||
bool rn2xx3_handler::set2ndRecvWindow(unsigned int dataRate, uint32_t frequency)
|
||
{
|
||
String value;
|
||
|
||
value = String(dataRate);
|
||
value += ' ';
|
||
value += String(frequency);
|
||
return sendMacSet(F("rx2"), value);
|
||
}
|
||
|
||
bool rn2xx3_handler::setAdaptiveDataRate(bool enabled)
|
||
{
|
||
return sendMacSetEnabled(F("adr"), enabled);
|
||
}
|
||
|
||
bool rn2xx3_handler::setAutomaticReply(bool enabled)
|
||
{
|
||
return sendMacSetEnabled(F("ar"), enabled);
|
||
}
|
||
|
||
bool rn2xx3_handler::setTXoutputPower(int pwridx)
|
||
{
|
||
// Possible values:
|
||
|
||
/*
|
||
433 MHz EU:
|
||
0: 10 dBm
|
||
1: 7 dBm
|
||
2: 4 dBm
|
||
3: 1 dBm
|
||
4: -2 dBm
|
||
5: -5 dBm
|
||
|
||
868 MHz EU:
|
||
0: N/A
|
||
1: 14 dBm
|
||
2: 11 dBm
|
||
3: 8 dBm
|
||
4: 5 dBm
|
||
5: 2 dBm
|
||
|
||
900 MHz US/AU:
|
||
5 : 20 dBm
|
||
7 : 16 dBm
|
||
8 : 14 dBm
|
||
9 : 12 dBm
|
||
10: 10 dBm
|
||
*/
|
||
return sendMacSet(F("pwridx"), String(pwridx));
|
||
}
|
||
|
||
void rn2xx3_handler::sendWakeSequence()
|
||
{
|
||
_serial.write(static_cast<uint8_t>(0x00));
|
||
_serial.write(static_cast<uint8_t>(0x55));
|
||
_serial.println();
|
||
}
|
||
|
||
bool rn2xx3_handler::check_set_keys()
|
||
{
|
||
// Strings are in HEX, so 1 character per 4 bits.
|
||
// Identifiers:
|
||
// - DevEUI - 64 bit end-device identifier, EUI-64 (unique)
|
||
// - DevAddr - 32 bit device address (non-unique)
|
||
// - AppEUI - 64 bit application identifier, EUI-64 (unique)
|
||
//
|
||
// Security keys: NwkSKey, AppSKey and AppKey.
|
||
// All keys have a length of 128 bits.
|
||
|
||
bool otaa_set =
|
||
rn2xx3_helper::isHexStr_of_length(_deveui, 16) &&
|
||
rn2xx3_helper::isHexStr_of_length(_appeui, 16) &&
|
||
rn2xx3_helper::isHexStr_of_length(_appkey, 32);
|
||
|
||
bool abp_set =
|
||
rn2xx3_helper::isHexStr_of_length(_nwkskey, 32) &&
|
||
rn2xx3_helper::isHexStr_of_length(_appskey, 32) &&
|
||
rn2xx3_helper::isHexStr_of_length(_devaddr, 8);
|
||
|
||
if (!otaa_set && !abp_set) {
|
||
return false;
|
||
}
|
||
|
||
if (_otaa && otaa_set) {
|
||
if (!abp_set) {
|
||
if (!rn2xx3_helper::isHexStr_of_length(_nwkskey, 32)) {
|
||
_nwkskey = F("00000000000000000000000000000000");
|
||
}
|
||
|
||
if (!rn2xx3_helper::isHexStr_of_length(_appskey, 32)) {
|
||
_appskey = F("00000000000000000000000000000000");
|
||
}
|
||
|
||
if (!rn2xx3_helper::isHexStr_of_length(_devaddr, 8))
|
||
{
|
||
// The default address to use on TTN if no address is defined.
|
||
// This one falls in the "testing" address space.
|
||
_devaddr = F("03FFBEEF");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
if (!_otaa && abp_set) {
|
||
if (!otaa_set) {
|
||
if (!rn2xx3_helper::isHexStr_of_length(_deveui, 16))
|
||
{
|
||
// if you want to use another DevEUI than the hardware one
|
||
// use this deveui for LoRa WAN
|
||
_deveui = F("0011223344556677");
|
||
}
|
||
|
||
if (!rn2xx3_helper::isHexStr_of_length(_appeui, 16)) {
|
||
_appeui = F("0000000000000000");
|
||
}
|
||
|
||
if (!rn2xx3_helper::isHexStr_of_length(_appkey, 32)) {
|
||
_appkey = F("00000000000000000000000000000000");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|