diff --git a/Firmware/LoRaSerial_Firmware/Begin.ino b/Firmware/LoRaSerial_Firmware/Begin.ino index 961d93fc..0cf06f9a 100644 --- a/Firmware/LoRaSerial_Firmware/Begin.ino +++ b/Firmware/LoRaSerial_Firmware/Begin.ino @@ -100,13 +100,17 @@ void channelTimerHandler() channelTimerStart = millis(); //Record when this ISR happened. Used for calculating clock sync. radioCallHistory[RADIO_CALL_channelTimerHandler] = channelTimerStart; - //Restore the full timer interval - channelTimer.setInterval_MS(settings.maxDwellTime, channelTimerHandler); - channelTimerMsec = settings.maxDwellTime; //ISR update - digitalWrite(pin_hop_timer, channelNumber & 1); + //If the last timer was used to sync clocks, restore full timer interval + if (reloadChannelTimer == true) + { + reloadChannelTimer = false; + channelTimer.setInterval_MS(settings.maxDwellTime, channelTimerHandler); + channelTimerMsec = settings.maxDwellTime; //ISR update + } if (settings.frequencyHop) { + digitalWrite(pin_hop_timer, ((channelNumber + 1) % settings.numberOfChannels) & 1); triggerEvent(TRIGGER_CHANNEL_TIMER_ISR); timeToHop = true; } diff --git a/Firmware/LoRaSerial_Firmware/Commands.ino b/Firmware/LoRaSerial_Firmware/Commands.ino index ad2b3528..2bd08c72 100644 --- a/Firmware/LoRaSerial_Firmware/Commands.ino +++ b/Firmware/LoRaSerial_Firmware/Commands.ino @@ -411,15 +411,14 @@ bool commandAT(const char * commandString) //Clock synchronization systemPrintln(" Clock Synchronization"); - systemPrint(" TX Time: "); - systemPrint(txTimeUsec / 1000., 4); - systemPrintln(" mSec"); - systemPrint(" RX Time: "); - systemPrint(rxTimeUsec / 1000., 4); - systemPrintln(" mSec"); - systemPrint(" Total Time: "); - systemPrint((txTimeUsec + rxTimeUsec) / 1000., 5); - systemPrintln(" mSec"); + systemPrint(" ACK Time: "); + systemPrint(txDataAckUsec); + systemPrint(" HEARTBEAT Time: "); + systemPrint(txHeartbeatUsec); + systemPrintln(" uSec"); + systemPrint(" SYNC_CLOCKS Time: "); + systemPrint(txSyncClockUsec); + systemPrintln(" uSec"); systemPrint(" Uptime: "); deltaMillis = millis(); systemPrintTimestamp(deltaMillis); @@ -1068,6 +1067,7 @@ const COMMAND_ENTRY commands[] = {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "DebugTransmit", &tempSettings.debugTransmit}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "DebugSerial", &tempSettings.debugSerial}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "DisplayRealMillis", &tempSettings.displayRealMillis}, + {'D', 0, 1, 0, 1000, 0, TYPE_U16, valInt, "OverHeadtime", &tempSettings.overheadTime}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "PrintAckNumbers", &tempSettings.printAckNumbers}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "PrintChannel", &tempSettings.printChannel}, {'D', 1, 0, 0, 1, 0, TYPE_BOOL, valInt, "PrintFrequency", &tempSettings.printFrequency}, @@ -1102,12 +1102,10 @@ const COMMAND_ENTRY commands[] = {'R', 0, 1, 0, 1, 0, TYPE_BOOL, valInt, "EncryptData", &tempSettings.encryptData}, {'R', 0, 1, 0, 0, 0, TYPE_KEY, valKey, "EncryptionKey", &tempSettings.encryptionKey}, {'R', 0, 0, 0, 255, 0, TYPE_U8, valInt, "FramesToYield", &tempSettings.framesToYield}, - {'R', 0, 0, 10, 2000, 0, TYPE_U16, valInt, "FrameTimeout", &tempSettings.serialTimeoutBeforeSendingFrame_ms}, {'R', 0, 0, 250, 65535, 0, TYPE_U16, valInt, "HeartBeatTimeout", &tempSettings.heartbeatTimeout}, {'R', 0, 0, 0, 255, 0, TYPE_U8, valInt, "MaxResends", &tempSettings.maxResends}, {'R', 0, 1, 0, 255, 0, TYPE_U8, valInt, "NetID", &tempSettings.netID}, {'R', 0, 1, 0, 2, 0, TYPE_U8, valInt, "OperatingMode", &tempSettings.operatingMode}, - {'R', 0, 1, 0, 1000, 0, TYPE_U16, valInt, "OverHeadtime", &tempSettings.overheadTime}, {'R', 0, 0, 0, 1, 0, TYPE_BOOL, valServer, "Server", &tempSettings.server}, {'R', 0, 1, 0, 1, 0, TYPE_BOOL, valInt, "VerifyRxNetID", &tempSettings.verifyRxNetID}, @@ -1118,6 +1116,7 @@ const COMMAND_ENTRY commands[] = {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "FlowControl", &tempSettings.flowControl}, {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "InvertCts", &tempSettings.invertCts}, {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "InvertRts", &tempSettings.invertRts}, + {'S', 0, 0, 10, 2000, 0, TYPE_U16, valInt, "SerialDelay", &tempSettings.serialTimeoutBeforeSendingFrame_ms}, {'S', 0, 0, 0, 0, 0, TYPE_SPEED_SERIAL, valSpeedSerial, "SerialSpeed", &tempSettings.serialSpeed}, {'S', 0, 0, 0, 1, 0, TYPE_BOOL, valInt, "UsbSerialWait", &tempSettings.usbSerialWait}, diff --git a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino index be1d3708..b3ab6c2e 100644 --- a/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino +++ b/Firmware/LoRaSerial_Firmware/LoRaSerial_Firmware.ino @@ -66,11 +66,13 @@ const int FIRMWARE_VERSION_MINOR = 0; //Frame lengths #define MP_HEARTBEAT_BYTES 0 //Number of data bytes in the MP_HEARTBEAT frame #define P2P_FIND_PARTNER_BYTES sizeof(unsigned long) //Number of data bytes in the FIND_PARTNER frame -#define P2P_SYNC_CLOCKS_BYTES (sizeof(uint8_t) + sizeof(unsigned long)) //Number of data bytes in the SYNC_CLOCKS frame +#define P2P_SYNC_CLOCKS_BYTES (sizeof(uint8_t) + sizeof(unsigned long) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t)) //Number of data bytes in the SYNC_CLOCKS frame #define P2P_ZERO_ACKS_BYTES sizeof(unsigned long) //Number of data bytes in the ZERO_ACKS frame #define P2P_HEARTBEAT_BYTES sizeof(unsigned long) //Number of data bytes in the HEARTBEAT frame #define P2P_ACK_BYTES sizeof(unsigned long) //Number of data bytes in the ACK frame -#define VC_HEARTBEAT_BYTES 0 //Number of data bytes in the VC_HEARTBEAT frame +#define VC_HEARTBEAT_BYTES 1 + 1 + 1 + 16 + 4 //Number of data bytes in the VC_HEARTBEAT frame + +#define TX_TO_RX_USEC 680 //Time from TX to RX transactionComplete in microseconds //Bit 0: Signal Detected indicates that a valid LoRa preamble has been detected //Bit 1: Signal Synchronized indicates that the end of Preamble has been detected, the modem is in lock @@ -125,14 +127,16 @@ uint8_t pin_hop_timer = PIN_UNDEFINED; #define RADIO_USE_BAD_FRAMES_LED BLUE_LED //Blue #define RADIO_USE_BAD_CRC_LED YELLOW_LED //Yellow -#define LED_MP_HOP_CHANNEL BLUE_LED -#define LED_MP_HEARTBEAT YELLOW_LED +#define LED_MP_HEARTBEAT BLUE_LED +#define LED_MP_HOP_CHANNEL YELLOW_LED #define CYLON_TX_DATA_LED BLUE_LED #define CYLON_RX_DATA_LED YELLOW_LED #define LED_ON HIGH #define LED_OFF LOW + +#define LED_MAX_PULSE_WIDTH 24 //mSec //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Radio Library @@ -170,6 +174,7 @@ SAMDTimer channelTimer(TIMER_TCC); //Available: TC3, TC4, TC5, TCC, TCC1 or TCC2 volatile uint16_t channelTimerMsec; //Last value programmed into the channel timer volatile unsigned long channelTimerStart = 0; //Tracks how long our timer has been running since last hop volatile bool timeToHop = false; //Set by channelTimerHandler to indicate that hopChannel needs to be called +volatile bool reloadChannelTimer = false; //When set channel timer interval needs to be reloaded with settings.maxDwellTime uint16_t petTimeout = 0; //A reduced amount of time before WDT triggers. Helps reduce amount of time spent petting. unsigned long lastPet = 0; //Remebers time of last WDT pet. @@ -363,10 +368,8 @@ bool writeOnCommandExit = false; //Goes true if user specifies ATW command //Global variables - LEDs //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -int trainCylonNumber = 0b0001; -int trainCylonDirection = -1; - -unsigned long lastTrainBlink = 0; //Controls LED during training +uint8_t cylonLedPattern = 0b0001; +bool cylonPatternGoingLeft; //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Global variables - Radio (General) @@ -389,19 +392,8 @@ float frequencyCorrection = 0; //Adjust receive freq based on the last packet re volatile bool hop = true; //Clear the DIO1 hop ISR when possible -//RSSI must be above these negative numbers for LED to illuminate -const int rssiLevelLow = -150; -const int rssiLevelMed = -120; -const int rssiLevelHigh = -100; -const int rssiLevelMax = -70; int rssi; //Average signal level, measured during reception of a packet -//LED control values -uint32_t ledPreviousRssiMillis; -uint32_t ledHeartbeatMillis; -int8_t ledRssiCount; //Pulse width count for LED display -int8_t ledRssiValue; //Target pulse width count for LED display - //Link quality metrics uint32_t datagramsSent; //Total number of datagrams sent uint32_t datagramsReceived; //Total number of datagrams received @@ -439,6 +431,8 @@ unsigned long radioStateHistory[RADIO_MAX_STATE]; uint8_t packetLength = 0; //Total bytes received, used for calculating clock sync times in multi-point mode int16_t msToNextHopRemote; //Can become negative +uint8_t clockSyncIndex; +CLOCK_SYNC_DATA clockSyncData[16]; bool requestYield = false; //Datagram sender can tell this radio to stop transmitting to enable two-way comm unsigned long yieldTimerStart = 0; @@ -585,13 +579,18 @@ char platformPrefix[25]; //Used for printing platform specific device name, ie " //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- bool clockSyncReceiver; //Receives and processes the clock synchronization +bool startChannelTimerPending; //When true, call startChannelTimer in transactionCompleteISR //RX and TX time measurements uint32_t rxTimeUsec; uint32_t txTimeUsec; uint16_t txRxTimeMsec; +uint32_t txSetChannelTimerMicros; //Timestamp when millis is read in TX routine to set channel timer value uint32_t transactionCompleteMicros; //Timestamp at the beginning of the transactionCompleteIsr routine +uint32_t txDataAckUsec; //Time in microseconds to transmit the DATA_ACK frame +uint32_t txHeartbeatUsec; //Time in microseconds to transmit the HEARTBEAT frame +uint32_t txSyncClockUsec; //Time in microseconds to transmit the SYNC_CLOCKS frame uint32_t txDatagramMicros; //Timestamp at the beginning of the transmitDatagram routine uint16_t maxFrameAirTime; //Air time of the maximum sized message unsigned long remoteSystemMillis; //Millis value contained in the received message diff --git a/Firmware/LoRaSerial_Firmware/Radio.ino b/Firmware/LoRaSerial_Firmware/Radio.ino index 890e8262..541e4cce 100644 --- a/Firmware/LoRaSerial_Firmware/Radio.ino +++ b/Firmware/LoRaSerial_Firmware/Radio.ino @@ -558,6 +558,7 @@ void hopChannel(bool moveForwardThroughTable, uint8_t channelCount) //Select the new frequency setRadioFrequency(radioStateTable[radioState].rxState); + blinkChannelHopLed(true); } //Determine the time in milliseconds when channel zero is reached again @@ -702,9 +703,17 @@ void printSX1276Registers() void transactionCompleteISR(void) { transactionCompleteMicros = micros(); - triggerEvent(TRIGGER_TRANSACTION_COMPLETE); radioCallHistory[RADIO_CALL_transactionCompleteISR] = millis(); + triggerEvent(TRIGGER_TRANSACTION_COMPLETE); + //Start the channel timer if requested + if (startChannelTimerPending) + { + startChannelTimer(); //transactionCompleteISR, upon TX or RX of SYNC_CLOCKS + startChannelTimerPending = false; //transactionCompleteISR, upon TX or RX of SYNC_CLOCKS + } + + //Signal the state machine that a receive or transmit has completed transactionComplete = true; } @@ -729,51 +738,6 @@ void updateHopISR() } } -//As we complete linkup, different airspeeds exit at different rates -//We adjust the initial clock setup as needed -int16_t getLinkupOffset() -{ - int linkupOffset = 0; - - switch (settings.airSpeed) - { - default: - break; - case (40): - linkupOffset = 0; - break; - case (150): - linkupOffset = 0; - break; - case (400): - linkupOffset = 0; - break; - case (1200): - linkupOffset = 0; - break; - case (2400): - linkupOffset = 0; - break; - case (4800): - linkupOffset = 0; - break; - case (9600): - linkupOffset = 0; - break; - case (19200): - linkupOffset = 0; - break; - case (28800): - linkupOffset = 0; - break; - case (38400): - linkupOffset = 0; - break; - } - - return (settings.maxDwellTime - getReceiveCompletionOffset() - linkupOffset); //Reduce the default window by the offset -} - //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Table for use in calculating the software CRC-16 //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -925,15 +889,24 @@ bool xmitDatagramP2PSyncClocks() memcpy(endOfTxData, ¤tMillis, sizeof(currentMillis)); endOfTxData += sizeof(unsigned long); + memcpy(endOfTxData, &txHeartbeatUsec, sizeof(txHeartbeatUsec)); + endOfTxData += sizeof(txHeartbeatUsec); + + memcpy(endOfTxData, &txSyncClockUsec, sizeof(txSyncClockUsec)); + endOfTxData += sizeof(txSyncClockUsec); + + memcpy(endOfTxData, &txDataAckUsec, sizeof(txDataAckUsec)); + endOfTxData += sizeof(txDataAckUsec); + /* - endOfTxData ---. - | - V - +----------+---------+----------+------------+---------+---------+----------+ - | Optional | | Optional | Optional | Channel | | | - | NET ID | Control | C-Timer | SF6 Length | Number | Millis | Trailer | - | 8 bits | 8 bits | 2 bytes | 8 bits | 1 byte | 4 bytes | n Bytes | - +----------+---------+----------+------------+---------+---------+----------+ + endOfTxData ---. + | + V + +----------+---------+----------+------------+---------+---------+-------------+-------------+-----------+----------+ + | Optional | | Optional | Optional | Channel | | HEARTBEAT | SYNC_CLOCKS | DATA_ACK | | + | NET ID | Control | C-Timer | SF6 Length | Number | Millis | Micros | Micros | Micros | Trailer | + | 8 bits | 8 bits | 2 bytes | 8 bits | 1 byte | 4 bytes | 4 Bytes | 4 Bytes | 4 Bytes | n Bytes | + +----------+---------+----------+------------+---------+---------+-------------+-------------+-----------+----------+ */ //Verify the data length @@ -1420,14 +1393,14 @@ bool xmitVcHeartbeat(int8_t addr, uint8_t * id) *txData = (uint8_t)(endOfTxData - txData); /* - endOfTxData ---. - | - V - +----------+---------+----------+------------+----------+---------+----------+---------+----------+ - | Optional | | Optional | Optional | | | | | Optional | - | NET ID | Control | C-Timer | SF6 Length | DestAddr | SrcAddr | Src ID | millis | Trailer | - | 8 bits | 8 bits | 2 bytes | 8 bits | 8 bits | 8 bits | 16 Bytes | 4 Bytes | n Bytes | - +----------+---------+----------+------------+----------+---------+----------+---------+----------+ + endOfTxData ---. + | + V + +----------+---------+----------+------------+----------+----------+---------+----------+---------+----------+ + | Optional | | Optional | Optional | | | | | | Optional | + | NET ID | Control | C-Timer | SF6 Length | Length | DestAddr | SrcAddr | Src ID | millis | Trailer | + | 8 bits | 8 bits | 2 bytes | 8 bits | 8 bits | 8 bits | 8 bits | 16 Bytes | 4 Bytes | n Bytes | + +----------+---------+----------+------------+----------+----------+---------+----------+---------+----------+ */ //Verify the data length @@ -1572,6 +1545,110 @@ bool xmitVcZeroAcks(int8_t destVc) //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Datagram reception +// +//Security or lack there of: +// +//The comments below outline the radio parameters and how they can be determined by +//listening to the radio traffic. The required parameters are followed by one or +//more step numbers indicating the step which determines the parameter's value. The +//comments further describe the steps that can be taken to reduce the attack surface +//for the LoRaSerial network. +// +//The following settings define the LoRaSerial radio network: +// +// AirSpeed - 3, 4, 5 +// AutoTune - This is receive only +// Bandwidth - 3 +// CodingRate - 5 +// FrequencyHop - 1 +// FrequencyMax - 1 +// FrequencyMin - 1 +// MaxDwellTime - 1 +// NumberOfChannels - 1 +// PreambleLength - 4 +// SpreadFactor - 4 +// SyncWord - 4 +// TxPower +// +// Protocol settings: +// +// DataScrambling - 6 +// EnableCRC16 - 8 +// EncryptData - 7 +// EncryptionKey - 7 +// FramesToYield +// HeartBeatTimeout - 12 +// MaxResends - 14 +// NetID - 9 +// OperatingMode - 10, 11 +// Server - 10, 11 +// VerifyRxNetID - 9 +// +//Attempting to attack a network of LoRaSerial radios would be done with the following +//steps: +// +// 1. Locate the frequencies that the network is using +// a. Listen for traffic on a specific channel to determine if the network is using +// that channel +// b. Repeat across all of the channels in the available frequency band +// 2. Once the frequencies are identified, attempt to determine the channel sequence +// by monitoring when traffic occurs on each of the identified frequencies, this +// allows the attacker to construct the hop table for the network +// 3. Look at the bandwidth utilized by the radio network. The signal for each +// symbol is a ramp that spans the bandwidth selected for use +// 4. Using a receiver that uses the opposite ramp to generate the sub frequencies +// within the bandwidth to decode the symbols. By monitoring the network traffic +// it is possible to determine the spreadfactor since there are 2^SF sub frequencies +// that will be used within the bandwidth +// 5. Now that the spread factor is known, the next step is to determine the coding +// rate used for forward error correction. Here 4 data bits are converted into +// between 5 to 8 bits in the transmitted frame +// 6. Look at the signal for multiple transmissions, does this signal have a DC offset? +// The data scrambling setting is false if a DC offset is present, or is true when +// no DC offset is present +// 7. Next step is breaking the encryption key +// 8. After the encryption key is obtained, it is possible determine if the link is +// using software CRC by computing the CRC-16 value on the first n-2 bytes and +// comparing that with the last 2 bytes +// 9. Determine if the link is using a network ID value, by checking the first byte +// in each of the transmitted frames +// 10. Determine if the virtual circuit header is contained in the transmitted frames +// 11. Determine which set of data frames are being transmitted +// 12. Determine the maximum interval between HEARTBEAT frames to roughly determine +// the HeartbeatTimeout value +// 13. Look for HEARTBEAT frames and FIND_PARTNER frames. A FIND_PARTNER frame +// is sent following a link failure which occurs after three HEARTBEAT timeout +// periods. +// 14. The MaxResends value can be estimated by the maximum number of data +// retransmissions done prior to the link failure. A large number of link +// failures will need to be collected from a very radio near the network. +// +//How do you prevent the attacks on a LoRaSerial radio network: +// +// 1. Don't advertize the network. Reduce the TxPower value to the minimum needed +// to successfully operate the network +// +// 2. Encrypt the data on the network. Don't use the factory default encryption key. +// Select a new encryption key and and train all of the radios in the network with +// this key. This will prevent attacks from any groups that are not able to break +// the encryption. However the radio network is vulnerable to well funded or +// dedicated groups that are able to break the encryption. +// +// 3. Change the encryption key. When another network is using the same encryption key +// and same network ID traffic from the other network is likely to be received. +// Selecting another encryption key will avoid this behavior. Also the encryption +// key may be tested by setting a radio to MODE_MULTIPOINT, disabling VerifyRxNetID +// and disabling EnableCRC16. Both settings of DataScrambling will need to be tested. +// +// 4. Use a network ID. This won't prevent attacks, but it prevents the reception +// of frames from other networks use the same encryption key. +// +// 5. Use software CRC-16. This won't prevent attacks but will eliminate most +// frames from networks using the same encryption key that don't use a network +// ID. Occasionally, when they get lucky, our network ID matches the first byte +// of their transmitted frame. The software CRC-16 will most likely cause this +// frame to be discarded. +// //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Determine the type of datagram received @@ -2227,10 +2304,7 @@ PacketType rcvDatagram() linkDownTimer = millis(); //Blink the RX LED - if (settings.selectLedUse == LEDS_RADIO_USE) - digitalWrite(RADIO_USE_RX_DATA_LED, LED_ON); - else if (settings.selectLedUse == LEDS_CYLON) - digitalWrite(CYLON_RX_DATA_LED, LED_ON); + blinkRadioRxLed(true); return datagramType; } @@ -2529,10 +2603,18 @@ bool transmitDatagram() //Hake sure that the transmitted msToNextHop is in the range 0 - maxDwellTime if (timeToHop) hopChannel(); - uint16_t msToNextHop = settings.maxDwellTime - (millis() - channelTimerStart); //TX channel timer value - memcpy(header, &msToNextHop, sizeof(msToNextHop)); - header += sizeof(msToNextHop); //aka CHANNEL_TIMER_BYTES + //Measure the time to the next hop + triggerEvent(TRIGGER_TX_LOAD_CHANNE_TIMER_VALUE); + txSetChannelTimerMicros = micros(); + unsigned long currentMillis = millis(); + uint16_t msToNextHop; //TX channel timer value + if (channelTimerMsec) + msToNextHop = channelTimerMsec - (currentMillis - channelTimerStart); + else + msToNextHop = settings.maxDwellTime; + + //Validate this value if (ENABLE_DEVELOPER && (!clockSyncReceiver)) { if ((msToNextHop < 0) || (msToNextHop > settings.maxDwellTime)) @@ -2540,7 +2622,8 @@ bool transmitDatagram() int16_t channelTimer; uint8_t * data; - systemPrintln("TX Frame"); + systemPrint("TX Frame "); + systemPrintln(radioDatagramType[txControl.datagramType]); data = outgoingPacket; if ((settings.operatingMode == MODE_POINT_TO_POINT) || settings.verifyRxNetID) { @@ -2558,10 +2641,28 @@ bool transmitDatagram() systemPrint("ERROR: Invalid msToNextHop value, "); systemPrintln(msToNextHop); - waitForever(); + + //Set a valid value + msToNextHop = settings.maxDwellTime; + } + else if (settings.debugSync) + { + switch (txControl.datagramType) + { + case DATAGRAM_DATA_ACK: + case DATAGRAM_SYNC_CLOCKS: + systemPrint("TX msToNextHop: "); + systemPrint(msToNextHop); + systemPrintln(" mSec"); + break; + } } } //ENABLE_DEVELOPER + //Set the time in the frame + memcpy(header, &msToNextHop, sizeof(msToNextHop)); + header += sizeof(msToNextHop); //aka CHANNEL_TIMER_BYTES + if (settings.debugTransmit) { systemPrintTimestamp(); @@ -2973,13 +3074,14 @@ bool retransmitDatagram(VIRTUAL_CIRCUIT * vc) frameAirTime += responseDelay; datagramTimer = millis(); //Move timestamp even if error - retransmitTimeout = random(ackAirTime, frameAirTime + ackAirTime); //Wait this number of ms between retransmits. Increases with each re-transmit. - //BLink the RX LED - if (settings.selectLedUse == LEDS_RADIO_USE) - digitalWrite(RADIO_USE_TX_DATA_LED, LED_ON); - else if (settings.selectLedUse == LEDS_CYLON) - digitalWrite(CYLON_TX_DATA_LED, LED_ON); + //Compute the interval before a retransmission occurs in milliseconds, + //this value increases with each transmission + retransmitTimeout = random(ackAirTime, frameAirTime + ackAirTime) + * (frameSentCount + 1) * 3 / 2; + + //BLink the TX LED + blinkRadioTxLed(true); return (true); //Transmission has started } @@ -2998,6 +3100,7 @@ void startChannelTimer(int16_t startAmount) channelTimer.disableTimer(); channelTimer.setInterval_MS(startAmount, channelTimerHandler); digitalWrite(pin_hop_timer, channelNumber & 1); + reloadChannelTimer = (startAmount != settings.maxDwellTime); timeToHop = false; channelTimerStart = millis(); //startChannelTimer - ISR updates value channelTimerMsec = startAmount; //startChannelTimer - ISR updates value @@ -3013,6 +3116,7 @@ void stopChannelTimer() //Turn off the channel timer channelTimer.disableTimer(); digitalWrite(pin_hop_timer, channelNumber & 1); + channelTimerMsec = 0; //Indicate that the timer is off triggerEvent(TRIGGER_HOP_TIMER_STOP); timeToHop = false; @@ -3020,7 +3124,7 @@ void stopChannelTimer() //Given the remote unit's number of ms before its next hop, //adjust our own channelTimer interrupt to be synchronized with the remote unit -void syncChannelTimer() +void syncChannelTimer(uint16_t frameAirTimeMsec) { int16_t adjustment; unsigned long currentMillis; @@ -3065,7 +3169,7 @@ void syncChannelTimer() systemPrint("ERROR: Invalid msToNextHopRemote value, "); systemPrintln(msToNextHopRemote); - waitForever(); + return; } } //ENABLE_DEVELOPER @@ -3103,15 +3207,14 @@ void syncChannelTimer() //Compute the remote system's channel timer firing time offset in milliseconds //using the channel timer value and the adjustments for transmit and receive //time (time of flight) - rmtHopTimeMsec = msToNextHopRemote - txRxTimeMsec; + rmtHopTimeMsec = msToNextHopRemote - frameAirTimeMsec; //Compute the when the local system last hopped lclHopTimeMsec = currentMillis - channelTimerStart; adjustment = 0; #define REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC 2 -#define CHANNEL_TIMER_SYNC_PLUS_MINUS_MSEC 3 -#define CHANNEL_TIMER_SYNC_MSEC (2 * CHANNEL_TIMER_SYNC_PLUS_MINUS_MSEC) +#define CHANNEL_TIMER_SYNC_MSEC (frameAirTimeMsec + REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) //Determine if the remote has hopped or is likely to hop very soon if (rmtHopTimeMsec <= REMOTE_SYSTEM_LIKELY_TO_HOP_MSEC) @@ -3120,7 +3223,7 @@ void syncChannelTimer() adjustment += settings.maxDwellTime; //Determine if the local system has just hopped - if (lclHopTimeMsec <= CHANNEL_TIMER_SYNC_MSEC) + if (((unsigned long)lclHopTimeMsec) <= CHANNEL_TIMER_SYNC_MSEC) { //Case 1 above, both systems using the same channel // @@ -3181,7 +3284,7 @@ void syncChannelTimer() { //The remote system has not hopped //Determine if the local system has just hopped - if (lclHopTimeMsec <= CHANNEL_TIMER_SYNC_MSEC) + if (((unsigned long)lclHopTimeMsec) <= CHANNEL_TIMER_SYNC_MSEC) { //Case 3 above, extend the channel timer value // @@ -3239,20 +3342,34 @@ void syncChannelTimer() //Compute the next hop time msToNextHop = rmtHopTimeMsec + adjustment; + //When the ISR fires, reload the channel timer with settings.maxDwellTime + reloadChannelTimer = true; + + //Log the previous clock sync + clockSyncData[clockSyncIndex].msToNextHop = msToNextHop; + clockSyncData[clockSyncIndex].frameAirTimeMsec = frameAirTimeMsec; + clockSyncData[clockSyncIndex].msToNextHopRemote = msToNextHopRemote; + clockSyncData[clockSyncIndex].adjustment = adjustment; + clockSyncData[clockSyncIndex].delayedHopCount = delayedHopCount; + clockSyncData[clockSyncIndex].lclHopTimeMsec = lclHopTimeMsec; + clockSyncData[clockSyncIndex].timeToHop = timeToHop; + clockSyncIndex += 1; + //Restart the channel timer + timeToHop = false; channelTimer.setInterval_MS(msToNextHop, channelTimerHandler); //Adjust our hardware timer to match our mate's - digitalWrite(pin_hop_timer, channelNumber & 1); + digitalWrite(pin_hop_timer, ((channelNumber + delayedHopCount) % settings.numberOfChannels) & 1); channelTimerStart = currentMillis; channelTimerMsec = msToNextHop; //syncChannelTimer update channelTimer.enableTimer(); - //Trigger after adjustments to timer to avoid skew during debug - triggerEvent(TRIGGER_SYNC_CHANNEL_TIMER); - //---------------------------------------------------------------------- // Leave the critical section //---------------------------------------------------------------------- + //Trigger after adjustments to timer to avoid skew during debug + triggerEvent(TRIGGER_SYNC_CHANNEL_TIMER); + //Hop if the timer fired prior to disabling the timer, resetting the channelTimerStart value if (delayedHopCount) hopChannel(true, delayedHopCount); @@ -3264,7 +3381,7 @@ void syncChannelTimer() systemPrint(" Hops, "); systemPrint(msToNextHopRemote); systemPrint(" Nxt Hop - "); - systemPrint(txRxTimeMsec); + systemPrint(frameAirTimeMsec); systemPrint(" (TX + RX)"); if (adjustment) { diff --git a/Firmware/LoRaSerial_Firmware/Serial.ino b/Firmware/LoRaSerial_Firmware/Serial.ino index dc31408e..7a13f2d4 100644 --- a/Firmware/LoRaSerial_Firmware/Serial.ino +++ b/Firmware/LoRaSerial_Firmware/Serial.ino @@ -247,7 +247,7 @@ void updateSerial() previousHead = rxHead; while (rtsAsserted && arch.serialAvailable() && (transactionComplete == false)) { - rxLED(true); //Turn on LED during serial reception + blinkSerialRxLed(true); //Turn on LED during serial reception //Take a break if there are ISRs to attend to petWDT(); @@ -262,7 +262,7 @@ void updateSerial() serialReceiveBuffer[rxHead++] = incoming; //Push char to holding buffer rxHead %= sizeof(serialReceiveBuffer); } //End Serial.available() - rxLED(false); //Turn off LED + blinkSerialRxLed(false); //Turn off LED //Print the number of bytes received via serial if (settings.debugSerial && (rxHead - previousHead)) @@ -440,7 +440,7 @@ void outputSerialData(bool ignoreISR) dataBytes = availableTXBytes(); while (dataBytes-- && isCTS() && (ignoreISR || (!transactionComplete))) { - txLED(true); //Turn on LED during serial transmissions + blinkSerialTxLed(true); //Turn on LED during serial transmissions //Take a break if there are ISRs to attend to petWDT(); @@ -452,7 +452,7 @@ void outputSerialData(bool ignoreISR) txTail++; txTail %= sizeof(serialTransmitBuffer); } - txLED(false); //Turn off LED + blinkSerialTxLed(false); //Turn off LED } //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= diff --git a/Firmware/LoRaSerial_Firmware/States.ino b/Firmware/LoRaSerial_Firmware/States.ino index 927a7b79..8a160faf 100644 --- a/Firmware/LoRaSerial_Firmware/States.ino +++ b/Firmware/LoRaSerial_Firmware/States.ino @@ -48,15 +48,15 @@ ackTimer = 0; \ } -#define COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift) \ - { \ - unsigned long deltaUsec = txTimeUsec + rxTimeUsec; \ - memcpy(&remoteSystemMillis, millisBuffer, sizeof(currentMillis)); \ - timestampOffset = (remoteSystemMillis + (deltaUsec / 1000) - currentMillis);\ - timestampOffset >>= rShift; \ +#define COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift, frameAirTimeUsec) \ + { \ + unsigned long deltaUsec = frameAirTimeUsec + micros() - transactionCompleteMicros; \ + memcpy(&remoteSystemMillis, millisBuffer, sizeof(currentMillis)); \ + timestampOffset = (remoteSystemMillis + (deltaUsec / 1000) - currentMillis); \ + timestampOffset >>= rShift; \ } -#define COMPUTE_RX_TIME(millisBuffer, rShift) \ +#define COMPUTE_RX_TIME(millisBuffer, rShift, frameAirTimeUsec) \ { \ currentMillis = millis(); \ long deltaUsec = micros() - transactionCompleteMicros; \ @@ -80,7 +80,7 @@ } \ \ /*Adjust the timestamp offset*/ \ - COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift); \ + COMPUTE_TIMESTAMP_OFFSET(millisBuffer, rShift, frameAirTimeUsec); \ } #define COMPUTE_TX_TIME() \ @@ -187,36 +187,45 @@ void updateRadioState() //Start the link between the radios if (settings.operatingMode == MODE_POINT_TO_POINT) + { changeState(RADIO_P2P_LINK_DOWN); - else if (settings.operatingMode == MODE_VIRTUAL_CIRCUIT) + break; + } + + //Virtual circuit mode + if (settings.operatingMode == MODE_VIRTUAL_CIRCUIT) { if (settings.server) { //Reserve the server's address (0) myVc = vcIdToAddressByte(VC_SERVER, myUniqueId); clockSyncReceiver = false; //VC server is clock source + if (settings.frequencyHop) + startChannelTimer(); + + //Start sending heartbeats + xmitVcHeartbeat(myVc, myUniqueId); + changeState(RADIO_VC_WAIT_TX_DONE); + break; } - else - //Unknown client address - myVc = VC_UNASSIGNED; + + //Unknown client address + myVc = VC_UNASSIGNED; //Start sending heartbeats - xmitVcHeartbeat(myVc, myUniqueId); - changeState(RADIO_VC_WAIT_TX_DONE); + changeState(RADIO_VC_WAIT_SERVER); + break; } - else + + //Multipoint mode + if (settings.server == true) { - if (settings.server == true) - { - clockSyncReceiver = false; //Multipoint server is clock source - startChannelTimer(); //Start hopping - multipoint clock source - changeState(RADIO_MP_STANDBY); - } - else - { - changeState(RADIO_DISCOVER_BEGIN); - } + clockSyncReceiver = false; //Multipoint server is clock source + startChannelTimer(); //Start hopping - multipoint clock source + changeState(RADIO_MP_STANDBY); } + else + changeState(RADIO_DISCOVER_BEGIN); break; //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -243,18 +252,23 @@ void updateRadioState() | P2P_WAIT_TX_FIND_PARTNER_DONE | = true | | | | | | | | | Tx Complete - - - - - > | Rx FIND_PARTNER | | - | | Start Rx | Start HOP Timer | | - | | MAX_PACKET_SIZE | clockSyncReceiver | | - | V | = false | | + | | Start Rx | clockSyncReceiver | | + | | MAX_PACKET_SIZE | = false | | + | V | | | `---- P2P_WAIT_SYNC_CLOCKS | | | | | Tx SYNC_CLOCKS | | | V | | | P2P_WAIT_TX_SYNC_CLOCKS_DONE | | | | | | - Rx SYNC_CLOCKS | < - - - - - - - - - - - | Tx Complete | | + Rx Complete ISR | < - - - - - - - - - - - | Tx Complete ISR | | + | | Start HOP Timer | | + | Start HOP Timer | | | + | V | | + V | In state machine | | + Rx SYNC_CLOCKS | In state machine | Tx Complete | | | | Start Rx | | - | Start HOP Timer | MAX_PACKET_SIZE | | - | Sync HOP Timer | | | + | | MAX_PACKET_SIZE | | + | | | | | V Timeout | | | P2P_WAIT_ZERO_ACKS ----------------' | | | | @@ -288,6 +302,10 @@ void updateRadioState() setRadioFrequency(false); } + //Stop the channel timer if it is running + if (channelTimerMsec) + stopChannelTimer(); + //Determine if a FIND_PARTNER was received if (transactionComplete) { @@ -319,10 +337,7 @@ void updateRadioState() case DATAGRAM_FIND_PARTNER: //Received FIND_PARTNER //Compute the receive time - COMPUTE_RX_TIME(rxData, 1); - - //Start the channel timer - startChannelTimer(); //Start hopping - P2P clock source + COMPUTE_RX_TIME(rxData, 1, calcAirTimeUsec(headerBytes + P2P_FIND_PARTNER_BYTES + trailerBytes)); //This system is the source of clock synchronization clockSyncReceiver = false; //P2P clock source @@ -338,6 +353,7 @@ void updateRadioState() triggerEvent(TRIGGER_TX_SYNC_CLOCKS); if (xmitDatagramP2PSyncClocks() == true) { + startChannelTimerPending = true; //Starts at SYNC_CLOCKS TX done sf6ExpectedSize = headerBytes + P2P_ZERO_ACKS_BYTES + trailerBytes; //Tell SF6 we expect ZERO_ACKS to contain millis info changeState(RADIO_P2P_WAIT_TX_SYNC_CLOCKS_DONE); } @@ -367,6 +383,7 @@ void updateRadioState() COMPUTE_TX_TIME(); triggerEvent(TRIGGER_TX_DONE); transactionComplete = false; //Reset ISR flag + startChannelTimerPending = true; //Starts at RX of SYNC_CLOCKS frame returnToReceiving(); changeState(RADIO_P2P_WAIT_SYNC_CLOCKS); } @@ -378,6 +395,13 @@ void updateRadioState() //Decode the received packet PacketType packetType = rcvDatagram(); + //Restart the channel timer when the SYNC_CLOCKS frame is received + if (packetType != DATAGRAM_SYNC_CLOCKS) + { + stopChannelTimer(); //Restart channel timer if SYNC_CLOCKS not RX + startChannelTimerPending = true; //Restart channel timer if SYNC_CLOCKS not RX + } + //Process the received datagram switch (packetType) { @@ -403,7 +427,7 @@ void updateRadioState() case DATAGRAM_FIND_PARTNER: //Received FIND_PARTNER //Compute the receive time - COMPUTE_RX_TIME(rxData + 1, 1); + COMPUTE_RX_TIME(rxData + 1, 1, calcAirTimeUsec(headerBytes + P2P_FIND_PARTNER_BYTES + trailerBytes)); //Acknowledge the FIND_PARTNER triggerEvent(TRIGGER_TX_SYNC_CLOCKS); @@ -416,10 +440,6 @@ void updateRadioState() case DATAGRAM_SYNC_CLOCKS: //Received SYNC_CLOCKS - //Start the channel timer - startChannelTimer(); //Start hopping - P2P clock receiver - syncChannelTimer(); - //Display the channelTimer sink if (settings.debugSync) { @@ -428,7 +448,10 @@ void updateRadioState() } //Compute the receive time - COMPUTE_RX_TIME(rxData + 1, 1); + COMPUTE_RX_TIME(rxData + 1, 1, txSyncClockUsec); + + //Hop to the next channel + hopChannel(); //Acknowledge the SYNC_CLOCKS triggerEvent(TRIGGER_TX_ZERO_ACKS); @@ -474,9 +497,23 @@ void updateRadioState() //Determine if a SYNC_CLOCKS has completed transmission if (transactionComplete) { + //Compute the SYNC_CLOCKS frame transmit frame + if ((!txSyncClockUsec) && (txControl.datagramType == DATAGRAM_SYNC_CLOCKS)) + { + txSyncClockUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txSyncClockUsec: "); + systemPrintln(txSyncClockUsec); + } + } + COMPUTE_TX_TIME(); triggerEvent(TRIGGER_TX_DONE); transactionComplete = false; //Reset ISR flag + + //Hop to the next channel + hopChannel(); returnToReceiving(); changeState(RADIO_P2P_WAIT_ZERO_ACKS); } @@ -513,7 +550,7 @@ void updateRadioState() case DATAGRAM_ZERO_ACKS: //Received ACK 2 //Compute the receive time - COMPUTE_RX_TIME(rxData, 1); + COMPUTE_RX_TIME(rxData, 1, calcAirTimeUsec(headerBytes + P2P_ZERO_ACKS_BYTES + trailerBytes)); setHeartbeatLong(); //We sent SYNC_CLOCKS and they sent ZERO_ACKS, so don't be the first to send heartbeat @@ -644,6 +681,28 @@ void updateRadioState() { transactionComplete = false; //Reset ISR flag + //Compute the ACK frame transmit frame + if ((!txDataAckUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txDataAckUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txDataAckUsec: "); + systemPrintln(txDataAckUsec); + } + } + + //Compute the HEARTBEAT frame transmit frame + if ((!txHeartbeatUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txHeartbeatUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } + //Determine if an ACK was transmitted if (txControl.datagramType = DATAGRAM_DATA_ACK) { @@ -722,8 +781,9 @@ void updateRadioState() case DATAGRAM_HEARTBEAT: //Received heartbeat while link was idle. Send ack to sync clocks. //Adjust the timestamp offset - COMPUTE_TIMESTAMP_OFFSET(rxData, 1); + COMPUTE_TIMESTAMP_OFFSET(rxData, 1, txHeartbeatUsec); triggerEvent(TRIGGER_RX_HEARTBEAT); + blinkHeartbeatLed(true); //Transmit ACK P2P_SEND_ACK(TRIGGER_TX_ACK); @@ -745,10 +805,10 @@ void updateRadioState() case DATAGRAM_DATA_ACK: //Adjust the timestamp offset - COMPUTE_RX_TIME(rxData, 1); + COMPUTE_RX_TIME(rxData, 1, txDataAckUsec); //The datagram we are expecting - syncChannelTimer(); //Adjust freq hop ISR based on remote's remaining clock + syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on remote's remaining clock triggerEvent(TRIGGER_RX_ACK); @@ -823,73 +883,71 @@ void updateRadioState() //An ACK is expected when the ACK timer running else if (ackTimer) { - //Check for ACK timeout - if ((millis() - ackTimer) >= (frameAirTime + ackAirTime + settings.overheadTime + getReceiveCompletionOffset())) + //Throttle back retransmits based on the number of retransmits we've attempted + //retransmitTimeout is a random number set in retransmitDatagram + if (millis() - datagramTimer > retransmitTimeout) { //Determine if another retransmission is allowed if ((!settings.maxResends) || (frameSentCount < settings.maxResends)) { - //Throttle back retransmits based on the number of retransmits we've attempted - //retransmitTimeout is a random number, set when the first datagram is sent - if (millis() - datagramTimer > (frameSentCount * retransmitTimeout)) + lostFrames++; + + //Display the retransmit + if (settings.debugDatagrams) { - lostFrames++; + systemPrintTimestamp(); + systemPrintln("RX: ACK Timeout"); + outputSerialData(true); + } - //Display the retransmit - if (settings.debugDatagrams) + if (settings.debugDatagrams) + { + systemPrintTimestamp(); + systemPrint("TX: Retransmit "); + systemPrint(frameSentCount); + systemPrint(", "); + systemPrint(radioDatagramType[txControl.datagramType]); + switch (txControl.datagramType) { - systemPrintTimestamp(); - systemPrintln("RX: ACK Timeout"); - outputSerialData(true); - } + default: + systemPrintln(); + break; - triggerEvent(TRIGGER_RETRANSMIT); - if (settings.debugDatagrams) - { - systemPrintTimestamp(); - systemPrint("TX: Retransmit "); - systemPrint(frameSentCount); - systemPrint(", "); - systemPrint(radioDatagramType[txControl.datagramType]); - switch (txControl.datagramType) - { - default: - systemPrintln(); - break; - - case DATAGRAM_DATA: - case DATAGRAM_DATA_ACK: - case DATAGRAM_REMOTE_COMMAND: - case DATAGRAM_REMOTE_COMMAND_RESPONSE: - case DATAGRAM_HEARTBEAT: - systemPrint(" (ACK #"); - systemPrint(txControl.ackNumber); - systemPrint(")"); - systemPrintln(); - break; - } - outputSerialData(true); + case DATAGRAM_DATA: + case DATAGRAM_DATA_ACK: + case DATAGRAM_REMOTE_COMMAND: + case DATAGRAM_REMOTE_COMMAND_RESPONSE: + case DATAGRAM_HEARTBEAT: + systemPrint(" (ACK #"); + systemPrint(txControl.ackNumber); + systemPrint(")"); + systemPrintln(); + break; } + outputSerialData(true); + } - //Attempt the retransmission - RESTORE_TX_BUFFER(); - if (rexmtControl.datagramType == DATAGRAM_HEARTBEAT) + //Attempt the retransmission + RESTORE_TX_BUFFER(); + if (rexmtControl.datagramType == DATAGRAM_HEARTBEAT) + { + //Never retransmit the heartbeat, always send a new version to + //send the updated time value + if (xmitDatagramP2PHeartbeat() == true) { - //Never retransmit the heartbeat, always send a new version to - //send the updated time value - if (xmitDatagramP2PHeartbeat() == true) - { - changeState(RADIO_P2P_LINK_UP_WAIT_TX_DONE); - triggerEvent(TRIGGER_TX_HEARTBEAT); - } - } - else if (retransmitDatagram(NULL) == true) + triggerEvent(TRIGGER_TX_HEARTBEAT); changeState(RADIO_P2P_LINK_UP_WAIT_TX_DONE); - - //We're re-sending data, so don't be the first to send next heartbeat - setHeartbeatLong(); - START_ACK_TIMER(); + } } + else if (retransmitDatagram(NULL) == true) + { + triggerEvent(TRIGGER_RETRANSMIT); + changeState(RADIO_P2P_LINK_UP_WAIT_TX_DONE); + } + + //We're re-sending data, so don't be the first to send next heartbeat + setHeartbeatLong(); + START_ACK_TIMER(); } else { @@ -1037,8 +1095,6 @@ void updateRadioState() //Start searching for other radios //==================== case RADIO_DISCOVER_BEGIN: - stopChannelTimer(); //Stop hopping - multipoint discovery - if (settings.debugSync) { systemPrintln("Start scanning"); @@ -1056,6 +1112,8 @@ void updateRadioState() //Walk through channel table backwards, transmitting a FIND_PARTNER and looking for an SYNC_CLOCKS //==================== case RADIO_DISCOVER_SCANNING: + stopChannelTimer(); //Stop hopping - multipoint discovery + if (transactionComplete) { //Decode the received datagram @@ -1099,32 +1157,65 @@ void updateRadioState() break; case DATAGRAM_SYNC_CLOCKS: - triggerEvent(TRIGGER_RX_SYNC_CLOCKS); + if (!settings.server) + { + //Change to the server's channel number + channelNumber = rxVcData[0]; - //Compute the common clock - currentMillis = millis(); - COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0); + //Get the HEARTBEAT TX time + if (!txHeartbeatUsec) + { + memcpy(&txHeartbeatUsec, rxData + 1 + 4, sizeof(txHeartbeatUsec)); + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } - //Server has responded with ACK - syncChannelTimer(); //Start and adjust freq hop ISR based on remote's remaining clock + //Get the SYNC_CLOCKS TX time + if (!txSyncClockUsec) + { + memcpy(&txSyncClockUsec, rxData + 1 + 4 + 4, sizeof(txSyncClockUsec)); + if (settings.debugSync) + { + systemPrint("txSyncClockUsec: "); + systemPrintln(txSyncClockUsec); + } + } - //Change to the server's channel number - channelNumber = rxVcData[0]; + //Ignore this frame when the times are not filled in + if ((!txHeartbeatUsec) || (!txSyncClockUsec)) + triggerEvent(TRIGGER_RX_SYNC_CLOCKS); + else + { + COMPUTE_TIMESTAMP_OFFSET(rxData + 1, 0, txSyncClockUsec); - if (settings.debugSync) - { - systemPrint(" Channel Number: "); - systemPrintln(channelNumber); - outputSerialData(true); - if (timeToHop == true) //If the channelTimer has expired, move to next frequency - hopChannel(); - } + //Server has responded to FIND_PARTNER with SYNC_CLOCKS + //Start and adjust freq hop ISR based on remote's remaining clock + startChannelTimer(); + channelTimerStart -= settings.maxDwellTime; + syncChannelTimer((txSyncClockUsec + TX_TO_RX_USEC + micros() - transactionCompleteMicros) / 1000); + triggerEvent(TRIGGER_RX_SYNC_CLOCKS); - frequencyCorrection += radio.getFrequencyError() / 1000000.0; + //Switch to the proper frequency for the channel + setRadioFrequency(false); - lastPacketReceived = millis(); //Reset + if (settings.debugSync) + { + systemPrint(" Channel Number: "); + systemPrintln(channelNumber); + outputSerialData(true); + if (timeToHop == true) //If the channelTimer has expired, move to next frequency + hopChannel(); + } - changeState(RADIO_MP_STANDBY); + frequencyCorrection += radio.getFrequencyError() / 1000000.0; + + lastPacketReceived = millis(); //Reset + changeState(RADIO_MP_STANDBY); + } + } break; } } @@ -1262,18 +1353,25 @@ void updateRadioState() break; case DATAGRAM_HEARTBEAT: - //Received heartbeat - do not ack. - triggerEvent(TRIGGER_RX_HEARTBEAT); - //Sync clock if server sent the heartbeat if (settings.server == false) - syncChannelTimer(); //Adjust freq hop ISR based on remote's remaining clock + { + uint16_t frameAirTimeMsec; + + //Adjust freq hop ISR based on server's remaining clock + syncChannelTimer((txHeartbeatUsec + TX_TO_RX_USEC + micros() - transactionCompleteMicros) / 1000); + systemPrint("HEARTBEAT TX mSec: "); + systemPrintln(frameAirTime); + } + + //Received heartbeat - do not ack. + triggerEvent(TRIGGER_RX_HEARTBEAT); frequencyCorrection += radio.getFrequencyError() / 1000000.0; lastPacketReceived = millis(); //Update timestamp for Link LED - ledMpHeartbeatOn(); + blinkHeartbeatLed(true); changeState(RADIO_MP_STANDBY); break; @@ -1281,10 +1379,6 @@ void updateRadioState() //Received data - do not ack. triggerEvent(TRIGGER_RX_DATA); - //Sync clock if server sent the datagram - if (settings.server == false) - syncChannelTimer(); //Adjust freq hop ISR based on remote's remaining clock - setHeartbeatMultipoint(); //We're sync'd so reset heartbeat timer //Place any available data in the serial output buffer @@ -1305,17 +1399,14 @@ void updateRadioState() heartbeatTimeout = ((millis() - heartbeatTimer) > heartbeatRandomTime); //Only the server transmits heartbeats - if (settings.server == true) + if ((settings.server == true) && (heartbeatTimeout)) { - if (heartbeatTimeout) + if (xmitDatagramMpHeartbeat() == true) { - if (xmitDatagramMpHeartbeat() == true) - { - triggerEvent(TRIGGER_TX_HEARTBEAT); - setHeartbeatMultipoint(); //We're sending something with clock data so reset heartbeat timer - changeState(RADIO_MP_WAIT_TX_DONE); //Wait for heartbeat to transmit - } - ledMpHeartbeatOn(); + triggerEvent(TRIGGER_TX_HEARTBEAT); + blinkHeartbeatLed(true); + setHeartbeatMultipoint(); //We're sending something with clock data so reset heartbeat timer + changeState(RADIO_MP_WAIT_TX_DONE); //Wait for heartbeat to transmit } } @@ -1340,6 +1431,7 @@ void updateRadioState() systemPrintln("HEARTBEAT Timeout"); outputSerialData(true); } + dumpClockSynchronization(); changeState(RADIO_DISCOVER_BEGIN); } } @@ -1358,6 +1450,29 @@ void updateRadioState() if (transactionComplete == true) { transactionComplete = false; //Reset ISR flag + + //Compute the HEARTBEAT frame transmit frame + if ((!txHeartbeatUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txHeartbeatUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } + + //Compute the SYNC_CLOCKS frame transmit frame + else if ((!txSyncClockUsec) && (txControl.datagramType == DATAGRAM_SYNC_CLOCKS)) + { + txSyncClockUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txSyncClockUsec: "); + systemPrintln(txSyncClockUsec); + } + } + returnToReceiving(); changeState(RADIO_MP_STANDBY); } @@ -1709,12 +1824,21 @@ void updateRadioState() //Wait for a HEARTBEAT from the server //==================== case RADIO_VC_WAIT_SERVER: + //The server should never be in this state if (myVc == VC_SERVER) { changeState(RADIO_VC_WAIT_RECEIVE); break; } + //Stop the frequency hopping + stopChannelTimer(); + if (channelNumber != 0) + { + channelNumber = 0; + setRadioFrequency(false); + } + //If dio0ISR has fired, a packet has arrived currentMillis = millis(); if (transactionComplete == true) @@ -1725,6 +1849,11 @@ void updateRadioState() //Process the received datagram if ((packetType == DATAGRAM_VC_HEARTBEAT) && (rxSrcVc == VC_SERVER)) { + //Start the channel timer + startChannelTimer(); + channelTimerStart -= settings.maxDwellTime; + + //Synchronize the channel timer with the server vcReceiveHeartbeat(millis() - currentMillis); changeState(RADIO_VC_WAIT_RECEIVE); } @@ -1747,6 +1876,17 @@ void updateRadioState() { transactionComplete = false; + //Compute the HEARTBEAT frame transmit frame + if ((!txHeartbeatUsec) && (txControl.datagramType == DATAGRAM_HEARTBEAT)) + { + txHeartbeatUsec = transactionCompleteMicros - txSetChannelTimerMicros; + if (settings.debugSync) + { + systemPrint("txHeartbeatUsec: "); + systemPrintln(txHeartbeatUsec); + } + } + //Indicate that the transmission is complete triggerEvent(TRIGGER_TX_DONE); @@ -1796,6 +1936,8 @@ void updateRadioState() break; case DATAGRAM_DATA: + triggerEvent(TRIGGER_RX_DATA); + //Move the data into the serial output buffer if (settings.debugSerial) { @@ -1813,6 +1955,8 @@ void updateRadioState() break; case DATAGRAM_DATAGRAM: + triggerEvent(TRIGGER_RX_DATAGRAM); + //Move the data into the serial output buffer if (settings.debugSerial) { @@ -1828,6 +1972,7 @@ void updateRadioState() break; case DATAGRAM_DATA_ACK: + triggerEvent(TRIGGER_RX_ACK); vcSendPcAckNack(rexmtTxDestVc, true); STOP_ACK_TIMER(); break; @@ -1845,6 +1990,7 @@ void updateRadioState() //Second step in the 3-way handshake, received UNKNOWN_ACKS, respond //with SYNC_ACKS case DATAGRAM_VC_UNKNOWN_ACKS: + triggerEvent(TRIGGER_RX_VC_UNKNOWN_ACKS); if (xmitVcSyncAcks(rxSrcVc)) changeState(RADIO_VC_WAIT_TX_DONE); vcChangeState(rxSrcVc, VC_STATE_WAIT_ZERO_ACKS); @@ -1853,6 +1999,7 @@ void updateRadioState() //Third step in the 3-way handshake, received SYNC_ACKS, respond with ZERO_ACKS case DATAGRAM_VC_SYNC_ACKS: + triggerEvent(TRIGGER_RX_VC_SYNC_ACKS); if (xmitVcZeroAcks(rxSrcVc)) { changeState(RADIO_VC_WAIT_TX_DONE); @@ -1863,11 +2010,13 @@ void updateRadioState() //Last step in the 3-way handshake, received ZERO_ACKS, done case DATAGRAM_VC_ZERO_ACKS: + triggerEvent(TRIGGER_RX_VC_ZERO_ACKS); vcZeroAcks(rxSrcVc); vcChangeState(rxSrcVc, VC_STATE_CONNECTED); break; case DATAGRAM_REMOTE_COMMAND: + triggerEvent(TRIGGER_RX_COMMAND); rmtCmdVc = rxSrcVc; //Copy the command into the command buffer @@ -1913,6 +2062,8 @@ void updateRadioState() break; case DATAGRAM_REMOTE_COMMAND_RESPONSE: + triggerEvent(TRIGGER_RX_COMMAND_RESPONSE); + //Debug the serial path if (settings.debugSerial) { @@ -1951,7 +2102,7 @@ void updateRadioState() //Send another heartbeat if (xmitVcHeartbeat(myVc, myUniqueId)) { - triggerEvent(TRIGGER_TX_HEARTBEAT); + triggerEvent(TRIGGER_TX_VC_HEARTBEAT); if (((uint8_t)myVc) < MAX_VC) virtualCircuitList[myVc].lastTrafficMillis = currentMillis; changeState(RADIO_VC_WAIT_TX_DONE); @@ -2076,6 +2227,7 @@ void updateRadioState() //Send the UNKNOWN_ACKS datagram, first part of the 3-way handshake if (xmitVcUnknownAcks(index)) { + triggerEvent(TRIGGER_RX_VC_UNKNOWN_ACKS); vcChangeState(index, VC_STATE_WAIT_SYNC_ACKS); virtualCircuitList[index].timerMillis = datagramTimer; changeState(RADIO_VC_WAIT_TX_DONE); @@ -2092,6 +2244,7 @@ void updateRadioState() //Retransmit the UNKNOWN_ACKS if (xmitVcUnknownAcks(index)) { + triggerEvent(TRIGGER_RX_VC_UNKNOWN_ACKS); virtualCircuitList[index].timerMillis = datagramTimer; changeState(RADIO_VC_WAIT_TX_DONE); } @@ -2108,6 +2261,7 @@ void updateRadioState() //Retransmit the SYNC_CLOCKS if (xmitVcSyncAcks(index)) { + triggerEvent(TRIGGER_RX_VC_SYNC_ACKS); virtualCircuitList[index].timerMillis = datagramTimer; changeState(RADIO_VC_WAIT_TX_DONE); } @@ -2139,15 +2293,20 @@ void updateRadioState() if (vcHeader->destVc == VC_BROADCAST) { //Broadcast this data to all VCs, no ACKs will be received - triggerEvent(TRIGGER_TX_DATA); - xmitVcDatagram(); + if (xmitVcDatagram()) + { + triggerEvent(TRIGGER_TX_DATA); + changeState(RADIO_VC_WAIT_TX_DONE); + } break; } //Transmit the packet - triggerEvent(TRIGGER_TX_DATA); if (xmitDatagramP2PData() == true) + { + triggerEvent(TRIGGER_TX_DATA); changeState(RADIO_VC_WAIT_TX_DONE); + } START_ACK_TIMER(); @@ -2218,7 +2377,10 @@ void updateRadioState() //Send the remote command vcHeader->destVc &= VCAB_NUMBER_MASK; if (xmitDatagramP2PCommand() == true) + { + triggerEvent(TRIGGER_TX_COMMAND); changeState(RADIO_VC_WAIT_TX_DONE); + } START_ACK_TIMER(); @@ -2600,23 +2762,81 @@ void displayRadioStateHistory() petWDT(); } +//Dump the clock synchronization data +void dumpClockSynchronization() +{ + //Dump the clock sync data + petWDT(); + for (uint8_t x = 0; x < (sizeof(clockSyncData) / sizeof(clockSyncData[0])); x++) + { + uint8_t index = (x + clockSyncIndex) % clockSyncIndex; + if (clockSyncData[index].frameAirTimeMsec) + { + systemPrint("Lcl: "); + systemPrint(clockSyncData[index].lclHopTimeMsec); + systemPrint(", Rmt: "); + systemPrint(clockSyncData[index].msToNextHopRemote); + systemPrint(" - "); + systemPrint(clockSyncData[index].frameAirTimeMsec); + systemPrint(" = "); + systemPrint(clockSyncData[index].msToNextHopRemote - clockSyncData[index].frameAirTimeMsec); + systemPrint(" + "); + systemPrint(clockSyncData[index].adjustment); + systemPrint(" = "); + systemPrint(clockSyncData[index].msToNextHop); + systemPrint(" msToNextHop"); + if (clockSyncData[index].delayedHopCount) + { + systemPrint(", timeToHop: "); + systemPrint(clockSyncData[index].timeToHop); + systemPrint(", Hops: "); + systemPrint(clockSyncData[index].delayedHopCount); + } + systemPrintln(); + outputSerialData(true); + petWDT(); + } + } +} + //Break a point-to-point link void breakLink() { + unsigned long linkDownTime; + //Break the link + if (settings.printTimestamp) + { + linkDownTime = millis(); + + //Offset the value for display + if (!settings.displayRealMillis) + linkDownTime += timestampOffset; + } linkFailures++; + + //Stop the ACK timer + STOP_ACK_TIMER(); + + //Dump the clock synchronization + dumpClockSynchronization(); + if (settings.printLinkUpDown) { + if (settings.printTimestamp) + { + systemPrintTimestamp(linkDownTime); + systemPrint(": "); + } systemPrintln("--------- Link DOWN ---------"); outputSerialData(true); } - triggerEvent(TRIGGER_RADIO_RESET); - - //Stop the ACK timer - STOP_ACK_TIMER(); //Flush the buffers resetSerial(); + + //Reset the radio and the link + triggerEvent(TRIGGER_RADIO_RESET); changeState(RADIO_RESET); } @@ -2648,6 +2868,7 @@ void enterLinkUp() changeState(RADIO_P2P_LINK_UP); if (settings.printLinkUpDown) { + systemPrintTimestamp(); systemPrintln("========== Link UP =========="); outputSerialData(true); } @@ -2882,18 +3103,22 @@ void vcReceiveHeartbeat(uint32_t rxMillis) uint32_t deltaMillis; int vcSrc; - if (rxSrcVc == VC_SERVER) - syncChannelTimer(); //Adjust freq hop ISR based on server's remaining clock + if ((rxSrcVc == VC_SERVER) || (memcmp(rxVcData, myUniqueId, sizeof(myUniqueId)) == 0)) + syncChannelTimer(txRxTimeMsec); //Adjust freq hop ISR based on server's remaining clock //Update the timestamp offset if (rxSrcVc == VC_SERVER) { + //Blink the heartbeat LED + blinkHeartbeatLed(true); + //Assume client and server are running the same level of debugging, //then the delay from reading the millisecond value on the server should //get offset by the transmit setup time and the receive overhead time. memcpy(×tampOffset, &rxVcData[UNIQUE_ID_BYTES], sizeof(timestampOffset)); timestampOffset += vcTxHeartbeatMillis + rxMillis - millis(); } + triggerEvent(TRIGGER_RX_VC_HEARTBEAT); //Save our address if ((myVc == VC_UNASSIGNED) && (memcmp(myUniqueId, rxVcData, UNIQUE_ID_BYTES) == 0)) diff --git a/Firmware/LoRaSerial_Firmware/System.ino b/Firmware/LoRaSerial_Firmware/System.ino index 0d17a326..9e2de995 100644 --- a/Firmware/LoRaSerial_Firmware/System.ino +++ b/Firmware/LoRaSerial_Firmware/System.ino @@ -592,31 +592,166 @@ void setRSSI(uint8_t ledBits) digitalWrite(pin_rssi4LED, LOW); } +//Start the cylon LEDs +void startCylonLEDs() +{ + cylonLedPattern = 0b0001; + cylonPatternGoingLeft = true; +} + +//Update the RSSI LED or LEDs +void blinkRadioRssiLed() +{ + unsigned long currentMillis; + static uint32_t ledPreviousRssiMillis; + static unsigned long lastCylonBlink; //Controls LED during training + static int8_t ledRssiPulseWidth; //Target pulse width for LED display + + currentMillis = millis(); + switch (settings.selectLedUse) + { + //Update the pattern on the 4 green LEDs + case LEDS_CYLON: + //Determine if it is time to update the cylon pattern + if ((currentMillis - lastCylonBlink) > 75) + { + lastCylonBlink = currentMillis; + + //The following shows the cylon pattern in four LEDs + // + // LED3 LED2 LED1 LED0 + // X + // X + // X + // X + // X + // X + // X + // X + // ... + // + //Cylon the RSSI LEDs + setRSSI(cylonLedPattern); + + //Determine if a change in direction is necessary + if ((cylonLedPattern == 0b1000) || (cylonLedPattern == 0b0001)) + cylonPatternGoingLeft = cylonPatternGoingLeft ? false : true; + + if (cylonPatternGoingLeft) + cylonLedPattern <<= 1; + else + cylonLedPattern >>= 1; + } + break; + + //Update the single RSSI LED + case LEDS_MULTIPOINT: + case LEDS_RADIO_USE: + //Check for the start of a new pulse + if ((currentMillis - ledPreviousRssiMillis) >= LED_MAX_PULSE_WIDTH) + { + ledPreviousRssiMillis = currentMillis; + + //Determine the RSSI LED pulse width + //The RSSI value ranges from -164 to 30 dB + ledRssiPulseWidth = (150 + rssi) / 10; + if (ledRssiPulseWidth > 0) + digitalWrite(RADIO_USE_RSSI_LED, LED_ON); + } + + //Check for time to turn off the LED + else if ((currentMillis - ledPreviousRssiMillis) >= ledRssiPulseWidth) + digitalWrite(RADIO_USE_RSSI_LED, LED_OFF); + break; + + //Update the 4 green LEDs based upon the RSSI value + case LEDS_RSSI: + if (rssi > -70) + setRSSI(0b1111); + else if (rssi > -100) + setRSSI(0b0111); + else if (rssi > -120) + setRSSI(0b0011); + else if (rssi > -150) + setRSSI(0b0001); + else + setRSSI(0); + break; + } +} + //Set the serial TX (blue) LED value -void txLED(bool illuminate) +void blinkSerialTxLed(bool illuminate) { - if (settings.selectLedUse != LEDS_RSSI) - return; - if (pin_txLED != PIN_UNDEFINED) + switch (settings.selectLedUse) { - if (illuminate == true) - digitalWrite(pin_txLED, HIGH); - else - digitalWrite(pin_txLED, LOW); + case LEDS_RSSI: + if (pin_txLED != PIN_UNDEFINED) + { + if (illuminate == true) + digitalWrite(pin_txLED, HIGH); + else + digitalWrite(pin_txLED, LOW); + } + break; } } //Set the serial RX (yellow) LED value -void rxLED(bool illuminate) +void blinkSerialRxLed(bool illuminate) { - if (settings.selectLedUse != LEDS_RSSI) - return; - if (pin_rxLED != PIN_UNDEFINED) + switch (settings.selectLedUse) + { + case LEDS_RSSI: + if (illuminate == true) + digitalWrite(pin_rxLED, HIGH); + else + digitalWrite(pin_rxLED, LOW); + break; + } +} + +//Blink the RX LED +void blinkRadioRxLed(bool on) +{ + switch (settings.selectLedUse) + { + case LEDS_CYLON: + if (on) + digitalWrite(CYLON_RX_DATA_LED, LED_ON); + else if ((millis() - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(CYLON_RX_DATA_LED, LED_OFF); + break; + + case LEDS_MULTIPOINT: + case LEDS_RADIO_USE: + if (on) + digitalWrite(RADIO_USE_RX_DATA_LED, LED_ON); + else if ((millis() - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(RADIO_USE_RX_DATA_LED, LED_OFF); + break; + } +} + +//BLink the TX LED +void blinkRadioTxLed(bool on) +{ + switch (settings.selectLedUse) { - if (illuminate == true) - digitalWrite(pin_rxLED, HIGH); - else - digitalWrite(pin_rxLED, LOW); + case LEDS_CYLON: + if (on) + digitalWrite(CYLON_TX_DATA_LED, LED_ON); + else if ((millis() - datagramTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(CYLON_TX_DATA_LED, LED_OFF); + break; + + case LEDS_MULTIPOINT: + case LEDS_RADIO_USE: + if (on) + digitalWrite(RADIO_USE_TX_DATA_LED, LED_ON); + else if ((millis() - datagramTimer) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(RADIO_USE_TX_DATA_LED, LED_OFF); + break; } } @@ -636,16 +771,19 @@ void radioLeds() static uint32_t badCrcMillis; //Turn off the RX LED to end the blink - currentMillis = millis(); - if ((currentMillis - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_RX_DATA_LED, LED_OFF); + blinkRadioRxLed(false); //Turn off the TX LED to end the blink - currentMillis = millis(); - if ((currentMillis - datagramTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_TX_DATA_LED, LED_OFF); + blinkRadioTxLed(false); + + //Update the link LED + digitalWrite(RADIO_USE_LINK_LED, isLinked() ? LED_ON : LED_OFF); + + //Update the RSSI LED + blinkRadioRssiLed(); //Blink the bad frames LED + currentMillis = millis(); if (badFrames != previousBadFrames) { previousBadFrames = badFrames; @@ -676,35 +814,39 @@ void radioLeds() badCrcMillis = 0; digitalWrite(RADIO_USE_BAD_CRC_LED, LED_OFF); } +} - //Update the link LED - digitalWrite(RADIO_USE_LINK_LED, isLinked() ? LED_ON : LED_OFF); +//Blink the heartbeat LED +void blinkHeartbeatLed(bool on) +{ + static unsigned long ledHeartbeatMillis; - //Update the RSSI LED - if (currentMillis != ledPreviousRssiMillis) + switch (settings.selectLedUse) { - if (ledRssiCount >= 16) - { - ledRssiValue = (150 + rssi) / 10; - ledRssiCount = 0; - if (ledRssiValue >= ledRssiCount) - digitalWrite(RADIO_USE_RSSI_LED, LED_ON); - } - - //Turn off the RSSI LED - else if (ledRssiValue < ledRssiCount++) - digitalWrite(RADIO_USE_RSSI_LED, LED_OFF); + case LEDS_MULTIPOINT: + if (on) + { + digitalWrite(LED_MP_HEARTBEAT, LED_ON); + ledHeartbeatMillis = millis(); + } + else if ((millis() - ledHeartbeatMillis) > RADIO_USE_BLINK_MILLIS) + digitalWrite(LED_MP_HEARTBEAT, LED_OFF); + break; } - - //Save the last millis value - ledPreviousRssiMillis = currentMillis; } -//Turn on the heartbeat LED -void ledMpHeartbeatOn() +//Blink the channel hop LED +void blinkChannelHopLed(bool on) { - digitalWrite(LED_MP_HEARTBEAT, LED_OFF); - ledHeartbeatMillis = millis(); + switch (settings.selectLedUse) + { + case LEDS_MULTIPOINT: + if (on) + digitalWrite(LED_MP_HOP_CHANNEL, LED_ON); + else if ((millis() - radioCallHistory[RADIO_CALL_hopChannel]) >= RADIO_USE_BLINK_MILLIS) + digitalWrite(LED_MP_HOP_CHANNEL, LED_OFF); + break; + } } //Display the multi-point LED pattern @@ -713,83 +855,37 @@ void multiPointLeds() uint32_t currentMillis; //Turn off the RX LED to end the blink - currentMillis = millis(); - if ((currentMillis - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_RX_DATA_LED, LED_OFF); + blinkRadioRxLed(false); //Turn off the TX LED to end the blink - currentMillis = millis(); - if ((currentMillis - datagramTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(RADIO_USE_TX_DATA_LED, LED_OFF); + blinkRadioTxLed(false); //Update the sync LED digitalWrite(RADIO_USE_LINK_LED, isMultiPointSync() ? LED_ON : LED_OFF); //Update the RSSI LED - if (currentMillis != ledPreviousRssiMillis) - { - if (ledRssiCount >= 16) - { - ledRssiValue = (150 + rssi) / 10; - ledRssiCount = 0; - if (ledRssiValue >= ledRssiCount) - digitalWrite(RADIO_USE_RSSI_LED, LED_ON); - } - - //Turn off the RSSI LED - else if (ledRssiValue < ledRssiCount++) - digitalWrite(RADIO_USE_RSSI_LED, LED_OFF); - } + blinkRadioRssiLed(); //Update the hop LED if ((millis() - radioCallHistory[RADIO_CALL_hopChannel]) >= RADIO_USE_BLINK_MILLIS) digitalWrite(LED_MP_HOP_CHANNEL, LED_OFF); //Update the HEARTBEAT LED - if ((millis() - ledHeartbeatMillis) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(LED_MP_HEARTBEAT, LED_OFF); - - //Save the last millis value - ledPreviousRssiMillis = currentMillis; -} - -//Start the cylon LEDs -void startCylonLEDs() -{ - trainCylonNumber = 0b0001; - trainCylonDirection = -1; + blinkHeartbeatLed(false); } //Update the cylon LEDs void updateCylonLEDs() { - unsigned long currentMillis; - - currentMillis = millis(); - if ((currentMillis - lastTrainBlink) > 75) //Blink while unit waits in training state - { - lastTrainBlink = millis(); - - //Cylon the RSSI LEDs - setRSSI(trainCylonNumber); - - if (trainCylonNumber == 0b1000 || trainCylonNumber == 0b0001) - trainCylonDirection *= -1; //Change direction - - if (trainCylonDirection > 0) - trainCylonNumber <<= 1; - else - trainCylonNumber >>= 1; - } + //Display the cylon pattern on the RSSI LEDs + blinkRadioRssiLed(); //Use the blue and yellow LEDS to indicate radio activity during training //Turn off the RX LED to end the blink - if ((currentMillis - linkDownTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(CYLON_RX_DATA_LED, LED_OFF); + blinkRadioRxLed(false); //Turn off the TX LED to end the blink - if ((currentMillis - datagramTimer) >= RADIO_USE_BLINK_MILLIS) - digitalWrite(CYLON_TX_DATA_LED, LED_OFF); + blinkRadioTxLed(false); } //Update the LED values depending upon the selected display @@ -816,14 +912,7 @@ void updateLeds() //Set LEDs according to RSSI level default: case LEDS_RSSI: - if (rssi > rssiLevelLow) - setRSSI(0b0001); - if (rssi > rssiLevelMed) - setRSSI(0b0011); - if (rssi > rssiLevelHigh) - setRSSI(0b0111); - if (rssi > rssiLevelMax) - setRSSI(0b1111); + blinkRadioRssiLed(); break; case LEDS_RADIO_USE: diff --git a/Firmware/LoRaSerial_Firmware/settings.h b/Firmware/LoRaSerial_Firmware/settings.h index cf1e1a40..33e7257a 100644 --- a/Firmware/LoRaSerial_Firmware/settings.h +++ b/Firmware/LoRaSerial_Firmware/settings.h @@ -263,10 +263,15 @@ enum TRIGGER_RX_COMMAND, TRIGGER_RX_COMMAND_RESPONSE, TRIGGER_RX_DATA, + TRIGGER_RX_DATAGRAM, TRIGGER_RX_FIND_PARTNER, TRIGGER_RX_HEARTBEAT, TRIGGER_RX_SPI_DONE, TRIGGER_RX_SYNC_CLOCKS, + TRIGGER_RX_VC_HEARTBEAT, + TRIGGER_RX_VC_SYNC_ACKS, + TRIGGER_RX_VC_UNKNOWN_ACKS, + TRIGGER_RX_VC_ZERO_ACKS, TRIGGER_RX_YIELD, TRIGGER_RX_ZERO_ACKS, TRIGGER_SYNC_CHANNEL_TIMER, @@ -282,12 +287,18 @@ enum TRIGGER_TX_COMMAND, TRIGGER_TX_COMMAND_RESPONSE, TRIGGER_TX_DATA, + TRIGGER_TX_DATAGRAM, TRIGGER_TX_DONE, TRIGGER_TX_DUPLICATE_ACK, TRIGGER_TX_FIND_PARTNER, TRIGGER_TX_HEARTBEAT, + TRIGGER_TX_LOAD_CHANNE_TIMER_VALUE, TRIGGER_TX_SPI_DONE, TRIGGER_TX_SYNC_CLOCKS, + TRIGGER_TX_VC_HEARTBEAT, + TRIGGER_TX_VC_SYNC_ACKS, + TRIGGER_TX_VC_UNKNOWN_ACKS, + TRIGGER_TX_VC_ZERO_ACKS, TRIGGER_TX_YIELD, TRIGGER_TX_ZERO_ACKS, TRIGGER_UNKNOWN_PACKET, @@ -353,6 +364,17 @@ typedef enum //Add user LED types from 255 working down } LEDS_USE_TYPE; +typedef struct _CLOCK_SYNC_DATA +{ + int16_t msToNextHopRemote; + uint16_t frameAirTimeMsec; + uint16_t msToNextHop; + int16_t lclHopTimeMsec; + int16_t adjustment; + int8_t delayedHopCount; + bool timeToHop; +} CLOCK_SYNC_DATA; + //These are all the settings that can be set on Serial Terminal Radio. It's recorded to NVM. typedef struct struct_settings { uint16_t sizeOfSettings = 0; //sizeOfSettings **must** be the first entry and must be int