RadioShuttle Lib for the STM32 L4 Heltec Board

Dependents:   Turtle_RadioShuttle

Revision:
0:0c31756924a2
Child:
11:91bc7ef20f21
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RadioShuttle.h	Wed Feb 06 15:26:48 2019 +0000
@@ -0,0 +1,586 @@
+/*
+ * This is an unpublished work copyright
+ * (c) 2019 Helmut Tschemernjak
+ * 30826 Garbsen (Hannover) Germany
+ *
+ *
+ * Use is granted to registered RadioShuttle licensees only.
+ * Licensees must own a valid serial number and product code.
+ * Details see: www.radioshuttle.de
+ */
+
+#ifndef __RADIOSHUTTLE_H__
+#define __RADIOSHUTTLE_H__
+
+#ifdef ARDUINO
+#include "arduino-mbed.h"
+#include <time.h>
+#include <assert.h>
+#undef min
+#undef max
+#undef map
+#define MyTimeout Timeout
+#define MyTimer	Timer
+#define STATIC_ASSERT   _Static_assert
+#define ASSERT assert
+using namespace std;
+#define FEATURE_LORA	1
+
+#else
+
+#if defined(DEVICE_LPTICKER) || defined(DEVICE_LOWPOWERTIMER) // LOWPOWERTIMER in older mbed versions
+#define MyTimeout LowPowerTimeout
+#define MyTimer	LowPowerTimer
+#else
+#define MyTimeout Timeout
+#define MyTimer	Timer
+#endif
+
+#endif
+
+#include <list>
+#include <map>
+#include "radio.h"
+#include "RadioStatusInterface.h"
+#include "RadioSecurityInterface.h"
+
+#ifdef ARDUINO
+#define map	std::map // map clashes with Arduino map()
+#endif
+#ifdef FEATURE_LORA
+
+
+typedef enum RSErrorCode {
+    RS_NoErr    = 0,
+    RS_DuplicateAppID,
+    RS_AppID_NotFound,
+    RS_StationNotConnected,
+    RS_NoPasswordSet,
+    RS_PasswordSet,
+    RS_NoSecurityInterface,
+    RS_MsgID_NotFound,
+    RS_NoRadioConfigured,			// No radio added so far
+    RS_NoRadioAvailable,            // Radio could not be detected
+    RS_RadioNotFound,				// The specified radio is not available
+    RS_UnknownModemType,            // No FSK no LoRa modem type
+    RS_MessageSizeExceeded,			// Message size too long
+    RS_InvalidProductCode,			// license does not match
+    RS_InvalidParam,				// invalid parameter.
+    RS_OutOfMemory,					// unable to allocate memory
+} RSCode;
+
+
+
+// Shuttle
+class RadioShuttle
+{
+public:
+    typedef uint32_t	devid_t;
+    static const int DEV_ID_ANY = 0;
+    static const int TX_POWER_AUTO = 9999;
+    
+    struct RadioProfile {
+        int Frequency;      // in Hz
+        int Bandwidth;      // in Hz
+        int TXPower;        // in dBm
+        int SpreadingFaktor;// 7-12
+        int FrequencyOffset;// +/- in Hz
+    };
+    
+    enum RadioType {
+		RS_RadioType_Invalid = 0,
+        RS_Node_Offline,   	// Sleep mode until sending, < 10k RAM
+        RS_Node_Checking,   // Sleep mode, checks for messages regulary, < 10k RAM
+        RS_Node_Online,     // Always powered-on, < 10k RAM
+        RS_Station_Basic,	// Always active for one or more apps < 15k RAM
+        RS_Station_Server,	// Always active, lots of memory, routing options, < 1 MB RAM
+    };
+    
+    typedef void (*AppRecvHandler)(int AppID, devid_t stationID, int msgID, int status, void *data, int length);
+
+
+    enum MsgStatus {
+        MS_SentCompleted,			// A previous SendMsg has been sent
+        MS_SentCompletedConfirmed,	// A previous SendMsg has been sent and confirmed
+        MS_SentTimeout,				// A timeout occurred, number of retries exceeded
+        
+        MS_RecvData,				// A simple input message
+        MS_RecvDataConfirmed,		// Received a confirmed message
+        MS_NoStationFound,
+        MS_NoStationSupportsApp,
+        MS_AuthenicationRequired,
+        
+        MS_StationConnected,		// A confirmation that the connection was accepted
+        MS_StationDisconnected,		// A confirmation that the disconnect was accepted
+    };
+    
+    enum MsgFlags {
+        MF_Response			= 0x01, // A response from a previous request, or request
+        MF_NeedsConfirm		= 0x02, // Station needs to acknloglage receivd
+        MF_LowPriority		= 0x04, // Transfer within one minute
+        MF_HighPriority		= 0x08, // ImmEdate transfer
+        MF_MoreDataToCome	= 0x10, // Additional data is wait to be sent
+        MF_Connect			= 0x20, // Connect a node to the station with password
+        MF_Encrypted		= 0x40, // Message is encrypted
+        MF_Authentication	= 0x80,	// Message requires prior authentication
+        MF_SwitchOptions	= 0x100,// Tell the node to switch the channel for this trans ID
+        MF_FlagsMask		= 0b111111111, // max flags for the RadioShuttle protocol, see msgFlags : 9
+        /*
+         * optional control flags are here.
+         */
+        CF_FreeData			= 0x200, // if a data buffer is provided, free it afterwords.
+        CF_CopyData			= 0x400, // create a copy of the data.
+    };
+    
+    struct RadioStats {
+        int RxPackets;
+        int TxPackets;
+        int RxErrorCount;
+        int channelBusyCount;
+        long long rxBytes;
+        long long txBytes;
+        int noAuthMessageCount;
+        int unkownMessageCount;
+        int appNotSupported;
+        int protocolError;
+        int noMemoryError;
+        int decryptError;
+		int lastRSSI;
+		int lastSNR;
+		devid_t lastRXdeviceID;
+        time_t startupTime;
+    };
+    
+    /*
+     * Constructor requires a radio
+     */
+    RadioShuttle(const char *deviceName);
+
+    /*
+     * Destructor, stop radios, free resources
+     */
+    ~RadioShuttle();
+    
+    /*
+     * Adds a license to use the RadioShuttle, licenses are
+     * may be bundled with the board or are available for 
+     * purchase at: www.radioshuttle.com
+     * The license is bound to a special board ID and a fixed
+     * node deviceID.
+     */
+    RSCode AddLicense(devid_t deviceID, uint32_t productCode);
+
+    /*
+     * Adds a new radio with a given profile
+     * Optional profile, must be called before startup
+     */
+    RSCode AddRadio(Radio *radio, RadioModems_t modem, const struct RadioProfile *profile = NULL);
+    
+    /*
+     * This allows to swtich between RS_Node_Offline and RS_Node_Online
+     * after the Startup() is already completed.
+     */
+    RSCode UpdateNodeStartup(RadioType newRadioType);
+
+    /*
+     * The status interface allows custom status callbacks for
+     * reporting send/receive/timeout activity e.g.: LED blinks
+     */
+    RSCode AddRadioStatus(RadioStatusInterface *statusIntf);
+    
+    /*
+     * Support function for password hash generation and encryption
+     * The external API interface has the advantage that the security
+     * can be upgraded independently of the RadioShuttle library.
+     * AES hardware accelleration can be one reason for it.
+     */
+    RSCode AddRadioSecurity(RadioSecurityInterface *securityIntf);
+
+    /*
+     * Starts the service with the specified RadioType
+     */
+    RSCode Startup(RadioType radioType);
+    
+    /*
+     * get the current radio type
+     */
+    RadioType GetRadioType(void);
+    
+    /*
+     * Registers an application, the AppType is unique worldwide
+     * and must be used for sending and receiving app data.
+     * Multiple AppID registrations are supported.
+     * The password can be a string to the password,
+     * in case of binary passwd data the pwLen must be set.
+     * If the password is set, clients must call Connect() prior
+     * to any SendMsg.
+     */
+    RSCode RegisterApplication(int AppID, AppRecvHandler, void *password = NULL, int pwLen = 0);
+    /*
+     * Removes the AppID
+     */
+    RSCode DeRegisterApplication(int AppID);
+    
+    /*
+     * Check if the password is specified for an app
+     */
+    RSCode  AppRequiresAuthentication(int AppID);
+                       
+    /*
+     * Start a pairing process to connect a node to a station
+     */
+    RSCode StartPairing(char *name = NULL);
+    /*
+     * Connect the node against a station, it can be called multiple times
+     * if communication with multiple station IDs is used.
+     * The connect verifies the password against the app of remote station.
+     */
+    RSCode Connect(int AppID, devid_t stationID = DEV_ID_ANY);
+    
+    /*
+     * Inform the station that an app is discontinuing
+     */
+    RSCode Disconnect(int AppID, devid_t stationID = ~0);
+    
+    /*
+     * Prepare sending data to a remote application
+     * The request is queued for sending.
+     * valid flags see enum MsgFlags
+     * txPower allows to overwrite the txPower in dBm.
+     * The data is busy until the AppRecvHandler is called
+     */
+    RSCode SendMsg(int AppID, void *data, int len, int flags = 0, devid_t stationID = DEV_ID_ANY, int txPower = TX_POWER_AUTO, int *msgID = NULL);
+    /*
+     * Removes a message from the queue
+     */
+    RSCode KillMsg(int AppID, int msgID);
+    
+    /*
+     * Sets a new profile for a given radio with a given profile
+     * This can be called anytime after the RadioShuttle startup.
+     */
+    RSCode UpdateRadioProfile(Radio *radio, RadioType radioType, const struct RadioProfile *profile);
+    
+    /*
+     * Sets the size value to the largest messages available
+     * for all configured radios
+     * The flags are important because encrypted messages need more space
+     */
+    RSCode MaxMessageSize(int *size, int msgFlags = 0);
+    
+    /*
+     * Get statistics of all messages and errors
+     * A pointer reference containing the RadioStats must be provided.
+     * The statistics contain the information of the first radio, unless
+     * the RadioEntry parameter is provided for a specified radio.
+     */
+    RSCode GetStatistics(struct RadioStats **stats, Radio *radio = NULL);
+    
+
+    /*
+     * Dump all received and sent packets
+     */
+    void EnablePacketTrace(devid_t stationID = DEV_ID_ANY, bool sents = false, bool recvs = false, Radio *radio = 0);
+    
+    /*
+     * The RadioShuttle is idle when there are no ongoing jobs, etc.
+     */
+    bool Idle(void);
+    
+    /*
+     * Converts a RadioShuttle error code into a string.
+     */
+    const char *StrError(RSErrorCode err);
+    
+    /*
+     * Converts the radio type into a clear text name
+     */
+    const char *GetRadioName(RadioType radioType);
+    
+    /*
+     * Starts the main RadioShuttle loop, returns 0 when nothing needs to be done
+     * Retuns > 0 when it should be called again
+     * RunShuttle is called on user level (non-interrupt level)
+     */
+    int RunShuttle(void);
+    
+private:
+    enum PacketStatus {
+        PS_Queued,
+        PS_Sent,
+        PS_GotSendSlot,
+        PS_WaitForConfirm,
+        PS_SendRequestCompleted,
+        PS_SendRequestConfirmed,
+        PS_SendTimeout,
+    };
+
+    struct RadioEntry; // forward decl.
+    struct ReceivedMsgEntry {
+        void *RxData;
+        int RxSize;
+        int rssi;
+        int snr;
+        struct RadioEntry *re;
+    };
+    
+
+    struct RadioEntry {
+        Radio *radio;
+        RadioEvents_t radioEvents;
+        const RadioProfile *profile;
+        RadioModems_t modem;
+        volatile signed char _CADdetected;
+        uint16_t lastTxSize;
+        int lastTxPower;
+        int timeOnAir12Bytes;
+        struct ReceivedMsgEntry rxMsg;
+        struct RadioStats rStats;
+        int maxTimeOnAir;
+        int retry_ms;
+        uint32_t lastTxDone;
+        volatile bool txDoneReceived;
+        volatile const char *intrDelayedMsg;
+        uint32_t random;
+        uint32_t random2;
+    };
+    
+    struct AppEntry {
+        int AppID;
+        AppRecvHandler handler;
+        int msgID;
+        void *password;
+        uint8_t pwLen;
+        bool pwdConnected;
+    };
+    
+    struct ConnectEntry {
+        devid_t stationID;
+        int AppID;
+        bool authorized;
+        uint32_t random[2];
+    };
+    
+    struct SendMsgEntry {
+        int AppID;
+        void *data;
+        int len;
+        int flags;
+        devid_t stationID;
+        int txPower;
+        int msgID;
+        int retryCount;
+        bool releaseData;
+        AppEntry *aep;
+        ConnectEntry *cep;
+        PacketStatus pStatus;
+        int respWindow;
+        uint32_t responseTime;
+        uint32_t lastSentTime;
+        uint32_t lastTimeOnAir;
+        uint32_t confirmTimeout;
+        int retry_ms;
+        uint8_t channel;
+        uint8_t factor;
+        uint32_t securityData[8];
+        uint32_t tmpRandom[2];
+    };
+    
+    struct SignalStrengthEntry {
+        int rx_dBm;
+        devid_t stationID;
+        time_t lastUpdate;
+        int rcnCnt;
+    };
+    
+    struct TimeOnAirSlotEntry {
+        devid_t stationID;
+        int AppID;
+        uint8_t channel;
+        uint8_t factor;
+        uint32_t busy_time;
+        int busy_ms;
+    };
+    
+    struct EncryptionHeader {
+        uint32_t version : 3;	// 3-bit encryption version
+		uint32_t dataSum : 13;	// Checksum of all packet data
+	    uint16_t msgSize : 11;	// Same as in RadioHeader
+        uint16_t msgID   : 5;	// Same as in RadioHeader
+        uint32_t random;
+    };
+    
+    enum RadioHeaderVersions {
+        RSMagic					= 0b1011,
+        RSHeaderFully_v1 		= 0b001,
+        RSHeaderPacked_v1		= 0b010,
+        RSHeaderFullySize_v1 	= 16,
+        RSHeaderPackedSize_v1 	= 12,
+        msgIDv1Mask 			= 0b11111,
+        Packedv1MaxApps			= (1<<11)-1,
+        Packedv1MaxRespWindow 	= (1<<11)-1,
+        Fullyv1MaxRespWindow 	= (1<<16)-1,
+        Packedv1MaxDeviceID 	= (1<<21)-1,
+        MaxWinScale				= (1<<4)-1,
+        DataSumBits				= 13,
+    };
+    
+    /*
+     * The RadioShuttle communication header (similar to UDP),
+     * but optimized for radios.
+     * A packet crc checksum is done on the link layer, no field needed here
+     * The source always sends this request to the other side
+     * - Check immediately for a response after sending
+     * - Check again for a response in responseWindow*2 milliseconds
+     *
+     */
+    struct RadioHeader {
+        // 4 bytes
+        uint16_t magic   : 4;		// 4-bit magic,
+        uint16_t version : 3;		// 3-bit version
+        uint16_t msgFlags: 9;		// e.g.: Request/Response NeedsConfirm, Priority, Encrypted
+        union {
+            struct {
+                uint16_t msgSize : 11;	// 11-bit message size allows a maximum of 2048 bytes
+                uint16_t msgID   : 5;	// ID for the app communication, truncated to 5-bit
+            } data;
+            struct {
+                uint16_t channel : 4;	// Specify the channel to switch to for this transaction
+                uint16_t factor  : 3;   // Specifies the spreading factor index (6-12)
+                uint16_t winScale: 4;	// Set the window scaling factor
+            } option;
+        } s;
+        union {
+            struct { // 8 bytes
+                uint32_t appID      : 11;	// First 2048 application identifiers for the request
+                devid_t destination : 21;	// Device ID of the destination 2^11 first 2 million devices
+                uint32_t respWindow : 11;	// Wait for the respWindow (ms) max 2048 ms before sending data.
+                devid_t source      : 21;	// Device ID of the destination 2^11 first 2 million devices
+            } packedv1;
+            struct { // 12 bytes
+                uint16_t appID;			// Application identifier for the request
+                uint16_t respWindow;	// We listen for a 2nd resonse in responseWindow (ms)
+                devid_t destination;	// Device ID of the destination
+                devid_t source;			// DeviceID of the source
+            } fullyv1;
+        } u;
+    };
+    
+    struct WireDumpSettings {
+        devid_t stationID;
+        bool sents;
+        bool recvs;
+        Radio *radio;
+    };
+    
+    /*
+     * Radio specific callbacks are public to allow C wrapper function to call us.
+     */
+public:
+    void RS_TxDone(Radio *radio, void *userData);
+    void RS_RxDone(Radio *radio, void *userData, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr);
+    void RS_TxTimeout(Radio *radio, void *userData);
+    void RS_RxTimeout(Radio *radio, void *userData);
+    void RS_RxError(Radio *radio, void *userData);
+    void RS_CadDone(Radio *radio, void *userData, bool channelActivityDetected);
+
+private:
+    /*
+ 	 * The CadDetection will turn the radio into a Cad listening mode
+     * to detect radio-modulated signals on the channel.
+     * This takes a few ms (at SF7) and the interrupt RS_CadDone will report
+     * the status.
+     * The radio->_CADdetected contains the result (0 for idle, 1 for busy)
+     */
+    bool CadDetection(RadioEntry *re);
+    
+    /*
+     * init the RX/TX of the Radio.
+     */
+    RSCode _initRadio(RadioEntry *re);
+    
+    /*
+     * The processing function returns a status code if it has been able to process
+     * this message. true' for messages processed, false for messages skipped.
+     */
+    bool ProcessResponseMessage(ReceivedMsgEntry *rme, AppEntry *aep, SendMsgEntry *mep, int  msgFlags, void *data, int len, devid_t source, devid_t respWindow, uint8_t channel, uint8_t factor);
+    
+    bool ProcessRequestMessage(ReceivedMsgEntry *rme, AppEntry *aep, int  msgFlags, void *data, int len, int msgID, devid_t source, uint32_t respWindow, uint8_t channel, uint8_t factor);
+    
+    void MessageSecurityError(ReceivedMsgEntry *rme, AppEntry *aep, int msgID, devid_t source, uint8_t channel, uint8_t factor);
+    
+    void SaveTimeOnAirSlot(devid_t destination, int AppID, int msgFlags, int respWindow, uint8_t channel, uint8_t factor, int timeOnAir);
+    
+    /*
+     * Our main send function is responsible for header packing,
+     * compression and encryption, and finally sends a packet via the radio.
+     * It returns true if we have been able to sent the message.
+     */
+    bool SendMessage(RadioEntry *re, void *data, int len, int msgID, int AppID, devid_t stationID, int flags, int txPower, int respWindow, uint8_t channel, uint8_t factor);
+    
+    /*
+     * We keep a little cache list of the power needed for different stations
+     * This saves a lot of energy and reduces signal strength to keep the network
+     * less busy. For example, other radio networks need not receive our signals.
+     */
+    int CalculateTXPower(RadioEntry *re, devid_t stationID);
+    bool UpdateSignalStrength(devid_t stationID, int dBm);
+    bool DeleteSignalStrength(devid_t stationID);
+    
+    
+    /*
+     * Our main receive function is responsible for header unpacking,
+     * de-compression and decryption, and finally provides the unpacked data.
+     * It returns true if we have been able to detect and unpack the message.
+     */
+    bool ReceiveMessage(ReceivedMsgEntry *rme, void **data, int &len, int &msgID, int &AppID, int &flags, devid_t &destination, devid_t &source, int &respWindow, uint8_t &channel, uint8_t &factor);
+    
+    /*
+     * We need to process all input messages, the _recv list should be empty ASAP
+     * because the data is only temporarily available until the next packet.
+     */
+    void ProcessReceivedMessages(void);
+    
+    
+    void PacketTrace(RadioEntry *re, const char *name, RadioHeader *rh, void *data, int len, bool sent, ReceivedMsgEntry *rme);
+    
+    /*
+     * A dummy function which is called for every timeout, the timer wakes up
+     * the sleep and therefore the RunShuttle starts processing.
+     */
+    void TimeoutFunc(void);
+    
+    uint32_t GetDataSum(int maxbits, void *data, int len);
+    
+    
+private:
+    const char *_name;
+    devid_t _deviceID;
+    devid_t _tmpdeviceID;
+    uint8_t _uuid[16];
+    RadioType _radioType;
+    int _maxMTUSize;
+    list<RadioEntry> _radios;
+    map<int, AppEntry> _apps;
+    map<std::pair<devid_t,int>, ConnectEntry> _connections;
+    list<SendMsgEntry> _sends;
+    list<ReceivedMsgEntry> _recvs;
+    map<devid_t, SignalStrengthEntry> _signals;
+    list<TimeOnAirSlotEntry> _airtimes;
+    MyTimeout *timer;
+    MyTimer *ticker;
+    volatile uint32_t prevWakeup;
+    int SetTimerCount;
+    
+    static const RadioProfile defaultProfile[];
+    volatile bool busyInShuttle;
+    WireDumpSettings _wireDumpSettings;
+    const static int MAX_SENT_RETRIES = 3;	// Defines the number of retries of sents (with confirm)
+    const static int RX_TIMEOUT_1HOUR	= 3600000;
+    RadioStatusInterface *_statusIntf;
+    RadioSecurityInterface *_securityIntf;
+    uint32_t _;
+};
+
+#endif // __RADIOSHUTTLE_H__
+#endif // FEATURE_LORA