Bleeding edge development version of the mDot library for mbed 5. This version of the library is not guaranteed to be stable or well tested and should not be used in production or deployment scenarios.
Dependents: mDot-IKS01A1 mDot-IKS01A1 mDot-Examples mDot-IKS01A1-Explora ... more
Fork of libmDot-dev-mbed2-deprecated by
The Dot library provides a LoRaWan certified stack for LoRa communication using MultiTech mDot and xDot devices. The stack is compatible with mbed 5.
Dot Library Version 3 Updates
Dot Library versions 3.x.x require a channel plan to be injected into the stack. Channel plans are included with the 3.x.x Dot Library releases. The following code snippet demonstrates how to create a channel plan and inject it into the stack.
#include "mDot.h" #include "channel_plans.h" int main() { ChannelPlan* plan = new lora::ChannelPlan_US915(); assert(plan); mDot* dot = mDot::getInstance(plan); assert(dot); // ... }
Dot devices must not be deployed with software using a different channel plan than the Dot's default plan! This functionality is for development and testing only!
Multicast Sessions
Multicast sessions and packet rx events in library. When in Class C mode Multicast downlinks can be received. Recieved packets should be filtered on address, counter value will be maintained in the session or can be set explicitly depending on Application support to share Multicast Address, Keys and Counters.
mDot.h
/** * Add a multicast session address and keys * Downlink counter is set to 0 * Up to 3 MULTICAST_SESSIONS can be set */ int32_t setMulticastSession(uint8_t index, uint32_t addr, const uint8_t* nsk, const uint8_t* dsk); /** * Set a multicast session counter * Up to 3 MULTICAST_SESSIONS can be set */ int32_t setMulticastDownlinkCounter(uint8_t index, uint32_t count);
mDotEvent.h
The address field was added to PacketRx event.
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);
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:
- libmDot-mbed5 -> production-ready build for mDot
- libmDot-dev-mbed5 -> development build for mDot
- libxDot-mbed5 -> production-ready build for xDot
- libxDot-dev-mbed5 -> development build for xDot
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.
Lora.h@16:b630e18103e5, 2016-08-04 (annotated)
- Committer:
- Mike Fiore
- Date:
- Thu Aug 04 15:11:24 2016 -0500
- Revision:
- 16:b630e18103e5
- Child:
- 17:306ffaa5d79b
update from libmDot-2.0.1-ARMCC.tar.gz
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
Mike Fiore |
16:b630e18103e5 | 1 | /** __ ___ ____ _ ______ __ ____ __ ____ |
Mike Fiore |
16:b630e18103e5 | 2 | * / |/ /_ __/ / /_(_)__/_ __/__ ____/ / / __/_ _____ / /____ __ _ ___ / _/__ ____ |
Mike Fiore |
16:b630e18103e5 | 3 | * / /|_/ / // / / __/ /___// / / -_) __/ _ \ _\ \/ // (_-</ __/ -_) ' \(_-< _/ // _ \/ __/ __ |
Mike Fiore |
16:b630e18103e5 | 4 | * /_/ /_/\_,_/_/\__/_/ /_/ \__/\__/_//_/ /___/\_, /___/\__/\__/_/_/_/___/ /___/_//_/\__/ /_/ |
Mike Fiore |
16:b630e18103e5 | 5 | * Copyright (C) 2015 by Multi-Tech Systems /___/ |
Mike Fiore |
16:b630e18103e5 | 6 | * |
Mike Fiore |
16:b630e18103e5 | 7 | * |
Mike Fiore |
16:b630e18103e5 | 8 | * @author Jason Reiss |
Mike Fiore |
16:b630e18103e5 | 9 | * @date 10-31-2015 |
Mike Fiore |
16:b630e18103e5 | 10 | * @brief lora namespace defines global settings, structures and enums for the lora library |
Mike Fiore |
16:b630e18103e5 | 11 | * |
Mike Fiore |
16:b630e18103e5 | 12 | * @details |
Mike Fiore |
16:b630e18103e5 | 13 | * |
Mike Fiore |
16:b630e18103e5 | 14 | * |
Mike Fiore |
16:b630e18103e5 | 15 | * |
Mike Fiore |
16:b630e18103e5 | 16 | */ |
Mike Fiore |
16:b630e18103e5 | 17 | |
Mike Fiore |
16:b630e18103e5 | 18 | #ifndef __LORA_H__ |
Mike Fiore |
16:b630e18103e5 | 19 | #define __LORA_H__ |
Mike Fiore |
16:b630e18103e5 | 20 | |
Mike Fiore |
16:b630e18103e5 | 21 | #include "mbed.h" |
Mike Fiore |
16:b630e18103e5 | 22 | #include <assert.h> |
Mike Fiore |
16:b630e18103e5 | 23 | #include "MTSLog.h" |
Mike Fiore |
16:b630e18103e5 | 24 | //#include <cstring> |
Mike Fiore |
16:b630e18103e5 | 25 | #include <inttypes.h> |
Mike Fiore |
16:b630e18103e5 | 26 | |
Mike Fiore |
16:b630e18103e5 | 27 | namespace lora { |
Mike Fiore |
16:b630e18103e5 | 28 | |
Mike Fiore |
16:b630e18103e5 | 29 | /** |
Mike Fiore |
16:b630e18103e5 | 30 | * Frequency bandwidth of a Datarate, higher bandwidth gives higher datarate |
Mike Fiore |
16:b630e18103e5 | 31 | */ |
Mike Fiore |
16:b630e18103e5 | 32 | enum Bandwidth { |
Mike Fiore |
16:b630e18103e5 | 33 | BW_125, |
Mike Fiore |
16:b630e18103e5 | 34 | BW_250, |
Mike Fiore |
16:b630e18103e5 | 35 | BW_500, |
Mike Fiore |
16:b630e18103e5 | 36 | BW_FSK = 50 |
Mike Fiore |
16:b630e18103e5 | 37 | }; |
Mike Fiore |
16:b630e18103e5 | 38 | |
Mike Fiore |
16:b630e18103e5 | 39 | /** |
Mike Fiore |
16:b630e18103e5 | 40 | * Spreading factor of a Datarate, lower spreading factor gives higher datarate |
Mike Fiore |
16:b630e18103e5 | 41 | */ |
Mike Fiore |
16:b630e18103e5 | 42 | enum SpreadingFactors { |
Mike Fiore |
16:b630e18103e5 | 43 | SF_6 = 6, |
Mike Fiore |
16:b630e18103e5 | 44 | SF_7, |
Mike Fiore |
16:b630e18103e5 | 45 | SF_8, |
Mike Fiore |
16:b630e18103e5 | 46 | SF_9, |
Mike Fiore |
16:b630e18103e5 | 47 | SF_10, |
Mike Fiore |
16:b630e18103e5 | 48 | SF_11, |
Mike Fiore |
16:b630e18103e5 | 49 | SF_12, |
Mike Fiore |
16:b630e18103e5 | 50 | SF_FSK, |
Mike Fiore |
16:b630e18103e5 | 51 | SF_INVALID |
Mike Fiore |
16:b630e18103e5 | 52 | }; |
Mike Fiore |
16:b630e18103e5 | 53 | |
Mike Fiore |
16:b630e18103e5 | 54 | /** |
Mike Fiore |
16:b630e18103e5 | 55 | * Datarates for use with ChannelPlan |
Mike Fiore |
16:b630e18103e5 | 56 | */ |
Mike Fiore |
16:b630e18103e5 | 57 | enum Datarates { |
Mike Fiore |
16:b630e18103e5 | 58 | DR_0 = 0, |
Mike Fiore |
16:b630e18103e5 | 59 | DR_1, |
Mike Fiore |
16:b630e18103e5 | 60 | DR_2, |
Mike Fiore |
16:b630e18103e5 | 61 | DR_3, |
Mike Fiore |
16:b630e18103e5 | 62 | DR_4, |
Mike Fiore |
16:b630e18103e5 | 63 | DR_5, |
Mike Fiore |
16:b630e18103e5 | 64 | DR_6, |
Mike Fiore |
16:b630e18103e5 | 65 | DR_7, |
Mike Fiore |
16:b630e18103e5 | 66 | DR_8, |
Mike Fiore |
16:b630e18103e5 | 67 | DR_9, |
Mike Fiore |
16:b630e18103e5 | 68 | DR_10, |
Mike Fiore |
16:b630e18103e5 | 69 | DR_11, |
Mike Fiore |
16:b630e18103e5 | 70 | DR_12, |
Mike Fiore |
16:b630e18103e5 | 71 | DR_13, |
Mike Fiore |
16:b630e18103e5 | 72 | DR_14, |
Mike Fiore |
16:b630e18103e5 | 73 | DR_15 |
Mike Fiore |
16:b630e18103e5 | 74 | }; |
Mike Fiore |
16:b630e18103e5 | 75 | |
Mike Fiore |
16:b630e18103e5 | 76 | const uint8_t MIN_DATARATE = (uint8_t) DR_0; //!< Minimum datarate |
Mike Fiore |
16:b630e18103e5 | 77 | |
Mike Fiore |
16:b630e18103e5 | 78 | |
Mike Fiore |
16:b630e18103e5 | 79 | const uint8_t MAX_PHY_PACKET_SIZE = 255; //!< Maximum size for a packet |
Mike Fiore |
16:b630e18103e5 | 80 | const uint8_t MAX_APPS = 8; //!< Maximum number of apps sessions to save |
Mike Fiore |
16:b630e18103e5 | 81 | const uint8_t MAX_MULTICAST_SESSIONS = 8; //!< Maximum number of multicast sessions to save |
Mike Fiore |
16:b630e18103e5 | 82 | const uint8_t EUI_SIZE = 8; //!< Number of bytes in an EUI |
Mike Fiore |
16:b630e18103e5 | 83 | const uint8_t KEY_SIZE = 16; //!< Number of bytes in an AES key |
Mike Fiore |
16:b630e18103e5 | 84 | |
Mike Fiore |
16:b630e18103e5 | 85 | const uint8_t DEFAULT_NUM_CHANNELS = 16; //!< Default number of channels in a plan |
Mike Fiore |
16:b630e18103e5 | 86 | const uint8_t DEFAULT_RX1_DR_OFFSET = 0; //!< Default datarate offset for first rx window |
Mike Fiore |
16:b630e18103e5 | 87 | const uint8_t DEFAULT_RX2_DATARATE = 0; //!< Default datarate for second rx window |
Mike Fiore |
16:b630e18103e5 | 88 | const uint8_t DEFAULT_TX_POWER = 14; //!< Default transmit power |
Mike Fiore |
16:b630e18103e5 | 89 | const uint8_t DEFAULT_CODE_RATE = 1; //!< Default coding rate 1:4/5, 2:4/6, 3:4/7, 4:4/8 |
Mike Fiore |
16:b630e18103e5 | 90 | const uint8_t DEFAULT_PREAMBLE_LEN = 8; //!< Default preamble length |
Mike Fiore |
16:b630e18103e5 | 91 | |
Mike Fiore |
16:b630e18103e5 | 92 | const int32_t MAX_FCNT_GAP = 16384; //!< Maximum allowed gap in sequence numbers before roll-over |
Mike Fiore |
16:b630e18103e5 | 93 | |
Mike Fiore |
16:b630e18103e5 | 94 | const uint16_t PRIVATE_JOIN_DELAY = 1000; //!< Default join delay used for private network |
Mike Fiore |
16:b630e18103e5 | 95 | const uint16_t PUBLIC_JOIN_DELAY = 5000; //!< Default join delay used for public network |
Mike Fiore |
16:b630e18103e5 | 96 | const uint16_t DEFAULT_JOIN_DELAY = PRIVATE_JOIN_DELAY; //!< Default join delay1 |
Mike Fiore |
16:b630e18103e5 | 97 | const uint16_t DEFAULT_RX_DELAY = 1000; //!< Default delay for first receive window |
Mike Fiore |
16:b630e18103e5 | 98 | const uint16_t DEFAULT_RX_TIMEOUT = 3000; //!< Default timeout for receive windows |
Mike Fiore |
16:b630e18103e5 | 99 | |
Mike Fiore |
16:b630e18103e5 | 100 | const uint8_t HI_DR_SYMBOL_TIMEOUT = 12; //!< Symbol timeout for receive at datarate with SF < 11 |
Mike Fiore |
16:b630e18103e5 | 101 | const uint8_t LO_DR_SYMBOL_TIMEOUT = 8; //!< Symbol timeout for receive at datarate with SF > 10 |
Mike Fiore |
16:b630e18103e5 | 102 | |
Mike Fiore |
16:b630e18103e5 | 103 | const uint16_t RX2_DELAY_OFFSET = 1000; //!< Delay between first and second window |
Mike Fiore |
16:b630e18103e5 | 104 | const uint16_t RXC_OFFSET = 50; //!< Time between end of RXC after TX and RX1 |
Mike Fiore |
16:b630e18103e5 | 105 | |
Mike Fiore |
16:b630e18103e5 | 106 | const uint8_t US915_125K_NUM_CHANS = 64; //!< Number of 125k channels in US915 channel plan |
Mike Fiore |
16:b630e18103e5 | 107 | const uint8_t US915_500K_NUM_CHANS = 8; //!< Number of 500k channels in US915 channel plan |
Mike Fiore |
16:b630e18103e5 | 108 | |
Mike Fiore |
16:b630e18103e5 | 109 | const uint32_t US915_125K_FREQ_BASE = 902300000; //!< Frequency base for 125k US915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 110 | const uint32_t US915_125K_FREQ_STEP = 200000; //!< Frequency step for 125k US915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 111 | |
Mike Fiore |
16:b630e18103e5 | 112 | const uint32_t US915_500K_FREQ_BASE = 903000000; //!< Frequency base for 500k US915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 113 | const uint32_t US915_500K_FREQ_STEP = 1600000; //!< Frequency step for 500k US915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 114 | |
Mike Fiore |
16:b630e18103e5 | 115 | const uint32_t US915_500K_DBASE = 923300000; //!< Frequency base for 500k US915 downlink channels |
Mike Fiore |
16:b630e18103e5 | 116 | const uint32_t US915_500K_DSTEP = 600000; //!< Frequency step for 500k US915 downlink channels |
Mike Fiore |
16:b630e18103e5 | 117 | |
Mike Fiore |
16:b630e18103e5 | 118 | const uint32_t US915_FREQ_MIN = 902000000; |
Mike Fiore |
16:b630e18103e5 | 119 | const uint32_t US915_FREQ_MAX = 928000000; |
Mike Fiore |
16:b630e18103e5 | 120 | |
Mike Fiore |
16:b630e18103e5 | 121 | const uint8_t US915_MIN_DATARATE = (uint8_t) DR_0; //!< Minimum transmit datarate for US915 |
Mike Fiore |
16:b630e18103e5 | 122 | const uint8_t US915_MAX_DATARATE = (uint8_t) DR_4; //!< Maximum transmit datarate for US915 |
Mike Fiore |
16:b630e18103e5 | 123 | |
Mike Fiore |
16:b630e18103e5 | 124 | const uint8_t US915_MIN_DATARATE_OFFSET = (uint8_t) 0; //!< Minimum transmit datarate for US915 |
Mike Fiore |
16:b630e18103e5 | 125 | const uint8_t US915_MAX_DATARATE_OFFSET = (uint8_t) 3; //!< Maximum transmit datarate for US915 |
Mike Fiore |
16:b630e18103e5 | 126 | |
Mike Fiore |
16:b630e18103e5 | 127 | const uint8_t AU915_125K_NUM_CHANS = 64; //!< Number of 125k channels in AU915 channel plan |
Mike Fiore |
16:b630e18103e5 | 128 | const uint8_t AU915_500K_NUM_CHANS = 8; //!< Number of 500k channels in AU915 channel plan |
Mike Fiore |
16:b630e18103e5 | 129 | |
Mike Fiore |
16:b630e18103e5 | 130 | const uint32_t AU915_125K_FREQ_BASE = 915200000; //!< Frequency base for 125k AU915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 131 | const uint32_t AU915_125K_FREQ_STEP = 200000; //!< Frequency step for 125k AU915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 132 | |
Mike Fiore |
16:b630e18103e5 | 133 | const uint32_t AU915_500K_FREQ_BASE = 915900000; //!< Frequency base for 500k AU915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 134 | const uint32_t AU915_500K_FREQ_STEP = 1600000; //!< Frequency step for 500k AU915 uplink channels |
Mike Fiore |
16:b630e18103e5 | 135 | |
Mike Fiore |
16:b630e18103e5 | 136 | const uint32_t AU915_500K_DBASE = 923300000; //!< Frequency base for 500k AU915 downlink channels |
Mike Fiore |
16:b630e18103e5 | 137 | const uint32_t AU915_500K_DSTEP = 600000; //!< Frequency step for 500k AU915 downlink channels |
Mike Fiore |
16:b630e18103e5 | 138 | |
Mike Fiore |
16:b630e18103e5 | 139 | const uint32_t AU915_FREQ_MIN = 915000000; |
Mike Fiore |
16:b630e18103e5 | 140 | const uint32_t AU915_FREQ_MAX = 928000000; |
Mike Fiore |
16:b630e18103e5 | 141 | |
Mike Fiore |
16:b630e18103e5 | 142 | const uint8_t AU915_MIN_DATARATE = (uint8_t) DR_0; //!< Minimum transmit datarate for AU915 |
Mike Fiore |
16:b630e18103e5 | 143 | const uint8_t AU915_MAX_DATARATE = (uint8_t) DR_4; //!< Maximum transmit datarate for AU915 |
Mike Fiore |
16:b630e18103e5 | 144 | |
Mike Fiore |
16:b630e18103e5 | 145 | const uint8_t AU915_MIN_DATARATE_OFFSET = (uint8_t) 0; //!< Minimum transmit datarate for AU915 |
Mike Fiore |
16:b630e18103e5 | 146 | const uint8_t AU915_MAX_DATARATE_OFFSET = (uint8_t) 3; //!< Maximum transmit datarate for AU915 |
Mike Fiore |
16:b630e18103e5 | 147 | |
Mike Fiore |
16:b630e18103e5 | 148 | const uint8_t EU868_125K_NUM_CHANS = 16; //!< Number of 125k channels in EU868 channel plan |
Mike Fiore |
16:b630e18103e5 | 149 | const uint8_t EU868_DEFAULT_NUM_CHANS = 3; //!< Number of defualt channels in EU868 channel plan |
Mike Fiore |
16:b630e18103e5 | 150 | const uint32_t EU868_125K_FREQ_BASE = 868100000; //!< Frequency base for 125k EU868 uplink channels |
Mike Fiore |
16:b630e18103e5 | 151 | const uint32_t EU868_125K_FREQ_STEP = 200000; //!< Frequency step for 125k EU868 uplink channels |
Mike Fiore |
16:b630e18103e5 | 152 | const uint32_t EU868_RX2_FREQ = 869525000; //!< Frequency default for second rx window in EU868 |
Mike Fiore |
16:b630e18103e5 | 153 | |
Mike Fiore |
16:b630e18103e5 | 154 | const uint8_t EU868_TX_POWER_MAX = 14; //!< Max power for EU868 channel plan |
Mike Fiore |
16:b630e18103e5 | 155 | |
Mike Fiore |
16:b630e18103e5 | 156 | // 0.1% duty cycle 863-868 |
Mike Fiore |
16:b630e18103e5 | 157 | // Limiting to 865-868 allows for 1% duty cycle |
Mike Fiore |
16:b630e18103e5 | 158 | const uint32_t EU868_MILLI_FREQ_MIN = 865000000; |
Mike Fiore |
16:b630e18103e5 | 159 | const uint32_t EU868_MILLI_FREQ_MAX = 868000000; |
Mike Fiore |
16:b630e18103e5 | 160 | |
Mike Fiore |
16:b630e18103e5 | 161 | const uint32_t EU868_MILLI_1_FREQ_MIN = 868700000; |
Mike Fiore |
16:b630e18103e5 | 162 | const uint32_t EU868_MILLI_1_FREQ_MAX = 869200000; |
Mike Fiore |
16:b630e18103e5 | 163 | |
Mike Fiore |
16:b630e18103e5 | 164 | // 1% duty cycle |
Mike Fiore |
16:b630e18103e5 | 165 | const uint32_t EU868_CENTI_FREQ_MIN = 868000000; |
Mike Fiore |
16:b630e18103e5 | 166 | const uint32_t EU868_CENTI_FREQ_MAX = 868600000; |
Mike Fiore |
16:b630e18103e5 | 167 | |
Mike Fiore |
16:b630e18103e5 | 168 | // 10% duty cycle |
Mike Fiore |
16:b630e18103e5 | 169 | const uint32_t EU868_DECI_FREQ_MIN = 869400000; |
Mike Fiore |
16:b630e18103e5 | 170 | const uint32_t EU868_DECI_FREQ_MAX = 869650000; |
Mike Fiore |
16:b630e18103e5 | 171 | |
Mike Fiore |
16:b630e18103e5 | 172 | // Below 7dBm there is no duty cycle for these frequencies |
Mike Fiore |
16:b630e18103e5 | 173 | // Up to 14dBm there is 1% duty cycle |
Mike Fiore |
16:b630e18103e5 | 174 | const uint32_t EU868_VAR_FREQ_MIN = 869700000; |
Mike Fiore |
16:b630e18103e5 | 175 | const uint32_t EU868_VAR_FREQ_MAX = 870000000; |
Mike Fiore |
16:b630e18103e5 | 176 | |
Mike Fiore |
16:b630e18103e5 | 177 | const uint32_t EU868_FREQ_MIN = 863000000; |
Mike Fiore |
16:b630e18103e5 | 178 | const uint32_t EU868_FREQ_MAX = 870000000; |
Mike Fiore |
16:b630e18103e5 | 179 | |
Mike Fiore |
16:b630e18103e5 | 180 | const uint8_t EU868_MIN_DATARATE = (uint8_t) DR_0; //!< Minimum transmit datarate for EU868 |
Mike Fiore |
16:b630e18103e5 | 181 | const uint8_t EU868_MAX_DATARATE = (uint8_t) DR_7; //!< Maximum transmit datarate for EU868 |
Mike Fiore |
16:b630e18103e5 | 182 | |
Mike Fiore |
16:b630e18103e5 | 183 | const uint8_t EU868_MIN_DATARATE_OFFSET = (uint8_t) 0; //!< Minimum transmit datarate for US915 |
Mike Fiore |
16:b630e18103e5 | 184 | const uint8_t EU868_MAX_DATARATE_OFFSET = (uint8_t) 5; //!< Maximum transmit datarate for US915 |
Mike Fiore |
16:b630e18103e5 | 185 | |
Mike Fiore |
16:b630e18103e5 | 186 | const int16_t DEFAULT_FREE_CHAN_RSSI_THRESHOLD = -90; //!< Threshold for channel activity detection (CAD) dBm |
Mike Fiore |
16:b630e18103e5 | 187 | |
Mike Fiore |
16:b630e18103e5 | 188 | const uint8_t CHAN_MASK_SIZE = 16; //!< Number of bits in a channel mask |
Mike Fiore |
16:b630e18103e5 | 189 | const uint8_t COMMANDS_BUFFER_SIZE = 15; //!< Size of Mac Command buffer |
Mike Fiore |
16:b630e18103e5 | 190 | |
Mike Fiore |
16:b630e18103e5 | 191 | const uint8_t PKT_HEADER = 0; //!< Index to packet mHdr field |
Mike Fiore |
16:b630e18103e5 | 192 | const uint8_t PKT_ADDRESS = 1; //!< Index to first byte of packet address field |
Mike Fiore |
16:b630e18103e5 | 193 | const uint8_t PKT_FRAME_CONTROL = PKT_ADDRESS + 4; //!< Index to packet fCtrl field @see UplinkControl |
Mike Fiore |
16:b630e18103e5 | 194 | const uint8_t PKT_FRAME_COUNTER = PKT_FRAME_CONTROL + 1; //!< Index to packet frame counter field |
Mike Fiore |
16:b630e18103e5 | 195 | const uint8_t PKT_OPTIONS_START = PKT_FRAME_COUNTER + 2; //!< Index to start of optional mac commands |
Mike Fiore |
16:b630e18103e5 | 196 | |
Mike Fiore |
16:b630e18103e5 | 197 | const uint8_t PKT_JOIN_APP_NONCE = 1; //!< Index to application nonce in Join Accept message |
Mike Fiore |
16:b630e18103e5 | 198 | const uint8_t PKT_JOIN_NETWORK_ID = 4; //!< Index to network id in Join Accept message |
Mike Fiore |
16:b630e18103e5 | 199 | const uint8_t PKT_JOIN_NETWORK_ADDRESS = 7; //!< Index to network address in Join Accept message |
Mike Fiore |
16:b630e18103e5 | 200 | const uint8_t PKT_JOIN_DL_SETTINGS = 11; //!< Index to downlink settings in Join Accept message |
Mike Fiore |
16:b630e18103e5 | 201 | const uint8_t PKT_JOIN_RX_DELAY = 12; //!< Index to rx delay in Join Accept message |
Mike Fiore |
16:b630e18103e5 | 202 | |
Mike Fiore |
16:b630e18103e5 | 203 | const uint8_t ADR_ACK_LIMIT = 64; //!< Number of packets without ADR ACK Request |
Mike Fiore |
16:b630e18103e5 | 204 | const uint8_t ADR_ACK_DELAY = 32; //!< Number of packets to expect ADR ACK Response within |
Mike Fiore |
16:b630e18103e5 | 205 | |
Mike Fiore |
16:b630e18103e5 | 206 | const uint16_t ACK_TIMEOUT = 2000; //!< Base millisecond timeout to resend after missed ACK |
Mike Fiore |
16:b630e18103e5 | 207 | const uint16_t ACK_TIMEOUT_RND = 1000; //!< Random millisecond adjustment to resend after missed ACK |
Mike Fiore |
16:b630e18103e5 | 208 | |
Mike Fiore |
16:b630e18103e5 | 209 | const uint8_t FRAME_OVERHEAD = 13; //!< Bytes of network info overhead in a frame |
Mike Fiore |
16:b630e18103e5 | 210 | |
Mike Fiore |
16:b630e18103e5 | 211 | /** |
Mike Fiore |
16:b630e18103e5 | 212 | * Settings to choose ChannelPlan |
Mike Fiore |
16:b630e18103e5 | 213 | */ |
Mike Fiore |
16:b630e18103e5 | 214 | enum FrequencyBand { |
Mike Fiore |
16:b630e18103e5 | 215 | EU868, //!< EU 863-870 16 channel uplink |
Mike Fiore |
16:b630e18103e5 | 216 | US915, //!< US 902-928 64-125k/8-500k uplink and 8-500k downlink channels |
Mike Fiore |
16:b630e18103e5 | 217 | AU915, //!< US 915-928 64-125k/8-500k uplink and 8-500k downlink channels |
Mike Fiore |
16:b630e18103e5 | 218 | CN779, //!< |
Mike Fiore |
16:b630e18103e5 | 219 | CN470, //!< |
Mike Fiore |
16:b630e18103e5 | 220 | EU433, //!< |
Mike Fiore |
16:b630e18103e5 | 221 | }; |
Mike Fiore |
16:b630e18103e5 | 222 | |
Mike Fiore |
16:b630e18103e5 | 223 | /** |
Mike Fiore |
16:b630e18103e5 | 224 | * Settings for type of network |
Mike Fiore |
16:b630e18103e5 | 225 | * PUBLIC - defaults to 5/6 second join windows and 0x34 sync word |
Mike Fiore |
16:b630e18103e5 | 226 | * PRIVATE - defaults to 1/2 second join windows and 0x12 sync word |
Mike Fiore |
16:b630e18103e5 | 227 | */ |
Mike Fiore |
16:b630e18103e5 | 228 | enum NetworkType { |
Mike Fiore |
16:b630e18103e5 | 229 | PRIVATE = 0, |
Mike Fiore |
16:b630e18103e5 | 230 | PUBLIC = 1, |
Mike Fiore |
16:b630e18103e5 | 231 | PEER_TO_PEER = 4 |
Mike Fiore |
16:b630e18103e5 | 232 | }; |
Mike Fiore |
16:b630e18103e5 | 233 | |
Mike Fiore |
16:b630e18103e5 | 234 | /** |
Mike Fiore |
16:b630e18103e5 | 235 | * Enum for on/off settings |
Mike Fiore |
16:b630e18103e5 | 236 | */ |
Mike Fiore |
16:b630e18103e5 | 237 | enum Enabled { |
Mike Fiore |
16:b630e18103e5 | 238 | OFF = 0, |
Mike Fiore |
16:b630e18103e5 | 239 | ON = 1 |
Mike Fiore |
16:b630e18103e5 | 240 | }; |
Mike Fiore |
16:b630e18103e5 | 241 | |
Mike Fiore |
16:b630e18103e5 | 242 | /** |
Mike Fiore |
16:b630e18103e5 | 243 | * Return status of mac functions |
Mike Fiore |
16:b630e18103e5 | 244 | */ |
Mike Fiore |
16:b630e18103e5 | 245 | enum MacStatus { |
Mike Fiore |
16:b630e18103e5 | 246 | LORA_OK = 0, |
Mike Fiore |
16:b630e18103e5 | 247 | LORA_ERROR = 1, |
Mike Fiore |
16:b630e18103e5 | 248 | LORA_JOIN_ERROR = 2, |
Mike Fiore |
16:b630e18103e5 | 249 | LORA_SEND_ERROR = 3, |
Mike Fiore |
16:b630e18103e5 | 250 | LORA_MIC_ERROR = 4, |
Mike Fiore |
16:b630e18103e5 | 251 | LORA_ADDRESS_ERROR = 5, |
Mike Fiore |
16:b630e18103e5 | 252 | LORA_NO_CHANS_ENABLED = 6, |
Mike Fiore |
16:b630e18103e5 | 253 | LORA_COMMAND_BUFFER_FULL = 7, |
Mike Fiore |
16:b630e18103e5 | 254 | LORA_UNKNOWN_MAC_COMMAND = 8, |
Mike Fiore |
16:b630e18103e5 | 255 | LORA_ADR_OFF = 9, |
Mike Fiore |
16:b630e18103e5 | 256 | LORA_BUSY = 10, |
Mike Fiore |
16:b630e18103e5 | 257 | LORA_LINK_BUSY = 11, |
Mike Fiore |
16:b630e18103e5 | 258 | LORA_RADIO_BUSY = 12, |
Mike Fiore |
16:b630e18103e5 | 259 | LORA_BUFFER_FULL = 13, |
Mike Fiore |
16:b630e18103e5 | 260 | LORA_JOIN_BACKOFF = 14, |
Mike Fiore |
16:b630e18103e5 | 261 | LORA_NO_FREE_CHAN = 15, |
Mike Fiore |
16:b630e18103e5 | 262 | LORA_AGGREGATED_DUTY_CYCLE = 16, |
Mike Fiore |
16:b630e18103e5 | 263 | LORA_MAX_PAYLOAD_EXCEEDED |
Mike Fiore |
16:b630e18103e5 | 264 | }; |
Mike Fiore |
16:b630e18103e5 | 265 | |
Mike Fiore |
16:b630e18103e5 | 266 | /** |
Mike Fiore |
16:b630e18103e5 | 267 | * State for Link |
Mike Fiore |
16:b630e18103e5 | 268 | */ |
Mike Fiore |
16:b630e18103e5 | 269 | enum LinkState { |
Mike Fiore |
16:b630e18103e5 | 270 | LINK_IDLE = 0, //!< Link ready to send or receive |
Mike Fiore |
16:b630e18103e5 | 271 | LINK_TX, //!< Link is busy sending |
Mike Fiore |
16:b630e18103e5 | 272 | LINK_ACK_TX, //!< Link is busy resending after missed ACK |
Mike Fiore |
16:b630e18103e5 | 273 | LINK_REP_TX, //!< Link is busy repeating |
Mike Fiore |
16:b630e18103e5 | 274 | LINK_RX, //!< Link has receive window open |
Mike Fiore |
16:b630e18103e5 | 275 | LINK_RX1, //!< Link has first received window open |
Mike Fiore |
16:b630e18103e5 | 276 | LINK_RX2, //!< Link has second received window open |
Mike Fiore |
16:b630e18103e5 | 277 | LINK_RXC, //!< Link has class C received window open |
Mike Fiore |
16:b630e18103e5 | 278 | LINK_P2P, //!< Link is busy sending |
Mike Fiore |
16:b630e18103e5 | 279 | }; |
Mike Fiore |
16:b630e18103e5 | 280 | |
Mike Fiore |
16:b630e18103e5 | 281 | /** |
Mike Fiore |
16:b630e18103e5 | 282 | * State for MAC |
Mike Fiore |
16:b630e18103e5 | 283 | */ |
Mike Fiore |
16:b630e18103e5 | 284 | enum MacState { |
Mike Fiore |
16:b630e18103e5 | 285 | MAC_IDLE, |
Mike Fiore |
16:b630e18103e5 | 286 | MAC_RX1, |
Mike Fiore |
16:b630e18103e5 | 287 | MAC_RX2, |
Mike Fiore |
16:b630e18103e5 | 288 | MAC_RXC, |
Mike Fiore |
16:b630e18103e5 | 289 | MAC_TX |
Mike Fiore |
16:b630e18103e5 | 290 | }; |
Mike Fiore |
16:b630e18103e5 | 291 | |
Mike Fiore |
16:b630e18103e5 | 292 | /** |
Mike Fiore |
16:b630e18103e5 | 293 | * Operation class for device |
Mike Fiore |
16:b630e18103e5 | 294 | */ |
Mike Fiore |
16:b630e18103e5 | 295 | enum MoteClass { |
Mike Fiore |
16:b630e18103e5 | 296 | CLASS_A = 0x00, //!< Device can only receive in windows opened after a transmit |
Mike Fiore |
16:b630e18103e5 | 297 | CLASS_B = 0x01, //!< Device can receive in windows sychronized with gateway beacon |
Mike Fiore |
16:b630e18103e5 | 298 | CLASS_C = 0x02 //!< Device can receive any time when not transmitting |
Mike Fiore |
16:b630e18103e5 | 299 | }; |
Mike Fiore |
16:b630e18103e5 | 300 | |
Mike Fiore |
16:b630e18103e5 | 301 | /** |
Mike Fiore |
16:b630e18103e5 | 302 | * Direction of a packet |
Mike Fiore |
16:b630e18103e5 | 303 | */ |
Mike Fiore |
16:b630e18103e5 | 304 | enum Direction { |
Mike Fiore |
16:b630e18103e5 | 305 | DIR_UP = 0, //!< Packet is sent from mote to gateway |
Mike Fiore |
16:b630e18103e5 | 306 | DIR_DOWN = 1, //!< Packet was received from gateway |
Mike Fiore |
16:b630e18103e5 | 307 | DIR_PEER = 2 //!< Packet was received from peer |
Mike Fiore |
16:b630e18103e5 | 308 | }; |
Mike Fiore |
16:b630e18103e5 | 309 | |
Mike Fiore |
16:b630e18103e5 | 310 | |
Mike Fiore |
16:b630e18103e5 | 311 | /** |
Mike Fiore |
16:b630e18103e5 | 312 | * Received window used by Link |
Mike Fiore |
16:b630e18103e5 | 313 | */ |
Mike Fiore |
16:b630e18103e5 | 314 | enum ReceiveWindows { |
Mike Fiore |
16:b630e18103e5 | 315 | RX_1 = 1, //!< First receive window |
Mike Fiore |
16:b630e18103e5 | 316 | RX_2, //!< Second receive window |
Mike Fiore |
16:b630e18103e5 | 317 | RX_BEACON, //!< Beacon receive window |
Mike Fiore |
16:b630e18103e5 | 318 | RX_SLOT, //!< Beacon Slot receive window |
Mike Fiore |
16:b630e18103e5 | 319 | RX_TEST |
Mike Fiore |
16:b630e18103e5 | 320 | }; |
Mike Fiore |
16:b630e18103e5 | 321 | |
Mike Fiore |
16:b630e18103e5 | 322 | /** |
Mike Fiore |
16:b630e18103e5 | 323 | * Datarate range for a Channel |
Mike Fiore |
16:b630e18103e5 | 324 | */ |
Mike Fiore |
16:b630e18103e5 | 325 | typedef union { |
Mike Fiore |
16:b630e18103e5 | 326 | int8_t Value; |
Mike Fiore |
16:b630e18103e5 | 327 | struct { |
Mike Fiore |
16:b630e18103e5 | 328 | int8_t Min :4; |
Mike Fiore |
16:b630e18103e5 | 329 | int8_t Max :4; |
Mike Fiore |
16:b630e18103e5 | 330 | } Fields; |
Mike Fiore |
16:b630e18103e5 | 331 | } DatarateRange; |
Mike Fiore |
16:b630e18103e5 | 332 | |
Mike Fiore |
16:b630e18103e5 | 333 | /** |
Mike Fiore |
16:b630e18103e5 | 334 | * Datarate used for transmitting and receiving |
Mike Fiore |
16:b630e18103e5 | 335 | */ |
Mike Fiore |
16:b630e18103e5 | 336 | typedef struct Datarate { |
Mike Fiore |
16:b630e18103e5 | 337 | uint8_t Index; |
Mike Fiore |
16:b630e18103e5 | 338 | uint8_t Bandwidth; |
Mike Fiore |
16:b630e18103e5 | 339 | uint8_t Coderate; |
Mike Fiore |
16:b630e18103e5 | 340 | uint8_t PreambleLength; |
Mike Fiore |
16:b630e18103e5 | 341 | uint8_t SpreadingFactor; |
Mike Fiore |
16:b630e18103e5 | 342 | uint8_t Crc; |
Mike Fiore |
16:b630e18103e5 | 343 | uint8_t TxIQ; |
Mike Fiore |
16:b630e18103e5 | 344 | uint8_t RxIQ; |
Mike Fiore |
16:b630e18103e5 | 345 | uint8_t SymbolTimeout(); |
Mike Fiore |
16:b630e18103e5 | 346 | Datarate(); |
Mike Fiore |
16:b630e18103e5 | 347 | } Datarate; |
Mike Fiore |
16:b630e18103e5 | 348 | |
Mike Fiore |
16:b630e18103e5 | 349 | /** |
Mike Fiore |
16:b630e18103e5 | 350 | * Channel used for transmitting |
Mike Fiore |
16:b630e18103e5 | 351 | */ |
Mike Fiore |
16:b630e18103e5 | 352 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 353 | uint8_t Index; |
Mike Fiore |
16:b630e18103e5 | 354 | uint32_t Frequency; |
Mike Fiore |
16:b630e18103e5 | 355 | DatarateRange DrRange; |
Mike Fiore |
16:b630e18103e5 | 356 | } Channel; |
Mike Fiore |
16:b630e18103e5 | 357 | |
Mike Fiore |
16:b630e18103e5 | 358 | /** |
Mike Fiore |
16:b630e18103e5 | 359 | * Receive window |
Mike Fiore |
16:b630e18103e5 | 360 | */ |
Mike Fiore |
16:b630e18103e5 | 361 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 362 | uint8_t Index; |
Mike Fiore |
16:b630e18103e5 | 363 | uint32_t Frequency; |
Mike Fiore |
16:b630e18103e5 | 364 | uint8_t DatarateIndex; |
Mike Fiore |
16:b630e18103e5 | 365 | } RxWindow; |
Mike Fiore |
16:b630e18103e5 | 366 | |
Mike Fiore |
16:b630e18103e5 | 367 | /** |
Mike Fiore |
16:b630e18103e5 | 368 | * Duty band for limiting time-on-air for regional regulations |
Mike Fiore |
16:b630e18103e5 | 369 | */ |
Mike Fiore |
16:b630e18103e5 | 370 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 371 | uint8_t Index; |
Mike Fiore |
16:b630e18103e5 | 372 | uint32_t FrequencyMin; |
Mike Fiore |
16:b630e18103e5 | 373 | uint32_t FrequencyMax; |
Mike Fiore |
16:b630e18103e5 | 374 | uint8_t PowerMax; |
Mike Fiore |
16:b630e18103e5 | 375 | uint16_t DutyCycle; //!< Multiplier of time on air, 0:100%, 1:50%, 2:33%, 10:10%, 100:1%, 1000,0.1% |
Mike Fiore |
16:b630e18103e5 | 376 | uint32_t TimeOffEnd; //!< Timestamp when this band will be available |
Mike Fiore |
16:b630e18103e5 | 377 | } DutyBand; |
Mike Fiore |
16:b630e18103e5 | 378 | |
Mike Fiore |
16:b630e18103e5 | 379 | /** |
Mike Fiore |
16:b630e18103e5 | 380 | * Device configuration |
Mike Fiore |
16:b630e18103e5 | 381 | */ |
Mike Fiore |
16:b630e18103e5 | 382 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 383 | uint8_t FrequencyBand; //!< Used to choose ChannelPlan |
Mike Fiore |
16:b630e18103e5 | 384 | uint8_t EUI[8]; //!< Unique identifier assigned to device |
Mike Fiore |
16:b630e18103e5 | 385 | } DeviceConfig; |
Mike Fiore |
16:b630e18103e5 | 386 | |
Mike Fiore |
16:b630e18103e5 | 387 | /** |
Mike Fiore |
16:b630e18103e5 | 388 | * Network configuration |
Mike Fiore |
16:b630e18103e5 | 389 | */ |
Mike Fiore |
16:b630e18103e5 | 390 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 391 | uint8_t Mode; //!< PUBLIC, PRIVATE or PEER_TO_PEER network mode |
Mike Fiore |
16:b630e18103e5 | 392 | uint8_t Class; //!< Operating class of device |
Mike Fiore |
16:b630e18103e5 | 393 | uint8_t EUI[8]; //!< Network ID or AppEUI |
Mike Fiore |
16:b630e18103e5 | 394 | uint8_t Key[16]; //!< Network Key or AppKey |
Mike Fiore |
16:b630e18103e5 | 395 | uint8_t JoinDelay; //!< Number of seconds to wait before 1st RX Window |
Mike Fiore |
16:b630e18103e5 | 396 | uint8_t RxDelay; //!< Number of seconds to wait before 1st RX Window |
Mike Fiore |
16:b630e18103e5 | 397 | uint8_t ChannelGroup; //!< ChannelGroup used for US915 hybrid operation 0:72 channels, 1:1-8 channels ... |
Mike Fiore |
16:b630e18103e5 | 398 | uint8_t AckAttempts; //!< Number of attempts to send packet and receive an ACK from server |
Mike Fiore |
16:b630e18103e5 | 399 | uint8_t Retries; //!< Number of times to resend a packet without receiving an ACK, redundancy |
Mike Fiore |
16:b630e18103e5 | 400 | uint8_t ADREnabled; //!< Enable adaptive datarate |
Mike Fiore |
16:b630e18103e5 | 401 | uint8_t CADEnabled; //!< Enable listen before talk/channel activity detection |
Mike Fiore |
16:b630e18103e5 | 402 | uint8_t RepeaterMode; //!< Limit payloads to repeater compatible sizes |
Mike Fiore |
16:b630e18103e5 | 403 | uint8_t TxPower; //!< Default radio output power in dBm |
Mike Fiore |
16:b630e18103e5 | 404 | uint8_t TxPowerMax; //!< Max transmit power |
Mike Fiore |
16:b630e18103e5 | 405 | uint8_t TxDatarate; //!< Datarate for P2P transmit |
Mike Fiore |
16:b630e18103e5 | 406 | uint32_t TxFrequency; //!< Frequency for P2P transmit |
Mike Fiore |
16:b630e18103e5 | 407 | int8_t AntennaGain; //!< Antenna Gain |
Mike Fiore |
16:b630e18103e5 | 408 | uint8_t DisableEncryption; //!< Disable Encryption |
Mike Fiore |
16:b630e18103e5 | 409 | uint8_t DisableCRC; //!< Disable CRC on uplink packets |
Mike Fiore |
16:b630e18103e5 | 410 | uint16_t P2PACKTimeout; |
Mike Fiore |
16:b630e18103e5 | 411 | uint16_t P2PACKBackoff; |
Mike Fiore |
16:b630e18103e5 | 412 | uint8_t JoinRx1DatarateOffset; //!< Offset for datarate for first window |
Mike Fiore |
16:b630e18103e5 | 413 | uint32_t JoinRx2Frequency; //!< Frequency used in second window |
Mike Fiore |
16:b630e18103e5 | 414 | uint8_t JoinRx2DatarateIndex; //!< Datarate for second window |
Mike Fiore |
16:b630e18103e5 | 415 | } NetworkConfig; |
Mike Fiore |
16:b630e18103e5 | 416 | |
Mike Fiore |
16:b630e18103e5 | 417 | /** |
Mike Fiore |
16:b630e18103e5 | 418 | * Network session info |
Mike Fiore |
16:b630e18103e5 | 419 | * Some settings are acquired in join message and others may be changed through Mac Commands from server |
Mike Fiore |
16:b630e18103e5 | 420 | */ |
Mike Fiore |
16:b630e18103e5 | 421 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 422 | uint8_t Joined; //!< State of session |
Mike Fiore |
16:b630e18103e5 | 423 | uint8_t Rx1DatarateOffset; //!< Offset for datarate for first window |
Mike Fiore |
16:b630e18103e5 | 424 | uint32_t Rx2Frequency; //!< Frequency used in second window |
Mike Fiore |
16:b630e18103e5 | 425 | uint8_t Rx2DatarateIndex; //!< Datarate for second window |
Mike Fiore |
16:b630e18103e5 | 426 | uint8_t TxPower; //!< Current total radiated output power in dBm |
Mike Fiore |
16:b630e18103e5 | 427 | uint8_t TxDatarate; //!< Current datarate can be changed when ADR is enabled |
Mike Fiore |
16:b630e18103e5 | 428 | uint32_t Address; //!< Network address |
Mike Fiore |
16:b630e18103e5 | 429 | uint32_t NetworkID; //!< Network ID 24-bits |
Mike Fiore |
16:b630e18103e5 | 430 | uint8_t NetworkSessionKey[16]; //!< Network session key |
Mike Fiore |
16:b630e18103e5 | 431 | uint8_t ApplicationSessionKey[16]; //!< Data session key |
Mike Fiore |
16:b630e18103e5 | 432 | uint16_t ChannelMask[4]; //!< Current channel mask |
Mike Fiore |
16:b630e18103e5 | 433 | uint16_t ChannelMask500k; //!< Current channel mask for 500k channels |
Mike Fiore |
16:b630e18103e5 | 434 | uint32_t DownlinkCounter; //!< Downlink counter of last packet received from server |
Mike Fiore |
16:b630e18103e5 | 435 | uint32_t UplinkCounter; //!< Uplink counter of last packet received from server |
Mike Fiore |
16:b630e18103e5 | 436 | uint8_t Redundancy; //!< Number of time to repeat an uplink |
Mike Fiore |
16:b630e18103e5 | 437 | uint8_t MaxDutyCycle; //!< Current Max Duty Cycle value |
Mike Fiore |
16:b630e18103e5 | 438 | uint32_t JoinTimeOnAir; //!< Balance of time on air used during join attempts |
Mike Fiore |
16:b630e18103e5 | 439 | uint32_t JoinTimeOffEnd; //!< RTC time of next join attempt |
Mike Fiore |
16:b630e18103e5 | 440 | uint32_t JoinFirstAttempt; //!< RTC time of first failed join attempt |
Mike Fiore |
16:b630e18103e5 | 441 | uint32_t AggregatedTimeOffEnd; //!< Time off air expiration for aggregate duty cycle |
Mike Fiore |
16:b630e18103e5 | 442 | uint16_t AggregateDutyCycle; //!< Used for enforcing time-on-air |
Mike Fiore |
16:b630e18103e5 | 443 | uint8_t AckCounter; //!< Current number of packets sent without ACK from server |
Mike Fiore |
16:b630e18103e5 | 444 | uint8_t AdrCounter; //!< Current number of packets received without downlink from server |
Mike Fiore |
16:b630e18103e5 | 445 | uint8_t RxDelay; //!< Number of seconds to wait before 1st RX Window |
Mike Fiore |
16:b630e18103e5 | 446 | uint8_t CommandBuffer[COMMANDS_BUFFER_SIZE]; //!< Buffer to hold Mac Commands and parameters to be sent in next packet |
Mike Fiore |
16:b630e18103e5 | 447 | uint8_t CommandBufferIndex; //!< Index to place next Mac Command, also current size of Command Buffer |
Mike Fiore |
16:b630e18103e5 | 448 | bool SrvRequestedAck; //!< Indicator of ACK requested by server in last packet received |
Mike Fiore |
16:b630e18103e5 | 449 | bool DataPending; //!< Indicator of data pending at server |
Mike Fiore |
16:b630e18103e5 | 450 | uint8_t RxTimingSetupReqReceived; //!< Indicator that RxTimingSetupAns should be included in uplink |
Mike Fiore |
16:b630e18103e5 | 451 | uint8_t RxParamSetupReqAnswer; //!< Indicator that RxParamSetupAns should be included in uplink |
Mike Fiore |
16:b630e18103e5 | 452 | } NetworkSession; |
Mike Fiore |
16:b630e18103e5 | 453 | |
Mike Fiore |
16:b630e18103e5 | 454 | /** |
Mike Fiore |
16:b630e18103e5 | 455 | * Multicast session info |
Mike Fiore |
16:b630e18103e5 | 456 | */ |
Mike Fiore |
16:b630e18103e5 | 457 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 458 | uint32_t Address; //!< Network address |
Mike Fiore |
16:b630e18103e5 | 459 | uint8_t NetworkSessionKey[16]; //!< Network session key |
Mike Fiore |
16:b630e18103e5 | 460 | uint8_t DataSessionKey[16]; //!< Data session key |
Mike Fiore |
16:b630e18103e5 | 461 | uint32_t DownlinkCounter; //!< Downlink counter of last packet received from server |
Mike Fiore |
16:b630e18103e5 | 462 | } MulticastSession; |
Mike Fiore |
16:b630e18103e5 | 463 | |
Mike Fiore |
16:b630e18103e5 | 464 | /** |
Mike Fiore |
16:b630e18103e5 | 465 | * Application configuration |
Mike Fiore |
16:b630e18103e5 | 466 | */ |
Mike Fiore |
16:b630e18103e5 | 467 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 468 | uint8_t Port; //!< Port used by application |
Mike Fiore |
16:b630e18103e5 | 469 | uint8_t AppEUI; //!< Application ID |
Mike Fiore |
16:b630e18103e5 | 470 | uint8_t AppKey[16]; //!< Application Key |
Mike Fiore |
16:b630e18103e5 | 471 | } ApplicationConfig; |
Mike Fiore |
16:b630e18103e5 | 472 | |
Mike Fiore |
16:b630e18103e5 | 473 | /** |
Mike Fiore |
16:b630e18103e5 | 474 | * Statistics of current network session |
Mike Fiore |
16:b630e18103e5 | 475 | */ |
Mike Fiore |
16:b630e18103e5 | 476 | typedef struct Statistics { |
Mike Fiore |
16:b630e18103e5 | 477 | uint32_t Up; //!< Number of uplink packets sent |
Mike Fiore |
16:b630e18103e5 | 478 | uint32_t Down; //!< Number of downlink packets received |
Mike Fiore |
16:b630e18103e5 | 479 | uint32_t Joins; //!< Number of join requests sent |
Mike Fiore |
16:b630e18103e5 | 480 | uint32_t JoinFails; //!< Number of join requests without response or invalid response |
Mike Fiore |
16:b630e18103e5 | 481 | uint32_t MissedAcks; //!< Number of missed acknowledgement attempts of confirmed packets |
Mike Fiore |
16:b630e18103e5 | 482 | uint32_t CRCErrors; //!< Number of CRC errors in received packets |
Mike Fiore |
16:b630e18103e5 | 483 | int32_t AvgCount; //!< Number of packets used to compute rolling average of RSSI and SNR |
Mike Fiore |
16:b630e18103e5 | 484 | int16_t Rssi; //!< RSSI of last packet received |
Mike Fiore |
16:b630e18103e5 | 485 | int16_t RssiMin; //!< Minimum RSSI of last AvgCount packets |
Mike Fiore |
16:b630e18103e5 | 486 | int16_t RssiMax; //!< Maximum RSSI of last AvgCount packets |
Mike Fiore |
16:b630e18103e5 | 487 | int16_t RssiAvg; //!< Rolling average RSSI of last AvgCount packets |
Mike Fiore |
16:b630e18103e5 | 488 | int16_t Snr; //!< SNR of last packet received |
Mike Fiore |
16:b630e18103e5 | 489 | int16_t SnrMin; //!< Minimum SNR of last AvgCount packets |
Mike Fiore |
16:b630e18103e5 | 490 | int16_t SnrMax; //!< Maximum SNR of last AvgCount packets |
Mike Fiore |
16:b630e18103e5 | 491 | int16_t SnrAvg; //!< Rolling average SNR of last AvgCount packets |
Mike Fiore |
16:b630e18103e5 | 492 | } Statistics; |
Mike Fiore |
16:b630e18103e5 | 493 | |
Mike Fiore |
16:b630e18103e5 | 494 | /** |
Mike Fiore |
16:b630e18103e5 | 495 | * Testing settings |
Mike Fiore |
16:b630e18103e5 | 496 | */ |
Mike Fiore |
16:b630e18103e5 | 497 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 498 | uint8_t TestMode; |
Mike Fiore |
16:b630e18103e5 | 499 | uint8_t SkipMICCheck; |
Mike Fiore |
16:b630e18103e5 | 500 | uint8_t DisableDutyCycle; |
Mike Fiore |
16:b630e18103e5 | 501 | uint8_t DisableRx1; |
Mike Fiore |
16:b630e18103e5 | 502 | uint8_t DisableRx2; |
Mike Fiore |
16:b630e18103e5 | 503 | uint8_t FixedUplinkCounter; |
Mike Fiore |
16:b630e18103e5 | 504 | uint8_t DisableRandomJoinDatarate; |
Mike Fiore |
16:b630e18103e5 | 505 | } Testing; |
Mike Fiore |
16:b630e18103e5 | 506 | |
Mike Fiore |
16:b630e18103e5 | 507 | /** |
Mike Fiore |
16:b630e18103e5 | 508 | * Combination of device, network, testing settings and statistics |
Mike Fiore |
16:b630e18103e5 | 509 | */ |
Mike Fiore |
16:b630e18103e5 | 510 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 511 | DeviceConfig Device; |
Mike Fiore |
16:b630e18103e5 | 512 | NetworkConfig Network; |
Mike Fiore |
16:b630e18103e5 | 513 | NetworkSession Session; |
Mike Fiore |
16:b630e18103e5 | 514 | ApplicationConfig Applications[MAX_APPS]; |
Mike Fiore |
16:b630e18103e5 | 515 | MulticastSession Multicast[MAX_MULTICAST_SESSIONS]; |
Mike Fiore |
16:b630e18103e5 | 516 | Statistics Stats; |
Mike Fiore |
16:b630e18103e5 | 517 | Testing Test; |
Mike Fiore |
16:b630e18103e5 | 518 | } Settings; |
Mike Fiore |
16:b630e18103e5 | 519 | |
Mike Fiore |
16:b630e18103e5 | 520 | /** |
Mike Fiore |
16:b630e18103e5 | 521 | * Downlink settings sent in Join Accept message |
Mike Fiore |
16:b630e18103e5 | 522 | */ |
Mike Fiore |
16:b630e18103e5 | 523 | typedef union { |
Mike Fiore |
16:b630e18103e5 | 524 | uint8_t Value; |
Mike Fiore |
16:b630e18103e5 | 525 | struct { |
Mike Fiore |
16:b630e18103e5 | 526 | uint8_t Rx2Datarate :4; |
Mike Fiore |
16:b630e18103e5 | 527 | uint8_t Rx1Offset :3; |
Mike Fiore |
16:b630e18103e5 | 528 | uint8_t RFU :1; |
Mike Fiore |
16:b630e18103e5 | 529 | }; |
Mike Fiore |
16:b630e18103e5 | 530 | } DownlinkSettings; |
Mike Fiore |
16:b630e18103e5 | 531 | |
Mike Fiore |
16:b630e18103e5 | 532 | /** |
Mike Fiore |
16:b630e18103e5 | 533 | * Frame structure for Join Request |
Mike Fiore |
16:b630e18103e5 | 534 | */ |
Mike Fiore |
16:b630e18103e5 | 535 | typedef struct { |
Mike Fiore |
16:b630e18103e5 | 536 | uint8_t Type; |
Mike Fiore |
16:b630e18103e5 | 537 | uint8_t AppEUI[8]; |
Mike Fiore |
16:b630e18103e5 | 538 | uint8_t DevEUI[8]; |
Mike Fiore |
16:b630e18103e5 | 539 | uint8_t Nonce[2]; |
Mike Fiore |
16:b630e18103e5 | 540 | uint8_t MIC[4]; |
Mike Fiore |
16:b630e18103e5 | 541 | } JoinRequestFrame; |
Mike Fiore |
16:b630e18103e5 | 542 | |
Mike Fiore |
16:b630e18103e5 | 543 | /** |
Mike Fiore |
16:b630e18103e5 | 544 | * Mac header of uplink and downlink packets |
Mike Fiore |
16:b630e18103e5 | 545 | */ |
Mike Fiore |
16:b630e18103e5 | 546 | typedef union { |
Mike Fiore |
16:b630e18103e5 | 547 | uint8_t Value; |
Mike Fiore |
16:b630e18103e5 | 548 | struct { |
Mike Fiore |
16:b630e18103e5 | 549 | uint8_t Major :2; |
Mike Fiore |
16:b630e18103e5 | 550 | uint8_t RFU :3; |
Mike Fiore |
16:b630e18103e5 | 551 | uint8_t MType :3; |
Mike Fiore |
16:b630e18103e5 | 552 | } Bits; |
Mike Fiore |
16:b630e18103e5 | 553 | } MacHeader; |
Mike Fiore |
16:b630e18103e5 | 554 | |
Mike Fiore |
16:b630e18103e5 | 555 | /** |
Mike Fiore |
16:b630e18103e5 | 556 | * Frame control field of uplink packets |
Mike Fiore |
16:b630e18103e5 | 557 | */ |
Mike Fiore |
16:b630e18103e5 | 558 | typedef union { |
Mike Fiore |
16:b630e18103e5 | 559 | uint8_t Value; |
Mike Fiore |
16:b630e18103e5 | 560 | struct { |
Mike Fiore |
16:b630e18103e5 | 561 | uint8_t OptionsLength :4; |
Mike Fiore |
16:b630e18103e5 | 562 | uint8_t ClassB :1; |
Mike Fiore |
16:b630e18103e5 | 563 | uint8_t Ack :1; |
Mike Fiore |
16:b630e18103e5 | 564 | uint8_t AdrAckReq :1; |
Mike Fiore |
16:b630e18103e5 | 565 | uint8_t Adr :1; |
Mike Fiore |
16:b630e18103e5 | 566 | } Bits; |
Mike Fiore |
16:b630e18103e5 | 567 | } UplinkControl; |
Mike Fiore |
16:b630e18103e5 | 568 | |
Mike Fiore |
16:b630e18103e5 | 569 | /** |
Mike Fiore |
16:b630e18103e5 | 570 | * Frame control field of downlink packets |
Mike Fiore |
16:b630e18103e5 | 571 | */ |
Mike Fiore |
16:b630e18103e5 | 572 | typedef union { |
Mike Fiore |
16:b630e18103e5 | 573 | uint8_t Value; |
Mike Fiore |
16:b630e18103e5 | 574 | struct { |
Mike Fiore |
16:b630e18103e5 | 575 | uint8_t OptionsLength :4; |
Mike Fiore |
16:b630e18103e5 | 576 | uint8_t FPending :1; |
Mike Fiore |
16:b630e18103e5 | 577 | uint8_t Ack :1; |
Mike Fiore |
16:b630e18103e5 | 578 | uint8_t RFU :1; |
Mike Fiore |
16:b630e18103e5 | 579 | uint8_t Adr :1; |
Mike Fiore |
16:b630e18103e5 | 580 | } Bits; |
Mike Fiore |
16:b630e18103e5 | 581 | } DownlinkControl; |
Mike Fiore |
16:b630e18103e5 | 582 | |
Mike Fiore |
16:b630e18103e5 | 583 | /** |
Mike Fiore |
16:b630e18103e5 | 584 | * Frame type of packet |
Mike Fiore |
16:b630e18103e5 | 585 | */ |
Mike Fiore |
16:b630e18103e5 | 586 | typedef enum { |
Mike Fiore |
16:b630e18103e5 | 587 | FRAME_TYPE_JOIN_REQ = 0x00, |
Mike Fiore |
16:b630e18103e5 | 588 | FRAME_TYPE_JOIN_ACCEPT = 0x01, |
Mike Fiore |
16:b630e18103e5 | 589 | FRAME_TYPE_DATA_UNCONFIRMED_UP = 0x02, |
Mike Fiore |
16:b630e18103e5 | 590 | FRAME_TYPE_DATA_UNCONFIRMED_DOWN = 0x03, |
Mike Fiore |
16:b630e18103e5 | 591 | FRAME_TYPE_DATA_CONFIRMED_UP = 0x04, |
Mike Fiore |
16:b630e18103e5 | 592 | FRAME_TYPE_DATA_CONFIRMED_DOWN = 0x05, |
Mike Fiore |
16:b630e18103e5 | 593 | FRAME_TYPE_RFU = 0x06, |
Mike Fiore |
16:b630e18103e5 | 594 | FRAME_TYPE_PROPRIETARY = 0x07, |
Mike Fiore |
16:b630e18103e5 | 595 | } FrameType; |
Mike Fiore |
16:b630e18103e5 | 596 | |
Mike Fiore |
16:b630e18103e5 | 597 | /** |
Mike Fiore |
16:b630e18103e5 | 598 | * LoRaWAN mote MAC commands |
Mike Fiore |
16:b630e18103e5 | 599 | */ |
Mike Fiore |
16:b630e18103e5 | 600 | typedef enum { |
Mike Fiore |
16:b630e18103e5 | 601 | /* Class A */ |
Mike Fiore |
16:b630e18103e5 | 602 | MOTE_MAC_LINK_CHECK_REQ = 0x02, |
Mike Fiore |
16:b630e18103e5 | 603 | MOTE_MAC_LINK_ADR_ANS = 0x03, |
Mike Fiore |
16:b630e18103e5 | 604 | MOTE_MAC_DUTY_CYCLE_ANS = 0x04, |
Mike Fiore |
16:b630e18103e5 | 605 | MOTE_MAC_RX_PARAM_SETUP_ANS = 0x05, |
Mike Fiore |
16:b630e18103e5 | 606 | MOTE_MAC_DEV_STATUS_ANS = 0x06, |
Mike Fiore |
16:b630e18103e5 | 607 | MOTE_MAC_NEW_CHANNEL_ANS = 0x07, |
Mike Fiore |
16:b630e18103e5 | 608 | MOTE_MAC_RX_TIMING_SETUP_ANS = 0x08, |
Mike Fiore |
16:b630e18103e5 | 609 | |
Mike Fiore |
16:b630e18103e5 | 610 | /* Class B */ |
Mike Fiore |
16:b630e18103e5 | 611 | MOTE_MAC_PING_SLOT_INFO_REQ = 0x09, |
Mike Fiore |
16:b630e18103e5 | 612 | MOTE_MAC_PING_SLOT_FREQ_ANS = 0x0a, |
Mike Fiore |
16:b630e18103e5 | 613 | MOTE_MAC_PING_SLOT_CHANNEL_ANS = 0x0a, |
Mike Fiore |
16:b630e18103e5 | 614 | MOTE_MAC_BEACON_TIMING_REQ = 0x0b, |
Mike Fiore |
16:b630e18103e5 | 615 | MOTE_MAC_BEACON_FREQ_ANS = 0x0c, |
Mike Fiore |
16:b630e18103e5 | 616 | |
Mike Fiore |
16:b630e18103e5 | 617 | /* Multitech */ |
Mike Fiore |
16:b630e18103e5 | 618 | MOTE_MAC_PING_REQ = 0x80, |
Mike Fiore |
16:b630e18103e5 | 619 | MOTE_MAC_CHANGE_CLASS = 0x81, |
Mike Fiore |
16:b630e18103e5 | 620 | MOTE_MAC_MULTIPART_START_REQ = 0x82, |
Mike Fiore |
16:b630e18103e5 | 621 | MOTE_MAC_MULTIPART_START_ANS = 0x83, |
Mike Fiore |
16:b630e18103e5 | 622 | MOTE_MAC_MULTIPART_CHUNK = 0x84, |
Mike Fiore |
16:b630e18103e5 | 623 | MOTE_MAC_MULTIPART_END_REQ = 0x85, |
Mike Fiore |
16:b630e18103e5 | 624 | MOTE_MAC_MULTIPART_END_ANS = 0x86 |
Mike Fiore |
16:b630e18103e5 | 625 | } MoteCommand; |
Mike Fiore |
16:b630e18103e5 | 626 | |
Mike Fiore |
16:b630e18103e5 | 627 | /*! |
Mike Fiore |
16:b630e18103e5 | 628 | * LoRaWAN server MAC commands |
Mike Fiore |
16:b630e18103e5 | 629 | */ |
Mike Fiore |
16:b630e18103e5 | 630 | typedef enum { |
Mike Fiore |
16:b630e18103e5 | 631 | /* Class A */ |
Mike Fiore |
16:b630e18103e5 | 632 | SRV_MAC_LINK_CHECK_ANS = 0x02, |
Mike Fiore |
16:b630e18103e5 | 633 | SRV_MAC_LINK_ADR_REQ = 0x03, |
Mike Fiore |
16:b630e18103e5 | 634 | SRV_MAC_DUTY_CYCLE_REQ = 0x04, |
Mike Fiore |
16:b630e18103e5 | 635 | SRV_MAC_RX_PARAM_SETUP_REQ = 0x05, |
Mike Fiore |
16:b630e18103e5 | 636 | SRV_MAC_DEV_STATUS_REQ = 0x06, |
Mike Fiore |
16:b630e18103e5 | 637 | SRV_MAC_NEW_CHANNEL_REQ = 0x07, |
Mike Fiore |
16:b630e18103e5 | 638 | SRV_MAC_RX_TIMING_SETUP_REQ = 0x08, |
Mike Fiore |
16:b630e18103e5 | 639 | |
Mike Fiore |
16:b630e18103e5 | 640 | /* Class B */ |
Mike Fiore |
16:b630e18103e5 | 641 | SRV_MAC_PING_SLOT_INFO_ANS = 0x09, |
Mike Fiore |
16:b630e18103e5 | 642 | SRV_MAC_PING_SLOT_FREQ_REQ = 0x0a, |
Mike Fiore |
16:b630e18103e5 | 643 | SRV_MAC_PING_SLOT_CHANNEL_REQ = 0x0a, |
Mike Fiore |
16:b630e18103e5 | 644 | SRV_MAC_BEACON_TIMING_ANS = 0x0b, |
Mike Fiore |
16:b630e18103e5 | 645 | SRV_MAC_BEACON_FREQ_REQ = 0x0c, |
Mike Fiore |
16:b630e18103e5 | 646 | |
Mike Fiore |
16:b630e18103e5 | 647 | /* Multitech */ |
Mike Fiore |
16:b630e18103e5 | 648 | SRV_MAC_PING_ANS = 0x80, |
Mike Fiore |
16:b630e18103e5 | 649 | SRV_MAC_CHANGE_CLASS = 0x81, |
Mike Fiore |
16:b630e18103e5 | 650 | SRV_MAC_MULTIPART_START_REQ = 0x82, |
Mike Fiore |
16:b630e18103e5 | 651 | SRV_MAC_MULTIPART_START_ANS = 0x83, |
Mike Fiore |
16:b630e18103e5 | 652 | SRV_MAC_MULTIPART_CHUNK = 0x84, |
Mike Fiore |
16:b630e18103e5 | 653 | SRV_MAC_MULTIPART_END_REQ = 0x85, |
Mike Fiore |
16:b630e18103e5 | 654 | SRV_MAC_MULTIPART_END_ANS = 0x86 |
Mike Fiore |
16:b630e18103e5 | 655 | } ServerCommand; |
Mike Fiore |
16:b630e18103e5 | 656 | |
Mike Fiore |
16:b630e18103e5 | 657 | /** |
Mike Fiore |
16:b630e18103e5 | 658 | * Random seed for software RNG |
Mike Fiore |
16:b630e18103e5 | 659 | */ |
Mike Fiore |
16:b630e18103e5 | 660 | void srand(uint32_t seed); |
Mike Fiore |
16:b630e18103e5 | 661 | |
Mike Fiore |
16:b630e18103e5 | 662 | /** |
Mike Fiore |
16:b630e18103e5 | 663 | * Software RNG for consistent results across differing hardware |
Mike Fiore |
16:b630e18103e5 | 664 | */ |
Mike Fiore |
16:b630e18103e5 | 665 | int rand(void); |
Mike Fiore |
16:b630e18103e5 | 666 | |
Mike Fiore |
16:b630e18103e5 | 667 | /** |
Mike Fiore |
16:b630e18103e5 | 668 | * Generate random number bounded by min and max |
Mike Fiore |
16:b630e18103e5 | 669 | */ |
Mike Fiore |
16:b630e18103e5 | 670 | int32_t rand_r(int32_t min, int32_t max); |
Mike Fiore |
16:b630e18103e5 | 671 | |
Mike Fiore |
16:b630e18103e5 | 672 | uint8_t CountBits(uint16_t mask); |
Mike Fiore |
16:b630e18103e5 | 673 | |
Mike Fiore |
16:b630e18103e5 | 674 | /** |
Mike Fiore |
16:b630e18103e5 | 675 | * Copy 3-bytes network order from array into LSB of integer value |
Mike Fiore |
16:b630e18103e5 | 676 | */ |
Mike Fiore |
16:b630e18103e5 | 677 | void CopyNetIDtoInt(const uint8_t* arr, uint32_t& val); |
Mike Fiore |
16:b630e18103e5 | 678 | |
Mike Fiore |
16:b630e18103e5 | 679 | /** |
Mike Fiore |
16:b630e18103e5 | 680 | * Copy LSB 3-bytes from integer value into array network order |
Mike Fiore |
16:b630e18103e5 | 681 | */ |
Mike Fiore |
16:b630e18103e5 | 682 | void CopyNetIDtoArray(uint32_t val, uint8_t* arr); |
Mike Fiore |
16:b630e18103e5 | 683 | |
Mike Fiore |
16:b630e18103e5 | 684 | /** |
Mike Fiore |
16:b630e18103e5 | 685 | * Copy 4-bytes network order from array in to integer value |
Mike Fiore |
16:b630e18103e5 | 686 | */ |
Mike Fiore |
16:b630e18103e5 | 687 | void CopyAddrtoInt(const uint8_t* arr, uint32_t& val); |
Mike Fiore |
16:b630e18103e5 | 688 | |
Mike Fiore |
16:b630e18103e5 | 689 | /** |
Mike Fiore |
16:b630e18103e5 | 690 | * Copy 4-bytes from integer in to array network order |
Mike Fiore |
16:b630e18103e5 | 691 | */ |
Mike Fiore |
16:b630e18103e5 | 692 | void CopyAddrtoArray(uint32_t val, uint8_t* arr); |
Mike Fiore |
16:b630e18103e5 | 693 | |
Mike Fiore |
16:b630e18103e5 | 694 | /** |
Mike Fiore |
16:b630e18103e5 | 695 | * Copy 3-bytes network order from array into integer value and multiply by 100 |
Mike Fiore |
16:b630e18103e5 | 696 | */ |
Mike Fiore |
16:b630e18103e5 | 697 | void CopyFreqtoInt(const uint8_t* arr, uint32_t& freq); |
Mike Fiore |
16:b630e18103e5 | 698 | |
Mike Fiore |
16:b630e18103e5 | 699 | /** |
Mike Fiore |
16:b630e18103e5 | 700 | * Reverse memory copy |
Mike Fiore |
16:b630e18103e5 | 701 | */ |
Mike Fiore |
16:b630e18103e5 | 702 | void memcpy_r(uint8_t *dst, const uint8_t *src, size_t n); |
Mike Fiore |
16:b630e18103e5 | 703 | |
Mike Fiore |
16:b630e18103e5 | 704 | } |
Mike Fiore |
16:b630e18103e5 | 705 | |
Mike Fiore |
16:b630e18103e5 | 706 | #endif |
Mike Fiore |
16:b630e18103e5 | 707 |