RadioShuttle Lib for the STM32 L4 Heltec Board

Dependents:   Turtle_RadioShuttle

RadioShuttle.h

Committer:
Helmut Tschemernjak
Date:
7 weeks ago
Revision:
11:91bc7ef20f21
Parent:
0:0c31756924a2

File content as of revision 11:91bc7ef20f21:

/*
 * 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
	 * The optional deviceID allows to specify a custom ID, e.g. for failover.
     */
    RSCode Startup(RadioType radioType, devid_t deviceID = 0);
    
    /*
     * 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