Stable version of the mDot library for mbed 5. This version of the library is suitable for deployment scenarios. See lastest commit message for version of mbed-os library that has been tested against.

Dependents:   mdot_two_way unh-hackathon-example unh-hackathon-example-raw TelitSensorToCloud ... more

Fork of libmDot-dev-mbed5-deprecated by MultiTech

The Dot library provides a LoRaWan certified stack for LoRa communication using MultiTech mDot and xDot devices. The stack is compatible with mbed 5.

The name of the repository can be used to determine which device the stack was compiled for and if it's a development or production-ready build:

A changelog for the Dot library can be found here.

The Dot library version and the version of mbed-os it was compiled against can both be found in the commit message for that revision of the Dot library. Building your application with the same version of mbed-os as what was used to build the Dot library is highly recommended!

The Dot-Examples repository demonstrates how to use the Dot library in a custom application.

The mDot and xDot platform pages have lots of platform specific information and document potential issues, gotchas, etc, and provide instructions for getting started with development. Please take a look at the platform page before starting development as they should answer many questions you will have.

FOTA

Full FOTA support is only available with mDot, xDot does not have the required external flash. xDot can use the FOTA example to dynamically join a multicast session only. After joining the multicast session the received Fragmentation packets could be handed to a host MCU for processing and at completion the firmware can be loaded into the xDot using the bootloader and y-modem. See xDot Developer Guide.

  • Add the following code to allow Fota to use the Dot instance

main.cpp

    // Initialize FOTA singleton
    Fota::getInstance(dot);
  • Add fragmentation handling the the PacketRx event

RadioEvent.h

    virtual void PacketRx(uint8_t port, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr, lora::DownlinkControl ctrl, uint8_t slot, uint8_t retries, uint32_t address, bool dupRx) {
        mDotEvent::PacketRx(port, payload, size, rssi, snr, ctrl, slot, retries, address, dupRx);

#if ACTIVE_EXAMPLE == FOTA_EXAMPLE
        if(port == 200 || port == 201 || port == 202) {
            Fota::getInstance()->processCmd(payload, port, size);
        }
#endif
    }

A definition is needed to enable Fragmentation support on mDot and save fragments to flash. This should not be defined for xDot and will result in a compiler error.

mbed_app.json

{
    "macros": [
        "FOTA=1"
    ]
}

The FOTA implementation has a few differences from the LoRaWAN Protocol

  • Fragmentation Indexing starts at 0
  • McKEKey is 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
  • Start Time is a count-down in seconds to start of session

Files at this revision

API Documentation at this revision

Comitter:
Jenkins@KEILDM1.dc.multitech.prv
Date:
Thu Jul 27 10:43:57 2017 -0500
Parent:
60:7985b4783af9
Child:
62:255e2ddc294e
Commit message:
mdot-library revision 3.0.0 and mbed-os revision mbed-os-5.4.7

Changed in this revision

