MultiTech / mDot_Channel_Plans

The channel plans in this library can be used as starting points for new channel plans and used as a reference for implementation.

Information

To use source version of a channel plan, first remove the Channel Plans folder from libmDot-Custom library.

Not all plans are complete to LoRaWAN specifications.

AS923 and KR920 have the default channels defined and can accept in channels in the Join Accept message or from New Channel MAC commands.

Channel Set must match those expected by the network server in order for ADR to work

AS923 regional settings can be adjusted by the network server using Tx Param Setup MAC command to set max EIRP and dwell time for uplinks.

Committer:
Jason Reiss
Date:
Tue Feb 07 15:32:43 2017 -0600
Revision:
14:5bbcd92d635a
Parent:
13:996f1663d12e
Update ADR ACK to change DR after each ACK_DELAY packets after ACK_LIMIT

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Jason Reiss 13:996f1663d12e 1 /**********************************************************************
Jason Reiss 13:996f1663d12e 2 * COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
Jason Reiss 13:996f1663d12e 3 *
Jason Reiss 13:996f1663d12e 4 * ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
Jason Reiss 13:996f1663d12e 5 * MULTI-TECH SYSTEMS, INC.
Jason Reiss 13:996f1663d12e 6 *
Jason Reiss 13:996f1663d12e 7 * MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
Jason Reiss 13:996f1663d12e 8 * INFORMATION AND/OR TRADE SECRET.
Jason Reiss 13:996f1663d12e 9 *
Jason Reiss 13:996f1663d12e 10 * NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
Jason Reiss 13:996f1663d12e 11 * DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
Jason Reiss 13:996f1663d12e 12 * INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
Jason Reiss 13:996f1663d12e 13 * USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
Jason Reiss 13:996f1663d12e 14 * WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
Jason Reiss 13:996f1663d12e 15 *
Jason Reiss 13:996f1663d12e 16 ***********************************************************************/
Jason Reiss 13:996f1663d12e 17
Jason Reiss 13:996f1663d12e 18 #include "CustomChannelPlan_NZ918.h"
Jason Reiss 13:996f1663d12e 19 #include "limits.h"
Jason Reiss 13:996f1663d12e 20
Jason Reiss 13:996f1663d12e 21 using namespace lora;
Jason Reiss 13:996f1663d12e 22
Jason Reiss 13:996f1663d12e 23 const uint8_t CustomChannelPlan_NZ918::NZ918_TX_POWERS[] = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 };
Jason Reiss 13:996f1663d12e 24 const uint8_t CustomChannelPlan_NZ918::NZ918_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 19 };
Jason Reiss 13:996f1663d12e 25 const uint8_t CustomChannelPlan_NZ918::NZ918_MAX_PAYLOAD_SIZE[] = { 51, 51, 51, 115, 222, 222, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 };
Jason Reiss 13:996f1663d12e 26 const uint8_t CustomChannelPlan_NZ918::NZ918_MAX_PAYLOAD_SIZE_REPEATER[] = { 51, 51, 51, 115, 222, 222, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 };
Jason Reiss 13:996f1663d12e 27
Jason Reiss 13:996f1663d12e 28 CustomChannelPlan_NZ918::CustomChannelPlan_NZ918(SxRadio& radio, Settings& settings)
Jason Reiss 13:996f1663d12e 29 :
Jason Reiss 13:996f1663d12e 30 ChannelPlan(radio, settings)
Jason Reiss 13:996f1663d12e 31 {
Jason Reiss 13:996f1663d12e 32
Jason Reiss 13:996f1663d12e 33 }
Jason Reiss 13:996f1663d12e 34
Jason Reiss 13:996f1663d12e 35 CustomChannelPlan_NZ918::~CustomChannelPlan_NZ918() {
Jason Reiss 13:996f1663d12e 36
Jason Reiss 13:996f1663d12e 37 }
Jason Reiss 13:996f1663d12e 38
Jason Reiss 13:996f1663d12e 39 void CustomChannelPlan_NZ918::Init() {
Jason Reiss 13:996f1663d12e 40 _type = FIXED;
Jason Reiss 13:996f1663d12e 41 _planName = "NZ918";
Jason Reiss 13:996f1663d12e 42
Jason Reiss 13:996f1663d12e 43 _datarates.clear();
Jason Reiss 13:996f1663d12e 44 _channels.clear();
Jason Reiss 13:996f1663d12e 45 _dutyBands.clear();
Jason Reiss 13:996f1663d12e 46
Jason Reiss 13:996f1663d12e 47 DutyBand band;
Jason Reiss 13:996f1663d12e 48
Jason Reiss 13:996f1663d12e 49 band.Index = 0;
Jason Reiss 13:996f1663d12e 50 band.DutyCycle = 0;
Jason Reiss 13:996f1663d12e 51
Jason Reiss 13:996f1663d12e 52 Datarate dr;
Jason Reiss 13:996f1663d12e 53
Jason Reiss 13:996f1663d12e 54 logWarning("Custom ChannelPlan");
Jason Reiss 13:996f1663d12e 55
Jason Reiss 13:996f1663d12e 56 _maxTxPower = 30;
Jason Reiss 13:996f1663d12e 57 _minTxPower = 10;
Jason Reiss 13:996f1663d12e 58
Jason Reiss 13:996f1663d12e 59 _minFrequency = 918000000;
Jason Reiss 13:996f1663d12e 60 _maxFrequency = 928000000;
Jason Reiss 13:996f1663d12e 61
Jason Reiss 13:996f1663d12e 62 TX_POWERS = NZ918_TX_POWERS;
Jason Reiss 13:996f1663d12e 63 RADIO_POWERS = NZ918_RADIO_POWERS;
Jason Reiss 13:996f1663d12e 64 MAX_PAYLOAD_SIZE = NZ918_MAX_PAYLOAD_SIZE;
Jason Reiss 13:996f1663d12e 65 MAX_PAYLOAD_SIZE_REPEATER = NZ918_MAX_PAYLOAD_SIZE_REPEATER;
Jason Reiss 13:996f1663d12e 66
Jason Reiss 13:996f1663d12e 67 band.FrequencyMin = _minFrequency;
Jason Reiss 13:996f1663d12e 68 band.FrequencyMax = _maxFrequency;
Jason Reiss 13:996f1663d12e 69
Jason Reiss 13:996f1663d12e 70 _freqUBase125k = 918400000;
Jason Reiss 13:996f1663d12e 71 _freqUStep125k = 200000;
Jason Reiss 13:996f1663d12e 72 _freqUBase500k = 0;
Jason Reiss 13:996f1663d12e 73 _freqUStep500k = 0;
Jason Reiss 13:996f1663d12e 74 _freqDBase500k = 923300000;
Jason Reiss 13:996f1663d12e 75 _freqDStep500k = 600000;
Jason Reiss 13:996f1663d12e 76 _settings.Session.Rx2Frequency = 926300000;
Jason Reiss 13:996f1663d12e 77
Jason Reiss 13:996f1663d12e 78 _minDatarate = lora::DR_0;
Jason Reiss 13:996f1663d12e 79 _maxDatarate = lora::DR_5;
Jason Reiss 13:996f1663d12e 80 _minRx2Datarate = DR_8;
Jason Reiss 13:996f1663d12e 81 _maxRx2Datarate = DR_13;
Jason Reiss 13:996f1663d12e 82 _minDatarateOffset = 0;
Jason Reiss 13:996f1663d12e 83 _maxDatarateOffset = 5;
Jason Reiss 13:996f1663d12e 84
Jason Reiss 13:996f1663d12e 85 _numChans125k = 8;
Jason Reiss 13:996f1663d12e 86 _numChans500k = 0;
Jason Reiss 13:996f1663d12e 87
Jason Reiss 13:996f1663d12e 88 logInfo("Initialize channels...");
Jason Reiss 13:996f1663d12e 89
Jason Reiss 13:996f1663d12e 90 SetNumberOfChannels(_numChans125k + _numChans500k, false);
Jason Reiss 13:996f1663d12e 91
Jason Reiss 13:996f1663d12e 92 dr.SpreadingFactor = SF_12;
Jason Reiss 13:996f1663d12e 93
Jason Reiss 13:996f1663d12e 94 logInfo("Initialize datarates...");
Jason Reiss 13:996f1663d12e 95
Jason Reiss 13:996f1663d12e 96 // Add DR0-5
Jason Reiss 13:996f1663d12e 97 while (dr.SpreadingFactor >= SF_7) {
Jason Reiss 13:996f1663d12e 98 AddDatarate(-1, dr);
Jason Reiss 13:996f1663d12e 99 dr.SpreadingFactor--;
Jason Reiss 13:996f1663d12e 100 dr.Index++;
Jason Reiss 13:996f1663d12e 101 }
Jason Reiss 13:996f1663d12e 102
Jason Reiss 13:996f1663d12e 103 // Skip DR6-7 RFU
Jason Reiss 13:996f1663d12e 104 dr.SpreadingFactor = SF_INVALID;
Jason Reiss 13:996f1663d12e 105 AddDatarate(-1, dr), dr.Index++;
Jason Reiss 13:996f1663d12e 106 AddDatarate(-1, dr), dr.Index++;
Jason Reiss 13:996f1663d12e 107
Jason Reiss 13:996f1663d12e 108 if (_settings.Network.ChannelGroup == 0) {
Jason Reiss 13:996f1663d12e 109 band.PowerMax = 30;
Jason Reiss 13:996f1663d12e 110 } else {
Jason Reiss 13:996f1663d12e 111 band.PowerMax = 21;
Jason Reiss 13:996f1663d12e 112 }
Jason Reiss 13:996f1663d12e 113
Jason Reiss 13:996f1663d12e 114 band.TimeOffEnd = 0;
Jason Reiss 13:996f1663d12e 115
Jason Reiss 13:996f1663d12e 116 AddDutyBand(-1, band);
Jason Reiss 13:996f1663d12e 117
Jason Reiss 13:996f1663d12e 118 _settings.Session.Rx2DatarateIndex = DR_8;
Jason Reiss 13:996f1663d12e 119
Jason Reiss 13:996f1663d12e 120 // Add DR8-13
Jason Reiss 13:996f1663d12e 121 dr.SpreadingFactor = SF_12;
Jason Reiss 13:996f1663d12e 122 dr.Bandwidth = BW_500;
Jason Reiss 13:996f1663d12e 123
Jason Reiss 13:996f1663d12e 124 while (dr.SpreadingFactor >= SF_7) {
Jason Reiss 13:996f1663d12e 125 AddDatarate(-1, dr);
Jason Reiss 13:996f1663d12e 126 dr.SpreadingFactor--;
Jason Reiss 13:996f1663d12e 127 dr.Index++;
Jason Reiss 13:996f1663d12e 128 }
Jason Reiss 13:996f1663d12e 129
Jason Reiss 13:996f1663d12e 130 // Skip DR14-15 RFU
Jason Reiss 13:996f1663d12e 131 dr.SpreadingFactor = SF_INVALID;
Jason Reiss 13:996f1663d12e 132 AddDatarate(-1, dr);
Jason Reiss 13:996f1663d12e 133 AddDatarate(-1, dr);
Jason Reiss 13:996f1663d12e 134
Jason Reiss 13:996f1663d12e 135 _settings.Session.TxDatarate = DR_0;
Jason Reiss 13:996f1663d12e 136
Jason Reiss 13:996f1663d12e 137 SetChannelGroup(_settings.Network.ChannelGroup);
Jason Reiss 13:996f1663d12e 138
Jason Reiss 13:996f1663d12e 139 }
Jason Reiss 13:996f1663d12e 140
Jason Reiss 13:996f1663d12e 141 uint8_t CustomChannelPlan_NZ918::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
Jason Reiss 13:996f1663d12e 142
Jason Reiss 13:996f1663d12e 143 if (size > 17) {
Jason Reiss 13:996f1663d12e 144 // TODO: Handle future channel mask settings
Jason Reiss 13:996f1663d12e 145 }
Jason Reiss 13:996f1663d12e 146 return LORA_OK;
Jason Reiss 13:996f1663d12e 147 }
Jason Reiss 13:996f1663d12e 148
Jason Reiss 13:996f1663d12e 149 void CustomChannelPlan_NZ918::SetNumberOfChannels(uint8_t channels, bool resize) {
Jason Reiss 13:996f1663d12e 150 uint8_t newsize = ((channels - 1) / CHAN_MASK_SIZE) + 1;
Jason Reiss 13:996f1663d12e 151
Jason Reiss 13:996f1663d12e 152 if (resize) {
Jason Reiss 13:996f1663d12e 153 _channels.resize(channels);
Jason Reiss 13:996f1663d12e 154 }
Jason Reiss 13:996f1663d12e 155
Jason Reiss 13:996f1663d12e 156 _channelMask.resize(newsize, 0x0);
Jason Reiss 13:996f1663d12e 157 _numChans = channels;
Jason Reiss 13:996f1663d12e 158
Jason Reiss 13:996f1663d12e 159 }
Jason Reiss 13:996f1663d12e 160
Jason Reiss 13:996f1663d12e 161 bool CustomChannelPlan_NZ918::IsChannelEnabled(uint8_t channel) {
Jason Reiss 13:996f1663d12e 162 uint8_t index = channel / CHAN_MASK_SIZE;
Jason Reiss 13:996f1663d12e 163 uint8_t shift = channel % CHAN_MASK_SIZE;
Jason Reiss 13:996f1663d12e 164
Jason Reiss 13:996f1663d12e 165 assert(index < _channelMask.size() * CHAN_MASK_SIZE);
Jason Reiss 13:996f1663d12e 166
Jason Reiss 13:996f1663d12e 167 // cannot shift over 32 bits
Jason Reiss 13:996f1663d12e 168 assert(shift < 32);
Jason Reiss 13:996f1663d12e 169
Jason Reiss 13:996f1663d12e 170 // logDebug("index: %d shift %d cm: %04x bit: %04x enabled: %d", index, shift, _channelMask[index], (1 << shift), (_channelMask[index] & (1 << shift)) == (1 << shift));
Jason Reiss 13:996f1663d12e 171
Jason Reiss 13:996f1663d12e 172 return (_channelMask[index] & (1 << shift)) == (1 << shift);
Jason Reiss 13:996f1663d12e 173 }
Jason Reiss 13:996f1663d12e 174
Jason Reiss 13:996f1663d12e 175 uint8_t CustomChannelPlan_NZ918::SetRx1Offset(uint8_t offset) {
Jason Reiss 13:996f1663d12e 176 _settings.Session.Rx1DatarateOffset = offset;
Jason Reiss 13:996f1663d12e 177 return LORA_OK;
Jason Reiss 13:996f1663d12e 178 }
Jason Reiss 13:996f1663d12e 179
Jason Reiss 13:996f1663d12e 180 uint8_t CustomChannelPlan_NZ918::SetRx2Frequency(uint32_t freq) {
Jason Reiss 13:996f1663d12e 181 _settings.Session.Rx2Frequency = freq;
Jason Reiss 13:996f1663d12e 182 return LORA_OK;
Jason Reiss 13:996f1663d12e 183 }
Jason Reiss 13:996f1663d12e 184
Jason Reiss 13:996f1663d12e 185 uint8_t CustomChannelPlan_NZ918::SetRx2DatarateIndex(uint8_t index) {
Jason Reiss 13:996f1663d12e 186 _settings.Session.Rx2DatarateIndex = index;
Jason Reiss 13:996f1663d12e 187 return LORA_OK;
Jason Reiss 13:996f1663d12e 188 }
Jason Reiss 13:996f1663d12e 189
Jason Reiss 13:996f1663d12e 190 uint8_t CustomChannelPlan_NZ918::SetTxConfig() {
Jason Reiss 13:996f1663d12e 191
Jason Reiss 13:996f1663d12e 192 logInfo("Configure radio for TX");
Jason Reiss 13:996f1663d12e 193
Jason Reiss 13:996f1663d12e 194 uint8_t band = GetDutyBand(GetChannel(_txChannel).Frequency);
Jason Reiss 13:996f1663d12e 195 Datarate txDr = GetDatarate(_settings.Session.TxDatarate);
Jason Reiss 13:996f1663d12e 196 int8_t max_pwr = _dutyBands[band].PowerMax;
Jason Reiss 13:996f1663d12e 197
Jason Reiss 13:996f1663d12e 198 int8_t pwr = 0;
Jason Reiss 13:996f1663d12e 199
Jason Reiss 13:996f1663d12e 200 pwr = std::min < int8_t > (_settings.Session.TxPower, max_pwr);
Jason Reiss 13:996f1663d12e 201 if (pwr + _settings.Network.AntennaGain >= max_pwr + 6 && _settings.Network.AntennaGain > 6) {
Jason Reiss 13:996f1663d12e 202 pwr -= (_settings.Network.AntennaGain - 6);
Jason Reiss 13:996f1663d12e 203 }
Jason Reiss 13:996f1663d12e 204
Jason Reiss 13:996f1663d12e 205 for (int i = 20; i >= 0; i--) {
Jason Reiss 13:996f1663d12e 206 if (RADIO_POWERS[i] <= pwr) {
Jason Reiss 13:996f1663d12e 207 pwr = i;
Jason Reiss 13:996f1663d12e 208 break;
Jason Reiss 13:996f1663d12e 209 }
Jason Reiss 13:996f1663d12e 210 if (i == 0) {
Jason Reiss 13:996f1663d12e 211 pwr = i;
Jason Reiss 13:996f1663d12e 212 }
Jason Reiss 13:996f1663d12e 213 }
Jason Reiss 13:996f1663d12e 214
Jason Reiss 13:996f1663d12e 215 logDebug("Session pwr: %d ant: %d max: %d", _settings.Session.TxPower, _settings.Network.AntennaGain, max_pwr);
Jason Reiss 13:996f1663d12e 216 logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + _settings.Network.AntennaGain);
Jason Reiss 13:996f1663d12e 217
Jason Reiss 13:996f1663d12e 218 uint32_t bw = txDr.Bandwidth;
Jason Reiss 13:996f1663d12e 219 uint32_t sf = txDr.SpreadingFactor;
Jason Reiss 13:996f1663d12e 220 uint8_t cr = txDr.Coderate;
Jason Reiss 13:996f1663d12e 221 uint8_t pl = txDr.PreambleLength;
Jason Reiss 13:996f1663d12e 222 uint16_t fdev = 0;
Jason Reiss 13:996f1663d12e 223 bool crc = txDr.Crc;
Jason Reiss 13:996f1663d12e 224 bool iq = txDr.TxIQ;
Jason Reiss 13:996f1663d12e 225
Jason Reiss 13:996f1663d12e 226 if (_settings.Network.DisableCRC == true)
Jason Reiss 13:996f1663d12e 227 crc = false;
Jason Reiss 13:996f1663d12e 228
Jason Reiss 13:996f1663d12e 229 SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
Jason Reiss 13:996f1663d12e 230
Jason Reiss 13:996f1663d12e 231 if (sf == SF_FSK) {
Jason Reiss 13:996f1663d12e 232 modem = SxRadio::MODEM_FSK;
Jason Reiss 13:996f1663d12e 233 sf = 50e3;
Jason Reiss 13:996f1663d12e 234 fdev = 25e3;
Jason Reiss 13:996f1663d12e 235 bw = 0;
Jason Reiss 13:996f1663d12e 236 }
Jason Reiss 13:996f1663d12e 237
Jason Reiss 13:996f1663d12e 238 _radio.SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
Jason Reiss 13:996f1663d12e 239
Jason Reiss 13:996f1663d12e 240 logDebug("TX PWR: %u DR: %u SF: %u BW: %u CR: %u PL: %u CRC: %d IQ: %d", pwr, txDr.Index, sf, bw, cr, pl, crc, iq);
Jason Reiss 13:996f1663d12e 241
Jason Reiss 13:996f1663d12e 242 return LORA_OK;
Jason Reiss 13:996f1663d12e 243 }
Jason Reiss 13:996f1663d12e 244
Jason Reiss 13:996f1663d12e 245 uint8_t CustomChannelPlan_NZ918::SetRxConfig(uint8_t window, bool continuous) {
Jason Reiss 13:996f1663d12e 246
Jason Reiss 13:996f1663d12e 247 RxWindow rxw = GetRxWindow(window);
Jason Reiss 13:996f1663d12e 248 _radio.SetChannel(rxw.Frequency);
Jason Reiss 13:996f1663d12e 249
Jason Reiss 13:996f1663d12e 250 Datarate rxDr = GetDatarate(rxw.DatarateIndex);
Jason Reiss 13:996f1663d12e 251 uint32_t bw = rxDr.Bandwidth;
Jason Reiss 13:996f1663d12e 252 uint32_t sf = rxDr.SpreadingFactor;
Jason Reiss 13:996f1663d12e 253 uint8_t cr = rxDr.Coderate;
Jason Reiss 13:996f1663d12e 254 uint8_t pl = rxDr.PreambleLength;
Jason Reiss 13:996f1663d12e 255 uint16_t sto = rxDr.SymbolTimeout();
Jason Reiss 13:996f1663d12e 256 uint32_t afc = 0;
Jason Reiss 13:996f1663d12e 257 bool crc = rxDr.Crc;
Jason Reiss 13:996f1663d12e 258
Jason Reiss 13:996f1663d12e 259 if (_settings.Network.DisableCRC == true)
Jason Reiss 13:996f1663d12e 260 crc = false;
Jason Reiss 13:996f1663d12e 261
Jason Reiss 13:996f1663d12e 262 Datarate txDr = GetDatarate(_settings.Session.TxDatarate);
Jason Reiss 13:996f1663d12e 263 bool iq = txDr.RxIQ;
Jason Reiss 13:996f1663d12e 264
Jason Reiss 13:996f1663d12e 265 if (P2PEnabled()) {
Jason Reiss 13:996f1663d12e 266 iq = txDr.TxIQ;
Jason Reiss 13:996f1663d12e 267 }
Jason Reiss 13:996f1663d12e 268
Jason Reiss 13:996f1663d12e 269 SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
Jason Reiss 13:996f1663d12e 270
Jason Reiss 13:996f1663d12e 271 if (sf == SF_FSK) {
Jason Reiss 13:996f1663d12e 272 modem = SxRadio::MODEM_FSK;
Jason Reiss 13:996f1663d12e 273 sf = 50e3;
Jason Reiss 13:996f1663d12e 274 cr = 0;
Jason Reiss 13:996f1663d12e 275 bw = 50e3;
Jason Reiss 13:996f1663d12e 276 afc = 83333;
Jason Reiss 13:996f1663d12e 277 iq = false;
Jason Reiss 13:996f1663d12e 278 }
Jason Reiss 13:996f1663d12e 279
Jason Reiss 13:996f1663d12e 280 // Disable printf's to actually receive packets, printing to debug may mess up the timing
Jason Reiss 13:996f1663d12e 281 // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
Jason Reiss 13:996f1663d12e 282 // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
Jason Reiss 13:996f1663d12e 283
Jason Reiss 13:996f1663d12e 284 _radio.SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
Jason Reiss 13:996f1663d12e 285
Jason Reiss 13:996f1663d12e 286 return LORA_OK;
Jason Reiss 13:996f1663d12e 287 }
Jason Reiss 13:996f1663d12e 288
Jason Reiss 13:996f1663d12e 289 uint8_t CustomChannelPlan_NZ918::AddChannel(int8_t index, Channel channel) {
Jason Reiss 13:996f1663d12e 290 logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
Jason Reiss 13:996f1663d12e 291
Jason Reiss 13:996f1663d12e 292 assert(index < (int) _channels.size());
Jason Reiss 13:996f1663d12e 293
Jason Reiss 13:996f1663d12e 294 if (index >= 0) {
Jason Reiss 13:996f1663d12e 295 _channels[index] = channel;
Jason Reiss 13:996f1663d12e 296 } else {
Jason Reiss 13:996f1663d12e 297 _channels.push_back(channel);
Jason Reiss 13:996f1663d12e 298 }
Jason Reiss 13:996f1663d12e 299
Jason Reiss 13:996f1663d12e 300 return LORA_OK;
Jason Reiss 13:996f1663d12e 301 }
Jason Reiss 13:996f1663d12e 302
Jason Reiss 13:996f1663d12e 303 Channel CustomChannelPlan_NZ918::GetChannel(int8_t index) {
Jason Reiss 13:996f1663d12e 304 Channel chan;
Jason Reiss 13:996f1663d12e 305 memset(&chan, 0, sizeof(Channel));
Jason Reiss 13:996f1663d12e 306
Jason Reiss 13:996f1663d12e 307 if (_channels.size() > 0) {
Jason Reiss 13:996f1663d12e 308 chan = _channels[index];
Jason Reiss 13:996f1663d12e 309 } else {
Jason Reiss 13:996f1663d12e 310 chan.Index = index;
Jason Reiss 13:996f1663d12e 311 chan.DrRange.Fields.Min = _minDatarate;
Jason Reiss 13:996f1663d12e 312 chan.DrRange.Fields.Max = _maxDatarate;
Jason Reiss 13:996f1663d12e 313 chan.Frequency = _freqUBase125k + (_freqUStep125k * index);
Jason Reiss 13:996f1663d12e 314 }
Jason Reiss 13:996f1663d12e 315
Jason Reiss 13:996f1663d12e 316 return chan;
Jason Reiss 13:996f1663d12e 317 }
Jason Reiss 13:996f1663d12e 318
Jason Reiss 13:996f1663d12e 319 uint8_t CustomChannelPlan_NZ918::SetChannelGroup(uint8_t group) {
Jason Reiss 13:996f1663d12e 320
Jason Reiss 13:996f1663d12e 321 _txChannelGroup = group;
Jason Reiss 13:996f1663d12e 322
Jason Reiss 13:996f1663d12e 323 SetChannelMask(0, 0xFFFF);
Jason Reiss 13:996f1663d12e 324
Jason Reiss 13:996f1663d12e 325 return LORA_OK;
Jason Reiss 13:996f1663d12e 326 }
Jason Reiss 13:996f1663d12e 327
Jason Reiss 13:996f1663d12e 328 void CustomChannelPlan_NZ918::LogRxWindow(uint8_t wnd) {
Jason Reiss 13:996f1663d12e 329
Jason Reiss 13:996f1663d12e 330 RxWindow rxw = GetRxWindow(wnd);
Jason Reiss 13:996f1663d12e 331 Datarate rxDr = GetDatarate(rxw.DatarateIndex);
Jason Reiss 13:996f1663d12e 332 uint8_t bw = rxDr.Bandwidth;
Jason Reiss 13:996f1663d12e 333 uint8_t sf = rxDr.SpreadingFactor;
Jason Reiss 13:996f1663d12e 334 uint8_t cr = rxDr.Coderate;
Jason Reiss 13:996f1663d12e 335 uint8_t pl = rxDr.PreambleLength;
Jason Reiss 13:996f1663d12e 336 uint16_t sto = rxDr.SymbolTimeout();
Jason Reiss 13:996f1663d12e 337 bool crc = rxDr.Crc;
Jason Reiss 13:996f1663d12e 338 bool iq = GetTxDatarate().RxIQ;
Jason Reiss 13:996f1663d12e 339
Jason Reiss 13:996f1663d12e 340 logTrace("RX%d on freq: %lu", wnd, rxw.Frequency);
Jason Reiss 13:996f1663d12e 341 logTrace("RX DR: %u SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", rxDr.Index, sf, bw, cr, pl, sto, crc, iq);
Jason Reiss 13:996f1663d12e 342 }
Jason Reiss 13:996f1663d12e 343
Jason Reiss 13:996f1663d12e 344 RxWindow CustomChannelPlan_NZ918::GetRxWindow(uint8_t window) {
Jason Reiss 13:996f1663d12e 345 RxWindow rxw;
Jason Reiss 13:996f1663d12e 346 int index = 0;
Jason Reiss 13:996f1663d12e 347
Jason Reiss 13:996f1663d12e 348 if (P2PEnabled()) {
Jason Reiss 13:996f1663d12e 349 rxw.Frequency = _settings.Network.TxFrequency;
Jason Reiss 13:996f1663d12e 350 index = _settings.Session.TxDatarate;
Jason Reiss 13:996f1663d12e 351 } else {
Jason Reiss 13:996f1663d12e 352 if (window == 1) {
Jason Reiss 13:996f1663d12e 353 if (_settings.Network.Mode == PUBLIC) {
Jason Reiss 13:996f1663d12e 354 rxw.Frequency = _freqDBase500k + (_txChannel % 8) * _freqDStep500k;
Jason Reiss 13:996f1663d12e 355 } else {
Jason Reiss 13:996f1663d12e 356 rxw.Frequency = _freqDBase500k + (_txChannel / 8) * _freqDStep500k;
Jason Reiss 13:996f1663d12e 357 }
Jason Reiss 13:996f1663d12e 358
Jason Reiss 13:996f1663d12e 359 index = _settings.Session.TxDatarate + 8 - _settings.Session.Rx1DatarateOffset;
Jason Reiss 13:996f1663d12e 360
Jason Reiss 13:996f1663d12e 361 if (index < DR_8)
Jason Reiss 13:996f1663d12e 362 index = DR_8;
Jason Reiss 13:996f1663d12e 363 if (index > DR_13)
Jason Reiss 13:996f1663d12e 364 index = DR_13;
Jason Reiss 13:996f1663d12e 365 } else {
Jason Reiss 13:996f1663d12e 366 if (_settings.Network.Mode == PUBLIC) {
Jason Reiss 13:996f1663d12e 367 rxw.Frequency = _settings.Session.Rx2Frequency;
Jason Reiss 13:996f1663d12e 368 } else {
Jason Reiss 13:996f1663d12e 369 if (_txChannel < 64)
Jason Reiss 13:996f1663d12e 370 rxw.Frequency = _freqDBase500k + (_txChannel / 8) * _freqDStep500k;
Jason Reiss 13:996f1663d12e 371 else
Jason Reiss 13:996f1663d12e 372 rxw.Frequency = _freqDBase500k + (_txChannel % 8) * _freqDStep500k;
Jason Reiss 13:996f1663d12e 373 }
Jason Reiss 13:996f1663d12e 374 index = _settings.Session.Rx2DatarateIndex;
Jason Reiss 13:996f1663d12e 375 }
Jason Reiss 13:996f1663d12e 376 }
Jason Reiss 13:996f1663d12e 377
Jason Reiss 13:996f1663d12e 378 rxw.DatarateIndex = index;
Jason Reiss 13:996f1663d12e 379
Jason Reiss 13:996f1663d12e 380 return rxw;
Jason Reiss 13:996f1663d12e 381 }
Jason Reiss 13:996f1663d12e 382
Jason Reiss 13:996f1663d12e 383 uint8_t CustomChannelPlan_NZ918::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
Jason Reiss 13:996f1663d12e 384 status = 0x07;
Jason Reiss 13:996f1663d12e 385 int8_t datarate = 0;
Jason Reiss 13:996f1663d12e 386 int8_t drOffset = 0;
Jason Reiss 13:996f1663d12e 387 uint32_t freq = 0;
Jason Reiss 13:996f1663d12e 388
Jason Reiss 13:996f1663d12e 389 drOffset = payload[index++];
Jason Reiss 13:996f1663d12e 390 datarate = drOffset & 0x0F;
Jason Reiss 13:996f1663d12e 391 drOffset = (drOffset >> 4) & 0x07;
Jason Reiss 13:996f1663d12e 392
Jason Reiss 13:996f1663d12e 393 freq = payload[index++];
Jason Reiss 13:996f1663d12e 394 freq |= payload[index++] << 8;
Jason Reiss 13:996f1663d12e 395 freq |= payload[index++] << 16;
Jason Reiss 13:996f1663d12e 396 freq *= 100;
Jason Reiss 13:996f1663d12e 397
Jason Reiss 13:996f1663d12e 398 if (!CheckRfFrequency(freq)) {
Jason Reiss 13:996f1663d12e 399 logInfo("Freq KO");
Jason Reiss 13:996f1663d12e 400 status &= 0xFE; // Channel frequency KO
Jason Reiss 13:996f1663d12e 401 }
Jason Reiss 13:996f1663d12e 402
Jason Reiss 13:996f1663d12e 403 if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
Jason Reiss 13:996f1663d12e 404 logInfo("DR KO");
Jason Reiss 13:996f1663d12e 405 status &= 0xFD; // Datarate KO
Jason Reiss 13:996f1663d12e 406 }
Jason Reiss 13:996f1663d12e 407
Jason Reiss 13:996f1663d12e 408 if (drOffset < 0 || drOffset > _maxDatarateOffset) {
Jason Reiss 13:996f1663d12e 409 logInfo("DR Offset KO");
Jason Reiss 13:996f1663d12e 410 status &= 0xFB; // Rx1DrOffset range KO
Jason Reiss 13:996f1663d12e 411 }
Jason Reiss 13:996f1663d12e 412
Jason Reiss 13:996f1663d12e 413 if ((status & 0x07) == 0x07) {
Jason Reiss 13:996f1663d12e 414 logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
Jason Reiss 13:996f1663d12e 415 SetRx2DatarateIndex(datarate);
Jason Reiss 13:996f1663d12e 416 SetRx2Frequency(freq);
Jason Reiss 13:996f1663d12e 417 SetRx1Offset(drOffset);
Jason Reiss 13:996f1663d12e 418 } else {
Jason Reiss 13:996f1663d12e 419 logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
Jason Reiss 13:996f1663d12e 420 }
Jason Reiss 13:996f1663d12e 421
Jason Reiss 13:996f1663d12e 422 return LORA_OK;
Jason Reiss 13:996f1663d12e 423 }
Jason Reiss 13:996f1663d12e 424
Jason Reiss 13:996f1663d12e 425 uint8_t CustomChannelPlan_NZ918::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
Jason Reiss 13:996f1663d12e 426
Jason Reiss 13:996f1663d12e 427 // Not Supported in NZ918
Jason Reiss 13:996f1663d12e 428 status = 0;
Jason Reiss 13:996f1663d12e 429 return LORA_OK;
Jason Reiss 13:996f1663d12e 430 }
Jason Reiss 13:996f1663d12e 431
Jason Reiss 13:996f1663d12e 432 uint8_t CustomChannelPlan_NZ918::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
Jason Reiss 13:996f1663d12e 433
Jason Reiss 13:996f1663d12e 434 lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
Jason Reiss 13:996f1663d12e 435 index += 3;
Jason Reiss 13:996f1663d12e 436
Jason Reiss 13:996f1663d12e 437 if (_beaconRxChannel.Frequency != 0) {
Jason Reiss 13:996f1663d12e 438 _beaconRxChannel.DrRange.Value = payload[index];
Jason Reiss 13:996f1663d12e 439 } else {
Jason Reiss 13:996f1663d12e 440 // TODO: set to default beacon rx channel
Jason Reiss 13:996f1663d12e 441 }
Jason Reiss 13:996f1663d12e 442
Jason Reiss 13:996f1663d12e 443 status = 0x03;
Jason Reiss 13:996f1663d12e 444 return LORA_OK;
Jason Reiss 13:996f1663d12e 445 }
Jason Reiss 13:996f1663d12e 446
Jason Reiss 13:996f1663d12e 447 uint8_t CustomChannelPlan_NZ918::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
Jason Reiss 13:996f1663d12e 448
Jason Reiss 13:996f1663d12e 449 status = 0x03;
Jason Reiss 13:996f1663d12e 450 Channel chParam;
Jason Reiss 13:996f1663d12e 451
Jason Reiss 13:996f1663d12e 452 // Skip channel index
Jason Reiss 13:996f1663d12e 453 index++;
Jason Reiss 13:996f1663d12e 454
Jason Reiss 13:996f1663d12e 455 lora::CopyFreqtoInt(payload + index, chParam.Frequency);
Jason Reiss 13:996f1663d12e 456 index += 3;
Jason Reiss 13:996f1663d12e 457 chParam.DrRange.Value = payload[index++];
Jason Reiss 13:996f1663d12e 458
Jason Reiss 13:996f1663d12e 459 if (!_radio.CheckRfFrequency(chParam.Frequency)) {
Jason Reiss 13:996f1663d12e 460 status &= 0xFE; // Channel frequency KO
Jason Reiss 13:996f1663d12e 461 }
Jason Reiss 13:996f1663d12e 462
Jason Reiss 13:996f1663d12e 463 if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
Jason Reiss 13:996f1663d12e 464 status &= 0xFD; // Datarate range KO
Jason Reiss 13:996f1663d12e 465 } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
Jason Reiss 13:996f1663d12e 466 status &= 0xFD; // Datarate range KO
Jason Reiss 13:996f1663d12e 467 } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
Jason Reiss 13:996f1663d12e 468 status &= 0xFD; // Datarate range KO
Jason Reiss 13:996f1663d12e 469 }
Jason Reiss 13:996f1663d12e 470
Jason Reiss 13:996f1663d12e 471 if ((status & 0x03) == 0x03) {
Jason Reiss 13:996f1663d12e 472 _beaconChannel = chParam;
Jason Reiss 13:996f1663d12e 473 }
Jason Reiss 13:996f1663d12e 474
Jason Reiss 13:996f1663d12e 475 if (_beaconChannel.Frequency == 0) {
Jason Reiss 13:996f1663d12e 476 // TODO: Set to default
Jason Reiss 13:996f1663d12e 477 }
Jason Reiss 13:996f1663d12e 478
Jason Reiss 13:996f1663d12e 479 status = 0x01;
Jason Reiss 13:996f1663d12e 480
Jason Reiss 13:996f1663d12e 481 return LORA_OK;
Jason Reiss 13:996f1663d12e 482 }
Jason Reiss 13:996f1663d12e 483
Jason Reiss 13:996f1663d12e 484 bool CustomChannelPlan_NZ918::AdrAckReq() {
Jason Reiss 13:996f1663d12e 485 if (_settings.Network.ADREnabled == false)
Jason Reiss 13:996f1663d12e 486 return false;
Jason Reiss 13:996f1663d12e 487
Jason Reiss 13:996f1663d12e 488 bool ret = false;
Jason Reiss 13:996f1663d12e 489
Jason Reiss 13:996f1663d12e 490 if (_settings.Session.TxDatarate == MIN_DATARATE) {
Jason Reiss 13:996f1663d12e 491 _settings.Session.AdrCounter = 0;
Jason Reiss 13:996f1663d12e 492 } else {
Jason Reiss 13:996f1663d12e 493 logDebug("ADR ACK CNT: %d LIMIT: %d DELAY: %d", _settings.Session.AdrCounter, ADR_ACK_LIMIT, ADR_ACK_DELAY);
Jason Reiss 13:996f1663d12e 494
Jason Reiss 13:996f1663d12e 495 ret = (_settings.Session.AdrCounter >= ADR_ACK_LIMIT);
Jason Reiss 13:996f1663d12e 496
Jason Reiss 13:996f1663d12e 497 if (_settings.Session.AdrCounter >= (ADR_ACK_LIMIT + ADR_ACK_DELAY)) {
Jason Reiss 14:5bbcd92d635a 498 if ((_settings.Session.AdrCounter - 1) % ADR_ACK_DELAY == 0) {
Jason Reiss 13:996f1663d12e 499
Jason Reiss 13:996f1663d12e 500 if (_settings.Session.TxDatarate > MIN_DATARATE) {
Jason Reiss 13:996f1663d12e 501 _settings.Session.TxDatarate--;
Jason Reiss 13:996f1663d12e 502 }
Jason Reiss 13:996f1663d12e 503
Jason Reiss 13:996f1663d12e 504 if (_settings.Session.TxDatarate == MIN_DATARATE) {
Jason Reiss 13:996f1663d12e 505 EnableDefaultChannels();
Jason Reiss 13:996f1663d12e 506 }
Jason Reiss 13:996f1663d12e 507 }
Jason Reiss 13:996f1663d12e 508 }
Jason Reiss 13:996f1663d12e 509 }
Jason Reiss 13:996f1663d12e 510
Jason Reiss 13:996f1663d12e 511 return ret;
Jason Reiss 13:996f1663d12e 512 }
Jason Reiss 13:996f1663d12e 513
Jason Reiss 13:996f1663d12e 514 uint8_t CustomChannelPlan_NZ918::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
Jason Reiss 13:996f1663d12e 515
Jason Reiss 13:996f1663d12e 516 uint8_t power = 0;
Jason Reiss 13:996f1663d12e 517 uint8_t datarate = 0;
Jason Reiss 13:996f1663d12e 518 uint16_t mask = 0;
Jason Reiss 13:996f1663d12e 519 uint8_t ctrl = 0;
Jason Reiss 13:996f1663d12e 520 uint8_t nbRep = 0;
Jason Reiss 13:996f1663d12e 521
Jason Reiss 13:996f1663d12e 522 status = 0x07;
Jason Reiss 13:996f1663d12e 523 datarate = payload[index++];
Jason Reiss 13:996f1663d12e 524 power = datarate & 0x0F;
Jason Reiss 13:996f1663d12e 525 datarate = (datarate >> 4) & 0x0F;
Jason Reiss 13:996f1663d12e 526
Jason Reiss 13:996f1663d12e 527 mask = payload[index++];
Jason Reiss 13:996f1663d12e 528 mask |= payload[index++] << 8;
Jason Reiss 13:996f1663d12e 529
Jason Reiss 13:996f1663d12e 530 nbRep = payload[index++];
Jason Reiss 13:996f1663d12e 531 ctrl = (nbRep >> 4) & 0x07;
Jason Reiss 13:996f1663d12e 532 nbRep &= 0x0F;
Jason Reiss 13:996f1663d12e 533
Jason Reiss 13:996f1663d12e 534 if (nbRep == 0) {
Jason Reiss 13:996f1663d12e 535 nbRep = 1;
Jason Reiss 13:996f1663d12e 536 }
Jason Reiss 13:996f1663d12e 537
Jason Reiss 13:996f1663d12e 538 if (ctrl == 7 || (ctrl >= 1 && ctrl <=5)) {
Jason Reiss 13:996f1663d12e 539 logWarning("Rejecting ADR invalid mask control field %d", ctrl);
Jason Reiss 13:996f1663d12e 540 status &= 0xFE; // ChannelMask KO
Jason Reiss 13:996f1663d12e 541 }
Jason Reiss 13:996f1663d12e 542
Jason Reiss 13:996f1663d12e 543 if (datarate > _maxDatarate) {
Jason Reiss 13:996f1663d12e 544 logDebug("ADR Datarate KO");
Jason Reiss 13:996f1663d12e 545 status &= 0xFD; // Datarate KO
Jason Reiss 13:996f1663d12e 546 }
Jason Reiss 13:996f1663d12e 547 //
Jason Reiss 13:996f1663d12e 548 // Remark MaxTxPower = 0 and MinTxPower = 5
Jason Reiss 13:996f1663d12e 549 //
Jason Reiss 13:996f1663d12e 550 if (TX_POWERS[power] < _minTxPower || TX_POWERS[power] > _maxTxPower) {
Jason Reiss 13:996f1663d12e 551 logDebug("ADR TxPower KO");
Jason Reiss 13:996f1663d12e 552 status &= 0xFB; // TxPower KO
Jason Reiss 13:996f1663d12e 553 }
Jason Reiss 13:996f1663d12e 554
Jason Reiss 13:996f1663d12e 555 if (ctrl == 7 && mask == 0) {
Jason Reiss 13:996f1663d12e 556 // reject command to disable all channels
Jason Reiss 13:996f1663d12e 557 logWarning("Rejecting ADR command to disable all channels");
Jason Reiss 13:996f1663d12e 558 status &= 0xFE; // ChannelMask KO
Jason Reiss 13:996f1663d12e 559 }
Jason Reiss 13:996f1663d12e 560
Jason Reiss 13:996f1663d12e 561 uint8_t chans_enabled = 0;
Jason Reiss 13:996f1663d12e 562
Jason Reiss 13:996f1663d12e 563 if (ctrl == 0) {
Jason Reiss 13:996f1663d12e 564 chans_enabled -= CountBits(_channelMask[ctrl]);
Jason Reiss 13:996f1663d12e 565 chans_enabled += CountBits(mask);
Jason Reiss 13:996f1663d12e 566
Jason Reiss 13:996f1663d12e 567 if (chans_enabled == 0) {
Jason Reiss 13:996f1663d12e 568 // reject command
Jason Reiss 13:996f1663d12e 569 logWarning("Rejecting ADR command to disable all channels");
Jason Reiss 13:996f1663d12e 570 status &= 0xFE; // ChannelMask KO
Jason Reiss 13:996f1663d12e 571 }
Jason Reiss 13:996f1663d12e 572 }
Jason Reiss 13:996f1663d12e 573
Jason Reiss 13:996f1663d12e 574 if ((status & 0x07) == 0x07) {
Jason Reiss 13:996f1663d12e 575 logDebug("ADR settings accepted");
Jason Reiss 13:996f1663d12e 576
Jason Reiss 13:996f1663d12e 577 if (_settings.Network.ADREnabled) {
Jason Reiss 13:996f1663d12e 578 _settings.Session.TxDatarate = datarate;
Jason Reiss 13:996f1663d12e 579 _settings.Session.TxPower = TX_POWERS[power];
Jason Reiss 13:996f1663d12e 580 } else {
Jason Reiss 13:996f1663d12e 581 logInfo("ADR is disabled, DR and Power not changed.");
Jason Reiss 13:996f1663d12e 582 status &= 0xFB; // TxPower KO
Jason Reiss 13:996f1663d12e 583 status &= 0xFD; // Datarate KO
Jason Reiss 13:996f1663d12e 584 }
Jason Reiss 13:996f1663d12e 585
Jason Reiss 13:996f1663d12e 586 if (ctrl == 6) {
Jason Reiss 13:996f1663d12e 587 // enable all 125 kHz channels
Jason Reiss 13:996f1663d12e 588 SetChannelMask(0, 0xFFFF);
Jason Reiss 13:996f1663d12e 589 } else {
Jason Reiss 13:996f1663d12e 590 SetChannelMask(ctrl, mask);
Jason Reiss 13:996f1663d12e 591 }
Jason Reiss 13:996f1663d12e 592
Jason Reiss 13:996f1663d12e 593 _settings.Session.Redundancy = nbRep;
Jason Reiss 13:996f1663d12e 594 }
Jason Reiss 13:996f1663d12e 595
Jason Reiss 13:996f1663d12e 596 logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
Jason Reiss 13:996f1663d12e 597
Jason Reiss 13:996f1663d12e 598 return LORA_OK;
Jason Reiss 13:996f1663d12e 599 }
Jason Reiss 13:996f1663d12e 600
Jason Reiss 13:996f1663d12e 601 uint32_t CustomChannelPlan_NZ918::GetTimeOffAir()
Jason Reiss 13:996f1663d12e 602 {
Jason Reiss 13:996f1663d12e 603 if (_settings.Test.DisableDutyCycle == lora::ON)
Jason Reiss 13:996f1663d12e 604 return 0;
Jason Reiss 13:996f1663d12e 605
Jason Reiss 13:996f1663d12e 606 uint32_t min = 0;
Jason Reiss 13:996f1663d12e 607 uint32_t now = _dutyCycleTimer.read_ms();
Jason Reiss 13:996f1663d12e 608
Jason Reiss 13:996f1663d12e 609 if (_settings.Session.AggregatedTimeOffEnd > 0 && _settings.Session.AggregatedTimeOffEnd > now) {
Jason Reiss 13:996f1663d12e 610 min = std::max < uint32_t > (min, _settings.Session.AggregatedTimeOffEnd - now);
Jason Reiss 13:996f1663d12e 611 }
Jason Reiss 13:996f1663d12e 612
Jason Reiss 13:996f1663d12e 613 now = time(NULL);
Jason Reiss 13:996f1663d12e 614 uint32_t join_time = 0;
Jason Reiss 13:996f1663d12e 615
Jason Reiss 13:996f1663d12e 616 if (_settings.Session.JoinFirstAttempt != 0 && now < _settings.Session.JoinTimeOffEnd) {
Jason Reiss 13:996f1663d12e 617 join_time = (_settings.Session.JoinTimeOffEnd - now) * 1000;
Jason Reiss 13:996f1663d12e 618 }
Jason Reiss 13:996f1663d12e 619
Jason Reiss 13:996f1663d12e 620 min = std::max < uint32_t > (join_time, min);
Jason Reiss 13:996f1663d12e 621
Jason Reiss 13:996f1663d12e 622 return min;
Jason Reiss 13:996f1663d12e 623 }
Jason Reiss 13:996f1663d12e 624
Jason Reiss 13:996f1663d12e 625 std::vector<uint32_t> lora::CustomChannelPlan_NZ918::GetChannels() {
Jason Reiss 13:996f1663d12e 626 std::vector < uint32_t > chans;
Jason Reiss 13:996f1663d12e 627
Jason Reiss 13:996f1663d12e 628 if (_settings.Network.ChannelGroup > 0) {
Jason Reiss 13:996f1663d12e 629 uint8_t chans_per_group = 8;
Jason Reiss 13:996f1663d12e 630 size_t start = (_settings.Network.ChannelGroup - 1) * chans_per_group;
Jason Reiss 13:996f1663d12e 631 for (int8_t i = start; i < int8_t(start + chans_per_group); i++) {
Jason Reiss 13:996f1663d12e 632 chans.push_back(GetChannel(i).Frequency);
Jason Reiss 13:996f1663d12e 633 }
Jason Reiss 13:996f1663d12e 634 chans.push_back(GetChannel(_numChans125k + (_settings.Network.ChannelGroup - 1)).Frequency);
Jason Reiss 13:996f1663d12e 635 chans.push_back(GetRxWindow(2).Frequency);
Jason Reiss 13:996f1663d12e 636 } else {
Jason Reiss 13:996f1663d12e 637 for (int8_t i = 0; i < _numChans; i++) {
Jason Reiss 13:996f1663d12e 638 chans.push_back(GetChannel(i).Frequency);
Jason Reiss 13:996f1663d12e 639 }
Jason Reiss 13:996f1663d12e 640 chans.push_back(GetRxWindow(2).Frequency);
Jason Reiss 13:996f1663d12e 641 }
Jason Reiss 13:996f1663d12e 642
Jason Reiss 13:996f1663d12e 643 return chans;
Jason Reiss 13:996f1663d12e 644 }
Jason Reiss 13:996f1663d12e 645
Jason Reiss 13:996f1663d12e 646 std::vector<uint8_t> lora::CustomChannelPlan_NZ918::GetChannelRanges() {
Jason Reiss 13:996f1663d12e 647 std::vector < uint8_t > ranges;
Jason Reiss 13:996f1663d12e 648
Jason Reiss 13:996f1663d12e 649 if (_settings.Network.ChannelGroup > 0) {
Jason Reiss 13:996f1663d12e 650 uint8_t chans_per_group = 8;
Jason Reiss 13:996f1663d12e 651 size_t start = (_settings.Network.ChannelGroup - 1) * chans_per_group;
Jason Reiss 13:996f1663d12e 652 for (int8_t i = start; i < int8_t(start + chans_per_group); i++) {
Jason Reiss 13:996f1663d12e 653 ranges.push_back(GetChannel(i).DrRange.Value);
Jason Reiss 13:996f1663d12e 654 }
Jason Reiss 13:996f1663d12e 655 ranges.push_back(GetChannel(_numChans125k + (_settings.Network.ChannelGroup - 1)).DrRange.Value);
Jason Reiss 13:996f1663d12e 656 ranges.push_back(GetRxWindow(2).DatarateIndex);
Jason Reiss 13:996f1663d12e 657 } else {
Jason Reiss 13:996f1663d12e 658 for (int8_t i = 0; i < _numChans; i++) {
Jason Reiss 13:996f1663d12e 659 ranges.push_back(GetChannel(i).DrRange.Value);
Jason Reiss 13:996f1663d12e 660 }
Jason Reiss 13:996f1663d12e 661 ranges.push_back(GetRxWindow(2).DatarateIndex);
Jason Reiss 13:996f1663d12e 662 }
Jason Reiss 13:996f1663d12e 663
Jason Reiss 13:996f1663d12e 664 ranges.push_back(GetRxWindow(2).DatarateIndex);
Jason Reiss 13:996f1663d12e 665
Jason Reiss 13:996f1663d12e 666 return ranges;
Jason Reiss 13:996f1663d12e 667
Jason Reiss 13:996f1663d12e 668 }
Jason Reiss 13:996f1663d12e 669
Jason Reiss 13:996f1663d12e 670 void lora::CustomChannelPlan_NZ918::EnableDefaultChannels() {
Jason Reiss 13:996f1663d12e 671 SetChannelGroup(GetChannelGroup());
Jason Reiss 13:996f1663d12e 672 }
Jason Reiss 13:996f1663d12e 673
Jason Reiss 13:996f1663d12e 674 uint8_t CustomChannelPlan_NZ918::GetNextChannel()
Jason Reiss 13:996f1663d12e 675 {
Jason Reiss 13:996f1663d12e 676 if (_settings.Session.AggregatedTimeOffEnd != 0) {
Jason Reiss 13:996f1663d12e 677 return LORA_AGGREGATED_DUTY_CYCLE;
Jason Reiss 13:996f1663d12e 678 }
Jason Reiss 13:996f1663d12e 679
Jason Reiss 13:996f1663d12e 680 if (P2PEnabled() || _settings.Network.TxFrequency != 0) {
Jason Reiss 13:996f1663d12e 681 logDebug("Using frequency %d", _settings.Network.TxFrequency);
Jason Reiss 13:996f1663d12e 682
Jason Reiss 13:996f1663d12e 683 if (_settings.Test.DisableDutyCycle != lora::ON) {
Jason Reiss 13:996f1663d12e 684 int8_t band = GetDutyBand(_settings.Network.TxFrequency);
Jason Reiss 13:996f1663d12e 685 logDebug("band: %d freq: %d", band, _settings.Network.TxFrequency);
Jason Reiss 13:996f1663d12e 686 if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
Jason Reiss 13:996f1663d12e 687 return LORA_NO_CHANS_ENABLED;
Jason Reiss 13:996f1663d12e 688 }
Jason Reiss 13:996f1663d12e 689 }
Jason Reiss 13:996f1663d12e 690
Jason Reiss 13:996f1663d12e 691 _radio.SetChannel(_settings.Network.TxFrequency);
Jason Reiss 13:996f1663d12e 692 return LORA_OK;
Jason Reiss 13:996f1663d12e 693 }
Jason Reiss 13:996f1663d12e 694
Jason Reiss 13:996f1663d12e 695 uint8_t start = 0;
Jason Reiss 13:996f1663d12e 696 uint8_t maxChannels = _numChans125k;
Jason Reiss 13:996f1663d12e 697 uint8_t nbEnabledChannels = 0;
Jason Reiss 13:996f1663d12e 698 uint8_t *enabledChannels = new uint8_t[maxChannels];
Jason Reiss 13:996f1663d12e 699
Jason Reiss 13:996f1663d12e 700 if (GetTxDatarate().Bandwidth == BW_500) {
Jason Reiss 13:996f1663d12e 701 maxChannels = _numChans500k;
Jason Reiss 13:996f1663d12e 702 start = _numChans125k;
Jason Reiss 13:996f1663d12e 703 }
Jason Reiss 13:996f1663d12e 704
Jason Reiss 13:996f1663d12e 705 // Search how many channels are enabled
Jason Reiss 13:996f1663d12e 706 DatarateRange range;
Jason Reiss 13:996f1663d12e 707 uint8_t dr_index = _settings.Session.TxDatarate;
Jason Reiss 13:996f1663d12e 708 uint32_t now = _dutyCycleTimer.read_ms();
Jason Reiss 13:996f1663d12e 709
Jason Reiss 13:996f1663d12e 710 for (size_t i = 0; i < _dutyBands.size(); i++) {
Jason Reiss 13:996f1663d12e 711 if (_dutyBands[i].TimeOffEnd < now || _settings.Test.DisableDutyCycle == lora::ON) {
Jason Reiss 13:996f1663d12e 712 _dutyBands[i].TimeOffEnd = 0;
Jason Reiss 13:996f1663d12e 713 }
Jason Reiss 13:996f1663d12e 714 }
Jason Reiss 13:996f1663d12e 715
Jason Reiss 13:996f1663d12e 716 for (uint8_t i = start; i < start + maxChannels; i++) {
Jason Reiss 13:996f1663d12e 717 range = GetChannel(i).DrRange;
Jason Reiss 13:996f1663d12e 718 // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
Jason Reiss 13:996f1663d12e 719
Jason Reiss 13:996f1663d12e 720 if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
Jason Reiss 13:996f1663d12e 721 int8_t band = GetDutyBand(GetChannel(i).Frequency);
Jason Reiss 13:996f1663d12e 722 // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
Jason Reiss 13:996f1663d12e 723 if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
Jason Reiss 13:996f1663d12e 724 enabledChannels[nbEnabledChannels++] = i;
Jason Reiss 13:996f1663d12e 725 }
Jason Reiss 13:996f1663d12e 726 }
Jason Reiss 13:996f1663d12e 727 }
Jason Reiss 13:996f1663d12e 728
Jason Reiss 13:996f1663d12e 729 if (GetTxDatarate().Bandwidth == BW_500) {
Jason Reiss 13:996f1663d12e 730 _dutyBands[0].PowerMax = 26;
Jason Reiss 13:996f1663d12e 731 } else {
Jason Reiss 13:996f1663d12e 732 if (nbEnabledChannels < 50)
Jason Reiss 13:996f1663d12e 733 _dutyBands[0].PowerMax = 21;
Jason Reiss 13:996f1663d12e 734 else
Jason Reiss 13:996f1663d12e 735 _dutyBands[0].PowerMax = 30;
Jason Reiss 13:996f1663d12e 736 }
Jason Reiss 13:996f1663d12e 737
Jason Reiss 13:996f1663d12e 738 logTrace("Number of available channels: %d", nbEnabledChannels);
Jason Reiss 13:996f1663d12e 739
Jason Reiss 13:996f1663d12e 740 uint32_t freq = 0;
Jason Reiss 13:996f1663d12e 741 uint8_t sf = GetTxDatarate().SpreadingFactor;
Jason Reiss 13:996f1663d12e 742 uint8_t bw = GetTxDatarate().Bandwidth;
Jason Reiss 13:996f1663d12e 743 int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
Jason Reiss 13:996f1663d12e 744
Jason Reiss 13:996f1663d12e 745 if (nbEnabledChannels == 0) {
Jason Reiss 13:996f1663d12e 746 delete[] enabledChannels;
Jason Reiss 13:996f1663d12e 747 return LORA_NO_CHANS_ENABLED;
Jason Reiss 13:996f1663d12e 748 }
Jason Reiss 13:996f1663d12e 749
Jason Reiss 13:996f1663d12e 750 if (_settings.Network.CADEnabled) {
Jason Reiss 13:996f1663d12e 751 // Search for free channel with ms timeout
Jason Reiss 13:996f1663d12e 752 int16_t timeout = 10000;
Jason Reiss 13:996f1663d12e 753 Timer tmr;
Jason Reiss 13:996f1663d12e 754 tmr.start();
Jason Reiss 13:996f1663d12e 755
Jason Reiss 13:996f1663d12e 756 for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
Jason Reiss 13:996f1663d12e 757 freq = GetChannel(enabledChannels[j]).Frequency;
Jason Reiss 13:996f1663d12e 758
Jason Reiss 13:996f1663d12e 759 if (_radio.IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
Jason Reiss 13:996f1663d12e 760 _txChannel = enabledChannels[j];
Jason Reiss 13:996f1663d12e 761 break;
Jason Reiss 13:996f1663d12e 762 }
Jason Reiss 13:996f1663d12e 763 }
Jason Reiss 13:996f1663d12e 764 } else {
Jason Reiss 13:996f1663d12e 765 uint8_t j = rand_r(0, nbEnabledChannels - 1);
Jason Reiss 13:996f1663d12e 766 _txChannel = enabledChannels[j];
Jason Reiss 13:996f1663d12e 767 freq = GetChannel(_txChannel).Frequency;
Jason Reiss 13:996f1663d12e 768 }
Jason Reiss 13:996f1663d12e 769
Jason Reiss 13:996f1663d12e 770 assert(freq != 0);
Jason Reiss 13:996f1663d12e 771
Jason Reiss 13:996f1663d12e 772 logDebug("Using channel %d : %d", _txChannel, freq);
Jason Reiss 13:996f1663d12e 773 _radio.SetChannel(freq);
Jason Reiss 13:996f1663d12e 774
Jason Reiss 13:996f1663d12e 775 delete[] enabledChannels;
Jason Reiss 13:996f1663d12e 776 return LORA_OK;
Jason Reiss 13:996f1663d12e 777 }
Jason Reiss 13:996f1663d12e 778
Jason Reiss 13:996f1663d12e 779 uint8_t lora::CustomChannelPlan_NZ918::GetJoinDatarate() {
Jason Reiss 13:996f1663d12e 780 uint8_t dr = _settings.Session.TxDatarate;
Jason Reiss 13:996f1663d12e 781
Jason Reiss 13:996f1663d12e 782 if (_settings.Test.DisableRandomJoinDatarate == lora::OFF) {
Jason Reiss 13:996f1663d12e 783 static bool altDatarate = false;
Jason Reiss 13:996f1663d12e 784
Jason Reiss 13:996f1663d12e 785 if (altDatarate) {
Jason Reiss 13:996f1663d12e 786 dr = lora::DR_5;
Jason Reiss 13:996f1663d12e 787 } else {
Jason Reiss 13:996f1663d12e 788 dr = lora::DR_0;
Jason Reiss 13:996f1663d12e 789 }
Jason Reiss 13:996f1663d12e 790 altDatarate = !altDatarate;
Jason Reiss 13:996f1663d12e 791 }
Jason Reiss 13:996f1663d12e 792
Jason Reiss 13:996f1663d12e 793 return dr;
Jason Reiss 13:996f1663d12e 794 }
Jason Reiss 13:996f1663d12e 795
Jason Reiss 13:996f1663d12e 796 uint8_t lora::CustomChannelPlan_NZ918::CalculateJoinBackoff(uint8_t size) {
Jason Reiss 13:996f1663d12e 797
Jason Reiss 13:996f1663d12e 798 time_t now = time(NULL);
Jason Reiss 13:996f1663d12e 799 uint32_t time_on_max = 0;
Jason Reiss 13:996f1663d12e 800 static uint32_t time_off_max = 15;
Jason Reiss 13:996f1663d12e 801 uint32_t rand_time_off = 0;
Jason Reiss 13:996f1663d12e 802 static uint16_t join_cnt = 0;
Jason Reiss 13:996f1663d12e 803
Jason Reiss 13:996f1663d12e 804 // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
Jason Reiss 13:996f1663d12e 805
Jason Reiss 13:996f1663d12e 806 if ((time_t)_settings.Session.JoinTimeOffEnd > now) {
Jason Reiss 13:996f1663d12e 807 return LORA_JOIN_BACKOFF;
Jason Reiss 13:996f1663d12e 808 }
Jason Reiss 13:996f1663d12e 809
Jason Reiss 13:996f1663d12e 810 uint32_t secs_since_first_attempt = (now - _settings.Session.JoinFirstAttempt);
Jason Reiss 13:996f1663d12e 811 uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
Jason Reiss 13:996f1663d12e 812
Jason Reiss 13:996f1663d12e 813 join_cnt = (join_cnt+1) % 16;
Jason Reiss 13:996f1663d12e 814
Jason Reiss 13:996f1663d12e 815 if (_settings.Session.JoinFirstAttempt == 0) {
Jason Reiss 13:996f1663d12e 816 /* 1 % duty-cycle for first hour
Jason Reiss 13:996f1663d12e 817 * 0.1 % next 10 hours
Jason Reiss 13:996f1663d12e 818 * 0.01 % upto 24 hours */
Jason Reiss 13:996f1663d12e 819 _settings.Session.JoinFirstAttempt = now;
Jason Reiss 13:996f1663d12e 820 _settings.Session.JoinTimeOnAir += GetTimeOnAir(size);
Jason Reiss 13:996f1663d12e 821 _settings.Session.JoinTimeOffEnd = now + rand_r(_settings.Network.JoinDelay + 2, _settings.Network.JoinDelay + 3);
Jason Reiss 13:996f1663d12e 822 } else if (join_cnt == 0) {
Jason Reiss 13:996f1663d12e 823 if (hours_since_first_attempt < 1) {
Jason Reiss 13:996f1663d12e 824 time_on_max = 36000;
Jason Reiss 13:996f1663d12e 825 rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
Jason Reiss 13:996f1663d12e 826 // time off max 1 hour
Jason Reiss 13:996f1663d12e 827 time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
Jason Reiss 13:996f1663d12e 828
Jason Reiss 13:996f1663d12e 829 if (_settings.Session.JoinTimeOnAir < time_on_max) {
Jason Reiss 13:996f1663d12e 830 _settings.Session.JoinTimeOnAir += GetTimeOnAir(size);
Jason Reiss 13:996f1663d12e 831 _settings.Session.JoinTimeOffEnd = now + rand_time_off;
Jason Reiss 13:996f1663d12e 832 } else {
Jason Reiss 13:996f1663d12e 833 logWarning("Max time-on-air limit met for current join backoff period");
Jason Reiss 13:996f1663d12e 834 _settings.Session.JoinTimeOffEnd = _settings.Session.JoinFirstAttempt + 60 * 60;
Jason Reiss 13:996f1663d12e 835 }
Jason Reiss 13:996f1663d12e 836 } else if (hours_since_first_attempt < 11) {
Jason Reiss 13:996f1663d12e 837 if (_settings.Session.JoinTimeOnAir < 36000) {
Jason Reiss 13:996f1663d12e 838 _settings.Session.JoinTimeOnAir = 36000;
Jason Reiss 13:996f1663d12e 839 }
Jason Reiss 13:996f1663d12e 840 time_on_max = 72000;
Jason Reiss 13:996f1663d12e 841 rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
Jason Reiss 13:996f1663d12e 842 // time off max 1 hour
Jason Reiss 13:996f1663d12e 843 time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
Jason Reiss 13:996f1663d12e 844
Jason Reiss 13:996f1663d12e 845 if (_settings.Session.JoinTimeOnAir < time_on_max) {
Jason Reiss 13:996f1663d12e 846 _settings.Session.JoinTimeOnAir += GetTimeOnAir(size);
Jason Reiss 13:996f1663d12e 847 _settings.Session.JoinTimeOffEnd = now + rand_time_off;
Jason Reiss 13:996f1663d12e 848 } else {
Jason Reiss 13:996f1663d12e 849 logWarning("Max time-on-air limit met for current join backoff period");
Jason Reiss 13:996f1663d12e 850 _settings.Session.JoinTimeOffEnd = _settings.Session.JoinFirstAttempt + 11 * 60 * 60;
Jason Reiss 13:996f1663d12e 851 }
Jason Reiss 13:996f1663d12e 852 } else {
Jason Reiss 13:996f1663d12e 853 if (_settings.Session.JoinTimeOnAir < 72000) {
Jason Reiss 13:996f1663d12e 854 _settings.Session.JoinTimeOnAir = 72000;
Jason Reiss 13:996f1663d12e 855 }
Jason Reiss 13:996f1663d12e 856 uint32_t join_time = 2500;
Jason Reiss 13:996f1663d12e 857
Jason Reiss 13:996f1663d12e 858 // 16 join attempts is ~2754 ms, check if this is the third of the 24 hour period
Jason Reiss 13:996f1663d12e 859
Jason Reiss 13:996f1663d12e 860 time_on_max = 80700;
Jason Reiss 13:996f1663d12e 861 time_off_max = 1 * 60 * 60; // 1 hour
Jason Reiss 13:996f1663d12e 862 rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
Jason Reiss 13:996f1663d12e 863
Jason Reiss 13:996f1663d12e 864 if (_settings.Session.JoinTimeOnAir < time_on_max - join_time) {
Jason Reiss 13:996f1663d12e 865 _settings.Session.JoinTimeOnAir += GetTimeOnAir(size);
Jason Reiss 13:996f1663d12e 866 _settings.Session.JoinTimeOffEnd = now + rand_time_off;
Jason Reiss 13:996f1663d12e 867 } else {
Jason Reiss 13:996f1663d12e 868 logWarning("Max time-on-air limit met for current join backoff period");
Jason Reiss 13:996f1663d12e 869 // Reset the join time on air and set end of restriction to the next 24 hour period
Jason Reiss 13:996f1663d12e 870 _settings.Session.JoinTimeOnAir = 72000;
Jason Reiss 13:996f1663d12e 871 uint16_t days = (now - _settings.Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
Jason Reiss 13:996f1663d12e 872 logWarning("days : %d", days);
Jason Reiss 13:996f1663d12e 873 _settings.Session.JoinTimeOffEnd = _settings.Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
Jason Reiss 13:996f1663d12e 874 }
Jason Reiss 13:996f1663d12e 875 }
Jason Reiss 13:996f1663d12e 876
Jason Reiss 13:996f1663d12e 877 logWarning("JoinBackoff: %lu seconds Time On Air: %lu / %lu", _settings.Session.JoinTimeOffEnd - now, _settings.Session.JoinTimeOnAir, time_on_max);
Jason Reiss 13:996f1663d12e 878 } else {
Jason Reiss 13:996f1663d12e 879 _settings.Session.JoinTimeOnAir += GetTimeOnAir(size);
Jason Reiss 13:996f1663d12e 880 _settings.Session.JoinTimeOffEnd = now + rand_r(_settings.Network.JoinDelay + 2, _settings.Network.JoinDelay + 3);
Jason Reiss 13:996f1663d12e 881 }
Jason Reiss 13:996f1663d12e 882
Jason Reiss 13:996f1663d12e 883 return LORA_OK;
Jason Reiss 13:996f1663d12e 884 }