RadioShuttle Lib for the STM32 L4 Heltec Board
Dependents: Turtle_RadioShuttle
RadioShuttle.h
- Committer:
- Helmut Tschemernjak
- Date:
- 2019-04-14
- Revision:
- 13:591254bed18b
- Parent:
- 11:91bc7ef20f21
File content as of revision 13:591254bed18b:
/* * 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