ChannelPlan.h Show annotated file Show diff for this revision Revisions of this file
SxRadio.h Show annotated file Show diff for this revision Revisions of this file
SxRadioEvents.h Show annotated file Show diff for this revision Revisions of this file
libmDot-ARMCC.ar Show annotated file Show diff for this revision Revisions of this file
libmDot-GCC_ARM.a Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_AS923.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_AS923.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_AS923_Japan.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_AS923_Japan.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_AU915.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_AU915.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_EU868.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_EU868.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_IN865.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_IN865.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_KR920.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_KR920.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_US915.cpp Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlan_US915.h Show annotated file Show diff for this revision Revisions of this file
plans/ChannelPlans.h Show annotated file Show diff for this revision Revisions of this file
plans/LICENSE.txt Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ChannelPlan.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,611 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_STRATEGY_H__
+#define __CHANNEL_STRATEGY_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include <vector>
+
+namespace lora {
+
+    class ChannelPlan {
+        public:
+
+            /**
+             * Descriptions for channel plans & region information.
+             * Bits 0-2 represent the plan type (fixed or dynamic)
+             *   0b000              cannot be used as plans may line up with old definitions and cause much badness
+             *   0b001              fixed channel plans
+             *   0b010              dynamic channel plans
+             *   0b011 - 0b111      RFU
+             * Bits 3-7 represent the specific channel plan/region within the plan type
+             */
+            enum PlanType {
+                FIXED = 0x20,
+                DYNAMIC = 0x40,
+            };
+
+            enum Plan {
+                EU868_OLD = 0x00,
+                US915_OLD = 0x01,
+                AU915_OLD = 0x02,
+
+                FB_EU868 = 0x00,
+                FB_US915 = 0x01,
+                FB_AU915 = 0x02,
+
+                FB_868 = 0x00,
+                FB_915 = 0x01,
+
+                US915 = FIXED | 0x00,
+                AU915 = FIXED | 0x01,
+
+                EU868 = DYNAMIC | 0x00,
+                IN865 = DYNAMIC | 0x01,
+                AS923 = DYNAMIC | 0x02,
+                KR920 = DYNAMIC | 0x03,
+                AS923_JAPAN = DYNAMIC | 0x04,
+
+                NONE = 0xFF,
+            };
+
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init() = 0;
+
+            /**
+             * Set SxRadio object to be used to set Tx/Rx config
+             */
+            virtual void SetRadio(SxRadio* radio);
+
+            /**
+             * Set Settings object
+             */
+            virtual void SetSettings(Settings* settings);
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel() = 0;
+
+            /**
+             * Set the number of channels in the plan
+             */
+            virtual void SetNumberOfChannels(uint8_t channels, bool resize = true);
+
+            /**
+             * Get the number of channels in the plan
+             */
+            virtual uint8_t GetNumberOfChannels();
+
+            /**
+             * Check if channel is enabled
+             * @return true if enabled
+             */
+            virtual bool IsChannelEnabled(uint8_t channel);
+
+            /**
+             * Set a 16 bit channel mask with offset
+             * @param index of mask to set 0:0-15, 1:16-31 ...
+             * @param mask 16 bit mask of enabled channels
+             * @return true
+             */
+            virtual bool SetChannelMask(uint8_t index, uint16_t mask);
+
+            /**
+             * Get the channel mask of currently enabled channels
+             * @return vector containing channel bit masks
+             */
+            virtual std::vector<uint16_t> GetChannelMask();
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel) = 0;
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index) = 0;
+
+            /**
+             * Add a downlink channel to the ChannelPlan
+             * Set to 0 to use the default uplink channel frequency
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddDownlinkChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetDownlinkChannel(uint8_t index);
+
+            /**
+             * Set number of datarates in ChannelPlan
+             * @param datarates
+             */
+            virtual void SetNumberOfDatarates(uint8_t datarates);
+
+            /**
+             * Add a datarate to the ChannelPlan
+             * @param index of datarate, use -1 to add to end
+             * @param datarate settings to add
+             */
+            virtual uint8_t AddDatarate(int8_t index, Datarate datarate);
+
+            /**
+             * Get datarate at index
+             * @return Datarate
+             */
+            virtual Datarate GetDatarate(int8_t index);
+
+            /**
+             * Get max payload size for current datarate
+             * @return size in bytes
+             */
+            virtual uint8_t GetMaxPayloadSize();
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window) = 0;
+
+            /**
+             * Get current channel to use for transmitting
+             * @param channel index of channel
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxChannel(uint8_t channel);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate() = 0;
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size) = 0;
+
+            /**
+             * Get the current datarate
+             * @return Datarate
+             */
+            virtual Datarate GetTxDatarate();
+
+            /**
+             * Set the current datarate
+             * @param index of datarate
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxDatarate(uint8_t index);
+
+            /**
+             * Set the datarate offset used for first receive window
+             * @param offset
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx1Offset(uint8_t offset);
+
+            /**
+             * Set the frequency for second receive window
+             * @param freq
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx2Frequency(uint32_t freq);
+
+            /**
+             * Set the datarate index used for second receive window
+             * @param index
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx2DatarateIndex(uint8_t index);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig() = 0;
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous) = 0;
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t group) = 0;
+
+            /**
+             * Get frequency sub band if supported by plan
+             * @return sub band 0-8 or 0 if not supported
+             */
+            virtual uint8_t GetFrequencySubBand();
+
+            /**
+             * Reset the ack counter used to lower datarate if ACK's are missed
+             */
+            virtual void ResetAckCounter();
+
+            /**
+             * Callback for radio to request channel change when frequency hopping
+             * @param currentChannel
+             */
+            virtual void FhssChangeChannel(uint8_t currentChannel);
+
+            /**
+             * Callback for ACK timeout event
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAckTimeout();
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size) = 0;
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) = 0;
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) = 0;
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) = 0;
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) = 0;
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) = 0;
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration() = 0;
+
+            /**
+             * Check that Rf Frequency is within channel plan range
+             * @param freq frequency in Hz
+             * @return true if valid frequency
+             */
+            virtual bool CheckRfFrequency(uint32_t freq);
+
+            /**
+             * Flag for ADR
+             * @return true if ADR is enable in settings
+             */
+            virtual bool IsAdrEnabled();
+
+            /**
+             * Flag if ADR ACK should be sent in next packet
+             * @return true when flag should be set
+             */
+            virtual bool AdrAckReq();
+
+            /**
+             * Increment the ADR counter to track when ADR ACK request should be sent
+             * @return current value
+             */
+            virtual uint8_t IncAdrCounter();
+
+            /**
+             * Reset the ADR counter when valid downlink is received from network server
+             */
+            virtual void ResetAdrCounter();
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir() = 0;
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels() = 0;
+
+            /**
+             * Get the downlink channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetDownlinkChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges() = 0;
+
+            /**
+             * Set the time off air for the given duty band
+             * @param band index
+             * @param time off air in ms
+             */
+            virtual void SetDutyBandTimeOff(uint8_t band, uint32_t timeoff);
+
+            /**
+             * Get the time off air for the given duty band
+             * @param band index
+             * @return time off air in ms
+             */
+            virtual uint32_t GetDutyBandTimeOff(uint8_t band);
+
+            /**
+             * Get the number of duty bands in the current channel plan
+             * @return number of bands
+             */
+            virtual uint8_t GetNumDutyBands();
+
+            /**
+             * Get the duty band index for the given frequency
+             * @param freq frequency in Hz
+             * @return index of duty band
+             */
+            virtual int8_t GetDutyBand(uint32_t freq);
+
+            /**
+             * Add duty band
+             * @param index of duty band or -1 to append
+             * @param band DutyBand definition
+             * @return LORA_OK
+             */
+            virtual uint8_t AddDutyBand(int8_t index, DutyBand band);
+
+            /**
+             * Update duty cycle with current settings
+             */
+            virtual void UpdateDutyCycle(uint8_t bytes);
+
+            /**
+             * Update duty cycle with at given frequency and time on air
+             * @param freq frequency
+             * @param time_on_air_ms tx time on air
+             */
+            virtual void UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms);
+
+            /**
+             * Get time on air with current settings
+             * @param bytes number of bytes to be sent
+             */
+            virtual uint32_t GetTimeOnAir(uint8_t bytes);
+
+            /**
+             * Reset the duty timers with the current time off air
+             */
+            virtual void ResetDutyCycleTimer();
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd) = 0;
+
+            /**
+             * Indicator of P2P mode
+             * @return true if enabled
+             */
+            virtual bool P2PEnabled();
+
+            /**
+             * Ack timeout for P2P mode
+             * @return timeout in ms
+             */
+            virtual uint16_t P2PTimeout();
+
+            /**
+             * Ack backoff for P2P mode
+             * @return backoff in ms
+             */
+            virtual uint16_t P2PBackoff();
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels() = 0;
+
+            /**
+             *  Callback for radio thread to signal
+             */
+            virtual void MacEvent();
+
+            /**
+             * Called when MAC layer doesn't know about a command.
+             * Use to add custom or new mac command handling
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleMacCommand(uint8_t* payload, uint8_t& index);
+
+            virtual void DecrementDatarate();
+            virtual void IncrementDatarate();
+
+            virtual std::string GetPlanName();
+            virtual uint8_t GetPlan();
+            virtual bool IsPlanFixed();
+            virtual bool IsPlanDynamic();
+            static bool IsPlanFixed(uint8_t plan);
+            static bool IsPlanDynamic(uint8_t plan);
+            virtual uint32_t GetMinFrequency();
+            virtual uint32_t GetMaxFrequency();
+
+            virtual uint8_t GetMinDatarate();
+            virtual uint8_t GetMaxDatarate();
+            virtual uint8_t GetMinDatarateOffset();
+            virtual uint8_t GetMaxDatarateOffset();
+
+            virtual uint8_t GetMinRx2Datarate();
+            virtual uint8_t GetMaxRx2Datarate();
+            virtual uint8_t GetMaxTxPower();
+            virtual uint8_t GetMinTxPower();
+
+            virtual uint16_t GetLBT_TimeUs();
+            virtual void SetLBT_TimeUs(uint16_t us);
+
+            virtual int8_t GetLBT_Threshold();
+            virtual void SetLBT_Threshold(int8_t rssi);
+
+            /**
+             * Set LBT time and threshold to defaults
+             */
+            virtual void DefaultLBT();
+
+            virtual bool ListenBeforeTalk();
+
+        protected:
+
+            SxRadio* GetRadio();                //!< Get pointer to the SxRadio object or assert if it is null
+            Settings* GetSettings();            //!< Get pointer to the settings object or assert if it is null
+
+            uint8_t _txChannel;                 //!< Current channel for transmit
+            uint8_t _txFrequencySubBand;        //!< Current frequency sub band for hybrid operation
+
+            std::vector<Datarate> _datarates;   //!< List of datarates
+
+            std::vector<Channel> _channels;     //!< List of channels for transmit
+            std::vector<Channel> _dlChannels;   //!< List of channels for receive if changed from default
+
+            std::vector<DutyBand> _dutyBands;   //!< List of duty bands to limit radio time on air
+
+            uint8_t _maxTxPower;                //!< Max Tx power for channel Plan
+            uint8_t _minTxPower;
+
+            uint32_t _minFrequency;             //!< Minimum Frequency
+            uint32_t _maxFrequency;             //!< Maximum Frequency
+
+            Channel _beaconChannel;             //!< Beacon window settings
+            Channel _beaconRxChannel;           //!< Beacon slot rx window settings
+
+            uint8_t _minDatarate;               //!< Minimum datarate to accept in ADR request
+            uint8_t _maxDatarate;               //!< Maximum datarate to accept in ADR request
+
+            uint8_t _minRx2Datarate;            //!< Minimum datarate to accept in for Rx2
+            uint8_t _maxRx2Datarate;            //!< Maximum datarate to accept in for Rx2
+            uint8_t _minDatarateOffset;         //!< Minimum datarate offset to accept
+            uint8_t _maxDatarateOffset;         //!< Maximum datarate offset to accept
+
+            uint32_t _freqUBase125k;            //!< Start of 125K uplink channels
+            uint32_t _freqUStep125k;            //!< Step between 125K uplink channels
+            uint32_t _freqUBase500k;            //!< Start of 500K uplink channels
+            uint32_t _freqUStep500k;            //!< Step between 500K uplink channels
+            uint32_t _freqDBase500k;            //!< Start of 500K downlink channels
+            uint32_t _freqDStep500k;            //!< Step between 500K downlink channels
+
+            uint8_t _numChans;                  //!< Number of total channels in plan
+            uint8_t _numChans125k;              //!< Number of 125K  channels in plan
+            uint8_t _numChans500k;              //!< Number of 500K channels in plan
+
+            uint16_t _LBT_TimeUs;               //!< Sample time in us for LBT
+            int8_t _LBT_Threshold;              //!< Threshold in dBm for LBT
+
+            std::vector<uint16_t> _channelMask; //!< Bit mask for currently enabled channels
+
+            Timer _dutyCycleTimer;              //!< Timer for tracking time-off-air
+            RtosTimer _txDutyTimer;             //!< Event timer for expiration of time-off-air
+
+            bool _txDutyCyclePending;           //!< Flag for pending duty cycle event
+
+            static void OnTxDutyCycleEvent(const void* arg);    //!< Rtos callback for duty cycle event
+            void OnTxDutyCycleEventBottom();                    //!< Callback for duty cycle event
+
+            static const uint8_t* TX_POWERS;                    //!< List of available tx powers
+            static const uint8_t* RADIO_POWERS;                    //!< List of available tx powers
+            static const uint8_t* MAX_PAYLOAD_SIZE;             //!< List of max payload sizes for each datarate
+            static const uint8_t* MAX_PAYLOAD_SIZE_REPEATER;    //!< List of repeater compatible max payload sizes for each datarate
+
+            uint8_t _plan;
+            std::string _planName;
+
+        private:
+
+            SxRadio* _radio;                    //!< Injected SxRadio dependency
+            Settings* _settings;                //!< Current settings
+    };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SxRadio.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,290 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+    (C)2013 Semtech
+
+Description: Generic radio driver definition
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Miguel Luis and Gregory Cristian
+*/
+#ifndef __SXRADIO_H__
+#define __SXRADIO_H__
+
+#include <stdint.h>
+#include "rtos.h"
+#include "SxRadioEvents.h"
+
+/*!
+ * \brief Radio driver definition
+ */
+class SxRadio
+{
+public:
+    /*!
+     * Radio driver supported modems
+     */
+    typedef enum
+    {
+        MODEM_FSK = 0,
+        MODEM_LORA,
+    }RadioModems_t;
+
+    /*!
+     * Radio driver internal state machine states definition
+     */
+    typedef enum
+    {
+        RF_IDLE = 0,
+        RF_RX_RUNNING,
+        RF_TX_RUNNING,
+        RF_CAD,
+    }RadioState_t;
+
+    SxRadio(uint32_t WakeupTime) : WakeupTime(WakeupTime), State(RF_IDLE), Modem(MODEM_LORA) { }
+    virtual ~SxRadio() {};
+
+    /*!
+     * \brief Initializes the radio
+     *
+     * \param [IN] events Structure containing the driver callback functions
+     */
+    virtual void Init( SxRadioEvents *events ) = 0;
+    /*!
+     * \brief Prepares the radio for destruction
+     */
+    virtual void Terminate( void ) = 0;
+    /*!
+     * Return current radio status
+     *
+     * \param status Radio status.[RF_IDLE, RF_RX_RUNNING, RF_TX_RUNNING]
+     */
+    virtual RadioState_t Status( void ) { return State; }
+    /*!
+     * \brief Configures the radio with the given modem
+     *
+     * \param [IN] modem Modem to be used [0: FSK, 1: LoRa] 
+     */
+    virtual void SetModem( RadioModems_t modem ) = 0;
+    /*!
+     * \brief Sets the channel frequency
+     *
+     * \param [IN] freq         Channel RF frequency
+     */
+    virtual void SetChannel( uint32_t freq ) = 0;
+    /*!
+     * \brief Sets the channels configuration
+     *
+     * \param [IN] modem      Radio modem to be used [0: FSK, 1: LoRa]
+     * \param [IN] freq       Channel RF frequency
+     * \param [IN] rssiThresh RSSI threshold
+     * \param [IN] rssiVal    pointer to variable to hold RSSI value if desired - ignored if NULL
+     *
+     * \retval isFree         [true: Channel is free, false: Channel is not free]
+     */
+    virtual bool IsChannelFree( RadioModems_t modem, uint32_t freq, uint8_t datarate, int16_t rssiThresh, uint8_t bandwidth, uint32_t timeout = 5000, int16_t *rssiVal = NULL ) = 0;
+    /*!
+     * \brief Generates a 32 bits random value based on the RSSI readings
+     *
+     * \remark This function sets the radio in LoRa modem mode and disables 
+     *         all interrupts.
+     *         After calling this function either Radio.SetRxConfig or
+     *         Radio.SetTxConfig functions must be called.
+     *
+     * \retval randomValue    32 bits random value
+     */
+    virtual uint32_t Random( void ) = 0;
+    /*!
+     * \brief Sets the reception parameters
+     *
+     * \param [IN] modem        Radio modem to be used [0: FSK, 1: LoRa]
+     * \param [IN] bandwidth    Sets the bandwidth
+     *                          FSK : >= 2600 and <= 250000 Hz
+     *                          LoRa: [0: 125 kHz, 1: 250 kHz,
+     *                                 2: 500 kHz, 3: Reserved] 
+     * \param [IN] datarate     Sets the Datarate
+     *                          FSK : 600..300000 bits/s
+     *                          LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
+     *                                10: 1024, 11: 2048, 12: 4096  chips]
+     * \param [IN] coderate     Sets the coding rate (LoRa only)
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] 
+     * \param [IN] bandwidthAfc Sets the AFC Bandwidth (FSK only) 
+     *                          FSK : >= 2600 and <= 250000 Hz
+     *                          LoRa: N/A ( set to 0 ) 
+     * \param [IN] preambleLen  Sets the Preamble length
+     *                          FSK : Number of bytes 
+     *                          LoRa: Length in symbols (the hardware adds 4 more symbols)
+     * \param [IN] symbTimeout  Sets the RxSingle timeout value (LoRa only) 
+     *                          FSK : N/A ( set to 0 ) 
+     *                          LoRa: timeout in symbols
+     * \param [IN] fixLen       Fixed length packets [0: variable, 1: fixed]
+     * \param [IN] payloadLen   Sets payload length when fixed length is used
+     * \param [IN] crcOn        Enables/Disables the CRC [0: OFF, 1: ON]
+     * \param [IN] FreqHopOn    Enables disables the intra-packet frequency hopping
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: [0: OFF, 1: ON]
+     * \param [IN] HopPeriod    Number of symbols bewteen each hop
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: Number of symbols
+     * \param [IN] iqInverted   Inverts IQ signals (LoRa only)
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: [0: not inverted, 1: inverted]
+     * \param [IN] rxContinuous Sets the reception in continuous mode
+     *                          [false: single mode, true: continuous mode]
+     */
+    virtual void SetRxConfig( RadioModems_t modem, uint32_t bandwidth,
+                              uint32_t datarate, uint8_t coderate,
+                              uint32_t bandwidthAfc, uint16_t preambleLen,
+                              uint16_t symbTimeout, bool fixLen,
+                              uint8_t payloadLen,
+                              bool crcOn, bool FreqHopOn, uint8_t HopPeriod,
+                              bool iqInverted, bool rxContinuous ) = 0;
+    /*!
+     * \brief Sets the transmission parameters
+     *
+     * \param [IN] modem        Radio modem to be used [0: FSK, 1: LoRa] 
+     * \param [IN] power        Sets the output power [dBm]
+     * \param [IN] fdev         Sets the frequency deviation (FSK only)
+     *                          FSK : [Hz]
+     *                          LoRa: 0
+     * \param [IN] bandwidth    Sets the bandwidth (LoRa only)
+     *                          FSK : 0
+     *                          LoRa: [0: 125 kHz, 1: 250 kHz,
+     *                                 2: 500 kHz, 3: Reserved] 
+     * \param [IN] datarate     Sets the Datarate
+     *                          FSK : 600..300000 bits/s
+     *                          LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
+     *                                10: 1024, 11: 2048, 12: 4096  chips]
+     * \param [IN] coderate     Sets the coding rate (LoRa only)
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] 
+     * \param [IN] preambleLen  Sets the preamble length
+     *                          FSK : Number of bytes 
+     *                          LoRa: Length in symbols (the hardware adds 4 more symbols)
+     * \param [IN] fixLen       Fixed length packets [0: variable, 1: fixed]
+     * \param [IN] crcOn        Enables disables the CRC [0: OFF, 1: ON]
+     * \param [IN] FreqHopOn    Enables disables the intra-packet frequency hopping
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: [0: OFF, 1: ON]
+     * \param [IN] HopPeriod    Number of symbols bewteen each hop
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: Number of symbols
+     * \param [IN] iqInverted   Inverts IQ signals (LoRa only)
+     *                          FSK : N/A ( set to 0 )
+     *                          LoRa: [0: not inverted, 1: inverted]
+     * \param [IN] timeout      Transmission timeout [us]
+     */
+    virtual void SetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev, 
+                              uint32_t bandwidth, uint32_t datarate,
+                              uint8_t coderate, uint16_t preambleLen,
+                              bool fixLen, bool crcOn, bool FreqHopOn,
+                              uint8_t HopPeriod, bool iqInverted, uint32_t timeout ) = 0;
+
+    virtual void SetTxContinuous(bool enable) = 0;
+
+    /*!
+     * \brief Checks if the given RF frequency is supported by the hardware
+     *
+     * \param [IN] frequency RF frequency to be checked
+     * \retval isSupported [true: supported, false: unsupported]
+     */
+    virtual bool CheckRfFrequency( uint32_t frequency ) { return true; }
+    /*!
+     * \brief Computes the packet time on air for the given payload
+     *
+     * \Remark Can only be called once SetRxConfig or SetTxConfig have been called
+     *
+     * \param [IN] modem      Radio modem to be used [0: FSK, 1: LoRa]
+     * \param [IN] pktLen     Packet payload length
+     *
+     * \retval airTime        Computed airTime for the given packet payload length
+     */
+    virtual double TimeOnAir( RadioModems_t modem, uint8_t pktLen ) = 0;
+    /*!
+     * \brief Sends the buffer of size. Prepares the packet to be sent and sets
+     *        the radio in transmission
+     *
+     * \param [IN]: buffer     Buffer pointer
+     * \param [IN]: size       Buffer size
+     */
+    virtual void Send( const uint8_t *buffer, uint8_t size ) = 0;
+    /*!
+     * \brief Sets the radio in sleep mode
+     */
+    virtual void Sleep( void ) = 0;
+    /*!
+     * \brief Sets the radio in standby mode
+     */
+    virtual void Standby( void ) = 0;
+    /*!
+     * \brief Sets the radio in reception mode for the given time
+     * \param [IN] timeout Reception timeout [us]
+     *                     [0: continuous, others timeout]
+     */
+    virtual void Rx( uint32_t timeout ) = 0;
+    /*!
+     * \brief Start a Channel Activity Detection
+     */
+    virtual void StartCad( void ) = 0;
+    /*!
+     * \brief Reads the current RSSI value
+     *
+     * \retval rssiValue Current RSSI value in [dBm]
+     */
+    virtual int16_t Rssi( RadioModems_t modem ) = 0;
+    /*!
+     * \brief Writes the radio register at the specified address
+     *
+     * \param [IN]: addr Register address
+     * \param [IN]: data New register value
+     */
+    virtual void Write( uint8_t addr, uint8_t data ) = 0;
+    /*!
+     * \brief Reads the radio register at the specified address
+     *
+     * \param [IN]: addr Register address
+     * \retval data Register value
+     */
+    virtual uint8_t Read ( uint8_t addr ) = 0;
+    /*!
+     * \brief Writes multiple radio registers starting at address
+     *
+     * \param [IN] addr   First Radio register address
+     * \param [IN] buffer Buffer containing the new register's values
+     * \param [IN] size   Number of registers to be written
+     */
+    virtual void WriteBuffer( uint8_t addr, const uint8_t *buffer, uint8_t size ) = 0;
+    /*!
+     * \brief Reads multiple radio registers starting at address
+     *
+     * \param [IN] addr First Radio register address
+     * \param [OUT] buffer Buffer where to copy the registers data
+     * \param [IN] size Number of registers to be read
+     */
+    virtual void ReadBuffer( uint8_t addr, uint8_t *buffer, uint8_t size ) = 0;
+
+    virtual void SignalMacEvent(void) {};
+
+    void GrabMutex(void) { mutex.lock(); }
+    void ReleaseMutex(void) { mutex.unlock(); }
+
+    const uint32_t WakeupTime;
+
+protected:
+    RadioState_t State;
+
+    RadioModems_t Modem;
+
+    /*!
+     * Access protection
+     */
+    Mutex mutex;
+};
+
+#endif // __SXRADIO_H__
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SxRadioEvents.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,72 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+    (C)2013 Semtech
+
+Description: Generic radio driver definition
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Miguel Luis and Gregory Cristian
+*/
+#ifndef __SXRADIOEVENTS_H__
+#define __SXRADIOEVENTS_H__
+
+/*!
+ * \brief Radio driver callback functions
+ */
+class SxRadioEvents
+{
+public:
+    /*!
+     * \brief  Tx Done callback prototype.
+     */
+    virtual void TxDone( void ) {}
+    /*!
+     * \brief  Tx Timeout callback prototype.
+     */
+    virtual void TxTimeout( void ) {}
+    /*!
+     * \brief Rx Done callback prototype.
+     *
+     * \param [IN] payload Received buffer pointer
+     * \param [IN] size    Received buffer size
+     * \param [IN] rssi    RSSI value computed while receiving the frame [dBm]
+     * \param [IN] snr     Raw SNR value given by the radio hardware
+     *                     FSK : N/A ( set to 0 )
+     *                     LoRa: SNR value is two's complement in 1/4 dB
+     */
+    virtual void RxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) {}
+    /*!
+     * \brief  Rx Timeout callback prototype.
+     */
+    virtual void RxTimeout( void ) {}
+    /*!
+     * \brief Rx Error callback prototype.
+     */
+    virtual void RxError( void ) {}
+    /*!
+     * \brief  FHSS Change Channel callback prototype.
+     *
+     * \param [IN] currentChannel   Index number of the current channel
+     */
+    virtual void FhssChangeChannel( uint8_t currentChannel ) {}
+    /*!
+     * \brief CAD Done callback prototype.
+     *
+     * \param [IN] channelActivityDetected  Channel Activity detected during the CAD
+     */
+    virtual void CadDone( bool channelActivityDetected ) {}
+    /*!
+     * \brief Mac Event callback prototype.
+     */
+    virtual void MacEvent( void ) {}
+
+    virtual void LinkIdle(void) {}
+};
+
+#endif // __SXRADIOEVENTS_H__
+
Binary file libmDot-ARMCC.ar has changed
Binary file libmDot-GCC_ARM.a has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_AS923.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,1048 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_AS923.h"
+#include "limits.h"
+
+using namespace lora;
+
+const uint8_t ChannelPlan_AS923::AS923_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 20 };
+const uint8_t ChannelPlan_AS923::AS923_MAX_PAYLOAD_SIZE[] = { 51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0 };
+const uint8_t ChannelPlan_AS923::AS923_MAX_PAYLOAD_SIZE_REPEATER[] = { 51, 51, 51, 115, 222, 222, 222, 222, 0, 0, 0, 0, 0, 0, 0, 0 };
+const uint8_t ChannelPlan_AS923::AS923_MAX_PAYLOAD_SIZE_400[] = { 0, 0, 19, 61, 133, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0 };
+const uint8_t ChannelPlan_AS923::AS923_MAX_PAYLOAD_SIZE_REPEATER_400[] = { 0, 0, 19, 61, 133, 222, 222, 222, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+const uint8_t MAX_ERP_VALUES[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 };
+
+ChannelPlan_AS923::ChannelPlan_AS923()
+:
+    ChannelPlan(NULL, NULL)
+{
+
+}
+
+ChannelPlan_AS923::ChannelPlan_AS923(Settings* settings)
+:
+    ChannelPlan(NULL, settings)
+{
+
+}
+
+ChannelPlan_AS923::ChannelPlan_AS923(SxRadio* radio, Settings* settings)
+:
+    ChannelPlan(radio, settings)
+{
+
+}
+
+ChannelPlan_AS923::~ChannelPlan_AS923() {
+
+}
+
+void ChannelPlan_AS923::Init() {
+
+    _datarates.clear();
+    _channels.clear();
+    _dutyBands.clear();
+
+    DutyBand band;
+
+    band.Index = 0;
+    band.DutyCycle = 0;
+
+    Datarate dr;
+
+    _plan = AS923;
+    _planName = "AS923";
+    _maxTxPower = 36;
+    _minTxPower = 0;
+
+    _minFrequency = 920000000;
+    _maxFrequency = 928000000;
+
+    RADIO_POWERS = AS923_RADIO_POWERS;
+    MAX_PAYLOAD_SIZE = AS923_MAX_PAYLOAD_SIZE;
+    MAX_PAYLOAD_SIZE_REPEATER = AS923_MAX_PAYLOAD_SIZE_REPEATER;
+
+    _minDatarate = 0;
+    _maxDatarate = 7;
+
+    _minRx2Datarate = DR_0;
+    _maxRx2Datarate = DR_7;
+
+    _minDatarateOffset = 0;
+    _maxDatarateOffset = 7;
+
+    _numChans125k = 16;
+    _numChans500k = 0;
+
+    GetSettings()->Session.Rx2Frequency = 923200000;
+    GetSettings()->Session.Rx2DatarateIndex = DR_2;
+    GetSettings()->Session.Max_EIRP  = 20;
+
+    logInfo("Initialize datarates...");
+
+    dr.SpreadingFactor = SF_12;
+
+    // Add DR0-5
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Add DR6
+    dr.SpreadingFactor = SF_7;
+    dr.Bandwidth = BW_250;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    // Add DR7
+    dr.SpreadingFactor = SF_FSK;
+    dr.Bandwidth = BW_FSK;
+    dr.PreambleLength = 10;
+    dr.Coderate = 0;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    _maxDatarate = DR_7;
+
+    // Skip DR8-15 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    while (dr.Index++ < DR_15) {
+        AddDatarate(-1, dr);
+    }
+
+    GetSettings()->Session.TxDatarate = 0;
+
+    logInfo("Initialize channels...");
+
+    Channel chan;
+    chan.DrRange.Fields.Min = DR_0;
+    chan.DrRange.Fields.Max = DR_5;
+    chan.Index = 0;
+    chan.Frequency = 923200000;
+    SetNumberOfChannels(16);
+
+    for (uint8_t i = 0; i < 2; i++) {
+        AddChannel(i, chan);
+        chan.Index++;
+        chan.Frequency += 200000;
+    }
+
+    chan.DrRange.Value = 0;
+    chan.Frequency = 0;
+
+    for (uint8_t i = 2; i < 16; i++) {
+        AddChannel(i, chan);
+        chan.Index++;
+    }
+
+    SetChannelMask(0, 0x03);
+
+
+    // Add downlink channel defaults
+    chan.Index = 0;
+    _dlChannels.resize(16);
+    for (uint8_t i = 0; i < 16; i++) {
+        AddDownlinkChannel(i, chan);
+        chan.Index++;
+    }
+
+    band.Index = 0;
+    band.FrequencyMin = _minFrequency;
+    band.FrequencyMax = _maxFrequency;
+    band.PowerMax = 14;
+    band.TimeOffEnd = 0;
+
+    // Disable duty-cycle limits
+    band.DutyCycle = 0;
+
+    AddDutyBand(-1, band);
+
+    GetSettings()->Session.TxPower = GetSettings()->Network.TxPower;
+}
+
+uint8_t ChannelPlan_AS923::AddChannel(int8_t index, Channel channel) {
+    logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
+
+    assert(index < (int) _channels.size());
+
+    if (index >= 0) {
+        _channels[index] = channel;
+    } else {
+        _channels.push_back(channel);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
+
+    if (size == 33) {
+        Channel ch;
+        int index = 2;
+        for (int i = 13; i < size - 5; i += 3) {
+
+            ch.Frequency = ((buffer[i]) | (buffer[i + 1] << 8) | (buffer[i + 2] << 16)) * 100u;
+
+            if (ch.Frequency > 0) {
+                ch.Index = index;
+                ch.DrRange.Fields.Min = static_cast<int8_t>(DR_0);
+                ch.DrRange.Fields.Max = static_cast<int8_t>(DR_5);
+                AddChannel(index, ch);
+
+                if (GetDutyBand(ch.Frequency) > -1)
+                    _channelMask[0] |= (1 << index);
+                else
+                    _channelMask[0] |= ~(1 << index);
+
+                index += 1;
+            }
+        }
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::SetTxConfig() {
+
+    logInfo("Configure radio for TX");
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    int8_t max_pwr = GetSettings()->Session.Max_EIRP;
+
+    int8_t pwr = 0;
+
+    pwr = std::min < int8_t > (GetSettings()->Session.TxPower, (max_pwr - GetSettings()->Network.AntennaGain));
+
+    for (int i = 20; i >= 0; i--) {
+        if (RADIO_POWERS[i] <= pwr) {
+            pwr = i;
+            break;
+        }
+        if (i == 0) {
+            pwr = i;
+        }
+    }
+
+    logDebug("Session pwr: %d ant: %d max: %d", GetSettings()->Session.TxPower, GetSettings()->Network.AntennaGain, max_pwr);
+    logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + GetSettings()->Network.AntennaGain);
+
+    uint32_t bw = txDr.Bandwidth;
+    uint32_t sf = txDr.SpreadingFactor;
+    uint8_t cr = txDr.Coderate;
+    uint8_t pl = txDr.PreambleLength;
+    uint16_t fdev = 0;
+    bool crc = txDr.Crc;
+    bool iq = txDr.TxIQ;
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        fdev = 25e3;
+        bw = 0;
+    }
+
+    GetRadio()->SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
+
+    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);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::SetRxConfig(uint8_t window, bool continuous) {
+
+    RxWindow rxw = GetRxWindow(window);
+
+    if (_dlChannels[_txChannel].Frequency != 0)
+        GetRadio()->SetChannel(_dlChannels[_txChannel].Frequency);
+    else
+        GetRadio()->SetChannel(rxw.Frequency);
+
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint32_t bw = rxDr.Bandwidth;
+    uint32_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    uint32_t afc = 0;
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    bool iq = txDr.RxIQ;
+
+    if (P2PEnabled()) {
+        iq = txDr.TxIQ;
+    }
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        cr = 0;
+        bw = 50e3;
+        afc = 83333;
+        iq = false;
+        crc = true;  // FSK must use CRC
+    }
+
+    // Disable printf's to actually receive packets, printing to debug may mess up the timing
+    // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
+    // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
+
+    GetRadio()->SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
+
+    return LORA_OK;
+}
+
+Channel ChannelPlan_AS923::GetChannel(int8_t index) {
+    Channel chan;
+    memset(&chan, 0, sizeof(Channel));
+
+    chan = _channels[index];
+
+    return chan;
+}
+
+uint8_t ChannelPlan_AS923::SetFrequencySubBand(uint8_t sub_band) {
+    return LORA_OK;
+}
+
+void ChannelPlan_AS923::LogRxWindow(uint8_t wnd) {
+
+    RxWindow rxw = GetRxWindow(wnd);
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint8_t bw = rxDr.Bandwidth;
+    uint8_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+    bool iq = GetTxDatarate().RxIQ;
+    uint32_t freq = rxw.Frequency;
+
+    if (_dlChannels[_txChannel].Frequency != 0)
+        freq = _dlChannels[_txChannel].Frequency;
+
+    logTrace("RX%d on freq: %lu", wnd, freq);
+    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);
+}
+
+uint8_t ChannelPlan_AS923::GetMaxPayloadSize() {
+    if (GetSettings()->Session.UplinkDwelltime == 1) {
+        if (GetSettings()->Network.RepeaterMode)
+            return AS923_MAX_PAYLOAD_SIZE_REPEATER_400[GetSettings()->Session.TxDatarate];
+        else
+            return AS923_MAX_PAYLOAD_SIZE_400[GetSettings()->Session.TxDatarate];
+    } else {
+        if (GetSettings()->Network.RepeaterMode)
+            return MAX_PAYLOAD_SIZE_REPEATER[GetSettings()->Session.TxDatarate];
+        else
+            return MAX_PAYLOAD_SIZE[GetSettings()->Session.TxDatarate];
+    }
+}
+
+RxWindow ChannelPlan_AS923::GetRxWindow(uint8_t window) {
+    RxWindow rxw;
+    int index = 0;
+
+    if (P2PEnabled()) {
+        rxw.Frequency = GetSettings()->Network.TxFrequency;
+        index = GetSettings()->Session.TxDatarate;
+    } else {
+        if (window == 1) {
+            // Use same frequency as TX
+            rxw.Frequency = _channels[_txChannel].Frequency;
+
+            if (GetSettings()->Session.Rx1DatarateOffset >= 6) {
+                index =  GetSettings()->Session.TxDatarate + (GetSettings()->Session.Rx1DatarateOffset == 6 ? 1 : 2);
+                index = std::min<int>(index, _maxDatarate);
+            } else if (GetSettings()->Session.TxDatarate > GetSettings()->Session.Rx1DatarateOffset) {
+                index = GetSettings()->Session.TxDatarate - GetSettings()->Session.Rx1DatarateOffset;
+            } else {
+                index = 0;
+            }
+
+            // LoRaWAN 1.0.2 Sec 2.7.7
+            uint8_t minDr = GetSettings()->Session.DownlinkDwelltime == 1 ? 2 : 0;
+            index = std::min<uint8_t>(5, std::max<uint8_t>(minDr, index));
+
+        } else {
+            // Use session RX2 frequency
+            rxw.Frequency = GetSettings()->Session.Rx2Frequency;
+            index = GetSettings()->Session.Rx2DatarateIndex;
+        }
+    }
+
+    rxw.DatarateIndex = index;
+
+    return rxw;
+}
+
+uint8_t ChannelPlan_AS923::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    status = 0x07;
+    int8_t datarate = 0;
+    int8_t drOffset = 0;
+    uint32_t freq = 0;
+
+    drOffset = payload[index++];
+    datarate = drOffset & 0x0F;
+    drOffset = (drOffset >> 4) & 0x07;
+
+    freq = payload[index++];
+    freq |= payload[index++] << 8;
+    freq |= payload[index++] << 16;
+    freq *= 100;
+
+    if (!CheckRfFrequency(freq)) {
+        logInfo("Freq KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
+        logInfo("DR KO");
+        status &= 0xFD; // Datarate KO
+    }
+
+    if (drOffset < 0 || drOffset > _maxDatarateOffset) {
+        logInfo("DR Offset KO");
+        status &= 0xFB; // Rx1DrOffset range KO
+    }
+
+    if ((status & 0x07) == 0x07) {
+        logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+        SetRx2DatarateIndex(datarate);
+        SetRx2Frequency(freq);
+        SetRx1Offset(drOffset);
+    } else {
+        logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    uint8_t channelIndex = 0;
+    Channel chParam;
+
+    channelIndex = payload[index++];
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (channelIndex < 2 || channelIndex > _channels.size() - 1) {
+        logError("New Channel index KO");
+        status &= 0xFE; // Channel index KO
+    }
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        logError("New Channel frequency KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min > chParam.DrRange.Fields.Max) {
+        logError("New Channel datarate min/max KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        logError("New Channel datarate min KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        logError("New Channel datarate max KO");
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        logInfo("New Channel accepted index: %d freq: %lu drRange: %02x", channelIndex, chParam.Frequency, chParam.DrRange.Value);
+        AddChannel(channelIndex, chParam);
+        SetChannelMask(0, _channelMask[0] | 1 << (channelIndex));
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
+    index += 3;
+
+    if (_beaconRxChannel.Frequency != 0) {
+        _beaconRxChannel.DrRange.Value = payload[index];
+    } else {
+        // TODO: set to default beacon rx channel
+    }
+
+    status = 0x03;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    Channel chParam;
+
+    // Skip channel index
+    index++;
+
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        _beaconChannel = chParam;
+    }
+
+    if (_beaconChannel.Frequency == 0) {
+        // TODO: Set to default
+    }
+
+    status = 0x01;
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    uint8_t power = 0;
+    uint8_t datarate = 0;
+    uint16_t mask = 0;
+    uint16_t new_mask = 0;
+    uint8_t ctrl = 0;
+    uint8_t nbRep = 0;
+
+    status = 0x07;
+    datarate = payload[index++];
+    power = datarate & 0x0F;
+    datarate = (datarate >> 4) & 0x0F;
+
+    mask = payload[index++];
+    mask |= payload[index++] << 8;
+
+    nbRep = payload[index++];
+    ctrl = (nbRep >> 4) & 0x07;
+    nbRep &= 0x0F;
+
+    if (nbRep == 0) {
+        nbRep = 1;
+    }
+
+    if (datarate > _maxDatarate) {
+        status &= 0xFD; // Datarate KO
+    }
+    //
+    // Remark MaxTxPower = 0 and MinTxPower = 7
+    //
+    if (power < 0 || power > 7) {
+        status &= 0xFB; // TxPower KO
+    }
+
+    switch (ctrl) {
+        case 0:
+            SetChannelMask(0, mask);
+            break;
+
+        case 6:
+            // enable all currently defined channels
+            // set bits 0 - N of a number by (2<<N)-1
+            new_mask = (1 << _channels.size()) - 1;
+            SetChannelMask(0, new_mask);
+            break;
+
+        default:
+            logWarning("rejecting RFU or unknown control value %d", ctrl);
+            status &= 0xFE; // ChannelMask KO
+            return LORA_ERROR;
+    }
+
+    if (GetSettings()->Network.ADREnabled) {
+        GetSettings()->Session.TxDatarate = datarate;
+        GetSettings()->Session.TxPower = GetSettings()->Session.Max_EIRP - (power * 2);
+        GetSettings()->Session.Redundancy = nbRep;
+    } else {
+        logDebug("ADR is disabled, DR and Power not changed.");
+        status &= 0xFB; // TxPower KO
+        status &= 0xFD; // Datarate KO
+    }
+
+    logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AS923::ValidateAdrConfiguration() {
+    uint8_t status = 0x07;
+    uint8_t datarate = GetSettings()->Session.TxDatarate;
+    uint8_t power = GetSettings()->Session.TxPower;
+
+    if (!GetSettings()->Network.ADREnabled) {
+        logDebug("ADR disabled - no applied changes to validate");
+        return status;
+    }
+
+    if (datarate > _maxDatarate) {
+        logWarning("ADR Datarate KO - outside allowed range");
+        status &= 0xFD; // Datarate KO
+    }
+    if (power < _minTxPower || power > _maxTxPower) {
+        logWarning("ADR TX Power KO - outside allowed range");
+        status &= 0xFB; // TxPower KO
+    }
+
+    // default channels must be enabled
+    if ((_channelMask[0] & 0x0003) != 0x0003) {
+        logWarning("ADR Channel Mask KO - default channels must be enabled");
+        status &= 0xFE; // ChannelMask KO
+    }
+
+    // mask must not contain any undefined channels
+    for (int i = 2; i < 16; i++) {
+        if ((_channelMask[0] & (1 << i)) && (_channels[i].Frequency == 0)) {
+            logWarning("ADR Channel Mask KO - cannot enable undefined channel");
+            status &= 0xFE; // ChannelMask KO
+            break;
+        }
+    }
+
+    return status;
+}
+
+uint8_t ChannelPlan_AS923::HandleAckTimeout() {
+
+    if (!GetSettings()->Network.ADREnabled) {
+        return LORA_ADR_OFF;
+    }
+
+    if ((++(GetSettings()->Session.AckCounter) % 2) == 0) {
+        if (GetSettings()->Session.TxPower < GetSettings()->Network.TxPowerMax) {
+            logTrace("ADR Setting power to maximum");
+            GetSettings()->Session.TxPower = GetSettings()->Network.TxPowerMax;
+        } else if (GetSettings()->Session.TxDatarate > 0) {
+            logTrace("ADR Lowering datarate");
+            GetSettings()->Session.TxDatarate--;
+        }
+    }
+
+    return LORA_OK;
+}
+
+
+uint32_t ChannelPlan_AS923::GetTimeOffAir()
+{
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON)
+        return 0;
+
+    uint32_t min = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+
+    min = UINT_MAX;
+    int8_t band = 0;
+
+    if (P2PEnabled()) {
+        int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+        if (_dutyBands[band].TimeOffEnd > now) {
+            min = _dutyBands[band].TimeOffEnd - now;
+        } else {
+            min = 0;
+        }
+    } else {
+        for (size_t i = 0; i < _channels.size(); i++) {
+            if (IsChannelEnabled(i) && GetChannel(i).Frequency != 0 &&
+                !(GetSettings()->Session.TxDatarate < GetChannel(i).DrRange.Fields.Min ||
+                  GetSettings()->Session.TxDatarate > GetChannel(i).DrRange.Fields.Max)) {
+
+                band = GetDutyBand(GetChannel(i).Frequency);
+                if (band != -1) {
+                    // logDebug("band: %d time-off: %d now: %d", band, _dutyBands[band].TimeOffEnd, now);
+                    if (_dutyBands[band].TimeOffEnd > now) {
+                        min = std::min < uint32_t > (min, _dutyBands[band].TimeOffEnd - now);
+                    } else {
+                        min = 0;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+
+    if (GetSettings()->Session.AggregatedTimeOffEnd > 0 && GetSettings()->Session.AggregatedTimeOffEnd > now) {
+        min = std::max < uint32_t > (min, GetSettings()->Session.AggregatedTimeOffEnd - now);
+    }
+
+    now = time(NULL);
+    uint32_t join_time = 0;
+
+    if (GetSettings()->Session.JoinFirstAttempt != 0 && now < GetSettings()->Session.JoinTimeOffEnd) {
+        join_time = (GetSettings()->Session.JoinTimeOffEnd - now) * 1000;
+    }
+
+    min = std::max < uint32_t > (join_time, min);
+
+    return min;
+}
+
+
+void ChannelPlan_AS923::UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms) {
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON) {
+        _dutyCycleTimer.stop();
+        for (size_t i = 0; i < _dutyBands.size(); i++) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+        return;
+    }
+
+    _dutyCycleTimer.start();
+
+    if (GetSettings()->Session.MaxDutyCycle > 0 && GetSettings()->Session.MaxDutyCycle <= 15) {
+        GetSettings()->Session.AggregatedTimeOffEnd = _dutyCycleTimer.read_ms() + time_on_air_ms * GetSettings()->Session.AggregateDutyCycle;
+        logDebug("Updated Aggregate DCycle Time-off: %lu DC: %f%%", GetSettings()->Session.AggregatedTimeOffEnd, 1 / float(GetSettings()->Session.AggregateDutyCycle));
+    } else {
+        GetSettings()->Session.AggregatedTimeOffEnd = 0;
+    }
+
+
+    uint32_t time_off_air = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now) {
+            _dutyBands[i].TimeOffEnd = 0;
+        } else {
+            _dutyBands[i].TimeOffEnd -= now;
+        }
+
+        if (freq >= _dutyBands[i].FrequencyMin && freq <= _dutyBands[i].FrequencyMax) {
+            logDebug("update TOE: freq: %d i:%d toa: %d DC:%d", freq, i, time_on_air_ms, _dutyBands[i].DutyCycle);
+
+            if (freq > _minFrequency && freq < _maxFrequency && (GetSettings()->Session.TxPower + GetSettings()->Network.AntennaGain) <= 7) {
+                _dutyBands[i].TimeOffEnd = 0;
+            } else {
+                time_off_air = time_on_air_ms * _dutyBands[i].DutyCycle;
+                _dutyBands[i].TimeOffEnd = time_off_air;
+            }
+        }
+    }
+
+
+    ResetDutyCycleTimer();
+}
+
+std::vector<uint32_t> lora::ChannelPlan_AS923::GetChannels() {
+    std::vector < uint32_t > chans;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        chans.push_back(_channels[i].Frequency);
+    }
+    chans.push_back(GetRxWindow(2).Frequency);
+
+    return chans;
+}
+
+std::vector<uint8_t> lora::ChannelPlan_AS923::GetChannelRanges() {
+    std::vector < uint8_t > ranges;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        ranges.push_back(_channels[i].DrRange.Value);
+    }
+
+    ranges.push_back(GetRxWindow(2).DatarateIndex);
+
+    return ranges;
+
+}
+
+void lora::ChannelPlan_AS923::EnableDefaultChannels() {
+    _channelMask[0] |= 0x0003;
+}
+
+uint8_t ChannelPlan_AS923::GetNextChannel()
+{
+    if (GetSettings()->Session.AggregatedTimeOffEnd != 0) {
+        return LORA_AGGREGATED_DUTY_CYCLE;
+    }
+
+    if (P2PEnabled() || GetSettings()->Network.TxFrequency != 0) {
+        logDebug("Using frequency %d", GetSettings()->Network.TxFrequency);
+
+        if (GetSettings()->Test.DisableDutyCycle != lora::ON) {
+            int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+            logDebug("band: %d freq: %d", band, GetSettings()->Network.TxFrequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
+                return LORA_NO_CHANS_ENABLED;
+            }
+        }
+
+        GetRadio()->SetChannel(GetSettings()->Network.TxFrequency);
+        return LORA_OK;
+    }
+
+    uint8_t start = 0;
+    uint8_t maxChannels = _numChans125k;
+    uint8_t nbEnabledChannels = 0;
+    uint8_t *enabledChannels = new uint8_t[maxChannels];
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        maxChannels = _numChans500k;
+        start = _numChans125k;
+    }
+
+// Search how many channels are enabled
+    DatarateRange range;
+    uint8_t dr_index = GetSettings()->Session.TxDatarate;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now || GetSettings()->Test.DisableDutyCycle == lora::ON) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+    }
+
+    for (uint8_t i = start; i < start + maxChannels; i++) {
+        range = GetChannel(i).DrRange;
+        // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
+
+        if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
+            int8_t band = GetDutyBand(GetChannel(i).Frequency);
+            // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
+                enabledChannels[nbEnabledChannels++] = i;
+            }
+        }
+    }
+
+    logTrace("Number of available channels: %d", nbEnabledChannels);
+
+    uint32_t freq = 0;
+    uint8_t sf = GetTxDatarate().SpreadingFactor;
+    uint8_t bw = GetTxDatarate().Bandwidth;
+    int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
+
+    if (nbEnabledChannels == 0) {
+        delete [] enabledChannels;
+        return LORA_NO_CHANS_ENABLED;
+    }
+
+    if (GetSettings()->Network.CADEnabled) {
+        // Search for free channel with ms timeout
+        int16_t timeout = 10000;
+        Timer tmr;
+        tmr.start();
+
+        for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
+            freq = GetChannel(enabledChannels[j]).Frequency;
+
+            // Listen before talk
+            if (GetRadio()->IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
+                _txChannel = enabledChannels[j];
+                break;
+            }
+        }
+    } else {
+        uint8_t j = rand_r(0, nbEnabledChannels - 1);
+        _txChannel = enabledChannels[j];
+        freq = GetChannel(_txChannel).Frequency;
+    }
+
+    assert(freq != 0);
+
+    logDebug("Using channel %d : %d", _txChannel, freq);
+    GetRadio()->SetChannel(freq);
+
+    delete [] enabledChannels;
+    return LORA_OK;
+}
+
+
+uint8_t lora::ChannelPlan_AS923::GetJoinDatarate() {
+    uint8_t dr = GetSettings()->Session.TxDatarate;
+    
+    // Default join datarate is DR2:SF10BW125
+    dr = lora::DR_2;
+    
+    return dr;
+}
+
+uint8_t ChannelPlan_AS923::CalculateJoinBackoff(uint8_t size) {
+
+    time_t now = time(NULL);
+    uint32_t time_on_max = 0;
+    static uint32_t time_off_max = 15;
+    uint32_t rand_time_off = 0;
+
+    // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
+
+    if ((time_t)GetSettings()->Session.JoinTimeOffEnd > now) {
+        return LORA_JOIN_BACKOFF;
+    }
+
+    uint32_t secs_since_first_attempt = (now - GetSettings()->Session.JoinFirstAttempt);
+    uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
+
+    static uint8_t join_cnt = 0;
+
+    join_cnt = (join_cnt+1) % 8;
+
+    if (GetSettings()->Session.JoinFirstAttempt == 0) {
+        /* 1 % duty-cycle for first hour
+         * 0.1 % next 10 hours
+         * 0.01 % upto 24 hours         */
+        GetSettings()->Session.JoinFirstAttempt = now;
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    } else if (join_cnt == 0) {
+        if (hours_since_first_attempt < 1) {
+            time_on_max = 36000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 60 * 60;
+            }
+        } else if (hours_since_first_attempt < 11) {
+            if (GetSettings()->Session.JoinTimeOnAir < 36000) {
+                GetSettings()->Session.JoinTimeOnAir = 36000;
+            }
+            time_on_max = 72000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 11 * 60 * 60;
+            }
+        } else {
+            if (GetSettings()->Session.JoinTimeOnAir < 72000) {
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+            }
+            uint32_t join_time = 2500;
+
+            time_on_max = 80700;
+            time_off_max = 1 * 60 * 60; // 1 hour
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max - join_time) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                // Reset the join time on air and set end of restriction to the next 24 hour period
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+                uint16_t days = (now - GetSettings()->Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
+                logWarning("days : %d", days);
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
+            }
+        }
+
+        logWarning("JoinBackoff: %lu seconds  Time On Air: %lu / %lu", GetSettings()->Session.JoinTimeOffEnd - now, GetSettings()->Session.JoinTimeOnAir, time_on_max);
+    } else {
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    }
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_AS923::HandleMacCommand(uint8_t* payload, uint8_t& index) {
+    logDebug("AS923 Handle Mac index: %d", index);
+
+    switch (payload[index++]) {
+        case SRV_MAC_TX_PARAM_SETUP_REQ: {
+            uint8_t eirp_dwell = payload[index++];
+
+            GetSettings()->Session.DownlinkDwelltime = eirp_dwell >> 5 & 0x01;
+            GetSettings()->Session.UplinkDwelltime = eirp_dwell >> 4 & 0x01;
+            GetSettings()->Session.Max_EIRP = MAX_ERP_VALUES[(eirp_dwell & 0x0F)];
+            logDebug("buffer index %d", GetSettings()->Session.CommandBufferIndex);
+            if (GetSettings()->Session.CommandBufferIndex < COMMANDS_BUFFER_SIZE) {
+                logDebug("Add tx param setup mac cmd to buffer");
+                GetSettings()->Session.CommandBuffer[GetSettings()->Session.CommandBufferIndex++] = MOTE_MAC_TX_PARAM_SETUP_ANS;
+            }
+
+            logDebug("TX PARAM DWELL UL: %d DL: %d Max EIRP: %d", GetSettings()->Session.UplinkDwelltime, GetSettings()->Session.DownlinkDwelltime, GetSettings()->Session.Max_EIRP);
+            break;
+        }
+        case SRV_MAC_DL_CHANNEL_REQ: {
+            uint8_t status = 0x03;
+            uint8_t channelIndex = 0;
+            Channel chParam;
+
+            channelIndex = payload[index++];
+            lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+            index += 3;
+
+            chParam.Index = channelIndex;
+            chParam.DrRange.Value = 0;
+
+            if (channelIndex > 15) {
+                status = 0x00;
+            } else if (_channels[channelIndex].Frequency == 0) {
+                status &= 0x02;
+            } else if (chParam.Frequency != 0 && (chParam.Frequency < _minFrequency || chParam.Frequency > _maxFrequency)) {
+                status &= 0x01;
+            }
+
+            if (status == 0x03 && GetSettings()->Session.CommandBufferIndex+1 < COMMANDS_BUFFER_SIZE) {
+                AddDownlinkChannel(channelIndex, chParam);
+            }
+
+            GetSettings()->Session.DlChannelReqAnswer = status;
+
+            logDebug("DL Channel: index: %d freq: %d status: %d", channelIndex, chParam.Frequency, status);
+            break;
+        }
+        default: {
+            return LORA_ERROR;
+        }
+    }
+
+    return LORA_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_AS923.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,239 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_AS923_H__
+#define __CHANNEL_PLAN_AS923_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include <vector>
+#include "ChannelPlan.h"
+
+namespace lora {
+
+    class ChannelPlan_AS923: public lora::ChannelPlan {
+        public:
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_AS923();
+            ChannelPlan_AS923(Settings* settings);
+            ChannelPlan_AS923(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_AS923();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel();
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index);
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate();
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig();
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous);
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t sub_band);
+
+            /**
+             * Callback for ACK timeout event
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAckTimeout();
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size);
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration();
+
+            /**
+             * Update duty cycle with at given frequency and time on air
+             * @param freq frequency
+             * @param time_on_air_ms tx time on air
+             */
+            virtual void UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms);
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir();
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges();
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd);
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels();
+
+            /**
+             * Called when MAC layer doesn't know about a command.
+             * Use to add custom or new mac command handling
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleMacCommand(uint8_t* payload, uint8_t& index);
+
+            /**
+             * Get max payload size for current datarate
+             * @return size in bytes
+             */
+            virtual uint8_t GetMaxPayloadSize();
+
+        protected:
+
+            static const uint8_t AS923_TX_POWERS[8];                    //!< List of available tx powers
+            static const uint8_t AS923_RADIO_POWERS[21];                 //!< List of calibrated tx powers
+            static const uint8_t AS923_MAX_PAYLOAD_SIZE[];              //!< List of max payload sizes for each datarate
+            static const uint8_t AS923_MAX_PAYLOAD_SIZE_400[];              //!< List of max payload sizes for each datarate
+            static const uint8_t AS923_MAX_PAYLOAD_SIZE_REPEATER[];     //!< List of repeater compatible max payload sizes for each datarate
+            static const uint8_t AS923_MAX_PAYLOAD_SIZE_REPEATER_400[];     //!< List of repeater compatible max payload sizes for each datarate
+    };
+}
+
+#endif //__CHANNEL_PLAN_AS923_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_AS923_Japan.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,60 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_AS923_Japan.h"
+
+using namespace lora;
+
+ChannelPlan_AS923_Japan::ChannelPlan_AS923_Japan()
+:
+    ChannelPlan_AS923(NULL, NULL)
+{
+
+}
+
+ChannelPlan_AS923_Japan::ChannelPlan_AS923_Japan(Settings* settings)
+:
+    ChannelPlan_AS923(NULL, settings)
+{
+
+}
+
+ChannelPlan_AS923_Japan::ChannelPlan_AS923_Japan(SxRadio* radio, Settings* settings)
+:
+    ChannelPlan_AS923(radio, settings)
+{
+
+}
+
+ChannelPlan_AS923_Japan::~ChannelPlan_AS923_Japan() {
+
+}
+
+void ChannelPlan_AS923_Japan::Init() {
+    ChannelPlan_AS923::Init();
+
+    _plan = AS923_JAPAN;
+    _planName = "AS923-JAPAN";
+
+    DefaultLBT();
+}
+
+void ChannelPlan_AS923_Japan::DefaultLBT() {
+    _LBT_TimeUs = 5000;
+    _LBT_Threshold = -65;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_AS923_Japan.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,51 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Mike Fiore
+ * @date   5-16-2017
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_AS923_JAPAN_H__
+#define __CHANNEL_PLAN_AS923_JAPAN_H__
+
+#include "ChannelPlan_AS923.h"
+
+namespace lora {
+
+    class ChannelPlan_AS923_Japan: public lora::ChannelPlan_AS923 {
+        public:
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_AS923_Japan();
+            ChannelPlan_AS923_Japan(Settings* settings);
+            ChannelPlan_AS923_Japan(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_AS923_Japan();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Set LBT time and threshold to defaults
+             */
+            virtual void DefaultLBT();
+    };
+}
+
+#endif //__CHANNEL_PLAN_AS923JAPAN_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_AU915.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,980 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_AU915.h"
+#include "limits.h"
+
+using namespace lora;
+
+const uint8_t ChannelPlan_AU915::AU915_TX_POWERS[] = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 };
+const uint8_t ChannelPlan_AU915::AU915_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 19 };
+const uint8_t ChannelPlan_AU915::AU915_MAX_PAYLOAD_SIZE[] = { 11, 53, 126, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 };
+const uint8_t ChannelPlan_AU915::AU915_MAX_PAYLOAD_SIZE_REPEATER[] = { 11, 53, 126, 222, 222, 0, 0, 33, 109, 222, 222, 222, 0, 0 };
+
+ChannelPlan_AU915::ChannelPlan_AU915()
+:
+  ChannelPlan(NULL, NULL)
+{
+
+}
+
+ChannelPlan_AU915::ChannelPlan_AU915(Settings* settings)
+:
+  ChannelPlan(NULL, settings)
+{
+
+}
+
+ChannelPlan_AU915::ChannelPlan_AU915(SxRadio* radio, Settings* settings)
+:
+  ChannelPlan(radio, settings)
+{
+
+}
+
+ChannelPlan_AU915::~ChannelPlan_AU915() {
+
+}
+
+void ChannelPlan_AU915::Init() {
+    _plan = AU915;
+    _planName = "AU915";
+
+    _datarates.clear();
+    _channels.clear();
+    _dutyBands.clear();
+
+    DutyBand band;
+
+    band.Index = 0;
+    band.DutyCycle = 0;
+
+    Datarate dr;
+
+    _maxTxPower = 30;
+    _minTxPower = 0;
+
+    _minFrequency = AU915_FREQ_MIN;
+    _maxFrequency = AU915_FREQ_MAX;
+
+    TX_POWERS = AU915_TX_POWERS;
+    RADIO_POWERS = AU915_RADIO_POWERS;
+    MAX_PAYLOAD_SIZE = AU915_MAX_PAYLOAD_SIZE;
+    MAX_PAYLOAD_SIZE_REPEATER = AU915_MAX_PAYLOAD_SIZE_REPEATER;
+
+    band.FrequencyMin = AU915_FREQ_MIN;
+    band.FrequencyMax = AU915_FREQ_MAX;
+
+    _freqUBase125k = AU915_125K_FREQ_BASE;
+    _freqUStep125k = AU915_125K_FREQ_STEP;
+    _freqUBase500k = AU915_500K_FREQ_BASE;
+    _freqUStep500k = AU915_500K_FREQ_STEP;
+    _freqDBase500k = AU915_500K_DBASE;
+    _freqDStep500k = AU915_500K_DSTEP;
+    GetSettings()->Session.Rx2Frequency = AU915_500K_DBASE;
+
+    _minDatarate = AU915_MIN_DATARATE;
+    _maxDatarate = AU915_MAX_DATARATE;
+    _minRx2Datarate = DR_8;
+    _maxRx2Datarate = DR_13;
+    _minDatarateOffset = AU915_MIN_DATARATE_OFFSET;
+    _maxDatarateOffset = AU915_MAX_DATARATE_OFFSET;
+
+    _numChans125k = AU915_125K_NUM_CHANS;
+    _numChans500k = AU915_500K_NUM_CHANS;
+
+    logInfo("Initialize channels...");
+
+    SetNumberOfChannels(AU915_125K_NUM_CHANS + AU915_500K_NUM_CHANS, false);
+
+    dr.SpreadingFactor = SF_10;
+
+    logInfo("Initialize datarates...");
+
+    // Add DR0-3
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Add DR4
+    dr.SpreadingFactor = SF_8;
+    dr.Bandwidth = BW_500;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    // Skip DR5-7 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    AddDatarate(-1, dr), dr.Index++;
+    AddDatarate(-1, dr), dr.Index++;
+    AddDatarate(-1, dr), dr.Index++;
+
+    if (GetSettings()->Network.FrequencySubBand == 0) {
+        band.PowerMax = 30;
+    } else {
+        band.PowerMax = 21;
+    }
+
+    band.TimeOffEnd = 0;
+
+    AddDutyBand(-1, band);
+
+    GetSettings()->Session.Rx2DatarateIndex = DR_8;
+
+    // Add DR8-13
+    dr.SpreadingFactor = SF_12;
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Skip DR14-15 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    AddDatarate(-1, dr), AddDatarate(-1, dr);
+
+    GetSettings()->Session.TxDatarate = DR_0;
+    GetSettings()->Session.TxPower = GetSettings()->Network.TxPower;
+
+    SetFrequencySubBand(GetSettings()->Network.FrequencySubBand);
+
+}
+
+uint8_t ChannelPlan_AU915::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
+    if (size > 17) {
+        // TODO: Handle future channel mask settings
+    }
+
+    return LORA_OK;
+}
+
+void ChannelPlan_AU915::SetNumberOfChannels(uint8_t channels, bool resize) {
+    uint8_t newsize = ((channels - 1) / CHAN_MASK_SIZE) + 1;
+
+    if (resize) {
+        _channels.resize(channels);
+    }
+
+    _channelMask.resize(newsize, 0x0);
+    _numChans = channels;
+
+}
+
+uint8_t ChannelPlan_AU915::GetMinDatarate() {
+    if (GetSettings()->Network.Mode == lora::PEER_TO_PEER)
+        return 8;
+    else
+        return _minDatarate;
+}
+
+uint8_t ChannelPlan_AU915::GetMaxDatarate() {
+    if (GetSettings()->Network.Mode == lora::PEER_TO_PEER)
+        return 13;
+    else
+        return _maxDatarate;
+}
+
+bool ChannelPlan_AU915::IsChannelEnabled(uint8_t channel) {
+    uint8_t index = channel / CHAN_MASK_SIZE;
+    uint8_t shift = channel % CHAN_MASK_SIZE;
+
+    assert(index < _channelMask.size() * CHAN_MASK_SIZE);
+
+    // cannot shift over 32 bits
+    assert(shift < 32);
+
+    // logDebug("index: %d shift %d cm: %04x bit: %04x enabled: %d", index, shift, _channelMask[index], (1 << shift), (_channelMask[index] & (1 << shift)) == (1 << shift));
+
+    return (_channelMask[index] & (1 << shift)) == (1 << shift);
+}
+
+uint8_t ChannelPlan_AU915::SetRx1Offset(uint8_t offset) {
+    GetSettings()->Session.Rx1DatarateOffset = offset;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::SetRx2Frequency(uint32_t freq) {
+    GetSettings()->Session.Rx2Frequency = freq;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::SetRx2DatarateIndex(uint8_t index) {
+    GetSettings()->Session.Rx2DatarateIndex = index;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::SetTxConfig() {
+
+    logInfo("Configure radio for TX");
+
+    uint8_t band = GetDutyBand(GetChannel(_txChannel).Frequency);
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    int8_t max_pwr = _dutyBands[band].PowerMax;
+
+    int8_t pwr = 0;
+
+    pwr = std::min < int8_t > (GetSettings()->Session.TxPower, max_pwr);
+    if (pwr + GetSettings()->Network.AntennaGain >= max_pwr + 6 && GetSettings()->Network.AntennaGain > 6) {
+        pwr -= (GetSettings()->Network.AntennaGain - 6);
+    }
+
+    for (int i = 20; i >= 0; i--) {
+        if (RADIO_POWERS[i] <= pwr) {
+            pwr = i;
+            break;
+        }
+        if (i == 0) {
+            pwr = i;
+        }
+    }
+
+    logDebug("Session pwr: %d ant: %d max: %d", GetSettings()->Session.TxPower, GetSettings()->Network.AntennaGain, max_pwr);
+    logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + GetSettings()->Network.AntennaGain);
+
+    uint32_t bw = txDr.Bandwidth;
+    uint32_t sf = txDr.SpreadingFactor;
+    uint8_t cr = txDr.Coderate;
+    uint8_t pl = txDr.PreambleLength;
+    uint16_t fdev = 0;
+    bool crc = txDr.Crc;
+    bool iq = txDr.TxIQ;
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        fdev = 25e3;
+        bw = 0;
+    }
+
+    GetRadio()->SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
+
+    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);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::SetRxConfig(uint8_t window, bool continuous) {
+
+    RxWindow rxw = GetRxWindow(window);
+    GetRadio()->SetChannel(rxw.Frequency);
+
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint32_t bw = rxDr.Bandwidth;
+    uint32_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    uint32_t afc = 0;
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    bool iq = txDr.RxIQ;
+
+    if (P2PEnabled()) {
+        iq = txDr.TxIQ;
+    }
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        cr = 0;
+        bw = 50e3;
+        afc = 83333;
+        iq = false;
+        crc = true;  // FSK must use CRC
+    }
+
+    // Disable printf's to actually receive packets, printing to debug may mess up the timing
+    // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
+    // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
+
+    GetRadio()->SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::AddChannel(int8_t index, Channel channel) {
+    logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
+
+    assert(index < (int) _channels.size());
+
+    if (index >= 0) {
+        _channels[index] = channel;
+    } else {
+        _channels.push_back(channel);
+    }
+
+    return LORA_OK;
+}
+
+Channel ChannelPlan_AU915::GetChannel(int8_t index) {
+    Channel chan;
+    memset(&chan, 0, sizeof(Channel));
+
+    if (_channels.size() > 0) {
+        chan = _channels[index];
+    } else {
+        if (index < 64) {
+            chan.Index = index;
+            chan.DrRange.Fields.Min = _minDatarate;
+            chan.DrRange.Fields.Max = _maxDatarate - 1;
+            chan.Frequency = _freqUBase125k + (_freqUStep125k * index);
+        } else if (index < 72) {
+            chan.Index = index;
+            chan.DrRange.Fields.Min = _maxDatarate;
+            chan.DrRange.Fields.Max = _maxDatarate;
+            chan.Frequency = _freqUBase500k + (_freqUStep500k * (index - 64));
+        }
+    }
+
+    return chan;
+}
+
+uint8_t ChannelPlan_AU915::SetFrequencySubBand(uint8_t sub_band) {
+
+    _txFrequencySubBand = sub_band;
+
+    if (sub_band > 0) {
+        SetChannelMask(0, 0x0000);
+        SetChannelMask(1, 0x0000);
+        SetChannelMask(2, 0x0000);
+        SetChannelMask(3, 0x0000);
+        SetChannelMask(4, 0x0000);
+        SetChannelMask((sub_band - 1) / 2, (sub_band % 2) ? 0x00FF : 0xFF00);
+        SetChannelMask(4, 1 << (sub_band - 1));
+    } else {
+        SetChannelMask(0, 0xFFFF);
+        SetChannelMask(1, 0xFFFF);
+        SetChannelMask(2, 0xFFFF);
+        SetChannelMask(3, 0xFFFF);
+        SetChannelMask(4, 0x00FF);
+    }
+
+    return LORA_OK;
+}
+
+
+void ChannelPlan_AU915::LogRxWindow(uint8_t wnd) {
+
+    RxWindow rxw = GetRxWindow(wnd);
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint8_t bw = rxDr.Bandwidth;
+    uint8_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+    bool iq = GetTxDatarate().RxIQ;
+
+    logTrace("RX%d on freq: %lu", wnd, rxw.Frequency);
+    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);
+}
+
+RxWindow ChannelPlan_AU915::GetRxWindow(uint8_t window) {
+    RxWindow rxw;
+    int index = 0;
+
+    if (P2PEnabled()) {
+        rxw.Frequency = GetSettings()->Network.TxFrequency;
+        index = GetSettings()->Session.TxDatarate;
+    } else {
+        if (window == 1) {
+            if (_txChannel < _numChans125k) {
+                if (GetSettings()->Network.Mode == PUBLIC) {
+                    rxw.Frequency = _freqDBase500k + (_txChannel % 8) * _freqDStep500k;
+                } else {
+                    rxw.Frequency = _freqDBase500k + (_txChannel / 8) * _freqDStep500k;
+                }
+            } else
+                rxw.Frequency = _freqDBase500k + (_txChannel - _numChans125k) * _freqDStep500k;
+
+            if (GetSettings()->Session.TxDatarate <= DR_6) {
+                index = GetSettings()->Session.TxDatarate + 10 - GetSettings()->Session.Rx1DatarateOffset;
+
+                if (index < DR_8)
+                    index = DR_8;
+                if (index > DR_13)
+                    index = DR_13;
+            } else if (GetSettings()->Session.TxDatarate >= DR_8) {
+                index = GetSettings()->Session.TxDatarate - GetSettings()->Session.Rx1DatarateOffset;
+                if (index < DR_8)
+                    index = DR_8;
+            }
+        } else {
+            if (GetSettings()->Network.Mode == PUBLIC) {
+                rxw.Frequency = GetSettings()->Session.Rx2Frequency;
+            } else {
+                if (_txChannel < 64)
+                    rxw.Frequency = _freqDBase500k + (_txChannel / 8) * _freqDStep500k;
+                else
+                    rxw.Frequency = _freqDBase500k + (_txChannel % 8) * _freqDStep500k;
+            }
+            index = GetSettings()->Session.Rx2DatarateIndex;
+        }
+    }
+
+    rxw.DatarateIndex = index;
+
+    return rxw;
+}
+
+uint8_t ChannelPlan_AU915::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    status = 0x07;
+    int8_t datarate = 0;
+    int8_t drOffset = 0;
+    uint32_t freq = 0;
+
+    drOffset = payload[index++];
+    datarate = drOffset & 0x0F;
+    drOffset = (drOffset >> 4) & 0x07;
+
+    freq = payload[index++];
+    freq |= payload[index++] << 8;
+    freq |= payload[index++] << 16;
+    freq *= 100;
+
+    if (!CheckRfFrequency(freq)) {
+        logInfo("Freq KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
+        logInfo("DR KO");
+        status &= 0xFD; // Datarate KO
+    }
+
+    if (drOffset < 0 || drOffset > _maxDatarateOffset) {
+        logInfo("DR Offset KO");
+        status &= 0xFB; // Rx1DrOffset range KO
+    }
+
+    if ((status & 0x07) == 0x07) {
+        logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+        SetRx2DatarateIndex(datarate);
+        SetRx2Frequency(freq);
+        SetRx1Offset(drOffset);
+    } else {
+        logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    // Not Supported in AU915
+    status = 0;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
+    index += 3;
+
+    if (_beaconRxChannel.Frequency != 0) {
+        _beaconRxChannel.DrRange.Value = payload[index];
+    } else {
+        // TODO: set to default beacon rx channel
+    }
+
+    status = 0x03;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    Channel chParam;
+
+    // Skip channel index
+    index++;
+
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        _beaconChannel = chParam;
+    }
+
+    if (_beaconChannel.Frequency == 0) {
+        // TODO: Set to default
+    }
+
+    status = 0x01;
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_AU915::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    uint8_t power = 0;
+    uint8_t datarate = 0;
+    uint16_t mask = 0;
+    uint8_t ctrl = 0;
+    uint8_t nbRep = 0;
+
+    status = 0x07;
+    datarate = payload[index++];
+    power = datarate & 0x0F;
+    datarate = (datarate >> 4) & 0x0F;
+
+    mask = payload[index++];
+    mask |= payload[index++] << 8;
+
+    nbRep = payload[index++];
+    ctrl = (nbRep >> 4) & 0x07;
+    nbRep &= 0x0F;
+
+    if (nbRep == 0) {
+        nbRep = 1;
+    }
+
+    if (datarate > _maxDatarate) {
+        status &= 0xFD; // Datarate KO
+    }
+    //
+    // Remark MaxTxPower = 0 and MinTxPower = 10
+    //
+    if (power < 0 || power > 10) {
+        status &= 0xFB; // TxPower KO
+    }
+
+    switch (ctrl) {
+        case 0:
+        case 1:
+        case 2:
+        case 3:
+        case 4:
+            SetChannelMask(ctrl, mask);
+            break;
+
+        case 6:
+            // enable all 125 kHz channels
+            SetChannelMask(0, 0xFFFF);
+            SetChannelMask(1, 0xFFFF);
+            SetChannelMask(2, 0xFFFF);
+            SetChannelMask(3, 0xFFFF);
+            SetChannelMask(4, mask);
+            break;
+
+        case 7:
+            // disable all 125 kHz channels
+            SetChannelMask(0, 0x0);
+            SetChannelMask(1, 0x0);
+            SetChannelMask(2, 0x0);
+            SetChannelMask(3, 0x0);
+            SetChannelMask(4, mask);
+            break;
+
+        default:
+            logWarning("rejecting RFU or unknown control value %d", ctrl);
+            status &= 0xFE; // ChannelMask KO
+            return LORA_ERROR;
+    }
+
+    if (GetSettings()->Network.ADREnabled) {
+        GetSettings()->Session.TxDatarate = datarate;
+        GetSettings()->Session.TxPower = TX_POWERS[power];
+        GetSettings()->Session.Redundancy = nbRep;
+    } else {
+        logDebug("ADR is disabled, DR and Power not changed.");
+        status &= 0xFB; // TxPower KO
+        status &= 0xFD; // Datarate KO
+    }
+
+    logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_AU915::ValidateAdrConfiguration() {
+    uint8_t status = 0x07;
+    uint8_t chans_enabled = 0;
+    uint8_t datarate = GetSettings()->Session.TxDatarate;
+    uint8_t power = GetSettings()->Session.TxPower;
+
+    if (!GetSettings()->Network.ADREnabled) {
+        logDebug("ADR disabled - no applied changes to validate");
+        return status;
+    }
+
+    if (datarate > _maxDatarate) {
+        logWarning("ADR Datarate KO - outside allowed range");
+        status &= 0xFD; // Datarate KO
+    }
+    if (power < _minTxPower || power > _maxTxPower) {
+        logWarning("ADR TX Power KO - outside allowed range");
+        status &= 0xFB; // TxPower KO
+    }
+    
+    // at least 2 125kHz channels must be enabled
+    chans_enabled += CountBits(_channelMask[0]);
+    chans_enabled += CountBits(_channelMask[1]);
+    chans_enabled += CountBits(_channelMask[2]);
+    chans_enabled += CountBits(_channelMask[3]);
+    // Semtech reference (LoRaMac-node) enforces at least 2 channels
+    if (chans_enabled < 2) {
+        logWarning("ADR Channel Mask KO - at least 2 125kHz channels must be enabled");
+        status &= 0xFE; // ChannelMask KO
+    }
+    
+    // if TXDR == 4 (SF8@500kHz) at least 1 500kHz channel must be enabled
+    if (datarate == DR_4 && (CountBits(_channelMask[4] & 0xFF) == 0)) {
+        logWarning("ADR Datarate KO - DR4 requires at least 1 500kHz channel enabled");
+        status &= 0xFD; // Datarate KO
+    }
+
+    return status;
+}
+
+uint32_t ChannelPlan_AU915::GetTimeOffAir()
+{
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON)
+        return 0;
+
+    uint32_t min = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    if (GetSettings()->Session.AggregatedTimeOffEnd > 0 && GetSettings()->Session.AggregatedTimeOffEnd > now) {
+        min = std::max < uint32_t > (min, GetSettings()->Session.AggregatedTimeOffEnd - now);
+    }
+
+    now = time(NULL);
+    uint32_t join_time = 0;
+
+    if (GetSettings()->Session.JoinFirstAttempt != 0 && now < GetSettings()->Session.JoinTimeOffEnd) {
+        join_time = (GetSettings()->Session.JoinTimeOffEnd - now) * 1000;
+    }
+
+    min = std::max < uint32_t > (join_time, min);
+
+    return min;
+}
+
+std::vector<uint32_t> lora::ChannelPlan_AU915::GetChannels() {
+    std::vector < uint32_t > chans;
+
+    if (GetSettings()->Network.FrequencySubBand > 0) {
+        uint8_t chans_per_group = 8;
+        size_t start = (GetSettings()->Network.FrequencySubBand - 1) * chans_per_group;
+        for (int8_t i = start; i < int8_t(start + chans_per_group); i++) {
+            chans.push_back(GetChannel(i).Frequency);
+        }
+        chans.push_back(GetChannel(_numChans125k + (GetSettings()->Network.FrequencySubBand - 1)).Frequency);
+        chans.push_back(GetRxWindow(2).Frequency);
+    } else {
+        for (int8_t i = 0; i < _numChans; i++) {
+            chans.push_back(GetChannel(i).Frequency);
+        }
+        chans.push_back(GetRxWindow(2).Frequency);
+    }
+
+    return chans;
+}
+
+std::vector<uint8_t> lora::ChannelPlan_AU915::GetChannelRanges() {
+    std::vector < uint8_t > ranges;
+
+    if (GetSettings()->Network.FrequencySubBand > 0) {
+        uint8_t chans_per_group = 8;
+        size_t start = (GetSettings()->Network.FrequencySubBand - 1) * chans_per_group;
+        for (int8_t i = start; i < int8_t(start + chans_per_group); i++) {
+            ranges.push_back(GetChannel(i).DrRange.Value);
+        }
+        ranges.push_back(GetChannel(_numChans125k + (GetSettings()->Network.FrequencySubBand - 1)).DrRange.Value);
+        ranges.push_back(GetRxWindow(2).DatarateIndex);
+    } else {
+        for (int8_t i = 0; i < _numChans; i++) {
+            ranges.push_back(GetChannel(i).DrRange.Value);
+        }
+        ranges.push_back(GetRxWindow(2).DatarateIndex);
+    }
+
+    ranges.push_back(GetRxWindow(2).DatarateIndex);
+
+    return ranges;
+
+}
+
+void lora::ChannelPlan_AU915::EnableDefaultChannels() {
+    SetFrequencySubBand(GetFrequencySubBand());
+}
+
+uint8_t ChannelPlan_AU915::GetNextChannel()
+{
+    if (GetSettings()->Session.AggregatedTimeOffEnd != 0) {
+        return LORA_AGGREGATED_DUTY_CYCLE;
+    }
+
+    if (P2PEnabled() || GetSettings()->Network.TxFrequency != 0) {
+        logDebug("Using frequency %d", GetSettings()->Network.TxFrequency);
+
+        if (GetSettings()->Test.DisableDutyCycle != lora::ON) {
+            int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+            logDebug("band: %d freq: %d", band, GetSettings()->Network.TxFrequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
+                return LORA_NO_CHANS_ENABLED;
+            }
+        }
+
+        GetRadio()->SetChannel(GetSettings()->Network.TxFrequency);
+        return LORA_OK;
+    }
+
+    uint8_t start = 0;
+    uint8_t maxChannels = _numChans125k;
+    uint8_t nbEnabledChannels = 0;
+    uint8_t *enabledChannels = new uint8_t[maxChannels];
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        maxChannels = _numChans500k;
+        start = _numChans125k;
+    }
+
+// Search how many channels are enabled
+    DatarateRange range;
+    uint8_t dr_index = GetSettings()->Session.TxDatarate;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now || GetSettings()->Test.DisableDutyCycle == lora::ON) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+    }
+
+    for (uint8_t i = start; i < start + maxChannels; i++) {
+        range = GetChannel(i).DrRange;
+        // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
+
+        if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
+            int8_t band = GetDutyBand(GetChannel(i).Frequency);
+            // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
+                enabledChannels[nbEnabledChannels++] = i;
+            }
+        }
+    }
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        _dutyBands[0].PowerMax = 26;
+    } else {
+        if (nbEnabledChannels < 50)
+            _dutyBands[0].PowerMax = 21;
+        else
+            _dutyBands[0].PowerMax = 30;
+    }
+
+    logTrace("Number of available channels: %d", nbEnabledChannels);
+
+    uint32_t freq = 0;
+    uint8_t sf = GetTxDatarate().SpreadingFactor;
+    uint8_t bw = GetTxDatarate().Bandwidth;
+    int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
+
+    if (nbEnabledChannels == 0) {
+        delete [] enabledChannels;
+        return LORA_NO_CHANS_ENABLED;
+    }
+
+    if (GetSettings()->Network.CADEnabled) {
+        // Search for free channel with ms timeout
+        int16_t timeout = 10000;
+        Timer tmr;
+        tmr.start();
+
+        for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
+            freq = GetChannel(enabledChannels[j]).Frequency;
+
+            if (GetRadio()->IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
+                _txChannel = enabledChannels[j];
+                break;
+            }
+        }
+    } else {
+        uint8_t j = rand_r(0, nbEnabledChannels - 1);
+        _txChannel = enabledChannels[j];
+        freq = GetChannel(_txChannel).Frequency;
+    }
+
+    assert(freq != 0);
+
+    logDebug("Using channel %d : %d", _txChannel, freq);
+    GetRadio()->SetChannel(freq);
+
+    delete [] enabledChannels;
+    return LORA_OK;
+}
+
+uint8_t lora::ChannelPlan_AU915::GetJoinDatarate() {
+    uint8_t dr = GetSettings()->Session.TxDatarate;
+
+    if (GetSettings()->Test.DisableRandomJoinDatarate == lora::OFF) {
+        static bool altDatarate = false;
+
+        if (GetSettings()->Network.FrequencySubBand == 0) {
+            static uint16_t used_bands_125k = 0;
+            static uint16_t used_bands_500k = 0;
+            uint8_t frequency_sub_band = 0;
+
+            if (altDatarate) {
+                // 500k channel
+                if (CountBits(used_bands_500k) == 8) {
+                    used_bands_500k = 0;
+                }
+                while ((frequency_sub_band = rand_r(1, 8)) && (used_bands_500k & (1 << (frequency_sub_band - 1))) != 0)
+                    ;
+                used_bands_500k |= (1 << (frequency_sub_band - 1));
+            } else {
+                // 125k channel
+                if (CountBits(used_bands_125k) == 8) {
+                    used_bands_125k = 0;
+                }
+                while ((frequency_sub_band = rand_r(1, 8)) && (used_bands_125k & (1 << (frequency_sub_band - 1))) != 0)
+                    ;
+                used_bands_125k |= (1 << (frequency_sub_band - 1));
+            }
+
+            logWarning("JoinDatarate setting frequency sub band to %d 125k: %04x 500k: %04x", frequency_sub_band, used_bands_125k, used_bands_500k);
+            SetFrequencySubBand(frequency_sub_band);
+        }
+
+        if (altDatarate && CountBits(_channelMask[4] > 0)) {
+            dr = lora::DR_4;
+        } else {
+            dr = lora::DR_0;
+        }
+        altDatarate = !altDatarate;
+    }
+
+    return dr;
+}
+
+uint8_t lora::ChannelPlan_AU915::CalculateJoinBackoff(uint8_t size) {
+
+    time_t now = time(NULL);
+    uint32_t time_on_max = 0;
+    static uint32_t time_off_max = 15;
+    uint32_t rand_time_off = 0;
+    static uint16_t join_cnt = 0;
+
+    // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
+
+    if ((time_t)GetSettings()->Session.JoinTimeOffEnd > now) {
+        return LORA_JOIN_BACKOFF;
+    }
+
+    uint32_t secs_since_first_attempt = (now - GetSettings()->Session.JoinFirstAttempt);
+    uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
+
+    join_cnt = (join_cnt+1) % 16;
+
+    if (GetSettings()->Session.JoinFirstAttempt == 0) {
+        /* 1 % duty-cycle for first hour
+         * 0.1 % next 10 hours
+         * 0.01 % upto 24 hours         */
+        GetSettings()->Session.JoinFirstAttempt = now;
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + rand_r(GetSettings()->Network.JoinDelay + 2, GetSettings()->Network.JoinDelay + 3);
+    } else if (join_cnt == 0) {
+        if (hours_since_first_attempt < 1) {
+            time_on_max = 36000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 60 * 60;
+            }
+        } else if (hours_since_first_attempt < 11) {
+            if (GetSettings()->Session.JoinTimeOnAir < 36000) {
+                GetSettings()->Session.JoinTimeOnAir = 36000;
+            }
+            time_on_max = 72000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 11 * 60 * 60;
+            }
+        } else {
+            if (GetSettings()->Session.JoinTimeOnAir < 72000) {
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+            }
+            uint32_t join_time = 2500;
+
+            // 16 join attempts is ~2754 ms, check if this is the third of the 24 hour period
+
+            time_on_max = 80700;
+            time_off_max = 1 * 60 * 60; // 1 hour
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max - join_time) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                // Reset the join time on air and set end of restriction to the next 24 hour period
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+                uint16_t days = (now - GetSettings()->Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
+                logWarning("days : %d", days);
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
+            }
+        }
+
+        logWarning("JoinBackoff: %lu seconds  Time On Air: %lu / %lu", GetSettings()->Session.JoinTimeOffEnd - now, GetSettings()->Session.JoinTimeOnAir, time_on_max);
+    } else {
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + rand_r(GetSettings()->Network.JoinDelay + 2, GetSettings()->Network.JoinDelay + 3);
+    }
+
+    return LORA_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_AU915.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,251 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_AU915_H__
+#define __CHANNEL_PLAN_AU915_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include "ChannelPlan.h"
+#include <vector>
+
+namespace lora {
+
+    class ChannelPlan_AU915 : public lora::ChannelPlan {
+        public:
+
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_AU915();
+            ChannelPlan_AU915(Settings* settings);
+            ChannelPlan_AU915(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_AU915();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel();
+
+            /**
+             * Set the number of channels in the plan
+             */
+            virtual void SetNumberOfChannels(uint8_t channels, bool resize = true);
+
+            /**
+             * Check if channel is enabled
+             * @return true if enabled
+             */
+            virtual bool IsChannelEnabled(uint8_t channel);
+
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index);
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate();
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size);
+
+            /**
+             * Set the datarate offset used for first receive window
+             * @param offset
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx1Offset(uint8_t offset);
+
+            /**
+             * Set the frequency for second receive window
+             * @param freq
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx2Frequency(uint32_t freq);
+
+            /**
+             * Set the datarate index used for second receive window
+             * @param index
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx2DatarateIndex(uint8_t index);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig();
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous);
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t sub_band);
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size);
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration();
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir();
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges();
+
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd);
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels();
+
+            virtual uint8_t GetMinDatarate();
+
+            virtual uint8_t GetMaxDatarate();
+
+        protected:
+
+            static const uint8_t AU915_TX_POWERS[11];                   //!< List of available tx powers
+            static const uint8_t AU915_RADIO_POWERS[21];                //!< List of calibrated tx powers
+            static const uint8_t AU915_MAX_PAYLOAD_SIZE[];              //!< List of max payload sizes for each datarate
+            static const uint8_t AU915_MAX_PAYLOAD_SIZE_REPEATER[];     //!< List of repeater compatible max payload sizes for each datarate
+
+    };
+}
+
+#endif // __CHANNEL_PLAN_AU915_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_EU868.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,1018 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_EU868.h"
+#include "limits.h"
+
+using namespace lora;
+
+// MWF - changed EU868_TX_POWERS to match final 1.0.2 regional spec
+const uint8_t ChannelPlan_EU868::EU868_TX_POWERS[] = { 16, 14, 12, 10, 8, 6, 4, 2 };
+const uint8_t ChannelPlan_EU868::EU868_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 20 };
+const uint8_t ChannelPlan_EU868::EU868_MAX_PAYLOAD_SIZE[] = { 51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0 };
+const uint8_t ChannelPlan_EU868::EU868_MAX_PAYLOAD_SIZE_REPEATER[] = { 51, 51, 51, 115, 222, 222, 222, 222, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ChannelPlan_EU868::ChannelPlan_EU868()
+:
+    ChannelPlan(NULL, NULL)
+{
+
+}
+
+ChannelPlan_EU868::ChannelPlan_EU868(Settings* settings)
+:
+    ChannelPlan(NULL, settings)
+{
+
+}
+
+ChannelPlan_EU868::ChannelPlan_EU868(SxRadio* radio, Settings* settings)
+:
+    ChannelPlan(radio, settings)
+{
+
+}
+
+ChannelPlan_EU868::~ChannelPlan_EU868() {
+
+}
+
+void ChannelPlan_EU868::Init() {
+
+    _datarates.clear();
+    _channels.clear();
+    _dutyBands.clear();
+
+    DutyBand band;
+
+    band.Index = 0;
+    band.DutyCycle = 0;
+
+    Datarate dr;
+
+    _plan = EU868;
+    _planName = "EU868";
+    _maxTxPower = 27;
+    _minTxPower = 0;
+
+    _minFrequency = EU868_FREQ_MIN;
+    _maxFrequency = EU868_FREQ_MAX;
+
+    TX_POWERS = EU868_TX_POWERS;
+    RADIO_POWERS = EU868_RADIO_POWERS;
+    MAX_PAYLOAD_SIZE = EU868_MAX_PAYLOAD_SIZE;
+    MAX_PAYLOAD_SIZE_REPEATER = EU868_MAX_PAYLOAD_SIZE_REPEATER;
+
+    _minDatarate = EU868_MIN_DATARATE;
+    _maxDatarate = EU868_MAX_DATARATE;
+
+    _minRx2Datarate = DR_0;
+    _maxRx2Datarate = DR_7;
+
+    _minDatarateOffset = EU868_MIN_DATARATE_OFFSET;
+    _maxDatarateOffset = EU868_MAX_DATARATE_OFFSET;
+
+    _numChans125k = EU868_125K_NUM_CHANS;
+    _numChans500k = 0;
+
+    GetSettings()->Session.Rx2Frequency = EU868_RX2_FREQ;
+    GetSettings()->Session.Rx2DatarateIndex = DR_0;
+
+    logInfo("Initialize datarates...");
+
+    dr.SpreadingFactor = SF_12;
+
+    // Add DR0-5
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Add DR6
+    dr.SpreadingFactor = SF_7;
+    dr.Bandwidth = BW_250;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    // Add DR7
+    dr.SpreadingFactor = SF_FSK;
+    dr.Bandwidth = BW_FSK;
+    dr.PreambleLength = 10;
+    dr.Coderate = 0;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    _maxDatarate = DR_7;
+
+    // Skip DR8-15 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    while (dr.Index++ < DR_15) {
+        AddDatarate(-1, dr);
+    }
+
+    GetSettings()->Session.TxDatarate = 0;
+
+    logInfo("Initialize channels...");
+
+    Channel chan;
+    chan.DrRange.Fields.Min = DR_0;
+    chan.DrRange.Fields.Max = DR_5;
+    chan.Index = 0;
+    chan.Frequency = EU868_125K_FREQ_BASE;
+    SetNumberOfChannels(EU868_125K_NUM_CHANS);
+
+    for (uint8_t i = 0; i < EU868_DEFAULT_NUM_CHANS; i++) {
+        if (i == 1)
+            chan.DrRange.Fields.Max = DR_6;
+        else
+            chan.DrRange.Fields.Max = DR_5;
+
+        AddChannel(i, chan);
+        chan.Index++;
+        chan.Frequency += EU868_125K_FREQ_STEP;
+    }
+
+    chan.DrRange.Value = 0;
+    chan.Frequency = 0;
+
+    for (uint8_t i = EU868_DEFAULT_NUM_CHANS; i < EU868_125K_NUM_CHANS; i++) {
+        AddChannel(i, chan);
+        chan.Index++;
+    }
+
+    // Add downlink channel defaults
+    chan.Index = 0;
+    _dlChannels.resize(16);
+    for (uint8_t i = 0; i < 16; i++) {
+        AddDownlinkChannel(i, chan);
+        chan.Index++;
+    }
+
+    SetChannelMask(0, 0x07);
+
+    band.Index = 0;
+    band.FrequencyMin = EU868_MILLI_FREQ_MIN;
+    band.FrequencyMax = EU868_MILLI_FREQ_MAX;
+    band.PowerMax = 14;
+    band.TimeOffEnd = 0;
+
+    // Limiting to 865-868 allows for 1% duty cycle
+    band.DutyCycle = 100;
+
+    AddDutyBand(-1, band);
+
+    band.Index++;
+    band.FrequencyMin = EU868_CENTI_FREQ_MIN;
+    band.FrequencyMax = EU868_CENTI_FREQ_MAX;
+    band.DutyCycle = 100;
+
+    AddDutyBand(-1, band);
+
+    band.Index++;
+    band.FrequencyMin = EU868_DECI_FREQ_MIN;
+    band.FrequencyMax = EU868_DECI_FREQ_MAX;
+    band.PowerMax = 27;
+    band.DutyCycle = 10;
+
+    AddDutyBand(-1, band);
+
+    band.Index++;
+    band.FrequencyMin = EU868_VAR_FREQ_MIN;
+    band.FrequencyMax = EU868_VAR_FREQ_MAX;
+    band.DutyCycle = 100;
+
+    AddDutyBand(-1, band);
+
+    band.Index++;
+    band.FrequencyMin = EU868_MILLI_1_FREQ_MIN;
+    band.FrequencyMax = EU868_MILLI_1_FREQ_MAX;
+    band.PowerMax = 14;
+    band.TimeOffEnd = 0;
+    band.DutyCycle = 1000;
+
+    AddDutyBand(-1, band);
+
+    GetSettings()->Session.TxPower = GetSettings()->Network.TxPower;
+}
+
+uint8_t ChannelPlan_EU868::AddChannel(int8_t index, Channel channel) {
+    logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
+
+    assert(index < (int) _channels.size());
+
+    if (index >= 0) {
+        _channels[index] = channel;
+    } else {
+        _channels.push_back(channel);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_EU868::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
+
+    if (size == 33) {
+        Channel ch;
+        int index = 3;
+        for (int i = 13; i < size - 5; i += 3) {
+
+            ch.Frequency = ((buffer[i]) | (buffer[i + 1] << 8) | (buffer[i + 2] << 16)) * 100u;
+
+            if (ch.Frequency > 0) {
+                ch.Index = index;
+                ch.DrRange.Fields.Min = static_cast<int8_t>(DR_0);
+                ch.DrRange.Fields.Max = static_cast<int8_t>(DR_5);
+                AddChannel(index, ch);
+
+                if (GetDutyBand(ch.Frequency) > -1)
+                    _channelMask[0] |= (1 << index);
+                else
+                    _channelMask[0] |= ~(1 << index);
+
+                index += 1;
+            }
+        }
+    }
+
+    return LORA_OK;
+}
+
+uint32_t ChannelPlan_EU868::GetTimeOnAir(uint8_t bytes) {
+    SetTxConfig();
+    if (GetSettings()->Session.TxDatarate == lora::DR_7) {
+        return GetRadio()->TimeOnAir(SxRadio::MODEM_FSK, bytes) / 1000u;
+    }
+    return GetRadio()->TimeOnAir(SxRadio::MODEM_LORA, bytes) / 1000u;
+}
+
+uint8_t ChannelPlan_EU868::SetTxConfig() {
+
+    logInfo("Configure radio for TX");
+
+    uint8_t band = GetDutyBand(GetChannel(_txChannel).Frequency);
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    int8_t max_pwr = _dutyBands[band].PowerMax;
+
+    int8_t pwr = 0;
+
+    pwr = std::min < int8_t > (GetSettings()->Session.TxPower, (max_pwr - GetSettings()->Network.AntennaGain));
+
+    for (int i = 20; i >= 0; i--) {
+        if (RADIO_POWERS[i] <= pwr) {
+            pwr = i;
+            break;
+        }
+        if (i == 0) {
+            pwr = i;
+        }
+    }
+
+    logDebug("Session pwr: %d ant: %d max: %d", GetSettings()->Session.TxPower, GetSettings()->Network.AntennaGain, max_pwr);
+    logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + GetSettings()->Network.AntennaGain);
+
+    uint32_t bw = txDr.Bandwidth;
+    uint32_t sf = txDr.SpreadingFactor;
+    uint8_t cr = txDr.Coderate;
+    uint8_t pl = txDr.PreambleLength;
+    uint16_t fdev = 0;
+    bool crc = txDr.Crc;
+    bool iq = txDr.TxIQ;
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        fdev = 25e3;
+        bw = 0;
+    }
+
+    GetRadio()->SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
+
+    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);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_EU868::SetRxConfig(uint8_t window, bool continuous) {
+
+    RxWindow rxw = GetRxWindow(window);
+    GetRadio()->SetChannel(rxw.Frequency);
+
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint32_t bw = rxDr.Bandwidth;
+    uint32_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    uint32_t afc = 0;
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    bool iq = txDr.RxIQ;
+
+    if (P2PEnabled()) {
+        iq = txDr.TxIQ;
+    }
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        cr = 0;
+        bw = 0;
+        afc = 83333;
+        iq = false;
+        crc = true;  // FSK must use CRC
+    }
+
+    // Disable printf's to actually receive packets, printing to debug may mess up the timing
+    // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
+    // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
+
+    GetRadio()->SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
+
+    return LORA_OK;
+}
+
+Channel ChannelPlan_EU868::GetChannel(int8_t index) {
+    Channel chan;
+    memset(&chan, 0, sizeof(Channel));
+
+    chan = _channels[index];
+
+    return chan;
+}
+
+uint8_t ChannelPlan_EU868::SetFrequencySubBand(uint8_t sub_band) {
+    return LORA_OK;
+}
+
+void ChannelPlan_EU868::LogRxWindow(uint8_t wnd) {
+
+    RxWindow rxw = GetRxWindow(wnd);
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint8_t bw = rxDr.Bandwidth;
+    uint8_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+    bool iq = GetTxDatarate().RxIQ;
+
+    logTrace("RX%d on freq: %lu", wnd, rxw.Frequency);
+    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);
+}
+
+RxWindow ChannelPlan_EU868::GetRxWindow(uint8_t window) {
+    RxWindow rxw;
+    int index = 0;
+
+    if (P2PEnabled()) {
+        rxw.Frequency = GetSettings()->Network.TxFrequency;
+        index = GetSettings()->Session.TxDatarate;
+    } else {
+        if (window == 1) {
+            rxw.Frequency = _channels[_txChannel].Frequency;
+
+            if (GetSettings()->Session.TxDatarate > GetSettings()->Session.Rx1DatarateOffset) {
+                index = GetSettings()->Session.TxDatarate - GetSettings()->Session.Rx1DatarateOffset;
+            } else {
+                index = 0;
+            }
+        } else {
+            rxw.Frequency = GetSettings()->Session.Rx2Frequency;
+            index = GetSettings()->Session.Rx2DatarateIndex;
+        }
+    }
+
+    rxw.DatarateIndex = index;
+
+    return rxw;
+}
+
+uint8_t ChannelPlan_EU868::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    status = 0x07;
+    int8_t datarate = 0;
+    int8_t drOffset = 0;
+    uint32_t freq = 0;
+
+    drOffset = payload[index++];
+    datarate = drOffset & 0x0F;
+    drOffset = (drOffset >> 4) & 0x07;
+
+    freq = payload[index++];
+    freq |= payload[index++] << 8;
+    freq |= payload[index++] << 16;
+    freq *= 100;
+
+    if (!CheckRfFrequency(freq)) {
+        logInfo("Freq KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
+        logInfo("DR KO");
+        status &= 0xFD; // Datarate KO
+    }
+
+    if (drOffset < 0 || drOffset > _maxDatarateOffset) {
+        logInfo("DR Offset KO");
+        status &= 0xFB; // Rx1DrOffset range KO
+    }
+
+    if ((status & 0x07) == 0x07) {
+        logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+        SetRx2DatarateIndex(datarate);
+        SetRx2Frequency(freq);
+        SetRx1Offset(drOffset);
+    } else {
+        logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_EU868::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    uint8_t channelIndex = 0;
+    Channel chParam;
+
+    channelIndex = payload[index++];
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (channelIndex < 3 || channelIndex > _channels.size() - 1) {
+        logError("New Channel index KO");
+        status &= 0xFE; // Channel index KO
+    }
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        logError("New Channel frequency KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min > chParam.DrRange.Fields.Max) {
+        logError("New Channel datarate min/max KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        logError("New Channel datarate min KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        logError("New Channel datarate max KO");
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        logInfo("New Channel accepted index: %d freq: %lu drRange: %02x", channelIndex, chParam.Frequency, chParam.DrRange.Value);
+        AddChannel(channelIndex, chParam);
+        SetChannelMask(0, _channelMask[0] | 1 << (channelIndex));
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_EU868::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
+    index += 3;
+
+    if (_beaconRxChannel.Frequency != 0) {
+        _beaconRxChannel.DrRange.Value = payload[index];
+    } else {
+        // TODO: set to default beacon rx channel
+    }
+
+    status = 0x03;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_EU868::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    Channel chParam;
+
+    // Skip channel index
+    index++;
+
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        _beaconChannel = chParam;
+    }
+
+    if (_beaconChannel.Frequency == 0) {
+        // TODO: Set to default
+    }
+
+    status = 0x01;
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_EU868::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    uint8_t power = 0;
+    uint8_t datarate = 0;
+    uint16_t mask = 0;
+    uint16_t new_mask = 0;
+    uint8_t ctrl = 0;
+    uint8_t nbRep = 0;
+
+    status = 0x07;
+    datarate = payload[index++];
+    power = datarate & 0x0F;
+    datarate = (datarate >> 4) & 0x0F;
+
+    mask = payload[index++];
+    mask |= payload[index++] << 8;
+
+    nbRep = payload[index++];
+    ctrl = (nbRep >> 4) & 0x07;
+    nbRep &= 0x0F;
+
+    if (nbRep == 0) {
+        nbRep = 1;
+    }
+
+    if (datarate > _maxDatarate) {
+        status &= 0xFD; // Datarate KO
+    }
+    //
+    // Remark MaxTxPower = 0 and MinTxPower = 7
+    //
+    if (power < 0 || power > 7) {
+        status &= 0xFB; // TxPower KO
+    }
+
+    switch (ctrl) {
+        case 0:
+            SetChannelMask(0, mask);
+            break;
+
+        case 6:
+            // enable all currently defined channels
+            // set bits 0 - N of a number by (2<<N)-1
+            new_mask = (1 << _channels.size()) - 1;
+            SetChannelMask(0, new_mask);
+            break;
+
+        default:
+            logWarning("rejecting RFU or unknown control value %d", ctrl);
+            status &= 0xFE; // ChannelMask KO
+            return LORA_ERROR;
+    }
+
+    if (GetSettings()->Network.ADREnabled) {
+        GetSettings()->Session.TxDatarate = datarate;
+        GetSettings()->Session.TxPower = TX_POWERS[power];
+        GetSettings()->Session.Redundancy = nbRep;
+    } else {
+        logDebug("ADR is disabled, DR and Power not changed.");
+        status &= 0xFB; // TxPower KO
+        status &= 0xFD; // Datarate KO
+    }
+
+    logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_EU868::ValidateAdrConfiguration() {
+    uint8_t status = 0x07;
+    uint8_t datarate = GetSettings()->Session.TxDatarate;
+    uint8_t power = GetSettings()->Session.TxPower;
+
+    if (!GetSettings()->Network.ADREnabled) {
+        logDebug("ADR disabled - no applied changes to validate");
+        return status;
+    }
+
+    if (datarate > _maxDatarate) {
+        logWarning("ADR Datarate KO - outside allowed range");
+        status &= 0xFD; // Datarate KO
+    }
+    if (power < _minTxPower || power > _maxTxPower) {
+        logWarning("ADR TX Power KO - outside allowed range");
+        status &= 0xFB; // TxPower KO
+    }
+
+    // default channels must be enabled
+    if ((_channelMask[0] & 0x0007) != 0x0007) {
+        logWarning("ADR Channel Mask KO - default channels must be enabled");
+        status &= 0xFE; // ChannelMask KO
+    }
+
+    // mask must not contain any undefined channels
+    for (int i = 3; i < 16; i++) {
+        if ((_channelMask[0] & (1 << i)) && (_channels[i].Frequency == 0)) {
+            logWarning("ADR Channel Mask KO - cannot enable undefined channel");
+            status &= 0xFE; // ChannelMask KO
+            break;
+        }
+    }
+
+    return status;
+}
+
+uint8_t ChannelPlan_EU868::HandleAckTimeout() {
+
+    if (!GetSettings()->Network.ADREnabled) {
+        return LORA_ADR_OFF;
+    }
+
+    if ((++(GetSettings()->Session.AckCounter) % 2) == 0) {
+        if (GetSettings()->Session.TxPower < GetSettings()->Network.TxPowerMax) {
+            logTrace("ADR Setting power to maximum");
+            GetSettings()->Session.TxPower = GetSettings()->Network.TxPowerMax;
+        } else if (GetSettings()->Session.TxDatarate > 0) {
+            logTrace("ADR Lowering datarate");
+            (GetSettings()->Session.TxDatarate)--;
+        }
+    }
+
+    return LORA_OK;
+}
+
+
+uint32_t ChannelPlan_EU868::GetTimeOffAir()
+{
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON)
+        return 0;
+
+    uint32_t min = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+
+    min = UINT_MAX;
+    int8_t band = 0;
+
+    if (P2PEnabled()) {
+        int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+        if (_dutyBands[band].TimeOffEnd > now) {
+            min = _dutyBands[band].TimeOffEnd - now;
+        } else {
+            min = 0;
+        }
+    } else {
+        for (size_t i = 0; i < _channels.size(); i++) {
+            if (IsChannelEnabled(i) && GetChannel(i).Frequency != 0 &&
+                !(GetSettings()->Session.TxDatarate < GetChannel(i).DrRange.Fields.Min ||
+                  GetSettings()->Session.TxDatarate > GetChannel(i).DrRange.Fields.Max)) {
+
+                band = GetDutyBand(GetChannel(i).Frequency);
+                if (band != -1) {
+                    // logDebug("band: %d time-off: %d now: %d", band, _dutyBands[band].TimeOffEnd, now);
+                    if (_dutyBands[band].TimeOffEnd > now) {
+                        min = std::min < uint32_t > (min, _dutyBands[band].TimeOffEnd - now);
+                    } else {
+                        min = 0;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+
+    if (GetSettings()->Session.AggregatedTimeOffEnd > 0 && GetSettings()->Session.AggregatedTimeOffEnd > now) {
+        min = std::max < uint32_t > (min, GetSettings()->Session.AggregatedTimeOffEnd - now);
+    }
+
+    now = time(NULL);
+    uint32_t join_time = 0;
+
+    if (GetSettings()->Session.JoinFirstAttempt != 0 && now < GetSettings()->Session.JoinTimeOffEnd) {
+        join_time = (GetSettings()->Session.JoinTimeOffEnd - now) * 1000;
+    }
+
+    min = std::max < uint32_t > (join_time, min);
+
+    return min;
+}
+
+
+void ChannelPlan_EU868::UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms) {
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON) {
+        _dutyCycleTimer.stop();
+        for (size_t i = 0; i < _dutyBands.size(); i++) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+        return;
+    }
+
+    _dutyCycleTimer.start();
+
+    if (GetSettings()->Session.MaxDutyCycle > 0 && GetSettings()->Session.MaxDutyCycle <= 15) {
+        GetSettings()->Session.AggregatedTimeOffEnd = _dutyCycleTimer.read_ms() + time_on_air_ms * GetSettings()->Session.AggregateDutyCycle;
+        logDebug("Updated Aggregate DCycle Time-off: %lu DC: %f%%", GetSettings()->Session.AggregatedTimeOffEnd, 1 / float(GetSettings()->Session.AggregateDutyCycle));
+    } else {
+        GetSettings()->Session.AggregatedTimeOffEnd = 0;
+    }
+
+
+    uint32_t time_off_air = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now) {
+            _dutyBands[i].TimeOffEnd = 0;
+        } else {
+            _dutyBands[i].TimeOffEnd -= now;
+        }
+
+        if (freq >= _dutyBands[i].FrequencyMin && freq <= _dutyBands[i].FrequencyMax) {
+            logDebug("update TOE: freq: %d i:%d toa: %d DC:%d", freq, i, time_on_air_ms, _dutyBands[i].DutyCycle);
+
+            if (freq > EU868_VAR_FREQ_MIN && freq < EU868_VAR_FREQ_MAX && (GetSettings()->Session.TxPower + GetSettings()->Network.AntennaGain) <= 7) {
+                _dutyBands[i].TimeOffEnd = 0;
+            } else {
+                time_off_air = time_on_air_ms * _dutyBands[i].DutyCycle;
+                _dutyBands[i].TimeOffEnd = time_off_air;
+            }
+        }
+    }
+
+
+    ResetDutyCycleTimer();
+}
+
+std::vector<uint32_t> lora::ChannelPlan_EU868::GetChannels() {
+    std::vector < uint32_t > chans;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        chans.push_back(_channels[i].Frequency);
+    }
+    chans.push_back(GetRxWindow(2).Frequency);
+
+    return chans;
+}
+
+std::vector<uint8_t> lora::ChannelPlan_EU868::GetChannelRanges() {
+    std::vector < uint8_t > ranges;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        ranges.push_back(_channels[i].DrRange.Value);
+    }
+
+    ranges.push_back(GetRxWindow(2).DatarateIndex);
+
+    return ranges;
+
+}
+
+void lora::ChannelPlan_EU868::EnableDefaultChannels() {
+    _channelMask[0] |= 0x0007;
+}
+
+uint8_t ChannelPlan_EU868::GetNextChannel()
+{
+    if (GetSettings()->Session.AggregatedTimeOffEnd != 0) {
+        return LORA_AGGREGATED_DUTY_CYCLE;
+    }
+
+    if (P2PEnabled() || GetSettings()->Network.TxFrequency != 0) {
+        logDebug("Using frequency %d", GetSettings()->Network.TxFrequency);
+
+        if (GetSettings()->Test.DisableDutyCycle != lora::ON) {
+            int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+            logDebug("band: %d freq: %d", band, GetSettings()->Network.TxFrequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
+                return LORA_NO_CHANS_ENABLED;
+            }
+        }
+
+        GetRadio()->SetChannel(GetSettings()->Network.TxFrequency);
+        return LORA_OK;
+    }
+
+    uint8_t start = 0;
+    uint8_t maxChannels = _numChans125k;
+    uint8_t nbEnabledChannels = 0;
+    uint8_t *enabledChannels = new uint8_t[maxChannels];
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        maxChannels = _numChans500k;
+        start = _numChans125k;
+    }
+
+// Search how many channels are enabled
+    DatarateRange range;
+    uint8_t dr_index = GetSettings()->Session.TxDatarate;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now || GetSettings()->Test.DisableDutyCycle == lora::ON) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+    }
+
+    for (uint8_t i = start; i < start + maxChannels; i++) {
+        range = GetChannel(i).DrRange;
+        // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
+
+        if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
+            int8_t band = GetDutyBand(GetChannel(i).Frequency);
+            // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
+                enabledChannels[nbEnabledChannels++] = i;
+            }
+        }
+    }
+
+    logTrace("Number of available channels: %d", nbEnabledChannels);
+
+    uint32_t freq = 0;
+    uint8_t sf = GetTxDatarate().SpreadingFactor;
+    uint8_t bw = GetTxDatarate().Bandwidth;
+    int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
+
+    if (nbEnabledChannels == 0) {
+        delete [] enabledChannels;
+        return LORA_NO_CHANS_ENABLED;
+    }
+
+
+    if (GetSettings()->Network.CADEnabled) {
+        // Search for free channel with ms timeout
+        int16_t timeout = 10000;
+        Timer tmr;
+        tmr.start();
+
+        for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
+            freq = GetChannel(enabledChannels[j]).Frequency;
+
+            if (GetRadio()->IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
+                _txChannel = enabledChannels[j];
+                break;
+            }
+        }
+    } else {
+        uint8_t j = rand_r(0, nbEnabledChannels - 1);
+        _txChannel = enabledChannels[j];
+        freq = GetChannel(_txChannel).Frequency;
+    }
+
+    assert(freq != 0);
+
+    logDebug("Using channel %d : %d", _txChannel, freq);
+    GetRadio()->SetChannel(freq);
+
+
+    delete [] enabledChannels;
+    return LORA_OK;
+}
+
+
+uint8_t lora::ChannelPlan_EU868::GetJoinDatarate() {
+    uint8_t dr = GetSettings()->Session.TxDatarate;
+    static uint8_t cnt = 0;
+
+    if (GetSettings()->Test.DisableRandomJoinDatarate == lora::OFF) {
+        if ((cnt++ % 20) == 0) {
+            dr = lora::DR_0;
+        } else if ((cnt % 16) == 0) {
+            dr = lora::DR_1;
+        } else if ((cnt % 12) == 0) {
+            dr = lora::DR_2;
+        } else if ((cnt % 8) == 0) {
+            dr = lora::DR_3;
+        } else if ((cnt % 4) == 0) {
+            dr = lora::DR_4;
+        } else {
+            dr = lora::DR_5;
+        }
+    }
+
+    return dr;
+}
+
+uint8_t ChannelPlan_EU868::CalculateJoinBackoff(uint8_t size) {
+
+    time_t now = time(NULL);
+    uint32_t time_on_max = 0;
+    static uint32_t time_off_max = 15;
+    uint32_t rand_time_off = 0;
+
+    // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
+
+    if ((time_t)GetSettings()->Session.JoinTimeOffEnd > now) {
+        return LORA_JOIN_BACKOFF;
+    }
+
+    uint32_t secs_since_first_attempt = (now - GetSettings()->Session.JoinFirstAttempt);
+    uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
+
+    static uint8_t join_cnt = 0;
+
+    join_cnt = (join_cnt+1) % 8;
+
+    if (GetSettings()->Session.JoinFirstAttempt == 0) {
+        /* 1 % duty-cycle for first hour
+         * 0.1 % next 10 hours
+         * 0.01 % upto 24 hours         */
+        GetSettings()->Session.JoinFirstAttempt = now;
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    } else if (join_cnt == 0) {
+        if (hours_since_first_attempt < 1) {
+            time_on_max = 36000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 60 * 60;
+            }
+        } else if (hours_since_first_attempt < 11) {
+            if (GetSettings()->Session.JoinTimeOnAir < 36000) {
+                GetSettings()->Session.JoinTimeOnAir = 36000;
+            }
+            time_on_max = 72000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 11 * 60 * 60;
+            }
+        } else {
+            if (GetSettings()->Session.JoinTimeOnAir < 72000) {
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+            }
+            uint32_t join_time = 1200;
+
+            time_on_max = 80700;
+            time_off_max = 1 * 60 * 60; // 1 hour
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+
+            // allow one final join attempt as long as it doesn't start past the max time on air
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max - join_time) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                // Reset the join time on air and set end of restriction to the next 24 hour period
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+                uint16_t days = (now - GetSettings()->Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
+                logWarning("days : %d", days);
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
+            }
+        }
+
+        logWarning("JoinBackoff: %lu seconds  Time On Air: %lu / %lu", GetSettings()->Session.JoinTimeOffEnd - now, GetSettings()->Session.JoinTimeOnAir, time_on_max);
+    } else {
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    }
+
+    return LORA_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_EU868.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,230 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_EU868_H__
+#define __CHANNEL_PLAN_EU868_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include <vector>
+#include "ChannelPlan.h"
+
+namespace lora {
+
+    class ChannelPlan_EU868 : public lora::ChannelPlan {
+        public:
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_EU868();
+            ChannelPlan_EU868(Settings* settings);
+            ChannelPlan_EU868(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_EU868();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel();
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index);
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate();
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig();
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous);
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t sub_band);
+
+            /**
+             * Get time on air with current settings
+             * @param bytes number of bytes to be sent
+             */
+            virtual uint32_t GetTimeOnAir(uint8_t bytes);
+
+            /**
+             * Callback for ACK timeout event
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAckTimeout();
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size);
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration();
+
+            /**
+             * Update duty cycle with at given frequency and time on air
+             * @param freq frequency
+             * @param time_on_air_ms tx time on air
+             */
+            virtual void UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms);
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir();
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges();
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd);
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels();
+
+        protected:
+
+            static const uint8_t EU868_TX_POWERS[8];                    //!< List of available tx powers
+            static const uint8_t EU868_RADIO_POWERS[21];                 //!< List of calibrated tx powers
+            static const uint8_t EU868_MAX_PAYLOAD_SIZE[];              //!< List of max payload sizes for each datarate
+            static const uint8_t EU868_MAX_PAYLOAD_SIZE_REPEATER[];     //!< List of repeater compatible max payload sizes for each datarate
+    };
+}
+
+#endif //__CHANNEL_PLAN_EU868_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_IN865.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,971 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_IN865.h"
+#include "limits.h"
+
+using namespace lora;
+
+const uint8_t ChannelPlan_IN865::IN865_TX_POWERS[] = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 };
+const uint8_t ChannelPlan_IN865::IN865_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 20 };
+const uint8_t ChannelPlan_IN865::IN865_MAX_PAYLOAD_SIZE[] = { 51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0 };
+const uint8_t ChannelPlan_IN865::IN865_MAX_PAYLOAD_SIZE_REPEATER[] = { 51, 51, 51, 115, 222, 222, 222, 222, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ChannelPlan_IN865::ChannelPlan_IN865()
+:
+    ChannelPlan(NULL, NULL)
+{
+
+}
+
+ChannelPlan_IN865::ChannelPlan_IN865(Settings* settings)
+:
+    ChannelPlan(NULL, settings)
+{
+
+}
+
+ChannelPlan_IN865::ChannelPlan_IN865(SxRadio* radio, Settings* settings)
+:
+    ChannelPlan(radio, settings)
+{
+
+}
+
+ChannelPlan_IN865::~ChannelPlan_IN865() {
+
+}
+
+void ChannelPlan_IN865::Init() {
+
+    _datarates.clear();
+    _channels.clear();
+    _dutyBands.clear();
+
+    DutyBand band;
+
+    band.Index = 0;
+    band.DutyCycle = 0;
+
+    Datarate dr;
+
+    _plan = IN865;
+    _planName = "IN865";
+    _maxTxPower = 30;
+    _minTxPower = 0;
+
+    _minFrequency = 865000000;
+    _maxFrequency = 867000000;
+
+    TX_POWERS = IN865_TX_POWERS;
+    RADIO_POWERS = IN865_RADIO_POWERS;
+    MAX_PAYLOAD_SIZE = IN865_MAX_PAYLOAD_SIZE;
+    MAX_PAYLOAD_SIZE_REPEATER = IN865_MAX_PAYLOAD_SIZE_REPEATER;
+
+    _minDatarate = 0;
+    _maxDatarate = 7;
+
+    _minRx2Datarate = DR_0;
+    _maxRx2Datarate = DR_7;
+
+    _minDatarateOffset = 0;
+    _maxDatarateOffset = 7;
+
+    _numChans125k = 16;
+    _numChans500k = 0;
+
+    GetSettings()->Session.Rx2Frequency = 865550000;
+    GetSettings()->Session.Rx2DatarateIndex = DR_2;
+
+    logInfo("Initialize datarates...");
+
+    dr.SpreadingFactor = SF_12;
+
+    // Add DR0-5
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Add DR6
+    dr.SpreadingFactor = SF_7;
+    dr.Bandwidth = BW_250;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    // Add DR7
+    dr.SpreadingFactor = SF_FSK;
+    dr.Bandwidth = BW_FSK;
+    dr.PreambleLength = 10;
+    dr.Coderate = 0;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    _maxDatarate = DR_7;
+
+    // Skip DR8-15 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    while (dr.Index++ < DR_15) {
+        AddDatarate(-1, dr);
+    }
+
+    GetSettings()->Session.TxDatarate = 0;
+
+    logInfo("Initialize channels...");
+
+    Channel chan;
+    chan.DrRange.Fields.Min = DR_0;
+    chan.DrRange.Fields.Max = DR_5;
+    chan.Index = 0;
+    chan.Frequency = 865062500;
+    SetNumberOfChannels(16);
+
+    uint8_t numDefaultChannels = 3;
+
+    AddChannel(0, chan);
+
+    chan.Index++;
+    chan.Frequency = 865402500;
+    AddChannel(1, chan);
+
+    chan.Index++;
+    chan.Frequency = 865985000;
+    AddChannel(2, chan);
+
+
+    chan.DrRange.Value = 0;
+    chan.Frequency = 0;
+
+    for (uint8_t i = numDefaultChannels; i < 16; i++) {
+        AddChannel(i, chan);
+        chan.Index++;
+    }
+
+    // Add downlink channel defaults
+    chan.Index = 0;
+    _dlChannels.resize(16);
+    for (uint8_t i = 0; i < 16; i++) {
+        AddDownlinkChannel(i, chan);
+        chan.Index++;
+    }
+
+    SetChannelMask(0, 0x07);
+
+    band.Index = 0;
+    band.FrequencyMin = _minFrequency;
+    band.FrequencyMax = _maxFrequency;
+    band.PowerMax = 30;
+    band.TimeOffEnd = 0;
+
+    // Disable duty-cycle limits
+    band.DutyCycle = 0;
+
+    AddDutyBand(-1, band);
+
+    GetSettings()->Session.TxPower = GetSettings()->Network.TxPower;
+}
+
+uint8_t ChannelPlan_IN865::AddChannel(int8_t index, Channel channel) {
+    logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
+
+    assert(index < (int) _channels.size());
+
+    if (index >= 0) {
+        _channels[index] = channel;
+    } else {
+        _channels.push_back(channel);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
+
+    if (size == 33) {
+        Channel ch;
+        int index = 3;
+        for (int i = 13; i < size - 5; i += 3) {
+
+            ch.Frequency = ((buffer[i]) | (buffer[i + 1] << 8) | (buffer[i + 2] << 16)) * 100u;
+
+            if (ch.Frequency > 0) {
+                ch.Index = index;
+                ch.DrRange.Fields.Min = static_cast<int8_t>(DR_0);
+                ch.DrRange.Fields.Max = static_cast<int8_t>(DR_5);
+                AddChannel(index, ch);
+
+                if (GetDutyBand(ch.Frequency) > -1)
+                    _channelMask[0] |= (1 << index);
+                else
+                    _channelMask[0] |= ~(1 << index);
+
+                index += 1;
+            }
+        }
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::SetTxConfig() {
+
+    logInfo("Configure radio for TX");
+
+    uint8_t band = GetDutyBand(GetChannel(_txChannel).Frequency);
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    int8_t max_pwr = _dutyBands[band].PowerMax;
+
+    int8_t pwr = 0;
+
+    pwr = std::min < int8_t > (GetSettings()->Session.TxPower, (max_pwr - GetSettings()->Network.AntennaGain));
+
+    for (int i = 20; i >= 0; i--) {
+        if (RADIO_POWERS[i] <= pwr) {
+            pwr = i;
+            break;
+        }
+        if (i == 0) {
+            pwr = i;
+        }
+    }
+
+    logDebug("Session pwr: %d ant: %d max: %d", GetSettings()->Session.TxPower, GetSettings()->Network.AntennaGain, max_pwr);
+    logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + GetSettings()->Network.AntennaGain);
+
+    uint32_t bw = txDr.Bandwidth;
+    uint32_t sf = txDr.SpreadingFactor;
+    uint8_t cr = txDr.Coderate;
+    uint8_t pl = txDr.PreambleLength;
+    uint16_t fdev = 0;
+    bool crc = txDr.Crc;
+    bool iq = txDr.TxIQ;
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        fdev = 25e3;
+        bw = 0;
+    }
+
+    GetRadio()->SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
+
+    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);
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_IN865::SetRxConfig(uint8_t window, bool continuous) {
+
+    RxWindow rxw = GetRxWindow(window);
+    GetRadio()->SetChannel(rxw.Frequency);
+
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint32_t bw = rxDr.Bandwidth;
+    uint32_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    uint32_t afc = 0;
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    bool iq = txDr.RxIQ;
+
+    if (P2PEnabled()) {
+        iq = txDr.TxIQ;
+    }
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        cr = 0;
+        bw = 50e3;
+        afc = 83333;
+        iq = false;
+        crc = true;  // FSK must use CRC
+    }
+
+    // Disable printf's to actually receive packets, printing to debug may mess up the timing
+    // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
+    // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
+
+    GetRadio()->SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
+
+    return LORA_OK;
+}
+
+Channel ChannelPlan_IN865::GetChannel(int8_t index) {
+    Channel chan;
+    memset(&chan, 0, sizeof(Channel));
+
+    chan = _channels[index];
+
+    return chan;
+}
+
+uint8_t ChannelPlan_IN865::SetFrequencySubBand(uint8_t sub_band) {
+    return LORA_OK;
+}
+
+void ChannelPlan_IN865::LogRxWindow(uint8_t wnd) {
+
+    RxWindow rxw = GetRxWindow(wnd);
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint8_t bw = rxDr.Bandwidth;
+    uint8_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+    bool iq = GetTxDatarate().RxIQ;
+
+    logTrace("RX%d on freq: %lu", wnd, rxw.Frequency);
+    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);
+}
+
+RxWindow ChannelPlan_IN865::GetRxWindow(uint8_t window) {
+    RxWindow rxw;
+    int index = 0;
+
+    if (P2PEnabled()) {
+        rxw.Frequency = GetSettings()->Network.TxFrequency;
+        index = GetSettings()->Session.TxDatarate;
+    } else {
+        if (window == 1) {
+            // Use same frequency as TX
+            rxw.Frequency = _channels[_txChannel].Frequency;
+
+            if (GetSettings()->Session.TxDatarate > GetSettings()->Session.Rx1DatarateOffset) {
+                index = GetSettings()->Session.TxDatarate - GetSettings()->Session.Rx1DatarateOffset;
+            } else {
+                index = 0;
+            }
+
+        } else {
+            // Use session RX2 frequency
+            rxw.Frequency = GetSettings()->Session.Rx2Frequency;
+            index = GetSettings()->Session.Rx2DatarateIndex;
+        }
+    }
+
+    rxw.DatarateIndex = index;
+
+    return rxw;
+}
+
+uint8_t ChannelPlan_IN865::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    status = 0x07;
+    int8_t datarate = 0;
+    int8_t drOffset = 0;
+    uint32_t freq = 0;
+
+    drOffset = payload[index++];
+    datarate = drOffset & 0x0F;
+    drOffset = (drOffset >> 4) & 0x07;
+
+    freq = payload[index++];
+    freq |= payload[index++] << 8;
+    freq |= payload[index++] << 16;
+    freq *= 100;
+
+    if (!CheckRfFrequency(freq)) {
+        logInfo("Freq KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
+        logInfo("DR KO");
+        status &= 0xFD; // Datarate KO
+    }
+
+    if (drOffset < 0 || drOffset > _maxDatarateOffset) {
+        logInfo("DR Offset KO");
+        status &= 0xFB; // Rx1DrOffset range KO
+    }
+
+    if ((status & 0x07) == 0x07) {
+        logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+        SetRx2DatarateIndex(datarate);
+        SetRx2Frequency(freq);
+        SetRx1Offset(drOffset);
+    } else {
+        logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    uint8_t channelIndex = 0;
+    Channel chParam;
+
+    channelIndex = payload[index++];
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (channelIndex < 3 || channelIndex > _channels.size() - 1) {
+        logError("New Channel index KO");
+        status &= 0xFE; // Channel index KO
+    }
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        logError("New Channel frequency KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min > chParam.DrRange.Fields.Max) {
+        logError("New Channel datarate min/max KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        logError("New Channel datarate min KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        logError("New Channel datarate max KO");
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        logInfo("New Channel accepted index: %d freq: %lu drRange: %02x", channelIndex, chParam.Frequency, chParam.DrRange.Value);
+        AddChannel(channelIndex, chParam);
+        SetChannelMask(0, _channelMask[0] | 1 << (channelIndex));
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
+    index += 3;
+
+    if (_beaconRxChannel.Frequency != 0) {
+        _beaconRxChannel.DrRange.Value = payload[index];
+    } else {
+        // TODO: set to default beacon rx channel
+    }
+
+    status = 0x03;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    Channel chParam;
+
+    // Skip channel index
+    index++;
+
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        _beaconChannel = chParam;
+    }
+
+    if (_beaconChannel.Frequency == 0) {
+        // TODO: Set to default
+    }
+
+    status = 0x01;
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_IN865::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    uint8_t power = 0;
+    uint8_t datarate = 0;
+    uint16_t mask = 0;
+    uint16_t new_mask = 0;
+    uint8_t ctrl = 0;
+    uint8_t nbRep = 0;
+
+    status = 0x07;
+    datarate = payload[index++];
+    power = datarate & 0x0F;
+    datarate = (datarate >> 4) & 0x0F;
+
+    mask = payload[index++];
+    mask |= payload[index++] << 8;
+
+    nbRep = payload[index++];
+    ctrl = (nbRep >> 4) & 0x07;
+    nbRep &= 0x0F;
+
+    if (nbRep == 0) {
+        nbRep = 1;
+    }
+
+    if (datarate > _maxDatarate) {
+        status &= 0xFD; // Datarate KO
+    }
+    //
+    // Remark MaxTxPower = 0 and MinTxPower = 10
+    //
+    if (power < 0 || power > 10) {
+        status &= 0xFB; // TxPower KO
+    }
+
+    switch (ctrl) {
+        case 0:
+            SetChannelMask(0, mask);
+            break;
+
+        case 6:
+            // enable all currently defined channels
+            // set bits 0 - N of a number by (2<<N)-1
+            new_mask = (1 << _channels.size()) - 1;
+            SetChannelMask(0, new_mask);
+            break;
+
+        default:
+            logWarning("rejecting RFU or unknown control value %d", ctrl);
+            status &= 0xFE; // ChannelMask KO
+            return LORA_ERROR;
+    }
+
+    if (GetSettings()->Network.ADREnabled) {
+        GetSettings()->Session.TxDatarate = datarate;
+        GetSettings()->Session.TxPower = TX_POWERS[power];
+        GetSettings()->Session.Redundancy = nbRep;
+    } else {
+        logDebug("ADR is disabled, DR and Power not changed.");
+        status &= 0xFB; // TxPower KO
+        status &= 0xFD; // Datarate KO
+    }
+
+    logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::ValidateAdrConfiguration() {
+    uint8_t status = 0x07;
+    uint8_t datarate = GetSettings()->Session.TxDatarate;
+    uint8_t power = GetSettings()->Session.TxPower;
+
+    if (!GetSettings()->Network.ADREnabled) {
+        logDebug("ADR disabled - no applied changes to validate");
+        return status;
+    }
+
+    if (datarate > _maxDatarate) {
+        logWarning("ADR Datarate KO - outside allowed range");
+        status &= 0xFD; // Datarate KO
+    }
+    if (power < _minTxPower || power > _maxTxPower) {
+        logWarning("ADR TX Power KO - outside allowed range");
+        status &= 0xFB; // TxPower KO
+    }
+
+    // default channels must be enabled
+    if ((_channelMask[0] & 0x0007) != 0x0007) {
+        logWarning("ADR Channel Mask KO - default channels must be enabled");
+        status &= 0xFE; // ChannelMask KO
+    }
+
+    // mask must not contain any undefined channels
+    for (int i = 3; i < 16; i++) {
+        if ((_channelMask[0] & (1 << i)) && (_channels[i].Frequency == 0)) {
+            logWarning("ADR Channel Mask KO - cannot enable undefined channel");
+            status &= 0xFE; // ChannelMask KO
+            break;
+        }
+    }
+
+    return status;
+}
+
+uint8_t ChannelPlan_IN865::HandleAckTimeout() {
+
+    if (!GetSettings()->Network.ADREnabled) {
+        return LORA_ADR_OFF;
+    }
+
+    if ((++(GetSettings()->Session.AckCounter) % 2) == 0) {
+        if (GetSettings()->Session.TxPower < GetSettings()->Network.TxPowerMax) {
+            logTrace("ADR Setting power to maximum");
+            GetSettings()->Session.TxPower = GetSettings()->Network.TxPowerMax;
+        } else if (GetSettings()->Session.TxDatarate > 0) {
+            logTrace("ADR Lowering datarate");
+            (GetSettings()->Session.TxDatarate)--;
+        }
+    }
+
+    return LORA_OK;
+}
+
+
+uint32_t ChannelPlan_IN865::GetTimeOffAir()
+{
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON)
+        return 0;
+
+    uint32_t min = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+
+    min = UINT_MAX;
+    int8_t band = 0;
+
+    if (P2PEnabled()) {
+        int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+        if (_dutyBands[band].TimeOffEnd > now) {
+            min = _dutyBands[band].TimeOffEnd - now;
+        } else {
+            min = 0;
+        }
+    } else {
+        for (size_t i = 0; i < _channels.size(); i++) {
+            if (IsChannelEnabled(i) && GetChannel(i).Frequency != 0 &&
+                !(GetSettings()->Session.TxDatarate < GetChannel(i).DrRange.Fields.Min ||
+                  GetSettings()->Session.TxDatarate > GetChannel(i).DrRange.Fields.Max)) {
+
+                band = GetDutyBand(GetChannel(i).Frequency);
+                if (band != -1) {
+                    // logDebug("band: %d time-off: %d now: %d", band, _dutyBands[band].TimeOffEnd, now);
+                    if (_dutyBands[band].TimeOffEnd > now) {
+                        min = std::min < uint32_t > (min, _dutyBands[band].TimeOffEnd - now);
+                    } else {
+                        min = 0;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+
+    if (GetSettings()->Session.AggregatedTimeOffEnd > 0 && GetSettings()->Session.AggregatedTimeOffEnd > now) {
+        min = std::max < uint32_t > (min, GetSettings()->Session.AggregatedTimeOffEnd - now);
+    }
+
+    now = time(NULL);
+    uint32_t join_time = 0;
+
+    if (GetSettings()->Session.JoinFirstAttempt != 0 && now < GetSettings()->Session.JoinTimeOffEnd) {
+        join_time = (GetSettings()->Session.JoinTimeOffEnd - now) * 1000;
+    }
+
+    min = std::max < uint32_t > (join_time, min);
+
+    return min;
+}
+
+
+void ChannelPlan_IN865::UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms) {
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON) {
+        _dutyCycleTimer.stop();
+        for (size_t i = 0; i < _dutyBands.size(); i++) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+        return;
+    }
+
+    _dutyCycleTimer.start();
+
+    if (GetSettings()->Session.MaxDutyCycle > 0 && GetSettings()->Session.MaxDutyCycle <= 15) {
+        GetSettings()->Session.AggregatedTimeOffEnd = _dutyCycleTimer.read_ms() + time_on_air_ms * GetSettings()->Session.AggregateDutyCycle;
+        logDebug("Updated Aggregate DCycle Time-off: %lu DC: %f%%", GetSettings()->Session.AggregatedTimeOffEnd, 1 / float(GetSettings()->Session.AggregateDutyCycle));
+    } else {
+        GetSettings()->Session.AggregatedTimeOffEnd = 0;
+    }
+
+
+    uint32_t time_off_air = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now) {
+            _dutyBands[i].TimeOffEnd = 0;
+        } else {
+            _dutyBands[i].TimeOffEnd -= now;
+        }
+
+        if (freq >= _dutyBands[i].FrequencyMin && freq <= _dutyBands[i].FrequencyMax) {
+            logDebug("update TOE: freq: %d i:%d toa: %d DC:%d", freq, i, time_on_air_ms, _dutyBands[i].DutyCycle);
+
+            if (freq > _minFrequency && freq < _maxFrequency && (GetSettings()->Session.TxPower + GetSettings()->Network.AntennaGain) <= 7) {
+                _dutyBands[i].TimeOffEnd = 0;
+            } else {
+                time_off_air = time_on_air_ms * _dutyBands[i].DutyCycle;
+                _dutyBands[i].TimeOffEnd = time_off_air;
+            }
+        }
+    }
+
+
+    ResetDutyCycleTimer();
+}
+
+std::vector<uint32_t> lora::ChannelPlan_IN865::GetChannels() {
+    std::vector < uint32_t > chans;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        chans.push_back(_channels[i].Frequency);
+    }
+    chans.push_back(GetRxWindow(2).Frequency);
+
+    return chans;
+}
+
+std::vector<uint8_t> lora::ChannelPlan_IN865::GetChannelRanges() {
+    std::vector < uint8_t > ranges;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        ranges.push_back(_channels[i].DrRange.Value);
+    }
+
+    ranges.push_back(GetRxWindow(2).DatarateIndex);
+
+    return ranges;
+
+}
+
+void lora::ChannelPlan_IN865::EnableDefaultChannels() {
+    _channelMask[0] |= 0x0003;
+}
+
+uint8_t ChannelPlan_IN865::GetNextChannel()
+{
+    if (GetSettings()->Session.AggregatedTimeOffEnd != 0) {
+        return LORA_AGGREGATED_DUTY_CYCLE;
+    }
+
+    if (P2PEnabled() || GetSettings()->Network.TxFrequency != 0) {
+        logDebug("Using frequency %d", GetSettings()->Network.TxFrequency);
+
+        if (GetSettings()->Test.DisableDutyCycle != lora::ON) {
+            int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+            logDebug("band: %d freq: %d", band, GetSettings()->Network.TxFrequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
+                return LORA_NO_CHANS_ENABLED;
+            }
+        }
+
+        GetRadio()->SetChannel(GetSettings()->Network.TxFrequency);
+        return LORA_OK;
+    }
+
+    uint8_t start = 0;
+    uint8_t maxChannels = _numChans125k;
+    uint8_t nbEnabledChannels = 0;
+    uint8_t *enabledChannels = new uint8_t[maxChannels];
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        maxChannels = _numChans500k;
+        start = _numChans125k;
+    }
+
+// Search how many channels are enabled
+    DatarateRange range;
+    uint8_t dr_index = GetSettings()->Session.TxDatarate;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now || GetSettings()->Test.DisableDutyCycle == lora::ON) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+    }
+
+    for (uint8_t i = start; i < start + maxChannels; i++) {
+        range = GetChannel(i).DrRange;
+        // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
+
+        if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
+            int8_t band = GetDutyBand(GetChannel(i).Frequency);
+            // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
+                enabledChannels[nbEnabledChannels++] = i;
+            }
+        }
+    }
+
+    logTrace("Number of available channels: %d", nbEnabledChannels);
+
+    uint32_t freq = 0;
+    uint8_t sf = GetTxDatarate().SpreadingFactor;
+    uint8_t bw = GetTxDatarate().Bandwidth;
+    int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
+
+    if (nbEnabledChannels == 0) {
+        delete [] enabledChannels;
+        return LORA_NO_CHANS_ENABLED;
+    }
+
+    if (GetSettings()->Network.CADEnabled) {
+        // Search for free channel with ms timeout
+        int16_t timeout = 10000;
+        Timer tmr;
+        tmr.start();
+
+        for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
+            freq = GetChannel(enabledChannels[j]).Frequency;
+
+            if (GetRadio()->IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
+                _txChannel = enabledChannels[j];
+                break;
+            }
+        }
+    } else {
+        uint8_t j = rand_r(0, nbEnabledChannels - 1);
+        _txChannel = enabledChannels[j];
+        freq = GetChannel(_txChannel).Frequency;
+    }
+
+    assert(freq != 0);
+
+    logDebug("Using channel %d : %d", _txChannel, freq);
+    GetRadio()->SetChannel(freq);
+
+    delete [] enabledChannels;
+    return LORA_OK;
+}
+
+
+uint8_t lora::ChannelPlan_IN865::GetJoinDatarate() {
+    uint8_t dr = GetSettings()->Session.TxDatarate;
+    
+    // Default join datarate is DR2:SF10BW125
+    dr = lora::DR_2;
+    
+    return dr;
+}
+
+uint8_t ChannelPlan_IN865::CalculateJoinBackoff(uint8_t size) {
+
+    time_t now = time(NULL);
+    uint32_t time_on_max = 0;
+    static uint32_t time_off_max = 15;
+    uint32_t rand_time_off = 0;
+
+    // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
+
+    if ((time_t)GetSettings()->Session.JoinTimeOffEnd > now) {
+        return LORA_JOIN_BACKOFF;
+    }
+
+    uint32_t secs_since_first_attempt = (now - GetSettings()->Session.JoinFirstAttempt);
+    uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
+
+    static uint8_t join_cnt = 0;
+
+    join_cnt = (join_cnt+1) % 8;
+
+    if (GetSettings()->Session.JoinFirstAttempt == 0) {
+        /* 1 % duty-cycle for first hour
+         * 0.1 % next 10 hours
+         * 0.01 % upto 24 hours         */
+        GetSettings()->Session.JoinFirstAttempt = now;
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    } else if (join_cnt == 0) {
+        if (hours_since_first_attempt < 1) {
+            time_on_max = 36000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 60 * 60;
+            }
+        } else if (hours_since_first_attempt < 11) {
+            if (GetSettings()->Session.JoinTimeOnAir < 36000) {
+                GetSettings()->Session.JoinTimeOnAir = 36000;
+            }
+            time_on_max = 72000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 11 * 60 * 60;
+            }
+        } else {
+            if (GetSettings()->Session.JoinTimeOnAir < 72000) {
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+            }
+            uint32_t join_time = 2500;
+
+            time_on_max = 80700;
+            time_off_max = 1 * 60 * 60; // 1 hour
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max - join_time) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                // Reset the join time on air and set end of restriction to the next 24 hour period
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+                uint16_t days = (now - GetSettings()->Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
+                logWarning("days : %d", days);
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
+            }
+        }
+
+        logWarning("JoinBackoff: %lu seconds  Time On Air: %lu / %lu", GetSettings()->Session.JoinTimeOffEnd - now, GetSettings()->Session.JoinTimeOnAir, time_on_max);
+    } else {
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_IN865::HandleMacCommand(uint8_t* payload, uint8_t& index) {
+    return LORA_ERROR;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_IN865.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,231 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_IN865_H__
+#define __CHANNEL_PLAN_IN865_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include <vector>
+#include "ChannelPlan.h"
+
+namespace lora {
+
+    class ChannelPlan_IN865 : public lora::ChannelPlan {
+        public:
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_IN865();
+            ChannelPlan_IN865(Settings* settings);
+            ChannelPlan_IN865(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_IN865();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel();
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index);
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate();
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig();
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous);
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t sub_band);
+
+            /**
+             * Callback for ACK timeout event
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAckTimeout();
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size);
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration();
+
+            /**
+             * Update duty cycle with at given frequency and time on air
+             * @param freq frequency
+             * @param time_on_air_ms tx time on air
+             */
+            virtual void UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms);
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir();
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges();
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd);
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels();
+
+            /**
+             * Called when MAC layer doesn't know about a command.
+             * Use to add custom or new mac command handling
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleMacCommand(uint8_t* payload, uint8_t& index);
+
+        protected:
+
+            static const uint8_t IN865_TX_POWERS[11];                    //!< List of available tx powers
+            static const uint8_t IN865_RADIO_POWERS[21];                 //!< List of calibrated tx powers
+            static const uint8_t IN865_MAX_PAYLOAD_SIZE[];              //!< List of max payload sizes for each datarate
+            static const uint8_t IN865_MAX_PAYLOAD_SIZE_REPEATER[];     //!< List of repeater compatible max payload sizes for each datarate
+    };
+}
+
+#endif //__CHANNEL_PLAN_IN865_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_KR920.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,977 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_KR920.h"
+#include "limits.h"
+
+using namespace lora;
+
+// MWF - changed KR920 to match final 1.0.2 regional spec
+const uint8_t ChannelPlan_KR920::KR920_TX_POWERS[] = { 14, 12, 10, 8, 6, 4, 2, 0 };
+const uint8_t ChannelPlan_KR920::KR920_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 20 };
+const uint8_t ChannelPlan_KR920::KR920_MAX_PAYLOAD_SIZE[] = { 65, 151, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+const uint8_t ChannelPlan_KR920::KR920_MAX_PAYLOAD_SIZE_REPEATER[] = { 45, 131, 222, 222, 222, 222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ChannelPlan_KR920::ChannelPlan_KR920()
+:
+    ChannelPlan(NULL, NULL)
+{
+
+}
+
+ChannelPlan_KR920::ChannelPlan_KR920(Settings* settings)
+:
+    ChannelPlan(NULL, settings)
+{
+
+}
+
+ChannelPlan_KR920::ChannelPlan_KR920(SxRadio* radio, Settings* settings)
+:
+    ChannelPlan(radio, settings)
+{
+
+}
+
+ChannelPlan_KR920::~ChannelPlan_KR920() {
+
+}
+
+void ChannelPlan_KR920::Init() {
+
+    _datarates.clear();
+    _channels.clear();
+    _dutyBands.clear();
+
+    DutyBand band;
+
+    band.Index = 0;
+    band.DutyCycle = 0;
+
+    Datarate dr;
+
+    _plan = KR920;
+    _planName = "KR920";
+    _maxTxPower = 14;
+    _minTxPower = 0;
+
+    _minFrequency = 920900000;
+    _maxFrequency = 923300000;
+
+    DefaultLBT();
+
+    TX_POWERS = KR920_TX_POWERS;
+    RADIO_POWERS = KR920_RADIO_POWERS;
+    MAX_PAYLOAD_SIZE = KR920_MAX_PAYLOAD_SIZE;
+    MAX_PAYLOAD_SIZE_REPEATER = KR920_MAX_PAYLOAD_SIZE_REPEATER;
+
+    _minDatarate = 0;
+    _maxDatarate = 5;
+
+    _minRx2Datarate = DR_0;
+    _maxRx2Datarate = DR_5;
+
+    _minDatarateOffset = 0;
+    _maxDatarateOffset = 5;
+
+    _numChans125k = 16;
+    _numChans500k = 0;
+
+    GetSettings()->Session.Rx2Frequency = 921900000;
+    GetSettings()->Session.Rx2DatarateIndex = DR_0;
+
+    logInfo("Initialize datarates...");
+
+    dr.SpreadingFactor = SF_12;
+
+    // Add DR0-5
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Skip DR6-15 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    while (dr.Index++ < DR_15) {
+        AddDatarate(-1, dr);
+    }
+
+    GetSettings()->Session.TxDatarate = 0;
+
+    logInfo("Initialize channels...");
+
+    Channel chan;
+    chan.DrRange.Fields.Min = DR_0;
+    chan.DrRange.Fields.Max = DR_5;
+    chan.Index = 0;
+    chan.Frequency = 922100000;
+    SetNumberOfChannels(16);
+
+    uint8_t numDefaultChannels = 3;
+
+    for (uint8_t i = 0; i < numDefaultChannels; i++) {
+        AddChannel(i, chan);
+        chan.Index++;
+        chan.Frequency += 200000;
+    }
+
+    chan.DrRange.Value = 0;
+    chan.Frequency = 0;
+
+    for (uint8_t i = numDefaultChannels; i < 16; i++) {
+        AddChannel(i, chan);
+        chan.Index++;
+    }
+
+    // Add downlink channel defaults
+    chan.Index = 0;
+    _dlChannels.resize(16);
+    for (uint8_t i = 0; i < 16; i++) {
+        AddDownlinkChannel(i, chan);
+        chan.Index++;
+    }
+
+    SetChannelMask(0, 0x07);
+
+    band.Index = 0;
+    band.FrequencyMin = _minFrequency;
+    band.FrequencyMax = _maxFrequency;
+    band.PowerMax = 14;
+    band.TimeOffEnd = 0;
+
+    // Disable duty-cycle limits
+    band.DutyCycle = 0;
+
+    AddDutyBand(-1, band);
+
+    GetSettings()->Session.TxPower = GetSettings()->Network.TxPower;
+}
+
+uint8_t ChannelPlan_KR920::AddChannel(int8_t index, Channel channel) {
+    logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
+
+    assert(index < (int) _channels.size());
+
+    if (index >= 0) {
+        _channels[index] = channel;
+    } else {
+        _channels.push_back(channel);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
+
+    if (size == 33) {
+        Channel ch;
+        int index = 3;
+        for (int i = 13; i < size - 5; i += 3) {
+
+            ch.Frequency = ((buffer[i]) | (buffer[i + 1] << 8) | (buffer[i + 2] << 16)) * 100u;
+
+            if (ch.Frequency > 0) {
+                ch.Index = index;
+                ch.DrRange.Fields.Min = static_cast<int8_t>(DR_0);
+                ch.DrRange.Fields.Max = static_cast<int8_t>(DR_5);
+                AddChannel(index, ch);
+
+                if (GetDutyBand(ch.Frequency) > -1)
+                    _channelMask[0] |= (1 << index);
+                else
+                    _channelMask[0] |= ~(1 << index);
+
+                index += 1;
+            }
+        }
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::SetTxConfig() {
+
+    logInfo("Configure radio for TX");
+
+    uint8_t band = GetDutyBand(GetChannel(_txChannel).Frequency);
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    int8_t max_pwr = _dutyBands[band].PowerMax;
+
+    if (GetChannel(_txChannel).Frequency < 922100000) {
+        max_pwr = 10;
+    } else {
+        max_pwr = 14;
+    }
+
+    int8_t pwr = 0;
+
+    pwr = std::min < int8_t > (GetSettings()->Session.TxPower, (max_pwr - GetSettings()->Network.AntennaGain));
+
+    for (int i = 20; i >= 0; i--) {
+        if (RADIO_POWERS[i] <= pwr) {
+            pwr = i;
+            break;
+        }
+        if (i == 0) {
+            pwr = i;
+        }
+    }
+
+    logDebug("Session pwr: %d ant: %d max: %d", GetSettings()->Session.TxPower, GetSettings()->Network.AntennaGain, max_pwr);
+    logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + GetSettings()->Network.AntennaGain);
+
+    uint32_t bw = txDr.Bandwidth;
+    uint32_t sf = txDr.SpreadingFactor;
+    uint8_t cr = txDr.Coderate;
+    uint8_t pl = txDr.PreambleLength;
+    uint16_t fdev = 0;
+    bool crc = txDr.Crc;
+    bool iq = txDr.TxIQ;
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        fdev = 25e3;
+        bw = 0;
+    }
+
+    GetRadio()->SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
+
+    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);
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_KR920::SetRxConfig(uint8_t window, bool continuous) {
+
+    RxWindow rxw = GetRxWindow(window);
+    GetRadio()->SetChannel(rxw.Frequency);
+
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint32_t bw = rxDr.Bandwidth;
+    uint32_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    uint32_t afc = 0;
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    bool iq = txDr.RxIQ;
+
+    if (P2PEnabled()) {
+        iq = txDr.TxIQ;
+    }
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        cr = 0;
+        bw = 50e3;
+        afc = 83333;
+        iq = false;
+        crc = true;  // FSK must use CRC
+    }
+
+    // Disable printf's to actually receive packets, printing to debug may mess up the timing
+    // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
+    // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
+
+    GetRadio()->SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
+
+    return LORA_OK;
+}
+
+Channel ChannelPlan_KR920::GetChannel(int8_t index) {
+    Channel chan;
+    memset(&chan, 0, sizeof(Channel));
+
+    chan = _channels[index];
+
+    return chan;
+}
+
+uint8_t ChannelPlan_KR920::SetFrequencySubBand(uint8_t sub_band) {
+    return LORA_OK;
+}
+
+void ChannelPlan_KR920::LogRxWindow(uint8_t wnd) {
+
+    RxWindow rxw = GetRxWindow(wnd);
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint8_t bw = rxDr.Bandwidth;
+    uint8_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+    bool iq = GetTxDatarate().RxIQ;
+
+    logTrace("RX%d on freq: %lu", wnd, rxw.Frequency);
+    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);
+}
+
+RxWindow ChannelPlan_KR920::GetRxWindow(uint8_t window) {
+    RxWindow rxw;
+    int index = 0;
+
+    if (P2PEnabled()) {
+        rxw.Frequency = GetSettings()->Network.TxFrequency;
+        index = GetSettings()->Session.TxDatarate;
+    } else {
+        if (window == 1) {
+            // Use same frequency as TX
+            rxw.Frequency = _channels[_txChannel].Frequency;
+
+            if (GetSettings()->Session.TxDatarate > GetSettings()->Session.Rx1DatarateOffset) {
+                index = GetSettings()->Session.TxDatarate - GetSettings()->Session.Rx1DatarateOffset;
+            } else {
+                index = 0;
+            }
+
+        } else {
+            // Use session RX2 frequency
+            rxw.Frequency = GetSettings()->Session.Rx2Frequency;
+            index = GetSettings()->Session.Rx2DatarateIndex;
+        }
+    }
+
+    rxw.DatarateIndex = index;
+
+    return rxw;
+}
+
+uint8_t ChannelPlan_KR920::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    status = 0x07;
+    int8_t datarate = 0;
+    int8_t drOffset = 0;
+    uint32_t freq = 0;
+
+    drOffset = payload[index++];
+    datarate = drOffset & 0x0F;
+    drOffset = (drOffset >> 4) & 0x07;
+
+    freq = payload[index++];
+    freq |= payload[index++] << 8;
+    freq |= payload[index++] << 16;
+    freq *= 100;
+
+    if (!CheckRfFrequency(freq)) {
+        logInfo("Freq KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
+        logInfo("DR KO");
+        status &= 0xFD; // Datarate KO
+    }
+
+    if (drOffset < 0 || drOffset > _maxDatarateOffset) {
+        logInfo("DR Offset KO");
+        status &= 0xFB; // Rx1DrOffset range KO
+    }
+
+    if ((status & 0x07) == 0x07) {
+        logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+        SetRx2DatarateIndex(datarate);
+        SetRx2Frequency(freq);
+        SetRx1Offset(drOffset);
+    } else {
+        logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    uint8_t channelIndex = 0;
+    Channel chParam;
+
+    channelIndex = payload[index++];
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (channelIndex < 3 || channelIndex > _channels.size() - 1) {
+        logError("New Channel index KO");
+        status &= 0xFE; // Channel index KO
+    }
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        logError("New Channel frequency KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min > chParam.DrRange.Fields.Max) {
+        logError("New Channel datarate min/max KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        logError("New Channel datarate min KO");
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        logError("New Channel datarate max KO");
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        logInfo("New Channel accepted index: %d freq: %lu drRange: %02x", channelIndex, chParam.Frequency, chParam.DrRange.Value);
+        AddChannel(channelIndex, chParam);
+        SetChannelMask(0, _channelMask[0] | 1 << (channelIndex));
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
+    index += 3;
+
+    if (_beaconRxChannel.Frequency != 0) {
+        _beaconRxChannel.DrRange.Value = payload[index];
+    } else {
+        // TODO: set to default beacon rx channel
+    }
+
+    status = 0x03;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    Channel chParam;
+
+    // Skip channel index
+    index++;
+
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        _beaconChannel = chParam;
+    }
+
+    if (_beaconChannel.Frequency == 0) {
+        // TODO: Set to default
+    }
+
+    status = 0x01;
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_KR920::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    uint8_t power = 0;
+    uint8_t datarate = 0;
+    uint16_t mask = 0;
+    uint16_t new_mask = 0;
+    uint8_t ctrl = 0;
+    uint8_t nbRep = 0;
+
+    status = 0x07;
+    datarate = payload[index++];
+    power = datarate & 0x0F;
+    datarate = (datarate >> 4) & 0x0F;
+
+    mask = payload[index++];
+    mask |= payload[index++] << 8;
+
+    nbRep = payload[index++];
+    ctrl = (nbRep >> 4) & 0x07;
+    nbRep &= 0x0F;
+
+    if (nbRep == 0) {
+        nbRep = 1;
+    }
+
+    if (datarate > _maxDatarate) {
+        status &= 0xFD; // Datarate KO
+    }
+    //
+    // Remark MaxTxPower = 0 and MinTxPower = 7
+    //
+    if (power < 0 || power > 7) {
+        status &= 0xFB; // TxPower KO
+    }
+
+    switch (ctrl) {
+        case 0:
+            SetChannelMask(0, mask);
+            break;
+
+        case 6:
+            // enable all currently defined channels
+            // set bits 0 - N of a number by (2<<N)-1
+            new_mask = (1 << _channels.size()) - 1;
+            SetChannelMask(0, new_mask);
+            break;
+
+        default:
+            logWarning("rejecting RFU or unknown control value %d", ctrl);
+            status &= 0xFE; // ChannelMask KO
+            return LORA_ERROR;
+    }
+
+    if (GetSettings()->Network.ADREnabled) {
+        GetSettings()->Session.TxDatarate = datarate;
+        GetSettings()->Session.TxPower = TX_POWERS[power];
+        GetSettings()->Session.Redundancy = nbRep;
+    } else {
+        logDebug("ADR is disabled, DR and Power not changed.");
+        status &= 0xFB; // TxPower KO
+        status &= 0xFD; // Datarate KO
+    }
+
+    logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::ValidateAdrConfiguration() {
+    uint8_t status = 0x07;
+    uint8_t datarate = GetSettings()->Session.TxDatarate;
+    uint8_t power = GetSettings()->Session.TxPower;
+
+    if (!GetSettings()->Network.ADREnabled) {
+        logDebug("ADR disabled - no applied changes to validate");
+        return status;
+    }
+
+    if (datarate > _maxDatarate) {
+        logWarning("ADR Datarate KO - outside allowed range");
+        status &= 0xFD; // Datarate KO
+    }
+    if (power < _minTxPower || power > _maxTxPower) {
+        logWarning("ADR TX Power KO - outside allowed range");
+        status &= 0xFB; // TxPower KO
+    }
+
+    // default channels must be enabled
+    if ((_channelMask[0] & 0x0007) != 0x0007) {
+        logWarning("ADR Channel Mask KO - default channels must be enabled");
+        status &= 0xFE; // ChannelMask KO
+    }
+
+    // mask must not contain any undefined channels
+    for (int i = 3; i < 16; i++) {
+        if ((_channelMask[0] & (1 << i)) && (_channels[i].Frequency == 0)) {
+            logWarning("ADR Channel Mask KO - cannot enable undefined channel");
+            status &= 0xFE; // ChannelMask KO
+            break;
+        }
+    }
+
+    return status;
+}
+
+uint8_t ChannelPlan_KR920::HandleAckTimeout() {
+
+    if (!GetSettings()->Network.ADREnabled) {
+        return LORA_ADR_OFF;
+    }
+
+    if ((++(GetSettings()->Session.AckCounter) % 2) == 0) {
+        if (GetSettings()->Session.TxPower < GetSettings()->Network.TxPowerMax) {
+            logTrace("ADR Setting power to maximum");
+            GetSettings()->Session.TxPower = GetSettings()->Network.TxPowerMax;
+        } else if (GetSettings()->Session.TxDatarate > 0) {
+            logTrace("ADR Lowering datarate");
+            (GetSettings()->Session.TxDatarate)--;
+        }
+    }
+
+    return LORA_OK;
+}
+
+
+uint32_t ChannelPlan_KR920::GetTimeOffAir()
+{
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON)
+        return 0;
+
+    uint32_t min = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+
+    min = UINT_MAX;
+    int8_t band = 0;
+
+    if (P2PEnabled()) {
+        int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+        if (_dutyBands[band].TimeOffEnd > now) {
+            min = _dutyBands[band].TimeOffEnd - now;
+        } else {
+            min = 0;
+        }
+    } else {
+        for (size_t i = 0; i < _channels.size(); i++) {
+            if (IsChannelEnabled(i) && GetChannel(i).Frequency != 0 &&
+                !(GetSettings()->Session.TxDatarate < GetChannel(i).DrRange.Fields.Min ||
+                  GetSettings()->Session.TxDatarate > GetChannel(i).DrRange.Fields.Max)) {
+
+                band = GetDutyBand(GetChannel(i).Frequency);
+                if (band != -1) {
+                    // logDebug("band: %d time-off: %d now: %d", band, _dutyBands[band].TimeOffEnd, now);
+                    if (_dutyBands[band].TimeOffEnd > now) {
+                        min = std::min < uint32_t > (min, _dutyBands[band].TimeOffEnd - now);
+                    } else {
+                        min = 0;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+
+    if (GetSettings()->Session.AggregatedTimeOffEnd > 0 && GetSettings()->Session.AggregatedTimeOffEnd > now) {
+        min = std::max < uint32_t > (min, GetSettings()->Session.AggregatedTimeOffEnd - now);
+    }
+
+    now = time(NULL);
+    uint32_t join_time = 0;
+
+    if (GetSettings()->Session.JoinFirstAttempt != 0 && now < GetSettings()->Session.JoinTimeOffEnd) {
+        join_time = (GetSettings()->Session.JoinTimeOffEnd - now) * 1000;
+    }
+
+    min = std::max < uint32_t > (join_time, min);
+
+    return min;
+}
+
+
+void ChannelPlan_KR920::UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms) {
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON) {
+        _dutyCycleTimer.stop();
+        for (size_t i = 0; i < _dutyBands.size(); i++) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+        return;
+    }
+
+    _dutyCycleTimer.start();
+
+    if (GetSettings()->Session.MaxDutyCycle > 0 && GetSettings()->Session.MaxDutyCycle <= 15) {
+        GetSettings()->Session.AggregatedTimeOffEnd = _dutyCycleTimer.read_ms() + time_on_air_ms * GetSettings()->Session.AggregateDutyCycle;
+        logDebug("Updated Aggregate DCycle Time-off: %lu DC: %f%%", GetSettings()->Session.AggregatedTimeOffEnd, 1 / float(GetSettings()->Session.AggregateDutyCycle));
+    } else {
+        GetSettings()->Session.AggregatedTimeOffEnd = 0;
+    }
+
+
+    uint32_t time_off_air = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now) {
+            _dutyBands[i].TimeOffEnd = 0;
+        } else {
+            _dutyBands[i].TimeOffEnd -= now;
+        }
+
+        if (freq >= _dutyBands[i].FrequencyMin && freq <= _dutyBands[i].FrequencyMax) {
+            logDebug("update TOE: freq: %d i:%d toa: %d DC:%d", freq, i, time_on_air_ms, _dutyBands[i].DutyCycle);
+
+            if (freq > _minFrequency && freq < _maxFrequency && (GetSettings()->Session.TxPower + GetSettings()->Network.AntennaGain) <= 7) {
+                _dutyBands[i].TimeOffEnd = 0;
+            } else {
+                time_off_air = time_on_air_ms * _dutyBands[i].DutyCycle;
+                _dutyBands[i].TimeOffEnd = time_off_air;
+            }
+        }
+    }
+
+
+    ResetDutyCycleTimer();
+}
+
+std::vector<uint32_t> lora::ChannelPlan_KR920::GetChannels() {
+    std::vector < uint32_t > chans;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        chans.push_back(_channels[i].Frequency);
+    }
+    chans.push_back(GetRxWindow(2).Frequency);
+
+    return chans;
+}
+
+std::vector<uint8_t> lora::ChannelPlan_KR920::GetChannelRanges() {
+    std::vector < uint8_t > ranges;
+
+    for (int8_t i = 0; i < (int) _channels.size(); i++) {
+        ranges.push_back(_channels[i].DrRange.Value);
+    }
+
+    ranges.push_back(GetRxWindow(2).DatarateIndex);
+
+    return ranges;
+
+}
+
+void lora::ChannelPlan_KR920::EnableDefaultChannels() {
+    _channelMask[0] |= 0x0003;
+}
+
+uint8_t ChannelPlan_KR920::GetNextChannel()
+{
+    if (GetSettings()->Session.AggregatedTimeOffEnd != 0) {
+        return LORA_AGGREGATED_DUTY_CYCLE;
+    }
+
+    if (P2PEnabled() || GetSettings()->Network.TxFrequency != 0) {
+        logDebug("Using frequency %d", GetSettings()->Network.TxFrequency);
+
+        if (GetSettings()->Test.DisableDutyCycle != lora::ON) {
+            int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+            logDebug("band: %d freq: %d", band, GetSettings()->Network.TxFrequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
+                return LORA_NO_CHANS_ENABLED;
+            }
+        }
+
+        GetRadio()->SetChannel(GetSettings()->Network.TxFrequency);
+        return LORA_OK;
+    }
+
+    uint8_t start = 0;
+    uint8_t maxChannels = _numChans125k;
+    uint8_t nbEnabledChannels = 0;
+    uint8_t *enabledChannels = new uint8_t[maxChannels];
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        maxChannels = _numChans500k;
+        start = _numChans125k;
+    }
+
+// Search how many channels are enabled
+    DatarateRange range;
+    uint8_t dr_index = GetSettings()->Session.TxDatarate;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now || GetSettings()->Test.DisableDutyCycle == lora::ON) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+    }
+
+    for (uint8_t i = start; i < start + maxChannels; i++) {
+        range = GetChannel(i).DrRange;
+        // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
+
+        if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
+            int8_t band = GetDutyBand(GetChannel(i).Frequency);
+            // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
+                enabledChannels[nbEnabledChannels++] = i;
+            }
+        }
+    }
+
+    logTrace("Number of available channels: %d", nbEnabledChannels);
+
+    uint32_t freq = 0;
+    uint8_t sf = GetTxDatarate().SpreadingFactor;
+    uint8_t bw = GetTxDatarate().Bandwidth;
+    int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
+
+    if (nbEnabledChannels == 0) {
+        delete [] enabledChannels;
+        return LORA_NO_CHANS_ENABLED;
+    }
+
+    if (GetSettings()->Network.CADEnabled) {
+        // Search for free channel with ms timeout
+        int16_t timeout = 10000;
+        Timer tmr;
+        tmr.start();
+
+        for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
+            freq = GetChannel(enabledChannels[j]).Frequency;
+
+            if (GetRadio()->IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
+                _txChannel = enabledChannels[j];
+                break;
+            }
+        }
+    } else {
+        uint8_t j = rand_r(0, nbEnabledChannels - 1);
+        _txChannel = enabledChannels[j];
+        freq = GetChannel(_txChannel).Frequency;
+    }
+
+    assert(freq != 0);
+
+    logDebug("Using channel %d : %d", _txChannel, freq);
+    GetRadio()->SetChannel(freq);
+
+    delete [] enabledChannels;
+    return LORA_OK;
+}
+
+
+uint8_t lora::ChannelPlan_KR920::GetJoinDatarate() {
+    uint8_t dr = GetSettings()->Session.TxDatarate;
+    static uint8_t cnt = 0;
+
+    if (GetSettings()->Test.DisableRandomJoinDatarate == lora::OFF) {
+        if ((cnt++ % 20) == 0) {
+            dr = lora::DR_0;
+        } else if ((cnt % 16) == 0) {
+            dr = lora::DR_1;
+        } else if ((cnt % 12) == 0) {
+            dr = lora::DR_2;
+        } else if ((cnt % 8) == 0) {
+            dr = lora::DR_3;
+        } else if ((cnt % 4) == 0) {
+            dr = lora::DR_4;
+        } else {
+            dr = lora::DR_5;
+        }
+    }
+
+    return dr;
+}
+
+uint8_t ChannelPlan_KR920::CalculateJoinBackoff(uint8_t size) {
+
+    time_t now = time(NULL);
+    uint32_t time_on_max = 0;
+    static uint32_t time_off_max = 15;
+    uint32_t rand_time_off = 0;
+
+    // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
+
+    if ((time_t)GetSettings()->Session.JoinTimeOffEnd > now) {
+        return LORA_JOIN_BACKOFF;
+    }
+
+    uint32_t secs_since_first_attempt = (now - GetSettings()->Session.JoinFirstAttempt);
+    uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
+
+    static uint8_t join_cnt = 0;
+
+    join_cnt = (join_cnt+1) % 8;
+
+    if (GetSettings()->Session.JoinFirstAttempt == 0) {
+        /* 1 % duty-cycle for first hour
+         * 0.1 % next 10 hours
+         * 0.01 % upto 24 hours         */
+        GetSettings()->Session.JoinFirstAttempt = now;
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    } else if (join_cnt == 0) {
+        if (hours_since_first_attempt < 1) {
+            time_on_max = 36000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 60 * 60;
+            }
+        } else if (hours_since_first_attempt < 11) {
+            if (GetSettings()->Session.JoinTimeOnAir < 36000) {
+                GetSettings()->Session.JoinTimeOnAir = 36000;
+            }
+            time_on_max = 72000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 11 * 60 * 60;
+            }
+        } else {
+            if (GetSettings()->Session.JoinTimeOnAir < 72000) {
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+            }
+            uint32_t join_time = 2500;
+
+            time_on_max = 80700;
+            time_off_max = 1 * 60 * 60; // 1 hour
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max - join_time) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                // Reset the join time on air and set end of restriction to the next 24 hour period
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+                uint16_t days = (now - GetSettings()->Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
+                logWarning("days : %d", days);
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
+            }
+        }
+
+        logWarning("JoinBackoff: %lu seconds  Time On Air: %lu / %lu", GetSettings()->Session.JoinTimeOffEnd - now, GetSettings()->Session.JoinTimeOnAir, time_on_max);
+    } else {
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + (GetTimeOnAir(size) / 10);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_KR920::HandleMacCommand(uint8_t* payload, uint8_t& index) {
+    return LORA_ERROR;
+}
+
+void ChannelPlan_KR920::DefaultLBT() {
+    _LBT_TimeUs = 5000;
+    _LBT_Threshold = -65;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_KR920.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,236 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_KR920_H__
+#define __CHANNEL_PLAN_KR920_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include <vector>
+#include "ChannelPlan.h"
+
+namespace lora {
+
+    class ChannelPlan_KR920 : public lora::ChannelPlan {
+        public:
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_KR920();
+            ChannelPlan_KR920(Settings* settings);
+            ChannelPlan_KR920(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_KR920();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel();
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index);
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate();
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig();
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous);
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t sub_band);
+
+            /**
+             * Callback for ACK timeout event
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAckTimeout();
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size);
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration();
+
+            /**
+             * Update duty cycle with at given frequency and time on air
+             * @param freq frequency
+             * @param time_on_air_ms tx time on air
+             */
+            virtual void UpdateDutyCycle(uint32_t freq, uint32_t time_on_air_ms);
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir();
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges();
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd);
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels();
+
+            /**
+             * Called when MAC layer doesn't know about a command.
+             * Use to add custom or new mac command handling
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleMacCommand(uint8_t* payload, uint8_t& index);
+
+            /**
+             * Set LBT time and threshold to defaults
+             */
+            virtual void DefaultLBT();
+
+        protected:
+
+            static const uint8_t KR920_TX_POWERS[8];                    //!< List of available tx powers
+            static const uint8_t KR920_RADIO_POWERS[21];                 //!< List of calibrated tx powers
+            static const uint8_t KR920_MAX_PAYLOAD_SIZE[];              //!< List of max payload sizes for each datarate
+            static const uint8_t KR920_MAX_PAYLOAD_SIZE_REPEATER[];     //!< List of repeater compatible max payload sizes for each datarate
+    };
+}
+
+#endif //__CHANNEL_PLAN_KR920_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_US915.cpp	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,987 @@
+/**********************************************************************
+* COPYRIGHT 2016 MULTI-TECH SYSTEMS, INC.
+*
+* ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF
+* MULTI-TECH SYSTEMS, INC.
+*
+* MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY
+* INFORMATION AND/OR TRADE SECRET.
+*
+* NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION,
+* DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL
+* INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC.
+* USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A
+* WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED.
+*
+***********************************************************************/
+
+#include "ChannelPlan_US915.h"
+#include "limits.h"
+
+using namespace lora;
+
+const uint8_t ChannelPlan_US915::US915_TX_POWERS[] = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 };
+const uint8_t ChannelPlan_US915::US915_RADIO_POWERS[] = { 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 19, 19 };
+const uint8_t ChannelPlan_US915::US915_MAX_PAYLOAD_SIZE[] = { 11, 53, 126, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 };
+const uint8_t ChannelPlan_US915::US915_MAX_PAYLOAD_SIZE_REPEATER[] = { 11, 53, 126, 222, 222, 0, 0, 33, 109, 222, 222, 222, 0, 0 };
+
+ChannelPlan_US915::ChannelPlan_US915()
+:
+  ChannelPlan(NULL, NULL)
+{
+
+}
+
+ChannelPlan_US915::ChannelPlan_US915(Settings* settings)
+:
+  ChannelPlan(NULL, settings)
+{
+
+}
+
+ChannelPlan_US915::ChannelPlan_US915(SxRadio* radio, Settings* settings)
+:
+  ChannelPlan(radio, settings)
+{
+
+}
+
+ChannelPlan_US915::~ChannelPlan_US915() {
+
+}
+
+void ChannelPlan_US915::Init() {
+    _plan = US915;
+    _planName = "US915";
+
+    _datarates.clear();
+    _channels.clear();
+    _dutyBands.clear();
+
+    DutyBand band;
+
+    band.Index = 0;
+    band.DutyCycle = 0;
+
+    Datarate dr;
+
+    _maxTxPower = 30;
+    _minTxPower = 0;
+
+    _minFrequency = US915_FREQ_MIN;
+    _maxFrequency = US915_FREQ_MAX;
+
+    TX_POWERS = US915_TX_POWERS;
+    RADIO_POWERS = US915_RADIO_POWERS;
+    MAX_PAYLOAD_SIZE = US915_MAX_PAYLOAD_SIZE;
+    MAX_PAYLOAD_SIZE_REPEATER = US915_MAX_PAYLOAD_SIZE_REPEATER;
+
+    band.FrequencyMin = US915_FREQ_MIN;
+    band.FrequencyMax = US915_FREQ_MAX;
+
+    _freqUBase125k = US915_125K_FREQ_BASE;
+    _freqUStep125k = US915_125K_FREQ_STEP;
+    _freqUBase500k = US915_500K_FREQ_BASE;
+    _freqUStep500k = US915_500K_FREQ_STEP;
+    _freqDBase500k = US915_500K_DBASE;
+    _freqDStep500k = US915_500K_DSTEP;
+    GetSettings()->Session.Rx2Frequency = US915_500K_DBASE;
+
+    _minDatarate = US915_MIN_DATARATE;
+    _maxDatarate = US915_MAX_DATARATE;
+    _minRx2Datarate = DR_8;
+    _maxRx2Datarate = DR_13;
+    _minDatarateOffset = US915_MIN_DATARATE_OFFSET;
+    _maxDatarateOffset = US915_MAX_DATARATE_OFFSET;
+
+    _numChans125k = US915_125K_NUM_CHANS;
+    _numChans500k = US915_500K_NUM_CHANS;
+
+    logInfo("Initialize channels...");
+
+    SetNumberOfChannels(US915_125K_NUM_CHANS + US915_500K_NUM_CHANS, false);
+
+    dr.SpreadingFactor = SF_10;
+
+    logInfo("Initialize datarates...");
+
+    // Add DR0-3
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Add DR4
+    dr.SpreadingFactor = SF_8;
+    dr.Bandwidth = BW_500;
+    AddDatarate(-1, dr);
+    dr.Index++;
+
+    // Skip DR5-7 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    AddDatarate(-1, dr), dr.Index++;
+    AddDatarate(-1, dr), dr.Index++;
+    AddDatarate(-1, dr), dr.Index++;
+
+    band.PowerMax = 30;
+
+    band.TimeOffEnd = 0;
+
+    AddDutyBand(-1, band);
+
+    GetSettings()->Session.Rx2DatarateIndex = DR_8;
+
+    // Add DR8-13
+    dr.SpreadingFactor = SF_12;
+    while (dr.SpreadingFactor >= SF_7) {
+        AddDatarate(-1, dr);
+        dr.SpreadingFactor--;
+        dr.Index++;
+    }
+
+    // Skip DR14-15 RFU
+    dr.SpreadingFactor = SF_INVALID;
+    AddDatarate(-1, dr), AddDatarate(-1, dr);
+
+    GetSettings()->Session.TxDatarate = DR_0;
+    GetSettings()->Session.TxPower = GetSettings()->Network.TxPower;
+
+    SetFrequencySubBand(GetSettings()->Network.FrequencySubBand);
+
+}
+
+uint8_t ChannelPlan_US915::HandleJoinAccept(const uint8_t* buffer, uint8_t size) {
+
+    if (size > 17) {
+        // TODO: Handle future channel mask settings
+    }
+
+    return LORA_OK;
+}
+
+void ChannelPlan_US915::SetNumberOfChannels(uint8_t channels, bool resize) {
+    uint8_t newsize = ((channels - 1) / CHAN_MASK_SIZE) + 1;
+
+    if (resize) {
+        _channels.resize(channels);
+    }
+
+    _channelMask.resize(newsize, 0x0);
+    _numChans = channels;
+
+}
+
+bool ChannelPlan_US915::IsChannelEnabled(uint8_t channel) {
+    uint8_t index = channel / CHAN_MASK_SIZE;
+    uint8_t shift = channel % CHAN_MASK_SIZE;
+
+    assert(index < _channelMask.size() * CHAN_MASK_SIZE);
+
+    // cannot shift over 32 bits
+    assert(shift < 32);
+
+    // logDebug("index: %d shift %d cm: %04x bit: %04x enabled: %d", index, shift, _channelMask[index], (1 << shift), (_channelMask[index] & (1 << shift)) == (1 << shift));
+
+    return (_channelMask[index] & (1 << shift)) == (1 << shift);
+}
+
+uint8_t ChannelPlan_US915::GetMinDatarate() {
+    if (GetSettings()->Network.Mode == lora::PEER_TO_PEER)
+        return 8;
+    else
+        return _minDatarate;
+}
+
+uint8_t ChannelPlan_US915::GetMaxDatarate() {
+    if (GetSettings()->Network.Mode == lora::PEER_TO_PEER)
+        return 13;
+    else
+        return _maxDatarate;
+}
+
+uint8_t ChannelPlan_US915::SetRx1Offset(uint8_t offset) {
+    GetSettings()->Session.Rx1DatarateOffset = offset;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::SetRx2Frequency(uint32_t freq) {
+    GetSettings()->Session.Rx2Frequency = freq;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::SetRx2DatarateIndex(uint8_t index) {
+    GetSettings()->Session.Rx2DatarateIndex = index;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::SetTxConfig() {
+    logInfo("Configure radio for TX");
+
+    uint8_t band = GetDutyBand(GetChannel(_txChannel).Frequency);
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    int8_t max_pwr = _dutyBands[band].PowerMax;
+    uint8_t chans_enabled = 0;
+
+    int8_t pwr = 0;
+
+    pwr = std::min < int8_t > (GetSettings()->Session.TxPower, max_pwr);
+
+    // spec states that if < 50 125kHz channels are enabled, power is limited to 21dB conducted
+    chans_enabled += CountBits(_channelMask[0]);
+    chans_enabled += CountBits(_channelMask[1]);
+    chans_enabled += CountBits(_channelMask[2]);
+    chans_enabled += CountBits(_channelMask[3]);
+    if (chans_enabled < 50 && pwr > 21) {
+        pwr = 21;
+    }
+
+    if (pwr + GetSettings()->Network.AntennaGain >= max_pwr + 6 && GetSettings()->Network.AntennaGain > 6) {
+        pwr -= (GetSettings()->Network.AntennaGain - 6);
+    }
+
+    for (int i = 20; i >= 0; i--) {
+        if (RADIO_POWERS[i] <= pwr) {
+            pwr = i;
+            break;
+        }
+        if (i == 0) {
+            pwr = i;
+        }
+    }
+
+    logDebug("Session pwr: %d ant: %d max: %d", GetSettings()->Session.TxPower, GetSettings()->Network.AntennaGain, max_pwr);
+    logDebug("Radio Power index: %d output: %d total: %d", pwr, RADIO_POWERS[pwr], RADIO_POWERS[pwr] + GetSettings()->Network.AntennaGain);
+
+    uint32_t bw = txDr.Bandwidth;
+    uint32_t sf = txDr.SpreadingFactor;
+    uint8_t cr = txDr.Coderate;
+    uint8_t pl = txDr.PreambleLength;
+    uint16_t fdev = 0;
+    bool crc = txDr.Crc;
+    bool iq = txDr.TxIQ;
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        fdev = 25e3;
+        bw = 0;
+    }
+
+    GetRadio()->SetTxConfig(modem, pwr, fdev, bw, sf, cr, pl, false, crc, false, 0, iq, 3e3);
+
+    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);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::SetRxConfig(uint8_t window, bool continuous) {
+
+    RxWindow rxw = GetRxWindow(window);
+    GetRadio()->SetChannel(rxw.Frequency);
+
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint32_t bw = rxDr.Bandwidth;
+    uint32_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    uint32_t afc = 0;
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+
+    if (GetSettings()->Network.DisableCRC == true)
+        crc = false;
+
+    Datarate txDr = GetDatarate(GetSettings()->Session.TxDatarate);
+    bool iq = txDr.RxIQ;
+
+    if (P2PEnabled()) {
+        iq = txDr.TxIQ;
+    }
+
+    SxRadio::RadioModems_t modem = SxRadio::MODEM_LORA;
+
+    if (sf == SF_FSK) {
+        modem = SxRadio::MODEM_FSK;
+        sf = 50e3;
+        cr = 0;
+        bw = 50e3;
+        afc = 83333;
+        iq = false;
+        crc = true;  // FSK must use CRC
+    }
+
+    // Disable printf's to actually receive packets, printing to debug may mess up the timing
+    // logTrace("Configure radio for RX%d on freq: %lu", window, rxw.Frequency);
+    // logTrace("RX SF: %u BW: %u CR: %u PL: %u STO: %u CRC: %d IQ: %d", sf, bw, cr, pl, sto, crc, iq);
+
+    GetRadio()->SetRxConfig(modem, bw, sf, cr, afc, pl, sto, false, 0, crc, false, 0, iq, continuous);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::AddChannel(int8_t index, Channel channel) {
+    logTrace("Add Channel %d : %lu : %02x %d", index, channel.Frequency, channel.DrRange.Value, _channels.size());
+
+    assert(index < (int) _channels.size());
+
+    if (index >= 0) {
+        _channels[index] = channel;
+    } else {
+        _channels.push_back(channel);
+    }
+
+    return LORA_OK;
+}
+
+Channel ChannelPlan_US915::GetChannel(int8_t index) {
+    Channel chan;
+    memset(&chan, 0, sizeof(Channel));
+
+    if (_channels.size() > 0) {
+        chan = _channels[index];
+    } else {
+        if (index < 64) {
+            chan.Index = index;
+            chan.DrRange.Fields.Min = _minDatarate;
+            chan.DrRange.Fields.Max = _maxDatarate - 1;
+            chan.Frequency = _freqUBase125k + (_freqUStep125k * index);
+        } else if (index < 72) {
+            chan.Index = index;
+            chan.DrRange.Fields.Min = _maxDatarate;
+            chan.DrRange.Fields.Max = _maxDatarate;
+            chan.Frequency = _freqUBase500k + (_freqUStep500k * (index - 64));
+        }
+    }
+
+    return chan;
+}
+
+uint8_t ChannelPlan_US915::SetFrequencySubBand(uint8_t sub_band) {
+
+    _txFrequencySubBand = sub_band;
+
+    if (sub_band > 0) {
+        SetChannelMask(0, 0x0000);
+        SetChannelMask(1, 0x0000);
+        SetChannelMask(2, 0x0000);
+        SetChannelMask(3, 0x0000);
+        SetChannelMask(4, 0x0000);
+        SetChannelMask((sub_band - 1) / 2, (sub_band % 2) ? 0x00FF : 0xFF00);
+        SetChannelMask(4, 1 << (sub_band - 1));
+    } else {
+        SetChannelMask(0, 0xFFFF);
+        SetChannelMask(1, 0xFFFF);
+        SetChannelMask(2, 0xFFFF);
+        SetChannelMask(3, 0xFFFF);
+        SetChannelMask(4, 0x00FF);
+    }
+
+    return LORA_OK;
+}
+
+
+void ChannelPlan_US915::LogRxWindow(uint8_t wnd) {
+
+    RxWindow rxw = GetRxWindow(wnd);
+    Datarate rxDr = GetDatarate(rxw.DatarateIndex);
+    uint8_t bw = rxDr.Bandwidth;
+    uint8_t sf = rxDr.SpreadingFactor;
+    uint8_t cr = rxDr.Coderate;
+    uint8_t pl = rxDr.PreambleLength;
+    uint16_t sto = rxDr.SymbolTimeout();
+    bool crc = false; // downlink does not use CRC according to LORAWAN
+    bool iq = GetTxDatarate().RxIQ;
+
+    logTrace("RX%d on freq: %lu", wnd, rxw.Frequency);
+    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);
+}
+
+RxWindow ChannelPlan_US915::GetRxWindow(uint8_t window) {
+    RxWindow rxw;
+    int index = 0;
+
+    if (P2PEnabled()) {
+        rxw.Frequency = GetSettings()->Network.TxFrequency;
+        index = GetSettings()->Session.TxDatarate;
+    } else {
+        if (window == 1) {
+            if (_txChannel < _numChans125k) {
+                if (GetSettings()->Network.Mode == PUBLIC) {
+                    rxw.Frequency = _freqDBase500k + (_txChannel % 8) * _freqDStep500k;
+                } else {
+                    rxw.Frequency = _freqDBase500k + (_txChannel / 8) * _freqDStep500k;
+                }
+            } else
+                rxw.Frequency = _freqDBase500k + (_txChannel - _numChans125k) * _freqDStep500k;
+
+            if (GetSettings()->Session.TxDatarate <= DR_6) {
+                index = GetSettings()->Session.TxDatarate + 10 - GetSettings()->Session.Rx1DatarateOffset;
+
+                if (index < DR_8)
+                    index = DR_8;
+                if (index > DR_13)
+                    index = DR_13;
+            } else if (GetSettings()->Session.TxDatarate >= DR_8) {
+                index = GetSettings()->Session.TxDatarate - GetSettings()->Session.Rx1DatarateOffset;
+                if (index < DR_8)
+                    index = DR_8;
+            }
+        } else {
+            if (GetSettings()->Network.Mode == PUBLIC) {
+                rxw.Frequency = GetSettings()->Session.Rx2Frequency;
+            } else {
+                if (_txChannel < 64)
+                    rxw.Frequency = _freqDBase500k + (_txChannel / 8) * _freqDStep500k;
+                else
+                    rxw.Frequency = _freqDBase500k + (_txChannel % 8) * _freqDStep500k;
+            }
+            index = GetSettings()->Session.Rx2DatarateIndex;
+        }
+    }
+
+    rxw.DatarateIndex = index;
+
+    return rxw;
+}
+
+uint8_t ChannelPlan_US915::HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+    status = 0x07;
+    int8_t datarate = 0;
+    int8_t drOffset = 0;
+    uint32_t freq = 0;
+
+    drOffset = payload[index++];
+    datarate = drOffset & 0x0F;
+    drOffset = (drOffset >> 4) & 0x07;
+
+    freq = payload[index++];
+    freq |= payload[index++] << 8;
+    freq |= payload[index++] << 16;
+    freq *= 100;
+
+    if (!CheckRfFrequency(freq)) {
+        logInfo("Freq KO");
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (datarate < _minRx2Datarate || datarate > _maxRx2Datarate) {
+        logInfo("DR KO");
+        status &= 0xFD; // Datarate KO
+    }
+
+    if (drOffset < 0 || drOffset > _maxDatarateOffset) {
+        logInfo("DR Offset KO");
+        status &= 0xFB; // Rx1DrOffset range KO
+    }
+
+    if ((status & 0x07) == 0x07) {
+        logInfo("RxParamSetup accepted Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+        SetRx2DatarateIndex(datarate);
+        SetRx2Frequency(freq);
+        SetRx1Offset(drOffset);
+    } else {
+        logInfo("RxParamSetup rejected Rx2DR: %d Rx2Freq: %d Rx1Offset: %d", datarate, freq, drOffset);
+    }
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    // Not Supported in US915
+    status = 0;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    lora::CopyFreqtoInt(payload + index, _beaconRxChannel.Frequency);
+    index += 3;
+
+    if (_beaconRxChannel.Frequency != 0) {
+        _beaconRxChannel.DrRange.Value = payload[index];
+    } else {
+        // TODO: set to default beacon rx channel
+    }
+
+    status = 0x03;
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    status = 0x03;
+    Channel chParam;
+
+    // Skip channel index
+    index++;
+
+    lora::CopyFreqtoInt(payload + index, chParam.Frequency);
+    index += 3;
+    chParam.DrRange.Value = payload[index++];
+
+    if (!GetRadio()->CheckRfFrequency(chParam.Frequency)) {
+        status &= 0xFE; // Channel frequency KO
+    }
+
+    if (chParam.DrRange.Fields.Min < chParam.DrRange.Fields.Max) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Min < _minDatarate || chParam.DrRange.Fields.Min > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    } else if (chParam.DrRange.Fields.Max < _minDatarate || chParam.DrRange.Fields.Max > _maxDatarate) {
+        status &= 0xFD; // Datarate range KO
+    }
+
+    if ((status & 0x03) == 0x03) {
+        _beaconChannel = chParam;
+    }
+
+    if (_beaconChannel.Frequency == 0) {
+        // TODO: Set to default
+    }
+
+    status = 0x01;
+
+    return LORA_OK;
+}
+
+
+uint8_t ChannelPlan_US915::HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status) {
+
+    uint8_t power = 0;
+    uint8_t datarate = 0;
+    uint16_t mask = 0;
+    uint8_t ctrl = 0;
+    uint8_t nbRep = 0;
+
+    status = 0x07;
+    datarate = payload[index++];
+    power = datarate & 0x0F;
+    datarate = (datarate >> 4) & 0x0F;
+
+    mask = payload[index++];
+    mask |= payload[index++] << 8;
+
+    nbRep = payload[index++];
+    ctrl = (nbRep >> 4) & 0x07;
+    nbRep &= 0x0F;
+
+    if (nbRep == 0) {
+        nbRep = 1;
+    }
+
+    if (datarate > _maxDatarate) {
+        status &= 0xFD; // Datarate KO
+    }
+    //
+    // Remark MaxTxPower = 0 and MinTxPower = 10
+    //
+    if (power < 0 || power > 10) {
+        status &= 0xFB; // TxPower KO
+    }
+
+    switch (ctrl) {
+        case 0:
+        case 1:
+        case 2:
+        case 3:
+        case 4:
+            SetChannelMask(ctrl, mask);
+            break;
+
+        case 6:
+            // enable all 125 kHz channels
+            SetChannelMask(0, 0xFFFF);
+            SetChannelMask(1, 0xFFFF);
+            SetChannelMask(2, 0xFFFF);
+            SetChannelMask(3, 0xFFFF);
+            SetChannelMask(4, mask);
+            break;
+
+        case 7:
+            // disable all 125 kHz channels
+            SetChannelMask(0, 0x0);
+            SetChannelMask(1, 0x0);
+            SetChannelMask(2, 0x0);
+            SetChannelMask(3, 0x0);
+            SetChannelMask(4, mask);
+            break;
+
+        default:
+            logWarning("rejecting RFU or unknown control value %d", ctrl);
+            status &= 0xFE; // ChannelMask KO
+            return LORA_ERROR;
+    }
+
+    if (GetSettings()->Network.ADREnabled) {
+        GetSettings()->Session.TxDatarate = datarate;
+        GetSettings()->Session.TxPower = TX_POWERS[power];
+        GetSettings()->Session.Redundancy = nbRep;
+    } else {
+        logDebug("ADR is disabled, DR and Power not changed.");
+        status &= 0xFB; // TxPower KO
+        status &= 0xFD; // Datarate KO
+    }
+
+    logDebug("ADR DR: %u PWR: %u Ctrl: %02x Mask: %04x NbRep: %u Stat: %02x", datarate, power, ctrl, mask, nbRep, status);
+
+    return LORA_OK;
+}
+
+uint8_t ChannelPlan_US915::ValidateAdrConfiguration() {
+    uint8_t status = 0x07;
+    uint8_t chans_enabled = 0;
+    uint8_t datarate = GetSettings()->Session.TxDatarate;
+    uint8_t power = GetSettings()->Session.TxPower;
+
+    if (!GetSettings()->Network.ADREnabled) {
+        logDebug("ADR disabled - no applied changes to validate");
+        return status;
+    }
+
+    if (datarate > _maxDatarate) {
+        logWarning("ADR Datarate KO - outside allowed range");
+        status &= 0xFD; // Datarate KO
+    }
+    if (power < _minTxPower || power > _maxTxPower) {
+        logWarning("ADR TX Power KO - outside allowed range");
+        status &= 0xFB; // TxPower KO
+    }
+    
+    // at least 2 125kHz channels must be enabled
+    chans_enabled += CountBits(_channelMask[0]);
+    chans_enabled += CountBits(_channelMask[1]);
+    chans_enabled += CountBits(_channelMask[2]);
+    chans_enabled += CountBits(_channelMask[3]);
+    // Semtech reference (LoRaMac-node) enforces at least 2 channels
+    if (chans_enabled < 2) {
+        logWarning("ADR Channel Mask KO - at least 2 125kHz channels must be enabled");
+        status &= 0xFE; // ChannelMask KO
+    }
+    
+    // if TXDR == 4 (SF8@500kHz) at least 1 500kHz channel must be enabled
+    if (datarate == DR_4 && (CountBits(_channelMask[4] & 0xFF) == 0)) {
+        logWarning("ADR Datarate KO - DR4 requires at least 1 500kHz channel enabled");
+        status &= 0xFD; // Datarate KO
+    }
+
+    return status;
+}
+
+uint32_t ChannelPlan_US915::GetTimeOffAir()
+{
+    if (GetSettings()->Test.DisableDutyCycle == lora::ON)
+        return 0;
+
+    uint32_t min = 0;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    if (GetSettings()->Session.AggregatedTimeOffEnd > 0 && GetSettings()->Session.AggregatedTimeOffEnd > now) {
+        min = std::max < uint32_t > (min, GetSettings()->Session.AggregatedTimeOffEnd - now);
+    }
+
+    now = time(NULL);
+    uint32_t join_time = 0;
+
+    if (GetSettings()->Session.JoinFirstAttempt != 0 && now < GetSettings()->Session.JoinTimeOffEnd) {
+        join_time = (GetSettings()->Session.JoinTimeOffEnd - now) * 1000;
+    }
+
+    min = std::max < uint32_t > (join_time, min);
+
+    return min;
+}
+
+std::vector<uint32_t> lora::ChannelPlan_US915::GetChannels() {
+    std::vector < uint32_t > chans;
+
+    if (GetSettings()->Network.FrequencySubBand > 0) {
+        uint8_t chans_per_group = 8;
+        size_t start = (GetSettings()->Network.FrequencySubBand - 1) * chans_per_group;
+        for (int8_t i = start; i < int8_t(start + chans_per_group); i++) {
+            chans.push_back(GetChannel(i).Frequency);
+        }
+        chans.push_back(GetChannel(_numChans125k + (GetSettings()->Network.FrequencySubBand - 1)).Frequency);
+        chans.push_back(GetRxWindow(2).Frequency);
+    } else {
+        for (int8_t i = 0; i < _numChans; i++) {
+            chans.push_back(GetChannel(i).Frequency);
+        }
+        chans.push_back(GetRxWindow(2).Frequency);
+    }
+
+    return chans;
+}
+
+std::vector<uint8_t> lora::ChannelPlan_US915::GetChannelRanges() {
+    std::vector < uint8_t > ranges;
+
+    if (GetSettings()->Network.FrequencySubBand > 0) {
+        uint8_t chans_per_group = 8;
+        size_t start = (GetSettings()->Network.FrequencySubBand - 1) * chans_per_group;
+        for (int8_t i = start; i < int8_t(start + chans_per_group); i++) {
+            ranges.push_back(GetChannel(i).DrRange.Value);
+        }
+        ranges.push_back(GetChannel(_numChans125k + (GetSettings()->Network.FrequencySubBand - 1)).DrRange.Value);
+        ranges.push_back(GetRxWindow(2).DatarateIndex);
+    } else {
+        for (int8_t i = 0; i < _numChans; i++) {
+            ranges.push_back(GetChannel(i).DrRange.Value);
+        }
+        ranges.push_back(GetRxWindow(2).DatarateIndex);
+    }
+
+    ranges.push_back(GetRxWindow(2).DatarateIndex);
+
+    return ranges;
+
+}
+
+void lora::ChannelPlan_US915::EnableDefaultChannels() {
+    SetFrequencySubBand(GetFrequencySubBand());
+}
+
+uint8_t ChannelPlan_US915::GetNextChannel()
+{
+    if (GetSettings()->Session.AggregatedTimeOffEnd != 0) {
+        return LORA_AGGREGATED_DUTY_CYCLE;
+    }
+
+    if (P2PEnabled() || GetSettings()->Network.TxFrequency != 0) {
+        logDebug("Using frequency %d", GetSettings()->Network.TxFrequency);
+
+        if (GetSettings()->Test.DisableDutyCycle != lora::ON) {
+            int8_t band = GetDutyBand(GetSettings()->Network.TxFrequency);
+            logDebug("band: %d freq: %d", band, GetSettings()->Network.TxFrequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd != 0) {
+                return LORA_NO_CHANS_ENABLED;
+            }
+        }
+
+        GetRadio()->SetChannel(GetSettings()->Network.TxFrequency);
+        return LORA_OK;
+    }
+
+    uint8_t start = 0;
+    uint8_t maxChannels = _numChans125k;
+    uint8_t nbEnabledChannels = 0;
+    uint8_t *enabledChannels = new uint8_t[maxChannels];
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        maxChannels = _numChans500k;
+        start = _numChans125k;
+    }
+
+// Search how many channels are enabled
+    DatarateRange range;
+    uint8_t dr_index = GetSettings()->Session.TxDatarate;
+    uint32_t now = _dutyCycleTimer.read_ms();
+
+    for (size_t i = 0; i < _dutyBands.size(); i++) {
+        if (_dutyBands[i].TimeOffEnd < now || GetSettings()->Test.DisableDutyCycle == lora::ON) {
+            _dutyBands[i].TimeOffEnd = 0;
+        }
+    }
+
+    for (uint8_t i = start; i < start + maxChannels; i++) {
+        range = GetChannel(i).DrRange;
+        // logDebug("chan: %d freq: %d range:%02x", i, GetChannel(i).Frequency, range.Value);
+
+        if (IsChannelEnabled(i) && (dr_index >= range.Fields.Min && dr_index <= range.Fields.Max)) {
+            int8_t band = GetDutyBand(GetChannel(i).Frequency);
+            // logDebug("band: %d freq: %d", band, _channels[i].Frequency);
+            if (band != -1 && _dutyBands[band].TimeOffEnd == 0) {
+                enabledChannels[nbEnabledChannels++] = i;
+            }
+        }
+    }
+
+    if (GetTxDatarate().Bandwidth == BW_500) {
+        _dutyBands[0].PowerMax = 26;
+    } else {
+        if (nbEnabledChannels < 50)
+            _dutyBands[0].PowerMax = 21;
+        else
+            _dutyBands[0].PowerMax = 30;
+    }
+
+    logTrace("Number of available channels: %d", nbEnabledChannels);
+
+    uint32_t freq = 0;
+    uint8_t sf = GetTxDatarate().SpreadingFactor;
+    uint8_t bw = GetTxDatarate().Bandwidth;
+    int16_t thres = DEFAULT_FREE_CHAN_RSSI_THRESHOLD;
+
+    if (nbEnabledChannels == 0) {
+        delete [] enabledChannels;
+        return LORA_NO_CHANS_ENABLED;
+    }
+
+    if (GetSettings()->Network.CADEnabled) {
+        // Search for free channel with ms timeout
+        int16_t timeout = 10000;
+        Timer tmr;
+        tmr.start();
+
+        for (uint8_t j = rand_r(0, nbEnabledChannels - 1); tmr.read_ms() < timeout; j++) {
+            freq = GetChannel(enabledChannels[j]).Frequency;
+
+            if (GetRadio()->IsChannelFree(SxRadio::MODEM_LORA, freq, sf, thres, bw)) {
+                _txChannel = enabledChannels[j];
+                break;
+            }
+        }
+    } else {
+        uint8_t j = rand_r(0, nbEnabledChannels - 1);
+        _txChannel = enabledChannels[j];
+        freq = GetChannel(_txChannel).Frequency;
+    }
+
+    assert(freq != 0);
+
+    logDebug("Using channel %d : %d", _txChannel, freq);
+    GetRadio()->SetChannel(freq);
+
+    delete [] enabledChannels;
+    return LORA_OK;
+}
+
+uint8_t lora::ChannelPlan_US915::GetJoinDatarate() {
+    uint8_t dr = GetSettings()->Session.TxDatarate;
+
+    if (GetSettings()->Test.DisableRandomJoinDatarate == lora::OFF) {
+        static bool altDatarate = false;
+
+        if (GetSettings()->Network.FrequencySubBand == 0) {
+            static uint16_t used_bands_125k = 0;
+            static uint16_t used_bands_500k = 0;
+            uint8_t frequency_sub_band = 0;
+
+            if (altDatarate) {
+                // 500k channel
+                if (CountBits(used_bands_500k) == 8) {
+                    used_bands_500k = 0;
+                }
+                while ((frequency_sub_band = rand_r(1, 8)) && (used_bands_500k & (1 << (frequency_sub_band - 1))) != 0)
+                    ;
+                used_bands_500k |= (1 << (frequency_sub_band - 1));
+            } else {
+                // 125k channel
+                if (CountBits(used_bands_125k) == 8) {
+                    used_bands_125k = 0;
+                }
+                while ((frequency_sub_band = rand_r(1, 8)) && (used_bands_125k & (1 << (frequency_sub_band - 1))) != 0)
+                    ;
+                used_bands_125k |= (1 << (frequency_sub_band - 1));
+            }
+
+            logWarning("JoinDatarate setting frequency sub band to %d 125k: %04x 500k: %04x", frequency_sub_band, used_bands_125k, used_bands_500k);
+            SetFrequencySubBand(frequency_sub_band);
+        }
+
+        if (altDatarate && CountBits(_channelMask[4] > 0)) {
+            dr = lora::DR_4;
+        } else {
+            dr = lora::DR_0;
+        }
+        altDatarate = !altDatarate;
+    }
+
+    return dr;
+}
+
+uint8_t lora::ChannelPlan_US915::CalculateJoinBackoff(uint8_t size) {
+
+    time_t now = time(NULL);
+    uint32_t time_on_max = 0;
+    static uint32_t time_off_max = 15;
+    uint32_t rand_time_off = 0;
+    static uint16_t join_cnt = 0;
+
+    // TODO: calc time-off-max based on RTC time from JoinFirstAttempt, time-off-max is lost over sleep
+
+    if ((time_t)GetSettings()->Session.JoinTimeOffEnd > now) {
+        return LORA_JOIN_BACKOFF;
+    }
+
+    uint32_t secs_since_first_attempt = (now - GetSettings()->Session.JoinFirstAttempt);
+    uint16_t hours_since_first_attempt = secs_since_first_attempt / (60 * 60);
+
+    join_cnt = (join_cnt+1) % 16;
+
+    if (GetSettings()->Session.JoinFirstAttempt == 0) {
+        /* 1 % duty-cycle for first hour
+         * 0.1 % next 10 hours
+         * 0.01 % upto 24 hours         */
+        GetSettings()->Session.JoinFirstAttempt = now;
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + rand_r(GetSettings()->Network.JoinDelay + 2, GetSettings()->Network.JoinDelay + 3);
+    } else if (join_cnt == 0) {
+        if (hours_since_first_attempt < 1) {
+            time_on_max = 36000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 60 * 60;
+            }
+        } else if (hours_since_first_attempt < 11) {
+            if (GetSettings()->Session.JoinTimeOnAir < 36000) {
+                GetSettings()->Session.JoinTimeOnAir = 36000;
+            }
+            time_on_max = 72000;
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+            // time off max 1 hour
+            time_off_max = std::min < uint32_t > (time_off_max * 2, 60 * 60);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + 11 * 60 * 60;
+            }
+        } else {
+            if (GetSettings()->Session.JoinTimeOnAir < 72000) {
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+            }
+            uint32_t join_time = 2500;
+
+            // 16 join attempts is ~2754 ms, check if this is the third of the 24 hour period
+
+            time_on_max = 80700;
+            time_off_max = 1 * 60 * 60; // 1 hour
+            rand_time_off = rand_r(time_off_max - 1, time_off_max + 1);
+
+            if (GetSettings()->Session.JoinTimeOnAir < time_on_max - join_time) {
+                GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+                GetSettings()->Session.JoinTimeOffEnd = now + rand_time_off;
+            } else {
+                logWarning("Max time-on-air limit met for current join backoff period");
+                // Reset the join time on air and set end of restriction to the next 24 hour period
+                GetSettings()->Session.JoinTimeOnAir = 72000;
+                uint16_t days = (now - GetSettings()->Session.JoinFirstAttempt) / (24 * 60 * 60) + 1;
+                logWarning("days : %d", days);
+                GetSettings()->Session.JoinTimeOffEnd = GetSettings()->Session.JoinFirstAttempt + ((days * 24) + 11) * 60 * 60;
+            }
+        }
+
+        logWarning("JoinBackoff: %lu seconds  Time On Air: %lu / %lu", GetSettings()->Session.JoinTimeOffEnd - now, GetSettings()->Session.JoinTimeOnAir, time_on_max);
+    } else {
+        GetSettings()->Session.JoinTimeOnAir += GetTimeOnAir(size);
+        GetSettings()->Session.JoinTimeOffEnd = now + rand_r(GetSettings()->Network.JoinDelay + 2, GetSettings()->Network.JoinDelay + 3);
+    }
+
+    return LORA_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlan_US915.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,251 @@
+/**   __  ___     ____  _    ______        __     ____         __                  ____
+ *   /  |/  /_ __/ / /_(_)__/_  __/__ ____/ /    / __/_ _____ / /____ __ _  ___   /  _/__  ____
+ *  / /|_/ / // / / __/ /___// / / -_) __/ _ \  _\ \/ // (_-</ __/ -_)  ' \(_-<  _/ // _ \/ __/ __
+ * /_/  /_/\_,_/_/\__/_/    /_/  \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/
+ * Copyright (C) 2015 by Multi-Tech Systems        /___/
+ *
+ *
+ * @author Jason Reiss
+ * @date   10-31-2015
+ * @brief  lora::ChannelPlan provides an interface for LoRaWAN channel schemes
+ *
+ * @details
+ *
+ */
+
+#ifndef __CHANNEL_PLAN_US915_H__
+#define __CHANNEL_PLAN_US915_H__
+
+#include "Lora.h"
+#include "SxRadio.h"
+#include "ChannelPlan.h"
+#include <vector>
+
+namespace lora {
+
+    class ChannelPlan_US915 : public lora::ChannelPlan {
+        public:
+
+            /**
+             * ChannelPlan constructor
+             * @param radio SxRadio object used to set Tx/Rx config
+             * @param settings Settings object
+             */
+            ChannelPlan_US915();
+            ChannelPlan_US915(Settings* settings);
+            ChannelPlan_US915(SxRadio* radio, Settings* settings);
+
+            /**
+             * ChannelPlan destructor
+             */
+            virtual ~ChannelPlan_US915();
+
+            /**
+             * Initialize channels, datarates and duty cycle bands according to current channel plan in settings
+             */
+            virtual void Init();
+
+            /**
+             * Get the next channel to use to transmit
+             * @return LORA_OK if channel was found
+             * @return LORA_NO_CHANS_ENABLED
+             */
+            virtual uint8_t GetNextChannel();
+
+            /**
+             * Set the number of channels in the plan
+             */
+            virtual void SetNumberOfChannels(uint8_t channels, bool resize = true);
+
+            /**
+             * Check if channel is enabled
+             * @return true if enabled
+             */
+            virtual bool IsChannelEnabled(uint8_t channel);
+
+
+            /**
+             * Add a channel to the ChannelPlan
+             * @param index of channel, use -1 to add to end
+             * @param channel settings to add
+             */
+            virtual uint8_t AddChannel(int8_t index, Channel channel);
+
+            /**
+             * Get channel at index
+             * @return Channel
+             */
+            virtual Channel GetChannel(int8_t index);
+
+            /**
+             * Get rx window settings for requested window
+             * RX_1, RX_2, RX_BEACON, RX_SLOT
+             * @param window
+             * @return RxWindow
+             */
+            virtual RxWindow GetRxWindow(uint8_t window);
+
+            /**
+             * Get datarate to use on the join request
+             * @return datarate index
+             */
+            virtual uint8_t GetJoinDatarate();
+
+            /**
+             * Calculate the next time a join request is possible
+             * @param size of join frame
+             * @returns LORA_OK
+             */
+            virtual uint8_t CalculateJoinBackoff(uint8_t size);
+
+            /**
+             * Set the datarate offset used for first receive window
+             * @param offset
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx1Offset(uint8_t offset);
+
+            /**
+             * Set the frequency for second receive window
+             * @param freq
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx2Frequency(uint32_t freq);
+
+            /**
+             * Set the datarate index used for second receive window
+             * @param index
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRx2DatarateIndex(uint8_t index);
+
+            /**
+             * Get next channel and set the SxRadio tx config with current settings
+             * @return LORA_OK
+             */
+            virtual uint8_t SetTxConfig();
+
+            /**
+             * Set the SxRadio rx config provided window
+             * @param window to be opened
+             * @param continuous keep window open
+             * @return LORA_OK
+             */
+            virtual uint8_t SetRxConfig(uint8_t window, bool continuous);
+
+            /**
+             * Set frequency sub band if supported by plan
+             * @param sub_band
+             * @return LORA_OK
+             */
+            virtual uint8_t SetFrequencySubBand(uint8_t sub_band);
+
+            /**
+             * Callback for Join Accept packet to load optional channels
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleJoinAccept(const uint8_t* buffer, uint8_t size);
+
+            /**
+             * Callback to for rx parameter setup ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleRxParamSetup(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for new channel ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleNewChannel(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for ping slot channel request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandlePingSlotChannelReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for beacon frequency request ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleBeaconFrequencyReq(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Callback to for adaptive datarate ServerCommand
+             * @param payload packet data
+             * @param index of start of command buffer
+             * @param size number of bytes in command buffer
+             * @param[out] status to be returned in MoteCommand answer
+             * @return LORA_OK
+             */
+            virtual uint8_t HandleAdrCommand(const uint8_t* payload, uint8_t index, uint8_t size, uint8_t& status);
+
+            /**
+             * Validate the configuration after multiple ADR commands have been applied
+             * @return status to be returned in MoteCommand answer
+             */
+            virtual uint8_t ValidateAdrConfiguration();
+
+            /**
+             * Get the time the radio must be off air to comply with regulations
+             * Time to wait may be dependent on duty-cycle restrictions per channel
+             * Or duty-cycle of join requests if OTAA is being attempted
+             * @return ms of time to wait for next tx opportunity
+             */
+            virtual uint32_t GetTimeOffAir();
+
+            /**
+             * Get the channels in use by current channel plan
+             * @return channel frequencies
+             */
+            virtual std::vector<uint32_t> GetChannels();
+
+            /**
+             * Get the channel datarate ranges in use by current channel plan
+             * @return channel datarate ranges
+             */
+            virtual std::vector<uint8_t> GetChannelRanges();
+
+
+            /**
+             * Print log message for given rx window
+             * @param wnd 1 or 2
+             */
+            virtual void LogRxWindow(uint8_t wnd);
+
+            /**
+             * Enable the default channels of the channel plan
+             */
+            virtual void EnableDefaultChannels();
+
+            virtual uint8_t GetMinDatarate();
+
+            virtual uint8_t GetMaxDatarate();
+
+        protected:
+
+            static const uint8_t US915_TX_POWERS[11];                   //!< List of available tx powers
+            static const uint8_t US915_RADIO_POWERS[21];                //!< List of calibrated tx powers
+            static const uint8_t US915_MAX_PAYLOAD_SIZE[];              //!< List of max payload sizes for each datarate
+            static const uint8_t US915_MAX_PAYLOAD_SIZE_REPEATER[];     //!< List of repeater compatible max payload sizes for each datarate
+
+    };
+}
+
+#endif // __CHANNEL_PLAN_US915_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/ChannelPlans.h	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,16 @@
+#include "ChannelPlan_AS923.h"
+#include "ChannelPlan_AU915.h"
+#include "ChannelPlan_US915.h"
+#include "ChannelPlan_EU868.h"
+#include "ChannelPlan_KR920.h"
+#include "ChannelPlan_IN865.h"
+#include "ChannelPlan_AS923_Japan.h"
+
+
+#define CP_AS923 1
+#define CP_AU915 2
+#define CP_US915 3
+#define CP_EU868 4
+#define CP_KR920 5
+#define CP_IN865 6
+#define CP_AS923_JAPAN 7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plans/LICENSE.txt	Thu Jul 27 10:43:57 2017 -0500
@@ -0,0 +1,89 @@
+IMPORTANT – READ BEFORE OPERATING OR INSTALLING THE MULTI-TECH PRODUCT OR SOFTWARE
+
+MULTI-TECH SYSTEMS, INC.
+END USER LICENSE AGREEMENT
+PLEASE READ THIS END USER LICENSE AGREEMENT (“AGREEMENT”) CAREFULLY BEFORE USING THE MULTI-TECH PRODUCT, INSTALLING OR ACCESSING THE SOFTWARE, OR DOWNLOADING ANY SOFTWARE UPDATES FOR USE WITH THE MULTI-TECH PRODUCT. BY USING THE MULTI-TECH PRODUCT, INSTALLING OR ACCESSING THE SOFTWARE OR DOWNLOADING SOFTWARE UPDATES FOR THE MULTI-TECH PRODUCT, YOU AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. IF YOU DO NOT AGREE TO THE TERMS OF THIS AGREEMENT, DO NOT USE THE MULTI-TECH PRODUCT, INSTALL OR ACCESS THE SOFTWARE, OR DOWNLOAD THE SOFTWARE UPDATES. INSTEAD, PLEASE CONTACT MULTI-TECH’S CUSTOMER SERVICE DEPARTMENT AT customerservice@multitech.com. 
+
+MULTITECH SOFTWARE LICENSE AGREEMENT
+This Software License Agreement ("Agreement") is entered into by and between Multi-Tech Systems, Inc. ("MultiTech") and the business
+entity identified as the customer below ("Customer"). For purposes of this Agreement, “Software” means the software modules provided
+by MultiTech to Customer as more specifically identified below. Software may be provided in binary, object code, or source code formats,
+and includes any updates or upgrades to or new version of the original software, if and when made available to Customer by MultiTech.
+
+1. LICENSE GRANT. Subject to the terms of this Agreement, MultiTech grants to Customer, during the Term, a worldwide, revocable,
+non-exclusive, non-transferable, non-sublicensable, royalty-free limited license to: (a) use and reproduce the Software, and modify the
+Software modules for which source code is provided, solely in conjunction with MultiTech’s products; (b) reproduce, have reproduced,
+display, perform, transmit, license and distribute the Software in binary or executable form, solely as incorporated or embedded into
+MultiTech’s products.
+Use of the Software in conjunction with any product other than MultiTech’s products is strictly prohibited. In no event may Customer (i)
+distribute, license or sell the Software or any modifications thereof as a standalone product; (ii) decrypt, disassemble, reverse assemble
+or reverse compile the Software, except to the extent such restrictions are prohibited by applicable law; and/or (iii) distribute the source
+code of the Software and/or modifications thereof.
+
+2. RESTRICTIONS ON USE. Customer acknowledges that the Software and the structure, organization, and source code thereof
+constitute valuable trade secrets of MultiTech. Accordingly, except as expressly permitted in Section 1 or as otherwise authorized by
+MultiTech in writing, Customer will not, directly or indirectly: (a) modify, adapt, alter, translate, or create derivative works from the
+Software; (b) sublicense, lease, rent, loan, sell, distribute, make available or otherwise transfer the Software to any third party; (c) reverse
+engineer, decompile, disassemble, or otherwise attempt to derive the source code for the Software; or (d) otherwise use or copy the
+Software except as expressly allowed under Section 1 above. Customer may not disclose to third parties or through publication the
+results of performance/benchmark tests run on the Software without the prior written consent of MultiTech.
+
+3. OWNERSHIP. As between the parties, the Software and all modifications and improvements to the Software, and all worldwide
+intellectual property rights and proprietary rights relating thereto or embodied therein, are the exclusive property of MultiTech and its
+suppliers. MultiTech and its suppliers reserve all rights in and to the Software not expressly granted to Customer in Section 1, and no
+other licenses or rights are granted by implication, estoppel or otherwise.
+
+4. TERM AND TERMINATION. This Agreement is effective upon acceptance by Customer and shall continue until terminated by
+MultiTech for any reason whatsoever upon thirty (30) days’ notice. Customer may terminate this Agreement at any time by destroying
+the Software and notifying MultiTech at: sales@multitech.com. If Customer breaches any provision of this Agreement, this Agreement
+will automatically terminate. Upon the termination of this Agreement, the license granted to Customer will terminate. The provisions of
+Sections 2, 3, 4, 5, 6, 7, 8, and 9 shall survive termination or expiration of this Agreement for any reason.
+
+5. CONFIDENTIALITY. MultiTech may disclose certain information regarding the business of MultiTech and its suppliers, including the
+Software and technical, marketing, financial, employee, planning, and other confidential or proprietary information of MultiTech or its
+suppliers ("Confidential Information"). Any information that Customer knew or should have known, under the circumstances, was
+considered confidential or proprietary by MultiTech will be considered Confidential Information. Customer agrees (a) not to disclose
+Confidential information to any persons outside its organization, except to its consultants or agents who agree in writing to protect such
+Confidential information as required herein; and (b) to use the Confidential information only for the purpose of evaluating the Software.
+
+6. DISCLAIMER. CUSTOMER ACKNOWLEDGES AND AGREES THAT CUSTOMER'S USE OF THE SOFTWARE IS ENTIRELY AT
+ITS OWN RISK AND THE SOFTWARE PROVIDED BY MULTITECH TO CUSTOMER IS PROVIDED "AS IS" WITHOUT ANY
+WARRANTY OF ANY KIND WHATSOEVER. MULTITECH, ON BEHALF OF ITSELF AND ITS SUPPLIERS, HEREBY EXPRESSLY
+DISCLAIMS ALL WARRANTIES WITH REGARD TO THE SOFTWARE, WHETHER EXPRESS, IMPLIED, STATUTORY OR
+OTHERWISE, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT AND ANY WARRANTIES ARISING FROM COURSE OF DEALING OR
+COURSE OF PERFORMANCE. TO THE EXTENT THAT, AS A MATTER OF APPLICABLE LAW, ANY IMPLIED OR STATUTORY 
+WARRANTY MAY NOT BE DISCLAIMED, THE DURATION AND SCOPE OF SUCH WARRANTY SHALL BE THE MINIMUM
+PERMISSIBLE UNDER SUCH APPLICABLE LAW.
+
+7. LIMITATION OF LIABILITY. IN NO EVENT WILL MULTITECH BE LIABLE FOR ANY CONSEQUENTIAL, INDIRECT, EXEMPLARY,
+PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR
+RELATING TO THE SOFTWARE OR THIS AGREEMENT, EVEN IF MULTITECH HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES. MULTITECH’S TOTAL CUMULATIVE LIABILITY IN CONNECTION WITH THIS AGREEMENT AND THE
+SOFTWARE, WHETHER IN CONTRACT OR TORT OR OTHERWISE, WILL NOT EXCEED $50. CUSTOMER ACKNOWLEDGES
+THAT THIS PROVISION REFLECTS THE AGREED UPON ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT MULTITECH
+WOULD NOT ENTER INTO THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY.
+
+8. COMPLIANCE WITH LAWS. Customer shall comply with all laws, regulations, rules, ordinances and orders applicable to its use of
+the Software. Without limiting the foregoing, Customer shall comply with the relevant export administration and control laws and
+regulations, as may be amended from time to time, including, without limitation, the United States Export Administration Act, to ensure
+that the Software is not shipped, transferred or exported (directly or indirectly) in violation of U.S. law.
+
+9. MISCELLANEOUS. Customer may not assign or delegate, directly or indirectly, by operation of law or otherwise, this Agreement or
+any of its rights or obligations under this Agreement (including the license rights granted to Customer to the Software) to any third party.
+Any attempted assignment or transfer in violation of the foregoing will be null and void and of no effect. This Agreement will be subject
+to and governed by the laws of the State of Minnesota and the United States of America without regard for its conflicts of law principles
+that would require application of the laws of a different state or country. The federal and state courts for Minneapolis, MN shall have
+jurisdiction over any disputes, claims or controversies arising out of or relating to this Agreement, and Customer hereby consents the
+jurisdiction of such courts over any such dispute, claim or controversy. All modifications, waivers and amendments must be in writing
+and signed by both parties. Any waiver or failure to enforce any provision of this Agreement on one occasion will not be deemed a waiver
+of any other provision or of such provision on any other occasion. If any provision of this Agreement is held by a court of competent
+jurisdiction to be unenforceable for any reason, the remaining provisions hereof shall be unaffected and continue in full force and effect.
+This Agreement constitutes the entire agreement between the parties regarding the subject hereof and supersedes all prior or
+contemporaneous agreements, understandings and communications, whether written or oral.
+
+   Multi-Tech Systems, Inc. 
+   2205 Woodale Drive
+   Mounds View, Minnesota 55112
+   customerservice@multitech.com 
+   20130910