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:
jreiss
Date:
Wed Jan 18 13:33:36 2017 +0000
Revision:
11:829f8c2ec1c3
Child:
12:2e8fda56093f
KR920: max DR 7; AS923: max tx power 36 max EIRP 20 defaults

Who changed what in which revision?

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