diff --git a/Firmware/RTK_Everywhere/Base.ino b/Firmware/RTK_Everywhere/Base.ino index f5537057..a1508c91 100644 --- a/Firmware/RTK_Everywhere/Base.ino +++ b/Firmware/RTK_Everywhere/Base.ino @@ -10,7 +10,7 @@ void processRTCM(uint8_t *rtcmData, uint16_t dataLength) } for (int x = 0; x < dataLength; x++) - espnowProcessRTCM(rtcmData[x]); + espNowProcessRTCM(rtcmData[x]); loraProcessRTCM(rtcmData, dataLength); @@ -41,4 +41,4 @@ void baseCasterEnableOverride() void baseCasterDisableOverride() { settings.baseCasterOverride = false; -} \ No newline at end of file +} diff --git a/Firmware/RTK_Everywhere/Begin.ino b/Firmware/RTK_Everywhere/Begin.ino index c4e22ef4..6251417c 100644 --- a/Firmware/RTK_Everywhere/Begin.ino +++ b/Firmware/RTK_Everywhere/Begin.ino @@ -69,13 +69,9 @@ void identifyBoard() // Get unit MAC address // This was in beginVersion, but is needed earlier so that beginBoard // can print the MAC address if identifyBoard fails. - esp_read_mac(wifiMACAddress, ESP_MAC_WIFI_STA); - memcpy(btMACAddress, wifiMACAddress, sizeof(wifiMACAddress)); - btMACAddress[5] += - 2; // Convert MAC address to Bluetooth MAC (add 2): - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system.html#mac-address - memcpy(ethernetMACAddress, wifiMACAddress, sizeof(wifiMACAddress)); - ethernetMACAddress[5] += 3; // Convert MAC address to Ethernet MAC (add 3) + getMacAddresses(wifiMACAddress, "wifiMACAddress", ESP_MAC_WIFI_STA, true); + getMacAddresses(btMACAddress, "btMACAddress", ESP_MAC_BT, true); + getMacAddresses(ethernetMACAddress, "ethernetMACAddress", ESP_MAC_ETH, true); // First, test for devices that do not have ID resistors if (productVariant == RTK_UNKNOWN) @@ -747,7 +743,7 @@ void beginBoard() void beginVersion() { char versionString[21]; - getFirmwareVersion(versionString, sizeof(versionString), true); + firmwareVersionGet(versionString, sizeof(versionString), true); char title[50]; RTKBrandAttribute *brandAttributes = getBrandAttributeFromBrand(present.brand); @@ -926,7 +922,7 @@ void beginSD() } // Load firmware file from the microSD card if it is present - scanForFirmware(); + microSDScanForFirmware(); // Mark card not yet usable for logging sdCardSize = 0; @@ -989,12 +985,7 @@ void beginGnssUart() // Never freed... if (rbOffsetArray == nullptr) - { - if (online.psram == true) - rbOffsetArray = (RING_BUFFER_OFFSET *)ps_malloc(length); - else - rbOffsetArray = (RING_BUFFER_OFFSET *)malloc(length); - } + rbOffsetArray = (RING_BUFFER_OFFSET *)rtkMalloc(length, "Ring buffer (rbOffsetArray)"); if (!rbOffsetArray) { diff --git a/Firmware/RTK_Everywhere/Buttons.ino b/Firmware/RTK_Everywhere/Buttons.ino index 3f2b24e4..76c24030 100644 --- a/Firmware/RTK_Everywhere/Buttons.ino +++ b/Firmware/RTK_Everywhere/Buttons.ino @@ -63,7 +63,7 @@ void powerDown(bool displayInfo) { // Platforms with power control won't get here // Postcard will get here if the battery is too low - + systemPrintln("Device powered down"); delay(5000); @@ -224,4 +224,4 @@ bool buttonPressedFor(uint8_t buttonNumber, uint16_t maxTime) uint8_t buttonLastPressed() { return (gpioExpander_lastReleased); -} \ No newline at end of file +} diff --git a/Firmware/RTK_Everywhere/Cellular.ino b/Firmware/RTK_Everywhere/Cellular.ino index 39528860..a8d6a3e2 100644 --- a/Firmware/RTK_Everywhere/Cellular.ino +++ b/Firmware/RTK_Everywhere/Cellular.ino @@ -87,7 +87,7 @@ void cellularEvent(arduino_event_id_t event) if (networkInterfaceHasInternet(NETWORK_CELLULAR) && (event != ARDUINO_EVENT_ETH_GOT_IP) && (event != ARDUINO_EVENT_ETH_GOT_IP6) && (event != ARDUINO_EVENT_PPP_CONNECTED)) { - networkInterfaceEventInternetLost(NETWORK_CELLULAR); + networkInterfaceEventInternetLost(NETWORK_CELLULAR, __FILE__, __LINE__); } // Cellular State Machine @@ -157,7 +157,7 @@ void cellularSimCheck(NetIndex_t index, uintptr_t parameter, bool debug) { systemPrintf("SIM card not present. Marking cellular offline.\r\n"); present.cellular_lara = false; - networkSequenceExit(index, debug); + networkSequenceExit(index, debug, __FILE__, __LINE__); } } diff --git a/Firmware/RTK_Everywhere/Developer.ino b/Firmware/RTK_Everywhere/Developer.ino index 90e0340a..dd356762 100644 --- a/Firmware/RTK_Everywhere/Developer.ino +++ b/Firmware/RTK_Everywhere/Developer.ino @@ -9,6 +9,7 @@ // Ethernet //---------------------------------------- +bool ethernetLinkUp() {return false;} void menuEthernet() {systemPrintln("**Ethernet not compiled**");} bool ntpLogIncreasing = false; @@ -33,8 +34,9 @@ void ntpServerStop() {} void menuTcpUdp() {systemPrint("**Network not compiled**");} void networkBegin() {} -uint8_t networkConsumers() {return(0);} -uint16_t networkGetConsumerTypes() {return(0);} +void networkConsumerAdd(NETCONSUMER_t consumer, NetIndex_t network, const char * fileName, uint32_t lineNumber) {} +bool networkConsumerIsConnected(NETCONSUMER_t consumer) {return false;} +void networkConsumerRemove(NETCONSUMER_t consumer, NetIndex_t network, const char * fileName, uint32_t lineNumber) {} IPAddress networkGetIpAddress() {return("0.0.0.0");} const uint8_t * networkGetMacAddress() { @@ -44,7 +46,8 @@ const uint8_t * networkGetMacAddress() return btMACAddress; #endif return zero; - } +} +NetPriority_t networkGetPriority() {return 0;} bool networkHasInternet() {return false;} bool networkHasInternet(NetIndex_t index) {return false;} bool networkInterfaceHasInternet(NetIndex_t index) {return false;} @@ -54,6 +57,8 @@ void networkMarkHasInternet(NetIndex_t index) {} void networkSequenceBoot(NetIndex_t index) {} void networkSequenceNextEntry(NetIndex_t index, bool debug) {} void networkUpdate() {} +void networkUserAdd(NETCONSUMER_t consumer, const char * fileName, uint32_t lineNumber) {} +void networkUserRemove(NETCONSUMER_t consumer, const char * fileName, uint32_t lineNumber) {} void networkValidateIndex(NetIndex_t index) {} void networkVerifyTables() {} @@ -83,30 +88,30 @@ bool ntripServerIsCasting(int serverIndex) { // TCP client //---------------------------------------- +void tcpClientDiscardBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {} int32_t tcpClientSendData(uint16_t dataHead) {return 0;} void tcpClientUpdate() {} void tcpClientValidateTables() {} void tcpClientZeroTail() {} -void discardTcpClientBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {} //---------------------------------------- // TCP server //---------------------------------------- +void tcpServerDiscardBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {} int32_t tcpServerSendData(uint16_t dataHead) {return 0;} void tcpServerZeroTail() {} void tcpServerValidateTables() {} -void discardTcpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {} //---------------------------------------- // UDP server //---------------------------------------- +void udpServerDiscardBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {} int32_t udpServerSendData(uint16_t dataHead) {return 0;} void udpServerStop() {} void udpServerUpdate() {} void udpServerZeroTail() {} -void discardUdpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {} #endif // COMPILE_NETWORK @@ -117,7 +122,9 @@ void discardUdpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET n #ifndef COMPILE_OTA_AUTO void otaAutoUpdate() {} -bool otaNeedsNetwork() {return false;} +bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength) {return false;} +void otaMenuDisplay(char * currentVersion) {} +bool otaMenuProcessInput(byte incoming) {return false;} void otaUpdate() {} void otaUpdateStop() {} void otaVerifyTables() {} @@ -131,9 +138,9 @@ void otaVerifyTables() {} #ifndef COMPILE_MQTT_CLIENT bool mqttClientIsConnected() {return false;} -bool mqttClientNeedsNetwork() {return false;} void mqttClientPrintStatus() {} void mqttClientRestart() {} +void mqttClientStartEnabled() {} void mqttClientUpdate() {} void mqttClientValidateTables() {} @@ -165,7 +172,6 @@ bool webServerStart(int httpPort = 80) bool parseIncomingSettings() {return false;} void sendStringToWebsocket(const char* stringToSend) {} void stopWebServer() {} -bool webServerNeedsNetwork() {return false;} void webServerStop() {} void webServerUpdate() {} void webServerVerifyTables() {} @@ -178,18 +184,17 @@ void webServerVerifyTables() {} #ifndef COMPILE_ESPNOW -bool espnowGetState() {return ESPNOW_OFF;} -bool espnowIsPaired() {return false;} -void espnowProcessRTCM(byte incoming) {} -esp_err_t espnowRemovePeer(uint8_t *peerMac) {return ESP_OK;} -esp_err_t espnowSendPairMessage(uint8_t *sendToMac) {return ESP_OK;} -bool espnowSetChannel(uint8_t channelNumber) {return false;} -bool espNowStart() {return false;} -#define ESPNOW_START() false -void espnowStaticPairing() {} +bool espNowGetState() {return ESPNOW_OFF;} +bool espNowIsPaired() {return false;} +void espNowProcessRTCM(byte incoming) {} +bool espNowProcessRxPairedMessage() {return true;} +esp_err_t espNowRemovePeer(const uint8_t *peerMac) {return ESP_OK;} +esp_err_t espNowSendPairMessage(const uint8_t *sendToMac) {return ESP_OK;} +bool espNowSetChannel(uint8_t channelNumber) {return false;} +bool espNowStart() {return true;} +void espNowStaticPairing() {} bool espNowStop() {return true;} -#define ESPNOW_STOP() true -void updateEspnow() {} +void espNowUpdate() {} #endif // COMPILE_ESPNOW @@ -199,19 +204,22 @@ void updateEspnow() {} #ifndef COMPILE_WIFI -void menuWiFi() {systemPrintln("**WiFi not compiled**");} -bool wifiApIsRunning() {return false;} -bool wifiConnect(bool startWiFiStation, bool startWiFiAP, unsigned long timeout) {return false;} -uint32_t wifiGetStartTimeout() {return 0;} -#define WIFI_IS_RUNNING() 0 -int wifiNetworkCount() {return 0;} -void wifiResetThrottleTimeout() {} -void wifiResetTimeout() {} -#define WIFI_SOFT_AP_RUNNING() false -bool wifiStart() {return false;} -bool wifiStationIsRunning() {return false;} -#define WIFI_STOP() {} -bool wifiUnavailable() {return true;} +void menuWiFi() {systemPrintln("**WiFi not compiled**");} +void wifiDisplayNetworkData() {} +void wifiDisplaySoftApStatus() {} +bool wifiEspNowOff(const char * fileName, uint32_t lineNumber) {return true;} +bool wifiEspNowOn(const char * fileName, uint32_t lineNumber) {return false;} +void wifiEspNowSetChannel(WIFI_CHANNEL_t channel) {} +uint32_t wifiGetStartTimeout() {return 0;} +int wifiNetworkCount() {return 0;} +void wifiResetThrottleTimeout() {} +void wifiResetTimeout() {} +const char * wifiSoftApGetSsid() {return "";} +bool wifiSoftApOff(const char * fileName, uint32_t lineNumber) {return true;} +bool wifiSoftApOn(const char * fileName, uint32_t lineNumber) {return false;} +bool wifiStationOff(const char * fileName, uint32_t lineNumber) {return true;} +bool wifiStationOn(const char * fileName, uint32_t lineNumber) {return false;} +void wifiStopAll() {} #endif // COMPILE_WIFI diff --git a/Firmware/RTK_Everywhere/Display.ino b/Firmware/RTK_Everywhere/Display.ino index fa466427..0a490397 100644 --- a/Firmware/RTK_Everywhere/Display.ino +++ b/Firmware/RTK_Everywhere/Display.ino @@ -74,18 +74,32 @@ #define ICON_ANTENNA_SHORT (1 << 11) #define ICON_ANTENNA_OPEN (1 << 12) +// Icon positions +enum ICON_POSITION_t +{ + ICON_POSITION_LEFT = 0, + ICON_POSITION_CENTER, + ICON_POSITION_RIGHT, + ICON_POSITION_MAX +}; + +// WiFi icons +const iconProperty * wifiIconTable[ICON_POSITION_MAX][4] +{ // 0 1 2 3 + {&WiFiSymbol0Left64x48, &WiFiSymbol1Left64x48, &WiFiSymbol2Left64x48, &WiFiSymbol3Left64x48}, + {&WiFiSymbol0128x64, &WiFiSymbol1128x64, &WiFiSymbol2128x64, &WiFiSymbol3128x64}, + {&WiFiSymbol0Right64x48, &WiFiSymbol1Right64x48, &WiFiSymbol2Right64x48, &WiFiSymbol3Right64x48}, +}; //---------------------------------------- // Locals //---------------------------------------- static QwiicCustomOLED *oled = nullptr; -unsigned long ssidDisplayTimer = 0; -bool ssidDisplayFirstHalf = false; - // Fonts #include #include +#include #include // Icons @@ -182,6 +196,8 @@ void beginDisplay(TwoWire *i2cBus) // Given the system state, display the appropriate information void displayUpdate() { + static std::vector iconPropertyList; // List of icons to be displayed + // Update the display if connected if (online.display == true) { @@ -196,7 +212,6 @@ void displayUpdate() oled->erase(); - std::vector iconPropertyList; // List of icons to be displayed iconPropertyList.clear(); // Redundant? switch (systemState) @@ -405,13 +420,7 @@ void displayUpdate() displayWebConfigNotStarted(); // Display 'Web Config' break; case (STATE_WEB_CONFIG): - if (networkInterfaceHasInternet(NETWORK_ETHERNET)) - displayConfigViaEthernet(); - else - { - setWiFiIcon(&iconPropertyList); // Blink WiFi in center - displayConfigViaWiFi(); // Display SSID and IP - } + displayWebConfig(iconPropertyList); // Display IP, subnet mask, etc. break; case (STATE_TEST): paintSystemTest(); @@ -487,7 +496,7 @@ void displaySplash() yPos = yPos + fontHeight + 7; char unitFirmware[50]; - getFirmwareVersion(unitFirmware, sizeof(unitFirmware), false); + firmwareVersionGet(unitFirmware, sizeof(unitFirmware), false); printTextCenter(unitFirmware, yPos, QW_FONT_5X7, 1, false); oled->display(); @@ -637,6 +646,8 @@ void paintBatteryLevel(std::vector *iconList) // up arrow, blink the ESP Now icon, etc. void setRadioIcons(std::vector *iconList) { + iconPropertyBlinking prop; + if (online.display == true) { if (present.display_type == DISPLAY_64x48) @@ -654,9 +665,9 @@ void setRadioIcons(std::vector *iconList) // Count the number of radios in use uint8_t numberOfRadios = 1; // Bluetooth always indicated. TODO don't count if BT radio type is OFF. - if (WIFI_IS_RUNNING()) + if (wifiStationRunning || wifiSoftApRunning) numberOfRadios++; - if (espnowGetState() > ESPNOW_OFF) + if (wifiEspNowRunning) numberOfRadios++; // Bluetooth only @@ -671,9 +682,9 @@ void setRadioIcons(std::vector *iconList) setBluetoothIcon_TwoRadios(iconList); // Do we have WiFi or ESP - if (WIFI_IS_RUNNING()) + if (wifiStationRunning || wifiSoftApRunning) setWiFiIcon_TwoRadios(iconList); - else if (espnowGetState() > ESPNOW_OFF) + else if (wifiEspNowRunning) setESPNowIcon_TwoRadios(iconList); setModeIcon(iconList); // Turn on Rover/Base type icons @@ -693,7 +704,6 @@ void setRadioIcons(std::vector *iconList) // No Rover/Base icons } - // On 64x48: squeeze the icon between SIV and logging static bool correctionsIconPosCalculated = false; const uint8_t correctionsIconXPos = 39; @@ -715,7 +725,6 @@ void setRadioIcons(std::vector *iconList) CORRECTION_ID_T correctionSource = correctionGetSource(); if (correctionSource < CORR_NUM) { - iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon.bitmap = correctionIconAttributes[correctionSource].pointer; prop.icon.width = correctionIconAttributes[correctionSource].width; @@ -733,32 +742,13 @@ void setRadioIcons(std::vector *iconList) // Bluetooth indicated when connected: Columns 25 to 31 . TODO don't count if BT radio type is OFF. if (bluetoothGetState() == BT_CONNECTED) { - iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon = BTSymbol128x64; iconList->push_back(prop); } - if (WIFI_IS_RUNNING()) // WiFi : Columns 34 - 46 - { -#ifdef COMPILE_WIFI - int wifiRSSI = WiFi.RSSI(); -#else // COMPILE_WIFI - int wifiRSSI = -40; // Dummy -#endif // COMPILE_WIFI - iconPropertyBlinking prop; - prop.duty = 0b11111111; - // Based on RSSI, select icon - if (wifiRSSI >= -40) - prop.icon = WiFiSymbol3128x64; - else if (wifiRSSI >= -60) - prop.icon = WiFiSymbol2128x64; - else if (wifiRSSI >= -80) - prop.icon = WiFiSymbol1128x64; - else - prop.icon = WiFiSymbol0128x64; - iconList->push_back(prop); - } + if (wifiStationRunning || wifiSoftApRunning) // WiFi : Columns 34 - 46 + displayWiFiIcon(iconList, prop, ICON_POSITION_CENTER, 0b11111111); #ifdef COMPILE_CELLULAR // Cellular : Columns 49 - 61 @@ -769,7 +759,6 @@ void setRadioIcons(std::vector *iconList) // 31 -51 dBm <= RSSI of the network if (cellularIsAttached) { - iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon.bitmap = nullptr; // Based on RSSI, select icon @@ -786,19 +775,19 @@ void setRadioIcons(std::vector *iconList) } #endif // /COMPILE_CELLULAR - if (espnowGetState() == ESPNOW_PAIRED) // ESPNOW : Columns 64 - 71 + if (espNowIsPaired()) // ESPNOW : Columns 64 - 71 { iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon.bitmap = nullptr; // Based on RSSI, select icon - if (espnowRSSI >= -40) + if (espNowRSSI >= -40) prop.icon = ESPNowSymbol3128x64; - else if (espnowRSSI >= -60) + else if (espNowRSSI >= -60) prop.icon = ESPNowSymbol2128x64; - else if (espnowRSSI >= -80) + else if (espNowRSSI >= -80) prop.icon = ESPNowSymbol1128x64; - else if (espnowRSSI > -255) + else if (espNowRSSI > -255) prop.icon = ESPNowSymbol0128x64; // Don't display a symbol if RSSI == -255 if (prop.icon.bitmap != nullptr) @@ -809,7 +798,6 @@ void setRadioIcons(std::vector *iconList) { if (bluetoothIncomingRTCM == true) // Download : Columns 74 - 81 { - iconPropertyBlinking prop; prop.icon = DownloadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); @@ -817,7 +805,6 @@ void setRadioIcons(std::vector *iconList) } if (bluetoothOutgoingRTCM == true) // Upload : Columns 83 - 90 { - iconPropertyBlinking prop; prop.icon = UploadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); @@ -825,30 +812,27 @@ void setRadioIcons(std::vector *iconList) } } - if (espnowGetState() == ESPNOW_PAIRED) + if (espNowIsPaired()) { - if (espnowIncomingRTCM == true) // Download : Columns 74 - 81 + if (espNowIncomingRTCM == true) // Download : Columns 74 - 81 { - iconPropertyBlinking prop; prop.icon = DownloadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); - espnowIncomingRTCM = false; + espNowIncomingRTCM = false; } - if (espnowOutgoingRTCM == true) // Upload : Columns 83 - 90 + if (espNowOutgoingRTCM == true) // Upload : Columns 83 - 90 { - iconPropertyBlinking prop; prop.icon = UploadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); - espnowOutgoingRTCM = false; + espNowOutgoingRTCM = false; } } if (usbSerialIncomingRtcm) { // Download : Columns 74 - 81 - iconPropertyBlinking prop; prop.icon = DownloadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); @@ -863,7 +847,7 @@ void setRadioIcons(std::vector *iconList) #endif // COMPILE_ETHERNET #ifdef COMPILE_WIFI - if (networkInterfaceHasInternet(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) networkHasInternet = true; #endif // COMPILE_WIFI @@ -876,7 +860,6 @@ void setRadioIcons(std::vector *iconList) { if (netIncomingRTCM == true) // Download : Columns 74 - 81 { - iconPropertyBlinking prop; prop.icon = DownloadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); @@ -884,7 +867,6 @@ void setRadioIcons(std::vector *iconList) } if (mqttClientDataReceived == true) // Download : Columns 74 - 81 { - iconPropertyBlinking prop; prop.icon = DownloadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); @@ -892,7 +874,6 @@ void setRadioIcons(std::vector *iconList) } if (netOutgoingRTCM == true) // Upload : Columns 83 - 90 { - iconPropertyBlinking prop; prop.icon = UploadArrow128x64; prop.duty = 0b11111111; iconList->push_back(prop); @@ -910,21 +891,18 @@ void setRadioIcons(std::vector *iconList) break; case (STATE_BASE_TEMP_SETTLE): case (STATE_BASE_TEMP_SURVEY_STARTED): { - iconPropertyBlinking prop; prop.duty = 0b00001111; prop.icon = BaseTemporaryProperties.iconDisplay[present.display_type]; iconList->push_back(prop); } break; case (STATE_BASE_TEMP_TRANSMITTING): { - iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon = BaseTemporaryProperties.iconDisplay[present.display_type]; iconList->push_back(prop); } break; case (STATE_BASE_FIXED_TRANSMITTING): { - iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon = BaseFixedProperties.iconDisplay[present.display_type]; iconList->push_back(prop); @@ -955,7 +933,6 @@ void setRadioIcons(std::vector *iconList) CORRECTION_ID_T correctionSource = correctionGetSource(); if (correctionSource < CORR_NUM) { - iconPropertyBlinking prop; prop.duty = 0b11111111; prop.icon.bitmap = correctionIconAttributes[correctionSource].pointer; prop.icon.width = correctionIconAttributes[correctionSource].width; @@ -1058,38 +1035,38 @@ void setBluetoothIcon_TwoRadios(std::vector *iconList) // This is 64x48-specific void setESPNowIcon_TwoRadios(std::vector *iconList) { - if (espnowGetState() == ESPNOW_PAIRED) + if (espNowIsPaired()) { - if (espnowIncomingRTCM == true || espnowOutgoingRTCM == true) + if (espNowIncomingRTCM == true || espNowOutgoingRTCM == true) { iconPropertyBlinking prop; prop.duty = 0b00001111; // Based on RSSI, select icon - if (espnowRSSI >= -40) + if (espNowRSSI >= -40) prop.icon = ESPNowSymbol3Left64x48; - else if (espnowRSSI >= -60) + else if (espNowRSSI >= -60) prop.icon = ESPNowSymbol2Left64x48; - else if (espnowRSSI >= -80) + else if (espNowRSSI >= -80) prop.icon = ESPNowSymbol1Left64x48; - else // if (espnowRSSI > -255) + else // if (espNowRSSI > -255) prop.icon = ESPNowSymbol0Left64x48; // Always show the symbol because we've got incoming or outgoing data iconList->push_back(prop); // Share the spot. Determine if we need to indicate Up, or Down - if (espnowIncomingRTCM == true) + if (espNowIncomingRTCM == true) { prop.icon = DownloadArrowLeft64x48; prop.duty = 0b11110000; iconList->push_back(prop); - espnowIncomingRTCM = false; // Reset, set during ESP Now data received call back + espNowIncomingRTCM = false; // Reset, set during ESP Now data received call back } - else // if (espnowOutgoingRTCM == true) + else // if (espNowOutgoingRTCM == true) { prop.icon = UploadArrowLeft64x48; prop.duty = 0b11110000; iconList->push_back(prop); - espnowOutgoingRTCM = false; // Reset, set during espnowProcessRTCM() + espNowOutgoingRTCM = false; // Reset, set during espNowProcessRTCM() } } else @@ -1098,16 +1075,16 @@ void setESPNowIcon_TwoRadios(std::vector *iconList) prop.duty = 0b11111111; prop.icon.bitmap = nullptr; // TODO: check this. Surely we want to indicate the correct signal level with no incoming RTCM? - if (espnowIncomingRTCM == true) + if (espNowIncomingRTCM == true) { // Based on RSSI, select icon - if (espnowRSSI >= -40) + if (espNowRSSI >= -40) prop.icon = ESPNowSymbol3Left64x48; - else if (espnowRSSI >= -60) + else if (espNowRSSI >= -60) prop.icon = ESPNowSymbol2Left64x48; - else if (espnowRSSI >= -80) + else if (espNowRSSI >= -80) prop.icon = ESPNowSymbol1Left64x48; - else if (espnowRSSI > -255) + else if (espNowRSSI > -255) prop.icon = ESPNowSymbol0Left64x48; // Don't display a symbol if RSSI == -255 } @@ -1133,24 +1110,14 @@ void setESPNowIcon_TwoRadios(std::vector *iconList) // This is 64x48-specific void setWiFiIcon_TwoRadios(std::vector *iconList) { + iconPropertyBlinking prop; + #ifdef COMPILE_WIFI - if (networkInterfaceHasInternet(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) { if (netIncomingRTCM || netOutgoingRTCM || mqttClientDataReceived) { - int wifiRSSI = WiFi.RSSI(); - iconPropertyBlinking prop; - prop.duty = 0b00001111; - // Based on RSSI, select icon - if (wifiRSSI >= -40) - prop.icon = WiFiSymbol3Left64x48; - else if (wifiRSSI >= -60) - prop.icon = WiFiSymbol2Left64x48; - else if (wifiRSSI >= -80) - prop.icon = WiFiSymbol1Left64x48; - else - prop.icon = WiFiSymbol0Left64x48; - iconList->push_back(prop); + displayWiFiIcon(iconList, prop, ICON_POSITION_LEFT, 0b00001111); // Share the spot. Determine if we need to indicate Up, or Down if (netIncomingRTCM || mqttClientDataReceived) @@ -1172,29 +1139,10 @@ void setWiFiIcon_TwoRadios(std::vector *iconList) } } else - { - int wifiRSSI = WiFi.RSSI(); - iconPropertyBlinking prop; - prop.duty = 0b11111111; - // Based on RSSI, select icon - if (wifiRSSI >= -40) - prop.icon = WiFiSymbol3Left64x48; - else if (wifiRSSI >= -60) - prop.icon = WiFiSymbol2Left64x48; - else if (wifiRSSI >= -80) - prop.icon = WiFiSymbol1Left64x48; - else - prop.icon = WiFiSymbol0Left64x48; - iconList->push_back(prop); - } + displayWiFiIcon(iconList, prop, ICON_POSITION_LEFT, 0b11111111); } else // We are not paired, blink icon - { - iconPropertyBlinking prop; - prop.duty = 0b00001111; - prop.icon = WiFiSymbol3Left64x48; // Full symbol - iconList->push_back(prop); - } + displayWiFiFullIcon(iconList, prop, ICON_POSITION_LEFT, 0b00001111); #endif // COMPILE_WIFI } @@ -1203,24 +1151,14 @@ void setWiFiIcon_TwoRadios(std::vector *iconList) // This is 64x48-specific void setWiFiIcon_ThreeRadios(std::vector *iconList) { + iconPropertyBlinking prop; + #ifdef COMPILE_WIFI - if (networkInterfaceHasInternet(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) { if (netIncomingRTCM || netOutgoingRTCM || mqttClientDataReceived) { - int wifiRSSI = WiFi.RSSI(); - iconPropertyBlinking prop; - prop.duty = 0b00001111; - // Based on RSSI, select icon - if (wifiRSSI >= -40) - prop.icon = WiFiSymbol3Right64x48; - else if (wifiRSSI >= -60) - prop.icon = WiFiSymbol2Right64x48; - else if (wifiRSSI >= -80) - prop.icon = WiFiSymbol1Right64x48; - else - prop.icon = WiFiSymbol0Right64x48; - iconList->push_back(prop); + displayWiFiIcon(iconList, prop, ICON_POSITION_RIGHT, 0b00001111); // Share the spot. Determine if we need to indicate Up, or Down if (netIncomingRTCM || mqttClientDataReceived) @@ -1242,29 +1180,10 @@ void setWiFiIcon_ThreeRadios(std::vector *iconList) } } else - { - int wifiRSSI = WiFi.RSSI(); - iconPropertyBlinking prop; - prop.duty = 0b11111111; - // Based on RSSI, select icon - if (wifiRSSI >= -40) - prop.icon = WiFiSymbol3Right64x48; - else if (wifiRSSI >= -60) - prop.icon = WiFiSymbol2Right64x48; - else if (wifiRSSI >= -80) - prop.icon = WiFiSymbol1Right64x48; - else - prop.icon = WiFiSymbol0Right64x48; - iconList->push_back(prop); - } + displayWiFiIcon(iconList, prop, ICON_POSITION_RIGHT, 0b11111111); } else // We are not paired, blink icon - { - iconPropertyBlinking prop; - prop.duty = 0b00001111; - prop.icon = WiFiSymbol3Right64x48; // Full symbol - iconList->push_back(prop); - } + displayWiFiFullIcon(iconList, prop, ICON_POSITION_RIGHT, 0b00001111); #endif // COMPILE_WIFI } @@ -1283,7 +1202,7 @@ void setWiFiIcon(std::vector *iconList) icon.icon.yPos = 0; #ifdef COMPILE_WIFI - if (networkInterfaceHasInternet(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) icon.duty = 0b11111111; else #endif // COMPILE_WIFI @@ -1612,7 +1531,7 @@ void paintDynamicModel(std::vector *iconList) } #endif // COMPILE_MOSAICX5 } - + if (prop.icon.bitmap) iconList->push_back(prop); } @@ -1743,6 +1662,7 @@ displayCoords paintSIVIcon(std::vector *iconList, const ic // override duty - solid satellite dish icon regardless of fix state duty = 0b11111111; } + // Determine if there is a fix else if (gnss->isFixed() == false) { @@ -2072,20 +1992,34 @@ void paintIPAddress() void displayFullIPAddress(std::vector *iconList) // Bottom left - 128x64 only { + static IPAddress ipAddress; + NetPriority_t priority; + static NetPriority_t previousPriority; + // Max width: 15*6 = 90 pixels (6 pixels per character, nnn.nnn.nnn.nnn) if (present.display_type == DISPLAY_128x64) { char myAddress[16]; - IPAddress ipAddress = networkGetIpAddress(); - - if (ipAddress != IPAddress((uint32_t)0)) + // Reduce calls to networkGetIpAddress + if (networkHasInternet()) { - snprintf(myAddress, sizeof(myAddress), "%s", ipAddress.toString()); + priority = networkGetPriority(); + if (priority != previousPriority) + { + previousPriority = priority; + ipAddress = networkGetIpAddress(); + } - oled->setFont(QW_FONT_5X7); // Set font to smallest - oled->setCursor(0, 55); - oled->print(ipAddress); + // Display the IP address when it is available + if (ipAddress != IPAddress((uint32_t)0)) + { + snprintf(myAddress, sizeof(myAddress), "%s", ipAddress.toString()); + + oled->setFont(QW_FONT_5X7); // Set font to smallest + oled->setCursor(0, 55); + oled->print(ipAddress); + } } } } @@ -2362,6 +2296,42 @@ void displaySDFail(uint16_t displayTime) } } +// Display the full WiFi icon +void displayWiFiFullIcon(std::vector *iconList, + iconPropertyBlinking prop, + uint8_t position, + uint8_t dutyCycle) +{ + prop.duty = dutyCycle; + prop.icon = *wifiIconTable[position][3]; + iconList->push_back(prop); +} + +// Display the WiFi icon based upon RSSI value +void displayWiFiIcon(std::vector *iconList, + iconPropertyBlinking prop, + uint8_t position, + uint8_t dutyCycle) +{ +#ifdef COMPILE_WIFI + int wifiRSSI = WiFi.RSSI(); +#else // COMPILE_WIFI + int wifiRSSI = -40; // Dummy +#endif // COMPILE_WIFI + + prop.duty = dutyCycle; + // Based on RSSI, select icon + if (wifiRSSI >= -40) + prop.icon = *wifiIconTable[position][3]; + else if (wifiRSSI >= -60) + prop.icon = *wifiIconTable[position][2]; + else if (wifiRSSI >= -80) + prop.icon = *wifiIconTable[position][1]; + else + prop.icon = *wifiIconTable[position][0]; + iconList->push_back(prop); +} + // Draw a frame at outside edge void drawFrame() { @@ -2408,6 +2378,20 @@ void displayEventMarked(uint16_t displayTime) displayMessage("Event Marked", displayTime); } +// Print the error message every 15 seconds +void displayHalt() +{ + if (online.display) + { + oled->erase(); // Clear the display's internal buffer + int yPos = (oled->getHeight() - 16) / 2; + QwiicFont * font = (oled->getWidth() > 64) ? (QwiicFont *)&QW_FONT_31X48 + : (QwiicFont *)&QW_FONT_8X16; + printTextCenter("Halt", yPos, *font, 1, false); // text, y, font type, kerning, inverted + oled->display(); // Push internal buffer to display + } +} + void displayNoLogging(uint16_t displayTime) { displayMessage("No Logging", displayTime); @@ -3152,17 +3136,31 @@ void displayWebConfigNotStarted() displayMessage("Web Config", 0); } -void displayConfigViaWiFi() +int displayEthernetIcon() { - int yPos = WiFi_Symbol_Height + 2; - int fontHeight = 8; + static bool blink; + uint8_t xPos = (oled->getWidth() - Ethernet_Icon_Width) / 2; + int yPos = Ethernet_Icon_Height / 2; // yPos is 6 - // Characters before pixels start getting cut off. 11 characters can cut off a few pixels. - const int displayMaxCharacters = (present.display_type == DISPLAY_64x48) ? 10 : 21; + blink ^= 1; + if (ethernetLinkUp() || blink) + displayBitmap(xPos, yPos, Ethernet_Icon_Width, Ethernet_Icon_Height, Ethernet_Icon); - printTextCenter("SSID:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted + yPos += Ethernet_Icon_Height * 1.5; // yPos is now 24 + return yPos; +} - yPos = yPos + fontHeight + 1; +void displayWebConfig(std::vector &iconPropertyList) +{ + // Characters before pixels start getting cut off. 11 characters can cut off a few pixels. + const int displayMaxCharacters = (present.display_type == DISPLAY_64x48) ? 10 : 21; + bool displaySsid = true; + int fontHeight = 8; + char myIP[20] = {'\0'}; + char mySSID[SSID_LENGTH + 1] = {'\0'}; + static bool ssidDisplayFirstHalf; + static unsigned long ssidDisplayTimer; + int yPos = WiFi_Symbol_Height + 2; // Toggle display back and forth for long SSIDs and IPs // Run the timer no matter what, but load firstHalf/lastHalf with the same thing if strlen < maxWidth @@ -3172,157 +3170,79 @@ void displayConfigViaWiFi() ssidDisplayFirstHalf = !ssidDisplayFirstHalf; } - // Convert current SSID to string - char mySSID[50] = {'\0'}; - -#ifdef COMPILE_WIFI - if (settings.wifiConfigOverAP == true) - snprintf(mySSID, sizeof(mySSID), "%s", WiFi.softAPSSID().c_str()); + // Get the SSID and IP Address +#ifndef COMPILE_WIFI +#ifndef COMPILE_ETHERNET + strcpy(mySSID, "!Compiled"); + strcpy(myIP, "0.0.0.0"); +#endif // COMPILE_ETHERNET +#else // COMPILE_WIFI + if (wifi.softApOnline()) + { + setWiFiIcon(&iconPropertyList); // Blink WiFi in center + snprintf(mySSID, sizeof(mySSID), "%s", wifiSoftApGetSsid()); + strcpy(myIP, wifi.softApIpAddress().toString().c_str()); + } + else if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) + { + setWiFiIcon(&iconPropertyList); // Blink WiFi in center + snprintf(mySSID, sizeof(mySSID), "%s", wifi.stationSsid()); + strcpy(myIP, wifi.stationIpAddress().toString().c_str()); + } else +#ifndef COMPILE_ETHERNET { - if (WiFi.getMode() == WIFI_STA) - snprintf(mySSID, sizeof(mySSID), "%s", WiFi.SSID().c_str()); - - // If we failed to connect to a friendly WiFi, and then fell back to AP mode, still display RTK Config - else if (WiFi.getMode() == WIFI_AP) - snprintf(mySSID, sizeof(mySSID), "%s", WiFi.softAPSSID().c_str()); - - // If we are in AP+STA mode, still display RTK Config - else if (WiFi.getMode() == WIFI_AP_STA) - snprintf(mySSID, sizeof(mySSID), "%s", WiFi.softAPSSID().c_str()); - - else - snprintf(mySSID, sizeof(mySSID), "%s", "Error"); + strcpy(mySSID, "Error"); + strcpy(myIP, "0.0.0.0"); } -#else // COMPILE_WIFI - snprintf(mySSID, sizeof(mySSID), "%s", "!Compiled"); -#endif // COMPILE_WIFI +#endif // COMPILE_ETHERNET +#endif // COMPILE_WIFI - char mySSIDFront[displayMaxCharacters + 1]; // 1 for null terminator - char mySSIDBack[displayMaxCharacters + 1]; // 1 for null terminator +#ifdef COMPILE_ETHERNET + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) + { + yPos = displayEthernetIcon(); + displaySsid = false; + strcpy(myIP, ETH.localIP().toString().c_str()); + } + else + { +#ifdef COMPILE_WIFI + setWiFiIcon(&iconPropertyList); // Blink WiFi in center + displaySsid = false; +#else // COMPILE_WIFI + yPos = displayEthernetIcon(); +#endif // COMPILE_WIFI + strcpy(mySSID, "Error"); + strcpy(myIP, "0.0.0.0"); + } +#endif // COMPILE_ETHERNET // Trim SSID to a max length - strncpy(mySSIDFront, mySSID, displayMaxCharacters); - - if (strlen(mySSID) > displayMaxCharacters) - strncpy(mySSIDBack, mySSID + (strlen(mySSID) - displayMaxCharacters), displayMaxCharacters); - else - strncpy(mySSIDBack, mySSID, displayMaxCharacters); + mySSID[SSID_LENGTH] = 0; + if ((strlen(mySSID) > displayMaxCharacters) && !ssidDisplayFirstHalf) + memcpy(mySSID, &mySSID[strlen(mySSID) - displayMaxCharacters], displayMaxCharacters); + mySSID[displayMaxCharacters] = '\0'; - mySSIDFront[displayMaxCharacters] = '\0'; - mySSIDBack[displayMaxCharacters] = '\0'; + // Trim IP address to a max length + if ((strlen(myIP) > displayMaxCharacters) && !ssidDisplayFirstHalf) + memcpy(myIP, &myIP[strlen(myIP) - displayMaxCharacters], displayMaxCharacters); + myIP[displayMaxCharacters] = '\0'; - if (ssidDisplayFirstHalf) - printTextCenter(mySSIDFront, yPos, QW_FONT_5X7, 1, false); - else - printTextCenter(mySSIDBack, yPos, QW_FONT_5X7, 1, false); + // Display the SSID header + if (displaySsid) + { + printTextCenter("SSID:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted + yPos = yPos + fontHeight + 1; + } + // Display the SSID + printTextCenter(mySSID, yPos, QW_FONT_5X7, 1, false); yPos = yPos + fontHeight + 3; - printTextCenter("IP:", yPos, QW_FONT_5X7, 1, false); + // Display the IP header + printTextCenter("IP:", yPos, QW_FONT_5X7, 1, false); yPos = yPos + fontHeight + 1; -#ifdef COMPILE_AP - IPAddress myIpAddress; - if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) - myIpAddress = WiFi.softAPIP(); - else - myIpAddress = WiFi.localIP(); - - // Convert to string - char myIP[20] = {'\0'}; - snprintf(myIP, sizeof(myIP), "%s", myIpAddress.toString()); - - char myIPFront[displayMaxCharacters + 1]; // 1 for null terminator - char myIPBack[displayMaxCharacters + 1]; // 1 for null terminator - - strncpy(myIPFront, myIP, displayMaxCharacters); - - if (strlen(myIP) > displayMaxCharacters) - strncpy(myIPBack, myIP + (strlen(myIP) - displayMaxCharacters), displayMaxCharacters); - else - strncpy(myIPBack, myIP, displayMaxCharacters); - - myIPFront[displayMaxCharacters] = '\0'; - myIPBack[displayMaxCharacters] = '\0'; - - if (ssidDisplayFirstHalf == true) - printTextCenter(myIPFront, yPos, QW_FONT_5X7, 1, false); - else - printTextCenter(myIPBack, yPos, QW_FONT_5X7, 1, false); - -#else // COMPILE_AP - printTextCenter("!Compiled", yPos, QW_FONT_5X7, 1, false); -#endif // COMPILE_AP -} - -void displayConfigViaEthernet() -{ -#ifdef COMPILE_ETHERNET - - if (online.display == true) - { - oled->erase(); - - uint8_t xPos = (oled->getWidth() / 2) - (Ethernet_Icon_Width / 2); - uint8_t yPos = Ethernet_Icon_Height / 2; // yPos is 6 - - static bool blink = 0; - blink ^= 1; - - if (ETH.linkUp() || blink) - displayBitmap(xPos, yPos, Ethernet_Icon_Width, Ethernet_Icon_Height, Ethernet_Icon); - - yPos += Ethernet_Icon_Height * 1.5; // yPos is now 24 - - printTextCenter("IP:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted - yPos += 8; // yPos is now 32 - if (present.display_type == DISPLAY_128x64) - yPos += 4; // yPos is 36 on 128x64 displays, 32 on 64x48 displays - - char ipAddress[16]; - IPAddress localIP = ETH.localIP(); - snprintf(ipAddress, sizeof(ipAddress), "%s", localIP.toString()); - - // yPos is 36 on 128x64 displays, 32 on 64x48 displays. QW_FONT_8x16 will fit - as it only uses ~12 rows. - - // See if 8x16 will fit. But widest character is only 7 pixels, so divide by 8 (7 plus 1 kerning) not 9. - int displayWidth8X16 = ((present.display_type == DISPLAY_128x64) ? 16 : 8); - int displayWidth5X7 = ((present.display_type == DISPLAY_128x64) ? 21 : 10); // 5 plus 1 kerning - - // If we can print the full IP address without shuttling - if (strlen(ipAddress) <= displayWidth8X16) - { - printTextCenter(ipAddress, yPos, QW_FONT_8X16, 1, false); - } - else if (strlen(ipAddress) <= displayWidth5X7) - { - printTextCenter(ipAddress, yPos, QW_FONT_5X7, 1, false); - } - else - { - // Print as many characters as we can. Shuttle back and forth to display all. - static int startPos = 0; - char printThis[displayWidth5X7 + 1]; - int extras = strlen(ipAddress) - displayWidth5X7; - int shuttle[2 * extras]; - int x; - for (x = 0; x <= extras; x++) - shuttle[x] = x; - for (int y = extras - 1; y > 0; y--) - shuttle[x++] = y; - if (startPos >= (2 * extras)) - startPos = 0; - snprintf(printThis, sizeof(printThis), &ipAddress[shuttle[startPos]]); - startPos++; - printTextCenter(printThis, yPos, QW_FONT_5X7, 1, false); - } - - oled->display(); - } - -#else // COMPILE_ETHERNET - uint8_t yPos = oled->getHeight() / 2 - 4; - printTextCenter("!Compiled", yPos, QW_FONT_5X7, 1, false); -#endif // COMPILE_ETHERNET + printTextCenter(myIP, yPos, QW_FONT_5X7, 1, false); } diff --git a/Firmware/RTK_Everywhere/ESPNOW.ino b/Firmware/RTK_Everywhere/ESPNOW.ino index e09bf0dc..63c05d5e 100644 --- a/Firmware/RTK_Everywhere/ESPNOW.ino +++ b/Firmware/RTK_Everywhere/ESPNOW.ino @@ -3,27 +3,26 @@ Handle the ESP-NOW events Use ESP NOW protocol to transmit RTCM between RTK Products via 2.4GHz + ESP-NOW start and stop based upon example 4_9_ESP_NOW in hw EVK repo How pairing works: 1. Device enters pairing mode 2. Device adds the broadcast MAC (all 0xFFs) as peer 3. Device waits for incoming pairing packet from remote - 4. If valid pairing packet received, add peer, immediately transmit a pairing packet to that peer and exit. - - ESP NOW is bare metal, there is no guaranteed packet delivery. For RTCM byte transmissions using ESP NOW: - We don't care about dropped packets or packets out of order. The ZED will check the integrity of the RTCM packet. - We don't care if the ESP NOW packet is corrupt or not. RTCM has its own CRC. RTK needs valid RTCM once every - few seconds so a single dropped frame is not critical. + 4. If valid pairing packet received, add peer, immediately transmit + a pairing packet to that peer and exit. + + ESP NOW is bare metal, there is no guaranteed packet delivery. For RTCM + byte transmissions using ESP NOW: + * We don't care about dropped packets or packets out of order. The GNSS + checks the integrity of the RTCM packet. + * We don't care if the ESP NOW packet is corrupt or not. RTCM has its own + CRC. RTK needs valid RTCM once every few seconds so a single dropped + frame is not critical. **********************************************************************/ #ifdef COMPILE_ESPNOW -//**************************************** -// Constants -//**************************************** - -const uint8_t espnowBroadcastAddr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - //**************************************** // Types //**************************************** @@ -41,11 +40,17 @@ typedef struct _ESP_NOW_PAIR_MESSAGE // Locals //**************************************** +uint16_t espNowBytesSent; // May be more than 255 +unsigned long espNowLastAdd; // Tracks how long since the last byte was added to the outgoing buffer +unsigned long espNowLastRssiUpdate; +uint8_t espNowOutgoing[250]; // ESP NOW has max of 250 characters +uint8_t espNowOutgoingSpot; // ESP Now has a max of 250 characters +uint8_t espNowReceivedMAC[6]; // Holds the MAC received during pairing ESPNOWState espNowState; //********************************************************************* // Add a peer to the ESP-NOW network -esp_err_t espNowAddPeer(const uint8_t * peerMac) +esp_err_t espNowAddPeer(const uint8_t * peerMac, bool encrypt) { esp_now_peer_info_t peerInfo; @@ -61,87 +66,156 @@ esp_err_t espNowAddPeer(const uint8_t * peerMac) esp_err_t result = esp_now_add_peer(&peerInfo); if (result != ESP_OK) { - systemPrintf("ERROR: Failed to add ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x, result: %d\r\n", - espnowBroadcastAddr[0], espnowBroadcastAddr[1], - espnowBroadcastAddr[2], espnowBroadcastAddr[3], - espnowBroadcastAddr[4], espnowBroadcastAddr[5], - result); + systemPrintf("ERROR: Failed to add ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x, result: %d (%s)\r\n", + peerMac[0], peerMac[1], peerMac[2], peerMac[3], + peerMac[4], peerMac[5], result, esp_err_to_name(result)); } else if (settings.debugEspNow) systemPrintf("Added ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x\r\n", - espnowBroadcastAddr[0], espnowBroadcastAddr[1], - espnowBroadcastAddr[2], espnowBroadcastAddr[3], - espnowBroadcastAddr[4], espnowBroadcastAddr[5]); + peerMac[0], peerMac[1], peerMac[2], peerMac[3], + peerMac[4], peerMac[5]); return result; } //********************************************************************* -// Get the current ESP-NOW state -ESPNOWState espnowGetState() +// Add a given MAC address to the peer list +esp_err_t espNowAddPeer(const uint8_t *peerMac) { - return espNowState; + return espNowAddPeer(peerMac, true); // Encrypt by default } -//---------------------------------------------------------------------- -// ESP-NOW bringup from example 4_9_ESP_NOW -// 1. Set station mode -// 2. Create nowSerial as new ESP_NOW_Serial_Class -// 3. nowSerial.begin -// ESP-NOW bringup from RTK -// 1. Get WiFi mode -// 2. Set WiFi station mode if necessary -// 3. Get WiFi station protocols -// 4. Set WIFI_PROTOCOL_LR protocol -// 5. Call esp_now_init -// 6. Call esp_wifi_set_promiscuous(true) -// 7. Set promiscuous receive callback [esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_cb)] -// to get RSSI of action frames -// 8. Assign a channel if necessary, call espnowSetChannel -// 9. Set receive callback [esp_now_register_recv_cb(espnowOnDataReceived)] -// 10. Add peers from settings -// A. If no peers exist -// i. Determine if broadcast peer exists, call esp_now_is_peer_exist -// ii. Add broadcast peer if necessary, call espnowAddPeer -// iii. Set ESP-NOW state, call espnowSetState(ESP_NOW_BROADCASTING) -// B. If peers exist, -// i. Set ESP-NOW state, call espnowSetState(ESP_NOW_PAIRED) -// ii. Loop through peers listed in settings, for each -// a. Determine if peer exists, call esp_now_is_peer_exist -// b. Add peer if necessary, call espnowAddPeer -// -// In espnowOnDataReceived -// 11. Save ESP-NOW RSSI -// 12. Set lastEspnowRssiUpdate = millis() -// 13. If in ESP_NOW_PAIRING state -// A. Validate message CRC -// B. If valid CRC -// i. Save peer MAC address -// ii. espnowSetState(ESPNOW_MAC_RECEIVED) -// 14. Else if ESPNOW_MAC_RECEIVED state -// A. If ESP-NOW is corrections source, correctionLastSeen(CORR_ESPNOW) -// i. gnss->pushRawData -// 15. Set espnowIncomingRTCM -// -// ESP-NOW shutdown from RTK -// 1. esp_wifi_set_promiscuous(false) -// 2. esp_wifi_set_promiscuous_rx_cb(nullptr) -// 3. esp_now_unregister_recv_cb() -// 4. Remove all peers by calling espnowRemovePeer -// 5. Get WiFi mode -// 6. Set WiFi station mode if necessary -// 7. esp_wifi_get_protocol -// 8. Turn off long range protocol if necessary, call esp_wifi_set_protocol -// 9. Turn off ESP-NOW. call esp_now_deinit -// 10. Set ESP-NOW state, call espnowSetState(ESPNOW_OFF) -// 11. Restart WiFi if necessary -//---------------------------------------------------------------------- +//********************************************************************* +// Start ESP-Now if needed, put ESP-Now into broadcast state +void espNowBeginPairing() +{ + // Start ESP-NOW if necessary + wifiEspNowOn(__FILE__, __LINE__); + + // To begin pairing, we must add the broadcast MAC to the peer list + espNowAddPeer(espNowBroadcastAddr, false); // Encryption is not supported for multicast addresses + + espNowSetState(ESPNOW_PAIRING); +} + +//********************************************************************* +// Determine if ESP-NOW is paired +bool espNowIsPaired() +{ + return (espNowState == ESPNOW_PAIRED); +} + +//********************************************************************* +// Callback when data is received +void espNowOnDataReceived(const esp_now_recv_info *mac, + const uint8_t *incomingData, + int len) +{ +// typedef struct esp_now_recv_info { +// uint8_t * src_addr; // Source address of ESPNOW packet +// uint8_t * des_addr; // Destination address of ESPNOW packet +// wifi_pkt_rx_ctrl_t * rx_ctrl; // Rx control info of ESPNOW packet +// } esp_now_recv_info_t; + + // Display the packet + if (settings.debugEspNow == true) + { + systemPrintf("*** ESP-NOW: RX %02x:%02x:%02x:%02x:%02x:%02x --> %02x:%02x:%02x:%02x:%02x:%02x, %d bytes, rssi: %d\r\n", + mac->src_addr[0], mac->src_addr[1], mac->src_addr[2], + mac->src_addr[3], mac->src_addr[4], mac->src_addr[5], + mac->des_addr[0], mac->des_addr[1], mac->des_addr[2], + mac->des_addr[3], mac->des_addr[4], mac->des_addr[5], + len, packetRSSI); + } + + if (espNowState == ESPNOW_PAIRING) + { + ESP_NOW_PAIR_MESSAGE pairMessage; + if (len == sizeof(pairMessage)) // First error check + { + memcpy(&pairMessage, incomingData, len); + + // Compute the CRC + uint8_t tempCRC = 0; + for (int x = 0; x < 6; x++) + tempCRC += pairMessage.macAddress[x]; + + // Check the CRC + if (tempCRC == pairMessage.crc) + { + // Valid CRC, save the MAC address + memcpy(&espNowReceivedMAC, pairMessage.macAddress, 6); + espNowSetState(ESPNOW_MAC_RECEIVED); + } + // else Pair CRC failed + } + } + else + { + espNowRSSI = packetRSSI; // Record this packet's RSSI as an ESP NOW packet + + // We've just received ESP-Now data. We assume this is RTCM and push it directly to the GNSS. + // Determine if ESPNOW is the correction source + if (correctionLastSeen(CORR_ESPNOW)) + { + // Pass RTCM bytes (presumably) from ESP NOW out ESP32-UART to GNSS + gnss->pushRawData((uint8_t *)incomingData, len); + + if ((settings.debugEspNow == true || settings.debugCorrections == true) && !inMainMenu) + systemPrintf("ESPNOW received %d RTCM bytes, pushed to GNSS, RSSI: %d\r\n", len, espNowRSSI); + } + else + { + if ((settings.debugEspNow == true || settings.debugCorrections == true) && !inMainMenu) + systemPrintf("ESPNOW received %d RTCM bytes, NOT pushed due to priority, RSSI: %d\r\n", len, + espNowRSSI); + } + + espNowIncomingRTCM = true; // Display a download icon + espNowLastRssiUpdate = millis(); + } +} + +//********************************************************************* +// Buffer RTCM data and send to ESP-NOW peer +void espNowProcessRTCM(byte incoming) +{ + // If we are paired, + // Or if the radio is broadcasting + // Then add bytes to the outgoing buffer + if (espNowState == ESPNOW_PAIRED || espNowState == ESPNOW_BROADCASTING) + { + // Move this byte into ESP NOW to send buffer + espNowOutgoing[espNowOutgoingSpot++] = incoming; + espNowLastAdd = millis(); + } + + // Send buffer when full + if (espNowOutgoingSpot == sizeof(espNowOutgoing)) + { + espNowOutgoingSpot = 0; // Wrap + + if (espNowState == ESPNOW_PAIRED) + esp_now_send(0, (uint8_t *)&espNowOutgoing, sizeof(espNowOutgoing)); // Send packet to all peers + else // if (espNowState == ESPNOW_BROADCASTING) + { + esp_now_send(espNowBroadcastAddr, (uint8_t *)&espNowOutgoing, + sizeof(espNowOutgoing)); // Send packet via broadcast + } + + delay(10); // We need a small delay between sending multiple packets + + espNowBytesSent += sizeof(espNowOutgoing); + + espNowOutgoingRTCM = true; + } +} //********************************************************************* // Update the state of the ESP Now state machine // -// +--------------------+ -// | ESPNOW_OFF | -// +--------------------+ +// +---------------------+ +// | ESPNOW_OFF | +// +---------------------+ // | | // | | No pairs listed // | V @@ -157,9 +231,9 @@ ESPNOWState espnowGetState() // | | // | | // | V -// | +--------------------+ +// | +---------------------+ // | | ESPNOW_MAC_RECEIVED | -// | +--------------------+ +// | +---------------------+ // | | // | | // | V @@ -173,46 +247,84 @@ ESPNOWState espnowGetState() //********************************************************************* //********************************************************************* -// Callback when data is received -void espNowRxHandler(const esp_now_recv_info *mac, - const uint8_t *incomingData, - int len) +// Regularly call during pairing to see if we've received a Pairing message +bool espNowProcessRxPairedMessage() { -// typedef struct esp_now_recv_info { -// uint8_t * src_addr; // Source address of ESPNOW packet -// uint8_t * des_addr; // Destination address of ESPNOW packet -// wifi_pkt_rx_ctrl_t * rx_ctrl; // Rx control info of ESPNOW packet -// } esp_now_recv_info_t; + if (espNowState != ESPNOW_MAC_RECEIVED) + return false; - uint8_t receivedMAC[6]; + // Remove broadcast peer + espNowRemovePeer(espNowBroadcastAddr); - if (espNowState == ESPNOW_PAIRING) + // Is the received MAC already paired? + if (esp_now_is_peer_exist(espNowReceivedMAC) == true) { - ESP_NOW_PAIR_MESSAGE pairMessage; - if (len == sizeof(pairMessage)) // First error check - { - memcpy(&pairMessage, incomingData, len); + // Yes, already paired + if (settings.debugEspNow == true) + systemPrintln("Peer already exists"); + } + else + { + // No, add new peer to system + espNowAddPeer(espNowReceivedMAC); - // Check CRC - uint8_t tempCRC = 0; - for (int x = 0; x < 6; x++) - tempCRC += pairMessage.macAddress[x]; + // Record this MAC to peer list + memcpy(settings.espnowPeers[settings.espnowPeerCount], espNowReceivedMAC, 6); + settings.espnowPeerCount++; + settings.espnowPeerCount %= ESPNOW_MAX_PEERS; + } - if (tempCRC == pairMessage.crc) // 2nd error check - { - memcpy(&receivedMAC, pairMessage.macAddress, 6); - espNowSetState(ESPNOW_MAC_RECEIVED); - } - // else Pair CRC failed - } + // Send message directly to the received MAC (not unicast), then exit + espNowSendPairMessage(espNowReceivedMAC); + + // Enable radio. User may have arrived here from the setup menu rather than serial menu. + settings.enableEspNow = true; + + // Record enableEspNow and espnowPeerCount to NVM + recordSystemSettings(); + espNowSetState(ESPNOW_PAIRED); + return (true); +} + +//********************************************************************* +// Remove a given MAC address from the peer list +esp_err_t espNowRemovePeer(const uint8_t *peerMac) +{ + esp_err_t result = esp_now_del_peer(peerMac); + if (settings.debugEspNow == true) + { + if (result != ESP_OK) + systemPrintf("ERROR: Failed to remove ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x, result: %s\r\n", + peerMac[0], peerMac[1], + peerMac[2], peerMac[3], + peerMac[4], peerMac[5], + esp_err_to_name(result)); + else + systemPrintf("Removed ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x\r\n", + peerMac[0], peerMac[1], + peerMac[2], peerMac[3], + peerMac[4], peerMac[5]); } - else if (settings.debugEspNow) - systemPrintf("*** ESP-NOW: RX %02x:%02x:%02x:%02x:%02x:%02x --> %02x:%02x:%02x:%02x:%02x:%02x, %d bytes, rssi: %d\r\n", - mac->src_addr[0], mac->src_addr[1], mac->src_addr[2], - mac->src_addr[3], mac->src_addr[4], mac->src_addr[5], - mac->des_addr[0], mac->des_addr[1], mac->des_addr[2], - mac->des_addr[3], mac->des_addr[4], mac->des_addr[5], - len, packetRSSI); + return result; +} + +//********************************************************************* +// Create special pair packet to a given MAC +esp_err_t espNowSendPairMessage(const uint8_t *sendToMac) +{ + // Assemble message to send + ESP_NOW_PAIR_MESSAGE pairMessage; + + // Get unit MAC address + memcpy(pairMessage.macAddress, wifiMACAddress, 6); + pairMessage.encrypt = false; + pairMessage.channel = 0; + + pairMessage.crc = 0; // Calculate CRC + for (int x = 0; x < 6; x++) + pairMessage.crc += wifiMACAddress[x]; + + return (esp_now_send(sendToMac, (uint8_t *)&pairMessage, sizeof(pairMessage))); // Send packet to given MAC } //********************************************************************* @@ -274,7 +386,7 @@ bool espNowStart() { started = false; - // 5. Call esp_now_init + // Initialize the ESP-NOW layer if (settings.debugEspNow) systemPrintf("Calling esp_now_init\r\n"); status = esp_now_init(); @@ -284,45 +396,91 @@ bool espNowStart() break; } - // 9. Set receive callback [esp_now_register_recv_cb(espnowOnDataReceived)] + // Set the receive packet routine address if (settings.debugEspNow) systemPrintf("Calling esp_now_register_recv_cb\r\n"); - status = esp_now_register_recv_cb(espNowRxHandler); + status = esp_now_register_recv_cb(espNowOnDataReceived); if (status != ESP_OK) { systemPrintf("ERROR: Failed to set ESP_NOW RX callback, status: %d\r\n", status); break; } - // 10. Add peers from settings - // i. Set ESP-NOW state, call espNowSetState(ESPNOW_PAIRED) - if (settings.debugEspNow) - systemPrintf("Calling espNowSetState\r\n"); - espNowSetState(ESPNOW_PAIRED); - - // ii. Loop through peers listed in settings, for each - for (index = 0; index < ESPNOW_MAX_PEERS; index++) + // Check for peers listed in settings + if (settings.espnowPeerCount == 0) { - // a. Determine if peer exists, call esp_now_is_peer_exist - if (settings.debugEspNow) - systemPrintf("Calling esp_now_is_peer_exist\r\n"); - if (esp_now_is_peer_exist(settings.espnowPeers[index]) == false) + // No peers, enter broadcast mode + // Determine if the broadcast peer already exists + if (esp_now_is_peer_exist(espNowBroadcastAddr) == true) + { + if (settings.debugEspNow == true) + systemPrintln("Broadcast peer already exists"); + } + else + { + // Add the broadcast peer + esp_err_t result = espNowAddPeer(espNowBroadcastAddr); + if (settings.debugEspNow == true) + { + if (result != ESP_OK) + systemPrintln("Failed to add broadcast peer"); + else + systemPrintln("Broadcast peer added"); + } + } + espNowSetState(ESPNOW_BROADCASTING); + } + else + { + // If we have peers, move to paired state + espNowSetState(ESPNOW_PAIRED); + + if (settings.debugEspNow == true) + systemPrintf("Adding %d espNow peers\r\n", settings.espnowPeerCount); + + // Loop through peers listed in settings + for (index = 0; index < ESPNOW_MAX_PEERS; index++) { - // b. Add peer if necessary, call espnowAddPeer - if (settings.debugEspNow) - systemPrintf("Calling espNowAddPeer\r\n"); - status = espNowAddPeer(&settings.espnowPeers[index][0]); - if (status != ESP_OK) + // Determine if peer exists + if (esp_now_is_peer_exist(settings.espnowPeers[index])) + { + if (settings.debugEspNow == true) + systemPrintf("ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x, status: already exists\r\n", + settings.espnowPeers[index][0], + settings.espnowPeers[index][1], + settings.espnowPeers[index][2], + settings.espnowPeers[index][3], + settings.espnowPeers[index][4], + settings.espnowPeers[index][5]); + } + else { - systemPrintf("ERROR: Failed to add ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x, status: %d\r\n", - settings.espnowPeers[index][0], - settings.espnowPeers[index][1], - settings.espnowPeers[index][2], - settings.espnowPeers[index][3], - settings.espnowPeers[index][4], - settings.espnowPeers[index][5], - status); - break; + // Add peer if necessary + status = espNowAddPeer(&settings.espnowPeers[index][0]); + if (status == ESP_OK) + { + if (settings.debugEspNow == true) + systemPrintf("Added ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x\r\n", + settings.espnowPeers[index][0], + settings.espnowPeers[index][1], + settings.espnowPeers[index][2], + settings.espnowPeers[index][3], + settings.espnowPeers[index][4], + settings.espnowPeers[index][5]); + } + else + { + if (settings.debugEspNow == true) + systemPrintf("ERROR: Failed to add ESP-NOW peer %02x:%02x:%02x:%02x:%02x:%02x, status: %d, %s\r\n", + settings.espnowPeers[index][0], + settings.espnowPeers[index][1], + settings.espnowPeers[index][2], + settings.espnowPeers[index][3], + settings.espnowPeers[index][4], + settings.espnowPeers[index][5], + status, esp_err_to_name(status)); + break; + } } } @@ -341,6 +499,50 @@ bool espNowStart() return started; } +//********************************************************************* +// A blocking function that is used to pair two devices +// either through the serial menu or AP config +void espNowStaticPairing() +{ + systemPrintln("Begin ESP NOW Pairing"); + + // Start ESP-Now if needed, put ESP-Now into broadcast state + espNowBeginPairing(); + + // Begin sending our MAC every 250ms until a remote device sends us there info + randomSeed(millis()); + + systemPrintln("Begin pairing. Place other unit in pairing mode. Press any key to exit."); + clearBuffer(); + + bool exitPair = false; + while (exitPair == false) + { + if (systemAvailable()) + { + systemPrintln("User pressed button. Pairing canceled."); + break; + } + + int timeout = 1000 + random(0, 100); // Delay 1000 to 1100ms + for (int x = 0; x < timeout; x++) + { + delay(1); + + if (espNowProcessRxPairedMessage() == true) // Check if we've received a pairing message + { + systemPrintln("Pairing compete"); + exitPair = true; + break; + } + } + + espNowSendPairMessage(espNowBroadcastAddr); // Send unit's MAC address over broadcast, no ack, no encryption + + systemPrintln("Scanning for other radio..."); + } +} + //********************************************************************* // Stop ESP-NOW layer bool espNowStop() @@ -372,14 +574,14 @@ bool espNowStop() if (settings.debugEspNow) systemPrintf("ESP-NOW offline\r\n"); - // 4. Remove all peers by calling espnowRemovePeer + // 4. Remove all peers by calling espNowRemovePeer if (settings.debugEspNow) systemPrintf("Calling esp_now_is_peer_exist\r\n"); - if (esp_now_is_peer_exist(espnowBroadcastAddr)) + if (esp_now_is_peer_exist(espNowBroadcastAddr)) { if (settings.debugEspNow) - systemPrintf("Calling esp_now_del_peer\r\n"); - status = esp_now_del_peer(espnowBroadcastAddr); + systemPrintf("Calling espNowRemovePeer\r\n"); + status = espNowRemovePeer(espNowBroadcastAddr); if (status != ESP_OK) { systemPrintf("ERROR: Failed to delete broadcast peer, status: %d\r\n", status); @@ -403,8 +605,8 @@ bool espNowStop() // Remove the unicast peer if (settings.debugEspNow) - systemPrintf("Calling esp_now_del_peer\r\n"); - status = esp_now_del_peer(peerInfo.peer_addr); + systemPrintf("Calling espNowRemovePeer\r\n"); + status = espNowRemovePeer(peerInfo.peer_addr); if (status != ESP_OK) { systemPrintf("ERROR: Failed to delete peer %02x:%02x:%02x:%02x:%02x:%02x, status: %d\r\n", @@ -464,25 +666,11 @@ bool espNowStop() return stopped; } -/* - Use ESP NOW protocol to transmit RTCM between RTK Products via 2.4GHz - - How pairing works: - 1. Device enters pairing mode - 2. Device adds the broadcast MAC (all 0xFFs) as peer - 3. Device waits for incoming pairing packet from remote - 4. If valid pairing packet received, add peer, immediately transmit a pairing packet to that peer and exit. - - ESP NOW is bare metal, there is no guaranteed packet delivery. For RTCM byte transmissions using ESP NOW: - We don't care about dropped packets or packets out of order. The ZED will check the integrity of the RTCM packet. - We don't care if the ESP NOW packet is corrupt or not. RTCM has its own CRC. RTK needs valid RTCM once every - few seconds so a single dropped frame is not critical. -*/ - +//********************************************************************* // Called from main loop // Control incoming/outgoing RTCM data from internal ESP NOW radio // Use the ESP32 to directly transmit/receive RTCM over 2.4GHz (no WiFi needed) -void updateEspnow() +void espNowUpdate() { if (settings.enableEspNow == true) { @@ -490,619 +678,29 @@ void updateEspnow() { // If it's been longer than a few ms since we last added a byte to the buffer // then we've reached the end of the RTCM stream. Send partial buffer. - if (espnowOutgoingSpot > 0 && (millis() - espnowLastAdd) > 50) + if (espNowOutgoingSpot > 0 && (millis() - espNowLastAdd) > 50) { if (espNowState == ESPNOW_PAIRED) - esp_now_send(0, (uint8_t *)&espnowOutgoing, espnowOutgoingSpot); // Send partial packet to all peers + esp_now_send(0, (uint8_t *)&espNowOutgoing, espNowOutgoingSpot); // Send partial packet to all peers else // if (espNowState == ESPNOW_BROADCASTING) - { - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - esp_now_send(broadcastMac, (uint8_t *)&espnowOutgoing, - espnowOutgoingSpot); // Send packet via broadcast - } + esp_now_send(espNowBroadcastAddr, (uint8_t *)&espNowOutgoing, + espNowOutgoingSpot); // Send packet via broadcast if (!inMainMenu) { if (settings.debugEspNow == true) - systemPrintf("ESPNOW transmitted %d RTCM bytes\r\n", espnowBytesSent + espnowOutgoingSpot); + systemPrintf("ESPNOW transmitted %d RTCM bytes\r\n", espNowBytesSent + espNowOutgoingSpot); } - espnowBytesSent = 0; - espnowOutgoingSpot = 0; // Reset + espNowBytesSent = 0; + espNowOutgoingSpot = 0; // Reset } // If we don't receive an ESP NOW packet after some time, set RSSI to very negative // This removes the ESPNOW icon from the display when the link goes down - if (millis() - lastEspnowRssiUpdate > 5000 && espnowRSSI > -255) - espnowRSSI = -255; + if (millis() - espNowLastRssiUpdate > 5000 && espNowRSSI > -255) + espNowRSSI = -255; } } } -// Callback when data is sent -void espnowOnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) -{ - // systemPrint("Last Packet Send Status: "); - // if (status == ESPNOW_SEND_SUCCESS) - // systemPrintln("Delivery Success"); - // else - // systemPrintln("Delivery Fail"); -} - -// Callback when data is received -void espnowOnDataReceived(const esp_now_recv_info *mac, const uint8_t *incomingData, int len) -{ - if (espNowState == ESPNOW_PAIRING) - { - if (len == sizeof(ESP_NOW_PAIR_MESSAGE)) // First error check - { - ESP_NOW_PAIR_MESSAGE pairMessage; - memcpy(&pairMessage, incomingData, sizeof(pairMessage)); - - // Check CRC - uint8_t tempCRC = 0; - for (int x = 0; x < 6; x++) - tempCRC += pairMessage.macAddress[x]; - - if (tempCRC == pairMessage.crc) // 2nd error check - { - memcpy(&receivedMAC, pairMessage.macAddress, 6); - espnowSetState(ESPNOW_MAC_RECEIVED); - } - // else Pair CRC failed - } - } - else - { - espnowRSSI = packetRSSI; // Record this packet's RSSI as an ESP NOW packet - - // We've just received ESP-Now data. We assume this is RTCM and push it directly to the GNSS. - // Determine if ESPNOW is the correction source - if (correctionLastSeen(CORR_ESPNOW)) - { - // Pass RTCM bytes (presumably) from ESP NOW out ESP32-UART to GNSS - gnss->pushRawData((uint8_t *)incomingData, len); - - if ((settings.debugEspNow == true || settings.debugCorrections == true) && !inMainMenu) - systemPrintf("ESPNOW received %d RTCM bytes, pushed to GNSS, RSSI: %d\r\n", len, espnowRSSI); - } - else - { - if ((settings.debugEspNow == true || settings.debugCorrections == true) && !inMainMenu) - systemPrintf("ESPNOW received %d RTCM bytes, NOT pushed due to priority, RSSI: %d\r\n", len, - espnowRSSI); - } - - espnowIncomingRTCM = true; // Display a download icon - lastEspnowRssiUpdate = millis(); - } -} - -// Callback for all RX Packets -// Get RSSI of all incoming management packets: https://esp32.com/viewtopic.php?t=13889 -#ifdef COMPILE_ESPNOW -void promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type) -{ - // All espnow traffic uses action frames which are a subtype of the mgmnt frames so filter out everything else. - if (type != WIFI_PKT_MGMT) - return; - - const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buf; - packetRSSI = ppkt->rx_ctrl.rssi; -} -#endif // COMPILE_ESPNOW - -// If WiFi is already enabled, simply add the LR protocol -// If the radio is off entirely, start the radio, turn on only the LR protocol -void espnowStart() -{ - if (settings.enableEspNow == false) - { - espnowStop(); - return; - } - - // Before we can issue esp_wifi_() commands WiFi must be started - if (WiFi.getMode() != WIFI_STA) - WiFi.mode(WIFI_STA); - - // Verify that the necessary protocols are set - uint8_t protocols = 0; - esp_err_t response = esp_wifi_get_protocol(WIFI_IF_STA, &protocols); - if (response != ESP_OK) - systemPrintf("espnowStart: Failed to get protocols: %s\r\n", esp_err_to_name(response)); - - if ((WIFI_IS_RUNNING() == false) && espNowState == ESPNOW_OFF) - { - // Radio is off, turn it on - if (protocols != (WIFI_PROTOCOL_LR)) - { - response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR); // Stops WiFi Station. - if (response != ESP_OK) - systemPrintf("espnowStart: Error setting ESP-Now lone protocol: %s\r\n", esp_err_to_name(response)); - else - { - if (settings.debugEspNow == true) - systemPrintln("WiFi off, ESP-Now added to protocols"); - } - } - else - { - if (settings.debugEspNow == true) - systemPrintln("LR protocol already in place"); - } - } - // If WiFi is on but ESP NOW is off, then enable LR protocol - else if (WIFI_IS_RUNNING() && espNowState == ESPNOW_OFF) - { - // Enable WiFi + ESP-Now - // Enable long range, PHY rate of ESP32 will be 512Kbps or 256Kbps - if (protocols != (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR)) - { - response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | - WIFI_PROTOCOL_LR); - if (response != ESP_OK) - systemPrintf("espnowStart: Error setting ESP-Now + WiFi protocols: %s\r\n", esp_err_to_name(response)); - else - { - if (settings.debugEspNow == true) - systemPrintln("WiFi on, ESP-Now added to protocols"); - } - } - else - { - if (settings.debugEspNow == true) - systemPrintln("WiFi was already on, and LR protocol already in place"); - } - } - - // If ESP-Now is already active, do nothing - else - { - if (settings.debugEspNow == true) - systemPrintln("ESP-Now already on."); - } - - // WiFi.setSleep(false); //We must disable sleep so that ESP-NOW can readily receive packets - // See: https://github.com/sparkfun/SparkFun_RTK_Everywhere_Firmware/issues/241 - - // Init ESP-NOW - if (esp_now_init() != ESP_OK) - { - systemPrintln("espnowStart: Error starting ESP-Now"); - return; - } - - // Use promiscuous callback to capture RSSI of packet - response = esp_wifi_set_promiscuous(true); - if (response != ESP_OK) - systemPrintf("espnowStart: Error setting promiscuous mode: %s\r\n", esp_err_to_name(response)); - - esp_wifi_set_promiscuous_rx_cb(&promiscuous_rx_cb); - - // Assign channel if not connected to an AP - if (WIFI_IS_CONNECTED() == false) - espnowSetChannel(settings.wifiChannel); - - // Register callbacks - // esp_now_register_send_cb(espnowOnDataSent); - esp_now_register_recv_cb(espnowOnDataReceived); - - if (settings.espnowPeerCount == 0) - { - // Enter broadcast mode - - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - - if (esp_now_is_peer_exist(broadcastMac) == true) - { - if (settings.debugEspNow == true) - systemPrintln("Broadcast peer already exists"); - } - else - { - esp_err_t result = espnowAddPeer(broadcastMac, false); // Encryption not support for broadcast MAC - if (result != ESP_OK) - { - if (settings.debugEspNow == true) - systemPrintln("Failed to add broadcast peer"); - } - else - { - if (settings.debugEspNow == true) - systemPrintln("Broadcast peer added"); - } - } - - espnowSetState(ESPNOW_BROADCASTING); - } - else - { - // If we have peers, move to paired state - espnowSetState(ESPNOW_PAIRED); - - if (settings.debugEspNow == true) - systemPrintf("Adding %d espnow peers\r\n", settings.espnowPeerCount); - - for (int x = 0; x < settings.espnowPeerCount; x++) - { - if (esp_now_is_peer_exist(settings.espnowPeers[x]) == true) - { - if (settings.debugEspNow == true) - systemPrintf("Peer #%d already exists\r\n", x); - } - else - { - esp_err_t result = espnowAddPeer(settings.espnowPeers[x]); - if (result != ESP_OK) - { - if (settings.debugEspNow == true) - systemPrintf("Failed to add peer #%d\r\n", x); - } - } - } - } - - systemPrintln("ESP-Now Started"); -} - -// If WiFi is already enabled, simply remove the LR protocol -// If WiFi is off, stop the radio entirely -void espnowStop() -{ - if (espNowState == ESPNOW_OFF) - return; - - // Turn off promiscuous WiFi mode - esp_err_t response = esp_wifi_set_promiscuous(false); - if (response != ESP_OK) - systemPrintf("espnowStop: Failed to set promiscuous mode: %s\r\n", esp_err_to_name(response)); - - esp_wifi_set_promiscuous_rx_cb(nullptr); - - // Deregister callbacks - // esp_now_unregister_send_cb(); - response = esp_now_unregister_recv_cb(); - if (response != ESP_OK) - systemPrintf("espnowStop: Failed to unregister receive callback: %s\r\n", esp_err_to_name(response)); - - // Forget all ESP-Now Peers - for (int x = 0; x < settings.espnowPeerCount; x++) - espnowRemovePeer(settings.espnowPeers[x]); - - if (WiFi.getMode() != WIFI_STA) - WiFi.mode(WIFI_STA); - - // Verify that the necessary protocols are set - uint8_t protocols = 0; - response = esp_wifi_get_protocol(WIFI_IF_STA, &protocols); - if (response != ESP_OK) - systemPrintf("espnowStop: Failed to get protocols: %s\r\n", esp_err_to_name(response)); - - // Leave WiFi with default settings (no WIFI_PROTOCOL_LR for ESP NOW) - if (protocols != (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)) - { - response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N); - if (response != ESP_OK) - systemPrintf("espnowStop: Error setting WiFi protocols: %s\r\n", esp_err_to_name(response)); - else - { - if (settings.debugEspNow == true) - systemPrintln("espnowStop: WiFi on, ESP-Now removed from protocols"); - } - } - else - { - if (settings.debugEspNow == true) - systemPrintln("espnowStop: ESP-Now already removed from protocols"); - } - - // Deinit ESP-NOW - if (esp_now_deinit() != ESP_OK) - { - systemPrintln("Error deinitializing ESP-NOW"); - return; - } - - espnowSetState(ESPNOW_OFF); - - if (WIFI_IS_RUNNING() == false) - { - // ESP-NOW was the only thing using the radio so turn WiFi radio off entirely - WiFi.mode(WIFI_OFF); - - if (settings.debugEspNow == true) - systemPrintln("WiFi Radio off entirely"); - } - // If WiFi is on, then restart WiFi - else if (WIFI_IS_RUNNING()) - { - if (settings.debugEspNow == true) - systemPrintln("ESP-Now starting WiFi"); - wifiStart(); // Force WiFi to restart - } -} - -// Start ESP-Now if needed, put ESP-Now into broadcast state -void espnowBeginPairing() -{ - espnowStart(); - - // To begin pairing, we must add the broadcast MAC to the peer list - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - espnowAddPeer(broadcastMac, false); // Encryption is not supported for multicast addresses - - espnowSetState(ESPNOW_PAIRING); -} - -// Regularly call during pairing to see if we've received a Pairing message -bool espnowIsPaired() -{ - if (espNowState == ESPNOW_MAC_RECEIVED) - { - // Remove broadcast peer - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - espnowRemovePeer(broadcastMac); - - if (esp_now_is_peer_exist(receivedMAC) == true) - { - if (settings.debugEspNow == true) - systemPrintln("Peer already exists"); - } - else - { - // Add new peer to system - espnowAddPeer(receivedMAC); - - // Record this MAC to peer list - memcpy(settings.espnowPeers[settings.espnowPeerCount], receivedMAC, 6); - settings.espnowPeerCount++; - settings.espnowPeerCount %= ESPNOW_MAX_PEERS; - } - - // Send message directly to the received MAC (not unicast), then exit - espnowSendPairMessage(receivedMAC); - - // Enable radio. User may have arrived here from the setup menu rather than serial menu. - settings.enableEspNow = true; - - recordSystemSettings(); // Record enableEspNow and espnowPeerCount to NVM - - espnowSetState(ESPNOW_PAIRED); - return (true); - } - return (false); -} - -// Create special pair packet to a given MAC -esp_err_t espnowSendPairMessage(uint8_t *sendToMac) -{ - // Assemble message to send - ESP_NOW_PAIR_MESSAGE pairMessage; - - // Get unit MAC address - memcpy(pairMessage.macAddress, wifiMACAddress, 6); - pairMessage.encrypt = false; - pairMessage.channel = 0; - - pairMessage.crc = 0; // Calculate CRC - for (int x = 0; x < 6; x++) - pairMessage.crc += wifiMACAddress[x]; - - return (esp_now_send(sendToMac, (uint8_t *)&pairMessage, sizeof(pairMessage))); // Send packet to given MAC -} - -// Add a given MAC address to the peer list -esp_err_t espnowAddPeer(uint8_t *peerMac) -{ - return (espnowAddPeer(peerMac, true)); // Encrypt by default -} - -esp_err_t espnowAddPeer(uint8_t *peerMac, bool encrypt) -{ - esp_now_peer_info_t peerInfo; - - memcpy(peerInfo.peer_addr, peerMac, 6); - peerInfo.channel = 0; - peerInfo.ifidx = WIFI_IF_STA; - // memcpy(peerInfo.lmk, "RTKProductsLMK56", 16); - // peerInfo.encrypt = encrypt; - peerInfo.encrypt = false; - - esp_err_t result = esp_now_add_peer(&peerInfo); - if (result != ESP_OK) - { - if (settings.debugEspNow == true) - systemPrintf("Failed to add peer: 0x%02X%02X%02X%02X%02X%02X\r\n", peerMac[0], peerMac[1], peerMac[2], - peerMac[3], peerMac[4], peerMac[5]); - } - return (result); -} - -// Remove a given MAC address from the peer list -esp_err_t espnowRemovePeer(uint8_t *peerMac) -{ - esp_err_t response = esp_now_del_peer(peerMac); - if (response != ESP_OK) - { - if (settings.debugEspNow == true) - systemPrintf("Failed to remove peer: %s\r\n", esp_err_to_name(response)); - } - - return (response); -} - -// Update the state of the ESP Now state machine -void espnowSetState(ESPNOWState newState) -{ - if (espNowState == newState && settings.debugEspNow == true) - systemPrint("*"); - espNowState = newState; - - if (settings.debugEspNow == true) - { - systemPrint("espNowState: "); - switch (newState) - { - case ESPNOW_OFF: - systemPrintln("ESPNOW_OFF"); - break; - case ESPNOW_BROADCASTING: - systemPrintln("ESPNOW_BROADCASTING"); - break; - case ESPNOW_PAIRING: - systemPrintln("ESPNOW_PAIRING"); - break; - case ESPNOW_MAC_RECEIVED: - systemPrintln("ESPNOW_MAC_RECEIVED"); - break; - case ESPNOW_PAIRED: - systemPrintln("ESPNOW_PAIRED"); - break; - default: - systemPrintf("Unknown ESPNOW state: %d\r\n", newState); - break; - } - } -} - -void espnowProcessRTCM(byte incoming) -{ - // If we are paired, - // Or if the radio is broadcasting - // Then add bytes to the outgoing buffer - if (espNowState == ESPNOW_PAIRED || espNowState == ESPNOW_BROADCASTING) - { - // Move this byte into ESP NOW to send buffer - espnowOutgoing[espnowOutgoingSpot++] = incoming; - espnowLastAdd = millis(); - } - - // Send buffer when full - if (espnowOutgoingSpot == sizeof(espnowOutgoing)) - { - espnowOutgoingSpot = 0; // Wrap - - if (espNowState == ESPNOW_PAIRED) - esp_now_send(0, (uint8_t *)&espnowOutgoing, sizeof(espnowOutgoing)); // Send packet to all peers - else // if (espNowState == ESPNOW_BROADCASTING) - { - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - esp_now_send(broadcastMac, (uint8_t *)&espnowOutgoing, - sizeof(espnowOutgoing)); // Send packet via broadcast - } - - delay(10); // We need a small delay between sending multiple packets - - espnowBytesSent += sizeof(espnowOutgoing); - - espnowOutgoingRTCM = true; - } -} - -// A blocking function that is used to pair two devices -// either through the serial menu or AP config -void espnowStaticPairing() -{ - systemPrintln("Begin ESP NOW Pairing"); - - // Start ESP-Now if needed, put ESP-Now into broadcast state - espnowBeginPairing(); - - // Begin sending our MAC every 250ms until a remote device sends us there info - randomSeed(millis()); - - systemPrintln("Begin pairing. Place other unit in pairing mode. Press any key to exit."); - clearBuffer(); - - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - - bool exitPair = false; - while (exitPair == false) - { - if (systemAvailable()) - { - systemPrintln("User pressed button. Pairing canceled."); - break; - } - - int timeout = 1000 + random(0, 100); // Delay 1000 to 1100ms - for (int x = 0; x < timeout; x++) - { - delay(1); - - if (espnowIsPaired() == true) // Check if we've received a pairing message - { - systemPrintln("Pairing compete"); - exitPair = true; - break; - } - } - - espnowSendPairMessage(broadcastMac); // Send unit's MAC address over broadcast, no ack, no encryption - - systemPrintln("Scanning for other radio..."); - } -} - -// Returns the current channel being used by WiFi -uint8_t espnowGetChannel() -{ - if (espNowState == ESPNOW_OFF) - return 0; - - bool originalPromiscuousMode = false; - esp_err_t response = esp_wifi_get_promiscuous(&originalPromiscuousMode); - if (response != ESP_OK) - systemPrintf("getPromiscuous failed: %s\r\n", esp_err_to_name(response)); - - // We must be in promiscuous mode to get the channel number - if (originalPromiscuousMode == false) - esp_wifi_set_promiscuous(true); - - uint8_t primaryChannelNumber = 0; - wifi_second_chan_t secondaryChannelNumber; - - response = esp_wifi_get_channel(&primaryChannelNumber, &secondaryChannelNumber); - if (response != ESP_OK) - systemPrintf("getChannel failed: %s\r\n", esp_err_to_name(response)); - - // Return to the original mode - if (originalPromiscuousMode == false) - esp_wifi_set_promiscuous(false); - - return (primaryChannelNumber); -} - -// Returns the current channel being used by WiFi -bool espnowSetChannel(uint8_t channelNumber) -{ - if (WIFI_IS_CONNECTED() == true) - { - systemPrintln("ESP-NOW channel can't be modified while WiFi is connected."); - return (false); - } - - bool originalPromiscuousMode = false; - esp_err_t response = esp_wifi_get_promiscuous(&originalPromiscuousMode); - if (response != ESP_OK) - systemPrintf("getPromiscuous failed: %s\r\n", esp_err_to_name(response)); - - // We must be in promiscuous mode to set the channel number - if (originalPromiscuousMode == false) - esp_wifi_set_promiscuous(true); - - bool setSuccess = false; - response = esp_wifi_set_channel(channelNumber, WIFI_SECOND_CHAN_NONE); - if (response != ESP_OK) - systemPrintf("setChannel to %d failed: %s\r\n", channelNumber, esp_err_to_name(response)); - else - setSuccess = true; - - // Return to the original mode - if (originalPromiscuousMode == false) - esp_wifi_set_promiscuous(false); - - return (setSuccess); -} - #endif // COMPILE_ESPNOW diff --git a/Firmware/RTK_Everywhere/Ethernet.ino b/Firmware/RTK_Everywhere/Ethernet.ino index 5e17a081..387037fe 100644 --- a/Firmware/RTK_Everywhere/Ethernet.ino +++ b/Firmware/RTK_Everywhere/Ethernet.ino @@ -188,9 +188,6 @@ void ethernetEvent(arduino_event_id_t event, arduino_event_info_t info) case ARDUINO_EVENT_ETH_DISCONNECTED: if (settings.enablePrintEthernetDiag && (!inMainMenu)) systemPrintln("ETH Disconnected"); - - wifiResetTimeout(); // If we loose ethernet, allow WiFi to immediately try to start - break; case ARDUINO_EVENT_ETH_STOP: @@ -200,12 +197,20 @@ void ethernetEvent(arduino_event_id_t event, arduino_event_info_t info) } // Take the network offline if necessary - if (networkInterfaceHasInternet(NETWORK_ETHERNET) && (event != ARDUINO_EVENT_ETH_GOT_IP)) + if (event != ARDUINO_EVENT_ETH_GOT_IP) { - networkInterfaceEventInternetLost((NetIndex_t)NETWORK_ETHERNET); + networkInterfaceEventInternetLost((NetIndex_t)NETWORK_ETHERNET, __FILE__, __LINE__); } } +//---------------------------------------- +// Determine if the Ethernet is up +//---------------------------------------- +bool ethernetLinkUp() +{ + return ETH.linkUp(); +} + //---------------------------------------- // Convert the Ethernet event ID into an ASCII string //---------------------------------------- diff --git a/Firmware/RTK_Everywhere/GNSS.h b/Firmware/RTK_Everywhere/GNSS.h index 6d71b901..7aeb8552 100644 --- a/Firmware/RTK_Everywhere/GNSS.h +++ b/Firmware/RTK_Everywhere/GNSS.h @@ -105,6 +105,18 @@ class GNSS // Returns true if successfully configured and false upon failure virtual bool configureRover(); + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + virtual void createMessageList(String &returnText); + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + virtual void createMessageListBase(String &returnText); + virtual void debuggingDisable(); virtual void debuggingEnable(); diff --git a/Firmware/RTK_Everywhere/GNSS.ino b/Firmware/RTK_Everywhere/GNSS.ino index e5e9884c..ee0a41ec 100644 --- a/Firmware/RTK_Everywhere/GNSS.ino +++ b/Firmware/RTK_Everywhere/GNSS.ino @@ -81,4 +81,4 @@ void pushGPGGA(char *ggaData) } } #endif // COMPILE_NETWORK -} \ No newline at end of file +} diff --git a/Firmware/RTK_Everywhere/GNSS_LG290P.h b/Firmware/RTK_Everywhere/GNSS_LG290P.h index d785179c..f12dd71a 100644 --- a/Firmware/RTK_Everywhere/GNSS_LG290P.h +++ b/Firmware/RTK_Everywhere/GNSS_LG290P.h @@ -34,7 +34,7 @@ typedef struct // Rate = Output once every N position fix(es). const lg290pMsg lgMessagesNMEA[] = { {"RMC", 1, 0}, {"GGA", 1, 0}, {"GSV", 1, 0}, {"GSA", 1, 0}, {"VTG", 1, 0}, {"GLL", 1, 0}, - {"GBS", 0, 4}, {"GNS", 0, 4}, {"GST", 1, 4}, {"ZDA", 0, 4}, + {"GBS", 0, 4}, {"GNS", 0, 4}, {"GST", 1, 4}, {"ZDA", 0, 4}, }; const lg290pMsg lgMessagesRTCM[] = { @@ -160,6 +160,18 @@ class GNSS_LG290P : GNSS // Returns true if successfully configured and false upon failure bool configureRover(); + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageList(String &returnText); + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageListBase(String &returnText); + void debuggingDisable(); void debuggingEnable(); diff --git a/Firmware/RTK_Everywhere/GNSS_LG290P.ino b/Firmware/RTK_Everywhere/GNSS_LG290P.ino index de46d50c..4553040f 100644 --- a/Firmware/RTK_Everywhere/GNSS_LG290P.ino +++ b/Firmware/RTK_Everywhere/GNSS_LG290P.ino @@ -466,6 +466,26 @@ bool GNSS_LG290P::configureBase() return (response); } +//---------------------------------------- +// Responds with the messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_LG290P::createMessageList(String &returnText) +{ +} + +//---------------------------------------- +// Responds with the RTCM/Base messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_LG290P::createMessageListBase(String &returnText) +{ +} + //---------------------------------------- // Return the survey-in mode from PQTMCFGSVIN // 0 - Disabled, 1 - Survey-in mode, 2 - Fixed mode diff --git a/Firmware/RTK_Everywhere/GNSS_Mosaic.h b/Firmware/RTK_Everywhere/GNSS_Mosaic.h index e4ad9d95..d17e1277 100644 --- a/Firmware/RTK_Everywhere/GNSS_Mosaic.h +++ b/Firmware/RTK_Everywhere/GNSS_Mosaic.h @@ -329,7 +329,7 @@ const mosaicNMEAMsg mosaicMessagesNMEA[] = { /* Do we need RTCMv2? -typedef struct +typedef struct { const char msgName[10]; const uint16_t defaultZCountOrInterval; @@ -347,7 +347,7 @@ const mosaicRTCMv2Msg mosaicMessagesRTCMv2[] = { {"RTCM1", 2, true, false}, {"RTCM3", 2, true, false}, {"RTCM9", 2, true, false}, {"RTCM16", 2, true, false}, {"RTCM17", 2, true, false}, - {"RTCM18|19", 1, false, false}, {"RTCM20|21", 1, false, false}, + {"RTCM18|19", 1, false, false}, {"RTCM20|21", 1, false, false}, {"RTCM22", 2, true, false}, {"RTCM23|24", 2, true, false}, {"RTCM31", 2, true, false}, {"RTCM32", 2, true, false}, }; @@ -418,7 +418,7 @@ const mosaicRTCMv3MsgIntervalGroup mosaicRTCMv3MsgIntervalGroups[] = { #define MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS (sizeof(mosaicRTCMv3MsgIntervalGroups) / sizeof(mosaicRTCMv3MsgIntervalGroup)) -typedef struct +typedef struct { const char name[9]; const uint8_t intervalGroup; @@ -454,7 +454,7 @@ const mosaicRTCMv3Msg mosaicMessagesRTCMv3[] = { {"MSM4", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM4, true}, {"MSM5", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM5, false}, {"MSM6", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM6, false}, - {"MSM7", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM7, false}, + {"MSM7", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM7, false}, /* {"RTCM1071", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM1, false}, {"RTCM1072", MOSAIC_RTCM_V3_INTERVAL_GROUP_MSM2, false}, @@ -566,7 +566,7 @@ class GNSS_MOSAIC : GNSS // Record NrBytesReceived so we can tell if Radio Ext (COM2) is receiving correction data. // On the mosaic, we know that InputLink will arrive at 1Hz. But on the ZED, UBX-MON-COMMS // is tied to the navigation rate. To keep it simple, record the last time NrBytesReceived - // was seen to increase and use that for corrections timeout. This is updated by the SBF + // was seen to increase and use that for corrections timeout. This is updated by the SBF // InputLink message. isCorrRadioExtPortActive returns true if the bytes-received has // increased in the previous settings.correctionsSourcesLifetime_s uint32_t _radioExtBytesReceived_millis; @@ -574,7 +574,7 @@ class GNSS_MOSAIC : GNSS // See notes at GNSS_MOSAIC::setCorrRadioExtPort uint32_t previousNrBytesReceived = 0; bool firstTimeNrBytesReceived = true; - + // Setup the general configuration of the GNSS // Not Rover or Base specific (ie, baud rates) // Outputs: @@ -658,6 +658,18 @@ class GNSS_MOSAIC : GNSS // Returns true if successfully configured and false upon failure bool configureRover(); + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageList(String &returnText); + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageListBase(String &returnText); + void debuggingDisable(); void debuggingEnable(); diff --git a/Firmware/RTK_Everywhere/GNSS_Mosaic.ino b/Firmware/RTK_Everywhere/GNSS_Mosaic.ino index 07d7ab7c..f930547a 100644 --- a/Firmware/RTK_Everywhere/GNSS_Mosaic.ino +++ b/Firmware/RTK_Everywhere/GNSS_Mosaic.ino @@ -692,6 +692,56 @@ bool GNSS_MOSAIC::configureRover() return (response); } +//---------------------------------------- +// Responds with the messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_MOSAIC::createMessageList(String &returnText) +{ + for (int messageNumber = 0; messageNumber < MAX_MOSAIC_NMEA_MSG; messageNumber++) + { + returnText += "messageStreamNMEA_" + String(mosaicMessagesNMEA[messageNumber].msgTextName) + "," + + String(settings.mosaicMessageStreamNMEA[messageNumber]) + ","; + } + for (int stream = 0; stream < MOSAIC_NUM_NMEA_STREAMS; stream++) + { + returnText += + "streamIntervalNMEA_" + String(stream) + "," + String(settings.mosaicStreamIntervalsNMEA[stream]) + ","; + } + for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS; messageNumber++) + { + returnText += "messageIntervalRTCMRover_" + String(mosaicRTCMv3MsgIntervalGroups[messageNumber].name) + + "," + String(settings.mosaicMessageIntervalsRTCMv3Rover[messageNumber]) + ","; + } + for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_MSG; messageNumber++) + { + returnText += "messageEnabledRTCMRover_" + String(mosaicMessagesRTCMv3[messageNumber].name) + "," + + (settings.mosaicMessageEnabledRTCMv3Rover[messageNumber] ? "true" : "false") + ","; + } +} + +//---------------------------------------- +// Responds with the RTCM/Base messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_MOSAIC::createMessageListBase(String &returnText) +{ + for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS; messageNumber++) + { + returnText += "messageIntervalRTCMBase_" + String(mosaicRTCMv3MsgIntervalGroups[messageNumber].name) + "," + + String(settings.mosaicMessageIntervalsRTCMv3Base[messageNumber]) + ","; + } + for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_MSG; messageNumber++) + { + returnText += "messageEnabledRTCMBase_" + String(mosaicMessagesRTCMv3[messageNumber].name) + "," + + (settings.mosaicMessageEnabledRTCMv3Base[messageNumber] ? "true" : "false") + ","; + } +} + //---------------------------------------- void GNSS_MOSAIC::debuggingDisable() { @@ -2512,7 +2562,7 @@ void GNSS_MOSAIC::storeBlock4059(SEMP_PARSE_STATE *parse) return; uint64_t diskUsage = (diskUsageMSB * 4294967296) + diskUsageLSB; - + sdCardSize = diskSizeMB * 1048576; // Convert to bytes sdFreeSpace = sdCardSize - diskUsage; diff --git a/Firmware/RTK_Everywhere/GNSS_None.h b/Firmware/RTK_Everywhere/GNSS_None.h index 7c43ec17..0c37b0e9 100644 --- a/Firmware/RTK_Everywhere/GNSS_None.h +++ b/Firmware/RTK_Everywhere/GNSS_None.h @@ -101,6 +101,40 @@ class GNSS_None : public GNSS return false; } + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageList(String &returnText) + { + + } + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageListBase(String &returnText) + { + + } + + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void getMessageList(String &returnText) + { + } + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void getMessageListBase(String &returnText) + { + } + void debuggingDisable() { } diff --git a/Firmware/RTK_Everywhere/GNSS_UM980.h b/Firmware/RTK_Everywhere/GNSS_UM980.h index e826b984..b4881347 100644 --- a/Firmware/RTK_Everywhere/GNSS_UM980.h +++ b/Firmware/RTK_Everywhere/GNSS_UM980.h @@ -206,6 +206,18 @@ class GNSS_UM980 : GNSS // Returns true if successfully configured and false upon failure bool configureRover(); + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageList(String &returnText); + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageListBase(String &returnText); + void debuggingDisable(); void debuggingEnable(); diff --git a/Firmware/RTK_Everywhere/GNSS_UM980.ino b/Firmware/RTK_Everywhere/GNSS_UM980.ino index 07e03580..4e8a531a 100644 --- a/Firmware/RTK_Everywhere/GNSS_UM980.ino +++ b/Firmware/RTK_Everywhere/GNSS_UM980.ino @@ -399,6 +399,41 @@ bool GNSS_UM980::configureRover() return (response); } +//---------------------------------------- +// Responds with the messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_UM980::createMessageList(String &returnText) +{ + for (int messageNumber = 0; messageNumber < MAX_UM980_NMEA_MSG; messageNumber++) + { + returnText += "messageRateNMEA_" + String(umMessagesNMEA[messageNumber].msgTextName) + "," + + String(settings.um980MessageRatesNMEA[messageNumber]) + ","; + } + for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) + { + returnText += "messageRateRTCMRover_" + String(umMessagesRTCM[messageNumber].msgTextName) + "," + + String(settings.um980MessageRatesRTCMRover[messageNumber]) + ","; + } +} + +//---------------------------------------- +// Responds with the RTCM/Base messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_UM980::createMessageListBase(String &returnText) +{ + for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) + { + returnText += "messageRateRTCMBase_" + String(umMessagesRTCM[messageNumber].msgTextName) + "," + + String(settings.um980MessageRatesRTCMBase[messageNumber]) + ","; + } +} + //---------------------------------------- void GNSS_UM980::debuggingDisable() { diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.h b/Firmware/RTK_Everywhere/GNSS_ZED.h index 2529d748..79a6157a 100644 --- a/Firmware/RTK_Everywhere/GNSS_ZED.h +++ b/Firmware/RTK_Everywhere/GNSS_ZED.h @@ -432,6 +432,18 @@ class GNSS_ZED : GNSS // Returns true if successfully configured and false upon failure bool configureRover(); + // Responds with the messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageList(String &returnText); + + // Responds with the RTCM/Base messages supported on this platform + // Inputs: + // returnText: String to receive message names + // Returns message names in the returnText string + void createMessageListBase(String &returnText); + void debuggingDisable(); void debuggingEnable(); diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.ino b/Firmware/RTK_Everywhere/GNSS_ZED.ino index 8cedaf99..e086d1ae 100644 --- a/Firmware/RTK_Everywhere/GNSS_ZED.ino +++ b/Firmware/RTK_Everywhere/GNSS_ZED.ino @@ -505,7 +505,7 @@ bool GNSS_ZED::configureNtpMode() if (!success) systemPrintln("NTP config fail"); - + // The configuration should be saved to RAM+BBR+FLASH. No need to saveConfiguration here. return (success); @@ -850,7 +850,7 @@ bool GNSS_ZED::configureRover() if (!success) systemPrintln("Rover config fail"); - + settings.gnssConfiguredRover = success; // The configuration should be saved to RAM+BBR+FLASH. No need to saveConfiguration here. @@ -858,6 +858,41 @@ bool GNSS_ZED::configureRover() return (success); } +//---------------------------------------- +// Responds with the messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_ZED::createMessageList(String &returnText) +{ + for (int messageNumber = 0; messageNumber < MAX_UBX_MSG; messageNumber++) + { + if (messageSupported(messageNumber) == true) + returnText += "ubxMessageRate_" + String(ubxMessages[messageNumber].msgTextName) + "," + + String(settings.ubxMessageRates[messageNumber]) + ","; + } +} + +//---------------------------------------- +// Responds with the RTCM/Base messages supported on this platform +// Inputs: +// returnText: String to receive message names +// Returns message names in the returnText string +//---------------------------------------- +void GNSS_ZED::createMessageListBase(String &returnText) +{ + GNSS_ZED *zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); + + for (int messageNumber = 0; messageNumber < MAX_UBX_MSG_RTCM; messageNumber++) + { + if (messageSupported(firstRTCMRecord + messageNumber) == true) + returnText += "ubxMessageRateBase_" + String(ubxMessages[messageNumber + firstRTCMRecord].msgTextName) + + "," + String(settings.ubxMessageRatesBase[messageNumber]) + ","; // UBX_RTCM_1074Base,4, + } +} + //---------------------------------------- void GNSS_ZED::debuggingDisable() { diff --git a/Firmware/RTK_Everywhere/HTTP_Client.ino b/Firmware/RTK_Everywhere/HTTP_Client.ino index 7e14eb96..9820c457 100644 --- a/Firmware/RTK_Everywhere/HTTP_Client.ino +++ b/Firmware/RTK_Everywhere/HTTP_Client.ino @@ -28,8 +28,8 @@ static const int MAX_HTTP_CLIENT_CONNECTION_ATTEMPTS = 3; enum HTTPClientState { HTTP_CLIENT_OFF = 0, - HTTP_CLIENT_ON, // WIFI_STATE_START state HTTP_CLIENT_NETWORK_STARTED, // Connecting to WiFi access point or Ethernet + HTTP_CLIENT_CONNECTION_DELAY, // Delay before connecting to HTTP server HTTP_CLIENT_CONNECTING_2_SERVER, // Connecting to the HTTP server HTTP_CLIENT_CONNECTED, // Connected to the HTTP services HTTP_CLIENT_COMPLETE, // Complete. Can not or do not need to continue @@ -38,8 +38,12 @@ enum HTTPClientState }; const char *const httpClientStateName[] = { - "HTTP_CLIENT_OFF", "HTTP_CLIENT_ON", "HTTP_CLIENT_NETWORK_STARTED", "HTTP_CLIENT_CONNECTING_2_SERVER", - "HTTP_CLIENT_CONNECTED", "HTTP_CLIENT_COMPLETE", + "HTTP_CLIENT_OFF", + "HTTP_CLIENT_NETWORK_STARTED", + "HTTP_CLIENT_CONNECTION_DELAY", + "HTTP_CLIENT_CONNECTING_2_SERVER", + "HTTP_CLIENT_CONNECTED", + "HTTP_CLIENT_COMPLETE", }; const int httpClientStateNameEntries = sizeof(httpClientStateName) / sizeof(httpClientStateName[0]); @@ -55,7 +59,7 @@ char *tempHolderPtr = nullptr; // Throttle the time between connection attempts static int httpClientConnectionAttempts; // Count the number of connection attempts between restarts -static uint32_t httpClientConnectionAttemptTimeout = 5 * 1000L; // Wait 5s +static uint32_t httpClientConnectionAttemptTimeout; static int httpClientConnectionAttemptsTotal; // Count the number of connection attempts absolutely static volatile uint32_t httpClientLastDataReceived; // Last time data was received via HTTP @@ -75,23 +79,29 @@ static uint32_t httpClientTimer; // HTTP Client Routines //---------------------------------------- +//---------------------------------------- // Determine if another connection is possible or if the limit has been reached +//---------------------------------------- bool httpClientConnectLimitReached() { bool limitReached; + int minutes; int seconds; // Retry the connection a few times limitReached = (httpClientConnectionAttempts >= MAX_HTTP_CLIENT_CONNECTION_ATTEMPTS); - bool enableHttpClient = true; - if (!settings.enablePointPerfectCorrections) - enableHttpClient = false; - // Restart the HTTP client - httpClientStop(limitReached || (!enableHttpClient)); - - httpClientConnectionAttempts++; + httpClientStop(limitReached || (!httpClientEnabled(nullptr))); + + // Limit to max connection delay + if (httpClientConnectionAttempts) + httpClientConnectionAttemptTimeout = (5 * MILLISECONDS_IN_A_SECOND) + << (httpClientConnectionAttempts - 1); + if (httpClientConnectionAttemptTimeout > RTK_MAX_CONNECTION_MSEC) + httpClientConnectionAttemptTimeout = httpClientConnectionAttemptTimeout; + else + httpClientConnectionAttempts++; httpClientConnectionAttemptsTotal++; if (settings.debugHttpClientState) httpClientPrintStatus(); @@ -101,8 +111,10 @@ bool httpClientConnectLimitReached() // Display the delay before starting the HTTP client if (settings.debugHttpClientState && httpClientConnectionAttemptTimeout) { - seconds = httpClientConnectionAttemptTimeout / 1000; - systemPrintf("HTTP Client trying again in %d seconds.\r\n", seconds); + seconds = httpClientConnectionAttemptTimeout / MILLISECONDS_IN_A_SECOND; + minutes = seconds / SECONDS_IN_A_MINUTE; + seconds -= minutes * SECONDS_IN_A_MINUTE; + systemPrintf("HTTP Client trying again in %d:%02d seconds.\r\n", minutes, seconds); } } else @@ -112,7 +124,36 @@ bool httpClientConnectLimitReached() return limitReached; } +//---------------------------------------- +// Determine if the HTTP client may be enabled +//---------------------------------------- +bool httpClientEnabled(const char ** line) +{ + bool enableHttpClient; + + do + { + enableHttpClient = false; + + // HTTP requires use of point perfect corrections + if (settings.enablePointPerfectCorrections == false) + { + if (line) + *line = ", PointPerfect corrections disabled!"; + break; + } + + // All conditions support running the HTTP client + enableHttpClient = httpClientModeNeeded; + if (line && !enableHttpClient) + *line = ", HTTP Client disabled!"; + } while (0); + return enableHttpClient; +} + +//---------------------------------------- // Print the HTTP client state summary +//---------------------------------------- void httpClientPrintStateSummary() { switch (httpClientState) @@ -124,8 +165,8 @@ void httpClientPrintStateSummary() systemPrint("Off"); break; - case HTTP_CLIENT_ON: case HTTP_CLIENT_NETWORK_STARTED: + case HTTP_CLIENT_CONNECTION_DELAY: systemPrint("Disconnected"); break; @@ -143,14 +184,19 @@ void httpClientPrintStateSummary() } } +//---------------------------------------- // Print the HTTP Client status +//---------------------------------------- void httpClientPrintStatus() { systemPrint("HTTP Client "); httpClientPrintStateSummary(); + systemPrintln(); } +//---------------------------------------- // Restart the HTTP client +//---------------------------------------- void httpClientRestart() { // Save the previous uptime value @@ -159,10 +205,12 @@ void httpClientRestart() httpClientConnectLimitReached(); } +//---------------------------------------- // Update the state of the HTTP client state machine +//---------------------------------------- void httpClientSetState(uint8_t newState) { - if (settings.debugHttpClientState || PERIODIC_DISPLAY(PD_HTTP_CLIENT_STATE)) + if (settings.debugHttpClientState) { if (httpClientState == newState) systemPrint("*"); @@ -170,9 +218,8 @@ void httpClientSetState(uint8_t newState) systemPrintf("%s --> ", httpClientStateName[httpClientState]); } httpClientState = newState; - if (settings.debugHttpClientState || PERIODIC_DISPLAY(PD_HTTP_CLIENT_STATE)) + if (settings.debugHttpClientState) { - PERIODIC_CLEAR(PD_HTTP_CLIENT_STATE); if (newState >= HTTP_CLIENT_STATE_MAX) { systemPrintf("Unknown HTTP Client state: %d\r\n", newState); @@ -183,13 +230,17 @@ void httpClientSetState(uint8_t newState) } } +//---------------------------------------- // Shutdown the HTTP client +//---------------------------------------- void httpClientShutdown() { httpClientStop(true); } +//---------------------------------------- // Start the HTTP client +//---------------------------------------- void httpClientStart() { // Display the heap state @@ -200,7 +251,9 @@ void httpClientStart() httpClientStop(false); } +//---------------------------------------- // Shutdown or restart the HTTP client +//---------------------------------------- void httpClientStop(bool shutdown) { // Free the httpClient resources @@ -225,83 +278,83 @@ void httpClientStop(bool shutdown) } // Increase timeouts if we started the network - if (httpClientState > HTTP_CLIENT_ON) + if (httpClientState > HTTP_CLIENT_NETWORK_STARTED) // Mark the Client stop so that we don't immediately attempt re-connect to Caster httpClientTimer = millis(); // Determine the next HTTP client state online.httpClient = false; + networkConsumerOffline(NETCONSUMER_HTTP_CLIENT); if (shutdown) { - httpClientSetState(HTTP_CLIENT_OFF); - // settings.enablePointPerfectCorrections = false; - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Why? This means PointPerfect Corrections - // cannot be restarted without opening the menu or web configuration page... + networkConsumerRemove(NETCONSUMER_HTTP_CLIENT, NETWORK_ANY, __FILE__, __LINE__); + httpClientModeNeeded = false; httpClientConnectionAttempts = 0; httpClientConnectionAttemptTimeout = 0; + httpClientSetState(HTTP_CLIENT_OFF); + systemPrintln("HTTP Client stopped"); } else - httpClientSetState(HTTP_CLIENT_ON); + httpClientSetState(HTTP_CLIENT_NETWORK_STARTED); } +//---------------------------------------- +// Update the state of the HTTP client +//---------------------------------------- void httpClientUpdate() { + bool connected; + bool enabled; + // Shutdown the HTTP client when the mode or setting changes DMW_st(httpClientSetState, httpClientState); + connected = networkConsumerIsConnected(NETCONSUMER_HTTP_CLIENT); + enabled = httpClientEnabled(nullptr); + if ((enabled == false) && (httpClientState > HTTP_CLIENT_OFF)) + httpClientShutdown(); - if (!httpClientModeNeeded) - { - if (httpClientState > HTTP_CLIENT_OFF) - { - systemPrintln("HTTP Client stopping"); - httpClientStop(true); // Was false - #StopVsRestart - httpClientConnectionAttempts = 0; - httpClientConnectionAttemptTimeout = 0; - httpClientSetState(HTTP_CLIENT_OFF); - } - } + // Determine if the network has failed + else if ((httpClientState > HTTP_CLIENT_NETWORK_STARTED) && !connected) + httpClientRestart(); // Enable the network and the HTTP client if requested switch (httpClientState) { default: case HTTP_CLIENT_OFF: { - if (httpClientModeNeeded) + if (enabled) + { httpClientStart(); + networkConsumerAdd(NETCONSUMER_HTTP_CLIENT, NETWORK_ANY, __FILE__, __LINE__); + } break; } - // Start the network - case HTTP_CLIENT_ON: { - if ((millis() - httpClientTimer) > httpClientConnectionAttemptTimeout) + // Wait for a network media connection + case HTTP_CLIENT_NETWORK_STARTED: { + // Wait until the network is connected to the media + if (connected) { - httpClientSetState(HTTP_CLIENT_NETWORK_STARTED); + // Reset the timeout when the network changes + if (networkChanged(NETCONSUMER_HTTP_CLIENT)) + httpClientConnectionAttemptTimeout = 0; + networkUserAdd(NETCONSUMER_HTTP_CLIENT, __FILE__, __LINE__); + httpClientSetState(HTTP_CLIENT_CONNECTION_DELAY); } break; } - // Wait for a network media connection - case HTTP_CLIENT_NETWORK_STARTED: { - // Determine if the HTTP client was turned off - if (!httpClientModeNeeded) - httpClientStop(true); - - // Wait until the network is connected - else if (networkHasInternet()) + // Delay before connecting to HTTP server + case HTTP_CLIENT_CONNECTION_DELAY: { + if ((millis() - httpClientTimer) > httpClientConnectionAttemptTimeout) + { httpClientSetState(HTTP_CLIENT_CONNECTING_2_SERVER); + } break; } // Connect to the HTTP server case HTTP_CLIENT_CONNECTING_2_SERVER: { - // Determine if the network has failed - if (networkHasInternet() == false) - { - // Failed to connect to the network, attempt to restart the network - httpClientStop(true); // Was httpClientRestart(); - #StopVsRestart - break; - } - // Allocate the httpSecureClient structure httpSecureClient = new NetworkClientSecure(); if (!httpSecureClient) @@ -351,14 +404,6 @@ void httpClientUpdate() } case HTTP_CLIENT_CONNECTED: { - // Determine if the network has failed - if (networkHasInternet() == false) - { - // Failed to connect to the network, attempt to restart the network - httpClientStop(true); // Was httpClientRestart(); - #StopVsRestart - break; - } - String ztpRequest; createZtpRequest(ztpRequest); @@ -455,10 +500,7 @@ void httpClientUpdate() } else { - if (online.psram == true) - tempHolderPtr = (char *)ps_malloc(MQTT_CERT_SIZE); - else - tempHolderPtr = (char *)malloc(MQTT_CERT_SIZE); + tempHolderPtr = (char *)rtkMalloc(MQTT_CERT_SIZE, "Certificate buffer (tempHolderPtr)"); if (!tempHolderPtr) { @@ -472,7 +514,7 @@ void httpClientUpdate() strncpy(tempHolderPtr, (const char *)((*jsonZtp)["privateKey"]), MQTT_CERT_SIZE - 1); recordFile("privateKey", tempHolderPtr, strlen(tempHolderPtr)); - free(tempHolderPtr); // Clean up. Done with tempHolderPtr + rtkFree(tempHolderPtr, "Certificate buffer (tempHolderPtr)"); // Clean up. Done with tempHolderPtr // Validate the keys if (!checkCertificates()) @@ -542,8 +584,6 @@ void httpClientUpdate() if (settings.debugCorrections || settings.debugHttpClientData) pointperfectPrintKeyInformation("HTTP Client"); - // displayKeysUpdated(); - ztpResponse = ZTP_SUCCESS; httpClientSetState(HTTP_CLIENT_COMPLETE); } @@ -554,21 +594,24 @@ void httpClientUpdate() // The ZTP HTTP POST is complete. We either can not or do not want to continue. // Hang here until httpClientModeNeeded is set to false by updateProvisioning - case HTTP_CLIENT_COMPLETE: { - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to the network, attempt to restart the network - httpClientStop(true); // Was httpClientRestart(); - #StopVsRestart + case HTTP_CLIENT_COMPLETE: break; } - } // Periodically display the HTTP client state if (PERIODIC_DISPLAY(PD_HTTP_CLIENT_STATE)) - httpClientSetState(httpClientState); + { + const char * line = ""; + httpClientEnabled(&line); + systemPrintf("HTTP Client state: %s%s\r\n", + httpClientStateName[httpClientState], line); + PERIODIC_CLEAR(PD_HTTP_CLIENT_STATE); + } } +//---------------------------------------- // Verify the HTTP client tables +//---------------------------------------- void httpClientValidateTables() { if (httpClientStateNameEntries != HTTP_CLIENT_STATE_MAX) diff --git a/Firmware/RTK_Everywhere/LARA.ino b/Firmware/RTK_Everywhere/LARA.ino index 3a6bc1a4..83e9a930 100644 --- a/Firmware/RTK_Everywhere/LARA.ino +++ b/Firmware/RTK_Everywhere/LARA.ino @@ -6,6 +6,7 @@ static uint32_t laraPowerLowMsec; // Measure the power off time #define LARA_ON_TIME (2 * 1000) // Milliseconds #define LARA_OFF_TIME (5 * 1000) // Milliseconds +#define LARA_PRIORITY_TIME 100 // Milliseconds #define LARA_SETTLE_TIME (2 * 1000) // Milliseconds #define LARA_SIM_DELAY (1 * 1000) // Milliseconds. 500 is too short @@ -29,11 +30,8 @@ void laraSetPins(NetIndex_t index, uintptr_t parameter, bool debug) if (!laraPowerPinRead(debug)) laraPowerLowMsec = currentMsec; - // Specify the timer expiration date - laraTimer = currentMsec + parameter; - - // Set the next state - networkSequenceNextEntry(index, debug); + // Specify the timer expiration date and set the next state + laraTimerStart(index, parameter, debug); } //---------------------------------------- @@ -51,9 +49,6 @@ void laraPowerHigh(NetIndex_t index, uintptr_t parameter, bool debug) digitalWrite(pin_Cellular_PWR_ON, LARA_PWR_HIGH_VALUE); currentMsec = millis(); - // Specify the timer expiration date - laraTimer = currentMsec + parameter; - // Display the pulse width if (debug) { @@ -64,8 +59,8 @@ void laraPowerHigh(NetIndex_t index, uintptr_t parameter, bool debug) systemPrintf("LARA power pulse width: %d.%03d Sec\r\n", seconds, milliseconds); } - // Set the next state - networkSequenceNextEntry(index, debug); + // Specify the timer expiration date and set the next state + laraTimerStart(index, parameter, debug); } //---------------------------------------- @@ -90,8 +85,16 @@ void laraPowerLow(NetIndex_t index, uintptr_t parameter, bool debug) laraPowerPinRead(debug); } + // Specify the timer expiration date and set the next state + laraTimerStart(index, parameter, debug); +} + +//---------------------------------------- +// Start the LARA timer and set the next state +void laraTimerStart(NetIndex_t index, uintptr_t parameter, bool debug) +{ // Specify the timer expiration date - laraTimer = currentMsec + parameter; + laraTimer = millis() + parameter; // Set the next state networkSequenceNextEntry(index, debug); @@ -138,6 +141,8 @@ NETWORK_POLL_SEQUENCE laraBootSequence[] = // (Remember that LARA_PWR is inverted by the RTK EVK level-shifter) NETWORK_POLL_SEQUENCE laraOnSequence[] = { // State Parameter Description + {laraTimerStart, LARA_PRIORITY_TIME, "Start the LARA timer"}, + {networkVerifyPriority, (uintptr_t)&laraTimer, "Verify the LARA priority"}, {laraPowerLow, LARA_ON_TIME, "Notify LARA of power state change"}, {networkDelay, (uintptr_t)&laraTimer, "Tell LARA to power on"}, {laraPowerHigh, LARA_SETTLE_TIME, "Finish power on sequence"}, diff --git a/Firmware/RTK_Everywhere/MP2762A_Charger.ino b/Firmware/RTK_Everywhere/MP2762A_Charger.ino index 304666d0..08f5be94 100644 --- a/Firmware/RTK_Everywhere/MP2762A_Charger.ino +++ b/Firmware/RTK_Everywhere/MP2762A_Charger.ino @@ -224,4 +224,4 @@ bool mp2762writeRegister(uint8_t address, uint8_t value) return (false); // Sensor did not ACK } return (true); -} \ No newline at end of file +} diff --git a/Firmware/RTK_Everywhere/MQTT_Client.ino b/Firmware/RTK_Everywhere/MQTT_Client.ino index bc3acfd8..980e827e 100644 --- a/Firmware/RTK_Everywhere/MQTT_Client.ino +++ b/Firmware/RTK_Everywhere/MQTT_Client.ino @@ -2,10 +2,10 @@ MQTT_Client.ino The MQTT client sits on top of the network layer and receives correction - data from a Point Perfect MQTT server and then provided to the ZED (GNSS + data from a Point Perfect MQTT broker and then provided to the ZED (GNSS radio). - MQTT Server + MQTT Broker | | SPARTN correction data V @@ -56,7 +56,7 @@ MQTT_Client.ino ^ | v - MQTT Server + MQTT Broker ------------------------------------------------------------------------------*/ @@ -95,9 +95,9 @@ static const int MQTT_CLIENT_DATA_TIMEOUT = (30 * 1000); // milliseconds enum MQTTClientState { MQTT_CLIENT_OFF = 0, // Using Bluetooth or NTRIP server - MQTT_CLIENT_ON, // WIFI_STATE_START state - MQTT_CLIENT_WAIT_FOR_NETWORK, // Connecting to WiFi access point or Ethernet - MQTT_CLIENT_CONNECTING_2_SERVER, // Connecting to the MQTT server + MQTT_CLIENT_WAIT_FOR_NETWORK, // Connecting to WiFi access point or Ethernet + MQTT_CLIENT_CONNECTION_DELAY, // Delay before using the network + MQTT_CLIENT_CONNECTING_2_BROKER, // Connecting to the MQTT broker MQTT_CLIENT_SERVICES_CONNECTED, // Connected to the MQTT services // Insert new states here MQTT_CLIENT_STATE_MAX // Last entry in the state list @@ -105,9 +105,9 @@ enum MQTTClientState const char *const mqttClientStateName[] = { "MQTT_CLIENT_OFF", - "MQTT_CLIENT_ON", "MQTT_CLIENT_WAIT_FOR_NETWORK", - "MQTT_CLIENT_CONNECTING_2_SERVER", + "MQTT_CLIENT_CONNECTION_DELAY", + "MQTT_CLIENT_CONNECTING_2_BROKER", "MQTT_CLIENT_SERVICES_CONNECTED", }; @@ -141,6 +141,7 @@ static volatile uint32_t mqttClientLastDataReceived; // Last time data was recei static NetworkClientSecure *mqttSecureClient; +static bool mqttClientStartRequested; static volatile uint8_t mqttClientState = MQTT_CLIENT_OFF; // MQTT client timer usage: @@ -154,35 +155,9 @@ static uint32_t mqttClientTimer; // MQTT Client Routines //---------------------------------------- -bool mqttClientIsNeeded() -{ - // If PointPerfectCorrections are not enabled, return false - if (!settings.enablePointPerfectCorrections) - return false; - - // For the mosaic-X5, settings.enablePointPerfectCorrections will be true if - // we are using the PPL and getting keys via ZTP. BUT the Facet mosaic-X5 - // uses the L-Band (only) plan. It should not and can not subscribe to PP IP - // MQTT corrections. So, return false - even though the L-Band frequencies topic - // and key distribution topic could be beneficial. - // We could use present.gnss_mosaicX5, but let's not. See notes on EVK below. - if (productVariant == RTK_FACET_MOSAIC) - return false; - - // For the Facet v2 L-Band, the same is true. It uses the L-Band (only) plan. - // We get keys during ZTP (HTTP_Client). It should not and can not subscribe - // to PP IP MQTT corrections. So, return true only if AssistNow is enabled - - // even though the L-Band frequencies topic and key distribution topic could - // be beneficial. - // Note: EVK supports both L-Band and IP, so we cannot use present.lband_neo - if (productVariant == RTK_FACET_V2_LBAND) - if (!settings.useAssistNow) - return false; - - return true; -} - +//---------------------------------------- // Determine if another connection is possible or if the limit has been reached +//---------------------------------------- bool mqttClientConnectLimitReached() { bool limitReached; @@ -190,11 +165,10 @@ bool mqttClientConnectLimitReached() // Retry the connection a few times limitReached = (mqttClientConnectionAttempts >= MAX_MQTT_CLIENT_CONNECTION_ATTEMPTS); - - bool enableMqttClient = mqttClientIsNeeded(); + limitReached = false; // Restart the MQTT client - MQTT_CLIENT_STOP(limitReached || (!enableMqttClient)); + MQTT_CLIENT_STOP(limitReached || (!mqttClientEnabled(nullptr))); mqttClientConnectionAttempts++; mqttClientConnectionAttemptsTotal++; @@ -215,6 +189,10 @@ bool mqttClientConnectLimitReached() mqttClientConnectionAttemptTimeout = (mqttClientConnectionAttempts - 4) * 5 * 60 * 1000L; // Wait 5, 10, 15, etc minutes between attempts + // Limit the maximum timeout + if (mqttClientConnectionAttemptTimeout > RTK_MAX_CONNECTION_MSEC) + mqttClientConnectionAttemptTimeout = RTK_MAX_CONNECTION_MSEC; + // Display the delay before starting the MQTT client if (settings.debugMqttClientState && mqttClientConnectionAttemptTimeout) { @@ -232,7 +210,67 @@ bool mqttClientConnectLimitReached() return limitReached; } +//---------------------------------------- +// Determine if the MQTT client may be enabled +//---------------------------------------- +bool mqttClientEnabled(const char ** line) +{ + bool enabled; + + do + { + enabled = false; + + // Verify the operating mode + if (NEQ_RTK_MODE(mqttClientMode)) + { + if (line) + *line = ", wrong mode!"; + break; + } + + // MQTT requires use of point perfect corrections + if (settings.enablePointPerfectCorrections == false) + { + if (line) + *line = ", PointPerfect corrections are disabled!"; + break; + } + + // For the mosaic-X5, settings.enablePointPerfectCorrections will be true if + // we are using the PPL and getting keys via ZTP. BUT the Facet mosaic-X5 + // uses the L-Band (only) plan. It should not and can not subscribe to PP IP + // MQTT corrections. So, if present.gnss_mosaicX5 is true, set enableMqttClient + // to false. + // TODO : review this. This feels like a bit of a hack... + if (present.gnss_mosaicX5) + { + if (line) + *line = ", Mosaic not present!"; + break; + } + + // Verify still enabled + enabled = mqttClientStartRequested; + if (line && !enabled) + *line = ", MQTT not enabled!"; + } while (0); + return enabled; +} + +//---------------------------------------- +// Determine if the client is connected to the services +//---------------------------------------- +bool mqttClientIsConnected() +{ + if (mqttClientState == MQTT_CLIENT_SERVICES_CONNECTED) + return true; + return false; +} + +//---------------------------------------- // Print the MQTT client state summary +//---------------------------------------- void mqttClientPrintStateSummary() { switch (mqttClientState) @@ -244,12 +282,12 @@ void mqttClientPrintStateSummary() systemPrint("Off"); break; - case MQTT_CLIENT_ON: case MQTT_CLIENT_WAIT_FOR_NETWORK: + case MQTT_CLIENT_CONNECTION_DELAY: systemPrint("Disconnected"); break; - case MQTT_CLIENT_CONNECTING_2_SERVER: + case MQTT_CLIENT_CONNECTING_2_BROKER: systemPrint("Connecting"); break; @@ -259,7 +297,9 @@ void mqttClientPrintStateSummary() } } +//---------------------------------------- // Print the MQTT Client status +//---------------------------------------- void mqttClientPrintStatus() { uint32_t days; @@ -268,10 +308,8 @@ void mqttClientPrintStatus() byte minutes; byte seconds; - bool enableMqttClient = mqttClientIsNeeded(); - // Display MQTT Client status and uptime - if (enableMqttClient && (EQ_RTK_MODE(mqttClientMode))) + if (mqttClientEnabled(nullptr)) { systemPrint("MQTT Client "); mqttClientPrintStateSummary(); @@ -306,7 +344,286 @@ void mqttClientPrintStatus() } } +//---------------------------------------- +// Print the subscribed topics +//---------------------------------------- +void mqttClientPrintSubscribedTopics() +{ + systemPrint("MQTT Client subscribe topics: "); + for (auto it = mqttSubscribeTopics.begin(); it != mqttSubscribeTopics.end(); it = std::next(it)) + { + String topic = *it; + systemPrint(topic); + systemPrint(" "); + } + systemPrintln(); + systemPrint("MQTT Client subscribed topics: "); + for (auto it = mqttClientSubscribedTopics.begin(); it != mqttClientSubscribedTopics.end(); it = std::next(it)) + { + String topic = *it; + systemPrint(topic); + systemPrint(" "); + } + systemPrintln(); +} + +//---------------------------------------- +// Process the localizedDistributionDictTopic message +//---------------------------------------- +void mqttClientProcessLocalizedDistributionDictTopic(uint8_t * mqttData) +{ + // We should be using a JSON library to read the nodes. But JSON is + // heavy on RAM and the dict could be 25KB for Level 3. + // Use sscanf instead. + // + // { + // "tile": "L2N5375W00125", + // "nodeprefix": "pp/ip/L2N5375W00125/", + // "nodes": [ + // "N5200W00300", + // "N5200W00200", + // "N5200W00100", + // "N5200E00000", + // "N5200E00100", + // "N5300W00300", + // "N5300W00200", + // "N5300W00100", + // "N5300E00000", + // "N5400W00200", + // "N5400W00100", + // "N5500W00300", + // "N5500W00200" + // ], + // "endpoint": "pp-eu.services.u-blox.com" + // } + char *nodes = strstr((const char *)mqttData, "\"nodes\":["); + if (nodes != nullptr) + { + nodes += strlen("\"nodes\":["); // Point to the first node + float minDist = 99999.0; // Minimum distance to tile center in centidegrees + char *preservedTile; + char *tile = strtok_r(nodes, ",", &preservedTile); + int latitude = int(gnss->getLatitude() * 100.0); // Centidegrees + int longitude = int(gnss->getLongitude() * 100.0); // Centidegrees + while (tile != nullptr) + { + char ns, ew; + int lat, lon; + if (sscanf(tile, "\"%c%d%c%d\"", &ns, &lat, &ew, &lon) == 4) + { + if (ns == 'S') + lat = 0 - lat; + if (ew == 'W') + lon = 0 - lon; + float factorLon = cos(radians(latitude / 100.0)); // Scale lon by the lat + float distScaled = pow(pow(lat - latitude, 2) + pow((lon - longitude) * factorLon, 2), + 0.5); // Calculate distance to tile center in centidegrees + if (distScaled < minDist) + { + minDist = distScaled; + tile[12] = 0; // Convert the second quote to NULL for snprintf + char tileTopic[50]; + snprintf(tileTopic, sizeof(tileTopic), "%s", localizedDistributionDictTopic.c_str()); + snprintf(&tileTopic[strlen(localizedPrefix) + 13], + sizeof(tileTopic) - (strlen(localizedPrefix) + 13), "%s", + tile + 1); // Start after the first quote + localizedDistributionTileTopic = tileTopic; + } + } + tile = strtok_r(nullptr, ",", &preservedTile); + } + } +} + +//---------------------------------------- +// Process the point perfect keys distribution message +//---------------------------------------- +void mqttClientProcessPointPerfectKeyDistributionTopic(uint8_t * mqttData) +{ + // Separate the UBX message into its constituent Key/ToW/Week parts + uint8_t *payLoad = &mqttData[6]; + uint8_t currentKeyLength = payLoad[5]; + uint16_t currentWeek = (payLoad[7] << 8) | payLoad[6]; + uint32_t currentToW = + (payLoad[11] << 8 * 3) | (payLoad[10] << 8 * 2) | (payLoad[9] << 8 * 1) | (payLoad[8] << 8 * 0); + + char currentKey[currentKeyLength]; + memcpy(¤tKey, &payLoad[20], currentKeyLength); + + uint8_t nextKeyLength = payLoad[13]; + uint16_t nextWeek = (payLoad[15] << 8) | payLoad[14]; + uint32_t nextToW = + (payLoad[19] << 8 * 3) | (payLoad[18] << 8 * 2) | (payLoad[17] << 8 * 1) | (payLoad[16] << 8 * 0); + + char nextKey[nextKeyLength]; + memcpy(&nextKey, &payLoad[20 + currentKeyLength], nextKeyLength); + + // Convert byte array to HEX character array + strcpy(settings.pointPerfectCurrentKey, ""); // Clear contents + strcpy(settings.pointPerfectNextKey, ""); // Clear contents + for (int x = 0; x < 16; x++) // Force length to max of 32 bytes + { + char temp[3]; + snprintf(temp, sizeof(temp), "%02X", currentKey[x]); + strcat(settings.pointPerfectCurrentKey, temp); + + snprintf(temp, sizeof(temp), "%02X", nextKey[x]); + strcat(settings.pointPerfectNextKey, temp); + } + + // Convert from ToW and Week to key duration and key start + WeekToWToUnixEpoch(&settings.pointPerfectCurrentKeyStart, currentWeek, currentToW); + WeekToWToUnixEpoch(&settings.pointPerfectNextKeyStart, nextWeek, nextToW); + + settings.pointPerfectCurrentKeyStart -= + gnss->getLeapSeconds(); // Remove GPS leap seconds to align with u-blox + settings.pointPerfectNextKeyStart -= gnss->getLeapSeconds(); + + settings.pointPerfectCurrentKeyStart *= 1000; // Convert to ms + settings.pointPerfectNextKeyStart *= 1000; + + settings.pointPerfectCurrentKeyDuration = + settings.pointPerfectNextKeyStart - settings.pointPerfectCurrentKeyStart - 1; + // settings.pointPerfectNextKeyDuration = + // settings.pointPerfectCurrentKeyDuration; // We assume next key duration is the same as current + // key duration because we have to + + settings.pointPerfectNextKeyDuration = + (1000LL * 60 * 60 * 24 * 28) - 1; // Assume next key duration is 28 days + + if (online.rtc) + settings.lastKeyAttempt = rtc.getEpoch(); // Mark it - but only if RTC is online + + recordSystemSettings(); // Record these settings to unit + + if (settings.debugCorrections == true) + pointperfectPrintKeyInformation("MQTT Topic"); +} + +//---------------------------------------- +// Send the message to the ZED +//---------------------------------------- +int mqttClientProcessZedMessage(uint8_t * mqttData, uint16_t mqttCount, int bytesPushed, char * topic) +{ +#ifdef COMPILE_ZED + // Only push SPARTN if the priority says we can + if ( + // We can get correction data from the continental topic + ((strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) && + (strcmp(topic, settings.regionalCorrectionTopics[settings.geographicRegion]) == 0)) || + // Or from the localized distribution tile topic + ((localizedDistributionTileTopic.length() > 0) && + (strcmp(topic, localizedDistributionTileTopic.c_str()) == 0))) + { + // Determine if MQTT (SPARTN data) is the correction source + if (correctionLastSeen(CORR_IP)) + { + if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true) + || PERIODIC_DISPLAY(PD_MQTT_CLIENT_DATA)) && !inMainMenu) + { + PERIODIC_CLEAR(PD_MQTT_CLIENT_DATA); + systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); + } + + GNSS_ZED *zed = (GNSS_ZED *)gnss; + zed->updateCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed + + gnss->pushRawData(mqttData, mqttCount); + bytesPushed += mqttCount; + + mqttClientDataReceived = true; + } + else + { + if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true) + || PERIODIC_DISPLAY(PD_MQTT_CLIENT_DATA)) && !inMainMenu) + { + PERIODIC_CLEAR(PD_MQTT_CLIENT_DATA); + systemPrintf("NOT pushing %d bytes from %s topic to GNSS due to priority\r\n", mqttCount, + topic); + } + } + } + // Always push KEYS and MGA to the ZED + else + { + // KEYS or MGA + if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true) + || PERIODIC_DISPLAY(PD_MQTT_CLIENT_DATA)) && !inMainMenu) + { + PERIODIC_CLEAR(PD_MQTT_CLIENT_DATA); + systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); + } + + gnss->pushRawData(mqttData, mqttCount); + bytesPushed += mqttCount; + } +#endif // COMPILE_ZED + return bytesPushed; +} + +//---------------------------------------- +// Process the subscribed messages +//---------------------------------------- +int mqttClientProcessMessage(uint8_t * mqttData, uint16_t mqttCount, int bytesPushed, char * topic) +{ + // Check for localizedDistributionDictTopic + if ((localizedDistributionDictTopic.length() > 0) && + (strcmp(topic, localizedDistributionDictTopic.c_str()) == 0)) + { + mqttClientProcessLocalizedDistributionDictTopic(mqttData); + mqttClientLastDataReceived = millis(); + return bytesPushed; // Return now - the dict topic should not be pushed + } + + // Is this the full AssistNow MGA data? If so, unsubscribe and subscribe to updates + if (strcmp(topic, MQTT_TOPIC_ASSISTNOW.c_str()) == 0) + { + std::vector::iterator pos = + std::find(mqttSubscribeTopics.begin(), mqttSubscribeTopics.end(), MQTT_TOPIC_ASSISTNOW); + if (pos != mqttSubscribeTopics.end()) + mqttSubscribeTopics.erase(pos); + + mqttSubscribeTopics.push_back(MQTT_TOPIC_ASSISTNOW_UPDATES); + } + + // Are these keys? If so, update our local copy + if ((strlen(settings.pointPerfectKeyDistributionTopic) > 0) && + (strcmp(topic, settings.pointPerfectKeyDistributionTopic) == 0)) + { + mqttClientProcessPointPerfectKeyDistributionTopic(mqttData); + } + + // Correction data from PP can go direct to GNSS + if (present.gnss_zedf9p) + bytesPushed = mqttClientProcessZedMessage(mqttData, + mqttCount, + bytesPushed, + topic); + + // For the UM980 or LG290P, we have to pass the data through the PPL first + else if (present.gnss_um980 || present.gnss_lg290p) + { + if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && !inMainMenu) + { + if (present.gnss_um980) + systemPrintf("Pushing %d bytes from %s topic to PPL for UM980\r\n", mqttCount, topic); + else if (present.gnss_lg290p) + systemPrintf("Pushing %d bytes from %s topic to PPL for LG290P\r\n", mqttCount, topic); + } + + if (online.ppl == false && settings.debugMqttClientData == true) + systemPrintln("Warning: PPL is offline"); + + sendSpartnToPpl(mqttData, mqttCount); + bytesPushed += mqttCount; + } + return bytesPushed; +} + +//---------------------------------------- // Called when a subscribed message arrives +//---------------------------------------- void mqttClientReceiveMessage(int messageSize) { // The Level 3 localized distribution dictionary topic can be up to 25KB @@ -314,12 +631,7 @@ void mqttClientReceiveMessage(int messageSize) const uint16_t mqttLimit = 26000; static uint8_t *mqttData = nullptr; if (mqttData == nullptr) // Allocate memory to hold the MQTT data. Never freed - { - if (online.psram == true) - mqttData = (uint8_t *)ps_malloc(mqttLimit); - else - mqttData = (uint8_t *)malloc(mqttLimit); - } + mqttData = (uint8_t *)rtkMalloc(mqttLimit, "MQTT data (mqttData)"); if (mqttData == nullptr) { systemPrintln(F("Memory allocation for mqttData failed!")); @@ -347,219 +659,11 @@ void mqttClientReceiveMessage(int messageSize) if (mqttCount > 0) { - // Check for localizedDistributionDictTopic - if ((localizedDistributionDictTopic.length() > 0) && - (strcmp(topic, localizedDistributionDictTopic.c_str()) == 0)) - { - // We should be using a JSON library to read the nodes. But JSON is - // heavy on RAM and the dict could be 25KB for Level 3. - // Use sscanf instead. - // - // { - // "tile": "L2N5375W00125", - // "nodeprefix": "pp/ip/L2N5375W00125/", - // "nodes": [ - // "N5200W00300", - // "N5200W00200", - // "N5200W00100", - // "N5200E00000", - // "N5200E00100", - // "N5300W00300", - // "N5300W00200", - // "N5300W00100", - // "N5300E00000", - // "N5400W00200", - // "N5400W00100", - // "N5500W00300", - // "N5500W00200" - // ], - // "endpoint": "pp-eu.services.u-blox.com" - // } - char *nodes = strstr((const char *)mqttData, "\"nodes\":["); - if (nodes != nullptr) - { - nodes += strlen("\"nodes\":["); // Point to the first node - float minDist = 99999.0; // Minimum distance to tile center in centidegrees - char *preservedTile; - char *tile = strtok_r(nodes, ",", &preservedTile); - int latitude = int(gnss->getLatitude() * 100.0); // Centidegrees - int longitude = int(gnss->getLongitude() * 100.0); // Centidegrees - while (tile != nullptr) - { - char ns, ew; - int lat, lon; - if (sscanf(tile, "\"%c%d%c%d\"", &ns, &lat, &ew, &lon) == 4) - { - if (ns == 'S') - lat = 0 - lat; - if (ew == 'W') - lon = 0 - lon; - float factorLon = cos(radians(latitude / 100.0)); // Scale lon by the lat - float distScaled = pow(pow(lat - latitude, 2) + pow((lon - longitude) * factorLon, 2), - 0.5); // Calculate distance to tile center in centidegrees - if (distScaled < minDist) - { - minDist = distScaled; - tile[12] = 0; // Convert the second quote to NULL for snprintf - char tileTopic[50]; - snprintf(tileTopic, sizeof(tileTopic), "%s", localizedDistributionDictTopic.c_str()); - snprintf(&tileTopic[strlen(localizedPrefix) + 13], - sizeof(tileTopic) - (strlen(localizedPrefix) + 13), "%s", - tile + 1); // Start after the first quote - localizedDistributionTileTopic = tileTopic; - } - } - tile = strtok_r(nullptr, ",", &preservedTile); - } - } - - mqttClientLastDataReceived = millis(); - break; // Break now - the dict topic should not be pushed - } - - // Is this the full AssistNow MGA data? If so, unsubscribe and subscribe to updates - if (strcmp(topic, MQTT_TOPIC_ASSISTNOW.c_str()) == 0) - { - std::vector::iterator pos = - std::find(mqttSubscribeTopics.begin(), mqttSubscribeTopics.end(), MQTT_TOPIC_ASSISTNOW); - if (pos != mqttSubscribeTopics.end()) - mqttSubscribeTopics.erase(pos); - - mqttSubscribeTopics.push_back(MQTT_TOPIC_ASSISTNOW_UPDATES); - } - - // Are these keys? If so, update our local copy - if ((strlen(settings.pointPerfectKeyDistributionTopic) > 0) && - (strcmp(topic, settings.pointPerfectKeyDistributionTopic) == 0)) - { - // Separate the UBX message into its constituent Key/ToW/Week parts - uint8_t *payLoad = &mqttData[6]; - uint8_t currentKeyLength = payLoad[5]; - uint16_t currentWeek = (payLoad[7] << 8) | payLoad[6]; - uint32_t currentToW = - (payLoad[11] << 8 * 3) | (payLoad[10] << 8 * 2) | (payLoad[9] << 8 * 1) | (payLoad[8] << 8 * 0); - - char currentKey[currentKeyLength]; - memcpy(¤tKey, &payLoad[20], currentKeyLength); - - uint8_t nextKeyLength = payLoad[13]; - uint16_t nextWeek = (payLoad[15] << 8) | payLoad[14]; - uint32_t nextToW = - (payLoad[19] << 8 * 3) | (payLoad[18] << 8 * 2) | (payLoad[17] << 8 * 1) | (payLoad[16] << 8 * 0); - - char nextKey[nextKeyLength]; - memcpy(&nextKey, &payLoad[20 + currentKeyLength], nextKeyLength); - - // Convert byte array to HEX character array - strcpy(settings.pointPerfectCurrentKey, ""); // Clear contents - strcpy(settings.pointPerfectNextKey, ""); // Clear contents - for (int x = 0; x < 16; x++) // Force length to max of 32 bytes - { - char temp[3]; - snprintf(temp, sizeof(temp), "%02X", currentKey[x]); - strcat(settings.pointPerfectCurrentKey, temp); - - snprintf(temp, sizeof(temp), "%02X", nextKey[x]); - strcat(settings.pointPerfectNextKey, temp); - } - - // Convert from ToW and Week to key duration and key start - WeekToWToUnixEpoch(&settings.pointPerfectCurrentKeyStart, currentWeek, currentToW); - WeekToWToUnixEpoch(&settings.pointPerfectNextKeyStart, nextWeek, nextToW); - - settings.pointPerfectCurrentKeyStart -= - gnss->getLeapSeconds(); // Remove GPS leap seconds to align with u-blox - settings.pointPerfectNextKeyStart -= gnss->getLeapSeconds(); - - settings.pointPerfectCurrentKeyStart *= 1000; // Convert to ms - settings.pointPerfectNextKeyStart *= 1000; - - settings.pointPerfectCurrentKeyDuration = - settings.pointPerfectNextKeyStart - settings.pointPerfectCurrentKeyStart - 1; - // settings.pointPerfectNextKeyDuration = - // settings.pointPerfectCurrentKeyDuration; // We assume next key duration is the same as current - // key duration because we have to - - settings.pointPerfectNextKeyDuration = - (1000LL * 60 * 60 * 24 * 28) - 1; // Assume next key duration is 28 days - - if (online.rtc) - settings.lastKeyAttempt = rtc.getEpoch(); // Mark it - but only if RTC is online - - recordSystemSettings(); // Record these settings to unit - - if (settings.debugCorrections == true) - pointperfectPrintKeyInformation("MQTT Topic"); - } - - // Correction data from PP can go direct to GNSS - if (present.gnss_zedf9p) - { -#ifdef COMPILE_ZED - // Only push SPARTN if the priority says we can - if ( - // We can get correction data from the continental topic - ((strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) && - (strcmp(topic, settings.regionalCorrectionTopics[settings.geographicRegion]) == 0)) || - // Or from the localized distribution tile topic - ((localizedDistributionTileTopic.length() > 0) && - (strcmp(topic, localizedDistributionTileTopic.c_str()) == 0))) - { - // Determine if MQTT (SPARTN data) is the correction source - if (correctionLastSeen(CORR_IP)) - { - if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && - !inMainMenu) - systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); - - GNSS_ZED *zed = (GNSS_ZED *)gnss; - zed->updateCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed - - gnss->pushRawData(mqttData, mqttCount); - bytesPushed += mqttCount; - - mqttClientDataReceived = true; - } - else - { - if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && - !inMainMenu) - systemPrintf("NOT pushing %d bytes from %s topic to GNSS due to priority\r\n", mqttCount, - topic); - } - } - // Always push KEYS and MGA to the ZED - else - { - // KEYS or MGA - if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && !inMainMenu) - systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); - - gnss->pushRawData(mqttData, mqttCount); - bytesPushed += mqttCount; - } -#endif // COMPILE_ZED - } - - // For the UM980 or LG290P, we have to pass the data through the PPL first - else if (present.gnss_um980 || present.gnss_lg290p) - { - if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && !inMainMenu) - { - if (present.gnss_um980) - systemPrintf("Pushing %d bytes from %s topic to PPL for UM980\r\n", mqttCount, topic); - else if (present.gnss_lg290p) - systemPrintf("Pushing %d bytes from %s topic to PPL for LG290P\r\n", mqttCount, topic); - } - - if (online.ppl == false && settings.debugMqttClientData == true) - systemPrintln("Warning: PPL is offline"); - - sendSpartnToPpl(mqttData, mqttCount); - bytesPushed += mqttCount; - - mqttClientDataReceived = true; - } + // Process the MQTT message + bytesPushed = mqttClientProcessMessage(mqttData, + mqttCount, + bytesPushed, + topic); // Record the arrival of data over MQTT mqttClientLastDataReceived = millis(); @@ -572,7 +676,9 @@ void mqttClientReceiveMessage(int messageSize) } } +//---------------------------------------- // Restart the MQTT client +//---------------------------------------- void mqttClientRestart() { // Save the previous uptime value @@ -581,10 +687,12 @@ void mqttClientRestart() mqttClientConnectLimitReached(); } +//---------------------------------------- // Update the state of the MQTT client state machine +//---------------------------------------- void mqttClientSetState(uint8_t newState) { - if (settings.debugMqttClientState || PERIODIC_DISPLAY(PD_MQTT_CLIENT_STATE)) + if (settings.debugMqttClientState) { if (mqttClientState == newState) systemPrint("*"); @@ -592,9 +700,8 @@ void mqttClientSetState(uint8_t newState) systemPrintf("%s --> ", mqttClientStateName[mqttClientState]); } mqttClientState = newState; - if (settings.debugMqttClientState || PERIODIC_DISPLAY(PD_MQTT_CLIENT_STATE)) + if (settings.debugMqttClientState) { - PERIODIC_CLEAR(PD_MQTT_CLIENT_STATE); if (newState >= MQTT_CLIENT_STATE_MAX) { systemPrintf("Unknown MQTT Client state: %d\r\n", newState); @@ -602,33 +709,31 @@ void mqttClientSetState(uint8_t newState) } else systemPrintln(mqttClientStateName[mqttClientState]); - - systemPrint("MQTT Client subscribe topics: "); - for (auto it = mqttSubscribeTopics.begin(); it != mqttSubscribeTopics.end(); it = std::next(it)) - { - String topic = *it; - systemPrint(topic); - systemPrint(" "); - } - systemPrintln(); - systemPrint("MQTT Client subscribed topics: "); - for (auto it = mqttClientSubscribedTopics.begin(); it != mqttClientSubscribedTopics.end(); it = std::next(it)) - { - String topic = *it; - systemPrint(topic); - systemPrint(" "); - } - systemPrintln(); } } +//---------------------------------------- // Shutdown the MQTT client +//---------------------------------------- void mqttClientShutdown() { + mqttClientStartRequested = false; MQTT_CLIENT_STOP(true); } +//---------------------------------------- +// Start the MQTT client +//---------------------------------------- +void mqttClientStartEnabled() +{ + if (settings.debugMqttClientState) + systemPrintf("MQTT_Client: Start enabled\r\n"); + mqttClientStartRequested = true; +} + +//---------------------------------------- // Shutdown or restart the MQTT client +//---------------------------------------- void mqttClientStop(bool shutdown) { // Display the uptime @@ -663,67 +768,76 @@ void mqttClientStop(bool shutdown) // Release the buffers if (mqttClientPrivateKeyBuffer != nullptr) { - free(mqttClientPrivateKeyBuffer); + rtkFree(mqttClientPrivateKeyBuffer, "Certificate buffer (mqttClientPrivateKeyBuffer)"); mqttClientPrivateKeyBuffer = nullptr; } if (mqttClientCertificateBuffer != nullptr) { - free(mqttClientCertificateBuffer); + rtkFree(mqttClientCertificateBuffer, "Certificate buffer (mqttClientCertificateBuffer)"); mqttClientCertificateBuffer = nullptr; } reportHeapNow(settings.debugMqttClientState); // Increase timeouts if we started the network - if (mqttClientState > MQTT_CLIENT_ON) - // Mark the Client stop so that we don't immediately attempt re-connect to Caster + if (mqttClientState > MQTT_CLIENT_WAIT_FOR_NETWORK) + // Mark client stop so that we don't immediately attempt re-connect to broker mqttClientTimer = millis(); // Determine the next MQTT client state online.mqttClient = false; mqttClientDataReceived = false; + networkConsumerOffline(NETCONSUMER_PPL_MQTT_CLIENT); if (shutdown) { - mqttClientSetState(MQTT_CLIENT_OFF); - // settings.enablePointPerfectCorrections = false; - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Why? This means PointPerfect Corrections - // cannot be restarted without opening the menu or web configuration page... + networkConsumerRemove(NETCONSUMER_PPL_MQTT_CLIENT, NETWORK_ANY, __FILE__, __LINE__); mqttClientConnectionAttempts = 0; mqttClientConnectionAttemptTimeout = 0; + mqttClientPrintSubscribedTopics(); + mqttClientSetState(MQTT_CLIENT_OFF); + if (settings.debugMqttClientState) + systemPrintln("MQTT Client stopped"); } else - mqttClientSetState(MQTT_CLIENT_ON); -} - -// Return true if we are in states that require network access -bool mqttClientNeedsNetwork() -{ - if (mqttClientState >= MQTT_CLIENT_WAIT_FOR_NETWORK && mqttClientState <= MQTT_CLIENT_SERVICES_CONNECTED) - return true; - return false; + { + mqttClientSetState(MQTT_CLIENT_WAIT_FOR_NETWORK); + mqttClientPrintSubscribedTopics(); + } } +//---------------------------------------- // Check for the arrival of any correction data. Push it to the GNSS. // Stop task if the connection has dropped or if we receive no data for // MQTT_CLIENT_RECEIVE_DATA_TIMEOUT +//---------------------------------------- void mqttClientUpdate() { - bool enableMqttClient = mqttClientIsNeeded(); + bool connected; + bool enabled; // Shutdown the MQTT client when the mode or setting changes DMW_st(mqttClientSetState, mqttClientState); + connected = networkConsumerIsConnected(NETCONSUMER_PPL_MQTT_CLIENT); + enabled = mqttClientEnabled(nullptr); + if ((enabled == false) && (mqttClientState > MQTT_CLIENT_OFF)) + { + if (settings.debugMqttClientState) + systemPrintln("MQTT Client stopping"); + mqttClientShutdown(); + } - if (NEQ_RTK_MODE(mqttClientMode) || (!enableMqttClient)) + // Determine if the network has failed + else if ((mqttClientState > MQTT_CLIENT_WAIT_FOR_NETWORK) && !connected) { - if (mqttClientState > MQTT_CLIENT_OFF) + if (mqttClientState == MQTT_CLIENT_SERVICES_CONNECTED) { - systemPrintln("MQTT Client stopping"); - MQTT_CLIENT_STOP(true); // Was false - #StopVsRestart + // The connection is successful, allow more retries in the future + // with immediate retries mqttClientConnectionAttempts = 0; mqttClientConnectionAttemptTimeout = 0; - mqttClientSetState(MQTT_CLIENT_OFF); } + mqttClientRestart(); } // Enable the network and the MQTT client if requested @@ -731,90 +845,71 @@ void mqttClientUpdate() { default: case MQTT_CLIENT_OFF: { - if (EQ_RTK_MODE(mqttClientMode) && enableMqttClient) + if (enabled) { // Start the MQTT client if (settings.debugMqttClientState) systemPrintln("MQTT Client start"); mqttClientStop(false); - } - break; - } - - // Start the network - case MQTT_CLIENT_ON: { - if ((millis() - mqttClientTimer) > mqttClientConnectionAttemptTimeout) - { - mqttClientSetState(MQTT_CLIENT_WAIT_FOR_NETWORK); + networkConsumerAdd(NETCONSUMER_PPL_MQTT_CLIENT, NETWORK_ANY, __FILE__, __LINE__); } break; } // Wait for a network media connection case MQTT_CLIENT_WAIT_FOR_NETWORK: { - // Determine if MQTT was turned off - if (NEQ_RTK_MODE(mqttClientMode) || !enableMqttClient) - mqttClientStop(true); - // Wait until the network is connected to the media - else if (networkHasInternet()) - mqttClientSetState(MQTT_CLIENT_CONNECTING_2_SERVER); + if (connected) + { + // Reset the timeout when the network changes + if (networkChanged(NETCONSUMER_PPL_MQTT_CLIENT)) + mqttClientConnectionAttemptTimeout = 0; + networkUserAdd(NETCONSUMER_PPL_MQTT_CLIENT, __FILE__, __LINE__); + mqttClientSetState(MQTT_CLIENT_CONNECTION_DELAY); + mqttClientPrintSubscribedTopics(); + } break; } - // Connect to the MQTT server - case MQTT_CLIENT_CONNECTING_2_SERVER: { - // Determine if the network has failed - if (networkHasInternet() == false) + // Delay before using the network + case MQTT_CLIENT_CONNECTION_DELAY: { + if ((millis() - mqttClientTimer) > mqttClientConnectionAttemptTimeout) { - // Failed to connect to the network, attempt to restart the network - mqttClientStop(true); // Was mqttClientRestart(); - #StopVsRestart - break; + mqttClientSetState(MQTT_CLIENT_CONNECTING_2_BROKER); + mqttClientPrintSubscribedTopics(); } + break; + } + // Connect to the MQTT broker + case MQTT_CLIENT_CONNECTING_2_BROKER: { // Allocate the mqttSecureClient structure mqttSecureClient = new NetworkClientSecure(); if (!mqttSecureClient) { systemPrintln("ERROR: Failed to allocate the mqttSecureClient structure!"); - mqttClientShutdown(); + mqttClientRestart(); break; } - // Allocate the buffers - // Freed by mqttClientShutdown / mqttClientStop - if (online.psram == true) - { - if (!mqttClientCertificateBuffer) - mqttClientCertificateBuffer = (char *)ps_malloc(MQTT_CERT_SIZE); - if (!mqttClientPrivateKeyBuffer) - mqttClientPrivateKeyBuffer = (char *)ps_malloc(MQTT_CERT_SIZE); - } - else - { - if (!mqttClientCertificateBuffer) - mqttClientCertificateBuffer = (char *)malloc(MQTT_CERT_SIZE); - if (!mqttClientPrivateKeyBuffer) - mqttClientPrivateKeyBuffer = (char *)malloc(MQTT_CERT_SIZE); - } + // Allocate the buffers, freed by mqttClientStop + if (!mqttClientCertificateBuffer) + mqttClientCertificateBuffer = (char *)rtkMalloc(MQTT_CERT_SIZE, "Certificate buffer (mqttClientCertificateBuffer)"); + if (!mqttClientPrivateKeyBuffer) + mqttClientPrivateKeyBuffer = (char *)rtkMalloc(MQTT_CERT_SIZE, "Certificate buffer (mqttClientPrivateKeyBuffer)"); + // Determine if the buffers were allocated if ((!mqttClientCertificateBuffer) || (!mqttClientPrivateKeyBuffer)) { - if (mqttClientCertificateBuffer) - { - free(mqttClientCertificateBuffer); - mqttClientCertificateBuffer = nullptr; - systemPrintln("Failed to allocate key buffer!"); - } + // Complain about the buffer allocation failure + if (mqttClientCertificateBuffer == nullptr) + systemPrintln("ERROR: Failed to allocate certificate buffer!"); - if (mqttClientPrivateKeyBuffer) - { - free(mqttClientPrivateKeyBuffer); - mqttClientPrivateKeyBuffer = nullptr; - systemPrintln("Failed to allocate certificate buffer!"); - } + if (mqttClientPrivateKeyBuffer == nullptr) + systemPrintln("ERROR: Failed to allocate key buffer!"); - mqttClientShutdown(); + // Free the buffers and attempt another connection after delay + mqttClientRestart(); break; } @@ -826,9 +921,8 @@ void mqttClientUpdate() if (!loadFile("certificate", mqttClientCertificateBuffer, settings.debugMqttClientState)) { if (settings.debugMqttClientState) - systemPrintln("MQTT_CLIENT_CONNECTING_2_SERVER no certificate available"); - mqttClientRestart(); // This does need a restart. Was mqttClientShutdown, but that causes an immediate retry - // with no timeout + systemPrintln("ERROR: MQTT_Client no certificate available"); + mqttClientShutdown(); break; } mqttSecureClient->setCertificate(mqttClientCertificateBuffer); @@ -838,9 +932,8 @@ void mqttClientUpdate() if (!loadFile("privateKey", mqttClientPrivateKeyBuffer, settings.debugMqttClientState)) { if (settings.debugMqttClientState) - systemPrintln("MQTT_CLIENT_CONNECTING_2_SERVER no private key available"); - mqttClientRestart(); // This does need a restart. Was mqttClientShutdown, but that causes an immediate retry - // with no timeout + systemPrintln("ERROR: MQTT_Client no private key available"); + mqttClientShutdown(); break; } mqttSecureClient->setPrivateKey(mqttClientPrivateKeyBuffer); @@ -851,7 +944,7 @@ void mqttClientUpdate() { // Failed to allocate the mqttClient structure systemPrintln("ERROR: Failed to allocate the mqttClient structure!"); - mqttClientShutdown(); + mqttClientRestart(); break; } @@ -861,7 +954,7 @@ void mqttClientUpdate() if (settings.debugMqttClientState) systemPrintf("MQTT client connecting to %s\r\n", settings.pointPerfectBrokerHost); - // Attempt to the MQTT broker + // Attempt connection to the MQTT broker if (!mqttClient->connect(settings.pointPerfectBrokerHost, 8883)) { systemPrintf("Failed to connect to MQTT broker %s\r\n", settings.pointPerfectBrokerHost); @@ -869,7 +962,7 @@ void mqttClientUpdate() break; } - // The MQTT server is now connected + // The MQTT broker is now connected mqttClient->onMessage(mqttClientReceiveMessage); mqttSubscribeTopics.clear(); // Clear the list of MQTT topics to be subscribed to @@ -882,10 +975,10 @@ void mqttClientUpdate() { mqttSubscribeTopics.push_back(MQTT_TOPIC_ASSISTNOW); } - + // Subscribe to the key distribution topic mqttSubscribeTopics.push_back(String(settings.pointPerfectKeyDistributionTopic)); - + // Subscribe to the continental correction topic for our region - if we have one. L-Band-only does not. if (strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) { @@ -907,16 +1000,15 @@ void mqttClientUpdate() online.mqttClient = true; mqttClientSetState(MQTT_CLIENT_SERVICES_CONNECTED); - + mqttClientPrintSubscribedTopics(); break; - } // /case MQTT_CLIENT_CONNECTING_2_SERVER + } // /case MQTT_CLIENT_CONNECTING_2_BROKER case MQTT_CLIENT_SERVICES_CONNECTED: { - // Determine if the network has failed - if (networkHasInternet() == false) + // Verify the connection to the broker + if (mqttSecureClient->connected() == false) { - // Failed to connect to the network, attempt to restart the network - mqttClientStop(true); // Was mqttClientRestart(); - #StopVsRestart + mqttClientRestart(); break; } @@ -947,6 +1039,8 @@ void mqttClientUpdate() { breakOut = true; // Break out of this state as we have successfully subscribed mqttClientSubscribedTopics.push_back(topic); + if (settings.debugMqttClientState) + systemPrintf("MQTT_Client successfully subscribed to topic %s\r\n", topic.c_str()); } else { @@ -1067,21 +1161,23 @@ void mqttClientUpdate() // Periodically display the MQTT client state if (PERIODIC_DISPLAY(PD_MQTT_CLIENT_STATE)) - mqttClientSetState(mqttClientState); + { + const char * line = ""; + mqttClientEnabled(&line); + systemPrintf("MQTT Client state: %s%s\r\n", + mqttClientStateName[mqttClientState], line); + mqttClientPrintSubscribedTopics(); + PERIODIC_CLEAR(PD_MQTT_CLIENT_STATE); + } } +//---------------------------------------- // Verify the MQTT client tables +//---------------------------------------- void mqttClientValidateTables() { if (mqttClientStateNameEntries != MQTT_CLIENT_STATE_MAX) reportFatalError("Fix mqttClientStateNameEntries to match MQTTClientState"); } -bool mqttClientIsConnected() -{ - if (mqttClientState == MQTT_CLIENT_SERVICES_CONNECTED) - return true; - return false; -} - #endif // COMPILE_MQTT_CLIENT diff --git a/Firmware/RTK_Everywhere/NTP.ino b/Firmware/RTK_Everywhere/NTP.ino index e35da17d..6fc62ab1 100644 --- a/Firmware/RTK_Everywhere/NTP.ino +++ b/Firmware/RTK_Everywhere/NTP.ino @@ -60,13 +60,20 @@ NTP.ino enum NTP_STATE { NTP_STATE_OFF, + NTP_STATE_WAIT_NETWORK, NTP_STATE_NETWORK_CONNECTED, NTP_STATE_SERVER_RUNNING, // Insert new states here NTP_STATE_MAX }; -const char *const ntpServerStateName[] = {"NTP_STATE_OFF", "NTP_STATE_NETWORK_CONNECTED", "NTP_STATE_SERVER_RUNNING"}; +const char *const ntpServerStateName[] = +{ + "NTP_STATE_OFF", + "NTP_STATE_WAIT_NETWORK", + "NTP_STATE_NETWORK_CONNECTED", + "NTP_STATE_SERVER_RUNNING" +}; const int ntpServerStateNameEntries = sizeof(ntpServerStateName) / sizeof(ntpServerStateName[0]); const RtkMode_t ntpServerMode = RTK_MODE_NTP; @@ -82,7 +89,6 @@ static uint32_t lastLoggedNTPRequest; //---------------------------------------- // Menu to get the NTP settings //---------------------------------------- - void menuNTP() { if (!present.ethernet_ws5500 == true) @@ -193,7 +199,7 @@ void menuNTP() } // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -// NTP Packet storage and utilities +// NTP Packet storage class and utilities struct NTPpacket { @@ -336,6 +342,9 @@ struct NTPpacket uint8_t unsigned8; } unsignedSigned8; + //---------------------------------------- + // Convert a 32-bit big-endian value to a little-endian value + //---------------------------------------- uint32_t extractUnsigned32(uint8_t *ptr) { uint32_t val = 0; @@ -346,6 +355,9 @@ struct NTPpacket return val; } + //---------------------------------------- + // Convert a 32-bit little-endian value to a big-endian value + //---------------------------------------- void insertUnsigned32(uint8_t *ptr, uint32_t val) { *ptr++ = val >> 24; // NTP data is Big-Endian @@ -354,7 +366,9 @@ struct NTPpacket *ptr++ = val & 0xFF; } + //---------------------------------------- // Extract the data from an NTP packet into the correct fields + //---------------------------------------- void extract() { uint8_t *ptr = packet; @@ -394,7 +408,9 @@ struct NTPpacket ptr += 4; } + //---------------------------------------- // Insert the data from the fields into an NTP packet + //---------------------------------------- void insert() { uint8_t *ptr = packet; @@ -435,6 +451,9 @@ struct NTPpacket ptr += 4; } + //---------------------------------------- + // Convert microseconds into two values, seconds and a 16-bit fraction of a second + //---------------------------------------- uint32_t convertMicrosToSecsAndFraction(uint32_t val) // 16-bit fraction used by root delay and dispersion { double secs = val; @@ -451,6 +470,9 @@ struct NTPpacket return (result); } + //---------------------------------------- + // Convert microseconds into a 32-bit fraction of a second + //---------------------------------------- uint32_t convertMicrosToFraction(uint32_t val) // 32-bit fraction used by the timestamps { val %= 1000000; // Just in case @@ -460,6 +482,9 @@ struct NTPpacket return (uint32_t)v; } + //---------------------------------------- + // Convert a 32-bit fraction of a second into microseconds + //---------------------------------------- uint32_t convertFractionToMicros(uint32_t val) // 32-bit fraction used by the timestamps { double v = val; // Convert fraction to double @@ -470,22 +495,29 @@ struct NTPpacket return ret; } + //---------------------------------------- + // Convert from NTP seconds to Unix seconds + //---------------------------------------- uint32_t convertNTPsecondsToUnix(uint32_t val) { return (val - NTPtoUnixOffset); } + //---------------------------------------- + // Convert from Unix seconds to NTP seconds + //---------------------------------------- uint32_t convertUnixSecondsToNTP(uint32_t val) { return (val + NTPtoUnixOffset); } }; -// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +//---------------------------------------- // NTP process one request // recTv contains the timeval the NTP packet was received // syncTv contains the timeval when the RTC was last sync'd // ntpDiag will contain useful diagnostics +//---------------------------------------- bool ntpProcessOneRequest(bool process, const timeval *recTv, const timeval *syncTv, char *ntpDiag = nullptr, size_t ntpDiagSize = 0); // Header bool ntpProcessOneRequest(bool process, const timeval *recTv, const timeval *syncTv, char *ntpDiag, size_t ntpDiagSize) @@ -509,8 +541,8 @@ bool ntpProcessOneRequest(bool process, const timeval *recTv, const timeval *syn if (ntpDiag != nullptr) // Add the packet size and remote IP/Port to the diagnostics { - snprintf(ntpDiag, ntpDiagSize, "NTP request from: Remote IP: %s Remote Port: %d\r\n", remoteIP.toString(), - remotePort); + snprintf(ntpDiag, ntpDiagSize, "NTP request from: Remote IP: %s Remote Port: %d\r\n", + remoteIP.toString().c_str(), remotePort); } if (packetDataSize >= NTPpacket::NTPpacketSize) @@ -691,8 +723,10 @@ bool ntpProcessOneRequest(bool process, const timeval *recTv, const timeval *syn return processed; } +//---------------------------------------- // Configure specific aspects of the receiver for NTP mode -bool configureUbloxModuleNTP() +//---------------------------------------- +bool ntpConfigureUbloxModule() { if (present.timePulseInterrupt == false) return (false); @@ -708,10 +742,12 @@ bool configureUbloxModuleNTP() // NTP Server routines //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +//---------------------------------------- // Update the state of the NTP server state machine +//---------------------------------------- void ntpServerSetState(uint8_t newState) { - if ((settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_STATE)) && (!inMainMenu)) + if (settings.debugNtp && (!inMainMenu)) { if (ntpServerState == newState) systemPrint("NTP Server: *"); @@ -719,9 +755,8 @@ void ntpServerSetState(uint8_t newState) systemPrintf("NTP Server: %s --> ", ntpServerStateName[ntpServerState]); } ntpServerState = newState; - if (settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_STATE)) + if (settings.debugNtp) { - PERIODIC_CLEAR(PD_NTP_SERVER_STATE); if (newState >= NTP_STATE_MAX) { systemPrintf("Unknown state: %d\r\n", newState); @@ -732,7 +767,9 @@ void ntpServerSetState(uint8_t newState) } } +//---------------------------------------- // Stop the NTP server +//---------------------------------------- void ntpServerStop() { // Mark the NTP server as off @@ -748,25 +785,37 @@ void ntpServerStop() reportHeapNow(settings.debugNtp); } - // Stop the NTP server - ntpServerSetState(NTP_STATE_OFF); + // Stop the NTP server if necessary + networkConsumerOffline(NETCONSUMER_NTP_SERVER); + if (NEQ_RTK_MODE(ntpServerMode)) + { + networkConsumerRemove(NETCONSUMER_NTP_SERVER, NETWORK_ETHERNET, __FILE__, __LINE__); + ntpServerSetState(NTP_STATE_OFF); + } + else + ntpServerSetState(NTP_STATE_WAIT_NETWORK); } +//---------------------------------------- // Update the NTP server state +//---------------------------------------- void ntpServerUpdate() { + bool enabled; + bool connected; char ntpDiag[768]; // Char array to hold diagnostic messages if (present.ethernet_ws5500 == false) return; - // Shutdown the NTP server when the mode or setting changes - if (NEQ_RTK_MODE(ntpServerMode)) - { - if (ntpServerState > NTP_STATE_OFF) - ntpServerStop(); - return; - } + // Shutdown the NTP server when the mode changes or network fails + // The NTP server only works over Ethernet + connected = networkInterfaceHasInternet(NETWORK_ETHERNET); + enabled = EQ_RTK_MODE(ntpServerMode); + if ((enabled == false) && (ntpServerState > NTP_STATE_OFF)) + ntpServerStop(); + else if ((ntpServerState > NTP_STATE_WAIT_NETWORK) && !connected) + ntpServerStop(); // Process the NTP state DMW_st(ntpServerSetState, ntpServerState); @@ -777,156 +826,157 @@ void ntpServerUpdate() case NTP_STATE_OFF: // Determine if the NTP server is enabled - if (EQ_RTK_MODE(ntpServerMode)) + if (enabled) { // The NTP server only works over Ethernet - if (networkInterfaceHasInternet(NETWORK_ETHERNET)) - { - ntpServerSetState(NTP_STATE_NETWORK_CONNECTED); - } + networkConsumerAdd(NETCONSUMER_NTP_SERVER, NETWORK_ETHERNET, __FILE__, __LINE__); + ntpServerSetState(NTP_STATE_WAIT_NETWORK); + } + break; + + case NTP_STATE_WAIT_NETWORK: + // Wait until the internet is accessible + if (connected) + { + networkUserAdd(NETCONSUMER_NTP_SERVER, __FILE__, __LINE__); + ntpServerSetState(NTP_STATE_NETWORK_CONNECTED); } break; case NTP_STATE_NETWORK_CONNECTED: - // Determine if the network has failed - if (networkHasInternet() == false) - // Stop the NTP server, restart it if possible + // Attempt to allocate the UDP object + ntpServer = new NetworkUDP; + if (!ntpServer) + // Insufficient memory to start the NTP server ntpServerStop(); - - // Attempt to start the NTP server else { - ntpServer = new NetworkUDP; - if (!ntpServer) - // Insufficient memory to start the NTP server - ntpServerStop(); - else - { - ntpServer->begin(settings.ethernetNtpPort); // Start the NTP server - online.ethernetNTPServer = true; - if (!inMainMenu) - reportHeapNow(settings.debugNtp); - ntpServerSetState(NTP_STATE_SERVER_RUNNING); - } + // Start the NTP server + ntpServer->begin(settings.ethernetNtpPort); // Start the NTP server + online.ethernetNTPServer = true; + if (!inMainMenu) + reportHeapNow(settings.debugNtp); + ntpServerSetState(NTP_STATE_SERVER_RUNNING); } break; case NTP_STATE_SERVER_RUNNING: - // Determine if the network has failed - if (networkHasInternet() == false) - // Stop the NTP server, restart it if possible - ntpServerStop(); + // Check for new NTP requests - if the time has been sync'd + bool processed = ntpProcessOneRequest(systemState == STATE_NTPSERVER_SYNC, (const timeval *)ðernetNtpTv, + (const timeval *)&gnssSyncTv, ntpDiag, sizeof(ntpDiag)); - else + // Print the diagnostics - if enabled + if ((settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_DATA)) && (strlen(ntpDiag) > 0) && (!inMainMenu)) { - // Check for new NTP requests - if the time has been sync'd - bool processed = ntpProcessOneRequest(systemState == STATE_NTPSERVER_SYNC, (const timeval *)ðernetNtpTv, - (const timeval *)&gnssSyncTv, ntpDiag, sizeof(ntpDiag)); - - // Print the diagnostics - if enabled - if ((settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_DATA)) && (strlen(ntpDiag) > 0) && (!inMainMenu)) - { - PERIODIC_CLEAR(PD_NTP_SERVER_DATA); - systemPrint(ntpDiag); - } + PERIODIC_CLEAR(PD_NTP_SERVER_DATA); + systemPrint(ntpDiag); + } - if (processed) + if (processed) + { + // Log the NTP request to file - if enabled + if (settings.enableNTPFile) { - // Log the NTP request to file - if enabled - if (settings.enableNTPFile) + // Gain access to the SPI controller for the microSD card + if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) { - // Gain access to the SPI controller for the microSD card - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) + markSemaphore(FUNCTION_NTPEVENT); + + // Get the marks file name + char fileName[55]; + bool fileOpen = false; + bool sdCardWasOnline; + int year; + int month; + int day; + + // Get the date + year = rtc.getYear(); + month = rtc.getMonth() + 1; + day = rtc.getDay(); + + // Build the file name + snprintf(fileName, sizeof(fileName), "/NTP_Requests_%04d_%02d_%02d.txt", year, month, day); + + // Try to gain access the SD card + sdCardWasOnline = online.microSD; + if (online.microSD != true) + beginSD(); + + if (online.microSD == true) { - markSemaphore(FUNCTION_NTPEVENT); - - // Get the marks file name - char fileName[55]; - bool fileOpen = false; - bool sdCardWasOnline; - int year; - int month; - int day; - - // Get the date - year = rtc.getYear(); - month = rtc.getMonth() + 1; - day = rtc.getDay(); - - // Build the file name - snprintf(fileName, sizeof(fileName), "/NTP_Requests_%04d_%02d_%02d.txt", year, month, day); - - // Try to gain access the SD card - sdCardWasOnline = online.microSD; - if (online.microSD != true) - beginSD(); - - if (online.microSD == true) - { - // Check if the NTP file already exists - bool ntpFileExists = false; - ntpFileExists = sd->exists(fileName); + // Check if the NTP file already exists + bool ntpFileExists = false; + ntpFileExists = sd->exists(fileName); - // Open the NTP file - SdFile ntpFile; + // Open the NTP file + SdFile ntpFile; - if (ntpFileExists) + if (ntpFileExists) + { + if (ntpFile && ntpFile.open(fileName, O_APPEND | O_WRITE)) { - if (ntpFile && ntpFile.open(fileName, O_APPEND | O_WRITE)) - { - fileOpen = true; - sdUpdateFileCreateTimestamp(&ntpFile); - } + fileOpen = true; + sdUpdateFileCreateTimestamp(&ntpFile); } - else + } + else + { + if (ntpFile && ntpFile.open(fileName, O_CREAT | O_WRITE)) { - if (ntpFile && ntpFile.open(fileName, O_CREAT | O_WRITE)) - { - fileOpen = true; - sdUpdateFileAccessTimestamp(&ntpFile); + fileOpen = true; + sdUpdateFileAccessTimestamp(&ntpFile); - // If you want to add a file header, do it here - } + // If you want to add a file header, do it here } + } - if (fileOpen) - { - // Write the NTP request to the file - ntpFile.write((const uint8_t *)ntpDiag, strlen(ntpDiag)); - - // Update the file to create time & date - sdUpdateFileCreateTimestamp(&ntpFile); + if (fileOpen) + { + // Write the NTP request to the file + ntpFile.write((const uint8_t *)ntpDiag, strlen(ntpDiag)); - // Close the mark file - ntpFile.close(); - } + // Update the file to create time & date + sdUpdateFileCreateTimestamp(&ntpFile); - // Dismount the SD card - if (!sdCardWasOnline) - endSD(true, false); + // Close the mark file + ntpFile.close(); } - // Done with the SPI controller - xSemaphoreGive(sdCardSemaphore); + // Dismount the SD card + if (!sdCardWasOnline) + endSD(true, false); + } - lastLoggedNTPRequest = millis(); - ntpLogIncreasing = true; - } // End sdCardSemaphore - } - } + // Done with the SPI controller + xSemaphoreGive(sdCardSemaphore); - if (millis() > (lastLoggedNTPRequest + 5000)) - ntpLogIncreasing = false; + lastLoggedNTPRequest = millis(); + ntpLogIncreasing = true; + } // End sdCardSemaphore + } } + + if (millis() > (lastLoggedNTPRequest + 5000)) + ntpLogIncreasing = false; break; } // Periodically display the NTP server state if (PERIODIC_DISPLAY(PD_NTP_SERVER_STATE)) - ntpServerSetState(ntpServerState); + { + const char * line = ""; + if (NEQ_RTK_MODE(ntpServerMode)) + line = ", Wrong mode!"; + systemPrintf("NTP state: %s%s\r\n", + ntpServerStateName[ntpServerState], line); + PERIODIC_CLEAR(PD_NTP_SERVER_STATE); + } } +//---------------------------------------- // Verify the NTP tables +//---------------------------------------- void ntpValidateTables() { if (ntpServerStateNameEntries != NTP_STATE_MAX) diff --git a/Firmware/RTK_Everywhere/NVM.ino b/Firmware/RTK_Everywhere/NVM.ino index 4ed0e052..be19eb15 100644 --- a/Firmware/RTK_Everywhere/NVM.ino +++ b/Firmware/RTK_Everywhere/NVM.ino @@ -653,7 +653,7 @@ void recordSystemSettingsToFile(File *settingsFile) // Below are things not part of settings.h char firmwareVersion[30]; // v1.3 December 31 2021 - getFirmwareVersion(firmwareVersion, sizeof(firmwareVersion), true); + firmwareVersionGet(firmwareVersion, sizeof(firmwareVersion), true); settingsFile->printf("%s=%s\r\n", "rtkFirmwareVersion", firmwareVersion); settingsFile->printf("%s=%s\r\n", "gnssFirmwareVersion", gnssFirmwareVersion); diff --git a/Firmware/RTK_Everywhere/Network.ino b/Firmware/RTK_Everywhere/Network.ino index b3f163e1..d7a00512 100644 --- a/Firmware/RTK_Everywhere/Network.ino +++ b/Firmware/RTK_Everywhere/Network.ino @@ -99,21 +99,64 @@ Network.ino #ifdef COMPILE_NETWORK +//---------------------------------------- +// Constants +//---------------------------------------- + +static const char * networkConsumerTable[] = +{ + "HTTP_CLIENT", + "NTP_SERVER", + "NTRIP_CLIENT", + "NTRIP_SERVER_0", + "NTRIP_SERVER_1", + "NTRIP_SERVER_2", + "NTRIP_SERVER_3", + "OTA_CLIENT", + "PPL_KEY_UPDATE", + "PPL_MQTT_CLIENT", + "TCP_CLIENT", + "TCP_SERVER", + "UDP_SERVER", + "WEB_CONFIG", +}; + +static const int networkConsumerTableEntries = sizeof(networkConsumerTable) / sizeof(networkConsumerTable[0]); + //---------------------------------------- // Locals //---------------------------------------- +NETCONSUMER_MASK_t netIfConsumers[NETWORK_MAX]; // Consumers of a specific network +NETCONSUMER_MASK_t networkConsumersAny; // Consumers of any network +NETCONSUMER_MASK_t networkSoftApConsumer; // Consumers of soft AP + +// Priority of the network when last checked by the consumer +// Index by consumer ID +NetPriority_t networkConsumerPriority[NETCONSUMER_MAX]; +NetPriority_t networkConsumerPriorityLast[NETCONSUMER_MAX]; + +// Index of the network interface +// Index by consumer ID +NetIndex_t networkConsumerIndexLast[NETCONSUMER_MAX]; + +// Users of the network +// Index by network index +NETCONSUMER_MASK_t netIfUsers[NETWORK_MAX]; // Users of a specific network + // Priority of each of the networks in the networkInterfaceTable // Index by networkInterfaceTable index to get network interface priority -NetPriority_t networkPriorityTable[NETWORK_OFFLINE]; +NetPriority_t networkPriorityTable[NETWORK_OFFLINE + 1]; // Index by priority to get the networkInterfaceTable index -NetIndex_t networkIndexTable[NETWORK_OFFLINE]; +NetIndex_t networkIndexTable[NETWORK_OFFLINE + 1]; // Priority of the default network interface NetPriority_t networkPriority = NETWORK_OFFLINE; // Index into networkPriorityTable // Interface event handlers set these flags, networkUpdate performs action +bool networkEventInternetAvailable[NETWORK_OFFLINE]; +bool networkEventInternetLost[NETWORK_OFFLINE]; bool networkEventStop[NETWORK_OFFLINE]; // The following entries have one bit per interface @@ -129,6 +172,7 @@ NetMask_t networkStarted; // Track the running networks // Active network sequence, may be nullptr NETWORK_POLL_SEQUENCE *networkSequence[NETWORK_OFFLINE]; +NetMask_t networkMdnsRequests; // Non-zero when one or more interfaces request mDNS NetMask_t networkMdnsRunning; // Non-zero when mDNS is running //---------------------------------------- @@ -261,6 +305,7 @@ void menuTcpUdp() else if (incoming == 'm') { settings.mdnsEnable ^= 1; + networkMulticastDNSUpdate(); } else if (settings.mdnsEnable && (incoming == 'n')) @@ -293,29 +338,425 @@ void networkBegin() // Set the network priority values // Normally these would come from settings - for (int index = 0; index < NETWORK_OFFLINE; index++) + for (int index = 0; index <= NETWORK_OFFLINE; index++) networkPriorityTable[index] = index; // Set the network index values based upon the priorities - for (int index = 0; index < NETWORK_OFFLINE; index++) + for (int index = 0; index <= NETWORK_OFFLINE; index++) networkIndexTable[networkPriorityTable[index]] = index; + // Set the network consumer priorities + for (int index = 0; index < NETCONSUMER_MAX; index++) + { + networkConsumerPriority[index] = NETWORK_OFFLINE; + networkConsumerPriorityLast[index] = NETWORK_OFFLINE; + } + // Handle the network events Network.onEvent(networkEvent); #ifdef COMPILE_ETHERNET // Start Ethernet if (present.ethernet_ws5500) + { + networkStart(NETWORK_ETHERNET, settings.enablePrintEthernetDiag, __FILE__, __LINE__); ethernetStart(); + if (settings.debugNetworkLayer) + networkDisplayStatus(); + } #endif // COMPILE_ETHERNET // WiFi and cellular networks are started/stopped as consumers and come online/offline in networkUpdate() } +//---------------------------------------- +// Determine if the network interface has changed +//---------------------------------------- +bool networkChanged(NETCONSUMER_t consumer) +{ + bool changed; + + // Validate the consumer + networkConsumerValidate(consumer); + + // Determine if the network interface has changed + changed = (networkConsumerPriority[consumer] != NETWORK_OFFLINE) + && (networkConsumerPriority[consumer] != networkConsumerPriorityLast[consumer]); + + // Remember the new network + if (changed) + networkConsumerPriorityLast[consumer] = networkConsumerPriority[consumer]; + return changed; +} + +//---------------------------------------- +// Add a network consumer +//---------------------------------------- +void networkConsumerAdd(NETCONSUMER_t consumer, + NetIndex_t network, + const char * fileName, + uint32_t lineNumber) +{ + NETCONSUMER_MASK_t bitMask; + NETCONSUMER_MASK_t * bits; + NETCONSUMER_MASK_t consumers; + NetIndex_t index; + const char * networkName; + NETCONSUMER_MASK_t previousBits; + NetPriority_t priority; + + // Validate the inputs + networkConsumerValidate(consumer); + bits = &networkConsumersAny; + networkName = "NETWORK_ANY"; + if (network != NETWORK_ANY) + { + networkValidateIndex(network); + bits = &netIfConsumers[network]; + networkName = networkInterfaceTable[network].name; + } + + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkConsumerAdd(%s, %s) from %s at line %d\r\n", + networkConsumerTable[consumer], networkName, fileName, lineNumber); + + // Add this consumer only once + previousBits = *bits; + consumers = networkConsumersAny | previousBits; + bitMask = 1 << consumer; + if ((previousBits & bitMask) == 0) + { + // Display the consumer + if (settings.debugNetworkLayer) + { + networkDisplayMode(); + systemPrintf("Network: Adding consumer %s\r\n", networkConsumerTable[consumer]); + } + + // Account for this consumer + *bits |= bitMask; + networkConsumerPriority[consumer] = NETWORK_OFFLINE; + networkConsumerPriorityLast[consumer] = NETWORK_OFFLINE; + + // Display the network consumers + if (settings.debugNetworkLayer) + networkConsumerDisplay(); + + // Start the networks if necessary + if (previousBits == 0) + { + // Walk the list of priorities + for (priority = 0; priority < NETWORK_MAX; priority += 1) + { + // Translate the priority into an interface index + index = networkIndexTable[priority]; + + // Verify that this interface is available on this system + if (networkIsPresent(index)) + { + // Determine if this is a supported network + if ((network == NETWORK_ANY) || (network == index)) + { + // Determine if this interface currently has internet access + if (networkInterfaceHasInternet(index)) + break; + + // A network without a start script should already have + // internet access, try the next network + if (networkInterfaceTable[index].start == nullptr) + continue; + + // Display the consumer + if (settings.debugNetworkLayer) + systemPrintf("Network: Starting the %s network\r\n", networkInterfaceTable[index].name); + + // Determine if the network has started + if (networkIsStarted(index) == false) + // Attempt to start the highest priority network + // The network layer will start the lower priority networks + networkStart(index, settings.debugNetworkLayer, __FILE__, __LINE__); + break; + } + } + } + if (settings.debugNetworkLayer) + networkDisplayStatus(); + } + } + else + { + systemPrintf("Network consumer %s added more than once\r\n", + networkConsumerTable[consumer]); + reportFatalError("Network consumer added more than once!"); + } +} + +//---------------------------------------- +// Get the bit mask of network consumers +//---------------------------------------- +NETCONSUMER_MASK_t networkConsumerBits(NetIndex_t index) +{ + networkValidateIndex(index); + return networkConsumersAny | netIfConsumers[index]; +} + +//---------------------------------------- +// Count the network consumer bits +//---------------------------------------- +int networkConsumerCount(NETCONSUMER_MASK_t bits) +{ + int bitCount; + NETCONSUMER_MASK_t bitMask; + int bitNumber; + int totalBits; + + // Determine the number of bits + totalBits = sizeof(bits) << 3; + + // Count the bits + bitCount = 0; + for (bitNumber = 0; bitNumber < totalBits; bitNumber++) + { + bitMask = 1 << bitNumber; + if (bits & bitMask) + bitCount += 1; + } + return bitCount; +} + +//---------------------------------------- +// Display a network consumer +//---------------------------------------- +void networkConsumerDisplay() +{ + NETCONSUMER_MASK_t bits; + NetIndex_t index; + NETCONSUMER_MASK_t users; + + // Determine the consumer count + bits = networkConsumersAny; + users = 0; + if (networkPriority < NETWORK_OFFLINE) + { + index = networkIndexTable[networkPriority]; + bits |= netIfConsumers[index]; + users = netIfUsers[index]; + } + systemPrintf("Network Consumers: %d", networkConsumerCount(bits)); + networkConsumerPrint(bits, users, ", "); + systemPrintln(); + + // Walk the networks in priority order + for (NetPriority_t priority = 0; priority < NETWORK_MAX; priority++) + networkPrintStatus(priority); + + // Display the soft AP consumers + wifiDisplaySoftApStatus(); +} + +//---------------------------------------- +// Determine if the specified consumer has access to the internet +//---------------------------------------- +bool networkConsumerIsConnected(NETCONSUMER_t consumer) +{ + int index; + + // Validate the consumer + networkConsumerValidate(consumer); + + // If the client is using the highest priority network and that + // network is still available then continue as normal + if (networkHasInternet_bm && (networkConsumerPriority[consumer] == networkPriority)) + { + index = networkIndexTable[networkPriority]; + return networkInterfaceHasInternet(index); + } + + // The network has changed, notify the client of the change + networkConsumerPriority[consumer] = networkPriority; + return false; +} + +//---------------------------------------- +// Mark the consumer as offline +//---------------------------------------- +void networkConsumerOffline(NETCONSUMER_t consumer) +{ + networkUserRemove(consumer, __FILE__, __LINE__); + networkConsumerPriority[consumer] = NETWORK_OFFLINE; +} + +//---------------------------------------- +// Print the list of consumers +//---------------------------------------- +void networkConsumerPrint(NETCONSUMER_MASK_t consumers, + NETCONSUMER_MASK_t users, + const char * separation) +{ + NETCONSUMER_MASK_t consumerMask; + + for (int consumer = 0; consumer < NETCONSUMER_MAX; consumer += 1) + { + consumerMask = 1 << consumer; + if (consumers & consumerMask) + { + const char * active = (users & consumerMask) ? "*" : ""; + systemPrintf("%s%s%s", separation, active, networkConsumerTable[consumer]); + separation = ", "; + } + } +} + +//---------------------------------------- +// Notify the consumers that they need to reconnect to the network +//---------------------------------------- +void networkConsumerReconnect(NetIndex_t index) +{ + NETCONSUMER_MASK_t consumers; + + networkValidateIndex(index); + + // Determine the consumers of the network + consumers = netIfConsumers[index]; + if ((networkPriority < NETWORK_OFFLINE) + && (networkIndexTable[networkPriority] == index)) + { + consumers |= networkConsumersAny; + } + + // Notify the consumers to restart + for (int consumer = 0; consumer < NETCONSUMER_MAX; consumer++) + { + // Mark this network offline + if (consumers & (1 << consumer)) + networkConsumerPriority[consumer] = NETWORK_OFFLINE; + } +} + +//---------------------------------------- +// Remove a network consumer +//---------------------------------------- +void networkConsumerRemove(NETCONSUMER_t consumer, + NetIndex_t network, + const char * fileName, + uint32_t lineNumber) +{ + NETCONSUMER_MASK_t bitMask; + NETCONSUMER_MASK_t * bits; + NETCONSUMER_MASK_t consumers; + NetIndex_t index; + const char * networkName; + NETCONSUMER_MASK_t previousBits; + int priority; + + // Validate the inputs + networkConsumerValidate(consumer); + bits = &networkConsumersAny; + networkName = "NETWORK_ANY"; + if (network != NETWORK_ANY) + { + networkValidateIndex(network); + bits = &netIfConsumers[network]; + networkName = networkInterfaceTable[network].name; + } + + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkConsumerRemove(%s, %s) from %s at line %d\r\n", + networkConsumerTable[consumer], networkName, fileName, lineNumber); + + // Done with the network + networkUserRemove(consumer, __FILE__, __LINE__); + + // Remove the consumer only once + previousBits = *bits; + consumers = networkConsumersAny | previousBits; + bitMask = 1 << consumer; + if (previousBits & bitMask) + { + // Display the consumer + if (settings.debugNetworkLayer) + { + networkDisplayMode(); + systemPrintf("Network: Removing consumer %s\r\n", networkConsumerTable[consumer]); + } + + // Account for this consumer + *bits &= ~bitMask; + + // Display the network consumers + if (settings.debugNetworkLayer) + networkConsumerDisplay(); + + // Stop the networks when the consumer count reaches zero + if (previousBits && (*bits == 0)) + { + // Display the consumer + if (settings.debugNetworkLayer) + systemPrintf("Network: Stopping the networks\r\n"); + + // Walk the networks in increasing priority + // Turn off the lower priority networks first to eliminate failover + for (priority = NETWORK_MAX - 1; priority >= 0; priority -= 1) + { + // Translate the priority into an interface index + index = networkIndexTable[priority]; + + // Verify that this interface is started + if (networkIsStarted(index)) + // Attempt to stop this network interface + networkStop(index, settings.debugNetworkLayer, __FILE__, __LINE__); + } + + // Update the network priority + networkPriority = NETWORK_OFFLINE; + + // Let other tasks handle the network failure + delay(100); + } + } +} + +//---------------------------------------- +// Determine if the current network interface has any consumer +//---------------------------------------- +NETCONSUMER_MASK_t networkConsumers() +{ + NETCONSUMER_MASK_t consumers; + NetIndex_t index; + NetPriority_t priority; + + // Get the network interface index + consumers = 0; + priority = networkPriority; + if (priority != NETWORK_OFFLINE) + { + index = networkIndexTable[priority]; + consumers = networkConsumerBits(index); + } + + // Return the consumers as a bit mask + return consumers; +} + +//---------------------------------------- +// Validate the network consumer +//---------------------------------------- +void networkConsumerValidate(NETCONSUMER_t consumer) +{ + // Validate the consumer + if (consumer >= NETCONSUMER_MAX) + { + systemPrintf("HALTED: Invalid consumer value %d, valid range (0 - %d)!\r\n", consumer, NETCONSUMER_MAX - 1); + reportFatalError("Invalid consumer value!"); + } +} + //---------------------------------------- // Delay for a while //---------------------------------------- -void networkDelay(uint8_t priority, uintptr_t parameter, bool debug) +void networkDelay(NetIndex_t index, uintptr_t parameter, bool debug) { // Get the timer address uint32_t *timer = (uint32_t *)parameter; @@ -324,25 +765,58 @@ void networkDelay(uint8_t priority, uintptr_t parameter, bool debug) if ((int32_t)(millis() - *timer) >= 0) { // Timer has expired - networkSequenceNextEntry(priority, debug); + networkSequenceNextEntry(index, debug); } } //---------------------------------------- -// Display the Ethernet data +// Display the network data //---------------------------------------- void networkDisplayInterface(NetIndex_t index) { const NETWORK_TABLE_ENTRY *entry; - bool hasIP; - const char *hostName; - NetworkInterface *netif; - const char *status; // Verify the index into the networkInterfaceTable networkValidateIndex(index); entry = &networkInterfaceTable[index]; - netif = entry->netif; + networkDisplayNetworkData(entry->name, entry->netif); +} + +//---------------------------------------- +// Display the mode +//---------------------------------------- +void networkDisplayMode() +{ + uint32_t mode; + + if (rtkMode == 0) + { + systemPrintf("rtkMode: 0 (Not specified)\r\n"); + return; + } + + // Display the mode name + for (mode = 0; mode < RTK_MODE_MAX; mode++) + { + if ((1 << mode) == rtkMode) + { + systemPrintf("rtkMode: 0x%02x (%s)\r\n", rtkMode, rtkModeName[mode]); + return; + } + } + + // Illegal mode value + systemPrintf("rtkMode: 0x%02x (Illegal value)\r\n", rtkMode); +} + +//---------------------------------------- +// Display the network data +//---------------------------------------- +void networkDisplayNetworkData(const char *name, NetworkInterface *netif) +{ + bool hasIP; + const char *hostName; + const char *status; hasIP = false; status = "Off"; @@ -357,7 +831,7 @@ void networkDisplayInterface(NetIndex_t index) status = "Online"; } } - systemPrintf("%s: %s%s\r\n", entry->name, status, netif->isDefault() ? ", default" : ""); + systemPrintf("%s: %s%s\r\n", name, status, netif->isDefault() ? ", default" : ""); hostName = netif->getHostname(); if (hostName) systemPrintf(" Host Name: %s\r\n", hostName); @@ -394,10 +868,16 @@ void networkDisplayStatus() for (NetPriority_t priority = 0; priority < NETWORK_OFFLINE; priority++) networkPrintStatus(networkIndexTable[priority]); + // Display the soft AP consumers + wifiDisplaySoftApStatus(); + // Display the interfaces details for (NetIndex_t index = 0; index < NETWORK_OFFLINE; index++) if (networkIsPresent(index)) networkDisplayInterface(index); + + // Display the soft AP details + wifiDisplayNetworkData(); } //---------------------------------------- @@ -453,7 +933,7 @@ void networkEvent(arduino_event_id_t event, arduino_event_info_t info) case ARDUINO_EVENT_WIFI_STA_GOT_IP: case ARDUINO_EVENT_WIFI_STA_GOT_IP6: case ARDUINO_EVENT_WIFI_STA_LOST_IP: - wifiEvent(event, info); + wifi.eventHandler(event, info); break; #endif // COMPILE_WIFI } @@ -469,16 +949,25 @@ IPAddress networkGetBroadcastIpAddress() // Get the networkInterfaceTable index index = networkPriority; + ip = IPAddress(255, 255, 255, 255); if (index < NETWORK_OFFLINE) { index = networkIndexTable[index]; // Return the local network broadcast IP address - return networkInterfaceTable[index].netif->broadcastIP(); + ip = networkInterfaceTable[index].netif->broadcastIP(); } // Return the broadcast address - return IPAddress(255, 255, 255, 255); + return ip; +} + +//---------------------------------------- +// Get the current interface index +//---------------------------------------- +NetIndex_t networkGetCurrentInterfaceIndex() +{ + return networkIndexTable[networkPriority]; } //---------------------------------------- @@ -520,7 +1009,7 @@ IPAddress networkGetIpAddress() IPAddress ip; // NETIF doesn't capture the IP address of a soft AP - if (WIFI_SOFT_AP_RUNNING() == true && wifiStationIsRunning() == false) + if (wifiSoftApRunning == true && wifiStationRunning == false) return WiFi.softAPIP(); // Get the networkInterfaceTable index @@ -543,21 +1032,21 @@ IPAddress networkGetIpAddress() //---------------------------------------- const uint8_t *networkGetMacAddress() { - static const uint8_t zero[6] = {0, 0, 0, 0, 0, 0}; - #ifdef COMPILE_BT if (bluetoothGetState() != BT_OFF) return btMACAddress; #endif // COMPILE_BT #ifdef COMPILE_WIFI - if (networkInterfaceHasInternet(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) return wifiMACAddress; #endif // COMPILE_WIFI #ifdef COMPILE_ETHERNET if (networkInterfaceHasInternet(NETWORK_ETHERNET)) return ethernetMACAddress; #endif // COMPILE_ETHERNET - return zero; + + // Use Bluetooth MAC address when nothing else is available + return btMACAddress; } //---------------------------------------- @@ -578,19 +1067,75 @@ const char *networkGetNameByPriority(NetPriority_t priority) if (priority < NETWORK_OFFLINE) { // Translate the priority into an index - NetIndex_t index = networkPriorityTable[priority]; + NetIndex_t index = networkIndexTable[priority]; return networkGetNameByIndex(index); } return "None"; } //---------------------------------------- -// Determine if any network interface has access to the internet +// Get the current network priority +//---------------------------------------- +NetPriority_t networkGetPriority() +{ + return networkPriority; +} + +//---------------------------------------- +// Determine if the current network interface has access to the internet //---------------------------------------- bool networkHasInternet() { + bool internetAccessible; + NetIndex_t index; + NetPriority_t priority; + + // Does any interface have access to the internet + internetAccessible = false; + priority = networkPriority; + if (priority != NETWORK_OFFLINE) + { + index = networkIndexTable[priority]; + internetAccessible = networkInterfaceHasInternet(index); + } + // Return the network state - return networkHasInternet_bm ? true : false; + return internetAccessible; +} + +//---------------------------------------- +// Internet available event +//---------------------------------------- +void networkInterfaceEventInternetAvailable(NetIndex_t index) +{ + // Validate the index + networkValidateIndex(index); + + // Notify networkUpdate of the change in state + if (settings.debugNetworkLayer) + systemPrintf("%s internet available event\r\n", networkInterfaceTable[index].name); + networkEventInternetAvailable[index] = true; +} + +//---------------------------------------- +// Internet lost event +//---------------------------------------- +void networkInterfaceEventInternetLost(NetIndex_t index, + const char * fileName, + uint32_t lineNumber) +{ + // Validate the index + networkValidateIndex(index); + + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkInterfaceEventInternetLost(%s) from %s at line %d\r\n", + networkInterfaceTable[index].name, fileName, lineNumber); + + // Notify networkUpdate of the change in state + if (settings.debugNetworkLayer) + systemPrintf("%s lost internet access event\r\n", networkInterfaceTable[index].name); + networkEventInternetLost[index] = true; } //---------------------------------------- @@ -598,6 +1143,12 @@ bool networkHasInternet() //---------------------------------------- void networkInterfaceEventStop(NetIndex_t index) { + // Validate the index + networkValidateIndex(index); + + // Notify networkUpdate of the change in state + if (settings.debugNetworkLayer) + systemPrintf("%s stop event\r\n", networkInterfaceTable[index].name); networkEventStop[index] = true; } @@ -607,103 +1158,44 @@ void networkInterfaceEventStop(NetIndex_t index) bool networkInterfaceHasInternet(NetIndex_t index) { // Validate the index - networkValidateIndex(index); + if (index >= NETWORK_OFFLINE) + return false; - // Return the network interface state + // Determine if the interface has access to the internet return (networkHasInternet_bm & (1 << index)) ? true : false; } //---------------------------------------- -// Mark network interface as having NO access to the internet +// Mark network interface as having access to the internet //---------------------------------------- -void networkInterfaceEventInternetLost(NetIndex_t index) +void networkInterfaceInternetConnectionAvailable(NetIndex_t index) { NetMask_t bitMask; + NetIndex_t previousIndex; NetPriority_t previousPriority; NetPriority_t priority; // Validate the index networkValidateIndex(index); - // Check for network offline - bitMask = 1 << index; - if (!(networkHasInternet_bm & bitMask)) - // Already offline, nothing to do - return; - - // Mark this network as offline - networkHasInternet_bm &= ~bitMask; - - // Disable mDNS if necessary - networkMulticastDNSStop(index); - - // Display offline message - if (settings.debugNetworkLayer) - systemPrintf("--------------- %s Offline ---------------\r\n", networkGetNameByIndex(index)); - - // Did the highest priority network just fail? - if (networkPriorityTable[index] == networkPriority) - { - // The highest priority network just failed - // Leave this network on in hopes that it will regain a connection - previousPriority = networkPriority; - - // Search in descending priority order for the next online network - priority = networkPriorityTable[index]; - for (priority += 1; priority < NETWORK_OFFLINE; priority += 1) - { - // Is the network online? - index = networkIndexTable[priority]; - bitMask = 1 << index; - if (networkHasInternet_bm & bitMask) - { - // Successfully found an online network - networkMulticastDNSStart(index); - break; - } - - // No, is this device present (nullptr: always present) - if (networkIsPresent(index)) - { - // No, does this network need starting - networkStart(index, settings.debugNetworkLayer); - } - } - - // Set the new network priority - networkPriority = priority; - if (priority < NETWORK_OFFLINE) - Network.setDefaultInterface(*networkInterfaceTable[index].netif); - - // Display the transition - if (settings.debugNetworkLayer) - systemPrintf("Default Network Interface: %s --> %s\r\n", networkGetNameByPriority(previousPriority), - networkGetNameByPriority(priority)); - } -} - -//---------------------------------------- -// Mark network interface as having access to the internet -//---------------------------------------- -void networkInterfaceEventInternetAvailable(NetIndex_t index) -{ - NetMask_t bitMask; - NetIndex_t previousIndex; - NetPriority_t previousPriority; - NetPriority_t priority; - - // Validate the index - networkValidateIndex(index); + // Clear the event flag + networkEventInternetAvailable[index] = false; // Check for network online - bitMask = 1 << index; previousIndex = index; - if (networkHasInternet_bm & bitMask) + if (networkInterfaceHasInternet(index)) + { // Already online, nothing to do + if (settings.debugNetworkLayer) + systemPrintf("%s already has internet access\r\n", networkInterfaceTable[index].name); return; + } // Mark this network as online + bitMask = 1 << index; networkHasInternet_bm |= bitMask; + if (settings.debugNetworkLayer) + systemPrintf("%s has internet access\r\n", networkInterfaceTable[index].name); // Raise the network priority if necessary previousPriority = networkPriority; @@ -744,9 +1236,13 @@ void networkInterfaceEventInternetAvailable(NetIndex_t index) { // Stop the previous network systemPrintf("Stopping %s\r\n", networkGetNameByIndex(index)); - networkSequenceStop(index, settings.debugNetworkLayer); + networkSequenceStop(index, settings.debugNetworkLayer, __FILE__, __LINE__); } } + + // Display the interface status + if (settings.debugNetworkLayer) + networkDisplayStatus(); } // Only start mDNS on the highest priority network @@ -754,6 +1250,126 @@ void networkInterfaceEventInternetAvailable(NetIndex_t index) networkMulticastDNSStart(previousIndex); } +//---------------------------------------- +// Mark network interface as having NO access to the internet +//---------------------------------------- +void networkInterfaceInternetConnectionLost(NetIndex_t index) +{ + NetMask_t bitMask; + NetPriority_t previousPriority; + NetPriority_t priority; + + // Validate the index + networkValidateIndex(index); + + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkInterfaceInternetConnectionLost(%s)\r\n", + networkInterfaceTable[index].name); + + // Clear the event flag + networkEventInternetLost[index] = false; + + // Check for network offline + if (networkInterfaceHasInternet(index) == false) + { + if (settings.debugNetworkLayer) + systemPrintf("%s previously lost internet access\r\n", networkInterfaceTable[index].name); + // Already offline, nothing to do + return; + } + + // Mark this network as offline + bitMask = 1 << index; + networkHasInternet_bm &= ~bitMask; + if (settings.debugNetworkLayer) + systemPrintf("%s does NOT have internet access\r\n", networkInterfaceTable[index].name); + + // Disable mDNS if necessary + networkMulticastDNSStop(index); + + // Display offline message + if (settings.debugNetworkLayer) + { + networkDisplayStatus(); + systemPrintf("--------------- %s Offline ---------------\r\n", networkGetNameByIndex(index)); + } + + // Did the highest priority network just fail? + if (networkPriorityTable[index] == networkPriority) + { + // The highest priority network just failed + if (settings.debugNetworkLayer) + systemPrintf("Network: Looking for another interface to use\r\n"); + + // Leave this network on in hopes that it will regain a connection + previousPriority = networkPriority; + + // Search in descending priority order for the next online network + priority = networkPriorityTable[index]; + for (priority += 1; priority < NETWORK_OFFLINE; priority += 1) + { + // Is the interface online? + index = networkIndexTable[priority]; + if (networkIsPresent(index)) + { + if (networkInterfaceHasInternet(index)) + { + // Successfully found an online network + if (settings.debugNetworkLayer) + systemPrintf("Network: Found interface %s\r\n", networkInterfaceTable[index].name); + break; + } + + // Interface not connected to the internet + // Start this interface + networkStart(index, settings.debugNetworkLayer, __FILE__, __LINE__); + } + } + + // Set the new network priority + networkPriority = priority; + if (priority < NETWORK_OFFLINE) + { + Network.setDefaultInterface(*networkInterfaceTable[index].netif); + + // Start mDNS if this interface is connected to the internet + if (networkInterfaceHasInternet(index)) + networkMulticastDNSStart(index); + } + + // Display the transition + if (settings.debugNetworkLayer) + { + systemPrintf("Default Network Interface: %s --> %s\r\n", networkGetNameByPriority(previousPriority), + networkGetNameByPriority(priority)); + networkDisplayStatus(); + } + } +} + +//---------------------------------------- +// Determine if the specified network interface is higher priority than +// the current network interface +//---------------------------------------- +bool networkIsHighestPriority(NetIndex_t index) +{ + NetPriority_t priority; + bool higherPriority; + + // Validate the index + networkValidateIndex(index); + + // Get the current network priority + priority = networkPriority; + + // Determine if the specified interface has higher priority + higherPriority = (priority == NETWORK_OFFLINE); + if (!higherPriority) + higherPriority = (priority > networkPriorityTable[index]); + return higherPriority; +} + //---------------------------------------- // Determine if the network interface has completed its start routine //---------------------------------------- @@ -779,17 +1395,15 @@ bool networkIsPresent(NetIndex_t index) } //---------------------------------------- -// Change multicast DNS to a given network +// Determine if the network is started //---------------------------------------- -void networkMulticastDNSSwitch(NetIndex_t startIndex) +bool networkIsStarted(NetIndex_t index) { - // Stop mDNS on the other networks - for (int index = 0; index < NETWORK_OFFLINE; index++) - if (index != startIndex) - networkMulticastDNSStop(index); + // Validate the index + networkValidateIndex(index); - // Start mDNS on the requested network - networkMulticastDNSStart(startIndex); // Start DNS on the selected network, either WiFi or Ethernet + // Determine if the interface is started + return ((networkSeqStarting | networkStarted) & (1 << index)); } //---------------------------------------- @@ -797,64 +1411,114 @@ void networkMulticastDNSSwitch(NetIndex_t startIndex) //---------------------------------------- bool networkMulticastDNSStart(NetIndex_t index) { - NetMask_t bitMask; - bool started; + bool mdnsStarted; - // Start mDNS if it is enabled and not running on this network - started = false; - bitMask = 1 << index; - if (settings.mdnsEnable && (!(networkMdnsRunning & bitMask)) && (mDNSUse & bitMask)) + // Verify that this interface uses mDNS + mdnsStarted = false; + if (networkInterfaceTable[index].mDNS) { - if (MDNS.begin(&settings.mdnsHostName[0]) == - false) // This should make the device findable from 'rtk.local' in a browser - systemPrintln("Error setting up MDNS responder!"); - else - { - MDNS.addService("http", "tcp", settings.httpPort); // Add service to MDNS - networkMdnsRunning |= bitMask; - started = true; + // Request mDNS for this interface + NetMask_t bitMask = 1 << index; + networkMdnsRequests |= bitMask; - if (settings.debugNetworkLayer) - systemPrintf("mDNS started as %s.local\r\n", settings.mdnsHostName); - } + // Start mDNS on this interface + mdnsStarted = networkMulticastDNSUpdate(); } - return started; + return mdnsStarted; } //---------------------------------------- // Stop multicast DNS //---------------------------------------- -void networkMulticastDNSStop(NetIndex_t index) +bool networkMulticastDNSStop(NetIndex_t index) { - // Stop mDNS if it is running on this network - NetMask_t bitMask = 1 << index; - if (settings.mdnsEnable && (networkMdnsRunning & bitMask)) + bool mdnsStopped; + + // Verify that this interface uses mDNS + mdnsStopped = false; + if (networkInterfaceTable[index].mDNS) { - MDNS.end(); - networkMdnsRunning &= ~bitMask; - if (settings.debugNetworkLayer) - systemPrintln("mDNS stopped"); + // Request mDNS stop on this interface + NetMask_t bitMask = 1 << index; + networkMdnsRequests &= ~bitMask; + + // Stop mDNS on this interface + mdnsStopped = networkMulticastDNSUpdate(); } + return mdnsStopped; } //---------------------------------------- // Stop multicast DNS //---------------------------------------- -void networkMulticastDNSStop() +bool networkMulticastDNSStop() { // Determine the highest priority network NetIndex_t startIndex = networkPriority; if (startIndex < NETWORK_OFFLINE) + // Convert the priority into an index startIndex = networkIndexTable[startIndex]; // Stop mDNS on the other networks for (int index = 0; index < NETWORK_OFFLINE; index++) if (index != startIndex) - networkMulticastDNSStop(index); + networkMdnsRequests &= ~(1 << index); // Restart mDNS on the highest priority network - if (startIndex < NETWORK_OFFLINE) - networkMulticastDNSStart(startIndex); + if ((startIndex < NETWORK_OFFLINE) && networkInterfaceTable[startIndex].mDNS) + networkMdnsRequests |= 1 << startIndex; + return networkMulticastDNSUpdate(); +} + +//---------------------------------------- +// Start multicast DNS +//---------------------------------------- +bool networkMulticastDNSUpdate() +{ + NetMask_t deltaMask; + NetMask_t requests; + bool status; + + // Determine if mDNS needs to restart + status = true; + requests = networkMdnsRequests; + + // Update the mDNS state + if (settings.mdnsEnable == false) + requests = 0; + + // Determine if mDNS needs to restart + deltaMask = requests ^ networkMdnsRunning; + if (deltaMask) + { + // Stop mDNS if it is running + if (networkMdnsRunning) + { + MDNS.end(); + if (settings.debugNetworkLayer) + systemPrintln("mDNS stopped"); + } + + // Restart mDNS if it is needed by any interface + if (deltaMask & requests) + { + // This should make the device findable from 'rtk.local' in a browser + if (MDNS.begin(&settings.mdnsHostName[0]) == false) + { + systemPrintln("Error setting up MDNS responder!"); + requests = 0; + status = false; + } + else + { + if (settings.debugNetworkLayer) + systemPrintf("mDNS started as %s.local\r\n", settings.mdnsHostName); + MDNS.addService("http", "tcp", settings.httpPort); // Add service to MDNS + } + } + networkMdnsRunning = requests; + } + return status; } //---------------------------------------- @@ -863,7 +1527,9 @@ void networkMulticastDNSStop() void networkPrintStatus(uint8_t priority) { NetMask_t bitMask; - char highestPriority; + NETCONSUMER_MASK_t consumerMask; + NETCONSUMER_t consumers; + bool highestPriority; int index; const char *name; const char *status; @@ -871,18 +1537,22 @@ void networkPrintStatus(uint8_t priority) // Validate the priority networkValidatePriority(priority); + // Verify that the network exists on this platform + index = networkIndexTable[priority]; + if (networkIsPresent(index) == false) + return; + // Get the network name name = networkGetNameByPriority(priority); // Determine the network status - index = networkIndexTable[priority]; - bitMask = (1 << index); - highestPriority = (networkPriority == priority) ? '*' : ' '; + highestPriority = (networkPriority == priority); status = "Starting"; - if (networkHasInternet_bm & bitMask) + if (networkInterfaceHasInternet(index)) status = "Online"; else if (networkInterfaceTable[index].boot) { + bitMask = (1 << index); if (networkSeqStopping & bitMask) status = "Stopping"; else if (networkStarted & bitMask) @@ -892,8 +1562,25 @@ void networkPrintStatus(uint8_t priority) } // Print the network interface status - if (networkIsPresent(index)) - systemPrintf("%c%d: %-10s %-8s\r\n", highestPriority, priority, name, status); + systemPrintf("%c%d: %-10s %s", + highestPriority ? '*' : ' ', priority, name, status); + + // Display more data about the highest priority network + if (highestPriority) + { + // Display the IP address + if (networkInterfaceHasInternet(index)) + { + IPAddress ipAddress = networkInterfaceTable[index].netif->localIP(); + systemPrintf(", %s", ipAddress.toString().c_str()); + } + + // Display the consumers + consumers = networkConsumersAny | netIfConsumers[index]; + NETCONSUMER_MASK_t users = netIfUsers[index]; + networkConsumerPrint(consumers, users, ", "); + } + systemPrintln(); } //---------------------------------------- @@ -938,10 +1625,36 @@ void networkSequenceBoot(NetIndex_t index) //---------------------------------------- // Exit the sequence by force //---------------------------------------- -void networkSequenceExit(NetIndex_t index, bool debug) +void networkSequenceExit(NetIndex_t index, + bool debug, + const char * fileName, + uint32_t lineNumber) { + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkSequenceExit(%s) from %s at line %d\r\n", + networkInterfaceTable[index].name, fileName, lineNumber); + // Stop the polling for this sequence - networkSequenceStopPolling(index, debug, true); + networkSequenceStopPolling(index, debug, true, __FILE__, __LINE__); +} + +//---------------------------------------- +// Determine if a sequence is running +//---------------------------------------- +bool networkSequenceIsRunning() +{ + // Determine if there are any pending sequences + if (networkSeqStarting || networkSeqStopping) + return true; + + // Determine if there is an active sequence + for (int index = 0; index < NETWORK_MAX; index++) + if (networkSequence[index]) + return true; + + // No sequences are running + return false; } //---------------------------------------- @@ -960,6 +1673,10 @@ void networkSequenceNextEntry(NetIndex_t index, bool debug) // Get the previous sequence entry next = networkSequence[index]; + // Determine if the sequence has already stopped. + if (next == nullptr) + return; + // Set the next sequence entry next += 1; if (next->routine) @@ -975,13 +1692,16 @@ void networkSequenceNextEntry(NetIndex_t index, bool debug) // Termination entry found, stop the sequence or start next sequence else - networkSequenceStopPolling(index, debug, false); + networkSequenceStopPolling(index, debug, false, __FILE__, __LINE__); } //---------------------------------------- // Attempt to start the start sequence //---------------------------------------- -void networkSequenceStart(NetIndex_t index, bool debug) +void networkSequenceStart(NetIndex_t index, + bool debug, + const char * fileName, + uint32_t lineNumber) { NetMask_t bitMask; const char *description; @@ -990,6 +1710,11 @@ void networkSequenceStart(NetIndex_t index, bool debug) // Validate the index networkValidateIndex(index); + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkSequenceStart(%s) from %s at line %d\r\n", + networkInterfaceTable[index].name, fileName, lineNumber); + // Set the network bit bitMask = 1 << index; @@ -1035,6 +1760,10 @@ void networkSequenceStart(NetIndex_t index, bool debug) systemPrintf("--------------- %s Start Sequence Starting ---------------\r\n", networkGetNameByIndex(index)); systemPrintf("%s: Stopped --> Starting\r\n", networkGetNameByIndex(index)); + + // Display the consumers + networkDisplayMode(); + networkConsumerDisplay(); } // Display the description @@ -1056,7 +1785,10 @@ void networkSequenceStart(NetIndex_t index, bool debug) //---------------------------------------- // Start the stop sequence //---------------------------------------- -void networkSequenceStop(NetIndex_t index, bool debug) +void networkSequenceStop(NetIndex_t index, + bool debug, + const char * fileName, + uint32_t lineNumber) { NetMask_t bitMask; const char *description; @@ -1065,6 +1797,11 @@ void networkSequenceStop(NetIndex_t index, bool debug) // Validate the index networkValidateIndex(index); + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkSequenceStop(%s) from %s at line %d\r\n", + networkInterfaceTable[index].name, fileName, lineNumber); + // Set the network bit bitMask = 1 << index; @@ -1109,6 +1846,10 @@ void networkSequenceStop(NetIndex_t index, bool debug) systemPrintf("%s sequencer idle\r\n", networkGetNameByIndex(index)); systemPrintf("--------------- %s Stop Sequence Starting ---------------\r\n", networkGetNameByIndex(index)); systemPrintf("%s: Started --> Stopping\r\n", networkGetNameByIndex(index)); + + // Display the consumers + networkDisplayMode(); + networkConsumerDisplay(); } // Display the description @@ -1130,7 +1871,11 @@ void networkSequenceStop(NetIndex_t index, bool debug) //---------------------------------------- // Stop the polling sequence //---------------------------------------- -void networkSequenceStopPolling(NetIndex_t index, bool debug, bool forcedStop) +void networkSequenceStopPolling(NetIndex_t index, + bool debug, + bool forcedStop, + const char * fileName, + uint32_t lineNumber) { NetMask_t bitMask; bool start; @@ -1138,6 +1883,15 @@ void networkSequenceStopPolling(NetIndex_t index, bool debug, bool forcedStop) // Validate the index networkValidateIndex(index); + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkSequenceStopPolling(%s, debug: %s, forcedStop: %s) from %s at line %d\r\n", + networkInterfaceTable[index].name, + debug ? "true" : "false", + forcedStop ? "true" : "false", + fileName, + lineNumber); + // Stop the polling for this sequence networkSequence[index] = nullptr; bitMask = 1 << index; @@ -1173,6 +1927,10 @@ void networkSequenceStopPolling(NetIndex_t index, bool debug, bool forcedStop) systemPrintf("--------------- %s %s Sequence Stopping ---------------\r\n", networkGetNameByIndex(index), sequenceName); systemPrintf("%s sequencer idle\r\n", networkGetNameByIndex(index)); + + // Display the consumers + networkDisplayMode(); + networkConsumerDisplay(); } // Clear the status bits @@ -1197,52 +1955,260 @@ void networkSequenceStopPolling(NetIndex_t index, bool debug, bool forcedStop) // Start the next sequence if (start) - networkSequenceStart(index, debug); + networkSequenceStart(index, debug, __FILE__, __LINE__); else - networkSequenceStop(index, debug); + networkSequenceStop(index, debug, __FILE__, __LINE__); } } //---------------------------------------- -// Start a network interface +// Add a soft AP consumer //---------------------------------------- -void networkStart(NetIndex_t index, bool debug) +void networkSoftApConsumerAdd(NETCONSUMER_t consumer, + const char * fileName, + uint32_t lineNumber) { - NetMask_t bitMask; + NETCONSUMER_MASK_t bitMask; + NetIndex_t index; + NETCONSUMER_MASK_t previousBits; - // Validate the index - networkValidateIndex(index); + // Validate the inputs + networkConsumerValidate(consumer); - // Only start networks that exist on the platform - if (networkIsPresent(index)) + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkSoftApConsumerAdd from %s at line %d\r\n", + fileName, lineNumber); + + // Add this consumer only once + bitMask = 1 << consumer; + if ((networkSoftApConsumer & bitMask) == 0) { - // Get the network bit - bitMask = (1 << index); + // Display the consumer + if (settings.debugNetworkLayer) + systemPrintf("Network: Adding soft AP consumer %s\r\n", networkConsumerTable[consumer]); - // If a network has a start sequence, and it is not started, start it - if (networkInterfaceTable[index].start && (!(networkStarted & bitMask))) + // Account for this consumer + previousBits = networkSoftApConsumer; + networkSoftApConsumer |= bitMask; + + // Display the network consumers + if (settings.debugNetworkLayer) + networkSoftApConsumerDisplay(); + + // Start the networks if necessary + if (previousBits == 0) { - if (debug) - systemPrintf("Starting network: %s\r\n", networkGetNameByIndex(index)); - networkSequenceStart(index, debug); + wifiSoftApOn(__FILE__, __LINE__); + if (settings.debugNetworkLayer) + networkDisplayStatus(); } } + else + { + systemPrintf("Network: Soft AP consumer %s added more than once!\r\n", + networkConsumerTable[consumer]); + reportFatalError("Network: Soft AP consumer added more than once!"); + } } //---------------------------------------- -// Stop a network interface +// Get the bit mask of network consumers //---------------------------------------- -void networkStop(NetIndex_t index, bool debug) +NETCONSUMER_MASK_t networkSoftApConsumerBits() { - NetMask_t bitMask; + return networkSoftApConsumer; +} - // Validate the index - networkValidateIndex(index); +//---------------------------------------- +// Display the soft AP consumers +//---------------------------------------- +void networkSoftApConsumerDisplay() +{ + systemPrintf("WiFi Soft AP Consumers: %d", networkConsumerCount(networkSoftApConsumer)); + networkConsumerPrint(networkSoftApConsumer, 0, ", "); + systemPrintln(); +} - // Only stop networks that exist on the platform - if (networkIsPresent(index)) +//---------------------------------------- +// Print the soft AP consumers +//---------------------------------------- +void networkSoftApConsumerPrint(const char * separator) +{ + NETCONSUMER_MASK_t consumerMask; + + // Determine if soft AP has any consumers + for (int consumer = 0; consumer < NETCONSUMER_MAX; consumer += 1) { - // Get the network bit + consumerMask = 1 << consumer; + if (networkSoftApConsumer & consumerMask) + { + systemPrintf("%s%s", separator, networkConsumerTable[consumer]); + separator = ", "; + } + } +} + +//---------------------------------------- +// Remove a soft AP consumer +//---------------------------------------- +void networkSoftApConsumerRemove(NETCONSUMER_t consumer, + const char * fileName, + uint32_t lineNumber) +{ + NETCONSUMER_MASK_t bitMask; + NetIndex_t index; + NETCONSUMER_MASK_t previousBits; + + // Validate the inputs + networkConsumerValidate(consumer); + + // Display the call + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkSoftApConsumerRemove from %s at line %d\r\n", + fileName, lineNumber); + + // Remove the consumer only once + bitMask = 1 << consumer; + if (networkSoftApConsumer & bitMask) + { + // Display the consumer + if (settings.debugNetworkLayer) + systemPrintf("Network: Removing soft AP consumer %s\r\n", networkConsumerTable[consumer]); + + // Account for this consumer + previousBits = networkSoftApConsumer; + networkSoftApConsumer &= ~bitMask; + + // Display the network consumers + if (settings.debugNetworkLayer) + networkSoftApConsumerDisplay(); + + // Stop the networks when the consumer count reaches zero + if (previousBits && (networkSoftApConsumer == 0)) + { + // Display the soft AP shutdown message + if (settings.debugNetworkLayer) + systemPrintf("Network: Stopping the soft AP\r\n"); + + // Turn off the soft AP + wifiSoftApOff(__FILE__, __LINE__); + + // Let other tasks handle the network failure + delay(100); + } + } +} + +//---------------------------------------- +// Start a network interface +//---------------------------------------- +void networkStart(NetIndex_t index, + bool debug, + const char * fileName, + uint32_t lineNumber) +{ + NETCONSUMER_MASK_t consumers; + + // Validate the index + networkValidateIndex(index); + + // Display the call + if (settings.debugNetworkLayer) + { + networkDisplayStatus(); + systemPrintf("Network: Calling networkStart(%s) from %s at line %d\r\n", + networkInterfaceTable[index].name, fileName, lineNumber); + } + + // Only start networks that exist on the platform + consumers = networkConsumersAny; + if (index < NETWORK_OFFLINE) + consumers |= netIfConsumers[index]; + if (networkIsPresent(index) && consumers) + { + // If a network has a start sequence, and it is not started, start it + if (networkInterfaceTable[index].start && (!networkIsStarted(index))) + { + if (debug) + systemPrintf("Starting network: %s\r\n", networkGetNameByIndex(index)); + networkSequenceStart(index, debug, __FILE__, __LINE__); + } + } + else if (debug) + { + if (networkIsPresent(index)) + systemPrintf("Network: No consumers, shutting down\r\n"); + else + systemPrintf("Network: %s not present\r\n", networkInterfaceTable[index].name); + } +} + +//---------------------------------------- +// Start the next lower priority network interface +//---------------------------------------- +void networkStartNextInterface(NetIndex_t index) +{ + NetMask_t bitMask; + NETCONSUMER_MASK_t consumers; + NetPriority_t priority; + + // Verify that a network consumer is requesting the network + consumers = networkConsumersAny; + if (index < NETWORK_OFFLINE) + consumers |= netIfConsumers[index]; + if (consumers) + { + // Validate the index + networkValidateIndex(index); + + // Get the priority of the specified interface + priority = networkPriorityTable[index]; + + // Determine if there is another interface available + for (priority = priority + 1; priority < NETWORK_MAX; priority += 1) + { + index = networkIndexTable[priority]; + bitMask = 1 << index; + if (networkIsPresent(index)) + { + if (((networkStarted | networkSeqStarting) & bitMask) == 0) + // Start this network interface + networkStart(index, settings.debugNetworkLayer, __FILE__, __LINE__); + break; + } + } + } +} + +//---------------------------------------- +// Stop a network interface +//---------------------------------------- +void networkStop(NetIndex_t index, + bool debug, + const char * fileName, + uint32_t lineNumber) +{ + NetMask_t bitMask; + + // Validate the index + networkValidateIndex(index); + + // Display the call + if (settings.debugNetworkLayer) + { + systemPrintf("Network: Calling networkStop(%s) from %s at line %d\r\n", + networkInterfaceTable[index].name, fileName, lineNumber); + networkDisplayStatus(); + } + + // Clear the event flag + networkEventStop[index] = false; + + // Only stop networks that exist on the platform + if (networkIsPresent(index)) + { + // Get the network bit bitMask = (1 << index); // If the network has a stop sequence, and it is started, then stop it @@ -1250,7 +2216,7 @@ void networkStop(NetIndex_t index, bool debug) { if (debug) systemPrintf("Stopping network: %s\r\n", networkGetNameByIndex(index)); - networkSequenceStop(index, debug); + networkSequenceStop(index, debug, __FILE__, __LINE__); } } } @@ -1302,7 +2268,7 @@ void networkStartDelayed(NetIndex_t index, uintptr_t parameter, bool debug) // Only lower priority interfaces or none running // Start this network interface - networkStart(index, settings.debugNetworkLayer); + networkStart(index, settings.debugNetworkLayer, __FILE__, __LINE__); } else if (debug) systemPrintf("%s online, leaving %s off\r\n", currentInterfaceName, name); @@ -1336,140 +2302,41 @@ void networkUpdate() uint8_t priority; NETWORK_POLL_SEQUENCE *sequence; - // Update the network services - DMW_c("mqttClientUpdate"); - mqttClientUpdate(); // Process any Point Perfect MQTT messages - DMW_c("ntpServerUpdate"); - ntpServerUpdate(); // Process any received NTP requests - DMW_c("ntripClientUpdate"); - ntripClientUpdate(); // Check the NTRIP client connection and move data NTRIP --> ZED - DMW_c("ntripServerUpdate"); - ntripServerUpdate(); // Check the NTRIP server connection and move data ZED --> NTRIP - DMW_c("tcpClientUpdate"); - tcpClientUpdate(); // Turn on the TCP client as needed - DMW_c("tcpServerUpdate"); - tcpServerUpdate(); // Turn on the TCP server as needed - DMW_c("udpServerUpdate"); - udpServerUpdate(); // Turn on the UDP server as needed - DMW_c("httpClientUpdate"); - httpClientUpdate(); // Process any Point Perfect HTTP messages - DMW_c("webServerUpdate"); - webServerUpdate(); // Start webServer for web config as needed - - // Once services have been updated, determine if the network needs to be started/stopped - - static uint16_t previousConsumerTypes = NETIF_NONE; - static uint16_t consumerTypes = NETIF_NONE; - - int consumerCount = networkConsumers(&consumerTypes); // Update the current consumer types - - // restartWiFi is used by the settings interface to indicate SSIDs or else has changed - // Stop WiFi to allow restart with new settings - if (restartWiFi == true) - { - restartWiFi = false; - - if (networkInterfaceHasInternet(NETWORK_WIFI)) - { - if (settings.debugNetworkLayer) - systemPrintln("WiFi settings changed, restarting WiFi"); - - wifiResetThrottleTimeout(); - WIFI_STOP(); - networkStop(NETWORK_WIFI, settings.debugNetworkLayer); - } - } - - // If there are no consumers, but the network is online, shut down all networks - if (consumerCount == 0 && networkHasInternet() == true) + // Walk the list of network priorities in descending order + for (priority = 0; priority < NETWORK_OFFLINE; priority++) { - if (settings.debugNetworkLayer && networkInterfaceHasInternet(NETWORK_ETHERNET) == - false) // Ethernet is never stopped, so only print for other networks - systemPrintln("Stopping all networks because there are no consumers"); - - // Shutdown all networks - for (int index = 0; index < NETWORK_OFFLINE; index++) - networkStop(index, settings.debugNetworkLayer); - } + index = networkIndexTable[priority]; - // If the consumers have indicated a network type change (ie, must have WiFi AP even though STA is connected) - // then stop all networks and let the lower code restart the network accordingly - if (consumerTypes != previousConsumerTypes) - { - if (settings.debugNetworkLayer) + // Handle the network lost internet event + if (networkEventInternetLost[index]) { - systemPrintf("Changing network from consumer type: 0x%02X (", previousConsumerTypes); + networkInterfaceInternetConnectionLost(index); - if (previousConsumerTypes == NETIF_NONE) - systemPrint("None"); - else + // Attempt to restart WiFi + if ((index == NETWORK_WIFI_STATION) && (networkIsHighestPriority(index))) { - if (previousConsumerTypes & (1 << NETIF_WIFI_STA)) - systemPrint("/STA"); - if (previousConsumerTypes & (1 << NETIF_WIFI_AP)) - systemPrint("/AP"); - if (previousConsumerTypes & (1 << NETIF_CELLULAR)) - systemPrint("/CELL"); - if (previousConsumerTypes & (1 << NETIF_ETHERNET)) - systemPrint("/ETH"); + if (networkIsStarted(index)) + networkStop(index, settings.debugWifiState, __FILE__, __LINE__); + networkStart(index, settings.debugWifiState, __FILE__, __LINE__); } - - systemPrintf(") to: 0x%02X (", consumerTypes); - - if (consumerTypes == NETIF_NONE) - systemPrint("None"); - else - { - if (consumerTypes & (1 << NETIF_WIFI_STA)) - systemPrint("/STA"); - if (consumerTypes & (1 << NETIF_WIFI_AP)) - systemPrint("/AP"); - if (consumerTypes & (1 << NETIF_CELLULAR)) - systemPrint("/CELL"); - if (consumerTypes & (1 << NETIF_ETHERNET)) - systemPrint("/ETH"); - } - - systemPrintln(")"); } - previousConsumerTypes = networkGetConsumerTypes(); // Update the previous consumer types - - // Shutdown all networks - for (int index = 0; index < NETWORK_OFFLINE; index++) - networkStop(index, settings.debugNetworkLayer); - } - - // Allow consumers to start networks - // Each network is expected to shut itself down if it is unavailable or of a lower priority - // so that a networkStart() succeeds. - if (consumerCount > 0 && networkHasInternet() == false) - { - // Attempt to start any network that is needed, in the order Ethernet/WiFi/Cellular + // Handle the network stop event + if (networkEventStop[index]) + networkStop(index, settings.debugNetworkLayer, __FILE__, __LINE__); - if (consumerTypes & (1 << NETIF_ETHERNET)) - { - networkStart(NETWORK_ETHERNET, settings.debugNetworkLayer); - } + // Handle the network has internet event + if (networkEventInternetAvailable[index]) + networkInterfaceInternetConnectionAvailable(index); - // Start WiFi if we need AP, or if we need STA+Internet - if ((consumerTypes & (1 << NETIF_WIFI_AP)) || - ((consumerTypes & (1 << NETIF_WIFI_STA) && networkHasInternet() == false))) - { - networkStart(NETWORK_WIFI, settings.debugNetworkLayer); - } - - if ((networkHasInternet() == false) && (consumerTypes & (1 << NETIF_CELLULAR))) + // Execute any active polling routine + sequence = networkSequence[index]; + if (sequence) { - // If we're in AP only mode (no internet), don't start cellular - if (WIFI_SOFT_AP_RUNNING() == false) - { - // Don't start cellular until WiFi has failed to connect - if (wifiUnavailable() == true) - { - networkStart(NETWORK_CELLULAR, settings.debugNetworkLayer); - } - } + pollRoutine = sequence->routine; + if (pollRoutine) + // Execute the poll routine + pollRoutine(index, sequence->parameter, settings.debugNetworkLayer); } } @@ -1488,6 +2355,31 @@ void networkUpdate() } } + // Update the network services + // Start or stop mDNS + if (networkMdnsRequests != networkMdnsRunning) + networkMulticastDNSUpdate(); + + // Update the network services + DMW_c("mqttClientUpdate"); + mqttClientUpdate(); // Process any Point Perfect MQTT messages + DMW_c("ntpServerUpdate"); + ntpServerUpdate(); // Process any received NTP requests + DMW_c("ntripClientUpdate"); + ntripClientUpdate(); // Check the NTRIP client connection and move data NTRIP --> ZED + DMW_c("ntripServerUpdate"); + ntripServerUpdate(); // Check the NTRIP server connection and move data ZED --> NTRIP + DMW_c("tcpClientUpdate"); + tcpClientUpdate(); // Turn on the TCP client as needed + DMW_c("tcpServerUpdate"); + tcpServerUpdate(); // Turn on the TCP server as needed + DMW_c("udpServerUpdate"); + udpServerUpdate(); // Turn on the UDP server as needed + DMW_c("httpClientUpdate"); + httpClientUpdate(); // Process any Point Perfect HTTP messages + DMW_c("webServerUpdate"); + webServerUpdate(); // Start webServer for web config as needed + // Periodically display the network interface state displayIpAddress = PERIODIC_DISPLAY(PD_IP_ADDRESS); for (int index = 0; index < NETWORK_OFFLINE; index++) @@ -1509,7 +2401,7 @@ void networkUpdate() else if (networkInterfaceTable[index].netif->started()) systemPrintf("%s: Started\r\n", networkInterfaceTable[index].name); - else if (index == NETWORK_WIFI && (WiFi.getMode() == WIFI_AP || WiFi.getMode() == WIFI_AP_STA)) + else if (index == NETWORK_WIFI_STATION && (WiFi.getMode() == WIFI_AP || WiFi.getMode() == WIFI_AP_STA)) { // NETIF doesn't capture the IP address of a soft AP ipAddress = WiFi.softAPIP(); @@ -1531,333 +2423,196 @@ void networkUpdate() } //---------------------------------------- -// Validate the network index +// Add a network user //---------------------------------------- -void networkValidateIndex(NetIndex_t index) +void networkUserAdd(NETCONSUMER_t consumer, + const char * fileName, + uint32_t lineNumber) { - // Validate the index - if (index >= NETWORK_OFFLINE) - { - systemPrintf("HALTED: Invalid index value %d, valid range (0 - %d)!\r\n", index, NETWORK_OFFLINE - 1); - reportFatalError("Invalid index value!"); - } -} + NetIndex_t index; + NETCONSUMER_MASK_t mask; -//---------------------------------------- -// Validate the network priority -//---------------------------------------- -void networkValidatePriority(NetPriority_t priority) -{ - // Validate the priority - if (priority >= NETWORK_OFFLINE) - { - systemPrintf("HALTED: Invalid priority value %d, valid range (0 - %d)!\r\n", priority, NETWORK_OFFLINE - 1); - reportFatalError("Invalid priority value!"); - } -} + // Validate the consumer + networkConsumerValidate(consumer); + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkUserAdd(%s) from %s at line %d\r\n", + networkConsumerTable[consumer], fileName, lineNumber); -//---------------------------------------- -// Verify the network layer tables -//---------------------------------------- -void networkVerifyTables() -{ - // Verify the table lengths - if (NETWORK_OFFLINE != NETWORK_MAX) - reportFatalError("Fix networkInterfaceTable to match NetworkType"); -} + // Convert the priority into a network interface index + index = networkIndexTable[networkPriority]; -// Return the bitfield containing the type of consumers currently using the network -uint16_t networkGetConsumerTypes() -{ - uint16_t consumerTypes = 0; + // Mark the interface in use + mask = 1 << consumer; + netIfUsers[index] |= mask; - networkConsumers(&consumerTypes); + // Display the user + if (settings.debugNetworkLayer) + systemPrintf("%s adding user %s\r\n", networkInterfaceTable[index].name, networkConsumerTable[consumer]); - return (consumerTypes); + // Remember this network interface + networkConsumerIndexLast[consumer] = index; } -// Return the count of consumers (TCP, NTRIP Client, etc) that are enabled -// and set consumerTypes bitfield -// From this number we can decide if the network (WiFi, ethernet, cellular, etc) needs to be started -// ESP-NOW is not considered a network consumer and is blended during wifiConnect() -uint8_t networkConsumers() +//---------------------------------------- +// Display the network interface users +//---------------------------------------- +void networkUserDisplay(NetIndex_t index) { - uint16_t consumerTypes = 0; + NETCONSUMER_MASK_t bits; + NETCONSUMER_t consumer; + + networkValidateIndex(index); - return (networkConsumers(&consumerTypes)); + // Determine if there are any users + bits = netIfUsers[index]; + systemPrintf("%s Users: %d", networkInterfaceTable[index].name, networkConsumerCount(bits)); + networkConsumerPrint(bits, 0, ", "); + systemPrintln(); } -uint8_t networkConsumers(uint16_t *consumerTypes) +//---------------------------------------- +// Remove a network user +//---------------------------------------- +void networkUserRemove(NETCONSUMER_t consumer, + const char * fileName, + uint32_t lineNumber) { - uint8_t consumerCount = 0; - uint16_t consumerId = 0; // Used to debug print who is asking for access + NetIndex_t index; + NETCONSUMER_MASK_t mask; - *consumerTypes = NETIF_NONE; // Clear bitfield + // Validate the consumer + networkConsumerValidate(consumer); + if (settings.debugNetworkLayer) + systemPrintf("Network: Calling networkUserRemove(%s) from %s at line %d\r\n", + networkConsumerTable[consumer], fileName, lineNumber); - // If a consumer needs the network or is currently consuming the network (is online) then increment - // consumer count + // Convert the priority into a network interface index + index = networkConsumerIndexLast[consumer]; - // Network needed for NTRIP Client - if (ntripClientNeedsNetwork() || online.ntripClient) + // Display the user + mask = 1 << consumer; + if (netIfUsers[index] & mask) { - consumerCount++; - consumerId |= (1 << NETCONSUMER_NTRIP_CLIENT); - - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - } + if (settings.debugNetworkLayer) + systemPrintf("%s removing user %s\r\n", networkInterfaceTable[index].name, networkConsumerTable[consumer]); - // Network needed for NTRIP Server - bool ntripServerOnline = false; - for (int index = 0; index < NTRIP_SERVER_MAX; index++) - { - if (online.ntripServer[index]) - { - ntripServerOnline = true; - break; - } + // The network interface is no longer in use by this consumer + netIfUsers[index] &= ~mask; } +} - if (ntripServerNeedsNetwork() || ntripServerOnline) - { - consumerCount++; - consumerId |= (1 << NETCONSUMER_NTRIP_SERVER); - - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - } +//---------------------------------------- +// Determine if there are any network users +//---------------------------------------- +NETCONSUMER_MASK_t networkUsersActive(NetIndex_t index) +{ + networkValidateIndex(index); - // Network needed for TCP Client - if (tcpClientNeedsNetwork() || online.tcpClient) - { - consumerCount++; - consumerId |= (1 << NETCONSUMER_TCP_CLIENT); - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - } + // Determine if there are any network users + if (index < NETWORK_OFFLINE) + return netIfUsers[index]; + return 0; +} - // Network needed for TCP Server - if (tcpServerNeedsNetwork() || online.tcpServer) +//---------------------------------------- +// Validate the network index +//---------------------------------------- +void networkValidateIndex(NetIndex_t index) +{ + // Validate the index + if (index >= NETWORK_OFFLINE) { - consumerCount++; - consumerId |= (1 << NETCONSUMER_TCP_SERVER); - - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - - // If NTRIP Caster is enabled then add AP mode - // Caster is available over ethernet, WiFi AP, WiFi STA, and cellular - // Caster is available in all mode: Rover, and Base - if (settings.enableNtripCaster == true || settings.baseCasterOverride == true) - *consumerTypes |= (1 << NETIF_WIFI_AP); + systemPrintf("HALTED: Invalid index value %d, valid range (0 - %d)!\r\n", index, NETWORK_OFFLINE - 1); + reportFatalError("Invalid index value!"); } +} - // Network needed for UDP Server - if (udpServerNeedsNetwork() || online.udpServer) +//---------------------------------------- +// Validate the network priority +//---------------------------------------- +void networkValidatePriority(NetPriority_t priority) +{ + // Validate the priority + if (priority >= NETWORK_OFFLINE) { - consumerCount++; - consumerId |= (1 << NETCONSUMER_UDP_SERVER); - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular + systemPrintf("HALTED: Invalid priority value %d, valid range (0 - %d)!\r\n", priority, NETWORK_OFFLINE - 1); + reportFatalError("Invalid priority value!"); } +} - // Network needed for PointPerfect ZTP or key update requested by scheduler, from menu, or display menu - if (provisioningNeedsNetwork() || online.httpClient) - { - consumerCount++; - consumerId |= (1 << NETCONSUMER_PPL_KEY_UPDATE); - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - } +//---------------------------------------- +// Verify the interface priority. Since the design has changed and WiFi +// is now brought up synchronously instead of asynchronously, give WiFi +// a chance to come online before really starting a lower priority +// device such as Cellular. +//---------------------------------------- +void networkVerifyPriority(NetIndex_t index, uintptr_t parameter, bool debug) +{ + uint32_t currentMsec; + NetPriority_t interfacePriority; - // Network needed for PointPerfect Corrections MQTT client - if (mqttClientNeedsNetwork() || online.mqttClient) - { - // PointPerfect is enabled, allow MQTT to begin - consumerCount++; - consumerId |= (1 << NETCONSUMER_PPL_MQTT_CLIENT); - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - } + // Validate the index + networkValidateIndex(index); - // Network needed to obtain the latest firmware version or do a firmware update - if (otaNeedsNetwork() || online.otaClient) + // Determine if this interface is still the highest priority + interfacePriority = networkPriorityTable[index]; + if (interfacePriority > networkPriority) { - consumerCount++; - consumerId |= (1 << NETCONSUMER_OTA_CLIENT); - *consumerTypes = (1 << NETIF_WIFI_STA); // OTA Pull library only supports WiFi + // Another device has come online and takes priority, this device + // does not need to startup. Note: Lowest number has highest + // priority! + if (debug) + systemPrintf("%s: Value %d > %d, indicating lower priority, stopping device!\r\n", + networkInterfaceTable[index].name, interfacePriority, networkPriority); + networkSequenceExit(index, debug, __FILE__, __LINE__); } - // Network needed for Web Config - if (webServerNeedsNetwork() || online.webServer) + // This device is still the highest priority, continue the delay and + // start the device if the end of delay is reached + else { - consumerCount++; - consumerId |= (1 << NETCONSUMER_WEB_CONFIG); - - *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular - - if (settings.wifiConfigOverAP == true) - *consumerTypes |= (1 << NETIF_WIFI_AP); // WebConfig requires both AP and STA (for firmware check) + // Get the timer address + uint32_t *timer = (uint32_t *)parameter; - // A good number of RTK products have only WiFi - // If WiFi STA has failed or we have no WiFi SSIDs, fall back to WiFi AP, but allow STA to keep hunting - if (networkIsPresent(NETWORK_ETHERNET) == false && networkIsPresent(NETWORK_CELLULAR) == false && - settings.wifiConfigOverAP == false && (wifiGetStartTimeout() > 0 || wifiNetworkCount() == 0)) + // Delay until the timer expires + if ((int32_t)(millis() - *timer) >= 0) { - *consumerTypes |= (1 << NETIF_WIFI_AP); // Re-allow Webconfig over AP - } - } - - // Debug - if (settings.debugNetworkLayer) - { - static unsigned long lastPrint = 0; + // Display the priorties + if (debug) + systemPrintf("%s: Value %d <= %d, indicating higher priority, starting device!\r\n", + networkInterfaceTable[index].name, interfacePriority, networkPriority); - if (millis() - lastPrint > 2000) - { - lastPrint = millis(); - systemPrintf("Network consumer count: %d ", consumerCount); - if (consumerCount > 0) - { - systemPrintf("- Consumers: ", consumerCount); - - if (consumerId & (1 << NETCONSUMER_NTRIP_CLIENT)) - systemPrint("Rover NTRIP Client, "); - if (consumerId & (1 << NETCONSUMER_NTRIP_SERVER)) - systemPrint("Base NTRIP Server, "); - if (consumerId & (1 << NETCONSUMER_TCP_CLIENT)) - systemPrint("TCP Client, "); - if (consumerId & (1 << NETCONSUMER_TCP_SERVER)) - systemPrint("TCP Server, "); - if (consumerId & (1 << NETCONSUMER_UDP_SERVER)) - systemPrint("UDP Server, "); - if (consumerId & (1 << NETCONSUMER_PPL_KEY_UPDATE)) - systemPrint("PPL Key Update Request, "); - if (consumerId & (1 << NETCONSUMER_PPL_MQTT_CLIENT)) - systemPrint("PPL MQTT Client, "); - if (consumerId & (1 << NETCONSUMER_OTA_CLIENT)) - systemPrint("OTA Version Check or Update, "); - if (consumerId & (1 << NETCONSUMER_WEB_CONFIG)) - systemPrint("Web Config, "); - } - systemPrintln(); + // Timer has expired + networkSequenceNextEntry(index, debug); } } - - return (consumerCount); } -// Return the count of consumers (TCP, NTRIP Client, etc) that are currently using the network -// This tells the network when it can shutdown to change (ie, move from STA to AP) -uint8_t networkConsumersOnline() +//---------------------------------------- +// Verify the network layer tables +//---------------------------------------- +void networkVerifyTables() { - uint8_t consumerCountOnline = 0; - uint16_t consumerId = 0; // Used to debug print who is asking for access - - // Network needed for NTRIP Client - if (online.ntripClient) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_NTRIP_CLIENT); - } - - // Network needed for NTRIP Server - bool ntripServerConnected = false; - for (int index = 0; index < NTRIP_SERVER_MAX; index++) - { - if (online.ntripServer[index]) - { - ntripServerConnected = true; - break; - } - } - - if (ntripServerConnected) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_NTRIP_SERVER); - } - - // Network needed for TCP Client - if (online.tcpClient) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_TCP_CLIENT); - } - - // Network needed for TCP Server - May use WiFi AP or WiFi STA - if (online.tcpServer) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_TCP_SERVER); - } - - // Network needed for UDP Server - if (online.udpServer) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_UDP_SERVER); - } - - // Network needed for PointPerfect ZTP or key update requested by scheduler, from menu, or display menu - if (online.httpClient) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_PPL_KEY_UPDATE); - } - - // Network needed for PointPerfect Corrections MQTT client - if (online.mqttClient) - { - // PointPerfect is enabled, allow MQTT to begin - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_PPL_MQTT_CLIENT); - } + // Verify the table lengths + if (NETWORK_OFFLINE != NETWORK_MAX) + reportFatalError("Fix networkInterfaceTable to match NetworkType"); - // Network needed to obtain the latest firmware version or do update - if (online.otaClient) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_OTA_CLIENT); - } + // Verify the consumers + if (networkConsumerTableEntries != NETCONSUMER_MAX) + reportFatalError("Fix networkConsumerTable to match NETCONSUMER list"); - // Network needed for Web Config - if (online.webServer) - { - consumerCountOnline++; - consumerId |= (1 << NETCONSUMER_WEB_CONFIG); - } + // Verify the modes of operation + if (rtkModeNameEntries != RTK_MODE_MAX) + reportFatalError("Fix rtkModeName to match RTK_MODE list"); - // Debug - if (settings.debugNetworkLayer) + // Verify that the default priorities match the indexes into the + // networkInterfaceTable table. This verifies the initial values in + // networkIndexTable and networkPriorityTable. + for (NetPriority_t priority = 0; priority < NETWORK_MAX; priority++) { - static unsigned long lastPrint = 0; - - if (millis() - lastPrint > 2000) - { - lastPrint = millis(); - systemPrintf("Network consumer online count: %d ", consumerCountOnline); - if (consumerCountOnline > 0) - { - systemPrintf("- Consumers: ", consumerCountOnline); - if (consumerId & (1 << NETCONSUMER_NTRIP_CLIENT)) - systemPrint("Rover NTRIP Client, "); - if (consumerId & (1 << NETCONSUMER_NTRIP_SERVER)) - systemPrint("Base NTRIP Server, "); - if (consumerId & (1 << NETCONSUMER_TCP_CLIENT)) - systemPrint("TCP Client, "); - if (consumerId & (1 << NETCONSUMER_TCP_SERVER)) - systemPrint("TCP Server, "); - if (consumerId & (1 << NETCONSUMER_UDP_SERVER)) - systemPrint("UDP Server, "); - if (consumerId & (1 << NETCONSUMER_PPL_KEY_UPDATE)) - systemPrint("PPL Key Update Request, "); - if (consumerId & (1 << NETCONSUMER_PPL_MQTT_CLIENT)) - systemPrint("PPL MQTT Client, "); - if (consumerId & (1 << NETCONSUMER_OTA_CLIENT)) - systemPrint("OTA Version Check or Update, "); - if (consumerId & (1 << NETCONSUMER_WEB_CONFIG)) - systemPrint("Web Config, "); - } - - systemPrintln(); - } + if (priority != networkInterfaceTable[priority].index) + reportFatalError("networkInterfaceTable and NetworkType must be in default priority order"); } - - return (consumerCountOnline); } #endif // COMPILE_NETWORK diff --git a/Firmware/RTK_Everywhere/NtripClient.ino b/Firmware/RTK_Everywhere/NtripClient.ino index 9b54aaaf..deca3835 100644 --- a/Firmware/RTK_Everywhere/NtripClient.ino +++ b/Firmware/RTK_Everywhere/NtripClient.ino @@ -204,6 +204,9 @@ bool ntripClientForcedShutdown = false; // NTRIP Client was turned off due to an // NTRIP Client Routines //---------------------------------------- +//---------------------------------------- +// Attempt to connect to the remote NTRIP caster +//---------------------------------------- bool ntripClientConnect() { if (!ntripClient) @@ -241,7 +244,7 @@ bool ntripClientConnect() snprintf(serverRequest, SERVER_BUFFER_SIZE, "GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun_RTK_%s_", settings.ntripClient_MountPoint, platformPrefix); length = strlen(serverRequest); - getFirmwareVersion(&serverRequest[length], SERVER_BUFFER_SIZE - 2 - length, false); + firmwareVersionGet(&serverRequest[length], SERVER_BUFFER_SIZE - 2 - length, false); length = strlen(serverRequest); serverRequest[length++] = '\r'; serverRequest[length++] = '\n'; @@ -298,16 +301,21 @@ bool ntripClientConnect() return true; } +//---------------------------------------- // Determine if another connection is possible or if the limit has been reached +//---------------------------------------- bool ntripClientConnectLimitReached() { + bool limitReached; + int minutes; int seconds; // Retry the connection a few times - bool limitReached = (ntripClientConnectionAttempts >= MAX_NTRIP_CLIENT_CONNECTION_ATTEMPTS); + limitReached = (ntripClientConnectionAttempts >= MAX_NTRIP_CLIENT_CONNECTION_ATTEMPTS); + limitReached = false; // Restart the NTRIP client - ntripClientStop(limitReached || (!settings.enableNtripClient)); + ntripClientStop(limitReached || (!ntripClientEnabled(nullptr))); ntripClientConnectionAttempts++; ntripClientConnectionAttemptsTotal++; @@ -327,15 +335,16 @@ bool ntripClientConnectLimitReached() else ntripClientConnectionAttemptTimeout = (ntripClientConnectionAttempts - 4) * 5 * 60 * 1000L; // Wait 5, 10, 15, etc minutes between attempts + if (ntripClientConnectionAttemptTimeout > RTK_MAX_CONNECTION_MSEC) + ntripClientConnectionAttemptTimeout = RTK_MAX_CONNECTION_MSEC; // Display the delay before starting the NTRIP client if (settings.debugNtripClientState && ntripClientConnectionAttemptTimeout) { - seconds = ntripClientConnectionAttemptTimeout / 1000; - if (seconds < 120) - systemPrintf("NTRIP Client trying again in %d seconds.\r\n", seconds); - else - systemPrintf("NTRIP Client trying again in %d minutes.\r\n", seconds / 60); + seconds = ntripClientConnectionAttemptTimeout / MILLISECONDS_IN_A_SECOND; + minutes = seconds / SECONDS_IN_A_MINUTE; + seconds -= minutes * SECONDS_IN_A_MINUTE; + systemPrintf("NTRIP Client trying again in %d:%02d seconds.\r\n", minutes, seconds); } } else @@ -344,7 +353,76 @@ bool ntripClientConnectLimitReached() return limitReached; } +//---------------------------------------- +// Determine if the NTRIP client may be enabled +//---------------------------------------- +bool ntripClientEnabled(const char ** line) +{ + bool enabled; + + do + { + enabled = false; + + // Verify the operating mode + if (NEQ_RTK_MODE(ntripClientMode)) + { + if (line) + *line = ", Wrong mode!"; + break; + } + + // Verify that the parameters were specified + if ((settings.ntripClient_CasterHost[0] == 0) + || (settings.ntripClient_CasterPort == 0) + || (settings.ntripClient_MountPoint[0] == 0)) + { + if (line) + { + if (settings.ntripClient_CasterHost[0] == 0) + *line = ", Caster host not specified!"; + else if (settings.ntripClient_CasterPort == 0) + *line = ", Caster port not specified!"; + else + *line = ", Mount point not specified!"; + } + break; + } + + // Verify still enabled + enabled = settings.enableNtripClient; + + // Determine if the shutdown is being forced + if (enabled && ntripClientForcedShutdown) + { + if (line) + *line = ", Forced shutdown!"; + enabled = false; + } + + // User manually disabled NTRIP client + else if (enabled == false) + { + if (line) + *line = ", Not enabled!"; + ntripClientForcedShutdown = false; + } + } while (0); + return enabled; +} + +//---------------------------------------- +// Shutdown the NTRIP client +//---------------------------------------- +void ntripClientForceShutdown() +{ + ntripClientStop(true); + ntripClientForcedShutdown = true; // NTRIP Client was turned off due to an error. Don't allow restart. +} + +//---------------------------------------- // Print the NTRIP client state summary +//---------------------------------------- void ntripClientPrintStateSummary() { switch (ntripClientState) @@ -368,7 +446,9 @@ void ntripClientPrintStateSummary() } } +//---------------------------------------- // Print the NTRIP Client status +//---------------------------------------- void ntripClientPrintStatus() { uint32_t days; @@ -414,13 +494,17 @@ void ntripClientPrintStatus() } } +//---------------------------------------- // Determine if NTRIP client data is available +//---------------------------------------- int ntripClientReceiveDataAvailable() { return ntripClient->available(); } +//---------------------------------------- // Read the response from the NTRIP client +//---------------------------------------- void ntripClientResponse(char *response, size_t maxLength) { char *responseEnd; @@ -438,7 +522,9 @@ void ntripClientResponse(char *response, size_t maxLength) *response = '\0'; } +//---------------------------------------- // Restart the NTRIP client +//---------------------------------------- void ntripClientRestart() { // Save the previous uptime value @@ -447,8 +533,10 @@ void ntripClientRestart() ntripClientConnectLimitReached(); } +//---------------------------------------- // Update the state of the NTRIP client state machine // PERIODIC_DISPLAY(PD_NTRIP_CLIENT_STATE) is handled by ntripClientUpdate +//---------------------------------------- void ntripClientSetState(uint8_t newState) { if (settings.debugNtripClientState) @@ -463,7 +551,7 @@ void ntripClientSetState(uint8_t newState) { if (newState >= NTRIP_CLIENT_STATE_MAX) { - systemPrintf("Unknown client state: %d\r\n", newState); + systemPrintf("Unknown NTRIP Client state: %d\r\n", newState); reportFatalError("Unknown NTRIP Client state"); } else @@ -471,14 +559,9 @@ void ntripClientSetState(uint8_t newState) } } -// Shutdown the NTRIP client -void ntripClientForceShutdown() -{ - ntripClientStop(true); - ntripClientForcedShutdown = true; // NTRIP Client was turned off due to an error. Don't allow restart. -} - +//---------------------------------------- // Start the NTRIP client +//---------------------------------------- void ntripClientStart() { // Display the heap state @@ -487,9 +570,13 @@ void ntripClientStart() // Start the NTRIP client systemPrintln("NTRIP Client start"); ntripClientStop(false); + if (ntripClientEnabled(nullptr)) + networkConsumerAdd(NETCONSUMER_NTRIP_CLIENT, NETWORK_ANY, __FILE__, __LINE__); } +//---------------------------------------- // Shutdown or restart the NTRIP client +//---------------------------------------- void ntripClientStop(bool shutdown) { if (ntripClient) @@ -515,8 +602,10 @@ void ntripClientStop(bool shutdown) // Determine the next NTRIP client state online.ntripClient = false; netIncomingRTCM = false; + networkConsumerOffline(NETCONSUMER_NTRIP_CLIENT); if (shutdown) { + networkConsumerRemove(NETCONSUMER_NTRIP_CLIENT, NETWORK_ANY, __FILE__, __LINE__); ntripClientSetState(NTRIP_CLIENT_OFF); ntripClientConnectionAttempts = 0; ntripClientConnectionAttemptTimeout = 0; @@ -525,38 +614,36 @@ void ntripClientStop(bool shutdown) ntripClientSetState(NTRIP_CLIENT_ON); } -// Return true if we are in states that require network access -bool ntripClientNeedsNetwork() -{ - if (ntripClientState >= NTRIP_CLIENT_WAIT_FOR_NETWORK && ntripClientState <= NTRIP_CLIENT_CONNECTED) - return true; - return false; -} - +//---------------------------------------- // Check for the arrival of any correction data. Push it to the GNSS. // Stop task if the connection has dropped or if we receive no data for // NTRIP_CLIENT_RECEIVE_DATA_TIMEOUT +//---------------------------------------- void ntripClientUpdate() { + bool connected; + bool enabled; + const char * line = ""; + // Shutdown the NTRIP client when the mode or setting changes DMW_st(ntripClientSetState, ntripClientState); - if (NEQ_RTK_MODE(ntripClientMode) || (!settings.enableNtripClient)) - { - if (ntripClientState > NTRIP_CLIENT_OFF) - { - ntripClientStop(true); // Was false - #StopVsRestart - ntripClientConnectionAttempts = 0; - ntripClientConnectionAttemptTimeout = 0; - ntripClientSetState(NTRIP_CLIENT_OFF); - } - } + enabled = ntripClientEnabled(&line); + connected = networkConsumerIsConnected(NETCONSUMER_NTRIP_CLIENT); + if ((!enabled) && (ntripClientState > NTRIP_CLIENT_OFF)) + ntripClientStop(true); + + // Determine if the network has failed + else if ((ntripClientState > NTRIP_CLIENT_WAIT_FOR_NETWORK) + && (!connected)) + // Attempt to restart the network + ntripClientRestart(); // Enable the network and the NTRIP client if requested switch (ntripClientState) { case NTRIP_CLIENT_OFF: // Don't allow the client to restart if a forced shutdown occurred - if (ntripClientForcedShutdown == false && EQ_RTK_MODE(ntripClientMode) && settings.enableNtripClient) + if (enabled) ntripClientStart(); break; @@ -567,12 +654,8 @@ void ntripClientUpdate() // Wait for a network media connection case NTRIP_CLIENT_WAIT_FOR_NETWORK: - // Determine if the NTRIP client was turned off - if (ntripClientForcedShutdown || NEQ_RTK_MODE(ntripClientMode) || !settings.enableNtripClient) - ntripClientStop(true); - - // Wait until the network is connected - else if (networkHasInternet()) + // Wait until the network is connected to the media + if (connected) { // Allocate the ntripClient structure ntripClient = new NetworkClient(); @@ -586,6 +669,16 @@ void ntripClientUpdate() { reportHeapNow(settings.debugNtripClientState); + // Connect immediately when the the network has changed + if (networkChanged(NETCONSUMER_NTRIP_CLIENT)) + { + ntripClientConnectionAttempts = 0; + ntripClientConnectionAttemptTimeout = 0; + } + + // Mark the network interface in use + networkUserAdd(NETCONSUMER_NTRIP_CLIENT, __FILE__, __LINE__); + // The network is available for the NTRIP client ntripClientSetState(NTRIP_CLIENT_NETWORK_CONNECTED); } @@ -593,14 +686,9 @@ void ntripClientUpdate() break; case NTRIP_CLIENT_NETWORK_CONNECTED: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - ntripClientStop(true); // Was ntripClientRestart(); - #StopVsRestart - // If GGA transmission is enabled, wait for GNSS lock before connecting to NTRIP Caster // If GGA transmission is not enabled, start connecting to NTRIP Caster - else if ((settings.ntripClient_TransmitGGA == false) || (gnss->isFixed() == true)) + if ((settings.ntripClient_TransmitGGA == false) || (gnss->isFixed() == true)) { // Delay before opening the NTRIP client connection if ((millis() - ntripClientTimer) >= ntripClientConnectionAttemptTimeout) @@ -626,13 +714,8 @@ void ntripClientUpdate() break; case NTRIP_CLIENT_WAIT_RESPONSE: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - ntripClientStop(true); // Was ntripClientRestart(); - #StopVsRestart - // Check for no response from the caster service - else if (ntripClientReceiveDataAvailable() < + if (ntripClientReceiveDataAvailable() < strlen("ICY 200 OK")) // Wait until at least a few bytes have arrived { // Check for response timeout @@ -747,13 +830,8 @@ void ntripClientUpdate() break; case NTRIP_CLIENT_CONNECTED: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - ntripClientStop(true); // Was ntripClientRestart(); - #StopVsRestart - // Check for a broken connection - else if (!ntripClient->connected()) + if (!ntripClient->connected()) { // Broken connection, retry the NTRIP client connection systemPrintln("NTRIP Client connection to caster was broken"); @@ -859,12 +937,15 @@ void ntripClientUpdate() // Periodically display the NTRIP client state if (PERIODIC_DISPLAY(PD_NTRIP_CLIENT_STATE)) { - systemPrintf("NTRIP Client state: %s\r\n", ntripClientStateName[ntripClientState]); + systemPrintf("NTRIP Client state: %s%s\r\n", + ntripClientStateName[ntripClientState], line); PERIODIC_CLEAR(PD_NTRIP_CLIENT_STATE); } } +//---------------------------------------- // Verify the NTRIP client tables +//---------------------------------------- void ntripClientValidateTables() { if (ntripClientStateNameEntries != NTRIP_CLIENT_STATE_MAX) diff --git a/Firmware/RTK_Everywhere/NtripServer.ino b/Firmware/RTK_Everywhere/NtripServer.ino index edb72a42..3e926248 100644 --- a/Firmware/RTK_Everywhere/NtripServer.ino +++ b/Firmware/RTK_Everywhere/NtripServer.ino @@ -180,7 +180,9 @@ static NTRIP_SERVER_DATA ntripServerArray[NTRIP_SERVER_MAX]; // NTRIP Server Routines //---------------------------------------- +//---------------------------------------- // Initiate a connection to the NTRIP caster +//---------------------------------------- bool ntripServerConnectCaster(int serverIndex) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -224,24 +226,29 @@ bool ntripServerConnectCaster(int serverIndex) settings.ntripServer_MountPointPW[serverIndex], settings.ntripServer_MountPoint[serverIndex], platformPrefix); int length = strlen(serverBuffer); - getFirmwareVersion(&serverBuffer[length], sizeof(serverBuffer) - length, false); + firmwareVersionGet(&serverBuffer[length], sizeof(serverBuffer) - length, false); // Send the authorization credentials to the NTRIP caster ntripServer->networkClient->write((const uint8_t *)serverBuffer, strlen(serverBuffer)); return true; } +//---------------------------------------- // Determine if the connection limit has been reached +//---------------------------------------- bool ntripServerConnectLimitReached(int serverIndex) { + bool limitReached; + int minutes; NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; int seconds; // Retry the connection a few times - bool limitReached = (ntripServer->connectionAttempts >= MAX_NTRIP_SERVER_CONNECTION_ATTEMPTS); + limitReached = (ntripServer->connectionAttempts >= MAX_NTRIP_SERVER_CONNECTION_ATTEMPTS); + limitReached = false; // Shutdown the NTRIP server - ntripServerStop(serverIndex, limitReached || (!settings.enableNtripServer)); + ntripServerStop(serverIndex, limitReached || (!ntripServerEnabled(serverIndex, nullptr))); ntripServer->connectionAttempts++; ntripServer->connectionAttemptsTotal++; @@ -261,15 +268,16 @@ bool ntripServerConnectLimitReached(int serverIndex) else ntripServer->connectionAttemptTimeout = (ntripServer->connectionAttempts - 4) * 5 * 60 * 1000L; // Wait 5, 10, 15, etc minutes between attempts + if (ntripServer->connectionAttemptTimeout > RTK_MAX_CONNECTION_MSEC) + ntripServer->connectionAttemptTimeout = RTK_MAX_CONNECTION_MSEC; // Display the delay before starting the NTRIP server if (settings.debugNtripServerState && ntripServer->connectionAttemptTimeout) { - seconds = ntripServer->connectionAttemptTimeout / 1000; - if (seconds < 120) - systemPrintf("NTRIP Server %d trying again in %d seconds.\r\n", serverIndex, seconds); - else - systemPrintf("NTRIP Server %d trying again in %d minutes.\r\n", serverIndex, seconds / 60); + seconds = ntripServer->connectionAttemptTimeout / MILLISECONDS_IN_A_SECOND; + minutes = seconds / SECONDS_IN_A_MINUTE; + seconds -= minutes * SECONDS_IN_A_MINUTE; + systemPrintf("NTRIP Server %d trying again in %d:%02d seconds.\r\n", serverIndex, minutes, seconds); } } else @@ -278,7 +286,53 @@ bool ntripServerConnectLimitReached(int serverIndex) return limitReached; } +//---------------------------------------- +// Determine if the NTRIP server may be enabled +//---------------------------------------- +bool ntripServerEnabled(int serverIndex, const char ** line) +{ + bool enabled; + + do + { + enabled = false; + + // Verify the operating mode + if (NEQ_RTK_MODE(ntripServerMode)) + { + if (line) + *line = ", Wrong mode!"; + break; + } + + // Verify that the parameters were specified + if ((settings.ntripServer_CasterHost[serverIndex][0] == 0) + || (settings.ntripServer_CasterPort[serverIndex] == 0) + || (settings.ntripServer_MountPoint[serverIndex][0] == 0)) + { + if (line) + { + if (settings.ntripServer_CasterHost[0] == 0) + *line = ", Caster host not specified!"; + else if (settings.ntripServer_CasterPort == 0) + *line = ", Caster port not specified!"; + else + *line = ", Mount point not specified!"; + } + break; + } + + // Verify still enabled + enabled = settings.enableNtripServer; + if (line && (enabled == false)) + *line = ", Not enabled!"; + } while (0); + return enabled; +} + +//---------------------------------------- // Print the NTRIP server state summary +//---------------------------------------- void ntripServerPrintStateSummary(int serverIndex) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -305,7 +359,9 @@ void ntripServerPrintStateSummary(int serverIndex) } } +//---------------------------------------- // Print the NTRIP server status +//---------------------------------------- void ntripServerPrintStatus(int serverIndex) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -351,7 +407,9 @@ void ntripServerPrintStatus(int serverIndex) } } +//---------------------------------------- // This function gets called as each RTCM byte comes in +//---------------------------------------- void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -386,7 +444,7 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) // If we have not gotten new RTCM bytes for a period of time, assume end of frame if (((millis() - ntripServer->timer) > 100) && (ntripServer->bytesSent > 0)) { - if ((!inMainMenu) && settings.debugNtripServerState) + if ((!inMainMenu) && settings.debugNtripServerRtcm) systemPrintf("NTRIP Server %d transmitted %d RTCM bytes to Caster\r\n", serverIndex, ntripServer->bytesSent); @@ -410,7 +468,9 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) } } +//---------------------------------------- // Read the authorization response from the NTRIP caster +//---------------------------------------- void ntripServerResponse(int serverIndex, char *response, size_t maxLength) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -427,7 +487,9 @@ void ntripServerResponse(int serverIndex, char *response, size_t maxLength) *response = '\0'; } +//---------------------------------------- // Restart the NTRIP server +//---------------------------------------- void ntripServerRestart(int serverIndex) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -438,20 +500,23 @@ void ntripServerRestart(int serverIndex) ntripServerConnectLimitReached(serverIndex); } +//---------------------------------------- // Update the state of the NTRIP server state machine +//---------------------------------------- void ntripServerSetState(NTRIP_SERVER_DATA *ntripServer, uint8_t newState) { - int serverIndex = -999; - for (int index = 0; index < NTRIP_SERVER_MAX; index++) + int serverIndex; + for (serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) { - if (ntripServer == &ntripServerArray[index]) - { - serverIndex = index; - break; - } + if (ntripServer == &ntripServerArray[serverIndex]) + serverIndex = serverIndex; + } + if (serverIndex >= NTRIP_SERVER_MAX) + { + systemPrintf("NTRIP Server: %p unknown NTRIP Server structure!\r\n", (void *)ntripServer); + return; } - // PERIODIC_DISPLAY(PD_NTRIP_SERVER_STATE) is handled by ntripServerUpdate if (settings.debugNtripServerState) { if (ntripServer->state == newState) @@ -472,13 +537,17 @@ void ntripServerSetState(NTRIP_SERVER_DATA *ntripServer, uint8_t newState) } } +//---------------------------------------- // Shutdown the NTRIP server +//---------------------------------------- void ntripServerShutdown(int serverIndex) { ntripServerStop(serverIndex, true); } +//---------------------------------------- // Start the NTRIP server +//---------------------------------------- void ntripServerStart(int serverIndex) { // Display the heap state @@ -487,9 +556,13 @@ void ntripServerStart(int serverIndex) // Start the NTRIP server systemPrintf("NTRIP Server %d start\r\n", serverIndex); ntripServerStop(serverIndex, false); + if (ntripServerEnabled(serverIndex, nullptr)) + networkConsumerAdd(NETCONSUMER_NTRIP_SERVER_0 + serverIndex, NETWORK_ANY, __FILE__, __LINE__); } +//---------------------------------------- // Shutdown or restart the NTRIP server +//---------------------------------------- void ntripServerStop(int serverIndex, bool shutdown) { bool enabled; @@ -515,23 +588,18 @@ void ntripServerStop(int serverIndex, bool shutdown) // Determine the next NTRIP server state online.ntripServer[serverIndex] = false; - if (shutdown || (!settings.ntripServer_CasterHost[serverIndex][0]) || - (!settings.ntripServer_CasterPort[serverIndex]) || (!settings.ntripServer_MountPoint[serverIndex][0])) + networkConsumerOffline(NETCONSUMER_NTRIP_SERVER_0 + serverIndex); + if (shutdown) { - if (shutdown) - { - if (settings.debugNtripServerState) - systemPrintf("NTRIP Server %d shutdown requested!\r\n", serverIndex); - } - else - { - if (settings.debugNtripServerState && (!settings.ntripServer_CasterHost[serverIndex][0])) - systemPrintf("NTRIP Server %d caster host not configured!\r\n", serverIndex); - if (settings.debugNtripServerState && (!settings.ntripServer_CasterPort[serverIndex])) - systemPrintf("NTRIP Server %d caster port not configured!\r\n", serverIndex); - if (settings.debugNtripServerState && (!settings.ntripServer_MountPoint[serverIndex][0])) - systemPrintf("NTRIP Server %d mount point not configured!\r\n", serverIndex); - } + if (settings.debugNtripServerState) + systemPrintf("NTRIP Server %d shutdown requested!\r\n", serverIndex); + if (settings.debugNtripServerState && (!settings.ntripServer_CasterHost[serverIndex][0])) + systemPrintf("NTRIP Server %d caster host not configured!\r\n", serverIndex); + if (settings.debugNtripServerState && (!settings.ntripServer_CasterPort[serverIndex])) + systemPrintf("NTRIP Server %d caster port not configured!\r\n", serverIndex); + if (settings.debugNtripServerState && (!settings.ntripServer_MountPoint[serverIndex][0])) + systemPrintf("NTRIP Server %d mount point not configured!\r\n", serverIndex); + networkConsumerRemove(NETCONSUMER_NTRIP_SERVER_0 + serverIndex, NETWORK_ANY, __FILE__, __LINE__); ntripServerSetState(ntripServer, NTRIP_SERVER_OFF); ntripServer->connectionAttempts = 0; ntripServer->connectionAttemptTimeout = 0; @@ -544,11 +612,6 @@ void ntripServerStop(int serverIndex, bool shutdown) enabled = true; break; } - // settings.enableNtripServer = enabled; - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Why? Setting settings.enableNtripServer to false means - // the server connections cannot be (re)started without setting settings.enableNtripServer back - // to true via the menu / web config... Was the intent to close the network connection when all - // servers have disconnected? } else { @@ -558,51 +621,36 @@ void ntripServerStop(int serverIndex, bool shutdown) } } -// Return true if we are in states that require network access -// Walk through all servers to see if we need the network -bool ntripServerNeedsNetwork() -{ - NTRIP_SERVER_DATA *ntripServer; - - for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) - { - ntripServer = &ntripServerArray[serverIndex]; - if (ntripServer->state >= NTRIP_SERVER_WAIT_FOR_NETWORK && ntripServer->state <= NTRIP_SERVER_CASTING) - return true; - } - - return false; -} - +//---------------------------------------- // Update the NTRIP server state machine +//---------------------------------------- void ntripServerUpdate(int serverIndex) { + bool connected; + bool enabled; + const char * line = ""; + // Get the NTRIP data structure NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; // Shutdown the NTRIP server when the mode or setting changes DMW_ds(ntripServerSetState, ntripServer); - if (NEQ_RTK_MODE(ntripServerMode) || (!settings.enableNtripServer)) - { - if (ntripServer->state > NTRIP_SERVER_OFF) - { - ntripServerStop(serverIndex, true); // Was false - #StopVsRestart - ntripServer->connectionAttempts = 0; - ntripServer->connectionAttemptTimeout = 0; - ntripServerSetState(ntripServer, NTRIP_SERVER_OFF); - } - } + connected = networkConsumerIsConnected(NETCONSUMER_NTRIP_SERVER_0 + serverIndex); + enabled = ntripServerEnabled(serverIndex, &line); + if (!enabled && (ntripServer->state > NTRIP_SERVER_OFF)) + ntripServerShutdown(serverIndex); + + // Determine if the network has failed + else if ((ntripServer->state > NTRIP_SERVER_WAIT_FOR_NETWORK) + && (!connected)) + ntripServerRestart(serverIndex); // Enable the network and the NTRIP server if requested switch (ntripServer->state) { case NTRIP_SERVER_OFF: - if (EQ_RTK_MODE(ntripServerMode) && settings.enableNtripServer && - settings.ntripServer_CasterHost[serverIndex][0] && settings.ntripServer_CasterPort[serverIndex] && - settings.ntripServer_MountPoint[serverIndex][0]) - { + if (enabled) ntripServerStart(serverIndex); - } break; // Start the network @@ -612,17 +660,8 @@ void ntripServerUpdate(int serverIndex) // Wait for a network media connection case NTRIP_SERVER_WAIT_FOR_NETWORK: - // Determine if the NTRIP server was turned off - if (NEQ_RTK_MODE(ntripServerMode) || (settings.enableNtripServer == false) || - (settings.ntripServer_CasterHost[serverIndex][0] == 0) || - (settings.ntripServer_CasterPort[serverIndex] == 0) || - (settings.ntripServer_MountPoint[serverIndex][0] == 0)) - { - ntripServerStop(serverIndex, true); - } - // Wait until the network is connected - else if (networkHasInternet()) + if (connected) { // Allocate the networkClient structure ntripServer->networkClient = new NetworkClient(); @@ -636,7 +675,15 @@ void ntripServerUpdate(int serverIndex) { reportHeapNow(settings.debugNtripServerState); + // Reset the timeout when the network changes + if (networkChanged(NETCONSUMER_NTRIP_SERVER_0 + serverIndex)) + { + ntripServer->connectionAttempts = 0; + ntripServer->connectionAttemptTimeout = 0; + } + // The network is available for the NTRIP server + networkUserAdd(NETCONSUMER_NTRIP_SERVER_0 + serverIndex, __FILE__, __LINE__); ntripServerSetState(ntripServer, NTRIP_SERVER_NETWORK_CONNECTED); } } @@ -645,9 +692,9 @@ void ntripServerUpdate(int serverIndex) // Network available case NTRIP_SERVER_NETWORK_CONNECTED: // Determine if the network has failed - if (networkHasInternet() == false) + if (!connected) // Failed to connect to to the network, attempt to restart the network - ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart + ntripServerRestart(serverIndex); else if (settings.enableNtripServer && (millis() - ntripServer->lastConnectionAttempt > ntripServer->connectionAttemptTimeout)) @@ -662,23 +709,13 @@ void ntripServerUpdate(int serverIndex) // Wait for GNSS correction data case NTRIP_SERVER_WAIT_GNSS_DATA: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart - // State change handled in ntripServerProcessRTCM break; // Initiate the connection to the NTRIP caster case NTRIP_SERVER_CONNECTING: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart - // Delay before opening the NTRIP server connection - else if ((millis() - ntripServer->timer) >= ntripServer->connectionAttemptTimeout) + if ((millis() - ntripServer->timer) >= ntripServer->connectionAttemptTimeout) { // Attempt a connection to the NTRIP caster if (!ntripServerConnectCaster(serverIndex)) @@ -700,13 +737,8 @@ void ntripServerUpdate(int serverIndex) // Wait for authorization response case NTRIP_SERVER_AUTHORIZATION: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart - // Check if caster service responded - else if (ntripServer->networkClient->available() < + if (ntripServer->networkClient->available() < strlen("ICY 200 OK")) // Wait until at least a few bytes have arrived { // Check for response timeout @@ -794,13 +826,8 @@ void ntripServerUpdate(int serverIndex) // NTRIP server authorized to send RTCM correction data to NTRIP caster case NTRIP_SERVER_CASTING: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to the network, attempt to restart the network - ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart - // Check for a broken connection - else if (!ntripServer->networkClient->connected()) + if (!ntripServer->networkClient->connected()) { // Broken connection, retry the NTRIP connection systemPrintf("Connection to NTRIP Caster %d - %s was lost\r\n", serverIndex, @@ -839,22 +866,29 @@ void ntripServerUpdate(int serverIndex) // Periodically display the state if (PERIODIC_DISPLAY(PD_NTRIP_SERVER_STATE)) { - systemPrintf("NTRIP Server %d state: %s\r\n", serverIndex, ntripServerStateName[ntripServer->state]); + systemPrintf("NTRIP Server %d state: %s%s\r\n", serverIndex, + ntripServerStateName[ntripServer->state], line); if (serverIndex == (NTRIP_SERVER_MAX - 1)) PERIODIC_CLEAR(PD_NTRIP_SERVER_STATE); // Clear the periodic display only on the last server } } +//---------------------------------------- // Update the NTRIP server state machine +//---------------------------------------- void ntripServerUpdate() { for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) ntripServerUpdate(serverIndex); } +//---------------------------------------- // Verify the NTRIP server tables +//---------------------------------------- void ntripServerValidateTables() { + if (NETCONSUMER_OTA_CLIENT != (NETCONSUMER_NTRIP_SERVER_0 + NTRIP_SERVER_MAX)) + reportFatalError("Adjust NETCONSUMER_NTRIP_SERVER_* entries to match NTRIP_SERVER_MAX"); if (ntripServerStateNameEntries != NTRIP_SERVER_STATE_MAX) reportFatalError("Fix ntripServerStateNameEntries to match NTRIPServerState"); } diff --git a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino index 0dad7262..822ae209 100644 --- a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino +++ b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino @@ -139,13 +139,7 @@ void beginPPL() // PPL_MAX_RTCM_BUFFER is 3345 bytes so we create it on the heap // Freed by stopPPL() if (pplRtcmBuffer == nullptr) - { - if (online.psram == true) - pplRtcmBuffer = (uint8_t *)ps_malloc(PPL_MAX_RTCM_BUFFER); - else - pplRtcmBuffer = (uint8_t *)malloc(PPL_MAX_RTCM_BUFFER); - } - + pplRtcmBuffer = (uint8_t *)rtkMalloc(PPL_MAX_RTCM_BUFFER, "PPL RTCM buffer (pplRtcmBuffer)"); if (!pplRtcmBuffer) { systemPrintln("ERROR: Failed to allocate rtcmBuffer"); @@ -215,7 +209,7 @@ void stopPPL() if (pplRtcmBuffer != nullptr) { - free(pplRtcmBuffer); + rtkFree(pplRtcmBuffer, "PPL RTCM buffer (pplRtcmBuffer)"); pplRtcmBuffer = nullptr; } diff --git a/Firmware/RTK_Everywhere/RTK_Everywhere.ino b/Firmware/RTK_Everywhere/RTK_Everywhere.ino index e862eff9..6564e926 100644 --- a/Firmware/RTK_Everywhere/RTK_Everywhere.ino +++ b/Firmware/RTK_Everywhere/RTK_Everywhere.ino @@ -17,6 +17,65 @@ to the GNSS receiver to achieve RTK Fix. Settings are loaded from microSD if available, otherwise settings are pulled from ESP32's file system LittleFS. + + Software Layers: + + +--------+ +--------+ + | GNSS | | GNSS | + | Rover | | Base | + +--------+ +--------+ + ^ | + | | + | v + +-------------+ + .------->| RTCM Serial |<--------------. + | +-------------+ | + | ^ | + HTTP Client | | +--------+ | + MQTT Client | | | Config | | + NTRIP Client | | +--------+ | + OTA Client | | ^ | + TCP Client | | | | + | v v | + | +-----------------+ | + | | Server Clients | | + | +-----------------+ | + | ^ | + | | NTP Server | + | | NTRIP Server | + | | TCP Server | + | | UDP Server | + | | Web Server | + V v | + +-----------------+ +-----------------+ | + | Clients | | Servers | | + +-----------------+ +-----------------+ | + ^ ^ | + | | | + v v v + +-----------------------------------------+ +-------------+ + | Network Services | | | + | | | | + | +---------------+ +---------------+ | | | + | | DNS Server | | mDNS | | | | + | +---------------+ +---------------+ | | | + | | | | + +-----------------------------------------+ | | + ^ ^ | | + | | | | + v v | | + +-----------------------------------------+ | ESP-NOW | + | Network Layer | | | + | | | | + | Priority Selection On/Off | | | + | +---------------+ +---------------+ | | | + | | Ethernet | | | | | | + | | WiFi Station | | WiFi Soft AP | | | | + | | Cellular | | | | | | + | +---------------+ +---------------+ | | | + | | | | + +-----------------------------------------+ +-------------+ + */ // To reduce compile times, various parts of the firmware can be disabled/removed if they are not @@ -87,6 +146,8 @@ #include #endif // COMPILE_NETWORK +#define RTK_MAX_CONNECTION_MSEC (15 * MILLISECONDS_IN_A_MINUTE) + bool RTK_CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC = false; // Flag used by the special build of libmbedtls (libmbedcrypto) to select external memory @@ -105,27 +166,30 @@ const uint16_t HTTPS_PORT = 443; #include "esp_wifi.h" //Needed for esp_wifi_set_protocol() #include //Built-in. #include //Built-in. -#include //Built-in. #endif // COMPILE_WIFI #ifdef COMPILE_CELLULAR #include #endif // COMPILE_CELLULAR +#include // MAC address support #include "settings.h" #define MAX_CPU_CORES 2 #define IDLE_COUNT_PER_SECOND 515400 // Found by empirical sketch #define IDLE_TIME_DISPLAY_SECONDS 5 #define MAX_IDLE_TIME_COUNT (IDLE_TIME_DISPLAY_SECONDS * IDLE_COUNT_PER_SECOND) -#define MILLISECONDS_IN_A_SECOND 1000 -#define MILLISECONDS_IN_A_MINUTE (60 * MILLISECONDS_IN_A_SECOND) -#define MILLISECONDS_IN_AN_HOUR (60 * MILLISECONDS_IN_A_MINUTE) -#define MILLISECONDS_IN_A_DAY (24 * MILLISECONDS_IN_AN_HOUR) -#define SECONDS_IN_A_MINUTE 60 -#define SECONDS_IN_AN_HOUR (60 * SECONDS_IN_A_MINUTE) -#define SECONDS_IN_A_DAY (24 * SECONDS_IN_AN_HOUR) +#define HOURS_IN_A_DAY 24L +#define MINUTES_IN_AN_HOUR 60L +#define SECONDS_IN_A_MINUTE 60L +#define MILLISECONDS_IN_A_SECOND 1000L +#define MILLISECONDS_IN_A_MINUTE (SECONDS_IN_A_MINUTE * MILLISECONDS_IN_A_SECOND) +#define MILLISECONDS_IN_AN_HOUR (MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE) +#define MILLISECONDS_IN_A_DAY (HOURS_IN_A_DAY * MILLISECONDS_IN_AN_HOUR) + +#define SECONDS_IN_AN_HOUR (MINUTES_IN_AN_HOUR * SECONDS_IN_A_MINUTE) +#define SECONDS_IN_A_DAY (HOURS_IN_A_DAY * SECONDS_IN_AN_HOUR) // Hardware connections //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -296,31 +360,31 @@ bool sdSizeCheckTaskComplete; char logFileName[sizeof("SFE_Reference_Station_230101_120101.ubx_plusExtraSpace")] = {0}; //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -// Over-the-Air (OTA) update support +// WiFi support //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - -#define MQTT_CERT_SIZE 2000 - -#include //http://librarymanager/All#Arduino_JSON_messagepack - -#include "esp_ota_ops.h" //Needed for partition counting and updateFromSD - #ifdef COMPILE_WIFI int packetRSSI; RTK_WIFI wifi(false); - -#define WIFI_IS_CONNECTED() wifiIsConnected() -#define WIFI_IS_RUNNING() wifiIsRunning() -#define WIFI_SOFT_AP_RUNNING() wifiApIsRunning() -#define WIFI_STOP() \ - { \ - if (settings.debugWifiState) \ - systemPrintf("wifiStop called by %s %d\r\n", __FILE__, __LINE__); \ - wifiStop(); \ - } #endif // COMPILE_WIFI -bool restartWiFi = false; // Restart WiFi if user changes anything +// WiFi Globals - For other module direct access +WIFI_CHANNEL_t wifiChannel; // Current WiFi channel number +bool wifiEspNowOnline; // ESP-Now started successfully +bool wifiEspNowRunning; // False: stopped, True: starting, running, stopping +uint32_t wifiReconnectionTimer; // Delay before reconnection, timer running when non-zero +bool wifiSoftApOnline; // WiFi soft AP started successfully +bool wifiSoftApRunning; // False: stopped, True: starting, running, stopping +bool wifiStationOnline; // WiFi station started successfully +bool wifiStationRunning; // False: stopped, True: starting, running, stopping + +const char * wifiSoftApSsid = "RTK Config"; +const char * wifiSoftApPassword = nullptr; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// MQTT support +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +#define MQTT_CERT_SIZE 2000 #define MQTT_CLIENT_STOP(shutdown) \ { \ @@ -329,6 +393,14 @@ bool restartWiFi = false; // Restart WiFi if user changes anything mqttClientStop(shutdown); \ } +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Over-the-Air (OTA) update support +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +#include //http://librarymanager/All#Arduino_JSON_messagepack + +#include "esp_ota_ops.h" //Needed for partition counting and updateFromSD + #define OTA_FIRMWARE_JSON_URL_LENGTH 128 // 1 1 1 // 1 2 3 4 5 6 7 8 9 0 1 2 @@ -497,7 +569,7 @@ TaskHandle_t pinBluetoothTaskHandle; // Dummy task to start hardware on an assig volatile bool bluetoothPinned; // This variable is touched by core 0 but checked by core 1. Must be volatile. volatile static int combinedSpaceRemaining; // Overrun indicator -volatile static uint64_t logFileSize; // Updated with each write +volatile static uint64_t logFileSize; // Updated with each write int bufferOverruns; // Running count of possible data losses since power-on bool zedUartPassed; // Goes true during testing if ESP can communicate with ZED over UART @@ -578,18 +650,8 @@ unsigned long lastDynamicDataUpdate; // https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino DNSServer *dnsserver; -WebServer *webServer; - -httpd_handle_t *wsserver = nullptr; -// httpd_req_t *last_ws_req; -int last_ws_fd; -TaskHandle_t updateWebServerTaskHandle; -const uint8_t updateWebServerTaskPriority = 0; // 3 being the highest, and 0 being the lowest -const int updateWebServerTaskStackSize = - AP_CONFIG_SETTING_SIZE + 3000; // Needs to be large enough to hold the file manager file list -const int updateWebSocketStackSize = - AP_CONFIG_SETTING_SIZE + 3000; // Needs to be large enough to hold the full settings string +bool websocketConnected = false; #endif // COMPILE_AP #endif // COMPILE_WIFI @@ -605,26 +667,19 @@ const int updateWebSocketStackSize = float lBandEBNO; // Used on system status menu //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -// ESP NOW for multipoint wireless broadcasting over 2.4GHz +// ESP-NOW for multipoint wireless broadcasting over 2.4GHz //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #ifdef COMPILE_ESPNOW #include //Built-in -uint8_t espnowOutgoing[250]; // ESP NOW has max of 250 characters -unsigned long espnowLastAdd; // Tracks how long since the last byte was added to the outgoing buffer -uint8_t espnowOutgoingSpot; // ESP Now has a max of 250 characters -uint16_t espnowBytesSent; // May be more than 255 -uint8_t receivedMAC[6]; // Holds the broadcast MAC during pairing - -unsigned long lastEspnowRssiUpdate; - -#define ESPNOW_START() espnowStart() -#define ESPNOW_STOP() espnowStop() #endif // COMPILE_ESPNOW -int espnowRSSI; -// const uint8_t ESPNOW_MAX_PEERS = 5 is defined in settings.h +// ESP-NOW Globals - For other module direct access +bool espNowIncomingRTCM; +bool espNowOutgoingRTCM; +int espNowRSSI; +const uint8_t espNowBroadcastAddr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // Ethernet //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -785,8 +840,6 @@ bool bluetoothIncomingRTCM; bool bluetoothOutgoingRTCM; bool netIncomingRTCM; bool netOutgoingRTCM; -bool espnowIncomingRTCM; -bool espnowOutgoingRTCM; volatile bool mqttClientDataReceived; // Flag for display uint16_t failedParserMessages_UBX; @@ -870,23 +923,9 @@ volatile bool deadManWalking; deadManWalking = true; \ \ /* Output as much as possible to identify the location of the failure */ \ - settings.enableHeapReport = true; \ - settings.enableTaskReports = true; \ - settings.enablePrintPosition = true; \ - settings.enablePrintIdleTime = true; \ - settings.enablePrintBatteryMessages = true; \ - settings.enablePrintRoverAccuracy = true; \ - settings.enablePrintLogFileMessages = true; \ - settings.enablePrintLogFileStatus = true; \ - settings.enablePrintRingBufferOffsets = true; \ - settings.enablePrintStates = true; \ - settings.enablePrintDuplicateStates = true; \ - settings.enablePrintRtcSync = true; \ - settings.enablePrintBufferOverrun = true; \ - settings.enablePrintSDBuffers = true; \ - settings.periodicDisplay = (PeriodicDisplay_t) - 1; \ - settings.enablePrintEthernetDiag = true; \ settings.debugCorrections = true; \ + settings.debugEspNow = true; \ + settings.debugFirmwareUpdate = true; \ settings.debugGnss = true; \ settings.debugHttpClientData = true; \ settings.debugHttpClientState = true; \ @@ -894,7 +933,7 @@ volatile bool deadManWalking; settings.debugMqttClientData = true; \ settings.debugMqttClientState = true; \ settings.debugNetworkLayer = true; \ - settings.printNetworkStatus = true; \ + settings.debugNtp = true; \ settings.debugNtripClientRtcm = true; \ settings.debugNtripClientState = true; \ settings.debugNtripServerRtcm = true; \ @@ -906,7 +945,27 @@ volatile bool deadManWalking; settings.debugUdpServer = true; \ settings.debugWebServer = true; \ settings.debugWifiState = true; \ + settings.enableHeapReport = true; \ + settings.enableImuDebug = true; \ + settings.enablePrintBatteryMessages = true; \ + settings.enablePrintBufferOverrun = true; \ + settings.enablePrintDuplicateStates = true; \ + settings.enablePrintEthernetDiag = true; \ + settings.enablePrintIdleTime = true; \ + settings.enablePrintLogFileMessages = true; \ + settings.enablePrintLogFileStatus = true; \ + settings.enablePrintPosition = true; \ + settings.enablePrintRingBufferOffsets = true; \ + settings.enablePrintRoverAccuracy = true; \ + settings.enablePrintRtcSync = true; \ + settings.enablePrintSDBuffers = true; \ + settings.enablePrintStates = true; \ + settings.enableTaskReports = true; \ + settings.periodicDisplay = (PeriodicDisplay_t) - 1; \ settings.printBootTimes = true; \ + settings.printNetworkStatus = true; \ + settings.printPartitionTable = true; \ + settings.printTaskStartStop = true; \ } #else // 0 @@ -1107,9 +1166,6 @@ void setup() systemPrintln(); systemPrintln(); - DMW_b("verifyTables"); - verifyTables(); // Verify the consistency of the internal tables - DMW_b("findSpiffsPartition"); if (!findSpiffsPartition()) { @@ -1164,6 +1220,9 @@ void setup() DMW_b("beginDisplay"); beginDisplay(i2cDisplay); // Start display to be able to display any errors + DMW_b("verifyTables"); + verifyTables(); // Verify the consistency of the internal tables + beginVersion(); // Assemble platform name. Requires settings/LFS. DMW_b("beginGnssUart"); @@ -1346,8 +1405,8 @@ void loop() DMW_c("tiltUpdate"); tiltUpdate(); // Check if new lat/lon/alt have been calculated - DMW_c("updateEspnow"); - updateEspnow(); // Check if we need to finish sending any RTCM over ESP-NOW radio + DMW_c("espNowUpdate"); + espNowUpdate(); // Check if we need to finish sending any RTCM over ESP-NOW radio DMW_c("updateLora"); updateLora(); // Check if we need to finish sending any RTCM over LoRa radio diff --git a/Firmware/RTK_Everywhere/States.ino b/Firmware/RTK_Everywhere/States.ino index 094817ea..7f515014 100644 --- a/Firmware/RTK_Everywhere/States.ino +++ b/Firmware/RTK_Everywhere/States.ino @@ -7,8 +7,6 @@ static uint32_t lastStateTime = 0; -extern bool websocketConnected; - // Given the current state, see if conditions have moved us to a new state // A user pressing the mode button (change between rover/base) is handled by buttonCheckTask() void stateUpdate() @@ -123,7 +121,6 @@ void stateUpdate() displayRoverFail(1000); else { - //settings.gnssConfiguredRover is set by gnss->configureRover() settings.gnssConfiguredBase = false; // When the mode changes, reapply all settings settings.lastState = STATE_ROVER_NOT_STARTED; recordSystemSettings(); // Record this state for next POR @@ -256,7 +253,7 @@ void stateUpdate() if (settings.fixedBase == false) changeState(STATE_BASE_TEMP_SETTLE); - else if (settings.fixedBase == true) + else changeState(STATE_BASE_FIXED_NOT_STARTED); } else @@ -443,7 +440,7 @@ void stateUpdate() displayWebConfigNotStarted(); // Display immediately while we wait for server to start bluetoothStop(); // Bluetooth must be stopped to allow enough RAM for AP+STA (firmware check) - ESPNOW_STOP(); // We don't need ESP-NOW during web config + wifiEspNowOff(__FILE__, __LINE__); // We don't need ESP-NOW during web config // The GNSS UART task is left running to allow GNSS receivers to obtain LLh data for 1Hz page updates @@ -570,7 +567,7 @@ void stateUpdate() paintEspNowPairing(); // Start ESP-Now if needed, put ESP-Now into broadcast state - espnowBeginPairing(); + espNowBeginPairing(); changeState(STATE_ESPNOW_PAIRING); #else // COMPILE_ESPNOW @@ -580,7 +577,7 @@ void stateUpdate() break; case (STATE_ESPNOW_PAIRING): { - if (espnowIsPaired() == true) + if (espNowProcessRxPairedMessage() == true) { paintEspNowPaired(); @@ -588,10 +585,7 @@ void stateUpdate() changeState(lastSystemState); } else - { - uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - espnowSendPairMessage(broadcastMac); // Send unit's MAC address over broadcast, no ack, no encryption - } + espNowSendPairMessage(espNowBroadcastAddr); // Send unit's MAC address over broadcast, no ack, no encryption } break; @@ -606,7 +600,7 @@ void stateUpdate() displayNtpStart(500); // Show 'NTP' // Start UART connected to the GNSS receiver for NMEA and UBX data (enables logging) - if (tasksStartGnssUart() && configureUbloxModuleNTP()) + if (tasksStartGnssUart() && ntpConfigureUbloxModule()) { settings.lastState = STATE_NTPSERVER_NOT_STARTED; // Record this state for next POR settings.gnssConfiguredBase = false; // On the next boot, reapply all settings @@ -817,6 +811,7 @@ const RTK_MODE_ENTRY stateModeTable[] = {{"Rover", STATE_ROVER_NOT_STARTED, STAT {"Base", STATE_BASE_NOT_STARTED, STATE_BASE_FIXED_TRANSMITTING}, {"Setup", STATE_DISPLAY_SETUP, STATE_PROFILE}, {"ESPNOW Pairing", STATE_ESPNOW_PAIRING_NOT_STARTED, STATE_ESPNOW_PAIRING}, + {"Provisioning", STATE_KEYS_REQUESTED, STATE_KEYS_REQUESTED}, {"NTP", STATE_NTPSERVER_NOT_STARTED, STATE_NTPSERVER_SYNC}, {"Shutdown", STATE_SHUTDOWN, STATE_SHUTDOWN}}; const int stateModeTableEntries = sizeof(stateModeTable) / sizeof(stateModeTable[0]); diff --git a/Firmware/RTK_Everywhere/System.ino b/Firmware/RTK_Everywhere/System.ino index 99c9dc29..6d4ea968 100644 --- a/Firmware/RTK_Everywhere/System.ino +++ b/Firmware/RTK_Everywhere/System.ino @@ -21,6 +21,64 @@ void beginPsram() } } +// Free memory to PSRAM when available +void rtkFree(void * data, const char * text) +{ + if (settings.debugMalloc) + systemPrintf("%p: Freeing %s\r\n", data, text); + free(data); +} + +// Allocate memory from PSRAM when available +void * rtkMalloc(size_t sizeInBytes, const char * text) +{ + const char * area; + void * data; + + if (online.psram == true) + { + area = "PSRAM"; + data = ps_malloc(sizeInBytes); + } + else + { + area = "RAM"; + data = malloc(sizeInBytes); + } + + // Display the allocation + if (settings.debugMalloc) + { + if (data) + systemPrintf("%p, %s %d bytes allocated: %s\r\n", data, area, sizeInBytes, text); + else + systemPrintf("Failed to allocate %d bytes from %s: %s\r\n", sizeInBytes, area, text); + } + return data; +} + +// See https://en.cppreference.com/w/cpp/memory/new/operator_delete +void operator delete(void * ptr) noexcept +{ + rtkFree(ptr, "buffer"); +} + +void operator delete[](void * ptr) noexcept +{ + rtkFree(ptr, "array"); +} + +// See https://en.cppreference.com/w/cpp/memory/new/operator_new +void * operator new(std::size_t count) +{ + return rtkMalloc(count, "new buffer"); +} + +void * operator new[](std::size_t count) +{ + return rtkMalloc(count, "new array"); +} + // Continue showing display until time threshold void finishDisplay() { @@ -353,12 +411,9 @@ void printReports() lastPrintPosition = millis(); } - if ((settings.enablePrintRoverAccuracy && (millis() - lastPrintRoverAccuracy > 2000)) || - (PERIODIC_DISPLAY(PD_MQTT_CLIENT_DATA))) + if (settings.enablePrintRoverAccuracy && (millis() - lastPrintRoverAccuracy > 2000)) { lastPrintRoverAccuracy = millis(); - PERIODIC_CLEAR(PD_MQTT_CLIENT_DATA); - if (online.gnss) { // If we are in rover mode, display HPA and SIV @@ -709,6 +764,7 @@ const char *coordinatePrintableInputType(CoordinateInputType coordinateInputType // Print the error message every 15 seconds void reportFatalError(const char *errorMsg) { + displayHalt(); while (1) { systemPrint("HALTED: "); @@ -811,3 +867,18 @@ void trim(char *str) memmove(str, p, l + 1); } + +// Read the MAC addresses directly from the chip +void getMacAddresses(uint8_t * macAddress, const char * name, esp_mac_type_t type, bool debug) +{ + esp_err_t status; + + status = esp_read_mac(macAddress, type); + if (status) + systemPrintf("ERROR: Failed to get %s, status: %d, %s\r\n", name, status, esp_err_to_name(status)); + if (debug) + systemPrintf("%02x:%02x:%02x:%02x:%02x:%02x - %s\r\n", + macAddress[0], macAddress[1], macAddress[2], + macAddress[3], macAddress[4], macAddress[5], + name); +}; diff --git a/Firmware/RTK_Everywhere/Tasks.ino b/Firmware/RTK_Everywhere/Tasks.ino index 17857c51..8c2223d0 100644 --- a/Firmware/RTK_Everywhere/Tasks.ino +++ b/Firmware/RTK_Everywhere/Tasks.ino @@ -1003,9 +1003,9 @@ void updateRingBufferTails(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET n // Trim any long or medium tails discardRingBufferBytes(&btRingBufferTail, previousTail, newTail); discardRingBufferBytes(&sdRingBufferTail, previousTail, newTail); - discardTcpClientBytes(previousTail, newTail); - discardTcpServerBytes(previousTail, newTail); - discardUdpServerBytes(previousTail, newTail); + tcpClientDiscardBytes(previousTail, newTail); + tcpServerDiscardBytes(previousTail, newTail); + udpServerDiscardBytes(previousTail, newTail); } // Remove previous messages from the ring buffer diff --git a/Firmware/RTK_Everywhere/TcpClient.ino b/Firmware/RTK_Everywhere/TcpClient.ino index d59ff2e3..0e67fdd9 100644 --- a/Firmware/RTK_Everywhere/TcpClient.ino +++ b/Firmware/RTK_Everywhere/TcpClient.ino @@ -156,7 +156,54 @@ static volatile bool tcpClientWriteError; // TCP Client handleGnssDataTask Support Routines //---------------------------------------- +//---------------------------------------- +// Remove previous messages from the ring buffer +//---------------------------------------- +void tcpClientDiscardBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) +{ + if (previousTail < newTail) + { + // No buffer wrap occurred + if ((tcpClientTail >= previousTail) && (tcpClientTail < newTail)) + tcpClientTail = newTail; + } + else + { + // Buffer wrap occurred + if ((tcpClientTail >= previousTail) || (tcpClientTail < newTail)) + tcpClientTail = newTail; + } +} + +//---------------------------------------- +// Determine if the TCP client is enabled +//---------------------------------------- +bool tcpClientEnabled(const char ** line) +{ + bool enabled; + + do + { + enabled = false; + + // Verify the operating mode + if (NEQ_RTK_MODE(tcpClientMode)) + { + *line = ", Wrong mode!"; + break; + } + + // Verify enabled + enabled = settings.enableTcpClient; + if (enabled == false) + *line = ", Not enabled!"; + } while (0); + return enabled; +} + +//---------------------------------------- // Send TCP data to the server +//---------------------------------------- int32_t tcpClientSendData(uint16_t dataHead) { bool connected; @@ -222,10 +269,12 @@ int32_t tcpClientSendData(uint16_t dataHead) return bytesToSend; } +//---------------------------------------- // Update the state of the TCP client state machine +//---------------------------------------- void tcpClientSetState(uint8_t newState) { - if ((settings.debugTcpClient || PERIODIC_DISPLAY(PD_TCP_CLIENT_STATE)) && (!inMainMenu)) + if (settings.debugTcpClient && (!inMainMenu)) { if (tcpClientState == newState) systemPrint("*"); @@ -233,9 +282,8 @@ void tcpClientSetState(uint8_t newState) systemPrintf("%s --> ", tcpClientStateName[tcpClientState]); } tcpClientState = newState; - if ((settings.debugTcpClient || PERIODIC_DISPLAY(PD_TCP_CLIENT_STATE)) && (!inMainMenu)) + if (settings.debugTcpClient && (!inMainMenu)) { - PERIODIC_CLEAR(PD_TCP_CLIENT_STATE); if (newState >= TCP_CLIENT_STATE_MAX) { systemPrintf("Unknown TCP Client state: %d\r\n", tcpClientState); @@ -246,28 +294,13 @@ void tcpClientSetState(uint8_t newState) } } -// Remove previous messages from the ring buffer -void discardTcpClientBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) -{ - if (previousTail < newTail) - { - // No buffer wrap occurred - if ((tcpClientTail >= previousTail) && (tcpClientTail < newTail)) - tcpClientTail = newTail; - } - else - { - // Buffer wrap occurred - if ((tcpClientTail >= previousTail) || (tcpClientTail < newTail)) - tcpClientTail = newTail; - } -} - //---------------------------------------- // TCP Client Routines //---------------------------------------- +//---------------------------------------- // Start the TCP client +//---------------------------------------- bool tcpClientStart() { NetworkClient *client; @@ -309,6 +342,8 @@ bool tcpClientStart() tcpClient = client; tcpClientWriteError = false; online.tcpClient = true; + if (settings.debugTcpClient) + systemPrintln("TCP client online"); return true; } else @@ -321,8 +356,10 @@ bool tcpClientStart() return false; } +//---------------------------------------- // Stop the TCP client -void tcpClientStop() +//---------------------------------------- +void tcpClientStop(bool shutdown) { NetworkClient *client; IPAddress ipAddress; @@ -331,6 +368,8 @@ void tcpClientStop() if (client) { // Delay to allow the UART task to finish with the tcpClient + if (settings.debugTcpClient && online.tcpClient) + systemPrintln("TCP client offline"); online.tcpClient = false; delay(5); @@ -349,26 +388,29 @@ void tcpClientStop() // Initialize the TCP client tcpClientWriteError = false; - if (settings.debugTcpClient) - systemPrintln("TCP client offline"); - tcpClientSetState(TCP_CLIENT_STATE_OFF); -} - -// Return true if we are in a state that requires network access -bool tcpClientNeedsNetwork() -{ - if (tcpClientState >= TCP_CLIENT_STATE_WAIT_FOR_NETWORK && tcpClientState <= TCP_CLIENT_STATE_CONNECTED) - return true; - return false; + networkConsumerOffline(NETCONSUMER_TCP_CLIENT); + if (shutdown) + { + // Stop the network + networkConsumerRemove(NETCONSUMER_TCP_CLIENT, NETWORK_ANY, __FILE__, __LINE__); + tcpClientSetState(TCP_CLIENT_STATE_OFF); + } + else + tcpClientSetState(TCP_CLIENT_STATE_WAIT_FOR_NETWORK); } +//---------------------------------------- // Update the TCP client state +//---------------------------------------- void tcpClientUpdate() { + bool connected; static uint8_t connectionAttempt; static uint32_t connectionDelay; uint32_t days; + bool enabled; byte hours; + const char * line = ""; uint64_t milliseconds; byte minutes; byte seconds; @@ -376,11 +418,15 @@ void tcpClientUpdate() // Shutdown the TCP client when the mode or setting changes DMW_st(tcpClientSetState, tcpClientState); - if (NEQ_RTK_MODE(tcpClientMode) || (!settings.enableTcpClient)) - { - if (tcpClientState > TCP_CLIENT_STATE_OFF) - tcpClientStop(); - } + connected = networkConsumerIsConnected(NETCONSUMER_TCP_CLIENT); + enabled = tcpClientEnabled(&line); + if ((enabled == false) && (tcpClientState > TCP_CLIENT_STATE_OFF)) + tcpClientStop(true); + + // Determine if the network has failed + else if ((tcpClientState > TCP_CLIENT_STATE_WAIT_FOR_NETWORK) + && !connected) + tcpClientStop(false); /* TCP Client state machine @@ -413,48 +459,58 @@ void tcpClientUpdate() // Wait until the TCP client is enabled case TCP_CLIENT_STATE_OFF: // Determine if the TCP client should be running - if (EQ_RTK_MODE(tcpClientMode) && settings.enableTcpClient) + if (enabled) { timer = 0; + connectionAttempt = 0; + connectionDelay = 0; tcpClientSetState(TCP_CLIENT_STATE_WAIT_FOR_NETWORK); + + // Start the network + networkConsumerAdd(NETCONSUMER_TCP_CLIENT, NETWORK_ANY, __FILE__, __LINE__); } break; // Wait until the network is connected case TCP_CLIENT_STATE_WAIT_FOR_NETWORK: - // Determine if the TCP client was turned off - if (NEQ_RTK_MODE(tcpClientMode) || !settings.enableTcpClient) - tcpClientStop(); - // Wait until the network is connected - else if (networkHasInternet()) + if (connected) { -#ifdef COMPILE_WIFI - // Determine if WiFi is required - if ((!strlen(settings.tcpClientHost)) && (!networkInterfaceHasInternet(NETWORK_WIFI))) + NetIndex_t index = networkGetCurrentInterfaceIndex(); + + // Check for a valid configuration + if (!networkInterfaceHasInternet(index)) { - // Wrong network type, WiFi is required but another network is being used + // Valid configurations + // 1. Phone: connection via WiFi, no host name, use gateway + // IP address as phone IP address + // 2. Host address, name or IP address of the server + bool usingGatewayIpAddress = (index == NETWORK_WIFI_STATION) + && (!strlen(settings.tcpClientHost)); + + // Invalid configuration, display a message on a regular + // basis until the issue is resolved if ((millis() - timer) >= (15 * 1000)) { timer = millis(); - systemPrintln("TCP Client must connect via WiFi when no host is specified"); + if (usingGatewayIpAddress) + systemPrintln("TCP Client must connect via WiFi when no host is specified"); + else + systemPrintln("TCP Client requires host name to be specified!"); } } -#else // COMPILE_WIFI - if (!strlen(settings.tcpClientHost)) + + // The network type and host provide a valid configuration + else { - // Wrong network type - if ((millis() - timer) >= (15 * 1000)) + // Connect immediately when the network changes + if (networkChanged(NETCONSUMER_TCP_CLIENT)) { - timer = millis(); - systemPrintln("TCP Client requires host name to be specified!"); + connectionAttempt = 0; + connectionDelay = 0; } - } -#endif // COMPILE_WIFI - // The network type and host provide a valid configuration - else - { timer = millis(); + networkUserAdd(NETCONSUMER_TCP_CLIENT, __FILE__, __LINE__); tcpClientSetState(TCP_CLIENT_STATE_CLIENT_STARTING); } } @@ -462,13 +518,8 @@ void tcpClientUpdate() // Attempt the connection ot the TCP server case TCP_CLIENT_STATE_CLIENT_STARTING: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - tcpClientStop(); - // Delay before connecting to the network - else if ((millis() - timer) >= connectionDelay) + if ((millis() - timer) >= connectionDelay) { timer = millis(); @@ -478,8 +529,12 @@ void tcpClientUpdate() // Connection failure if (settings.debugTcpClient) systemPrintln("TCP Client connection failed"); + + // Limit to max connection delay connectionDelay = TCP_DELAY_BETWEEN_CONNECTIONS << connectionAttempt; - if (connectionAttempt < TCP_MAX_CONNECTIONS) + if (connectionDelay > RTK_MAX_CONNECTION_MSEC) + connectionDelay = RTK_MAX_CONNECTION_MSEC; + else connectionAttempt += 1; // Display the uptime @@ -507,31 +562,34 @@ void tcpClientUpdate() // Wait for the TCP client to shutdown or a TCP client link failure case TCP_CLIENT_STATE_CONNECTED: - // Determine if the network has failed - if (networkHasInternet() == false) - // Failed to connect to to the network, attempt to restart the network - tcpClientStop(); - // Determine if the TCP client link is broken - else if ((!*tcpClient) || (!tcpClient->connected()) || tcpClientWriteError) + if ((!*tcpClient) || (!tcpClient->connected()) || tcpClientWriteError) // Stop the TCP client - tcpClientStop(); + tcpClientStop(false); break; } // Periodically display the TCP client state if (PERIODIC_DISPLAY(PD_TCP_CLIENT_STATE)) - tcpClientSetState(tcpClientState); + { + systemPrintf("TCP Client state: %s%s\r\n", + tcpClientStateName[tcpClientState], line); + PERIODIC_CLEAR(PD_TCP_CLIENT_STATE); + } } +//---------------------------------------- // Verify the TCP client tables +//---------------------------------------- void tcpClientValidateTables() { if (tcpClientStateNameEntries != TCP_CLIENT_STATE_MAX) reportFatalError("Fix tcpClientStateNameEntries to match tcpClientStates"); } +//---------------------------------------- // Zero the TCP client tail +//---------------------------------------- void tcpClientZeroTail() { tcpClientTail = 0; diff --git a/Firmware/RTK_Everywhere/TcpServer.ino b/Firmware/RTK_Everywhere/TcpServer.ino index fac03892..1b4067d1 100644 --- a/Firmware/RTK_Everywhere/TcpServer.ino +++ b/Firmware/RTK_Everywhere/TcpServer.ino @@ -29,6 +29,30 @@ tcpServer.ino '------------> Cell Phone <-------------------------------' for display + TCP Server Testing: + + Using Ethernet or WiFi station or AP on RTK Reference Station, starting + the TCP Server and running Vespucci on the cell phone: + + Vespucci Setup: + * Click on the gear icon + * Scroll down and click on Advanced preferences + * Click on Location settings + * Click on GPS/GNSS source + * Set to NMEA from TCP client + * Click on NMEA network source + * Set IP address to rtk-evk.local:2948 for Ethernet or WiFi station + * Set IP address to 192.168.4.1 for WiFi soft AP + * Uncheck Leave GPS/GNSS turned off + * Check Fallback to network location + * Click on Stale location after + * Set the value 5 seconds + * Exit the menus + + 1. Verify connection to the Vespucci application on the cell phone. + + 2. When the connection breaks, stop and restart Vespucci to create + a new connection to the TCP server. */ #ifdef COMPILE_NETWORK @@ -81,43 +105,94 @@ static volatile RING_BUFFER_OFFSET tcpServerClientTails[TCP_SERVER_MAX_CLIENTS]; // TCP Server handleGnssDataTask Support Routines //---------------------------------------- +//---------------------------------------- +// Remove previous messages from the ring buffer +//---------------------------------------- +void tcpServerDiscardBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) +{ + int index; + uint16_t tail; + + // Update each of the clients + for (index = 0; index < TCP_SERVER_MAX_CLIENTS; index++) + { + tail = tcpServerClientTails[index]; + if (previousTail < newTail) + { + // No buffer wrap occurred + if ((tail >= previousTail) && (tail < newTail)) + tcpServerClientTails[index] = newTail; + } + else + { + // Buffer wrap occurred + if ((tail >= previousTail) || (tail < newTail)) + tcpServerClientTails[index] = newTail; + } + } +} + +//---------------------------------------- // Send data to the TCP clients +//---------------------------------------- int32_t tcpServerClientSendData(int index, uint8_t *data, uint16_t length) { - length = tcpServerClient[index]->write(data, length); - if (length > 0) + if (tcpServerClient[index]) { - // Update the data sent flag when data successfully sent + length = tcpServerClient[index]->write(data, length); if (length > 0) - tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); - if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_CLIENT_DATA)) && (!inMainMenu)) { - PERIODIC_CLEAR(PD_TCP_SERVER_CLIENT_DATA); - systemPrintf("TCP server wrote %d bytes to %s\r\n", length, - tcpServerClientIpAddress[index].toString().c_str()); + // Update the data sent flag when data successfully sent + if (length > 0) + tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); + if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_CLIENT_DATA)) && (!inMainMenu)) + { + PERIODIC_CLEAR(PD_TCP_SERVER_CLIENT_DATA); + systemPrintf("TCP server wrote %d bytes to %s\r\n", length, + tcpServerClientIpAddress[index].toString().c_str()); + } + } + + // Failed to write the data + else + { + // Done with this client connection + tcpServerStopClient(index); + length = 0; } } + return length; +} - // Failed to write the data - else +//---------------------------------------- +// Determine if the TCP server may be enabled +//---------------------------------------- +bool tcpServerEnabled(const char ** line) +{ + bool enabled; + + do { - // Done with this client connection - if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_CLIENT_DATA)) && (!inMainMenu)) + enabled = false; + + // Verify the operating mode + if (NEQ_RTK_MODE(tcpServerMode)) { - PERIODIC_CLEAR(PD_TCP_SERVER_CLIENT_DATA); - systemPrintf("TCP server breaking connection %d with client %s\r\n", index, - tcpServerClientIpAddress[index].toString().c_str()); + *line = ", Wrong mode!"; + break; } - tcpServerClient[index]->stop(); - tcpServerClientConnected = tcpServerClientConnected & (~(1 << index)); - tcpServerClientWriteError = tcpServerClientWriteError | (1 << index); - length = 0; - } - return length; + // Verify still enabled + enabled = settings.enableTcpServer || settings.baseCasterOverride; + if (enabled == false) + *line = ", Not enabled!"; + } while (0); + return enabled; } +//---------------------------------------- // Send TCP data to the clients +//---------------------------------------- int32_t tcpServerSendData(uint16_t dataHead) { int32_t usedSpace = 0; @@ -170,39 +245,16 @@ int32_t tcpServerSendData(uint16_t dataHead) return usedSpace; } -// Remove previous messages from the ring buffer -void discardTcpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) -{ - int index; - uint16_t tail; - - // Update each of the clients - for (index = 0; index < TCP_SERVER_MAX_CLIENTS; index++) - { - tail = tcpServerClientTails[index]; - if (previousTail < newTail) - { - // No buffer wrap occurred - if ((tail >= previousTail) && (tail < newTail)) - tcpServerClientTails[index] = newTail; - } - else - { - // Buffer wrap occurred - if ((tail >= previousTail) || (tail < newTail)) - tcpServerClientTails[index] = newTail; - } - } -} - //---------------------------------------- // TCP Server Routines //---------------------------------------- +//---------------------------------------- // Update the state of the TCP server state machine +//---------------------------------------- void tcpServerSetState(uint8_t newState) { - if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_STATE)) && (!inMainMenu)) + if (settings.debugTcpServer && (!inMainMenu)) { if (tcpServerState == newState) systemPrint("*"); @@ -210,9 +262,8 @@ void tcpServerSetState(uint8_t newState) systemPrintf("%s --> ", tcpServerStateName[tcpServerState]); } tcpServerState = newState; - if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_STATE)) && (!inMainMenu)) + if (settings.debugTcpServer && (!inMainMenu)) { - PERIODIC_CLEAR(PD_TCP_SERVER_STATE); if (newState >= TCP_SERVER_STATE_MAX) { systemPrintf("Unknown TCP Server state: %d\r\n", tcpServerState); @@ -223,7 +274,9 @@ void tcpServerSetState(uint8_t newState) } } +//---------------------------------------- // Start the TCP server +//---------------------------------------- bool tcpServerStart() { IPAddress localIp; @@ -253,7 +306,9 @@ bool tcpServerStart() return true; } +//---------------------------------------- // Stop the TCP server +//---------------------------------------- void tcpServerStop() { int index; @@ -261,6 +316,9 @@ void tcpServerStop() // Notify the rest of the system that the TCP server is shutting down if (online.tcpServer) { + if (settings.debugTcpServer && (!inMainMenu)) + systemPrintf("TcpServer: Notifying GNSS UART task to stop sending data\r\n"); + // Notify the GNSS UART tasks of the TCP server shutdown online.tcpServer = false; delay(5); @@ -279,73 +337,84 @@ void tcpServerStop() { // Stop the TCP server if (settings.debugTcpServer && (!inMainMenu)) - systemPrintln("TCP server stopping"); + systemPrintln("TcpServer: Stopping the server"); tcpServer->stop(); delete tcpServer; tcpServer = nullptr; } // Stop using the network + if (settings.debugTcpServer && (!inMainMenu)) + systemPrintln("TcpServer: Stopping network consumers"); + networkConsumerOffline(NETCONSUMER_TCP_SERVER); if (tcpServerState != TCP_SERVER_STATE_OFF) { + networkSoftApConsumerRemove(NETCONSUMER_TCP_SERVER, __FILE__, __LINE__); + networkConsumerRemove(NETCONSUMER_TCP_SERVER, NETWORK_ANY, __FILE__, __LINE__); + // The TCP server is now off tcpServerSetState(TCP_SERVER_STATE_OFF); tcpServerTimer = millis(); } } +//---------------------------------------- // Stop the TCP server client +//---------------------------------------- void tcpServerStopClient(int index) { bool connected; bool dataSent; - // Done with this client connection - if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_DATA)) && (!inMainMenu)) + // Determine if a client was allocated + if (tcpServerClient[index]) { - PERIODIC_CLEAR(PD_TCP_SERVER_DATA); - - // Determine the shutdown reason - connected = tcpServerClient[index]->connected() && (!(tcpServerClientWriteError & (1 << index))); - dataSent = - ((millis() - tcpServerTimer) < TCP_SERVER_CLIENT_DATA_TIMEOUT) || (tcpServerClientDataSent & (1 << index)); - if (!dataSent) - systemPrintf("TCP Server: No data sent over %d seconds\r\n", TCP_SERVER_CLIENT_DATA_TIMEOUT / 1000); - if (!connected) - systemPrintf("TCP Server: Link to client broken\r\n"); - systemPrintf("TCP server client %d disconnected from %s\r\n", index, - tcpServerClientIpAddress[index].toString().c_str()); - } + // Done with this client connection + if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_DATA)) && (!inMainMenu)) + { + PERIODIC_CLEAR(PD_TCP_SERVER_DATA); + + // Determine the shutdown reason + connected = tcpServerClient[index]->connected() + && (!(tcpServerClientWriteError & (1 << index))); + dataSent = ((millis() - tcpServerTimer) < TCP_SERVER_CLIENT_DATA_TIMEOUT) + || (tcpServerClientDataSent & (1 << index)); + if (!dataSent) + systemPrintf("TCP Server: No data sent over %d seconds\r\n", TCP_SERVER_CLIENT_DATA_TIMEOUT / 1000); + if (!connected) + systemPrintf("TCP Server: Link to client broken\r\n"); + systemPrintf("TCP server client %d disconnected from %s\r\n", index, + tcpServerClientIpAddress[index].toString().c_str()); + } - // Shutdown the TCP server client link - tcpServerClient[index]->stop(); + // Shutdown the TCP server client link + tcpServerClient[index]->stop(); + delete tcpServerClient[index]; + tcpServerClient[index] = nullptr; + } tcpServerClientConnected = tcpServerClientConnected & (~(1 << index)); tcpServerClientWriteError = tcpServerClientWriteError & (~(1 << index)); } -// Return true if we are in a state that requires network access -bool tcpServerNeedsNetwork() -{ - if (tcpServerState >= TCP_SERVER_STATE_WAIT_FOR_NETWORK && tcpServerState <= TCP_SERVER_STATE_RUNNING) - return true; - return false; -} - +//---------------------------------------- // Update the TCP server state +//---------------------------------------- void tcpServerUpdate() { + bool clientConnected; bool connected; bool dataSent; + bool enabled; int index; IPAddress ipAddress; + const char * line = ""; // Shutdown the TCP server when the mode or setting changes DMW_st(tcpServerSetState, tcpServerState); - if (NEQ_RTK_MODE(tcpServerMode) || (!settings.enableTcpServer && !settings.baseCasterOverride)) - { - if (tcpServerState > TCP_SERVER_STATE_OFF) - tcpServerStop(); - } + connected = networkConsumerIsConnected(NETCONSUMER_TCP_SERVER); + enabled = tcpServerEnabled(&line); + if ((tcpServerState > TCP_SERVER_STATE_OFF) && !enabled) + tcpServerStop(); /* TCP Server state machine @@ -376,22 +445,22 @@ void tcpServerUpdate() // Wait until the TCP server is enabled case TCP_SERVER_STATE_OFF: // Determine if the TCP server should be running - if (EQ_RTK_MODE(tcpServerMode) && (settings.enableTcpServer || settings.baseCasterOverride)) + if (enabled) { if (settings.debugTcpServer && (!inMainMenu)) systemPrintln("TCP server start"); + if (settings.wifiConfigOverAP == false) + networkConsumerAdd(NETCONSUMER_TCP_SERVER, NETWORK_ANY, __FILE__, __LINE__); + else + networkSoftApConsumerAdd(NETCONSUMER_TCP_SERVER, __FILE__, __LINE__); tcpServerSetState(TCP_SERVER_STATE_WAIT_FOR_NETWORK); } break; // Wait until the network is connected case TCP_SERVER_STATE_WAIT_FOR_NETWORK: - // Determine if the TCP server was turned off - if (NEQ_RTK_MODE(tcpServerMode) || (!settings.enableTcpServer && !settings.baseCasterOverride)) - tcpServerStop(); - // Wait until the network is connected to the media - else if (networkHasInternet() || WIFI_SOFT_AP_RUNNING()) + if (connected || wifiSoftApOnline) { // Delay before starting the TCP server if ((millis() - tcpServerTimer) >= (1 * 1000)) @@ -401,7 +470,10 @@ void tcpServerUpdate() // Start the TCP server if (tcpServerStart()) + { + networkUserAdd(NETCONSUMER_TCP_SERVER, __FILE__, __LINE__); tcpServerSetState(TCP_SERVER_STATE_RUNNING); + } } } break; @@ -409,7 +481,7 @@ void tcpServerUpdate() // Handle client connections and link failures case TCP_SERVER_STATE_RUNNING: // Determine if the network has failed - if ((networkHasInternet() == false && WIFI_SOFT_AP_RUNNING() == false) || (!settings.enableTcpServer && !settings.baseCasterOverride)) + if ((connected == false && wifiSoftApOnline == false) || (!settings.enableTcpServer && !settings.baseCasterOverride)) { if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_DATA)) && (!inMainMenu)) { @@ -430,10 +502,10 @@ void tcpServerUpdate() { // Data structure in use // Check for a working TCP server client connection - connected = tcpServerClient[index]->connected(); + clientConnected = tcpServerClient[index]->connected(); dataSent = ((millis() - tcpServerTimer) < TCP_SERVER_CLIENT_DATA_TIMEOUT) || (tcpServerClientDataSent & (1 << index)); - if (connected && dataSent) + if (clientConnected && dataSent) { // Display this client connection if (PERIODIC_DISPLAY(PD_TCP_SERVER_DATA) && (!inMainMenu)) @@ -542,10 +614,16 @@ void tcpServerUpdate() // Periodically display the TCP state if (PERIODIC_DISPLAY(PD_TCP_SERVER_STATE) && (!inMainMenu)) - tcpServerSetState(tcpServerState); + { + systemPrintf("TCP Server state: %s%s\r\n", + tcpServerStateName[tcpServerState], line); + PERIODIC_CLEAR(PD_TCP_SERVER_STATE); + } } +//---------------------------------------- // Verify the TCP server tables +//---------------------------------------- void tcpServerValidateTables() { char line[128]; @@ -562,7 +640,9 @@ void tcpServerValidateTables() reportFatalError("Fix tcpServerStateNameEntries to match tcpServerStates"); } +//---------------------------------------- // Zero the TCP server client tails +//---------------------------------------- void tcpServerZeroTail() { int index; diff --git a/Firmware/RTK_Everywhere/UdpServer.ino b/Firmware/RTK_Everywhere/UdpServer.ino index 3a9a8d27..39de22ef 100644 --- a/Firmware/RTK_Everywhere/UdpServer.ino +++ b/Firmware/RTK_Everywhere/UdpServer.ino @@ -45,7 +45,7 @@ UdpServer.ino * Click on Positioning * Add a new Positioning device * Set Connection type to UDP (NMEA) - * Set the Address to + * Set the Address to * Set the Port to the value of the specified udpServerPort (default 10110) * Optional: give it a name (e.g. RTK Express UDP) * Click on the Checkmark @@ -99,40 +99,58 @@ static volatile RING_BUFFER_OFFSET udpServerTail; // UDP Server handleGnssDataTask Support Routines //---------------------------------------- -// Send data as broadcast -int32_t udpServerSendDataBroadcast(uint8_t *data, uint16_t length) +//---------------------------------------- +// Remove previous messages from the ring buffer +//---------------------------------------- +void udpServerDiscardBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) { - if (!length) - return 0; + // int index; + uint16_t tail; - // Send the data as broadcast - if (settings.enableUdpServer && online.udpServer && networkHasInternet()) + tail = udpServerTail; + if (previousTail < newTail) { - IPAddress broadcastAddress = networkGetBroadcastIpAddress(); - udpServer->beginPacket(broadcastAddress, settings.udpServerPort); - udpServer->write(data, length); - if (udpServer->endPacket()) - { - if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_BROADCAST_DATA)) && (!inMainMenu)) - { - systemPrintf("UDP Server wrote %d bytes as broadcast (%s) on port %d\r\n", length, - broadcastAddress.toString().c_str(), settings.udpServerPort); - PERIODIC_CLEAR(PD_UDP_SERVER_BROADCAST_DATA); - } - } - // Failed to write the data - else if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_BROADCAST_DATA)) && (!inMainMenu)) + // No buffer wrap occurred + if ((tail >= previousTail) && (tail < newTail)) + udpServerTail = newTail; + } + else + { + // Buffer wrap occurred + if ((tail >= previousTail) || (tail < newTail)) + udpServerTail = newTail; + } +} + +//---------------------------------------- +// Determine if the UDP server may be enabled +//---------------------------------------- +bool udpServerEnabled(const char ** line) +{ + bool enabled; + + do + { + enabled = false; + + // Verify the operating mode + if (NEQ_RTK_MODE(udpServerMode)) { - PERIODIC_CLEAR(PD_UDP_SERVER_BROADCAST_DATA); - systemPrintf("UDP Server failed to write %d bytes as broadcast (%s) on port %d\r\n", length, - broadcastAddress.toString().c_str(), settings.udpServerPort); - length = 0; + *line = ", Wrong mode!"; + break; } - } - return length; + + // Verify still enabled + enabled = settings.enableUdpServer; + if (enabled == false) + *line = ", Not enabled!"; + } while (0); + return enabled; } +//---------------------------------------- // Send UDP data as broadcast +//---------------------------------------- int32_t udpServerSendData(uint16_t dataHead) { int32_t usedSpace = 0; @@ -176,35 +194,51 @@ int32_t udpServerSendData(uint16_t dataHead) return usedSpace; } -// Remove previous messages from the ring buffer -void discardUdpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) +//---------------------------------------- +// Send data as broadcast +//---------------------------------------- +int32_t udpServerSendDataBroadcast(uint8_t *data, uint16_t length) { - // int index; - uint16_t tail; + if (!length) + return 0; - tail = udpServerTail; - if (previousTail < newTail) - { - // No buffer wrap occurred - if ((tail >= previousTail) && (tail < newTail)) - udpServerTail = newTail; - } - else + // Send the data as broadcast + if (settings.enableUdpServer && online.udpServer && networkConsumerIsConnected(NETCONSUMER_UDP_SERVER)) { - // Buffer wrap occurred - if ((tail >= previousTail) || (tail < newTail)) - udpServerTail = newTail; + IPAddress broadcastAddress = networkGetBroadcastIpAddress(); + udpServer->beginPacket(broadcastAddress, settings.udpServerPort); + udpServer->write(data, length); + if (udpServer->endPacket()) + { + if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_BROADCAST_DATA)) && (!inMainMenu)) + { + systemPrintf("UDP Server wrote %d bytes as broadcast (%s) on port %d\r\n", length, + broadcastAddress.toString().c_str(), settings.udpServerPort); + PERIODIC_CLEAR(PD_UDP_SERVER_BROADCAST_DATA); + } + } + // Failed to write the data + else if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_BROADCAST_DATA)) && (!inMainMenu)) + { + PERIODIC_CLEAR(PD_UDP_SERVER_BROADCAST_DATA); + systemPrintf("UDP Server failed to write %d bytes as broadcast (%s) on port %d\r\n", length, + broadcastAddress.toString().c_str(), settings.udpServerPort); + length = 0; + } } + return length; } //---------------------------------------- // UDP Server Routines //---------------------------------------- +//---------------------------------------- // Update the state of the UDP server state machine +//---------------------------------------- void udpServerSetState(uint8_t newState) { - if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_STATE)) && (!inMainMenu)) + if (settings.debugUdpServer && (!inMainMenu)) { if (udpServerState == newState) systemPrint("UDP Server: *"); @@ -212,9 +246,8 @@ void udpServerSetState(uint8_t newState) systemPrintf("UDP Server: %s --> ", udpServerStateName[udpServerState]); } udpServerState = newState; - if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_STATE)) && (!inMainMenu)) + if (settings.debugUdpServer && (!inMainMenu)) { - PERIODIC_CLEAR(PD_UDP_SERVER_STATE); if (newState >= UDP_SERVER_STATE_MAX) { systemPrintf("Unknown state: %d\r\n", udpServerState); @@ -225,7 +258,9 @@ void udpServerSetState(uint8_t newState) } } +//---------------------------------------- // Start the UDP server +//---------------------------------------- bool udpServerStart() { IPAddress localIp; @@ -244,7 +279,9 @@ bool udpServerStart() return true; } +//---------------------------------------- // Stop the UDP server +//---------------------------------------- void udpServerStop() { // Notify the rest of the system that the UDP server is shutting down @@ -267,34 +304,33 @@ void udpServerStop() } // Stop using the network + networkConsumerOffline(NETCONSUMER_UDP_SERVER); if (udpServerState != UDP_SERVER_STATE_OFF) { // The UDP server is now off + networkSoftApConsumerRemove(NETCONSUMER_UDP_SERVER, __FILE__, __LINE__); + networkConsumerRemove(NETCONSUMER_UDP_SERVER, NETWORK_ANY, __FILE__, __LINE__); udpServerSetState(UDP_SERVER_STATE_OFF); udpServerTimer = millis(); } } -// Return true if we are in a state that requires network access -bool udpServerNeedsNetwork() -{ - if (udpServerState >= UDP_SERVER_STATE_WAIT_FOR_NETWORK && udpServerState <= UDP_SERVER_STATE_RUNNING) - return true; - return false; -} - +//---------------------------------------- // Update the UDP server state +//---------------------------------------- void udpServerUpdate() { + bool connected; + bool enabled; IPAddress ipAddress; + const char * line = ""; // Shutdown the UDP server when the mode or setting changes DMW_st(udpServerSetState, udpServerState); - if (NEQ_RTK_MODE(udpServerMode) || (!settings.enableUdpServer)) - { - if (udpServerState > UDP_SERVER_STATE_OFF) - udpServerStop(); - } + connected = networkConsumerIsConnected(NETCONSUMER_UDP_SERVER); + enabled = udpServerEnabled(&line); + if ((!enabled) && (udpServerState > UDP_SERVER_STATE_OFF)) + udpServerStop(); /* UDP Server state machine @@ -320,22 +356,22 @@ void udpServerUpdate() // Wait until the UDP server is enabled case UDP_SERVER_STATE_OFF: // Determine if the UDP server should be running - if (EQ_RTK_MODE(udpServerMode) && settings.enableUdpServer) + if (enabled) { if (settings.debugUdpServer && (!inMainMenu)) systemPrintln("UDP server starting the network"); + if (settings.wifiConfigOverAP == false) + networkConsumerAdd(NETCONSUMER_UDP_SERVER, NETWORK_ANY, __FILE__, __LINE__); + else + networkSoftApConsumerAdd(NETCONSUMER_UDP_SERVER, __FILE__, __LINE__); udpServerSetState(UDP_SERVER_STATE_WAIT_FOR_NETWORK); } break; // Wait until the network is connected case UDP_SERVER_STATE_WAIT_FOR_NETWORK: - // Determine if the UDP server was turned off - if (NEQ_RTK_MODE(udpServerMode) || !settings.enableUdpServer) - udpServerStop(); - // Wait until the network is connected - else if (networkHasInternet() || WIFI_SOFT_AP_RUNNING()) + if (connected || wifiSoftApRunning) { // Delay before starting the UDP server if ((millis() - udpServerTimer) >= (1 * 1000)) @@ -345,7 +381,10 @@ void udpServerUpdate() // Start the UDP server if (udpServerStart()) + { + networkUserAdd(NETCONSUMER_UDP_SERVER, __FILE__, __LINE__); udpServerSetState(UDP_SERVER_STATE_RUNNING); + } } } break; @@ -353,7 +392,7 @@ void udpServerUpdate() // Handle client connections and link failures case UDP_SERVER_STATE_RUNNING: // Determine if the network has failed - if ((networkHasInternet() == false && WIFI_SOFT_AP_RUNNING() == false) || (!settings.enableUdpServer && !settings.baseCasterOverride)) + if ((connected == false && wifiSoftApRunning == false) || (!settings.enableUdpServer && !settings.baseCasterOverride)) { if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_DATA)) && (!inMainMenu)) { @@ -370,10 +409,16 @@ void udpServerUpdate() // Periodically display the UDP state if (PERIODIC_DISPLAY(PD_UDP_SERVER_STATE) && (!inMainMenu)) - udpServerSetState(udpServerState); + { + systemPrintf("UDP Server state: %s%s\r\n", + udpServerStateName[udpServerState], line); + PERIODIC_CLEAR(PD_UDP_SERVER_STATE); + } } +//---------------------------------------- // Zero the UDP server tails +//---------------------------------------- void udpServerZeroTail() { udpServerTail = 0; diff --git a/Firmware/RTK_Everywhere/WebServer.ino b/Firmware/RTK_Everywhere/WebServer.ino index 59dadd7e..33010636 100644 --- a/Firmware/RTK_Everywhere/WebServer.ino +++ b/Firmware/RTK_Everywhere/WebServer.ino @@ -6,6 +6,10 @@ WebServer.ino #ifdef COMPILE_AP +//---------------------------------------- +// Constants +//---------------------------------------- + // State machine to allow web server access to network layer enum WebServerState { @@ -25,6 +29,26 @@ static const char *const webServerStateNames[] = { "WEBSERVER_STATE_RUNNING", }; +//---------------------------------------- +// Macros +//---------------------------------------- + +#define GET_PAGE(page, type, data) \ + webServer->on(page, HTTP_GET, []() { \ + String length; \ + if (settings.debugWebServer == true) \ + Serial.printf("WebServer: Sending %s (%p, %d bytes)\r\n", \ + page, (void *)data, sizeof(data)); \ + webServer->sendHeader("Content-Encoding", "gzip"); \ + length = String(sizeof(data)); \ + webServer->sendHeader("Content-Length", length.c_str()); \ + webServer->send_P(200, type, (const char *)data, sizeof(data)); \ + }); + +//---------------------------------------- +// Locals +//---------------------------------------- + static const int webServerStateEntries = sizeof(webServerStateNames) / sizeof(webServerStateNames[0]); static uint8_t webServerState; @@ -32,7 +56,16 @@ static uint8_t webServerState; // Once connected to the access point for WiFi Config, the ESP32 sends current setting values in one long string to // websocket After user clicks 'save', data is validated via main.js and a long string of values is returned. -bool websocketConnected = false; +static httpd_handle_t wsserver; +static WebServer *webServer; + +// httpd_req_t *last_ws_req; +static int last_ws_fd; + +static TaskHandle_t updateWebServerTaskHandle; +static const uint8_t updateWebServerTaskPriority = 0; // 3 being the highest, and 0 being the lowest +static const int webServerTaskStackSize = 4096; // Needs to be large enough to hold the file manager file list +static const int webSocketStackSize = 8192; // Inspired by: // https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/MultiHomedServers/MultiHomedServers.ino @@ -45,185 +78,10 @@ bool websocketConnected = false; // https://github.com/mo-thunderz/Esp32WifiPart2/blob/main/Arduino/ESP32WebserverWebsocket/ESP32WebserverWebsocket.ino // https://www.youtube.com/watch?v=15X0WvGaVg8 -void sendStringToWebsocket(const char *stringToSend) -{ - if (!websocketConnected) - { - systemPrintf("sendStringToWebsocket: not connected - could not send: %s\r\n", stringToSend); - return; - } - - // To send content to the webServer, we would call: webServer->sendContent(stringToSend); - // But here we want to send content to the websocket (wsserver)... - - httpd_ws_frame_t ws_pkt; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - ws_pkt.payload = (uint8_t *)stringToSend; - ws_pkt.len = strlen(stringToSend); - ws_pkt.type = HTTPD_WS_TYPE_TEXT; - - // If we use httpd_ws_send_frame, it requires a req. - // esp_err_t ret = httpd_ws_send_frame(last_ws_req, &ws_pkt); - // if (ret != ESP_OK) { - // ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret); - //} - - // If we use httpd_ws_send_frame_async, it requires a fd. - esp_err_t ret = httpd_ws_send_frame_async(*wsserver, last_ws_fd, &ws_pkt); - if (ret != ESP_OK) - { - systemPrintf("httpd_ws_send_frame failed with %d\r\n", ret); - } - else - { - if (settings.debugWebServer == true) - systemPrintf("sendStringToWebsocket: %s\r\n", stringToSend); - } -} - -static esp_err_t ws_handler(httpd_req_t *req) -{ - // Log the req, so we can reuse it for httpd_ws_send_frame - // TODO: do we need to be cleverer about this? - // last_ws_req = req; - - if (req->method == HTTP_GET) - { - // Log the fd, so we can reuse it for httpd_ws_send_frame_async - // TODO: do we need to be cleverer about this? - last_ws_fd = httpd_req_to_sockfd(req); - - if (settings.debugWebServer == true) - systemPrintf("Handshake done, the new ws connection was opened with fd %d\r\n", last_ws_fd); - - websocketConnected = true; - lastDynamicDataUpdate = millis(); - sendStringToWebsocket(settingsCSV); - - return ESP_OK; - } - - httpd_ws_frame_t ws_pkt; - uint8_t *buf = NULL; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - ws_pkt.type = HTTPD_WS_TYPE_TEXT; - /* Set max_len = 0 to get the frame len */ - esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0); - if (ret != ESP_OK) - { - systemPrintf("httpd_ws_recv_frame failed to get frame len with %d\r\n", ret); - return ret; - } - if (settings.debugWebServer == true) - systemPrintf("frame len is %d\r\n", ws_pkt.len); - if (ws_pkt.len) - { - /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ - if (online.psram == true) - buf = (uint8_t *)ps_malloc(ws_pkt.len + 1); - else - buf = (uint8_t *)malloc(ws_pkt.len + 1); - - if (buf == NULL) - { - systemPrintln("Failed to malloc memory for buf"); - return ESP_ERR_NO_MEM; - } - ws_pkt.payload = buf; - /* Set max_len = ws_pkt.len to get the frame payload */ - ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len); - if (ret != ESP_OK) - { - systemPrintf("httpd_ws_recv_frame failed with %d\r\n", ret); - free(buf); - return ret; - } - } - if (settings.debugWebServer == true) - systemPrintf("Packet type: %d\r\n", ws_pkt.type); - // HTTPD_WS_TYPE_CONTINUE = 0x0, - // HTTPD_WS_TYPE_TEXT = 0x1, - // HTTPD_WS_TYPE_BINARY = 0x2, - // HTTPD_WS_TYPE_CLOSE = 0x8, - // HTTPD_WS_TYPE_PING = 0x9, - // HTTPD_WS_TYPE_PONG = 0xA - - if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) - { - if (settings.debugWebServer == true) - { - systemPrintf("Got packet with message: %s\r\n", ws_pkt.payload); - dumpBuffer(ws_pkt.payload, ws_pkt.len); - } - - if (currentlyParsingData == false) - { - for (int i = 0; i < ws_pkt.len; i++) - { - incomingSettings[incomingSettingsSpot++] = ws_pkt.payload[i]; - incomingSettingsSpot %= AP_CONFIG_SETTING_SIZE; - } - timeSinceLastIncomingSetting = millis(); - } - else - { - if (settings.debugWebServer == true) - systemPrintln("Ignoring packet due to parsing block"); - } - } - else if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) - { - if (settings.debugWebServer == true) - systemPrintln("Client closed or refreshed the web page"); - - createSettingsString(settingsCSV); - websocketConnected = false; - } - - free(buf); - return ret; -} - -static const httpd_uri_t ws = {.uri = "/ws", - .method = HTTP_GET, - .handler = ws_handler, - .user_ctx = NULL, - .is_websocket = true, - .handle_ws_control_frames = true, - .supported_subprotocol = NULL}; - -bool websocketServerStart(void) -{ - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - - // Use different ports for websocket and webServer - use port 81 for the websocket - also defined in main.js - config.server_port = 81; - - // Increase the stack size from 4K to ~15K - config.stack_size = updateWebSocketStackSize; - - // Start the httpd server - if (settings.debugWebServer == true) - systemPrintf("Starting wsserver on port: %d\r\n", config.server_port); - - if (wsserver == nullptr) - wsserver = new httpd_handle_t; - - if (httpd_start(wsserver, &config) == ESP_OK) - { - // Registering the ws handler - if (settings.debugWebServer == true) - systemPrintln("Registering URI handlers"); - httpd_register_uri_handler(*wsserver, &ws); - return true; - } - - systemPrintln("Error starting wsserver!"); - return false; -} - +//---------------------------------------- // ===== Request Handler class used to answer more complex requests ===== // https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/WebServer/WebServer.ino +//---------------------------------------- class CaptiveRequestHandler : public RequestHandler { public: @@ -269,552 +127,514 @@ class CaptiveRequestHandler : public RequestHandler } }; -// Create the web server and web sockets -bool webServerAssignResources(int httpPort = 80) +//---------------------------------------- +// Create a csv string with the dynamic data to update (current coordinates, battery level, etc) +//---------------------------------------- +void createDynamicDataString(char *settingsCSV) { - do - { - // Freed by webServerStop - if (online.psram == true) - incomingSettings = (char *)ps_malloc(AP_CONFIG_SETTING_SIZE); - else - incomingSettings = (char *)malloc(AP_CONFIG_SETTING_SIZE); - - if (!incomingSettings) - { - systemPrintln("ERROR: Failed to allocate incomingSettings"); - break; - } - memset(incomingSettings, 0, AP_CONFIG_SETTING_SIZE); - - // Pre-load settings CSV - // Freed by webServerStop - if (online.psram == true) - settingsCSV = (char *)ps_malloc(AP_CONFIG_SETTING_SIZE); - else - settingsCSV = (char *)malloc(AP_CONFIG_SETTING_SIZE); - - if (!settingsCSV) - { - systemPrintln("ERROR: Failed to allocate settingsCSV"); - break; - } - createSettingsString(settingsCSV); + settingsCSV[0] = '\0'; // Erase current settings string - // - https: // github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino - if (settings.enableCaptivePortal == true) - { - dnsserver = new DNSServer; - dnsserver->start(); - } + // Current coordinates come from HPPOSLLH call back + stringRecord(settingsCSV, "geodeticLat", gnss->getLatitude(), haeNumberOfDecimals); + stringRecord(settingsCSV, "geodeticLon", gnss->getLongitude(), haeNumberOfDecimals); + stringRecord(settingsCSV, "geodeticAlt", gnss->getAltitude(), 3); - webServer = new WebServer(httpPort); - if (!webServer) - { - systemPrintln("ERROR: Failed to allocate webServer"); - break; - } + double ecefX = 0; + double ecefY = 0; + double ecefZ = 0; - if (settings.enableCaptivePortal == true) - { - webServer->addHandler(new CaptiveRequestHandler()); - } + geodeticToEcef(gnss->getLatitude(), gnss->getLongitude(), gnss->getAltitude(), &ecefX, &ecefY, &ecefZ); - // * index.html (not gz'd) - // * favicon.ico + stringRecord(settingsCSV, "ecefX", ecefX, 3); + stringRecord(settingsCSV, "ecefY", ecefY, 3); + stringRecord(settingsCSV, "ecefZ", ecefZ, 3); - // * /src/bootstrap.bundle.min.js - Needed for popper - // * /src/bootstrap.min.css - // * /src/bootstrap.min.js - // * /src/jquery-3.6.0.min.js - // * /src/main.js (not gz'd) - // * /src/rtk-setup.png - // * /src/style.css + if (online.batteryFuelGauge == false) // Product has no battery + { + stringRecord(settingsCSV, "batteryIconFileName", (char *)"src/BatteryBlank.png"); + stringRecord(settingsCSV, "batteryPercent", (char *)" "); + } + else + { + // Determine battery icon + int iconLevel = 0; + if (batteryLevelPercent < 25) + iconLevel = 0; + else if (batteryLevelPercent < 50) + iconLevel = 1; + else if (batteryLevelPercent < 75) + iconLevel = 2; + else // batt level > 75 + iconLevel = 3; - // * /src/fonts/icomoon.eot - // * /src/fonts/icomoon.svg - // * /src/fonts/icomoon.ttf - // * /src/fonts/icomoon.woof + char batteryIconFileName[sizeof("src/Battery2_Charging.png__")]; // sizeof() includes 1 for \0 termination - // * /listfiles responds with a CSV of files and sizes in root - // * /listMessages responds with a CSV of messages supported by this platform - // * /listMessagesBase responds with a CSV of RTCM Base messages supported by this platform - // * /file allows the download or deletion of a file + if (isCharging()) + snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d_Charging.png", iconLevel); + else + snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d.png", iconLevel); - webServer->onNotFound(notFound); + stringRecord(settingsCSV, "batteryIconFileName", batteryIconFileName); - webServer->on("/", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/html", (const char *)index_html, sizeof(index_html)); - }); + // Limit batteryLevelPercent to sane levels + if (batteryLevelPercent > 100) + batteryLevelPercent = 100; - webServer->on("/favicon.ico", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/plain", (const char *)favicon_ico, sizeof(favicon_ico)); - }); + // Determine battery percent + char batteryPercent[sizeof("+100%__")]; + if (isCharging()) + snprintf(batteryPercent, sizeof(batteryPercent), "+%d%%", batteryLevelPercent); + else + snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", batteryLevelPercent); + stringRecord(settingsCSV, "batteryPercent", batteryPercent); + } - webServer->on("/src/bootstrap.bundle.min.js", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/javascript", (const char *)bootstrap_bundle_min_js, - sizeof(bootstrap_bundle_min_js)); - }); + strcat(settingsCSV, "\0"); +} - webServer->on("/src/bootstrap.min.css", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/css", (const char *)bootstrap_min_css, sizeof(bootstrap_min_css)); - }); +//---------------------------------------- +// Report back to the web config page with a CSV that contains the either CURRENT or +// the latest version as obtained by the OTA state machine +//---------------------------------------- +void createFirmwareVersionString(char *settingsCSV) +{ + char newVersionCSV[100]; - webServer->on("/src/bootstrap.min.js", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/javascript", (const char *)bootstrap_min_js, sizeof(bootstrap_min_js)); - }); + settingsCSV[0] = '\0'; // Erase current settings string - webServer->on("/src/jquery-3.6.0.min.js", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/javascript", (const char *)jquery_js, sizeof(jquery_js)); - }); + // Create a string of the unit's current firmware version + char currentVersion[21]; + firmwareVersionGet(currentVersion, sizeof(currentVersion), enableRCFirmware); - webServer->on("/src/main.js", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/javascript", (const char *)main_js, sizeof(main_js)); - }); + // Compare the unit's version against the reported version from OTA + if (firmwareVersionIsReportedNewer(otaReportedVersion, currentVersion) == true) + { + if (settings.debugWebServer == true) + systemPrintln("New version detected"); + snprintf(newVersionCSV, sizeof(newVersionCSV), "%s,", otaReportedVersion); + } + else + { + if (settings.debugWebServer == true) + systemPrintln("No new firmware available"); + snprintf(newVersionCSV, sizeof(newVersionCSV), "CURRENT,"); + } - webServer->on("/src/rtk-setup.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - if (productVariant == RTK_EVK) - webServer->send_P(200, "image/png", (const char *)rtkSetup_png, sizeof(rtkSetup_png)); - else - webServer->send_P(200, "image/png", (const char *)rtkSetupWiFi_png, sizeof(rtkSetupWiFi_png)); - }); + stringRecord(settingsCSV, "newFirmwareVersion", newVersionCSV); - // Battery icons - webServer->on("/src/BatteryBlank.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)batteryBlank_png, sizeof(batteryBlank_png)); - }); - webServer->on("/src/Battery0.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery0_png, sizeof(battery0_png)); - }); - webServer->on("/src/Battery1.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery1_png, sizeof(battery1_png)); - }); - webServer->on("/src/Battery2.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery2_png, sizeof(battery2_png)); - }); - webServer->on("/src/Battery3.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery3_png, sizeof(battery3_png)); - }); + strcat(settingsCSV, "\0"); +} - webServer->on("/src/Battery0_Charging.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery0_Charging_png, sizeof(battery0_Charging_png)); - }); - webServer->on("/src/Battery1_Charging.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery1_Charging_png, sizeof(battery1_Charging_png)); - }); - webServer->on("/src/Battery2_Charging.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery2_Charging_png, sizeof(battery2_Charging_png)); - }); - webServer->on("/src/Battery3_Charging.png", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "image/png", (const char *)battery3_Charging_png, sizeof(battery3_Charging_png)); - }); +//---------------------------------------- +// When called, responds with the messages supported on this platform +// Message name and current rate are formatted in CSV, formatted to html by JS +//---------------------------------------- +void createMessageList(String &returnText) +{ + returnText = ""; + gnss->createMessageList(returnText); + if (settings.debugWebServer == true) + systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); +} - webServer->on("/src/style.css", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/css", (const char *)style_css, sizeof(style_css)); - }); +//---------------------------------------- +// When called, responds with the RTCM/Base messages supported on this platform +// Message name and current rate are formatted in CSV, formatted to html by JS +//---------------------------------------- +void createMessageListBase(String &returnText) +{ + returnText = ""; + gnss->createMessageListBase(returnText); + if (settings.debugWebServer == true) + systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); +} - webServer->on("/src/fonts/icomoon.eot", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/plain", (const char *)icomoon_eot, sizeof(icomoon_eot)); - }); +//---------------------------------------- +// When called, responds with the root folder list of files on SD card +// Name and size are formatted in CSV, formatted to html by JS +//---------------------------------------- +void getFileList(String &returnText) +{ + returnText = ""; - webServer->on("/src/fonts/icomoon.svg", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/plain", (const char *)icomoon_svg, sizeof(icomoon_svg)); - }); + // Update the SD Size and Free Space + String cardSize; + stringHumanReadableSize(cardSize, sdCardSize); + returnText += "sdSize," + cardSize + ","; + String freeSpace; + stringHumanReadableSize(freeSpace, sdFreeSpace); + returnText += "sdFreeSpace," + freeSpace + ","; - webServer->on("/src/fonts/icomoon.ttf", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/plain", (const char *)icomoon_ttf, sizeof(icomoon_ttf)); - }); + char fileName[50]; // Handle long file names - webServer->on("/src/fonts/icomoon.woof", HTTP_GET, []() { - webServer->sendHeader("Content-Encoding", "gzip"); - webServer->send_P(200, "text/plain", (const char *)icomoon_woof, sizeof(icomoon_woof)); - }); + // Attempt to gain access to the SD card + if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) + { + markSemaphore(FUNCTION_FILEMANAGER_UPLOAD1); - // https://lemariva.com/blog/2017/11/white-hacking-wemos-captive-portal-using-micropython - webServer->on("/connecttest.txt", HTTP_GET, - []() { webServer->send(200, "text/plain", "Microsoft Connect Test"); }); + SdFile root; + root.open("/"); // Open root + SdFile file; + uint16_t fileCount = 0; - // Handler for the /uploadFile form POST - webServer->on( - "/uploadFile", HTTP_POST, []() { webServer->send(200, "text/plain", ""); }, - handleUpload); // Run handleUpload function when file manager file is uploaded + while (file.openNext(&root, O_READ)) + { + if (file.isFile()) + { + fileCount++; - // Handler for the /uploadFirmware form POST - webServer->on( - "/uploadFirmware", HTTP_POST, []() { webServer->send(200, "text/plain", ""); }, handleFirmwareFileUpload); + file.getName(fileName, sizeof(fileName)); - // Handler for file manager - webServer->on("/listfiles", HTTP_GET, []() { - String logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); - if (settings.debugWebServer == true) - systemPrintln(logmessage); - String files; - getFileList(files); - webServer->send(200, "text/plain", files); - }); + String fileSize; + stringHumanReadableSize(fileSize, file.fileSize()); + returnText += "fmName," + String(fileName) + ",fmSize," + fileSize + ","; + } + } - // Handler for supported messages list - webServer->on("/listMessages", HTTP_GET, []() { - String logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); - if (settings.debugWebServer == true) - systemPrintln(logmessage); - String messages; - createMessageList(messages); - if (settings.debugWebServer == true) - systemPrintln(messages); - webServer->send(200, "text/plain", messages); - }); + root.close(); + file.close(); - // Handler for supported RTCM/Base messages list - webServer->on("/listMessagesBase", HTTP_GET, []() { - String logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); - if (settings.debugWebServer == true) - systemPrintln(logmessage); - String messageList; - createMessageListBase(messageList); - if (settings.debugWebServer == true) - systemPrintln(messageList); - webServer->send(200, "text/plain", messageList); - }); + xSemaphoreGive(sdCardSemaphore); + } + else + { + char semaphoreHolder[50]; + getSemaphoreFunction(semaphoreHolder); - // Handler for file manager - webServer->on("/file", HTTP_GET, handleFileManager); + // This is an error because the current settings no longer match the settings + // on the microSD card, and will not be restored to the expected settings! + systemPrintf("sdCardSemaphore failed to yield, held by %s, Form.ino line %d\r\n", semaphoreHolder, __LINE__); + } - webServer->begin(); + if (settings.debugWebServer == true) + systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); +} - // Starts task for updating webServer with handleClient - if (task.updateWebServerTaskRunning == false) - xTaskCreate( - updateWebServerTask, - "UpdateWebServer", // Just for humans - updateWebServerTaskStackSize, // Stack Size - needs to be large enough to hold the file manager list - nullptr, // Task input parameter - updateWebServerTaskPriority, - &updateWebServerTaskHandle); // Task handle +//---------------------------------------- +// Handler for firmware file downloads +//---------------------------------------- +static void handleFileManager() +{ + // This section does not tolerate semaphore transactions + String logmessage = + "handleFileManager: Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); - if (settings.debugWebServer == true) - systemPrintln("Web Server Started"); - reportHeapNow(false); + if (webServer->hasArg("name") && webServer->hasArg("action")) + { + String fileName = webServer->arg("name"); + String fileAction = webServer->arg("action"); - // Start the web socket server on port 81 using - if (websocketServerStart() == false) - { - if (settings.debugWebServer == true) - systemPrintln("Web Sockets failed to start"); + logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri() + + "?name=" + fileName + "&action=" + fileAction; + + char slashFileName[60]; + snprintf(slashFileName, sizeof(slashFileName), "/%s", fileName.c_str()); - webServerStopSockets(); - webServerReleaseResources(); + bool fileExists = sd->exists(slashFileName); - return (false); + if (fileExists == false) + { + logmessage += " ERROR: file "; + logmessage += slashFileName; + logmessage += " does not exist"; + webServer->send(400, "text/plain", "ERROR: file does not exist"); } + else + { + logmessage += " file exists"; - if (settings.debugWebServer == true) - systemPrintln("Web Socket Server Started"); - reportHeapNow(false); + if (fileAction == "download") + { + logmessage += " downloaded"; - return true; - } while (0); + // This would be SO much easier with webServer->streamFile + // except streamFile only works with File - not SdFile... + // TODO: if we ever upgrade to SD from SdFat, replace this with streamFile - // Release the resources - webServerStopSockets(); - webServerReleaseResources(); - return false; -} + if (managerFileOpen == false) + { + // Allocate the managerTempFile + if (!managerTempFile) + { + managerTempFile = new SdFile; + if (!managerTempFile) + { + systemPrintln("Failed to allocate managerTempFile!"); + return; + } + } -// Start the Web Server state machine -void webServerStart() -{ - // Display the heap state - reportHeapNow(settings.debugWebServer); + if (managerTempFile->open(slashFileName, O_READ) == true) + managerFileOpen = true; + else + systemPrintln("Error: File Manager failed to open file"); + } + else + { + // File is already in use. Wait your turn. + webServer->send(202, "text/plain", "ERROR: File already downloading"); + } - systemPrintln("Web Server start"); - webServerSetState(WEBSERVER_STATE_WAIT_FOR_NETWORK); -} + const size_t maxLen = 8192; + uint8_t *buf = new uint8_t[maxLen]; -// Stop the web config state machine -void webServerStop() -{ - online.webServer = false; + size_t dataAvailable = managerTempFile->size(); - if (settings.debugWebServer) - systemPrintln("webServerStop called"); + bool firstSend = true; - if (webServerState != WEBSERVER_STATE_OFF) - { - webServerStopSockets(); // Release socket resources - webServerReleaseResources(); // Release web server resources + while (dataAvailable > 0) + { + size_t sending; - // Stop network - systemPrintln("Web Server releasing network request"); + if (dataAvailable > maxLen) + { + sending = managerTempFile->read(buf, maxLen); + } + else + { + sending = managerTempFile->read(buf, dataAvailable); + } - // Stop the machine - webServerSetState(WEBSERVER_STATE_OFF); - } -} + if (firstSend) // First send? + { + webServer->setContentLength(dataAvailable); + webServer->sendHeader("Cache-Control", "no-cache"); + webServer->sendHeader("Content-Disposition", "attachment; filename=" + String(fileName)); + webServer->sendHeader("Access-Control-Allow-Origin", "*"); + webServer->send(200, "application/octet-stream", ""); + firstSend = false; + } -// Return true if we are in a state that requires network access -bool webServerNeedsNetwork() -{ - if (webServerState >= WEBSERVER_STATE_WAIT_FOR_NETWORK && webServerState <= WEBSERVER_STATE_RUNNING) - return true; - return false; -} + webServer->sendContent((const char *)buf, sending); -void webServerStopSockets() -{ - websocketConnected = false; + if (sending < maxLen) // Last send? + { + managerTempFile->close(); - if (wsserver != nullptr) + managerFileOpen = false; + + sendStringToWebsocket("fmNext,1,"); // Tell browser to send next file if needed + } + + dataAvailable -= sending; + } + + delete[] buf; + } + else if (fileAction == "delete") + { + logmessage += " deleted"; + sd->remove(slashFileName); + webServer->send(200, "text/plain", "Deleted File: " + fileName); + } + else + { + logmessage += " ERROR: invalid action param supplied"; + webServer->send(400, "text/plain", "ERROR: invalid action param supplied"); + } + } + systemPrintln(logmessage); + } + else { - // Stop the httpd server - esp_err_t ret = httpd_stop(*wsserver); - wsserver = nullptr; + webServer->send(400, "text/plain", "ERROR: name and action params required"); } } -// Set the next webconfig state -void webServerSetState(uint8_t newState) +//---------------------------------------- +// Handler for firmware file upload +//---------------------------------------- +static void handleFirmwareFileUpload() { - char string1[40]; - char string2[40]; - const char *arrow = nullptr; - const char *asterisk = nullptr; - const char *initialState = nullptr; - const char *endingState = nullptr; + String fileName = ""; - // Display the state transition - if (settings.debugWebServer) + HTTPUpload &upload = webServer->upload(); + + if (upload.status == UPLOAD_FILE_START) { - arrow = ""; - asterisk = ""; - initialState = ""; - if (newState == webServerState) - asterisk = "*"; + // Check file name against valid firmware names + const char *BIN_EXT = "bin"; + const char *BIN_HEADER = "RTK_Everywhere_Firmware"; + + fileName = upload.filename; + + int fnameLen = fileName.length(); + char fname[fnameLen + 2] = {'/'}; // Filename must start with / or VERY bad things happen on SD_MMC + fileName.toCharArray(&fname[1], fnameLen + 1); + fname[fnameLen + 1] = '\0'; // Terminate array + + // Check 'bin' extension + if (strcmp(BIN_EXT, &fname[strlen(fname) - strlen(BIN_EXT)]) == 0) + { + // Check for 'RTK_Everywhere_Firmware' start of file name + if (strncmp(fname, BIN_HEADER, strlen(BIN_HEADER)) == 0) + { + // Begin update process + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) + { + Update.printError(Serial); + webServer->send(400, "text/plain", "OTA could not begin"); + return; + } + } + else + { + systemPrintf("handleFirmwareFileUpload: Unknown: %s\r\n", fname); + webServer->send(400, "text/html", "Error: Unknown file type"); + return; + } + } else { - initialState = webServerGetStateName(webServerState, string1); - arrow = " --> "; + systemPrintf("handleFirmwareFileUpload: Unknown: %s\r\n", fname); + webServer->send(400, "text/html", "Error: Unknown file type"); + return; } } - // Set the new state - webServerState = newState; - if (settings.debugWebServer) + // Write chunked data to the free sketch space + else if (upload.status == UPLOAD_FILE_WRITE) { - // Display the new firmware update state - endingState = webServerGetStateName(newState, string2); - if (!online.rtc) - systemPrintf("%s%s%s%s\r\n", asterisk, initialState, arrow, endingState); - else + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - // Timestamp the state change - // 1 2 - // 12345678901234567890123456 - // YYYY-mm-dd HH:MM:SS.xxxrn0 - struct tm timeinfo = rtc.getTimeStruct(); - char s[30]; - strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &timeinfo); - systemPrintf("%s%s%s%s, %s.%03ld\r\n", asterisk, initialState, arrow, endingState, s, rtc.getMillis()); + webServer->send(400, "text/plain", "OTA could not begin"); + return; } - } + else + { + binBytesSent = upload.currentSize; - // Validate the state - if (newState >= WEBSERVER_STATE_MAX) - reportFatalError("Invalid web config state"); -} + // Send an update to browser every 100k + if (binBytesSent - binBytesLastUpdate > 100000) + { + binBytesLastUpdate = binBytesSent; -// Get the webconfig state name -const char *webServerGetStateName(uint8_t state, char *string) -{ - if (state < WEBSERVER_STATE_MAX) - return webServerStateNames[state]; - sprintf(string, "Unknown state (%d)", state); - return string; -} + char bytesSentMsg[100]; + snprintf(bytesSentMsg, sizeof(bytesSentMsg), "%'d bytes sent", binBytesSent); -bool webServerIsRunning() -{ - if (webServerState == WEBSERVER_STATE_RUNNING) - return (true); - return (false); -} + char statusMsg[200] = {'\0'}; + stringRecord(statusMsg, "firmwareUploadStatus", + bytesSentMsg); // Convert to "firmwareUploadMsg,11214 bytes sent," -void updateWebServerTask(void *e) -{ - // Start notification - task.updateWebServerTaskRunning = true; - if (settings.printTaskStartStop) - systemPrintln("Task updateWebServerTask started"); + systemPrintf("msg: %s\r\n", statusMsg); + sendStringToWebsocket(statusMsg); + } + } + } - // Verify that the task is still running - task.updateWebServerTaskStopRequest = false; - while (task.updateWebServerTaskStopRequest == false) + else if (upload.status == UPLOAD_FILE_END) { - // Display an alive message - if (PERIODIC_DISPLAY(PD_TASK_UPDATE_WEBSERVER)) + if (!Update.end(true)) { - PERIODIC_CLEAR(PD_TASK_UPDATE_WEBSERVER); - systemPrintln("updateWebServerTask running"); + Update.printError(Serial); + webServer->send(400, "text/plain", "Could not end OTA"); + return; + } + else + { + sendStringToWebsocket("firmwareUploadComplete,1,"); + systemPrintln("Firmware update complete. Restarting"); + delay(500); + ESP.restart(); } - - webServer->handleClient(); - - feedWdt(); - taskYIELD(); } - - // Stop notification - if (settings.printTaskStartStop) - systemPrintln("Task updateWebServerTask stopped"); - task.updateWebServerTaskRunning = false; - vTaskDelete(updateWebServerTaskHandle); } -void stopWebServer() +//---------------------------------------- +// Handles uploading of user files to SD +// https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino +//---------------------------------------- +void handleUpload() { - if (task.updateWebServerTaskRunning) - task.updateWebServerTaskStopRequest = true; - - // Wait for task to stop running - do - delay(10); - while (task.updateWebServerTaskRunning); - - if (webServer != nullptr) - { - webServer->close(); - free(webServer); - webServer = nullptr; - } - - // Stop the multicast DNS server - networkMulticastDNSStop(); + HTTPUpload &upload = webServer->upload(); - if (settingsCSV != nullptr) + if (upload.status == UPLOAD_FILE_START) { - free(settingsCSV); - settingsCSV = nullptr; - } + String filename = upload.filename; - if (incomingSettings != nullptr) - { - free(incomingSettings); - incomingSettings = nullptr; - } -} + String logmessage = "Upload Start: " + filename; -void webServerReleaseResources() -{ - if (task.updateWebServerTaskRunning) - task.updateWebServerTaskStopRequest = true; + int fileNameLen = filename.length(); + char tempFileName[fileNameLen + 2] = {'/'}; // Filename must start with / or VERY bad things happen on SD_MMC + filename.toCharArray(&tempFileName[1], fileNameLen + 1); + tempFileName[fileNameLen + 1] = '\0'; // Terminate array - // Wait for task to stop running - do - delay(10); - while (task.updateWebServerTaskRunning); + // Allocate the managerTempFile + if (!managerTempFile) + { + managerTempFile = new SdFile; + if (!managerTempFile) + { + systemPrintln("Failed to allocate managerTempFile!"); + return; + } + } - if (webServer != nullptr) - { - webServer->close(); - free(webServer); - webServer = nullptr; - } + if (managerFileOpen == false) + { + // Attempt to gain access to the SD card + if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) + { + markSemaphore(FUNCTION_FILEMANAGER_UPLOAD1); - // Stop the multicast DNS server - networkMulticastDNSStop(); + if (managerTempFile->open(tempFileName, O_CREAT | O_APPEND | O_WRITE) == true) + managerFileOpen = true; + else + systemPrintln("Error: handleUpload failed to open file"); - if (settingsCSV != nullptr) - { - free(settingsCSV); - settingsCSV = nullptr; - } + xSemaphoreGive(sdCardSemaphore); + } + } + else + { + // File is already in use. Wait your turn. + webServer->send(202, "text/plain", "ERROR: File already uploading"); + } - if (incomingSettings != nullptr) - { - free(incomingSettings); - incomingSettings = nullptr; + systemPrintln(logmessage); } -} -// State machine to handle the starting/stopping of the web server -void webServerUpdate() -{ - // Walk the state machine - switch (webServerState) + else if (upload.status == UPLOAD_FILE_WRITE) { - default: - systemPrintf("ERROR: Unknown Web Server state (%d)\r\n", webServerState); + // Attempt to gain access to the SD card + if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) + { + markSemaphore(FUNCTION_FILEMANAGER_UPLOAD2); - // Stop the machine - webServerStop(); - break; + managerTempFile->write(upload.buf, upload.currentSize); // stream the incoming chunk to the opened file - case WEBSERVER_STATE_OFF: - // Wait until webServerStart() is called - break; + xSemaphoreGive(sdCardSemaphore); + } + } - // Wait for connection to the network - case WEBSERVER_STATE_WAIT_FOR_NETWORK: - // Wait until the network is connected to the internet or has WiFi AP - if (networkHasInternet() || WIFI_SOFT_AP_RUNNING()) + else if (upload.status == UPLOAD_FILE_END) + { + String logmessage = "Upload Complete: " + String(upload.filename) + ", size: " + String(upload.totalSize); + + // Attempt to gain access to the SD card + if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) { - if (settings.debugWebServer) - systemPrintln("Web Server connected to network"); + markSemaphore(FUNCTION_FILEMANAGER_UPLOAD3); - webServerSetState(WEBSERVER_STATE_NETWORK_CONNECTED); - } - break; + sdUpdateFileCreateTimestamp(managerTempFile); // Update the file create time & date - // Start the web server - case WEBSERVER_STATE_NETWORK_CONNECTED: { - // Determine if the network has failed - if (networkHasInternet() == false && WIFI_SOFT_AP_RUNNING() == false) - webServerStop(); - if (settings.debugWebServer) - systemPrintln("Assigning web server resources"); + managerTempFile->close(); + managerFileOpen = false; - if (webServerAssignResources(settings.httpPort) == true) - { - online.webServer = true; - webServerSetState(WEBSERVER_STATE_RUNNING); + xSemaphoreGive(sdCardSemaphore); } - } - break; - - // Allow web services - case WEBSERVER_STATE_RUNNING: - // Determine if the network has failed - if (networkHasInternet() == false && WIFI_SOFT_AP_RUNNING() == false) - webServerStop(); - // This state is exited when webServerStop() is called + systemPrintln(logmessage); - break; + // Redirect to "/" + webServer->sendHeader("Location", "/"); + webServer->send(302, "text/plain", ""); } } +//---------------------------------------- +// Generate the Not Found page +//---------------------------------------- void notFound() { String logmessage = "notFound: Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); @@ -822,653 +642,823 @@ void notFound() webServer->send(404, "text/plain", "Not found"); } -// Handler for firmware file downloads -static void handleFileManager() +//---------------------------------------- +// Break CSV into setting constituents +// Can't use strtok because we may have two commas next to each other, ie +// measurementRateHz,4.00,measurementRateSec,,dynamicModel,0, +//---------------------------------------- +bool parseIncomingSettings() { - // This section does not tolerate semaphore transactions - String logmessage = - "handleFileManager: Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); + char settingName[100] = {'\0'}; + char valueStr[150] = {'\0'}; // stationGeodetic1,ANameThatIsTooLongToBeDisplayed 40.09029479 -105.18505761 1560.089 - if (webServer->hasArg("name") && webServer->hasArg("action")) + char *commaPtr = incomingSettings; + char *headPtr = incomingSettings; + + int counter = 0; + int maxAttempts = 500; + while (*headPtr) // Check if we've reached the end of the string { - String fileName = webServer->arg("name"); - String fileAction = webServer->arg("action"); + // Spin to first comma + commaPtr = strstr(headPtr, ","); + if (commaPtr != nullptr) + { + *commaPtr = '\0'; + strcpy(settingName, headPtr); + headPtr = commaPtr + 1; + } - logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri() + - "?name=" + fileName + "&action=" + fileAction; + commaPtr = strstr(headPtr, ","); + if (commaPtr != nullptr) + { + *commaPtr = '\0'; + strcpy(valueStr, headPtr); + headPtr = commaPtr + 1; + } - char slashFileName[60]; - snprintf(slashFileName, sizeof(slashFileName), "/%s", fileName.c_str()); + if (settings.debugWebServer == true) + systemPrintf("settingName: %s value: %s\r\n", settingName, valueStr); - bool fileExists = sd->exists(slashFileName); + updateSettingWithValue(false, settingName, valueStr); - if (fileExists == false) + // Avoid infinite loop if response is malformed + counter++; + if (counter == maxAttempts) { - logmessage += " ERROR: file "; - logmessage += slashFileName; - logmessage += " does not exist"; - webServer->send(400, "text/plain", "ERROR: file does not exist"); + systemPrintln("Error: Incoming settings malformed."); + break; } - else - { - logmessage += " file exists"; + } - if (fileAction == "download") - { - logmessage += " downloaded"; + if (counter < maxAttempts) + { + // Confirm receipt + if (settings.debugWebServer == true) + systemPrintln("Sending receipt confirmation of settings"); + sendStringToWebsocket("confirmDataReceipt,1,"); + } - // This would be SO much easier with webServer->streamFile - // except streamFile only works with File - not SdFile... - // TODO: if we ever upgrade to SD from SdFat, replace this with streamFile + return (true); +} - if (managerFileOpen == false) - { - // Allocate the managerTempFile - if (!managerTempFile) - { - managerTempFile = new SdFile; - if (!managerTempFile) - { - systemPrintln("Failed to allocate managerTempFile!"); - return; - } - } +//---------------------------------------- +// Send a string to the browser using the web socket +//---------------------------------------- +void sendStringToWebsocket(const char *stringToSend) +{ + if (!websocketConnected) + { + systemPrintf("sendStringToWebsocket: not connected - could not send: %s\r\n", stringToSend); + return; + } - if (managerTempFile->open(slashFileName, O_READ) == true) - managerFileOpen = true; - else - systemPrintln("Error: File Manager failed to open file"); - } - else - { - // File is already in use. Wait your turn. - webServer->send(202, "text/plain", "ERROR: File already downloading"); - } + // To send content to the webServer, we would call: webServer->sendContent(stringToSend); + // But here we want to send content to the websocket (wsserver)... - const size_t maxLen = 8192; - uint8_t *buf = new uint8_t[maxLen]; + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.payload = (uint8_t *)stringToSend; + ws_pkt.len = strlen(stringToSend); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; - size_t dataAvailable = managerTempFile->size(); + // If we use httpd_ws_send_frame, it requires a req. + // esp_err_t ret = httpd_ws_send_frame(last_ws_req, &ws_pkt); + // if (ret != ESP_OK) { + // ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret); + //} - bool firstSend = true; + // If we use httpd_ws_send_frame_async, it requires a fd. + esp_err_t ret = httpd_ws_send_frame_async(wsserver, last_ws_fd, &ws_pkt); + if (ret != ESP_OK) + { + systemPrintf("httpd_ws_send_frame failed with %d\r\n", ret); + } + else + { + if (settings.debugWebServer == true) + systemPrintf("sendStringToWebsocket: %s\r\n", stringToSend); + } +} - while (dataAvailable > 0) - { - size_t sending; +//---------------------------------------- +// Stop the web server +//---------------------------------------- +void stopWebServer() +{ + if (task.updateWebServerTaskRunning) + task.updateWebServerTaskStopRequest = true; - if (dataAvailable > maxLen) - { - sending = managerTempFile->read(buf, maxLen); - } - else - { - sending = managerTempFile->read(buf, dataAvailable); - } + // Wait for task to stop running + do + delay(10); + while (task.updateWebServerTaskRunning); - if (firstSend) // First send? - { - webServer->setContentLength(dataAvailable); - webServer->sendHeader("Cache-Control", "no-cache"); - webServer->sendHeader("Content-Disposition", "attachment; filename=" + String(fileName)); - webServer->sendHeader("Access-Control-Allow-Origin", "*"); - webServer->send(200, "application/octet-stream", ""); - firstSend = false; - } - - webServer->sendContent((const char *)buf, sending); - - if (sending < maxLen) // Last send? - { - managerTempFile->close(); - - managerFileOpen = false; - - sendStringToWebsocket("fmNext,1,"); // Tell browser to send next file if needed - } - - dataAvailable -= sending; - } + if (webServer != nullptr) + { + webServer->close(); + delete webServer; + webServer = nullptr; + } - delete[] buf; - } - else if (fileAction == "delete") - { - logmessage += " deleted"; - sd->remove(slashFileName); - webServer->send(200, "text/plain", "Deleted File: " + fileName); - } - else - { - logmessage += " ERROR: invalid action param supplied"; - webServer->send(400, "text/plain", "ERROR: invalid action param supplied"); - } - } - systemPrintln(logmessage); + if (settingsCSV != nullptr) + { + rtkFree(settingsCSV, "Settings buffer (settingsCSV)"); + settingsCSV = nullptr; } - else + + if (incomingSettings != nullptr) { - webServer->send(400, "text/plain", "ERROR: name and action params required"); + rtkFree(incomingSettings, "Settings buffer (incomingSettings)"); + incomingSettings = nullptr; } } -// Handler for firmware file upload -static void handleFirmwareFileUpload() +//---------------------------------------- +//---------------------------------------- +void updateWebServerTask(void *e) { - String fileName = ""; - - HTTPUpload &upload = webServer->upload(); + // Start notification + task.updateWebServerTaskRunning = true; + if (settings.printTaskStartStop) + systemPrintln("Task updateWebServerTask started"); - if (upload.status == UPLOAD_FILE_START) + // Verify that the task is still running + task.updateWebServerTaskStopRequest = false; + while (task.updateWebServerTaskStopRequest == false) { - // Check file name against valid firmware names - const char *BIN_EXT = "bin"; - const char *BIN_HEADER = "RTK_Everywhere_Firmware"; + // Display an alive message + if (PERIODIC_DISPLAY(PD_TASK_UPDATE_WEBSERVER)) + { + PERIODIC_CLEAR(PD_TASK_UPDATE_WEBSERVER); + systemPrintln("updateWebServerTask running"); + } - fileName = upload.filename; + webServer->handleClient(); - int fnameLen = fileName.length(); - char fname[fnameLen + 2] = {'/'}; // Filename must start with / or VERY bad things happen on SD_MMC - fileName.toCharArray(&fname[1], fnameLen + 1); - fname[fnameLen + 1] = '\0'; // Terminate array + feedWdt(); + taskYIELD(); + } - // Check 'bin' extension - if (strcmp(BIN_EXT, &fname[strlen(fname) - strlen(BIN_EXT)]) == 0) + // Stop notification + if (settings.printTaskStartStop) + systemPrintln("Task updateWebServerTask stopped"); + task.updateWebServerTaskRunning = false; + vTaskDelete(updateWebServerTaskHandle); +} + +//---------------------------------------- +// Create the web server and web sockets +//---------------------------------------- +bool webServerAssignResources(int httpPort = 80) +{ + if (settings.debugWebServer) + systemPrintln("Assigning web server resources"); + do + { + // Freed by webServerStop + incomingSettings = (char *)rtkMalloc(AP_CONFIG_SETTING_SIZE, "Settings buffer (incomingSettings)"); + if (!incomingSettings) { - // Check for 'RTK_Everywhere_Firmware' start of file name - if (strncmp(fname, BIN_HEADER, strlen(BIN_HEADER)) == 0) - { - // Begin update process - if (!Update.begin(UPDATE_SIZE_UNKNOWN)) - { - Update.printError(Serial); - webServer->send(400, "text/plain", "OTA could not begin"); - return; - } - } - else - { - systemPrintf("handleFirmwareFileUpload: Unknown: %s\r\n", fname); - webServer->send(400, "text/html", "Error: Unknown file type"); - return; - } + systemPrintln("ERROR: Web server failed to allocate incomingSettings"); + break; } - else + memset(incomingSettings, 0, AP_CONFIG_SETTING_SIZE); + + // Pre-load settings CSV + // Freed by webServerStop + settingsCSV = (char *)rtkMalloc(AP_CONFIG_SETTING_SIZE, "Settings buffer (settingsCSV)"); + if (!settingsCSV) { - systemPrintf("handleFirmwareFileUpload: Unknown: %s\r\n", fname); - webServer->send(400, "text/html", "Error: Unknown file type"); - return; + systemPrintln("ERROR: Web server failed to allocate settingsCSV"); + break; } - } + createSettingsString(settingsCSV); - // Write chunked data to the free sketch space - else if (upload.status == UPLOAD_FILE_WRITE) - { - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) + // + https: // github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino + if (settings.enableCaptivePortal == true) { - webServer->send(400, "text/plain", "OTA could not begin"); - return; + dnsserver = new DNSServer; + dnsserver->start(); } - else + + webServer = new WebServer(httpPort); + if (!webServer) { - binBytesSent = upload.currentSize; + systemPrintln("ERROR: Web server failed to allocate webServer"); + break; + } - // Send an update to browser every 100k - if (binBytesSent - binBytesLastUpdate > 100000) - { - binBytesLastUpdate = binBytesSent; + if (settings.enableCaptivePortal == true) + { + webServer->addHandler(new CaptiveRequestHandler()); + } - char bytesSentMsg[100]; - snprintf(bytesSentMsg, sizeof(bytesSentMsg), "%'d bytes sent", binBytesSent); + // * index.html (not gz'd) + // * favicon.ico - char statusMsg[200] = {'\0'}; - stringRecord(statusMsg, "firmwareUploadStatus", - bytesSentMsg); // Convert to "firmwareUploadMsg,11214 bytes sent," + // * /src/bootstrap.bundle.min.js - Needed for popper + // * /src/bootstrap.min.css + // * /src/bootstrap.min.js + // * /src/jquery-3.6.0.min.js + // * /src/main.js (not gz'd) + // * /src/rtk-setup.png + // * /src/style.css - systemPrintf("msg: %s\r\n", statusMsg); - sendStringToWebsocket(statusMsg); - } - } - } + // * /src/fonts/icomoon.eot + // * /src/fonts/icomoon.svg + // * /src/fonts/icomoon.ttf + // * /src/fonts/icomoon.woof - else if (upload.status == UPLOAD_FILE_END) - { - if (!Update.end(true)) + // * /listfiles responds with a CSV of files and sizes in root + // * /listMessages responds with a CSV of messages supported by this platform + // * /listMessagesBase responds with a CSV of RTCM Base messages supported by this platform + // * /file allows the download or deletion of a file + + webServer->onNotFound(notFound); + + GET_PAGE("/", "text/html", index_html); + GET_PAGE("/favicon.ico", "text/plain", favicon_ico); + GET_PAGE("/src/bootstrap.bundle.min.js", "text/javascript", bootstrap_bundle_min_js); + GET_PAGE("/src/bootstrap.min.css", "text/css", bootstrap_min_css); + GET_PAGE("/src/bootstrap.min.js", "text/javascript", bootstrap_min_js); + GET_PAGE("/src/jquery-3.6.0.min.js", "text/javascript", jquery_js); + GET_PAGE("/src/main.js", "text/javascript", main_js); + if (productVariant == RTK_EVK) { - Update.printError(Serial); - webServer->send(400, "text/plain", "Could not end OTA"); - return; + GET_PAGE("/src/rtk-setup.png", "image/png", rtkSetup_png); } else { - sendStringToWebsocket("firmwareUploadComplete,1,"); - systemPrintln("Firmware update complete. Restarting"); - delay(500); - ESP.restart(); + GET_PAGE("/src/rtk-setup.png", "image/png", rtkSetupWiFi_png); } - } -} -// Report back to the web config page with a CSV that contains the either CURRENT or -// the latest version as obtained by the OTA state machine -void createFirmwareVersionString(char *settingsCSV) -{ - char newVersionCSV[100]; + // Battery icons + GET_PAGE("/src/BatteryBlank.png", "image/png", batteryBlank_png); + GET_PAGE("/src/Battery0.png", "image/png", battery0_png); + GET_PAGE("/src/Battery1.png", "image/png", battery1_png); + GET_PAGE("/src/Battery2.png", "image/png", battery2_png); + GET_PAGE("/src/Battery3.png", "image/png", battery3_png); + GET_PAGE("/src/Battery0_Charging.png", "image/png", battery0_Charging_png); + GET_PAGE("/src/Battery1_Charging.png", "image/png", battery1_Charging_png); + GET_PAGE("/src/Battery2_Charging.png", "image/png", battery2_Charging_png); + GET_PAGE("/src/Battery3_Charging.png", "image/png", battery3_Charging_png); + GET_PAGE("/src/style.css", "text/css", style_css); + GET_PAGE("/src/fonts/icomoon.eot", "text/plain", icomoon_eot); + GET_PAGE("/src/fonts/icomoon.svg", "text/plain", icomoon_svg); + GET_PAGE("/src/fonts/icomoon.ttf", "text/plain", icomoon_ttf); + GET_PAGE("/src/fonts/icomoon.woof", "text/plain", icomoon_woof); + + // https://lemariva.com/blog/2017/11/white-hacking-wemos-captive-portal-using-micropython + webServer->on("/connecttest.txt", HTTP_GET, + []() { webServer->send(200, "text/plain", "Microsoft Connect Test"); }); + + // Handler for the /uploadFile form POST + webServer->on( + "/uploadFile", HTTP_POST, []() { webServer->send(200, "text/plain", ""); }, + handleUpload); // Run handleUpload function when file manager file is uploaded + + // Handler for the /uploadFirmware form POST + webServer->on( + "/uploadFirmware", HTTP_POST, []() { webServer->send(200, "text/plain", ""); }, handleFirmwareFileUpload); + + // Handler for file manager + webServer->on("/listfiles", HTTP_GET, []() { + String logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); + if (settings.debugWebServer == true) + systemPrintln(logmessage); + String files; + getFileList(files); + webServer->send(200, "text/plain", files); + }); + + // Handler for supported messages list + webServer->on("/listMessages", HTTP_GET, []() { + String logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); + if (settings.debugWebServer == true) + systemPrintln(logmessage); + String messages; + createMessageList(messages); + if (settings.debugWebServer == true) + systemPrintln(messages); + webServer->send(200, "text/plain", messages); + }); + + // Handler for supported RTCM/Base messages list + webServer->on("/listMessagesBase", HTTP_GET, []() { + String logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); + if (settings.debugWebServer == true) + systemPrintln(logmessage); + String messageList; + createMessageListBase(messageList); + if (settings.debugWebServer == true) + systemPrintln(messageList); + webServer->send(200, "text/plain", messageList); + }); + + // Handler for file manager + webServer->on("/file", HTTP_GET, handleFileManager); + + // Start the web server + webServer->begin(); + + // Starts task for updating webServer with handleClient + if (task.updateWebServerTaskRunning == false) + xTaskCreate( + updateWebServerTask, + "UpdateWebServer", // Just for humans + webServerTaskStackSize, // Stack Size - needs to be large enough to hold the file manager list + nullptr, // Task input parameter + updateWebServerTaskPriority, + &updateWebServerTaskHandle); // Task handle - settingsCSV[0] = '\0'; // Erase current settings string + if (settings.debugWebServer == true) + systemPrintln("Web Server: Started"); + reportHeapNow(false); - // Create a string of the unit's current firmware version - char currentVersion[21]; - getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); + // Start the web socket server on port 81 using + if (websocketServerStart() == false) + { + if (settings.debugWebServer == true) + systemPrintln("Web Sockets failed to start"); + break; + } - // Compare the unit's version against the reported version from OTA - if (isReportedVersionNewer(otaReportedVersion, currentVersion) == true) - { - if (settings.debugWebServer == true) - systemPrintln("New version detected"); - snprintf(newVersionCSV, sizeof(newVersionCSV), "%s,", otaReportedVersion); - } - else - { if (settings.debugWebServer == true) - systemPrintln("No new firmware available"); - snprintf(newVersionCSV, sizeof(newVersionCSV), "CURRENT,"); - } + { + systemPrintln("Web Socket Server Started"); + reportHeapNow(true); + } - stringRecord(settingsCSV, "newFirmwareVersion", newVersionCSV); + online.webServer = true; + return true; + } while (0); - strcat(settingsCSV, "\0"); + // Release the resources + if (settings.debugWebServer == true) + reportHeapNow(true); + webServerReleaseResources(); + return false; } -// Create a csv string with the dynamic data to update (current coordinates, battery level, etc) -void createDynamicDataString(char *settingsCSV) +//---------------------------------------- +// Get the webconfig state name +//---------------------------------------- +const char *webServerGetStateName(uint8_t state, char *string) { - settingsCSV[0] = '\0'; // Erase current settings string + if (state < WEBSERVER_STATE_MAX) + return webServerStateNames[state]; + sprintf(string, "Web Server: Unknown state (%d)", state); + return string; +} - // Current coordinates come from HPPOSLLH call back - stringRecord(settingsCSV, "geodeticLat", gnss->getLatitude(), haeNumberOfDecimals); - stringRecord(settingsCSV, "geodeticLon", gnss->getLongitude(), haeNumberOfDecimals); - stringRecord(settingsCSV, "geodeticAlt", gnss->getAltitude(), 3); +//---------------------------------------- +// Determine if the web server is running +//---------------------------------------- +bool webServerIsRunning() +{ + if (webServerState == WEBSERVER_STATE_RUNNING) + return (true); + return (false); +} - double ecefX = 0; - double ecefY = 0; - double ecefZ = 0; +//---------------------------------------- +//---------------------------------------- +void webServerReleaseResources() +{ + if (settings.debugWebServer) + systemPrintln("Releasing web server resources"); + if (task.updateWebServerTaskRunning) + task.updateWebServerTaskStopRequest = true; - geodeticToEcef(gnss->getLatitude(), gnss->getLongitude(), gnss->getAltitude(), &ecefX, &ecefY, &ecefZ); + // Wait for task to stop running + do + delay(10); + while (task.updateWebServerTaskRunning); - stringRecord(settingsCSV, "ecefX", ecefX, 3); - stringRecord(settingsCSV, "ecefY", ecefY, 3); - stringRecord(settingsCSV, "ecefZ", ecefZ, 3); + online.webServer = false; - if (online.batteryFuelGauge == false) // Product has no battery + webServerStopSockets(); // Release socket resources + + if (webServer != nullptr) { - stringRecord(settingsCSV, "batteryIconFileName", (char *)"src/BatteryBlank.png"); - stringRecord(settingsCSV, "batteryPercent", (char *)" "); + webServer->close(); + delete webServer; + webServer = nullptr; } - else - { - // Determine battery icon - int iconLevel = 0; - if (batteryLevelPercent < 25) - iconLevel = 0; - else if (batteryLevelPercent < 50) - iconLevel = 1; - else if (batteryLevelPercent < 75) - iconLevel = 2; - else // batt level > 75 - iconLevel = 3; - - char batteryIconFileName[sizeof("src/Battery2_Charging.png__")]; // sizeof() includes 1 for \0 termination - - if (isCharging()) - snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d_Charging.png", iconLevel); - else - snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d.png", iconLevel); - - stringRecord(settingsCSV, "batteryIconFileName", batteryIconFileName); - - // Limit batteryLevelPercent to sane levels - if (batteryLevelPercent > 100) - batteryLevelPercent = 100; - // Determine battery percent - char batteryPercent[sizeof("+100%__")]; - if (isCharging()) - snprintf(batteryPercent, sizeof(batteryPercent), "+%d%%", batteryLevelPercent); - else - snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", batteryLevelPercent); - stringRecord(settingsCSV, "batteryPercent", batteryPercent); + if (settingsCSV != nullptr) + { + rtkFree(settingsCSV, "Settings buffer (settingsCSV)"); + settingsCSV = nullptr; } - strcat(settingsCSV, "\0"); + if (incomingSettings != nullptr) + { + rtkFree(incomingSettings, "Settings buffer (incomingSettings)"); + incomingSettings = nullptr; + } } -// Break CSV into setting constituents -// Can't use strtok because we may have two commas next to each other, ie -// measurementRateHz,4.00,measurementRateSec,,dynamicModel,0, -bool parseIncomingSettings() +//---------------------------------------- +//---------------------------------------- +void webServerStopSockets() { - char settingName[100] = {'\0'}; - char valueStr[150] = {'\0'}; // stationGeodetic1,ANameThatIsTooLongToBeDisplayed 40.09029479 -105.18505761 1560.089 - - char *commaPtr = incomingSettings; - char *headPtr = incomingSettings; + websocketConnected = false; - int counter = 0; - int maxAttempts = 500; - while (*headPtr) // Check if we've reached the end of the string + if (wsserver != nullptr) { - // Spin to first comma - commaPtr = strstr(headPtr, ","); - if (commaPtr != nullptr) - { - *commaPtr = '\0'; - strcpy(settingName, headPtr); - headPtr = commaPtr + 1; - } - - commaPtr = strstr(headPtr, ","); - if (commaPtr != nullptr) - { - *commaPtr = '\0'; - strcpy(valueStr, headPtr); - headPtr = commaPtr + 1; - } - - if (settings.debugWebServer == true) - systemPrintf("settingName: %s value: %s\r\n", settingName, valueStr); + // Stop the httpd server + esp_err_t status = httpd_stop(wsserver); + if (status != ESP_OK) + systemPrintf("ERROR: wsserver failed to stop, status: %s!\r\n", + esp_err_to_name(status)); + wsserver = nullptr; + } +} - updateSettingWithValue(false, settingName, valueStr); +//---------------------------------------- +// Set the next webconfig state +//---------------------------------------- +void webServerSetState(uint8_t newState) +{ + char string1[40]; + char string2[40]; + const char *arrow = nullptr; + const char *asterisk = nullptr; + const char *initialState = nullptr; + const char *endingState = nullptr; - // Avoid infinite loop if response is malformed - counter++; - if (counter == maxAttempts) + // Display the state transition + if (settings.debugWebServer) + { + arrow = ""; + asterisk = ""; + initialState = ""; + if (newState == webServerState) + asterisk = "*"; + else { - systemPrintln("Error: Incoming settings malformed."); - break; + initialState = webServerGetStateName(webServerState, string1); + arrow = " --> "; } } - if (counter < maxAttempts) + // Set the new state + webServerState = newState; + if (settings.debugWebServer) { - // Confirm receipt - if (settings.debugWebServer == true) - systemPrintln("Sending receipt confirmation of settings"); - sendStringToWebsocket("confirmDataReceipt,1,"); + // Display the new firmware update state + endingState = webServerGetStateName(newState, string2); + if (!online.rtc) + systemPrintf("%s%s%s%s\r\n", asterisk, initialState, arrow, endingState); + else + { + // Timestamp the state change + // 1 2 + // 12345678901234567890123456 + // YYYY-mm-dd HH:MM:SS.xxxrn0 + struct tm timeinfo = rtc.getTimeStruct(); + char s[30]; + strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &timeinfo); + systemPrintf("%s%s%s%s, %s.%03ld\r\n", asterisk, initialState, arrow, endingState, s, rtc.getMillis()); + } } - return (true); + // Validate the state + if (newState >= WEBSERVER_STATE_MAX) + reportFatalError("Web Server: Invalid web config state"); } -// When called, responds with the root folder list of files on SD card -// Name and size are formatted in CSV, formatted to html by JS -void getFileList(String &returnText) +//---------------------------------------- +// Start the Web Server state machine +//---------------------------------------- +void webServerStart() { - returnText = ""; - - // Update the SD Size and Free Space - String cardSize; - stringHumanReadableSize(cardSize, sdCardSize); - returnText += "sdSize," + cardSize + ","; - String freeSpace; - stringHumanReadableSize(freeSpace, sdFreeSpace); - returnText += "sdFreeSpace," + freeSpace + ","; - - char fileName[50]; // Handle long file names - - // Attempt to gain access to the SD card - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_FILEMANAGER_UPLOAD1); - - SdFile root; - root.open("/"); // Open root - SdFile file; - uint16_t fileCount = 0; - - while (file.openNext(&root, O_READ)) - { - if (file.isFile()) - { - fileCount++; - - file.getName(fileName, sizeof(fileName)); - - String fileSize; - stringHumanReadableSize(fileSize, file.fileSize()); - returnText += "fmName," + String(fileName) + ",fmSize," + fileSize + ","; - } - } - - root.close(); - file.close(); + // Display the heap state + reportHeapNow(settings.debugWebServer); - xSemaphoreGive(sdCardSemaphore); + if (webServerState != WEBSERVER_STATE_OFF) + { + if (settings.debugWebServer) + systemPrintln("Web Server: Already running!"); } else { - char semaphoreHolder[50]; - getSemaphoreFunction(semaphoreHolder); + if (settings.debugWebServer) + systemPrintln("Web Server: Starting"); - // This is an error because the current settings no longer match the settings - // on the microSD card, and will not be restored to the expected settings! - systemPrintf("sdCardSemaphore failed to yield, held by %s, Form.ino line %d\r\n", semaphoreHolder, __LINE__); + // Start the network + if (settings.wifiConfigOverAP == false) + networkConsumerAdd(NETCONSUMER_WEB_CONFIG, NETWORK_ANY, __FILE__, __LINE__); + else + networkSoftApConsumerAdd(NETCONSUMER_WEB_CONFIG, __FILE__, __LINE__); + webServerSetState(WEBSERVER_STATE_WAIT_FOR_NETWORK); } +} - if (settings.debugWebServer == true) - systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); +//---------------------------------------- +// Stop the web config state machine +//---------------------------------------- +void webServerStop() +{ + networkUserRemove(NETCONSUMER_WEB_CONFIG, __FILE__, __LINE__); + if (webServerState != WEBSERVER_STATE_OFF) + { + webServerReleaseResources(); // Release web server resources + + // Stop network + systemPrintln("Web Server releasing network request"); + networkSoftApConsumerRemove(NETCONSUMER_WEB_CONFIG, __FILE__, __LINE__); + networkConsumerRemove(NETCONSUMER_WEB_CONFIG, NETWORK_ANY, __FILE__, __LINE__); + + // Stop the machine + webServerSetState(WEBSERVER_STATE_OFF); + if (settings.debugWebServer) + systemPrintln("Web Server: Stopped"); + + // Display the heap state + reportHeapNow(settings.debugWebServer); + } } -// When called, responds with the messages supported on this platform -// Message name and current rate are formatted in CSV, formatted to html by JS -void createMessageList(String &returnText) +//---------------------------------------- +// State machine to handle the starting/stopping of the web server +//---------------------------------------- +void webServerUpdate() { - returnText = ""; + bool connected; - if (present.gnss_zedf9p) + // Determine if the network is connected + connected = networkConsumerIsConnected(NETCONSUMER_WEB_CONFIG); + + // Walk the state machine + switch (webServerState) { -#ifdef COMPILE_ZED - for (int messageNumber = 0; messageNumber < MAX_UBX_MSG; messageNumber++) + default: + systemPrintf("ERROR: Unknown Web Server state (%d)\r\n", webServerState); + + // Stop the machine + webServerStop(); + break; + + case WEBSERVER_STATE_OFF: + // Wait until webServerStart() is called + break; + + // Wait for connection to the network + case WEBSERVER_STATE_WAIT_FOR_NETWORK: + // Wait until the network is connected to the internet or has WiFi AP + if (connected || wifiSoftApRunning) { - if (messageSupported(messageNumber) == true) - returnText += "ubxMessageRate_" + String(ubxMessages[messageNumber].msgTextName) + "," + - String(settings.ubxMessageRates[messageNumber]) + ","; + if (settings.debugWebServer) + systemPrintln("Web Server connected to network"); + + networkUserAdd(NETCONSUMER_WEB_CONFIG, __FILE__, __LINE__); + webServerSetState(WEBSERVER_STATE_NETWORK_CONNECTED); } -#endif // COMPILE_ZED - } + break; -#ifdef COMPILE_UM980 - else if (present.gnss_um980) - { - for (int messageNumber = 0; messageNumber < MAX_UM980_NMEA_MSG; messageNumber++) + // Start the web server + case WEBSERVER_STATE_NETWORK_CONNECTED: { + // Determine if the network has failed + if (connected == false && wifiSoftApRunning == false) { - returnText += "messageRateNMEA_" + String(umMessagesNMEA[messageNumber].msgTextName) + "," + - String(settings.um980MessageRatesNMEA[messageNumber]) + ","; + networkUserRemove(NETCONSUMER_WEB_CONFIG, __FILE__, __LINE__); + webServerSetState(WEBSERVER_STATE_WAIT_FOR_NETWORK); } - for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) + + // Attempt to start the web server + else if (webServerAssignResources(settings.httpPort) == true) + webServerSetState(WEBSERVER_STATE_RUNNING); + } + break; + + // Allow web services + case WEBSERVER_STATE_RUNNING: + // Determine if the network has failed + if (connected == false && wifiSoftApRunning == false) { - returnText += "messageRateRTCMRover_" + String(umMessagesRTCM[messageNumber].msgTextName) + "," + - String(settings.um980MessageRatesRTCMRover[messageNumber]) + ","; + webServerReleaseResources(); // Release web server resources + webServerSetState(WEBSERVER_STATE_WAIT_FOR_NETWORK); } + + // This state is exited when webServerStop() is called + + break; } -#endif // COMPILE_UM980 -#ifdef COMPILE_MOSAICX5 - else if (present.gnss_mosaicX5) + // Display an alive message + if (PERIODIC_DISPLAY(PD_WEB_SERVER_STATE)) { - for (int messageNumber = 0; messageNumber < MAX_MOSAIC_NMEA_MSG; messageNumber++) - { - returnText += "messageStreamNMEA_" + String(mosaicMessagesNMEA[messageNumber].msgTextName) + "," + - String(settings.mosaicMessageStreamNMEA[messageNumber]) + ","; - } - for (int stream = 0; stream < MOSAIC_NUM_NMEA_STREAMS; stream++) - { - returnText += - "streamIntervalNMEA_" + String(stream) + "," + String(settings.mosaicStreamIntervalsNMEA[stream]) + ","; - } - for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS; messageNumber++) - { - returnText += "messageIntervalRTCMRover_" + String(mosaicRTCMv3MsgIntervalGroups[messageNumber].name) + - "," + String(settings.mosaicMessageIntervalsRTCMv3Rover[messageNumber]) + ","; - } - for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_MSG; messageNumber++) - { - returnText += "messageEnabledRTCMRover_" + String(mosaicMessagesRTCMv3[messageNumber].name) + "," + - (settings.mosaicMessageEnabledRTCMv3Rover[messageNumber] ? "true" : "false") + ","; - } + systemPrintf("Web Server state: %s\r\n", webServerStateNames[webServerState]); + PERIODIC_CLEAR(PD_WEB_SERVER_STATE); } -#endif // COMPILE_MOSAICX5 +} - if (settings.debugWebServer == true) - systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); +//---------------------------------------- +// Verify the web server tables +//---------------------------------------- +void webServerVerifyTables() +{ + if (webServerStateEntries != WEBSERVER_STATE_MAX) + reportFatalError("Fix webServerStateNames to match WebServerState"); } -// When called, responds with the RTCM/Base messages supported on this platform -// Message name and current rate are formatted in CSV, formatted to html by JS -void createMessageListBase(String &returnText) +//---------------------------------------- +//---------------------------------------- +static esp_err_t ws_handler(httpd_req_t *req) { - returnText = ""; + // Log the req, so we can reuse it for httpd_ws_send_frame + // TODO: do we need to be cleverer about this? + // last_ws_req = req; - if (present.gnss_zedf9p) + if (req->method == HTTP_GET) { -#ifdef COMPILE_ZED - GNSS_ZED *zed = (GNSS_ZED *)gnss; - int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); + // Log the fd, so we can reuse it for httpd_ws_send_frame_async + // TODO: do we need to be cleverer about this? + last_ws_fd = httpd_req_to_sockfd(req); - for (int messageNumber = 0; messageNumber < MAX_UBX_MSG_RTCM; messageNumber++) - { - if (messageSupported(firstRTCMRecord + messageNumber) == true) - returnText += "ubxMessageRateBase_" + String(ubxMessages[messageNumber + firstRTCMRecord].msgTextName) + - "," + String(settings.ubxMessageRatesBase[messageNumber]) + ","; // UBX_RTCM_1074Base,4, - } -#endif // COMPILE_ZED + if (settings.debugWebServer == true) + systemPrintf("Handshake done, the new ws connection was opened with fd %d\r\n", last_ws_fd); + + websocketConnected = true; + lastDynamicDataUpdate = millis(); + sendStringToWebsocket(settingsCSV); + + return ESP_OK; } -#ifdef COMPILE_UM980 - else if (present.gnss_um980) + httpd_ws_frame_t ws_pkt; + uint8_t *buf = NULL; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0); + if (ret != ESP_OK) { - for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) - { - returnText += "messageRateRTCMBase_" + String(umMessagesRTCM[messageNumber].msgTextName) + "," + - String(settings.um980MessageRatesRTCMBase[messageNumber]) + ","; - } + systemPrintf("httpd_ws_recv_frame failed to get frame len with %d\r\n", ret); + return ret; } -#endif // COMPILE_UM980 - -#ifdef COMPILE_MOSAICX5 - else if (present.gnss_mosaicX5) + if (settings.debugWebServer == true) + systemPrintf("frame len is %d\r\n", ws_pkt.len); + if (ws_pkt.len) { - for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS; messageNumber++) + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = (uint8_t *)rtkMalloc(ws_pkt.len + 1, "Payload buffer (buf)"); + if (buf == NULL) { - returnText += "messageIntervalRTCMBase_" + String(mosaicRTCMv3MsgIntervalGroups[messageNumber].name) + "," + - String(settings.mosaicMessageIntervalsRTCMv3Base[messageNumber]) + ","; + systemPrintln("Failed to malloc memory for buf"); + return ESP_ERR_NO_MEM; } - for (int messageNumber = 0; messageNumber < MAX_MOSAIC_RTCM_V3_MSG; messageNumber++) + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { - returnText += "messageEnabledRTCMBase_" + String(mosaicMessagesRTCMv3[messageNumber].name) + "," + - (settings.mosaicMessageEnabledRTCMv3Base[messageNumber] ? "true" : "false") + ","; + systemPrintf("httpd_ws_recv_frame failed with %d\r\n", ret); + rtkFree(buf, "Payload buffer (buf)"); + return ret; } } -#endif // COMPILE_MOSAICX5 - if (settings.debugWebServer == true) - systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); -} - -// Handles uploading of user files to SD -// https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino -void handleUpload() -{ - HTTPUpload &upload = webServer->upload(); - - if (upload.status == UPLOAD_FILE_START) { - String filename = upload.filename; - - String logmessage = "Upload Start: " + filename; - - int fileNameLen = filename.length(); - char tempFileName[fileNameLen + 2] = {'/'}; // Filename must start with / or VERY bad things happen on SD_MMC - filename.toCharArray(&tempFileName[1], fileNameLen + 1); - tempFileName[fileNameLen + 1] = '\0'; // Terminate array - - // Allocate the managerTempFile - if (!managerTempFile) + const char * pktType; + size_t length = ws_pkt.len; + switch (ws_pkt.type) { - managerTempFile = new SdFile; - if (!managerTempFile) - { - systemPrintln("Failed to allocate managerTempFile!"); - return; - } + default: pktType = nullptr; break; + case HTTPD_WS_TYPE_CONTINUE: pktType = "HTTPD_WS_TYPE_CONTINUE"; break; + case HTTPD_WS_TYPE_TEXT: pktType = "HTTPD_WS_TYPE_TEXT"; break; + case HTTPD_WS_TYPE_BINARY: pktType = "HTTPD_WS_TYPE_BINARY"; break; + case HTTPD_WS_TYPE_CLOSE: pktType = "HTTPD_WS_TYPE_CLOSE"; break; + case HTTPD_WS_TYPE_PING: pktType = "HTTPD_WS_TYPE_PING"; break; + case HTTPD_WS_TYPE_PONG: pktType = "HTTPD_WS_TYPE_PONG"; break; } + systemPrintf("Packet: %p, %d bytes, type: %d%s%s%s\r\n", + ws_pkt.payload, + length, + ws_pkt.type, + pktType ? " (" : "", + pktType ? pktType : "", + pktType ? ")" : ""); + if (length > 0x40) + length = 0x40; + dumpBuffer(ws_pkt.payload, length); + } - if (managerFileOpen == false) + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) + { + if (currentlyParsingData == false) { - // Attempt to gain access to the SD card - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) + for (int i = 0; i < ws_pkt.len; i++) { - markSemaphore(FUNCTION_FILEMANAGER_UPLOAD1); - - if (managerTempFile->open(tempFileName, O_CREAT | O_APPEND | O_WRITE) == true) - managerFileOpen = true; - else - systemPrintln("Error: handleUpload failed to open file"); - - xSemaphoreGive(sdCardSemaphore); + incomingSettings[incomingSettingsSpot++] = ws_pkt.payload[i]; + incomingSettingsSpot %= AP_CONFIG_SETTING_SIZE; } + timeSinceLastIncomingSetting = millis(); } else { - // File is already in use. Wait your turn. - webServer->send(202, "text/plain", "ERROR: File already uploading"); + if (settings.debugWebServer == true) + systemPrintln("Ignoring packet due to parsing block"); } - - systemPrintln(logmessage); } - - else if (upload.status == UPLOAD_FILE_WRITE) + else if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) { - // Attempt to gain access to the SD card - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_FILEMANAGER_UPLOAD2); - - managerTempFile->write(upload.buf, upload.currentSize); // stream the incoming chunk to the opened file + if (settings.debugWebServer == true) + systemPrintln("Client closed or refreshed the web page"); - xSemaphoreGive(sdCardSemaphore); - } + createSettingsString(settingsCSV); + websocketConnected = false; } - else if (upload.status == UPLOAD_FILE_END) - { - String logmessage = "Upload Complete: " + String(upload.filename) + ", size: " + String(upload.totalSize); + rtkFree(buf, "Payload buffer (buf)"); + return ret; +} - // Attempt to gain access to the SD card - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_FILEMANAGER_UPLOAD3); +//---------------------------------------- +//---------------------------------------- +static const httpd_uri_t ws = {.uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .user_ctx = NULL, + .is_websocket = true, + .handle_ws_control_frames = true, + .supported_subprotocol = NULL}; - sdUpdateFileCreateTimestamp(managerTempFile); // Update the file create time & date +//---------------------------------------- +// Display the HTTPD configuration +//---------------------------------------- +void httpdDisplayConfig(struct httpd_config * config) +{ + systemPrintf("httpd_config object:\r\n"); + systemPrintf("%10d: task_priority\r\n", config->task_priority); + systemPrintf("%10d: stack_size\r\n", config->stack_size); + systemPrintf("%10d: core_id\r\n", config->core_id); + systemPrintf("%10d: server_port\r\n", config->server_port); + systemPrintf("%10d: ctrl_port\r\n", config->ctrl_port); + systemPrintf("%10d: max_open_sockets\r\n", config->max_open_sockets); + systemPrintf("%10d: max_uri_handlers\r\n", config->max_uri_handlers); + systemPrintf("%10d: max_resp_headers\r\n", config->max_resp_headers); + systemPrintf("%10d: backlog_conn\r\n", config->backlog_conn); + systemPrintf("%10s: lru_purge_enable\r\n", config->lru_purge_enable ? "true" : "false"); + systemPrintf("%10d: recv_wait_timeout\r\n", config->recv_wait_timeout); + + systemPrintf("%10d: send_wait_timeout\r\n", config->send_wait_timeout); + systemPrintf("%p: global_user_ctx\r\n", config->global_user_ctx); + systemPrintf("%p: global_user_ctx_free_fn\r\n", config->global_user_ctx_free_fn); + systemPrintf("%p: global_transport_ctx\r\n", config->global_transport_ctx); + systemPrintf("%p: global_transport_ctx_free_fn\r\n", (void *)config->global_transport_ctx_free_fn); + systemPrintf("%10s: enable_so_linger\r\n", config->enable_so_linger ? "true" : "false"); + systemPrintf("%10d: linger_timeout\r\n", config->linger_timeout); + systemPrintf("%10s: keep_alive_enable\r\n", config->keep_alive_enable ? "true" : "false"); + systemPrintf("%10d: keep_alive_idle\r\n", config->keep_alive_idle); + systemPrintf("%10d: keep_alive_interval\r\n", config->keep_alive_interval); + systemPrintf("%10d: keep_alive_count\r\n", config->keep_alive_count); + systemPrintf("%p: open_fn\r\n", (void *)config->open_fn); + systemPrintf("%p: close_fn\r\n", (void *)config->close_fn); + systemPrintf("%p: uri_match_fn\r\n", (void *)config->uri_match_fn); +} - managerTempFile->close(); - managerFileOpen = false; +//---------------------------------------- +//---------------------------------------- +bool websocketServerStart(void) +{ + esp_err_t status; - xSemaphoreGive(sdCardSemaphore); - } + // Gete the configuration object + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - systemPrintln(logmessage); + // Use different ports for websocket and webServer - use port 81 for the websocket - also defined in main.js + config.server_port = 81; - // Redirect to "/" - webServer->sendHeader("Location", "/"); - webServer->send(302, "text/plain", ""); + // Increase the stack size from 4K to handle page processing + config.stack_size = webSocketStackSize; + + // Start the httpd server + if (settings.debugWebServer == true) + systemPrintf("Starting wsserver on port: %d\r\n", config.server_port); + + if (settings.debugWebServer == true) + { + httpdDisplayConfig(&config); + reportHeapNow(true); + } + status = httpd_start(&wsserver, &config); + if (status == ESP_OK) + { + // Registering the ws handler + if (settings.debugWebServer == true) + systemPrintln("Registering URI handlers"); + httpd_register_uri_handler(wsserver, &ws); + return true; } -} -// Verify the web server tables -void webServerVerifyTables() -{ - if (webServerStateEntries != WEBSERVER_STATE_MAX) - reportFatalError("Fix webServerStateNames to match WebServerState"); + // Display the failure to start + systemPrintf("ERROR: wsserver failed to start, status: %s!\r\n", + esp_err_to_name(status)); + return false; } #endif // COMPILE_AP diff --git a/Firmware/RTK_Everywhere/WiFi.ino b/Firmware/RTK_Everywhere/WiFi.ino index f52cd453..9fbb37f8 100644 --- a/Firmware/RTK_Everywhere/WiFi.ino +++ b/Firmware/RTK_Everywhere/WiFi.ino @@ -10,7 +10,6 @@ // Constants //**************************************** -#define WIFI_RECONNECTION_DELAY 1000 #define WIFI_DEFAULT_CHANNEL 1 #define WIFI_IP_ADDRESS_TIMEOUT_MSEC (15 * 1000) @@ -31,61 +30,6 @@ static const char * wifiAuthorizationName[] = static const int wifiAuthorizationNameEntries = sizeof(wifiAuthorizationName) / sizeof(wifiAuthorizationName[0]); -const char * arduinoEventName[] = -{ - "ARDUINO_EVENT_NONE", - "ARDUINO_EVENT_ETH_START", - "ARDUINO_EVENT_ETH_STOP", - "ARDUINO_EVENT_ETH_CONNECTED", - "ARDUINO_EVENT_ETH_DISCONNECTED", - "ARDUINO_EVENT_ETH_GOT_IP", - "ARDUINO_EVENT_ETH_LOST_IP", - "ARDUINO_EVENT_ETH_GOT_IP6", - "ARDUINO_EVENT_WIFI_OFF", - "ARDUINO_EVENT_WIFI_READY", - "ARDUINO_EVENT_WIFI_SCAN_DONE", - "ARDUINO_EVENT_WIFI_STA_START", - "ARDUINO_EVENT_WIFI_STA_STOP", - "ARDUINO_EVENT_WIFI_STA_CONNECTED", - "ARDUINO_EVENT_WIFI_STA_DISCONNECTED", - "ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE", - "ARDUINO_EVENT_WIFI_STA_GOT_IP", - "ARDUINO_EVENT_WIFI_STA_GOT_IP6", - "ARDUINO_EVENT_WIFI_STA_LOST_IP", - "ARDUINO_EVENT_WIFI_AP_START", - "ARDUINO_EVENT_WIFI_AP_STOP", - "ARDUINO_EVENT_WIFI_AP_STACONNECTED", - "ARDUINO_EVENT_WIFI_AP_STADISCONNECTED", - "ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED", - "ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED", - "ARDUINO_EVENT_WIFI_AP_GOT_IP6", - "ARDUINO_EVENT_WIFI_FTM_REPORT", - "ARDUINO_EVENT_WPS_ER_SUCCESS", - "ARDUINO_EVENT_WPS_ER_FAILED", - "ARDUINO_EVENT_WPS_ER_TIMEOUT", - "ARDUINO_EVENT_WPS_ER_PIN", - "ARDUINO_EVENT_WPS_ER_PBC_OVERLAP", - "ARDUINO_EVENT_SC_SCAN_DONE", - "ARDUINO_EVENT_SC_FOUND_CHANNEL", - "ARDUINO_EVENT_SC_GOT_SSID_PSWD", - "ARDUINO_EVENT_SC_SEND_ACK_DONE", - "ARDUINO_EVENT_PROV_INIT", - "ARDUINO_EVENT_PROV_DEINIT", - "ARDUINO_EVENT_PROV_START", - "ARDUINO_EVENT_PROV_END", - "ARDUINO_EVENT_PROV_CRED_RECV", - "ARDUINO_EVENT_PROV_CRED_FAIL", - "ARDUINO_EVENT_PROV_CRED_SUCCESS", - "ARDUINO_EVENT_PPP_START", - "ARDUINO_EVENT_PPP_STOP", - "ARDUINO_EVENT_PPP_CONNECTED", - "ARDUINO_EVENT_PPP_DISCONNECTED", - "ARDUINO_EVENT_PPP_GOT_IP", - "ARDUINO_EVENT_PPP_LOST_IP", - "ARDUINO_EVENT_PPP_GOT_IP6", -}; -const int arduinoEventNameEntries = sizeof(arduinoEventName) / sizeof(arduinoEventName[0]); - //---------------------------------------------------------------------- // ESP-NOW bringup from example 4_9_ESP_NOW // 1. Set station mode @@ -100,43 +44,43 @@ const int arduinoEventNameEntries = sizeof(arduinoEventName) / sizeof(arduinoEve // 6. Call esp_wifi_set_promiscuous(true) // 7. Set promiscuous receive callback [esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_cb)] // to get RSSI of action frames -// 8. Assign a channel if necessary, call espnowSetChannel -// 9. Set receive callback [esp_now_register_recv_cb(espnowOnDataReceived)] +// 8. Assign a channel if necessary, call RTK_WIFI::espNowSetChannel +// 9. Set receive callback [esp_now_register_recv_cb(espNowOnDataReceived)] // 10. Add peers from settings // A. If no peers exist // i. Determine if broadcast peer exists, call esp_now_is_peer_exist -// ii. Add broadcast peer if necessary, call espnowAddPeer -// iii. Set ESP-NOW state, call espnowSetState(ESPNOW_BROADCASTING) +// ii. Add broadcast peer if necessary, call espNowAddPeer +// iii. Set ESP-NOW state, call espNowSetState(ESPNOW_BROADCASTING) // B. If peers exist, -// i. Set ESP-NOW state, call espnowSetState(ESPNOW_PAIRED) +// i. Set ESP-NOW state, call espNowSetState(ESPNOW_PAIRED) // ii. Loop through peers listed in settings, for each // a. Determine if peer exists, call esp_now_is_peer_exist -// b. Add peer if necessary, call espnowAddPeer +// b. Add peer if necessary, call espNowAddPeer // -// In espnowOnDataReceived +// In espNowOnDataReceived // 11. Save ESP-NOW RSSI -// 12. Set lastEspnowRssiUpdate = millis() +// 12. Set espNowLastRssiUpdate = millis() // 13. If in ESPNOW_PAIRING state // A. Validate message CRC // B. If valid CRC // i. Save peer MAC address -// ii. espnowSetState(ESPNOW_MAC_RECEIVED) +// ii. espNowSetState(ESPNOW_MAC_RECEIVED) // 14. Else if ESPNOW_MAC_RECEIVED state // A. If ESP-NOW is corrections source, correctionLastSeen(CORR_ESPNOW) // i. gnss->pushRawData -// 15. Set espnowIncomingRTCM +// 15. Set espNowIncomingRTCM // // ESP-NOW shutdown from RTK // 1. esp_wifi_set_promiscuous(false) // 2. esp_wifi_set_promiscuous_rx_cb(nullptr) // 3. esp_now_unregister_recv_cb() -// 4. Remove all peers by calling espnowRemovePeer +// 4. Remove all peers by calling espNowRemovePeer // 5. Get WiFi mode // 6. Set WiFi station mode if necessary // 7. esp_wifi_get_protocol // 8. Turn off long range protocol if necessary, call esp_wifi_set_protocol // 9. Turn off ESP-NOW. call esp_now_deinit -// 10. Set ESP-NOW state, call espnowSetState(ESPNOW_OFF) +// 10. Set ESP-NOW state, call espNowSetState(ESPNOW_OFF) // 11. Restart WiFi if necessary //---------------------------------------------------------------------- @@ -297,26 +241,24 @@ const int arduinoEventNameEntries = sizeof(arduinoEventName) / sizeof(arduinoEve #define WIFI_AP_SET_SSID_PASSWORD 0x00000800 #define WIFI_AP_SET_IP_ADDR 0x00001000 #define WIFI_AP_SET_HOST_NAME 0x00002000 -#define WIFI_AP_START_MDNS 0x00004000 -#define WIFI_AP_START_DNS_SERVER 0x00008000 -#define WIFI_AP_ONLINE 0x00010000 +#define WIFI_AP_START_DNS_SERVER 0x00004000 +#define WIFI_AP_ONLINE 0x00008000 // WiFi station -#define WIFI_STA_SET_HOST_NAME 0x00020000 -#define WIFI_STA_DISABLE_AUTO_RECONNECT 0x00040000 -#define WIFI_STA_CONNECT_TO_REMOTE_AP 0x00080000 -#define WIFI_STA_START_MDNS 0x00100000 -#define WIFI_STA_ONLINE 0x00200000 +#define WIFI_STA_SET_HOST_NAME 0x00010000 +#define WIFI_STA_DISABLE_AUTO_RECONNECT 0x00020000 +#define WIFI_STA_CONNECT_TO_REMOTE_AP 0x00040000 +#define WIFI_STA_ONLINE 0x00080000 // ESP-NOW -#define WIFI_EN_SET_CHANNEL 0x00400000 -#define WIFI_EN_SET_PROMISCUOUS_MODE 0x00800000 -#define WIFI_EN_PROMISCUOUS_RX_CALLBACK 0x01000000 -#define WIFI_EN_START_ESP_NOW 0x02000000 -#define WIFI_EN_ESP_NOW_ONLINE 0x04000000 +#define WIFI_EN_SET_CHANNEL 0x00100000 +#define WIFI_EN_SET_PROMISCUOUS_MODE 0x00200000 +#define WIFI_EN_PROMISCUOUS_RX_CALLBACK 0x00400000 +#define WIFI_EN_START_ESP_NOW 0x00800000 +#define WIFI_EN_ESP_NOW_ONLINE 0x01000000 // WIFI_MAX_START must be the last value in the define list -#define WIFI_MAX_START 0x08000000 +#define WIFI_MAX_START 0x02000000 const char * const wifiStartNames[] = { @@ -335,13 +277,12 @@ const char * const wifiStartNames[] = "WIFI_AP_SET_SSID_PASSWORD", "WIFI_AP_SET_IP_ADDR", "WIFI_AP_SET_HOST_NAME", - "WIFI_AP_START_MDNS", + "WIFI_AP_START_DNS_SERVER", "WIFI_AP_ONLINE", "WIFI_STA_SET_HOST_NAME", "WIFI_STA_DISABLE_AUTO_RECONNECT", "WIFI_STA_CONNECT_TO_REMOTE_AP", - "WIFI_STA_START_MDNS", "WIFI_STA_ONLINE", "WIFI_EN_SET_CHANNEL", @@ -367,7 +308,6 @@ const int wifiStartNamesEntries = sizeof(wifiStartNames) / sizeof(wifiStartNames | WIFI_AP_SET_SSID_PASSWORD \ | WIFI_AP_SET_IP_ADDR \ | WIFI_AP_SET_HOST_NAME \ - | WIFI_AP_START_MDNS \ | WIFI_AP_START_DNS_SERVER \ | WIFI_AP_ONLINE) @@ -379,7 +319,6 @@ const int wifiStartNamesEntries = sizeof(wifiStartNames) / sizeof(wifiStartNames | WIFI_STA_SET_HOST_NAME \ | WIFI_STA_DISABLE_AUTO_RECONNECT \ | WIFI_STA_CONNECT_TO_REMOTE_AP \ - | WIFI_STA_START_MDNS \ | WIFI_STA_ONLINE) #define WIFI_STA_RECONNECT (WIFI_STA_START_SCAN \ @@ -388,7 +327,6 @@ const int wifiStartNamesEntries = sizeof(wifiStartNames) / sizeof(wifiStartNames | WIFI_STA_SET_HOST_NAME \ | WIFI_STA_DISABLE_AUTO_RECONNECT \ | WIFI_STA_CONNECT_TO_REMOTE_AP \ - | WIFI_STA_START_MDNS \ | WIFI_STA_ONLINE) #define WIFI_SELECT_CHANNEL (WIFI_AP_SELECT_CHANNEL \ @@ -399,7 +337,6 @@ const int wifiStartNamesEntries = sizeof(wifiStartNames) / sizeof(wifiStartNames | WIFI_STA_SET_HOST_NAME \ | WIFI_STA_DISABLE_AUTO_RECONNECT \ | WIFI_STA_CONNECT_TO_REMOTE_AP \ - | WIFI_STA_START_MDNS \ | WIFI_STA_ONLINE) #define WIFI_STA_FAILED_SCAN (WIFI_STA_START_SCAN \ @@ -409,9 +346,6 @@ const int wifiStartNamesEntries = sizeof(wifiStartNames) / sizeof(wifiStartNames #define WIFI_MAX_TIMEOUT (15 * 60 * 1000) // Timeout in milliseconds #define WIFI_MIN_TIMEOUT (15 * 1000) // Timeout in milliseconds -const char * wifiSoftApSsid = "RTK Config"; -const char * wifiSoftApPassword = nullptr; - //**************************************** // Locals //**************************************** @@ -419,29 +353,24 @@ const char * wifiSoftApPassword = nullptr; // DNS server for Captive Portal static DNSServer dnsServer; -// Start timeout -static uint32_t wifiStartTimeout; -static uint32_t wifiStartLastTry; // The last time WiFi start was attempted - -// WiFi Timer usage: -// * Measure interval to display IP address -static unsigned long wifiDisplayTimer; +static int wifiFailedConnectionAttempts = 0; // Count the number of connection attempts between restarts +static bool wifiReconnectRequest; // Set true to request WiFi reconnection -// WiFi interface status -static bool wifiApRunning; -static bool wifiStationRunning; +const char * wifiSoftApName = "Soft AP"; -static int wifiFailedConnectionAttempts = 0; // Count the number of connection attempts between restarts -static WiFiMulti *wifiMulti; +// Start timeout +static uint32_t wifiStartTimeout; //********************************************************************* // Set WiFi credentials // Enable TCP connections void menuWiFi() { + bool wifiRestartRequested; // Restart WiFi if user changes anything + while (1) { - networkDisplayInterface(NETWORK_WIFI); + networkDisplayInterface(NETWORK_WIFI_STATION); systemPrintln(); systemPrintln("Menu: WiFi Networks"); @@ -480,12 +409,13 @@ void menuWiFi() } // If we are modifying the SSID table, force restart of WiFi - restartWiFi = true; + wifiRestartRequested = true; + wifiFailedConnectionAttempts = 0; } else if (incoming == 'a') { settings.wifiConfigOverAP ^= 1; - restartWiFi = true; + wifiRestartRequested = true; } else if (incoming == 'c') { @@ -508,17 +438,81 @@ void menuWiFi() strcpy(settings.wifiNetworks[x].password, ""); } + if (wifiRestartRequested) + { + // Fake the loss of the IP address + networkInterfaceEventInternetLost(NETWORK_WIFI_STATION, __FILE__, __LINE__); + wifiReconnectRequest = true; + } + clearBuffer(); // Empty buffer of any newline chars } +//********************************************************************* +// Display the soft AP details +void wifiDisplayNetworkData() +{ + bool hasIP; + const char *hostName; + IPAddress ipAddress; + NetworkInterface *netif; + const char *status; + + netif = &WiFi.AP; + ipAddress = WiFi.softAPIP(); + hasIP = ipAddress != "0.0.0.0"; + status = "Off"; + if (netif->started()) + { + status = "Disconnected"; + if (netif->linkUp()) + { + status = "Link Up - No IP address"; + if (hasIP) + status = "Online"; + } + } + systemPrintf("%s: %s%s\r\n", wifiSoftApName, status, netif->isDefault() ? ", default" : ""); + hostName = netif->getHostname(); + if (hostName) + systemPrintf(" Host Name: %s\r\n", hostName); + systemPrintf(" MAC Address: %s\r\n", netif->macAddress().c_str()); + if (hasIP) + { + systemPrintf(" IPv4 Address: %s (Static)\r\n", ipAddress.toString().c_str()); + systemPrintf(" Subnet Mask: %s\r\n", netif->subnetMask().toString().c_str()); + } +} + +//********************************************************************* +// Display the soft AP consumers +void wifiDisplaySoftApStatus() +{ + const char *status; + + // Determine the soft AP status + status = "Stopping"; + if (wifiSoftApOnline) + status = "Online"; + else if (wifiSoftApRunning) + status = "Starting"; + + // Print the network interface status + systemPrintf(" %-10s %s", wifiSoftApName, status); + + // Display the consumers + networkSoftApConsumerPrint(", "); + systemPrintln(); +} + //********************************************************************* // Display the WiFi state void wifiDisplayState() { - systemPrintf("WiFi: %s\r\n", networkInterfaceHasInternet(NETWORK_WIFI) ? "Online" : "Offline"); + systemPrintf("WiFi: %s\r\n", networkInterfaceHasInternet(NETWORK_WIFI_STATION) ? "Online" : "Offline"); systemPrintf(" MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\r\n", wifiMACAddress[0], wifiMACAddress[1], wifiMACAddress[2], wifiMACAddress[3], wifiMACAddress[4], wifiMACAddress[5]); - if (networkInterfaceHasInternet(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION)) { // Get the DNS addresses IPAddress dns1 = WiFi.STA.dnsIP(0); @@ -548,116 +542,63 @@ void wifiDisplayState() } //********************************************************************* -// Process the WiFi events -void wifiEvent(arduino_event_id_t event, arduino_event_info_t info) +// Set the ESP-NOW channel +void wifiEspNowSetChannel(WIFI_CHANNEL_t channel) { - char ssid[sizeof(info.wifi_sta_connected.ssid) + 1]; - IPAddress ipAddress; - - // If we are in AP or AP_STA, the network is immediately marked online - // Once AP is online, don't stop WiFi because STA has various events - if (WiFi.getMode() == WIFI_MODE_STA) - { - // Take the network offline if necessary - if (networkInterfaceHasInternet(NETWORK_WIFI) && (event != ARDUINO_EVENT_WIFI_STA_GOT_IP) && - (event != ARDUINO_EVENT_WIFI_STA_GOT_IP6)) - { - if (settings.debugWifiState) - systemPrintf("Stopping WiFi because of event # %d\r\n", event); - - networkStop(NETWORK_WIFI, settings.debugNetworkLayer); // Stop WiFi to allow it to restart - } - } - - // WiFi State Machine - // - // .--------+<----------+<-----------+<-------------+<----------+<----------+<------------. - // v | | | | | | | - // STOP --> READY --> STA_START --> SCAN_DONE --> CONNECTED --> GOT_IP --> LOST_IP --> DISCONNECTED - // ^ ^ | | - // | '-----------' | - // '-------------------------------------' - // - // Handle the event - switch (event) - { - default: - systemPrintf("ERROR: Unknown WiFi event: %d\r\n", event); - break; - - case ARDUINO_EVENT_WIFI_OFF: - systemPrintln("WiFi Off"); - break; - - case ARDUINO_EVENT_WIFI_READY: - if (settings.debugWifiState) - systemPrintln("WiFi Ready"); - WiFi.setHostname(settings.mdnsHostName); - break; - - case ARDUINO_EVENT_WIFI_SCAN_DONE: - if (settings.debugWifiState) - systemPrintln("WiFi Scan Done"); - // wifi_event_sta_scan_done_t info.wifi_scan_done; - break; - - case ARDUINO_EVENT_WIFI_STA_START: - if (settings.debugWifiState) - systemPrintln("WiFi STA Started"); - break; - - case ARDUINO_EVENT_WIFI_STA_STOP: - if (settings.debugWifiState) - systemPrintln("WiFi STA Stopped"); - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - memcpy(ssid, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); - ssid[info.wifi_sta_connected.ssid_len] = 0; - - ipAddress = WiFi.localIP(); - systemPrintf("WiFi STA connected to %s with IP address: ", ssid); - systemPrintln(ipAddress); - - WiFi.setHostname(settings.mdnsHostName); - break; - - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - memcpy(ssid, info.wifi_sta_disconnected.ssid, info.wifi_sta_disconnected.ssid_len); - ssid[info.wifi_sta_disconnected.ssid_len] = 0; - systemPrintf("WiFi STA disconnected from %s\r\n", ssid); - // wifi_event_sta_disconnected_t info.wifi_sta_disconnected; - break; - - case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - systemPrintln("WiFi STA Auth Mode Changed"); - // wifi_event_sta_authmode_change_t info.wifi_sta_authmode_change; - break; + wifi.espNowSetChannel(channel); +} - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - if (settings.debugWifiState) - { - ipAddress = WiFi.localIP(); - systemPrint("WiFi STA Got IPv4: "); - systemPrintln(ipAddress); - } - networkInterfaceEventInternetAvailable(NETWORK_WIFI); - break; +//********************************************************************* +// Stop ESP-NOW +// Inputs: +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine +// Outputs: +// Returns true if successful and false upon failure +bool wifiEspNowOff(const char * fileName, uint32_t lineNumber) +{ + // Display the call + if (settings.debugEspNow || settings.debugWifiState) + systemPrintf("wifiEspNowOff called in %s at line %d\r\n", + fileName, lineNumber); + + // Turn off ESP-NOW when enabled + if (wifiEspNowRunning) + return wifi.enable(false, + wifiSoftApRunning, + wifiStationRunning, + __FILE__, __LINE__); + return true; +} - case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - if (settings.debugWifiState) - { - ipAddress = WiFi.localIP(); - systemPrint("WiFi STA Got IPv6: "); - systemPrintln(ipAddress); - } - networkInterfaceEventInternetAvailable(NETWORK_WIFI); - break; +//********************************************************************* +// Start ESP-NOW +// Inputs: +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine +// Outputs: +// Returns true if successful and false upon failure +bool wifiEspNowOn(const char * fileName, uint32_t lineNumber) +{ + // Display the call + if (settings.debugEspNow || settings.debugWifiState) + systemPrintf("wifiEspNowOff called in %s at line %d\r\n", + fileName, lineNumber); + + // Turn on ESP-NOW when it is enabled + if (settings.enableEspNow && !wifiEspNowRunning) + return wifi.enable(true, + wifiSoftApRunning, + wifiStationRunning, + __FILE__, __LINE__); + return settings.enableEspNow; +} - case ARDUINO_EVENT_WIFI_STA_LOST_IP: - systemPrintln("WiFi STA Lost IP"); - break; - } +//********************************************************************* +// Return the start timeout in milliseconds +uint32_t wifiGetStartTimeout() +{ + return (wifiStartTimeout); } //********************************************************************* @@ -709,7 +650,7 @@ void wifiPromiscuousRxHandler(void *buf, wifi_promiscuous_pkt_type_t type) { const wifi_promiscuous_pkt_t *ppkt; // Defined in esp_wifi_types_native.h - // All espnow traffic uses action frames which are a subtype of the + // All espNow traffic uses action frames which are a subtype of the // mgmnt frames so filter out everything else. if (type != WIFI_PKT_MGMT) return; @@ -718,6 +659,14 @@ void wifiPromiscuousRxHandler(void *buf, wifi_promiscuous_pkt_type_t type) packetRSSI = ppkt->rx_ctrl.rssi; } +//********************************************************************* +// Reset the last WiFi start attempt +// Useful when WiFi settings have changed +void wifiResetThrottleTimeout() +{ + wifiReconnectionTimer = millis() - WIFI_MAX_TIMEOUT; +} + //********************************************************************* // Set WiFi timeout back to zero // Useful if other things (such as a successful ethernet connection) need @@ -730,129 +679,285 @@ void wifiResetTimeout() } //********************************************************************* -// Starts the WiFi connection state machine (moves from WIFI_STATE_OFF to WIFI_STATE_CONNECTING) -// Sets the appropriate protocols (WiFi + ESP-Now) -// If radio is off entirely, start WiFi -// If ESP-Now is active, only add the LR protocol -// Returns true if WiFi has connected and false otherwise -bool wifiStart() +// Get the IP address being used for the software access point (AP) +// Outputs: +// Returns an IPAddress object containing the IP address used by the +// soft AP +IPAddress wifiSoftApGetIpAddress() { - int wifiStatus; + return wifi.softApOnline() ? WiFi.AP.localIP() : IPAddress((uint32_t)0); +} - // Determine which parts of WiFi need to be started - bool startWiFiStation = false; - bool startWiFiAp = false; +//********************************************************************* +// Get the WiFi soft AP SSID +// Outputs: +// Returns a zero terminated string containing the SSID begin broadcast +// for the WiFi soft AP. The return value of an empty string occurs +// when the soft AP is not online. +const char * wifiSoftApGetSsid() +{ + const char * ssid; - uint16_t consumerTypes = networkGetConsumerTypes(); + ssid = wifi.softApOnline() ? wifi._apSsid : ""; + return ssid; +} - // The consumers need station - if (consumerTypes & (1 << NETIF_WIFI_STA)) - startWiFiStation = true; +//********************************************************************* +// Turn off WiFi soft AP mode +// Inputs: +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine +// Outputs: +// Returns the status of WiFi soft AP stop +bool wifiSoftApOff(const char * fileName, uint32_t lineNumber) +{ + // Display the call + if (settings.debugWifiState) + systemPrintf("wifiSoftApOff called in %s at line %d\r\n", + fileName, lineNumber); - // The consumers need AP - if (consumerTypes & (1 << NETIF_WIFI_AP)) - startWiFiAp = true; + return wifi.enable(wifiEspNowRunning, + false, + wifiStationRunning, + __FILE__, __LINE__); +} - if (startWiFiStation == false && startWiFiAp == false) - { - systemPrintln("wifiStart() requested without any NETCONSUMER combination"); - WIFI_STOP(); - return (false); - } +//********************************************************************* +// Turn on WiFi soft AP mode +// Inputs: +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine +// Outputs: +// Returns the status of WiFi soft AP start +bool wifiSoftApOn(const char * fileName, uint32_t lineNumber) +{ + // Display the call + if (settings.debugWifiState) + systemPrintf("wifiSoftApOn called in %s at line %d\r\n", + fileName, lineNumber); - // Determine if WiFi is already running - if (startWiFiStation == wifiStationRunning && startWiFiAp == wifiApRunning) + return wifi.enable(wifiEspNowRunning, + true, + wifiStationRunning, + __FILE__, __LINE__); +} + +//********************************************************************* +// Start WiFi with throttling, used by wifiStopSequence +void wifiStartThrottled(NetIndex_t index, uintptr_t parameter, bool debug) +{ + // Check for network shutdown + if (networkConsumerCount == 0) { - if (settings.debugWifiState == true) - systemPrintln("WiFi is already running with requested setup"); - return (true); + // Stop the connection attempts + wifiResetThrottleTimeout(); + wifiResetTimeout(); + networkSequenceExit(NETWORK_WIFI_STATION, debug, __FILE__, __LINE__); + return; } - // Handle special cases if no networks have been entered - if (wifiNetworkCount() == 0) + if (wifiReconnectRequest) { - if (startWiFiStation == true && startWiFiAp == false) - { - systemPrintln("Error: Please enter at least one SSID before using WiFi"); - displayNoSSIDs(2000); - WIFI_STOP(); - return false; - } - else if (startWiFiStation == true && startWiFiAp == true) - { - systemPrintln("Error: No SSID available to start WiFi Station during AP"); - // Allow the system to continue in AP only mode - startWiFiStation = false; - } + wifiReconnectRequest = false; + if (settings.debugWifiState) + systemPrintf("WiFi: Attempting WiFi restart\r\n"); } - // Start WiFi - wifiConnect(startWiFiStation, startWiFiAp, settings.wifiConnectTimeoutMs); + if (wifiStationReconnectionRequest()) + networkSequenceNextEntry(NETWORK_WIFI_STATION, debug); +} - // If we are in AP only mode, as long as the AP is started, return true - if (WiFi.getMode() == WIFI_MODE_AP) - return WIFI_SOFT_AP_RUNNING(); +//********************************************************************* +// Stop the WiFi station +// Inputs: +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine +// Outputs: +// Returns true if successful and false upon failure +bool wifiStationOff(const char * fileName, uint32_t lineNumber) +{ + // Display the call + if (settings.debugWifiState) + systemPrintf("wifiStationOff called in %s at line %d\r\n", + fileName, lineNumber); - // If we are in STA or AP+STA mode, return if the station connected successfully - wifiStatus = WiFi.status(); - return (wifiStatus == WL_CONNECTED); + return wifi.enable(wifiEspNowRunning, + wifiSoftApRunning, + false, + __FILE__, __LINE__); } //********************************************************************* -// Stop WiFi and release all resources -void wifiStop() +// Start the WiFi station +// Inputs: +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine +// Outputs: +// Returns true if successful and false upon failure +bool wifiStationOn(const char * fileName, uint32_t lineNumber) { - // Stop the web server - stopWebServer(); + // Display the call + if (settings.debugWifiState) + systemPrintf("wifiStationOn called in %s at line %d\r\n", + fileName, lineNumber); - // Stop the DNS server if we were using the captive portal - if (((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) && settings.enableCaptivePortal) - dnsServer.stop(); + return wifi.enable(wifiEspNowRunning, + wifiSoftApRunning, + true, + __FILE__, __LINE__); +} - wifiFailedConnectionAttempts = 0; // Reset the counter +//********************************************************************* +// Handle WiFi station reconnection requests +bool wifiStationReconnectionRequest() +{ + bool connected; + int minutes; + int seconds; - // If ESP-Now is active, change protocol to only Long Range - if (espnowGetState() > ESPNOW_OFF) + // Restart delay + connected = false; + if ((millis() - wifiReconnectionTimer) < wifiStartTimeout) + return connected; + wifiReconnectionTimer = millis(); + + // Attempt to start WiFi station + if (wifiStationOn(__FILE__, __LINE__)) { - if (WiFi.getMode() != WIFI_STA) - WiFi.mode(WIFI_STA); - - // Enable long range, PHY rate of ESP32 will be 512Kbps or 256Kbps - // esp_wifi_set_protocol requires WiFi to be started - esp_err_t response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR); - if (response != ESP_OK) - systemPrintf("wifiShutdown: Error setting ESP-Now lone protocol: %s\r\n", esp_err_to_name(response)); - else - { - if (settings.debugWifiState == true) - systemPrintln("WiFi disabled, ESP-Now left in place"); - } + // Successfully connected to a remote AP + connected = true; + if (settings.debugWifiState) + systemPrintf("WiFi: WiFi station successfully started\r\n"); + networkSequenceNextEntry(NETWORK_WIFI_STATION, settings.debugNetworkLayer); + wifiFailedConnectionAttempts = 0; } else { - WiFi.mode(WIFI_OFF); - if (settings.debugWifiState == true) - systemPrintln("WiFi Stopped"); + // Failed to connect to a remote AP + if (settings.debugWifiState) + systemPrintf("WiFi: WiFi station failed to start!\r\n"); + + // Account for this connection attempt + wifiFailedConnectionAttempts++; + + // Start the next network interface if necessary + if (wifiFailedConnectionAttempts >= 2) + networkStartNextInterface(NETWORK_WIFI_STATION); + + // Increase the timeout + wifiStartTimeout <<= 1; + if (!wifiStartTimeout) + wifiStartTimeout = WIFI_MIN_TIMEOUT; + else if (wifiStartTimeout > WIFI_MAX_TIMEOUT) + wifiStartTimeout = WIFI_MAX_TIMEOUT; + + // Display the delay + seconds = wifiStartTimeout / MILLISECONDS_IN_A_SECOND; + minutes = seconds / SECONDS_IN_A_MINUTE; + seconds -= minutes * SECONDS_IN_A_MINUTE; + if (settings.debugWifiState) + systemPrintf("WiFi: Delaying %2d:%02d before restarting WiFi\r\n", minutes, seconds); } + return connected; +} - // Take the network offline - networkInterfaceEventInternetLost(NETWORK_WIFI); +//********************************************************************* +// Start WiFi with throttling, used by wifiStopSequence +void wifiStationRestart(NetIndex_t index, uintptr_t parameter, bool debug) +{ + // Check for network shutdown + if (networkConsumerCount == 0) + { + // Stop the connection attempts + wifiResetThrottleTimeout(); + wifiResetTimeout(); + networkSequenceExit(NETWORK_WIFI_STATION, debug, __FILE__, __LINE__); + return; + } + + // Check for a reconnection request + if (wifiReconnectRequest) + { + // Fake a WiFi failure + networkConsumerReconnect(NETWORK_WIFI_STATION); + networkInterfaceEventInternetLost(NETWORK_WIFI_STATION, __FILE__, __LINE__); + + // Clear the bits to perform the restart operation + wifi.clearStarted(WIFI_STA_RECONNECT); + } + wifi.clearStarted(WIFI_STA_ONLINE); + wifiStationOnline = false; + + // Continue the stop sequence + networkSequenceNextEntry(NETWORK_WIFI_STATION, debug); +} + +//********************************************************************* +// Stop WiFi, used by wifiStopSequence +void wifiStop(NetIndex_t index, uintptr_t parameter, bool debug) +{ + networkInterfaceInternetConnectionLost(NETWORK_WIFI_STATION); - if (wifiMulti != nullptr) - wifiMulti = nullptr; + // Stop WiFi station + wifi.enable(wifiEspNowRunning, wifiSoftApRunning, false, __FILE__, __LINE__); + + networkSequenceNextEntry(NETWORK_WIFI_STATION, settings.debugNetworkLayer); +} + +//********************************************************************* +// Stop WiFi and release all resources +void wifiStopAll() +{ + // Stop the web server + stopWebServer(); + + // Stop the Wifi layer + wifi.enable(false, false, false, __FILE__, __LINE__); + + // Take the network offline + networkInterfaceEventInternetLost(NETWORK_WIFI_STATION, __FILE__, __LINE__); // Display the heap state reportHeapNow(settings.debugWifiState); - wifiStationRunning = false; - wifiApRunning = false; } //********************************************************************* -// Needed for wifiStopSequence -void wifiStop(NetIndex_t index, uintptr_t parameter, bool debug) +// Wait for WiFi users to release their connections +void wifiWaitNoUsers(NetIndex_t index, uintptr_t parameter, bool debug) { - wifiStop(); - networkSequenceNextEntry(NETWORK_WIFI, settings.debugNetworkLayer); + static uint32_t lastMsec; + + // Check for network shutdown + if (networkConsumerCount == 0) + { + // Stop the connection attempts + wifiResetThrottleTimeout(); + wifiResetTimeout(); + networkSequenceExit(NETWORK_WIFI_STATION, debug, __FILE__, __LINE__); + return; + } + + // Check for WiFi station users + if (networkUsersActive(NETWORK_WIFI_STATION) == 0) + { + // None, continue the start sequence + if (settings.debugWifiState) + systemPrintf("WiFi: No users\r\n"); + + // Continue the stop sequence + networkSequenceNextEntry(NETWORK_WIFI_STATION, debug); + } + else + { + // Display the network users + uint32_t currentMsec = millis(); + if (settings.debugWifiState && ((currentMsec - lastMsec) > (2 * 1000))) + { + lastMsec = currentMsec; + systemPrintf("WiFi: Waiting for WiFi users to shutdown\r\n"); + networkUserDisplay(NETWORK_WIFI_STATION); + } + } } //********************************************************************* @@ -865,15 +970,45 @@ RTK_WIFI::RTK_WIFI(bool verbose) _apGatewayAddress{(uint32_t)0}, _apIpAddress{IPAddress("192.168.4.1")}, _apMacAddress{0, 0, 0, 0, 0, 0}, - _apSubnetMask{IPAddress("255.255.255.0")}, _channel{0}, - _espNowChannel{0}, _espNowRunning{false}, - _scanRunning{false}, _softApRunning{false}, + _apSubnetMask{IPAddress("255.255.255.0")}, + _espNowChannel{0}, + _scanRunning{false}, _staIpAddress{IPAddress((uint32_t)0)}, _staIpType{0}, _staMacAddress{0, 0, 0, 0, 0, 0}, _staRemoteApSsid{nullptr}, _staRemoteApPassword{nullptr}, _started{false}, _stationChannel{0}, - _timer{0}, _usingDefaultChannel{true}, _verbose{verbose} + _usingDefaultChannel{true}, _verbose{verbose} { + wifiChannel = 0; + wifiEspNowOnline = false; + wifiEspNowRunning = false; + wifiFailedConnectionAttempts = 0; + wifiReconnectionTimer = 0; + wifiSoftApOnline = false; + wifiSoftApRunning = false; + wifiStationOnline = false; + wifiStationRunning = false; + + // Allocate the WiFi soft AP SSID + _apSsid = (char *)rtkMalloc(SSID_LENGTH, "SSID string (_apSsid)"); + if (_apSsid) + _apSsid[0] = 0; + + // Prepare to start WiFi immediately + wifiResetThrottleTimeout(); + wifiResetTimeout(); +} + +//********************************************************************* +// Clear some of the started components +// Inputs: +// components: Bitmask of components to clear +// Outputs: +// Returns the bitmask of started components +WIFI_ACTION_t RTK_WIFI::clearStarted(WIFI_ACTION_t components) +{ + _started = _started & ~components; + return _started; } //********************************************************************* @@ -887,13 +1022,19 @@ bool RTK_WIFI::connect(unsigned long timeout, log_w("WiFi: Not using timeout parameter for connect!\r\n"); // Enable WiFi station if necessary - if (_stationRunning == false) + if (wifiStationRunning == false) { displayWiFiConnect(); - started = enable(_espNowRunning, _softApRunning, true); + started = enable(wifiEspNowRunning, + wifiSoftApRunning, + true, + __FILE__, __LINE__); } - else if (startAP && !_softApRunning) - started = enable(_espNowRunning, true, _stationRunning); + else if (startAP && !wifiSoftApRunning) + started = enable(wifiEspNowRunning, + true, + wifiStationRunning, + __FILE__, __LINE__); // Determine the WiFi station status if (started) @@ -933,14 +1074,29 @@ void RTK_WIFI::displayComponents(const char * text, WIFI_ACTION_t components) // enableESPNow: Enable ESP-NOW mode // enableSoftAP: Enable soft AP mode // enableStataion: Enable station mode +// fileName: Name of file calling the enable routine +// lineNumber: Line number in the file calling the enable routine // Outputs: // Returns true if the modes were successfully configured -bool RTK_WIFI::enable(bool enableESPNow, bool enableSoftAP, bool enableStation) +bool RTK_WIFI::enable(bool enableESPNow, + bool enableSoftAP, + bool enableStation, + const char * fileName, + int lineNumber) { int authIndex; + bool startOrStopSomething; WIFI_ACTION_t starting; + bool status; WIFI_ACTION_t stopping; + // Turn on WiFi debugging if necessary + if (_verbose) + { + settings.debugEspNow = true; + settings.debugWifiState = true; + } + // Determine the next actions starting = 0; stopping = 0; @@ -948,6 +1104,7 @@ bool RTK_WIFI::enable(bool enableESPNow, bool enableSoftAP, bool enableStation) // Display the parameters if (settings.debugWifiState && _verbose) { + systemPrintf("WiFi: RTK_WIFI::enable called from %s line %d\r\n", fileName, lineNumber); systemPrintf("enableESPNow: %s\r\n", enableESPNow ? "true" : "false"); systemPrintf("enableSoftAP: %s\r\n", enableSoftAP ? "true" : "false"); systemPrintf("enableStation: %s\r\n", enableStation ? "true" : "false"); @@ -957,22 +1114,34 @@ bool RTK_WIFI::enable(bool enableESPNow, bool enableSoftAP, bool enableStation) if (enableESPNow) { starting |= WIFI_START_ESP_NOW; - _espNowRunning = true; + wifiEspNowRunning = true; } else { stopping |= WIFI_START_ESP_NOW; - _espNowRunning = false; + wifiEspNowRunning = false; } // Update the soft AP state if (enableSoftAP) { // Verify that the SSID is set - if (wifiSoftApSsid && strlen(wifiSoftApSsid) && wifiSoftApPassword) + if (wifiSoftApSsid && strlen(wifiSoftApSsid)) { - starting |= WIFI_START_SOFT_AP; - _softApRunning = true; + // Allocate the soft AP SSID + if (!_apSsid) + { + _apSsid = (char *)rtkMalloc(strlen(wifiSoftApSsid) + 1, "SSID string (_apSsid)"); + if (_apSsid) + _apSsid[0] = 0; + else + systemPrintf("ERROR: Failed to allocate buffer for AP SSID\r\n"); + } + if (_apSsid) + { + starting |= WIFI_START_SOFT_AP; + wifiSoftApRunning = true; + } } else systemPrintf("ERROR: AP SSID or password is missing\r\n"); @@ -980,48 +1149,49 @@ bool RTK_WIFI::enable(bool enableESPNow, bool enableSoftAP, bool enableStation) else { stopping |= WIFI_START_SOFT_AP; - _softApRunning = false; + wifiSoftApRunning = false; } // Update the station state if (enableStation) { - // Verify that at least one WiFi access point is in the list - if (MAX_WIFI_NETWORKS == 0) + // Verify that at least one SSID is set + for (authIndex = 0; authIndex < MAX_WIFI_NETWORKS; authIndex++) + if (strlen(settings.wifiNetworks[authIndex].ssid)) + break; + if (authIndex >= MAX_WIFI_NETWORKS) { - systemPrintf("ERROR: No entries in wiFiSsidPassword\r\n"); + systemPrintf("ERROR: No valid SSID in settings\r\n"); displayNoSSIDs(2000); } else { - // Verify that at least one SSID is set - for (authIndex = 0; authIndex < MAX_WIFI_NETWORKS; authIndex++) - if (strlen(settings.wifiNetworks[authIndex].ssid)) - { - break; - } - if (authIndex >= MAX_WIFI_NETWORKS) - { - systemPrintf("ERROR: No valid SSID in settings\r\n"); - displayNoSSIDs(2000); - } - else - { - // Start the WiFi station - starting |= WIFI_START_STATION; - _stationRunning = true; - } + // Start the WiFi station + starting |= WIFI_START_STATION; + wifiStationRunning = true; } } else { // Stop the WiFi station stopping |= WIFI_START_STATION; - _stationRunning = false; + wifiStationRunning = false; } // Stop and start the WiFi components - return stopStart(stopping, starting); + startOrStopSomething = (stopping & _started) | (starting & ~_started); + if (startOrStopSomething) + status = stopStart(stopping, starting); + else + // Nothing to do, verify the required devices are online + status = (enableESPNow == ((_started & WIFI_EN_ESP_NOW_ONLINE) != 0)) + && (enableSoftAP == ((_started & WIFI_AP_ONLINE) != 0)) + && (enableStation == ((_started & WIFI_STA_ONLINE) != 0)); + + // Display the final status + if (settings.debugWifiState && _verbose) + systemPrintf("WiFi: RTK_WIFI::enable returning %s\r\n", status ? "true" : "false"); + return status; } //********************************************************************* @@ -1033,13 +1203,6 @@ bool RTK_WIFI::espNowOnline() return (_started & WIFI_EN_ESP_NOW_ONLINE) ? true : false; } -//********************************************************************* -// Get the ESP-NOW status -bool RTK_WIFI::espNowRunning() -{ - return _espNowRunning; -} - //********************************************************************* // Set the ESP-NOW channel // Inputs: @@ -1056,7 +1219,7 @@ void RTK_WIFI::eventHandler(arduino_event_id_t event, arduino_event_info_t info) bool success; if (settings.debugWifiState) - systemPrintf("event: %d (%s)\r\n", event, arduinoEventName[event]); + systemPrintf("event: %d (%s)\r\n", event, Network.eventName(event)); // Handle the event switch (event) @@ -1114,44 +1277,7 @@ void RTK_WIFI::eventHandler(arduino_event_id_t event, arduino_event_info_t info) // Returns the current WiFi channel number WIFI_CHANNEL_t RTK_WIFI::getChannel() { - return _channel; -} - -//********************************************************************* -// Restart WiFi -bool RTK_WIFI::restart(bool always) -{ - // Determine if restart should be perforrmed - if (always || restartWiFi) - { - restartWiFi = false; - - // Determine how WiFi is being used - bool started = false; - bool espNowRunning = _espNowRunning; - bool softApRunning = _softApRunning; - - // Stop the WiFi layer - started = enable(false, false, false); - - // Restart the WiFi layer - if (started) - started = enable(espNowRunning, - softApRunning, - networkConsumers() ? true : false); - - // Return the started state - return started; - } - else - return false; -} - -//********************************************************************* -// Determine if any use of WiFi is starting or is online -bool RTK_WIFI::running() -{ - return _espNowRunning | _softApRunning | _stationRunning; + return wifiChannel; } //********************************************************************* @@ -1174,7 +1300,6 @@ bool RTK_WIFI::setWiFiMode(uint8_t setMode, uint8_t xorMode) uint8_t mode; uint8_t newMode; bool started; - esp_err_t status; started = false; do @@ -1199,12 +1324,19 @@ bool RTK_WIFI::setWiFiMode(uint8_t setMode, uint8_t xorMode) started = WiFi.mode((wifi_mode_t)newMode); if (!started) { - systemPrintf("ERROR: Failed to set %d (%s), status: %d!\r\n", + reportHeapNow(true); + systemPrintf("Current WiFi mode: 0x%08x (%s)\r\n", + mode, + ((mode == 0) ? "WiFi off" + : ((mode & (WIFI_MODE_AP | WIFI_MODE_STA)) == (WIFI_MODE_AP | WIFI_MODE_STA) ? "Soft AP + STA" + : ((mode & (WIFI_MODE_AP | WIFI_MODE_STA)) == WIFI_MODE_AP ? "Soft AP" + : "STA")))); + systemPrintf("ERROR: Failed to set %d (%s)!\r\n", newMode, ((newMode == 0) ? "WiFi off" : ((newMode & (WIFI_MODE_AP | WIFI_MODE_STA)) == (WIFI_MODE_AP | WIFI_MODE_STA) ? "Soft AP + STA mode" : ((newMode & (WIFI_MODE_AP | WIFI_MODE_STA)) == WIFI_MODE_AP ? "Soft AP mode" - : "STA mode"))), status); + : "STA mode")))); break; } if (settings.debugWifiState && _verbose) @@ -1347,9 +1479,15 @@ bool RTK_WIFI::softApConfiguration(IPAddress ipAddress, success = true; if (softApOnline()) { - success = enable(false, false, stationRunning()); + success = enable(false, + false, + wifiStationRunning, + __FILE__, __LINE__); if (success) - success = enable(false, true, stationRunning()); + success = enable(false, + true, + wifiStationRunning, + __FILE__, __LINE__); } return success; } @@ -1389,6 +1527,23 @@ void RTK_WIFI::softApEventHandler(arduino_event_id_t event, arduino_event_info_t } } +//********************************************************************* +// Get the soft AP IP address +// Returns the soft IP address +IPAddress RTK_WIFI::softApIpAddress() +{ + if (softApOnline()) + return _apIpAddress; + return IPAddress((uint32_t)0); +} + +//********************************************************************* +// Get the soft AP status +bool RTK_WIFI::softApOnline() +{ + return (_started & WIFI_AP_ONLINE) ? true : false; +} + //********************************************************************* // Set the soft AP host name // Inputs: @@ -1515,20 +1670,6 @@ bool RTK_WIFI::softApSetIpAddress(const char * ipAddress, return configured; } -//********************************************************************* -// Get the soft AP status -bool RTK_WIFI::softApOnline() -{ - return (_started & WIFI_AP_ONLINE) ? true : false; -} - -//********************************************************************* -// Determine if the soft AP is being started or is onine -bool RTK_WIFI::softApRunning() -{ - return _softApRunning; -} - //********************************************************************* // Set the soft AP SSID and password // Outputs: @@ -1544,7 +1685,8 @@ bool RTK_WIFI::softApSetSsidPassword(const char * ssid, const char * password) if (!created) systemPrintf("ERROR: Failed to set soft AP SSID and Password!\r\n"); else if (settings.debugWifiState) - systemPrintf("WiFi AP: SSID: %s, Password: %s\r\n", ssid, password); + systemPrintf("WiFi AP: SSID: %s%s%s\r\n", ssid, + password ? ", Password: " : "", password ? password : ""); return created; } @@ -1558,7 +1700,10 @@ bool RTK_WIFI::softApSetSsidPassword(const char * ssid, const char * password) // otherwise bool RTK_WIFI::startAp(bool forceAP) { - return enable(_espNowRunning, forceAP | settings.wifiConfigOverAP, _stationRunning); + return enable(wifiEspNowRunning, + forceAP | settings.wifiConfigOverAP, + wifiStationRunning, + __FILE__, __LINE__); } //********************************************************************* @@ -1574,9 +1719,9 @@ bool RTK_WIFI::stationConnectAP() if (settings.debugWifiState) systemPrintf("WiFi connecting to %s on channel %d with %s authorization\r\n", _staRemoteApSsid, - _channel, + wifiChannel, (_staAuthType < WIFI_AUTH_MAX) ? wifiAuthorizationName[_staAuthType] : "Unknown"); - connected = (WiFi.STA.connect(_staRemoteApSsid, _staRemoteApPassword, _channel)); + connected = (WiFi.STA.connect(_staRemoteApSsid, _staRemoteApPassword, wifiChannel)); if (!connected) { if (settings.debugWifiState) @@ -1587,7 +1732,7 @@ bool RTK_WIFI::stationConnectAP() if (settings.debugWifiState) systemPrintf("WiFi station connected to %s on channel %d with %s authorization\r\n", _staRemoteApSsid, - _channel, + wifiChannel, (_staAuthType < WIFI_AUTH_MAX) ? wifiAuthorizationName[_staAuthType] : "Unknown"); // Don't delay the next WiFi start request @@ -1640,14 +1785,6 @@ void RTK_WIFI::stationEventHandler(arduino_event_id_t event, arduino_event_info_ bool success; int type; - // Take the network offline if necessary - if (networkInterfaceHasInternet(NETWORK_WIFI) && (event != ARDUINO_EVENT_WIFI_STA_GOT_IP) && - (event != ARDUINO_EVENT_WIFI_STA_GOT_IP6)) - { - // Stop WiFi to allow it to restart - networkInterfaceEventStop(NETWORK_WIFI); - } - //------------------------------ // WiFi Status Values: // WL_CONNECTED: assigned when connected to a WiFi network @@ -1693,18 +1830,8 @@ void RTK_WIFI::stationEventHandler(arduino_event_id_t event, arduino_event_info_ // Start the reconnection timer if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { - if (settings.debugWifiState && _verbose && !_timer) - systemPrintf("WiFi: Reconnection timer started\r\n"); - _timer = millis(); - if (!_timer) - _timer = 1; - } - else - { - // Stop the reconnection timer - if (settings.debugWifiState && _verbose && _timer) - systemPrintf("WiFi: Reconnection timer stopped\r\n"); - _timer = 0; + networkInterfaceEventInternetLost(NETWORK_WIFI_STATION, __FILE__, __LINE__); + wifiReconnectRequest = true; } // Fall through @@ -1726,6 +1853,12 @@ void RTK_WIFI::stationEventHandler(arduino_event_id_t event, arduino_event_info_ // V case ARDUINO_EVENT_WIFI_STA_LOST_IP: + if (event == ARDUINO_EVENT_WIFI_STA_LOST_IP) + { + networkInterfaceEventInternetLost(NETWORK_WIFI_STATION, __FILE__, __LINE__); + wifiReconnectRequest = true; + } + // Mark the WiFi station offline if (_started & WIFI_STA_ONLINE) systemPrintf("WiFi: Station offline!\r\n"); @@ -1736,16 +1869,6 @@ void RTK_WIFI::stationEventHandler(arduino_event_id_t event, arduino_event_info_ systemPrintf("WiFi station lost IPv%c address %s\r\n", _staIpType, _staIpAddress.toString().c_str()); _staHasIp = false; - - // Stop mDNS if necessary - if (_started & WIFI_STA_START_MDNS) - { - if (settings.debugWifiState && _verbose) - systemPrintf("Calling networkMulticastDNSStop for WiFi station from %s event\r\n", - arduinoEventName[event]); - _started = _started & ~WIFI_STA_START_MDNS; - networkMulticastDNSStop(NETWORK_WIFI); - } _staIpAddress = IPAddress((uint32_t)0); _staIpType = 0; break; @@ -1775,7 +1898,7 @@ void RTK_WIFI::stationEventHandler(arduino_event_id_t event, arduino_event_info_ if (settings.debugWifiState) systemPrintf("WiFi: Got IPv%c address %s\r\n", type, _staIpAddress.toString().c_str()); - networkInterfaceEventInternetAvailable(NETWORK_WIFI); + networkInterfaceEventInternetAvailable(NETWORK_WIFI_STATION); break; } // End of switch } @@ -1816,36 +1939,20 @@ bool RTK_WIFI::stationHostName(const char * hostName) } //********************************************************************* -// Get the station status -bool RTK_WIFI::stationOnline() +// Get the WiFi station IP address +// Returns the IP address of the WiFi station +IPAddress RTK_WIFI::stationIpAddress() { - return (_started & WIFI_STA_ONLINE) ? true : false; + if (stationOnline()) + return _staIpAddress; + return IPAddress((uint32_t)0); } //********************************************************************* -// Handle WiFi station reconnection requests -void RTK_WIFI::stationReconnectionRequest() +// Get the station status +bool RTK_WIFI::stationOnline() { - uint32_t currentMsec; - - // Check for reconnection request - currentMsec = millis(); - if (_timer) - { - if ((currentMsec - _timer) >= WIFI_RECONNECTION_DELAY) - { - _timer = 0; - if (settings.debugWifiState) - systemPrintf("Reconnection timer fired!\r\n"); - - // Start the WiFi scan - if (stationRunning()) - { - _started = _started & ~WIFI_STA_RECONNECT; - stopStart(WIFI_AP_START_MDNS, WIFI_STA_RECONNECT); - } - } - } + return (_started & WIFI_STA_ONLINE) ? true : false; } //********************************************************************* @@ -1900,13 +2007,6 @@ int16_t RTK_WIFI::stationScanForAPs(WIFI_CHANNEL_t channel) return apCount; } -//********************************************************************* -// Get the station status -bool RTK_WIFI::stationRunning() -{ - return _stationRunning; -} - //********************************************************************* // Select the AP and channel to use for WiFi station // Inputs: @@ -1990,6 +2090,16 @@ WIFI_CHANNEL_t RTK_WIFI::stationSelectAP(uint8_t apCount, bool list) return apChannel; } +//********************************************************************* +// Get the SSID of the remote AP +const char * RTK_WIFI::stationSsid() +{ + if (stationOnline()) + return _staRemoteApSsid; + else + return ""; +} + //********************************************************************* // Stop and start WiFi components // Inputs: @@ -1999,15 +2109,17 @@ WIFI_CHANNEL_t RTK_WIFI::stationSelectAP(uint8_t apCount, bool list) // Returns true if the modes were successfully configured bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) { + const WIFI_ACTION_t allOnline = WIFI_AP_ONLINE | WIFI_EN_ESP_NOW_ONLINE | WIFI_STA_ONLINE; int authIndex; WIFI_CHANNEL_t channel; bool defaultChannel; WIFI_ACTION_t delta; - bool enabled; + WIFI_ACTION_t expected; WIFI_ACTION_t mask; WIFI_ACTION_t notStarted; uint8_t primaryChannel; WIFI_ACTION_t restarting; + bool restartWiFiStation; wifi_second_chan_t secondaryChannel; WIFI_ACTION_t startingNow; esp_err_t status; @@ -2015,13 +2127,15 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Determine the next actions notStarted = 0; - enabled = true; + restartWiFiStation = false; // Display the parameters if (settings.debugWifiState && _verbose) { + systemPrintf("WiFi: RTK_WIFI::stopStart called\r\n"); systemPrintf("stopping: 0x%08x\r\n", stopping); systemPrintf("starting: 0x%08x\r\n", starting); + reportHeapNow(true); } //**************************************** @@ -2039,11 +2153,10 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Determine if there is an active channel defaultChannel = _usingDefaultChannel; _usingDefaultChannel = false; - if (((_started & ~stopping) & (WIFI_AP_ONLINE | WIFI_EN_ESP_NOW_ONLINE | WIFI_STA_ONLINE)) - && _channel && !defaultChannel) + if ((allOnline & _started & ~stopping) && wifiChannel && !defaultChannel) { // Continue to use the active channel - channel = _channel; + channel = wifiChannel; if (settings.debugWifiState && _verbose) systemPrintf("channel: %d, active channel\r\n", channel); } @@ -2064,11 +2177,11 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) systemPrintf("channel: Determine by remote AP scan\r\n"); // Restart ESP-NOW if necessary - if (espNowRunning()) + if (wifiEspNowRunning) stopping |= WIFI_START_ESP_NOW; // Restart soft AP if necessary - if (softApRunning()) + if (wifiSoftApRunning) stopping |= WIFI_START_SOFT_AP; } @@ -2097,34 +2210,6 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) systemPrintf("channel: %d, default channel\r\n", channel); } - //**************************************** - // Determine the use of mDNS - //**************************************** - - // It is much more difficult to determine the DHCP address of the RTK - // versus the hard coded IP address of the server. As such give - // priority to the WiFi station for mDNS use. When the station is - // not running or being started, let mDNS start for the soft AP. - - // Determine if mDNS is being used for WiFi station - if (strlen(&settings.mdnsHostName[0])) - { - if (starting & WIFI_STA_START_MDNS) - { - // Don't start mDNS for soft AP - starting &= ~WIFI_AP_START_MDNS; - - // Stop it if it is being used for soft AP - if (_started & WIFI_AP_START_MDNS) - stopping |= WIFI_AP_START_MDNS; - } - } - - // Don't set host name or start mDNS if the host name is not specified - else - starting &= ~(WIFI_AP_SET_HOST_NAME | WIFI_AP_START_MDNS - | WIFI_STA_SET_HOST_NAME | WIFI_STA_START_MDNS); - //**************************************** // Determine if DNS needs to start //**************************************** @@ -2143,6 +2228,9 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Only stop the started components stopping &= _started; + // Determine the components that are being started + expected = starting & allOnline; + // Determine which components are being restarted restarting = _started & stopping & starting; if (settings.debugWifiState && _verbose) @@ -2150,7 +2238,8 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) systemPrintf("0x%08x: _started\r\n", _started); systemPrintf("0x%08x: stopping\r\n", stopping); systemPrintf("0x%08x: starting\r\n", starting); - systemPrintf("0x%08x: restarting\r\n", starting); + systemPrintf("0x%08x: restarting\r\n", restarting); + systemPrintf("0x%08x: expected\r\n", expected); } // Don't start components that are already running and are not being @@ -2188,21 +2277,9 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) } //**************************************** - // Return the use of mDNS to soft AP when WiFi STA stops + // Determine which components should end up online //**************************************** - // Determine if WiFi STA is stopping - if (stopping & WIFI_STA_START_MDNS) - { - // Determine if mDNS should continue to run on soft AP - if ((_started & WIFI_AP_ONLINE) && !(stopping & WIFI_AP_ONLINE)) - { - // Restart mDNS for soft AP - _started = _started & ~WIFI_AP_START_MDNS; - starting |= WIFI_AP_START_MDNS; - } - } - // Stop the components startingNow = starting; do @@ -2297,18 +2374,6 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) _started = _started & ~WIFI_STA_ONLINE; } - // Stop mDNS on WiFi station - if (stopping & WIFI_STA_START_MDNS) - { - if (_started & WIFI_STA_START_MDNS) - { - _started = _started & ~WIFI_STA_START_MDNS; - if (settings.debugWifiState && _verbose) - systemPrintf("Calling networkMulticastDNSStop for WiFi station\r\n"); - networkMulticastDNSStop(NETWORK_WIFI); - } - } - // Disconnect from the remote AP if (stopping & WIFI_STA_CONNECT_TO_REMOTE_AP) { @@ -2380,15 +2445,6 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) _started = _started & ~WIFI_AP_START_DNS_SERVER; } - // Stop mDNS - if (stopping & WIFI_AP_START_MDNS) - { - _started = _started & ~WIFI_AP_START_MDNS; - if (settings.debugWifiState && _verbose) - systemPrintf("Calling networkMulticastDNSStop for soft AP\r\n"); - networkMulticastDNSStop(NETWORK_WIFI); - } - // Handle the soft AP host name if (stopping & WIFI_AP_SET_HOST_NAME) _started = _started & ~WIFI_AP_SET_HOST_NAME; @@ -2411,7 +2467,11 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Stop use of SSID and password if (stopping & WIFI_AP_SET_SSID_PASSWORD) + { _started = _started & ~WIFI_AP_SET_SSID_PASSWORD; + if (_apSsid) + _apSsid[0] = 0; + } stillRunning = _started; @@ -2422,7 +2482,7 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Reset the channel if all components are stopped if ((softApOnline() == false) && (stationOnline() == false)) { - _channel = 0; + wifiChannel = 0; _usingDefaultChannel = true; } @@ -2486,12 +2546,12 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) systemPrintf("channel: %d\r\n", channel); _started = _started | WIFI_STA_START_SCAN; + displayWiFiConnect(); + // Determine if WiFi scan failed, stop WiFi station startup if (wifi.stationScanForAPs(channel) < 0) { starting &= ~WIFI_STA_FAILED_SCAN; - starting |= ((_started | starting) & WIFI_AP_ONLINE) ? WIFI_AP_START_MDNS : 0; - stopping &= ~WIFI_AP_START_MDNS; notStarted |= WIFI_STA_FAILED_SCAN; } } @@ -2503,15 +2563,15 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) _started = _started | WIFI_STA_SELECT_REMOTE_AP; if (channel == 0) { - if (_channel) - systemPrintf("WiFi STA: No compatible remote AP found on channel %d!\r\n", _channel); + if (wifiChannel) + systemPrintf("WiFi STA: No matching remote AP found on channel %d!\r\n", wifiChannel); else - systemPrintf("WiFi STA: No compatible remote AP found!\r\n"); + systemPrintf("WiFi STA: No matching remote AP found!\r\n"); + + displayNoWiFi(2000); // Stop bringing up WiFi station starting &= ~WIFI_STA_NO_REMOTE_AP; - starting |= ((_started | starting) & WIFI_AP_ONLINE) ? WIFI_AP_START_MDNS : 0; - stopping &= ~WIFI_AP_START_MDNS; notStarted |= WIFI_STA_FAILED_SCAN; } } @@ -2530,11 +2590,11 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Use the default channel if necessary if (!channel) channel = WIFI_DEFAULT_CHANNEL; - _channel = channel; + wifiChannel = channel; // Display the selected channel if (settings.debugWifiState) - systemPrintf("Channel: %d selected\r\n", _channel); + systemPrintf("Channel: %d selected\r\n", wifiChannel); } //**************************************** @@ -2544,7 +2604,15 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Set the soft AP SSID and password if (starting & WIFI_AP_SET_SSID_PASSWORD) { - if (!softApSetSsidPassword(wifiSoftApSsid, wifiSoftApPassword)) + // Append the last four digits of the MAC address + if (strlen(_apSsid) == 0) + { + snprintf(_apSsid, SSID_LENGTH, "%s %02X%02X", wifiSoftApSsid, btMACAddress[4], btMACAddress[5]); + _apSsid[SSID_LENGTH - 1] = 0; + } + + // Set the soft AP SSID and password + if (!softApSetSsidPassword(_apSsid, wifiSoftApPassword)) break; _started = _started | WIFI_AP_SET_SSID_PASSWORD; } @@ -2569,35 +2637,18 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) // Set the soft AP host name if (starting & WIFI_AP_SET_HOST_NAME) { + const char * hostName = &settings.mdnsHostName[0]; + // Display the host name if (settings.debugWifiState && _verbose) - systemPrintf("Host name: %s\r\n", &settings.mdnsHostName[0]); + systemPrintf("Host name: %s\r\n", hostName); // Set the host name - if (!softApSetHostName(&settings.mdnsHostName[0])) + if (!softApSetHostName(hostName)) break; _started = _started | WIFI_AP_SET_HOST_NAME; } - // Start mDNS for the AP network - if (starting & WIFI_AP_START_MDNS) - { - if (settings.debugWifiState) - systemPrintf("Starting mDNS on soft AP\r\n"); - if (!networkMulticastDNSStart(NETWORK_WIFI)) - { - systemPrintf("ERROR: Failed to start mDNS for soft AP!\r\n"); - break; - } - if (settings.debugWifiState) - { - systemPrintf("mDNS started on soft AP as %s.local (%s)\r\n", - &settings.mdnsHostName[0], - _apIpAddress.toString().c_str()); - } - _started = _started | WIFI_AP_START_MDNS; - } - // Start the DNS server if (starting & WIFI_AP_START_DNS_SERVER) { @@ -2619,29 +2670,30 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) _started = _started | WIFI_AP_ONLINE; // Display the soft AP status - String mdnsName(""); - if (_started & WIFI_AP_START_MDNS) - mdnsName = String(", local.") + String(&settings.mdnsHostName[0]); - systemPrintf("WiFi: Soft AP online, SSID: %s (%s%s), Password: %s\r\n", - wifiSoftApSsid, + systemPrintf("WiFi: Soft AP online, SSID: %s (%s)%s%s\r\n", + _apSsid ? _apSsid : "", _apIpAddress.toString().c_str(), - mdnsName.c_str(), - wifiSoftApPassword); + wifiSoftApPassword ? ", Password: " : "", + wifiSoftApPassword ? wifiSoftApPassword : ""); } //**************************************** // Start the WiFi station components //**************************************** + restartWiFiStation = true; + // Set the host name if (starting & WIFI_STA_SET_HOST_NAME) { + const char * hostName = &settings.mdnsHostName[0]; + // Display the host name if (settings.debugWifiState && _verbose) - systemPrintf("Host name: %s\r\n", &settings.mdnsHostName[0]); + systemPrintf("Host name: %s\r\n", hostName); // Set the host name - if (!stationHostName(&settings.mdnsHostName[0])) + if (!stationHostName(hostName)) break; _started = _started | WIFI_STA_SET_HOST_NAME; } @@ -2694,33 +2746,13 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) _staIpType = (_staIpAddress.type() == IPv4) ? '4' : '6'; } - // Start mDNS for the WiFi station - if (starting & WIFI_STA_START_MDNS) - { - if (settings.debugWifiState) - systemPrintf("Starting mDNS on WiFi station\r\n"); - if (!networkMulticastDNSStart(NETWORK_WIFI)) - systemPrintf("ERROR: Failed to start mDNS for WiFi station!\r\n"); - else - { - if (settings.debugWifiState) - systemPrintf("mDNS started on WiFi station as %s.local (%s)\r\n", - &settings.mdnsHostName[0], - _staIpAddress.toString().c_str()); - _started = _started | WIFI_STA_START_MDNS; - } - } - // Mark the station online if (starting & WIFI_STA_ONLINE) { + restartWiFiStation = false; _started = _started | WIFI_STA_ONLINE; - String mdnsName(""); - if (_started & WIFI_STA_START_MDNS) - mdnsName = String(", local.") + String(&settings.mdnsHostName[0]); - systemPrintf("WiFi: Station online (%s: %s%s)\r\n", - _staRemoteApSsid, _staIpAddress.toString().c_str(), - mdnsName.c_str()); + systemPrintf("WiFi: Station online (%s: %s)\r\n", + _staRemoteApSsid, _staIpAddress.toString().c_str()); } //**************************************** @@ -2748,7 +2780,7 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) } // Set the ESP-NOW channel - if (primaryChannel != _channel) + if (primaryChannel != wifiChannel) { if (settings.debugWifiState && _verbose) systemPrintf("Calling esp_wifi_set_channel\r\n"); @@ -2823,11 +2855,8 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) systemPrintf("WiFi: ESP-NOW online (%02x:%02x:%02x:%02x:%02x:%02x, channel: %d)\r\n", _staMacAddress[0], _staMacAddress[1], _staMacAddress[2], _staMacAddress[3], _staMacAddress[4], _staMacAddress[5], - _channel); + wifiChannel); } - - // All components started successfully - enabled = true; } while (0); //**************************************** @@ -2873,9 +2902,24 @@ bool RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) if (settings.debugWifiState && _verbose && _started) displayComponents("Started items", _started); + // Restart WiFi if necessary + if (restartWiFiStation) + wifiReconnectRequest = true; + + // Set the online flags + wifiEspNowOnline = espNowOnline(); + wifiSoftApOnline = softApOnline(); + wifiStationOnline = stationOnline(); + // Return the enable status + bool enabled = ((_started & allOnline) == expected); if (!enabled) - systemPrintf("ERROR: RTK_WIFI::enable failed!\r\n"); + systemPrintf("ERROR: RTK_WIFI::stopStart failed!\r\n"); + if (settings.debugWifiState && _verbose) + { + reportHeapNow(true); + systemPrintf("WiFi: RTK_WIFI::stopStart returning; %s\r\n", enabled ? "true" : "false"); + } return enabled; } @@ -2913,12 +2957,12 @@ void RTK_WIFI::test(uint32_t testDurationMsec) case 0: systemPrintf("-------------------- %d: All Stop --------------------\r\n", rand); - enable(false, false, false); + enable(false, false, false, __FILE__, __LINE__); break; case 1: systemPrintf("-------------------- %d: STA Start -------------------\r\n", rand); - enable(false, false, true); + enable(false, false, true, __FILE__, __LINE__); break; case 2: @@ -2928,57 +2972,57 @@ void RTK_WIFI::test(uint32_t testDurationMsec) case 4: systemPrintf("-------------------- %d: Soft AP Start -------------------\r\n", rand); - enable(false, true, false); + enable(false, true, false, __FILE__, __LINE__); break; case 5: systemPrintf("-------------------- %d: Soft AP & STA Start --------------------\r\n", rand); - enable(false, true, true); + enable(false, true, true, __FILE__, __LINE__); break; case 6: systemPrintf("-------------------- %d: Soft AP Start, STA Disconnect -------------------\r\n", rand); if (disconnectFirst) wifi.stationDisconnect(); - enable(false, true, false); + enable(false, true, false, __FILE__, __LINE__); if (!disconnectFirst) wifi.stationDisconnect(); break; case 8: systemPrintf("-------------------- %d: ESP-NOW Start --------------------\r\n", rand); - enable(true, false, false); + enable(true, false, false, __FILE__, __LINE__); break; case 9: systemPrintf("-------------------- %d: ESP-NOW & STA Start -------------------\r\n", rand); - enable(true, false, true); + enable(true, false, true, __FILE__, __LINE__); break; case 0xa: systemPrintf("-------------------- %d: ESP-NOW Start, STA Disconnect --------------\r\n", rand); if (disconnectFirst) wifi.stationDisconnect(); - enable(true, false, false); + enable(true, false, false, __FILE__, __LINE__); if (!disconnectFirst) wifi.stationDisconnect(); break; case 0xc: systemPrintf("-------------------- %d: ESP-NOW & Soft AP Start -------------------\r\n", rand); - enable(true, true, false); + enable(true, true, false, __FILE__, __LINE__); break; case 0xd: systemPrintf("-------------------- %d: ESP-NOW, Soft AP & STA Start --------------------\r\n", rand); - enable(true, true, true); + enable(true, true, true, __FILE__, __LINE__); break; case 0xe: systemPrintf("-------------------- %d: ESP-NOW & Soft AP Start, STA Disconnect -------------------\r\n", rand); if (disconnectFirst) wifi.stationDisconnect(); - enable(true, true, false); + enable(true, true, false, __FILE__, __LINE__); if (!disconnectFirst) wifi.stationDisconnect(); break; @@ -3008,331 +3052,33 @@ void RTK_WIFI::verifyTables() if (WIFI_AUTH_MAX != wifiAuthorizationNameEntries) { systemPrintf("ERROR: Fix wifiAuthorizationName list to match wifi_auth_mode_t in esp_wifi_types.h!\r\n"); - while (1) - { - } - } - - // Verify the Arduino event name table - if (ARDUINO_EVENT_MAX != arduinoEventNameEntries) - { - systemPrintf("ERROR: Fix arduinoEventName list to match arduino_event_id_t in NetworkEvents.h!\r\n"); - while (1) - { - } + reportFatalError("Fix wifiAuthorizationName list to match wifi_auth_mode_t in esp_wifi_types.h!"); } // Verify the start name table if (WIFI_MAX_START != (1 << wifiStartNamesEntries)) { systemPrintf("ERROR: Fix wifiStartNames list to match list of defines!\r\n"); - while (1) - { - } - } -} - -/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - WiFi Status Values: - WL_CONNECTED: assigned when connected to a WiFi network - WL_CONNECTION_LOST: assigned when the connection is lost - WL_CONNECT_FAILED: assigned when the connection fails for all the attempts - WL_DISCONNECTED: assigned when disconnected from a network - WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and - remains active until the number of attempts expires (resulting in - WL_CONNECT_FAILED) or a connection is established (resulting in - WL_CONNECTED) - WL_NO_SHIELD: assigned when no WiFi shield is present - WL_NO_SSID_AVAIL: assigned when no SSID are available - WL_SCAN_COMPLETED: assigned when the scan networks is completed - =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -// WiFi Routines -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -//---------------------------------------- -// Starts WiFi in STA, AP, or STA_AP mode depending on bools -// Returns true if STA connects, or if AP is started -//---------------------------------------- -bool wifiConnect(bool startWiFiStation, bool startWiFiAP, unsigned long timeout) -{ - // Is a change needed? - if (startWiFiStation && startWiFiAP && WiFi.getMode() == WIFI_AP_STA && WiFi.status() == WL_CONNECTED) - return (true); // There is nothing needing to be changed - - if (startWiFiStation && WiFi.getMode() == WIFI_STA && WiFi.status() == WL_CONNECTED) - return (true); // There is nothing needing to be changed - - if (startWiFiAP && WiFi.getMode() == WIFI_AP) - return (true); // There is nothing needing to be changed - - wifiStationRunning = false; // Mark it as offline while we mess about - wifiApRunning = false; // Mark it as offline while we mess about - - wifi_mode_t wifiMode = WIFI_OFF; - wifi_interface_t wifiInterface = WIFI_IF_STA; - - // Establish what WiFi mode we need to be in - if (startWiFiStation && startWiFiAP) - { - systemPrintln("Starting WiFi AP+Station"); - wifiMode = WIFI_AP_STA; - wifiInterface = WIFI_IF_AP; // There is no WIFI_IF_AP_STA - } - else if (startWiFiStation) - { - systemPrintln("Starting WiFi Station"); - wifiMode = WIFI_STA; - wifiInterface = WIFI_IF_STA; - } - else if (startWiFiAP) - { - systemPrintln("Starting WiFi AP"); - wifiMode = WIFI_AP; - wifiInterface = WIFI_IF_AP; - } - - displayWiFiConnect(); - - if (WiFi.mode(wifiMode) == false) // Start WiFi in the appropriate mode - { - systemPrintln("WiFi failed to set mode"); - return (false); - } - - // Verify that the necessary protocols are set - uint8_t protocols = 0; - esp_err_t response = esp_wifi_get_protocol(wifiInterface, &protocols); - if (response != ESP_OK) - systemPrintf("Failed to get protocols: %s\r\n", esp_err_to_name(response)); - - // If ESP-NOW is running, blend in ESP-NOW protocol. - if (espnowGetState() > ESPNOW_OFF) - { - if (protocols != (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR)) - { - esp_err_t response = - esp_wifi_set_protocol(wifiInterface, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | - WIFI_PROTOCOL_LR); // Enable WiFi + ESP-Now. - if (response != ESP_OK) - systemPrintf("Error setting WiFi + ESP-NOW protocols: %s\r\n", esp_err_to_name(response)); - } - } - else - { - // Make sure default WiFi protocols are in place - if (protocols != (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)) - { - esp_err_t response = esp_wifi_set_protocol(wifiInterface, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | - WIFI_PROTOCOL_11N); // Enable WiFi. - if (response != ESP_OK) - systemPrintf("Error setting WiFi protocols: %s\r\n", esp_err_to_name(response)); - } - } - - // Start AP with fixed IP - if (wifiMode == WIFI_AP || wifiMode == WIFI_AP_STA) - { - IPAddress local_IP(192, 168, 4, 1); - IPAddress gateway(192, 168, 4, 1); - IPAddress subnet(255, 255, 255, 0); - - WiFi.softAPConfig(local_IP, gateway, subnet); - - // Determine the AP name - // If in web config mode then 'RTK Config' - // otherwise 'RTK Caster' - - char softApSsid[strlen("RTK Config")]; // Must be short enough to fit OLED Width - - if (inWebConfigMode()) - strncpy(softApSsid, "RTK Config", sizeof(softApSsid)); - // snprintf("%s", sizeof(softApSsid), softApSsid, (const char)"RTK Config"); // TODO use - // settings.webconfigApName - else - strncpy(softApSsid, "RTK Caster", sizeof(softApSsid)); - // snprintf("%s", sizeof(softApSsid), softApSsid, (const char)"RTK Caster"); - - if (WiFi.softAP(softApSsid) == false) - { - systemPrintln("WiFi AP failed to start"); - return (false); - } - systemPrintf("WiFi AP '%s' started with IP: ", softApSsid); - systemPrintln(WiFi.softAPIP()); - - // Start DNS Server - if (dnsServer.start(53, "*", WiFi.softAPIP()) == false) - { - systemPrintln("WiFi DNS Server failed to start"); - return (false); - } - else - { - if (settings.debugWifiState == true) - systemPrintln("DNS Server started"); - } - - wifiApRunning = true; - - // If we're only here to start the AP, then we're done - if (wifiMode == WIFI_AP) - return true; - } - - systemPrintln("Connecting to WiFi... "); - - if (wifiMulti == nullptr) - wifiMulti = new WiFiMulti; - - // Load SSIDs - wifiMulti->APlistClean(); - for (int x = 0; x < MAX_WIFI_NETWORKS; x++) - { - if (strlen(settings.wifiNetworks[x].ssid) > 0) - wifiMulti->addAP((const char *)&settings.wifiNetworks[x].ssid, - (const char *)&settings.wifiNetworks[x].password); - } - - int wifiStatus = wifiMulti->run(timeout); - if (wifiStatus == WL_CONNECTED) - { - wifiResetTimeout(); // If we successfully connected then reset the throttling timeout - wifiStationRunning = true; - return true; - } - if (wifiStatus == WL_DISCONNECTED) - systemPrint("No friendly WiFi networks detected.\r\n"); - else - { - systemPrintf("WiFi failed to connect: error #%d - %s\r\n", wifiStatus, wifiPrintState((wl_status_t)wifiStatus)); - } - wifiStationRunning = false; - return false; -} - -//---------------------------------------- -// Determine if WIFI is connected -//---------------------------------------- -bool wifiIsConnected() -{ - bool connected; - - connected = (WiFi.status() == WL_CONNECTED); - return connected; -} - -//---------------------------------------- -// Determine if WiFi Station is running -//---------------------------------------- -bool wifiStationIsRunning() -{ - return wifiStationRunning; -} - -//---------------------------------------- -// Determine if WiFi AP is running -//---------------------------------------- -bool wifiApIsRunning() -{ - return wifiApRunning; -} - -//---------------------------------------- -// Determine if either Station or AP is running -//---------------------------------------- -bool wifiIsRunning() -{ - if (wifiStationRunning || wifiApRunning) - return true; - return false; -} - -//---------------------------------------- -//---------------------------------------- -uint32_t wifiGetStartTimeout() -{ - return (wifiStartTimeout); -} - -//---------------------------------------- -// Reset the last WiFi start attempt -// Useful when WiFi settings have changed -//---------------------------------------- -void wifiResetThrottleTimeout() -{ - wifiStartLastTry = 0; -} - -//---------------------------------------- -// Start WiFi with throttling -//---------------------------------------- -void wifiThrottledStart(NetIndex_t index, uintptr_t parameter, bool debug) -{ - int seconds; - int minutes; - - // Restart delay - if ((millis() - wifiStartLastTry) < wifiStartTimeout) - return; - wifiStartLastTry = millis(); - - // Start WiFi - if (wifiStart()) - { - networkSequenceNextEntry(NETWORK_WIFI, settings.debugNetworkLayer); - wifiFailedConnectionAttempts = 0; - } - else - { - // Increase the timeout - wifiStartTimeout <<= 1; - if (!wifiStartTimeout) - wifiStartTimeout = WIFI_MIN_TIMEOUT; - else if (wifiStartTimeout > WIFI_MAX_TIMEOUT) - wifiStartTimeout = WIFI_MAX_TIMEOUT; - - wifiFailedConnectionAttempts++; - - // Display the delay - seconds = wifiStartTimeout / MILLISECONDS_IN_A_SECOND; - minutes = seconds / SECONDS_IN_A_MINUTE; - seconds -= minutes * SECONDS_IN_A_MINUTE; - if (settings.debugWifiState) - systemPrintf("WiFi: Delaying %2d:%02d before restarting WiFi\r\n", minutes, seconds); + reportFatalError("Fix wifiStartNames list to match list of defines!!"); } } -//---------------------------------------- +//**************************************** // WiFi start sequence -//---------------------------------------- NETWORK_POLL_SEQUENCE wifiStartSequence[] = { - // State Parameter Description - {wifiThrottledStart, 0, "Initialize WiFi"}, - {nullptr, 0, "Termination"}, + // State Parameter Description + {wifiStartThrottled, 0, "Initialize WiFi"}, + {nullptr, 0, "Termination"}, }; -//---------------------------------------- +//**************************************** // WiFi stop sequence -//---------------------------------------- NETWORK_POLL_SEQUENCE wifiStopSequence[] = { - // State Parameter Description - {wifiStop, 0, "Shutdown WiFi"}, - {nullptr, 0, "Termination"}, + // State Parameter Description + {wifiStationRestart, 0, "Restart WiFi"}, + {wifiWaitNoUsers, 0, "Wait for no WiFi users"}, + {wifiStop, 0, "Shutdown WiFi"}, + {nullptr, 0, "Termination"}, }; -// Returns true if we deem WiFi is not going to connect -// Used to allow cellular to start -bool wifiUnavailable() -{ - if(wifiNetworkCount() == 0) - return true; - - if (wifiFailedConnectionAttempts > 2) - return true; - - return false; -} - #endif // COMPILE_WIFI diff --git a/Firmware/RTK_Everywhere/form.h b/Firmware/RTK_Everywhere/form.h index 9b71701d..a94ee41a 100644 --- a/Firmware/RTK_Everywhere/form.h +++ b/Firmware/RTK_Everywhere/form.h @@ -26,7 +26,7 @@ // python main_js_zipper.py static const uint8_t main_js[] PROGMEM = { - 0x1F, 0x8B, 0x08, 0x08, 0xAB, 0x62, 0xCF, 0x67, 0x02, 0xFF, 0x6D, 0x61, 0x69, 0x6E, 0x2E, 0x6A, + 0x1F, 0x8B, 0x08, 0x08, 0xFA, 0xAA, 0x26, 0x68, 0x02, 0xFF, 0x6D, 0x61, 0x69, 0x6E, 0x2E, 0x6A, 0x73, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0xED, 0x7D, 0xFD, 0x5E, 0xDC, 0x3A, 0x92, 0xE8, 0xFF, 0x79, 0x0A, 0x4D, 0xDF, 0xB9, 0x03, 0x0C, 0x4D, 0xE3, 0x6E, 0x3E, 0x02, 0x21, 0x64, 0x2F, 0x01, 0x92, 0x70, 0x07, 0x08, 0x97, 0x26, 0xE7, 0x9C, 0x9C, 0x6C, 0x96, 0x35, 0x6D, 0xD1, 0x78, 0xD3, @@ -1049,7 +1049,7 @@ static const uint8_t main_js[] PROGMEM = { // python index_html_zipper.py static const uint8_t index_html[] PROGMEM = { - 0x1F, 0x8B, 0x08, 0x08, 0xAB, 0x62, 0xCF, 0x67, 0x02, 0xFF, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E, + 0x1F, 0x8B, 0x08, 0x08, 0xFA, 0xAA, 0x26, 0x68, 0x02, 0xFF, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E, 0x68, 0x74, 0x6D, 0x6C, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0xED, 0x7D, 0x59, 0x76, 0xDB, 0xCA, 0x92, 0xE0, 0xFF, 0x5D, 0x45, 0x16, 0xAB, 0xBB, 0x2C, 0x57, 0x8B, 0x14, 0x48, 0x4A, 0xB2, 0xAC, 0x67, 0xEB, 0x1C, 0x8D, 0xB6, 0xFA, 0x49, 0x32, 0x4B, 0x94, 0xDF, 0x1D, 0xFA, 0x74, 0xDF, 0x03, diff --git a/Firmware/RTK_Everywhere/icons.h b/Firmware/RTK_Everywhere/icons.h index 77919853..e1d81589 100644 --- a/Firmware/RTK_Everywhere/icons.h +++ b/Firmware/RTK_Everywhere/icons.h @@ -1909,7 +1909,7 @@ const iconProperties BaseFixedProperties = {{{ &BaseFixed, BaseTemporary_Width, const uint8_t AccuracyIconXPos64x48 = 0; const uint8_t AccuracyIconYPos64x48 = 18; -const uint8_t AccuracyIconXPos128x64 = 0; +const uint8_t AccuracyIconXPos128x64 = 0; const uint8_t AccuracyIconYPos128x64 = 26; // Just because we can, move accuracy down by 8 pixels on 128x64 const iconProperties CrossHairProperties = {{{ &CrossHair, CrossHair_Width, CrossHair_Height, AccuracyIconXPos64x48, AccuracyIconYPos64x48 }, @@ -2031,4 +2031,4 @@ const iconBatteryProperties BatteryProperties = {{{{ &Battery_0, Battery_Width, {{ &Battery_3, Battery_Width, Battery_Height, BatteryIconXPos64x48, BatteryIconYPos64x48 }, { &Battery_3, Battery_Width, Battery_Height, BatteryIconXPos128x64, BatteryIconYPos128x64 }}}}; -#endif \ No newline at end of file +#endif diff --git a/Firmware/RTK_Everywhere/menuCommands.ino b/Firmware/RTK_Everywhere/menuCommands.ino index 5911d164..f8f6086e 100644 --- a/Firmware/RTK_Everywhere/menuCommands.ino +++ b/Firmware/RTK_Everywhere/menuCommands.ino @@ -1198,7 +1198,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting } else if (strcmp(settingName, "firmwareFileName") == 0) { - mountSDThenUpdate(settingValueStr); + microSDMountThenUpdate(settingValueStr); // If update is successful, it will force system reset and not get here. @@ -1277,7 +1277,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting { // Forget all ESP-Now Peers for (int x = 0; x < settings.espnowPeerCount; x++) - espnowRemovePeer(settings.espnowPeers[x]); + espNowRemovePeer(settings.espnowPeers[x]); settings.espnowPeerCount = 0; knownSetting = true; } @@ -1307,14 +1307,12 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting // Web Config immediately of success or failure // If we're in AP only mode (no internet), try WiFi with current SSIDs - if (networkIsInterfaceStarted(NETWORK_WIFI) && networkHasInternet() == false) - { - wifiStart(); - } + if (networkIsInterfaceStarted(NETWORK_WIFI_STATION) == false) + wifiStationOn(__FILE__, __LINE__); // Get firmware version from server char newVersionCSV[40]; - if (networkHasInternet() == false) + if (networkInterfaceHasInternet(NETWORK_WIFI_STATION) == false) { // No internet. Report error. if (settings.debugWebServer == true) @@ -1328,8 +1326,8 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting { // We got a version number, now determine if it's newer or not char currentVersion[40]; - getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); - if (isReportedVersionNewer(otaReportedVersion, currentVersion) == true) + firmwareVersionGet(currentVersion, sizeof(currentVersion), enableRCFirmware); + if (firmwareVersionIsReportedNewer(otaReportedVersion, currentVersion) == true) { if (settings.debugWebServer == true) systemPrintln("New version detected"); @@ -1405,7 +1403,47 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting // Last catch if (knownSetting == false) { - systemPrintf("Unknown '%s': %0.3lf\r\n", settingName, settingValue); + size_t length; + char * name; + char * suffix; + + // Allocate the buffers + length = strlen(settingName) + 1; + name = (char * )rtkMalloc(2 * length, "name & suffix buffers"); + if (name == nullptr) + systemPrintf("ERROR: Failed allocation, Unknown '%s': %0.3lf\r\n", settingName, settingValue); + else + { + int rtkIndex; + + suffix = &name[length]; + + // Split the name + commandSplitName(settingName, name, length, suffix, length); + + // Loop through the settings entries + for (rtkIndex = 0; rtkIndex < numRtkSettingsEntries; rtkIndex++) + { + const char * command = rtkSettingsEntries[rtkIndex].name; + + // For speed, compare the first letter, then the whole string + if ((command[0] == settingName[0]) && (strcmp(command, name) == 0)) + break; + } + + if (rtkIndex >= numRtkSettingsEntries) + systemPrintf("ERROR: Unknown '%s': %0.3lf\r\n", settingName, settingValue); + else + { + // Display the warning + if (settings.debugWebServer == true) + systemPrintf("Warning: InCommands is false for '%s': %0.3lf\r\n", settingName, settingValue); + knownSetting = true; + } + } + + // Done with the buffer + rtkFree(name, "name & suffix buffers"); } if (knownSetting == true && settingIsString == true) @@ -1431,7 +1469,7 @@ void createSettingsString(char *newSettings) stringRecord(newSettings, "platformPrefix", apPlatformPrefix); char apRtkFirmwareVersion[86]; - getFirmwareVersion(apRtkFirmwareVersion, sizeof(apRtkFirmwareVersion), true); + firmwareVersionGet(apRtkFirmwareVersion, sizeof(apRtkFirmwareVersion), true); stringRecord(newSettings, "rtkFirmwareVersion", apRtkFirmwareVersion); char apGNSSFirmwareVersion[80]; @@ -2365,6 +2403,7 @@ SettingValueResponse getSettingValue(bool inCommands, const char *settingName, c writeToString(settingValueStr, (int)*ptr); knownSetting = true; } + break; case tSysState: { SystemState *ptr = (SystemState *)var; writeToString(settingValueStr, (int)*ptr); @@ -3417,11 +3456,7 @@ bool commandIndexFill() // Allocate the command array. Never freed length = commandCount * sizeof(*commandIndex); - if (online.psram == true) - commandIndex = (int16_t *)ps_malloc(length); - else - commandIndex = (int16_t *)malloc(length); - + commandIndex = (int16_t *)rtkMalloc(length, "Command index array (commandIndex)"); if (!commandIndex) { // Failed to allocate the commandIndex diff --git a/Firmware/RTK_Everywhere/menuFirmware.ino b/Firmware/RTK_Everywhere/menuFirmware.ino index 25af250c..6245e12b 100644 --- a/Firmware/RTK_Everywhere/menuFirmware.ino +++ b/Firmware/RTK_Everywhere/menuFirmware.ino @@ -43,8 +43,10 @@ bool newOTAFirmwareAvailable = false; // Menu //---------------------------------------- +//---------------------------------------- // Update firmware if bin files found -void menuFirmware() +//---------------------------------------- +void firmwareMenu() { while (1) { @@ -52,40 +54,11 @@ void menuFirmware() systemPrintln("Menu: Firmware Update"); char currentVersion[21]; - getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); + firmwareVersionGet(currentVersion, sizeof(currentVersion), enableRCFirmware); systemPrintf("Current firmware: %s\r\n", currentVersion); - // Automatic firmware updates - systemPrintf("a) Automatic firmware updates: %s\r\n", - settings.enableAutoFirmwareUpdate ? "Enabled" : "Disabled"); - - systemPrint("c) Check SparkFun for device firmware: "); - - if (otaRequestFirmwareVersionCheck == true) - systemPrintln("Requested"); - else - systemPrintln("Not requested"); - - systemPrintf("e) Allow Beta Firmware: %s\r\n", enableRCFirmware ? "Enabled" : "Disabled"); - - if (settings.enableAutoFirmwareUpdate) - systemPrintf("i) Automatic firmware check minutes: %d\r\n", settings.autoFirmwareCheckMinutes); - - if (settings.debugWifiState == true) - { - systemPrintf("r) Change RC Firmware JSON URL: %s\r\n", otaRcFirmwareJsonUrl); - systemPrintf("s) Change Firmware JSON URL: %s\r\n", otaFirmwareJsonUrl); - } - - if (isReportedVersionNewer(otaReportedVersion, ¤tVersion[1]) == true || FIRMWARE_VERSION_MAJOR == 99 || - settings.debugFirmwareUpdate == true) - { - systemPrintf("u) Update to new firmware: v%s - ", otaReportedVersion); - if (otaRequestFirmwareUpdate == true) - systemPrintln("Requested"); - else - systemPrintln("Not requested"); - } + // Display the OTA portion of the menu + otaMenuDisplay(currentVersion); for (int x = 0; x < binCount; x++) systemPrintf("%d) Load SD file: %s\r\n", x + 1, binFileNames[x]); @@ -98,46 +71,11 @@ void menuFirmware() { // Adjust incoming to match array incoming--; - updateFromSD(binFileNames[incoming]); - } - - else if (incoming == 'a') - settings.enableAutoFirmwareUpdate ^= 1; - - else if (incoming == 'c') - { - otaRequestFirmwareVersionCheck ^= 1; - } - - else if (incoming == 'e') - { - enableRCFirmware ^= 1; - strncpy(otaReportedVersion, "", sizeof(otaReportedVersion) - 1); // Reset to force c) menu - newOTAFirmwareAvailable = false; - } - - else if ((incoming == 'i') && settings.enableAutoFirmwareUpdate) - { - - getNewSetting("Enter minutes before next firmware check", 1, 999999, &settings.autoFirmwareCheckMinutes); - } - - else if ((incoming == 'r') && (settings.debugWifiState == true)) - { - systemPrint("Enter RC Firmware JSON URL (empty to use default): "); - memset(otaRcFirmwareJsonUrl, 0, sizeof(otaRcFirmwareJsonUrl)); - getUserInputString(otaRcFirmwareJsonUrl, sizeof(otaRcFirmwareJsonUrl) - 1); - } - else if ((incoming == 's') && (settings.debugWifiState == true)) - { - systemPrint("Enter Firmware JSON URL (empty to use default): "); - memset(otaFirmwareJsonUrl, 0, sizeof(otaFirmwareJsonUrl)); - getUserInputString(otaFirmwareJsonUrl, sizeof(otaFirmwareJsonUrl) - 1); + microSDUpdateFirmware(binFileNames[incoming]); } - else if ((incoming == 'u') && (newOTAFirmwareAvailable || settings.debugFirmwareUpdate == true)) + else if (otaMenuProcessInput(incoming)) { - otaRequestFirmwareUpdate ^= 1; // Tell network we need access, and otaUpdate() that we want to update } else if (incoming == 'x') @@ -153,11 +91,170 @@ void menuFirmware() clearBuffer(); // Empty buffer of any newline chars } +//---------------------------------------- +// Version number comes in as v2.7-Jan 5 2023 +// Given a char string, break into version number major/minor, year, month, day +// Returns false if parsing failed +//---------------------------------------- +bool firmwareVersionBreakIntoParts(char *version, int *versionNumberMajor, int *versionNumberMinor, int *year, int *month, + int *day) +{ + char monthStr[20]; + int placed = 0; + + if (enableRCFirmware == false) + { + placed = sscanf(version, "%d.%d", versionNumberMajor, versionNumberMinor); + if (placed != 2) + { + log_d("Failed to sscanf basic"); + return (false); // Something went wrong + } + } + else + { + placed = sscanf(version, "%d.%d-%s %d %d", versionNumberMajor, versionNumberMinor, monthStr, day, year); + + if (placed != 5) + { + log_d("Failed to sscanf RC"); + return (false); // Something went wrong + } + + (*month) = firmwareVersionMapMonthName(monthStr); + if (*month == -1) + return (false); // Something went wrong + } + + return (true); +} + +//---------------------------------------- +// Format the firmware version +//---------------------------------------- +void firmwareVersionFormat(uint8_t major, uint8_t minor, char *buffer, int bufferLength, bool includeDate) +{ + char prefix; + + // Construct the full or release candidate version number + prefix = ENABLE_DEVELOPER ? 'd' : 'v'; + if (enableRCFirmware && (bufferLength >= 21)) + // 123456789012345678901 + // pxxx.yyy-dd-mmm-yyyy0 + snprintf(buffer, bufferLength, "%c%d.%d-%s", prefix, major, minor, __DATE__); + + // Construct a truncated version number + else if (bufferLength >= 9) + // 123456789 + // pxxx.yyy0 + snprintf(buffer, bufferLength, "%c%d.%d", prefix, major, minor); + + // The buffer is too small for the version number + else + { + systemPrintf("ERROR: Buffer too small for version number!\r\n"); + if (bufferLength > 0) + *buffer = 0; + } +} + +//---------------------------------------- +// Get the current firmware version +//---------------------------------------- +void firmwareVersionGet(char *buffer, int bufferLength, bool includeDate) +{ + firmwareVersionFormat(FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, buffer, bufferLength, includeDate); +} + +//---------------------------------------- +// Returns true if otaReportedVersion is newer than currentVersion +// Version number comes in as v2.7-Jan 5 2023 +// 2.7-Jan 5 2023 is newer than v2.7-Jan 1 2023 +// We can't use just the float number: v3.12 is a greater version than v3.9 but it is a smaller float number +//---------------------------------------- +bool firmwareVersionIsReportedNewer(char *reportedVersion, char *currentVersion) +{ + int currentVersionNumberMajor = 0; + int currentVersionNumberMinor = 0; + int currentDay = 0; + int currentMonth = 0; + int currentYear = 0; + + int reportedVersionNumberMajor = 0; + int reportedVersionNumberMinor = 0; + int reportedDay = 0; + int reportedMonth = 0; + int reportedYear = 0; + + firmwareVersionBreakIntoParts(currentVersion, ¤tVersionNumberMajor, ¤tVersionNumberMinor, ¤tYear, + ¤tMonth, ¤tDay); + firmwareVersionBreakIntoParts(reportedVersion, &reportedVersionNumberMajor, &reportedVersionNumberMinor, &reportedYear, + &reportedMonth, &reportedDay); + + if (settings.debugFirmwareUpdate) + { + systemPrintf("currentVersion (%s): %d.%d %d %d %d\r\n", currentVersion, currentVersionNumberMajor, + currentVersionNumberMinor, currentYear, currentMonth, currentDay); + systemPrintf("reportedVersion (%s): %d.%d %d %d %d\r\n", reportedVersion, reportedVersionNumberMajor, + reportedVersionNumberMinor, reportedYear, reportedMonth, reportedDay); + if (enableRCFirmware) + systemPrintln("RC firmware enabled"); + } + + // Production firmware is named "2.6" + // Release Candidate firmware is named "2.6-Dec 5 2022" + + // If the user is not using Release Candidate firmware, then check only the version number + if (enableRCFirmware == false) + { + if (reportedVersionNumberMajor > currentVersionNumberMajor) + return (true); + if (reportedVersionNumberMajor == currentVersionNumberMajor && + reportedVersionNumberMinor > currentVersionNumberMinor) + return (true); + return (false); + } + + // For RC firmware, compare firmware date as well + // Check version number + if (reportedVersionNumberMajor > currentVersionNumberMajor) + return (true); + if (reportedVersionNumberMajor == currentVersionNumberMajor && + reportedVersionNumberMinor > currentVersionNumberMinor) + return (true); + + // Check which date is more recent + // https://stackoverflow.com/questions/5283120/date-comparison-to-find-which-is-bigger-in-c + int reportedVersionScore = reportedDay + reportedMonth * 100 + reportedYear * 2000; + int currentVersionScore = currentDay + currentMonth * 100 + currentYear * 2000; + + if (reportedVersionScore > currentVersionScore) + return (true); + + return (false); +} + +//---------------------------------------- +// https://stackoverflow.com/questions/21210319/assign-month-name-and-integer-values-from-string-using-sscanf +//---------------------------------------- +int firmwareVersionMapMonthName(char *mmm) +{ + static char const *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + for (size_t i = 0; i < sizeof(months) / sizeof(months[0]); i++) + { + if (strcmp(mmm, months[i]) == 0) + return i + 1; + } + return -1; +} + //---------------------------------------- // Firmware update code //---------------------------------------- -void mountSDThenUpdate(const char *firmwareFileName) +//---------------------------------------- +//---------------------------------------- +void microSDMountThenUpdate(const char *firmwareFileName) { bool gotSemaphore; bool wasSdCardOnline; @@ -177,7 +274,7 @@ void mountSDThenUpdate(const char *firmwareFileName) if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) { gotSemaphore = true; - updateFromSD(firmwareFileName); + microSDUpdateFirmware(firmwareFileName); } // End Semaphore check else { @@ -192,10 +289,12 @@ void mountSDThenUpdate(const char *firmwareFileName) xSemaphoreGive(sdCardSemaphore); } +//---------------------------------------- // Looks for matching binary files in root // Loads a global called binCount // Called from beginSD with microSD card mounted and sdCardsemaphore held -void scanForFirmware() +//---------------------------------------- +void microSDScanForFirmware() { // Count available binaries SdFile tempFile; @@ -207,7 +306,7 @@ void scanForFirmware() dir.open("/"); // Open root - binCount = 0; // Reset count in case scanForFirmware is called again + binCount = 0; // Reset count in case microSDScanForFirmware is called again while (tempFile.openNext(&dir, O_READ) && binCount < maxBinFiles) { @@ -219,7 +318,7 @@ void scanForFirmware() { systemPrintln("Forced firmware detected. Loading..."); displayForcedFirmwareUpdate(); - updateFromSD(forceFirmwareFileName); + microSDUpdateFirmware(forceFirmwareFileName); } // Check 'bin' extension @@ -238,10 +337,12 @@ void scanForFirmware() } } +//---------------------------------------- // Look for firmware file on SD card and update as needed -// Called from scanForFirmware with microSD card mounted and sdCardsemaphore held -// Called from mountSDThenUpdate with microSD card mounted and sdCardsemaphore held -void updateFromSD(const char *firmwareFileName) +// Called from microSDScanForFirmware with microSD card mounted and sdCardsemaphore held +// Called from microSDMountThenUpdate with microSD card mounted and sdCardsemaphore held +//---------------------------------------- +void microSDUpdateFirmware(const char *firmwareFileName) { // Count app partitions int appPartitions = 0; @@ -260,32 +361,30 @@ void updateFromSD(const char *firmwareFileName) return; } - // Turn off any tasks so that we are not disrupted - ESPNOW_STOP(); - WIFI_STOP(); - bluetoothStop(); - - // Delete tasks if running - tasksStopGnssUart(); - - systemPrintf("Loading %s\r\n", firmwareFileName); - + // Verify that the firmware file exists if (!sd->exists(firmwareFileName)) { systemPrintln("No firmware file found"); return; } + // Verify that the SdFile object can be allocated SdFile firmwareFile; if (!firmwareFile) { systemPrintln("ERROR - Failed to allocate firmwareFile"); return; } - firmwareFile.open(firmwareFileName, O_READ); - size_t updateSize = firmwareFile.size(); + // Verify that the firmware file can be opened + if (!firmwareFile.open(firmwareFileName, O_READ)) + { + systemPrintf("ERROR - Failed to open %s on the microSD card!\r\n", firmwareFileName); + return; + } + // Verify that something exists in the firmware file + size_t updateSize = firmwareFile.size(); if (updateSize == 0) { systemPrintln("Error: Binary is empty"); @@ -293,6 +392,16 @@ void updateFromSD(const char *firmwareFileName) return; } + // Turn off any tasks so that we are not disrupted + wifiEspNowOff(__FILE__, __LINE__); + wifiStopAll(); + bluetoothStop(); + + // Delete tasks if running + tasksStopGnssUart(); + + systemPrintf("Loading %s\r\n", firmwareFileName); + if (Update.begin(updateSize) == false) { systemPrintln("Update begin failed. Not enough partition space available."); @@ -391,59 +500,16 @@ void updateFromSD(const char *firmwareFileName) systemPrintln("Firmware update failed. Please try again."); } -// Format the firmware version -void formatFirmwareVersion(uint8_t major, uint8_t minor, char *buffer, int bufferLength, bool includeDate) -{ - char prefix; - - // Construct the full or release candidate version number - prefix = ENABLE_DEVELOPER ? 'd' : 'v'; - if (enableRCFirmware && (bufferLength >= 21)) - // 123456789012345678901 - // pxxx.yyy-dd-mmm-yyyy0 - snprintf(buffer, bufferLength, "%c%d.%d-%s", prefix, major, minor, __DATE__); - - // Construct a truncated version number - else if (bufferLength >= 9) - // 123456789 - // pxxx.yyy0 - snprintf(buffer, bufferLength, "%c%d.%d", prefix, major, minor); - - // The buffer is too small for the version number - else - { - systemPrintf("ERROR: Buffer too small for version number!\r\n"); - if (bufferLength > 0) - *buffer = 0; - } -} - -// Get the current firmware version -void getFirmwareVersion(char *buffer, int bufferLength, bool includeDate) -{ - formatFirmwareVersion(FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, buffer, bufferLength, includeDate); -} - -const char *otaGetUrl() -{ - const char *url; - - // Return the user specified URL if it was specified - url = enableRCFirmware ? otaRcFirmwareJsonUrl : otaFirmwareJsonUrl; - if (strlen(url)) - return url; - - // Select the URL for the over-the-air (OTA) updates - return enableRCFirmware ? OTA_RC_FIRMWARE_JSON_URL : OTA_FIRMWARE_JSON_URL; -} +#ifdef COMPILE_OTA_AUTO +//---------------------------------------- // Returns true if we successfully got the versionAvailable // Modifies versionAvailable with OTA getVersion response // This is currently limited to only WiFi (no cellular) because of ESP32OTAPull limitations +//---------------------------------------- bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength) { bool gotVersion = false; -#ifdef COMPILE_NETWORK if (networkHasInternet() == false) { @@ -453,7 +519,7 @@ bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength) // Create a string of the unit's current firmware version char currentVersion[21]; - getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); + firmwareVersionGet(currentVersion, sizeof(currentVersion), enableRCFirmware); systemPrintf("Current firmware version: %s\r\n", currentVersion); @@ -486,55 +552,12 @@ bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength) systemPrintln("OTA failed"); } -#endif // COMPILE_NETWORK return (gotVersion); } -// Updates firmware using OTA pull -// Exits by either updating firmware and resetting, or failing to connect -void otaUpdateFirmware() -{ -#ifdef COMPILE_NETWORK - char versionString[9]; - formatFirmwareVersion(0, 0, versionString, sizeof(versionString), false); - - ESP32OTAPull ota; - - int response; - const char *url = otaGetUrl(); - response = ota.CheckForOTAUpdate(url, &versionString[1], ESP32OTAPull::DONT_DO_UPDATE); - - if (response == ESP32OTAPull::UPDATE_AVAILABLE) - { - systemPrintln("Installing new firmware"); - ota.SetCallback(otaPullCallback); - ota.CheckForOTAUpdate(url, &versionString[1]); // Install new firmware, no reset - - if (apConfigFirmwareUpdateInProcess) - { -#ifdef COMPILE_AP - // Tell AP page to display reset info - sendStringToWebsocket("confirmReset,1,"); -#endif // COMPILE_AP - } - ESP.restart(); - } - else if (response == ESP32OTAPull::NO_UPDATE_AVAILABLE) - systemPrintln("OTA Update: Current firmware is up to date"); - else if (response == ESP32OTAPull::HTTP_FAILED) - systemPrintln("OTA Update: Firmware server not available"); - else - systemPrintln("OTA Update: OTA failed"); -#endif // COMPILE_NETWORK -} - -// Called while the OTA Pull update is happening -void otaPullCallback(int bytesWritten, int totalLength) -{ - otaDisplayPercentage(bytesWritten, totalLength, false); -} - -void otaDisplayPercentage(int bytesWritten, int totalLength, bool alwaysDisplay) +//---------------------------------------- +//---------------------------------------- +void otaDisplayPercentage(int bytesWritten, int totalLength, bool alwaysDisplay) { static int previousPercent = -1; int percent = 100 * bytesWritten / totalLength; @@ -568,162 +591,144 @@ void otaDisplayPercentage(int bytesWritten, int totalLength, bool alwaysDisplay) } } -const char *otaPullErrorText(int code) +//---------------------------------------- +//---------------------------------------- +const char *otaGetUrl() { -#ifdef COMPILE_NETWORK - switch (code) - { - case ESP32OTAPull::UPDATE_AVAILABLE: - return "An update is available but wasn't installed"; - case ESP32OTAPull::NO_UPDATE_PROFILE_FOUND: - return "No profile matches"; - case ESP32OTAPull::NO_UPDATE_AVAILABLE: - return "Profile matched, but update not applicable"; - case ESP32OTAPull::UPDATE_OK: - return "An update was done, but no reboot"; - case ESP32OTAPull::HTTP_FAILED: - return "HTTP GET failure"; - case ESP32OTAPull::WRITE_ERROR: - return "Write error"; - case ESP32OTAPull::JSON_PROBLEM: - return "Invalid JSON"; - case ESP32OTAPull::OTA_UPDATE_FAIL: - return "Update fail (no OTA partition?)"; - default: - if (code > 0) - return "Unexpected HTTP response code"; - break; - } -#endif // COMPILE_NETWORK - return "Unknown error"; + const char *url; + + // Return the user specified URL if it was specified + url = enableRCFirmware ? otaRcFirmwareJsonUrl : otaFirmwareJsonUrl; + if (strlen(url)) + return url; + + // Select the URL for the over-the-air (OTA) updates + return enableRCFirmware ? OTA_RC_FIRMWARE_JSON_URL : OTA_FIRMWARE_JSON_URL; } -// Returns true if otaReportedVersion is newer than currentVersion -// Version number comes in as v2.7-Jan 5 2023 -// 2.7-Jan 5 2023 is newer than v2.7-Jan 1 2023 -// We can't use just the float number: v3.12 is a greater version than v3.9 but it is a smaller float number -bool isReportedVersionNewer(char *reportedVersion, char *currentVersion) +//---------------------------------------- +// Display the OTA portion of the firmware menu +//---------------------------------------- +void otaMenuDisplay(char * currentVersion) { - int currentVersionNumberMajor = 0; - int currentVersionNumberMinor = 0; - int currentDay = 0; - int currentMonth = 0; - int currentYear = 0; + // Automatic firmware updates + systemPrintf("a) Automatic firmware updates: %s\r\n", + settings.enableAutoFirmwareUpdate ? "Enabled" : "Disabled"); - int reportedVersionNumberMajor = 0; - int reportedVersionNumberMinor = 0; - int reportedDay = 0; - int reportedMonth = 0; - int reportedYear = 0; + systemPrint("c) Check SparkFun for device firmware: "); - breakVersionIntoParts(currentVersion, ¤tVersionNumberMajor, ¤tVersionNumberMinor, ¤tYear, - ¤tMonth, ¤tDay); - breakVersionIntoParts(reportedVersion, &reportedVersionNumberMajor, &reportedVersionNumberMinor, &reportedYear, - &reportedMonth, &reportedDay); + if (otaRequestFirmwareVersionCheck == true) + systemPrintln("Requested"); + else + systemPrintln("Not requested"); - if (settings.debugFirmwareUpdate) - { - systemPrintf("currentVersion (%s): %d.%d %d %d %d\r\n", currentVersion, currentVersionNumberMajor, - currentVersionNumberMinor, currentYear, currentMonth, currentDay); - systemPrintf("reportedVersion (%s): %d.%d %d %d %d\r\n", reportedVersion, reportedVersionNumberMajor, - reportedVersionNumberMinor, reportedYear, reportedMonth, reportedDay); - if (enableRCFirmware) - systemPrintln("RC firmware enabled"); - } + systemPrintf("e) Allow Beta Firmware: %s\r\n", enableRCFirmware ? "Enabled" : "Disabled"); - // Production firmware is named "2.6" - // Release Candidate firmware is named "2.6-Dec 5 2022" + if (settings.enableAutoFirmwareUpdate) + systemPrintf("i) Automatic firmware check minutes: %d\r\n", settings.autoFirmwareCheckMinutes); - // If the user is not using Release Candidate firmware, then check only the version number - if (enableRCFirmware == false) + if (settings.debugWifiState == true) { - if (reportedVersionNumberMajor > currentVersionNumberMajor) - return (true); - if (reportedVersionNumberMajor == currentVersionNumberMajor && - reportedVersionNumberMinor > currentVersionNumberMinor) - return (true); - return (false); + systemPrintf("r) Change RC Firmware JSON URL: %s\r\n", otaRcFirmwareJsonUrl); + systemPrintf("s) Change Firmware JSON URL: %s\r\n", otaFirmwareJsonUrl); } - // For RC firmware, compare firmware date as well - // Check version number - if (reportedVersionNumberMajor > currentVersionNumberMajor) - return (true); - if (reportedVersionNumberMajor == currentVersionNumberMajor && - reportedVersionNumberMinor > currentVersionNumberMinor) - return (true); - - // Check which date is more recent - // https://stackoverflow.com/questions/5283120/date-comparison-to-find-which-is-bigger-in-c - int reportedVersionScore = reportedDay + reportedMonth * 100 + reportedYear * 2000; - int currentVersionScore = currentDay + currentMonth * 100 + currentYear * 2000; - - if (reportedVersionScore > currentVersionScore) - return (true); - - return (false); + if (firmwareVersionIsReportedNewer(otaReportedVersion, ¤tVersion[1]) == true || FIRMWARE_VERSION_MAJOR == 99 || + settings.debugFirmwareUpdate == true) + { + systemPrintf("u) Update to new firmware: v%s - ", otaReportedVersion); + if (otaRequestFirmwareUpdate == true) + systemPrintln("Requested"); + else + systemPrintln("Not requested"); + } } -// Version number comes in as v2.7-Jan 5 2023 -// Given a char string, break into version number major/minor, year, month, day -// Returns false if parsing failed -bool breakVersionIntoParts(char *version, int *versionNumberMajor, int *versionNumberMinor, int *year, int *month, - int *day) +//---------------------------------------- +// Process the OTA specific firmware menu input +//---------------------------------------- +bool otaMenuProcessInput(byte incoming) { - char monthStr[20]; - int placed = 0; + if (incoming == 'a') + settings.enableAutoFirmwareUpdate ^= 1; - if (enableRCFirmware == false) + else if (incoming == 'c') + otaRequestFirmwareVersionCheck ^= 1; + + else if (incoming == 'e') { - placed = sscanf(version, "%d.%d", versionNumberMajor, versionNumberMinor); - if (placed != 2) - { - log_d("Failed to sscanf basic"); - return (false); // Something went wrong - } + enableRCFirmware ^= 1; + strncpy(otaReportedVersion, "", sizeof(otaReportedVersion) - 1); // Reset to force c) menu + newOTAFirmwareAvailable = false; } - else - { - placed = sscanf(version, "%d.%d-%s %d %d", versionNumberMajor, versionNumberMinor, monthStr, day, year); - if (placed != 5) - { - log_d("Failed to sscanf RC"); - return (false); // Something went wrong - } + else if ((incoming == 'i') && settings.enableAutoFirmwareUpdate) + getNewSetting("Enter minutes before next firmware check", 1, 999999, &settings.autoFirmwareCheckMinutes); - (*month) = mapMonthName(monthStr); - if (*month == -1) - return (false); // Something went wrong + else if ((incoming == 'r') && (settings.debugWifiState == true)) + { + systemPrint("Enter RC Firmware JSON URL (empty to use default): "); + memset(otaRcFirmwareJsonUrl, 0, sizeof(otaRcFirmwareJsonUrl)); + getUserInputString(otaRcFirmwareJsonUrl, sizeof(otaRcFirmwareJsonUrl) - 1); + } + else if ((incoming == 's') && (settings.debugWifiState == true)) + { + systemPrint("Enter Firmware JSON URL (empty to use default): "); + memset(otaFirmwareJsonUrl, 0, sizeof(otaFirmwareJsonUrl)); + getUserInputString(otaFirmwareJsonUrl, sizeof(otaFirmwareJsonUrl) - 1); } - return (true); + else if ((incoming == 'u') && (newOTAFirmwareAvailable || settings.debugFirmwareUpdate == true)) + otaRequestFirmwareUpdate ^= 1; // Tell network we need access, and otaUpdate() that we want to update + + // Input not associated with OTA menu items + else + return false; + return true; } -// https://stackoverflow.com/questions/21210319/assign-month-name-and-integer-values-from-string-using-sscanf -int mapMonthName(char *mmm) +//---------------------------------------- +// Called while the OTA Pull update is happening +//---------------------------------------- +void otaPullCallback(int bytesWritten, int totalLength) { - static char const *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - for (size_t i = 0; i < sizeof(months) / sizeof(months[0]); i++) - { - if (strcmp(mmm, months[i]) == 0) - return i + 1; - } - return -1; + otaDisplayPercentage(bytesWritten, totalLength, false); } -#ifdef COMPILE_OTA_AUTO - -// Get the OTA state name -const char *otaGetStateName(uint8_t state, char *string) +//---------------------------------------- +// Translate the ESP32OTAPull code into a zero terminated error string +//---------------------------------------- +const char *otaPullErrorText(int code) { - if (state < OTA_STATE_MAX) - return otaStateNames[state]; - sprintf(string, "Unknown state (%d)", state); - return string; + switch (code) + { + case ESP32OTAPull::UPDATE_AVAILABLE: + return "An update is available but wasn't installed"; + case ESP32OTAPull::NO_UPDATE_PROFILE_FOUND: + return "No profile matches"; + case ESP32OTAPull::NO_UPDATE_AVAILABLE: + return "Profile matched, but update not applicable"; + case ESP32OTAPull::UPDATE_OK: + return "An update was done, but no reboot"; + case ESP32OTAPull::HTTP_FAILED: + return "HTTP GET failure"; + case ESP32OTAPull::WRITE_ERROR: + return "Write error"; + case ESP32OTAPull::JSON_PROBLEM: + return "Invalid JSON"; + case ESP32OTAPull::OTA_UPDATE_FAIL: + return "Update fail (no OTA partition?)"; + default: + if (code > 0) + return "Unexpected HTTP response code"; + break; + } + return "Unknown error"; } +//---------------------------------------- // Set the next OTA state +//---------------------------------------- void otaSetState(uint8_t newState) { char string1[40]; @@ -743,7 +748,7 @@ void otaSetState(uint8_t newState) asterisk = "*"; else { - initialState = otaGetStateName(otaState, string1); + initialState = otaStateNameGet(otaState, string1); arrow = " --> "; } } @@ -753,7 +758,7 @@ void otaSetState(uint8_t newState) if (settings.debugFirmwareUpdate) { // Display the new firmware update state - endingState = otaGetStateName(newState, string2); + endingState = otaStateNameGet(newState, string2); if (!online.rtc) systemPrintf("%s%s%s%s\r\n", asterisk, initialState, arrow, endingState); else @@ -774,32 +779,26 @@ void otaSetState(uint8_t newState) reportFatalError("Invalid firmware update state"); } -// Stop the automatic OTA firmware update -void otaUpdateStop() +//---------------------------------------- +// Get the OTA state name +//---------------------------------------- +const char *otaStateNameGet(uint8_t state, char *string) { - if (settings.debugFirmwareUpdate) - systemPrintln("otaUpdateStop called"); - - if (otaState != OTA_STATE_OFF) - { - // Stop network - if (settings.debugFirmwareUpdate) - systemPrintln("Firmware update releasing network request"); - - online.otaClient = false; - - otaRequestFirmwareUpdate = false; // Let the network know we no longer need it - - // Stop the firmware update - otaSetState(OTA_STATE_OFF); - otaLastUpdateCheck = millis(); - } -}; + if (state < OTA_STATE_MAX) + return otaStateNames[state]; + sprintf(string, "Unknown state (%d)", state); + return string; +} +//---------------------------------------- // Initiate firmware version checks, scheduled automatic updates, or requested firmware over-the-air updates +//---------------------------------------- void otaUpdate() { + bool connected; + // Check if we need a scheduled check + connected = networkConsumerIsConnected(NETCONSUMER_OTA_CLIENT); if (settings.enableAutoFirmwareUpdate) { // Wait until it is time to check for a firmware update @@ -827,10 +826,12 @@ void otaUpdate() // Wait for a request from a user or from the scheduler case OTA_STATE_OFF: - if (otaRequestFirmwareVersionCheck == true) - otaSetState(OTA_STATE_WAIT_FOR_NETWORK); - if (otaRequestFirmwareUpdate == true) + if (otaRequestFirmwareVersionCheck || otaRequestFirmwareUpdate) + { + // Start the network if necessary + networkConsumerAdd(NETCONSUMER_OTA_CLIENT, NETWORK_ANY, __FILE__, __LINE__); otaSetState(OTA_STATE_WAIT_FOR_NETWORK); + } break; // Wait for connection to the network @@ -840,14 +841,13 @@ void otaUpdate() otaUpdateStop(); // Wait until the network is connected to the media - // else if (networkHasInternet()) - else if (networkInterfaceHasInternet( - NETWORK_WIFI)) // TODO Remove once OTA works over other network interfaces + else if (connected) { if (settings.debugFirmwareUpdate) systemPrintln("Firmware update connected to network"); // Get the latest firmware version + networkUserAdd(NETCONSUMER_OTA_CLIENT, __FILE__, __LINE__); otaSetState(OTA_STATE_GET_FIRMWARE_VERSION); } break; @@ -855,9 +855,7 @@ void otaUpdate() // Get firmware version from server case OTA_STATE_GET_FIRMWARE_VERSION: // Determine if the network has failed - // if (networkHasInternet() == false) - if (networkInterfaceHasInternet(NETWORK_WIFI) == - false) // TODO Remove once OTA works over other network interfaces + if (!connected) otaUpdateStop(); if (settings.debugFirmwareUpdate) systemPrintln("Checking for latest firmware version"); @@ -874,20 +872,20 @@ void otaUpdate() // Create a string of the unit's current firmware version char currentVersion[21]; - getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); + firmwareVersionGet(currentVersion, sizeof(currentVersion), enableRCFirmware); // We got a version number, now determine if it's newer or not // Allow update if locally compiled developer version - if ((isReportedVersionNewer(otaReportedVersion, ¤tVersion[1]) == true) || + if ((firmwareVersionIsReportedNewer(otaReportedVersion, ¤tVersion[1]) == true) || (currentVersion[0] == 'd') || (FIRMWARE_VERSION_MAJOR == 99)) { + newOTAFirmwareAvailable = true; systemPrintf("Version Check: New firmware version available: %s\r\n", otaReportedVersion); // If we are doing just a version check, set version number, turn off network request and stop // machine if (otaRequestFirmwareVersionCheck == true) { - otaRequestFirmwareVersionCheck = false; otaUpdateStop(); return; } @@ -913,7 +911,7 @@ void otaUpdate() // Update the firmware case OTA_STATE_UPDATE_FIRMWARE: // Determine if the network has failed - if (networkHasInternet() == false) + if (!connected) otaUpdateStop(); else { @@ -926,7 +924,71 @@ void otaUpdate() } } +//---------------------------------------- +// Updates firmware using OTA pull +// Exits by either updating firmware and resetting, or failing to connect +//---------------------------------------- +void otaUpdateFirmware() +{ + char versionString[9]; + firmwareVersionFormat(0, 0, versionString, sizeof(versionString), false); + + ESP32OTAPull ota; + + int response; + const char *url = otaGetUrl(); + response = ota.CheckForOTAUpdate(url, &versionString[1], ESP32OTAPull::DONT_DO_UPDATE); + + if (response == ESP32OTAPull::UPDATE_AVAILABLE) + { + systemPrintln("Installing new firmware"); + ota.SetCallback(otaPullCallback); + ota.CheckForOTAUpdate(url, &versionString[1]); // Install new firmware, no reset + + if (apConfigFirmwareUpdateInProcess) + // Tell AP page to display reset info + sendStringToWebsocket("confirmReset,1,"); + ESP.restart(); + } + else if (response == ESP32OTAPull::NO_UPDATE_AVAILABLE) + systemPrintln("OTA Update: Current firmware is up to date"); + else if (response == ESP32OTAPull::HTTP_FAILED) + systemPrintln("OTA Update: Firmware server not available"); + else + systemPrintln("OTA Update: OTA failed"); +} + +//---------------------------------------- +// Stop the automatic OTA firmware update +//---------------------------------------- +void otaUpdateStop() +{ + if (settings.debugFirmwareUpdate) + systemPrintln("otaUpdateStop called"); + + if (otaState != OTA_STATE_OFF) + { + // Stop network + if (settings.debugFirmwareUpdate) + systemPrintln("Firmware update releasing network request"); + + online.otaClient = false; + otaRequestFirmwareVersionCheck = false; + otaRequestFirmwareUpdate = false; + + // Let the network know we no longer need it + networkConsumerOffline(NETCONSUMER_OTA_CLIENT); + networkConsumerRemove(NETCONSUMER_OTA_CLIENT, NETWORK_ANY, __FILE__, __LINE__); + + // Stop the firmware update + otaSetState(OTA_STATE_OFF); + otaLastUpdateCheck = millis(); + } +}; + +//---------------------------------------- // Verify the OTA update tables +//---------------------------------------- void otaVerifyTables() { // Verify the table lengths @@ -934,10 +996,4 @@ void otaVerifyTables() reportFatalError("Fix otaStateNames table to match OtaState"); } -bool otaNeedsNetwork() -{ - if (otaState >= OTA_STATE_WAIT_FOR_NETWORK && otaState <= OTA_STATE_UPDATE_FIRMWARE) - return true; - return false; -} #endif // COMPILE_OTA_AUTO diff --git a/Firmware/RTK_Everywhere/menuMain.ino b/Firmware/RTK_Everywhere/menuMain.ino index cb000000..b50b994c 100644 --- a/Firmware/RTK_Everywhere/menuMain.ino +++ b/Firmware/RTK_Everywhere/menuMain.ino @@ -132,7 +132,7 @@ void menuMain() { systemPrintln(); char versionString[21]; - getFirmwareVersion(versionString, sizeof(versionString), true); + firmwareVersionGet(versionString, sizeof(versionString), true); RTKBrandAttribute *brandAttributes = getBrandAttributeFromBrand(present.brand); systemPrintf("%s RTK %s %s\r\n", brandAttributes->name, platformPrefix, versionString); @@ -242,7 +242,7 @@ void menuMain() else if (incoming == 'e' && (present.ethernet_ws5500 == true)) menuEthernet(); else if (incoming == 'f') - menuFirmware(); + firmwareMenu(); else if (incoming == 'i') menuCorrectionsPriorities(); else if (incoming == 'n' && (present.ethernet_ws5500 == true)) @@ -323,7 +323,7 @@ void menuMain() // Profile - // AP - // Setup button - -// Factory reset - +// Factory reset - void menuUserProfiles() { uint8_t originalProfileNumber = profileNumber; @@ -530,15 +530,57 @@ void factoryReset(bool alreadyHasSemaphore) } else systemPrintln("GNSS not online. Unable to factoryReset..."); - + systemPrintln("Settings erased successfully. Rebooting. Goodbye!"); delay(2000); ESP.restart(); } +// Display the Bluetooth radio menu item +void mmDisplayBluetoothRadioMenu(char menuChar, BluetoothRadioType_e bluetoothUserChoice) +{ + systemPrintf("%c) Set Bluetooth Mode: ", menuChar); + if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP_AND_BLE) + systemPrintln("Dual"); + else if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP) + systemPrintln("Classic"); + else if (bluetoothUserChoice == BLUETOOTH_RADIO_BLE) + systemPrintln("BLE"); + else + systemPrintln("Off"); +} + +// Select the Bluetooth protocol +BluetoothRadioType_e mmChangeBluetoothProtocol(BluetoothRadioType_e bluetoothUserChoice) +{ + // Change Bluetooth protocol + if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP_AND_BLE) + bluetoothUserChoice = BLUETOOTH_RADIO_SPP; + else if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP) + bluetoothUserChoice = BLUETOOTH_RADIO_BLE; + else if (bluetoothUserChoice == BLUETOOTH_RADIO_BLE) + bluetoothUserChoice = BLUETOOTH_RADIO_OFF; + else if (bluetoothUserChoice == BLUETOOTH_RADIO_OFF) + bluetoothUserChoice = BLUETOOTH_RADIO_SPP_AND_BLE; + return bluetoothUserChoice; +} + +// Restart Bluetooth radio if settings have changed +void mmSetBluetoothProtocol(BluetoothRadioType_e bluetoothUserChoice) +{ + if (bluetoothUserChoice != settings.bluetoothRadioType) + { + bluetoothStop(); + settings.bluetoothRadioType = bluetoothUserChoice; + bluetoothStart(); + } +} + // Configure the internal radio, if available void menuRadio() { + BluetoothRadioType_e bluetoothUserChoice = settings.bluetoothRadioType; + while (1) { systemPrintln(); @@ -574,7 +616,7 @@ void menuRadio() systemPrintln("2) Pair radios"); systemPrintln("3) Forget all radios"); - systemPrintf("4) Current channel: %d\r\n", espnowGetChannel()); + systemPrintf("4) Current channel: %d\r\n", wifiChannel); if (settings.debugEspNow == true) { @@ -609,23 +651,30 @@ void menuRadio() } } + // Display Bluetooth menu + mmDisplayBluetoothRadioMenu('b', bluetoothUserChoice); + systemPrintln("x) Exit"); - int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + byte incoming = getUserInputCharacterNumber(); - if (incoming == 1) + // Select the bluetooth radio + if (incoming == 'b') + bluetoothUserChoice = mmChangeBluetoothProtocol(bluetoothUserChoice); + + else if (incoming == 1) { settings.enableEspNow ^= 1; // Start ESP-NOW so that getChannel runs correctly if (settings.enableEspNow == true) - ESPNOW_START(); + wifiEspNowOn(__FILE__, __LINE__); else - ESPNOW_STOP(); + wifiEspNowOff(__FILE__, __LINE__); } else if (settings.enableEspNow == true && incoming == 2) { - espnowStaticPairing(); + espNowStaticPairing(); } else if (settings.enableEspNow == true && incoming == 3) { @@ -633,10 +682,10 @@ void menuRadio() byte bContinue = getUserInputCharacterNumber(); if (bContinue == 'y') { - if (espnowGetState() > ESPNOW_OFF) + if (wifiEspNowRunning) { for (int x = 0; x < settings.espnowPeerCount; x++) - espnowRemovePeer(settings.espnowPeers[x]); + espNowRemovePeer(settings.espnowPeers[x]); } settings.espnowPeerCount = 0; systemPrintln("Radios forgotten"); @@ -644,21 +693,30 @@ void menuRadio() } else if (settings.enableEspNow == true && incoming == 4) { - if (WIFI_IS_RUNNING() == false) - { - if (getNewSetting("Enter the WiFi channel to use for ESP-NOW communication", 1, 14, - &settings.wifiChannel) == INPUT_RESPONSE_VALID) - espnowSetChannel(settings.wifiChannel); - } - else + if (getNewSetting("Enter the WiFi channel to use for ESP-NOW communication", 1, 14, + &settings.wifiChannel) == INPUT_RESPONSE_VALID) { - systemPrintln("ESP-NOW channel can't be modified while WiFi is active."); + wifiEspNowSetChannel(settings.wifiChannel); + if (settings.wifiChannel) + { + if (settings.wifiChannel == wifiChannel) + systemPrintf("WiFi is already on channel %d.", settings.wifiChannel); + else + { + if (wifiSoftApRunning || wifiStationRunning) + systemPrintf("Restart WiFi to use channel %d.", settings.wifiChannel); + else if (wifiEspNowRunning) + systemPrintf("Restart ESP-NOW to use channel %d.", settings.wifiChannel); + else + systemPrintf("Please start ESP-NOW to use channel %d.", settings.wifiChannel); + } + } } } else if (settings.enableEspNow == true && incoming == 5 && settings.debugEspNow == true) { - if (espnowGetState() == ESPNOW_OFF) - ESPNOW_START(); + if (wifiEspNowRunning == false) + wifiEspNowOn(__FILE__, __LINE__); uint8_t peer1[] = {0xB8, 0xD6, 0x1A, 0x0D, 0x8F, 0x9C}; // Random MAC #ifdef COMPILE_ESPNOW @@ -667,7 +725,7 @@ void menuRadio() else { // Add new peer to system - espnowAddPeer(peer1); + espNowAddPeer(peer1); // Record this MAC to peer list memcpy(settings.espnowPeers[settings.espnowPeerCount], peer1, 6); @@ -676,34 +734,34 @@ void menuRadio() recordSystemSettings(); } - espnowSetState(ESPNOW_PAIRED); + espNowSetState(ESPNOW_PAIRED); #endif } else if (settings.enableEspNow == true && incoming == 6 && settings.debugEspNow == true) { - if (espnowGetState() == ESPNOW_OFF) - ESPNOW_START(); + if (wifiEspNowRunning == false) + wifiEspNowOn(__FILE__, __LINE__); - uint8_t espnowData[] = + uint8_t espNowData[] = "This is the long string to test how quickly we can send one string to the other unit. I am going to " "need a much longer sentence if I want to get a long amount of data into one transmission. This is " "nearing 200 characters but needs to be near 250."; #ifdef COMPILE_ESPNOW - esp_now_send(0, (uint8_t *)&espnowData, sizeof(espnowData)); // Send packet to all peers + esp_now_send(0, (uint8_t *)&espNowData, sizeof(espNowData)); // Send packet to all peers #endif } else if (settings.enableEspNow == true && incoming == 7 && settings.debugEspNow == true) { - if (espnowGetState() == ESPNOW_OFF) - ESPNOW_START(); + if (wifiEspNowRunning == false) + wifiEspNowOn(__FILE__, __LINE__); - uint8_t espnowData[] = + uint8_t espNowData[] = "This is the long string to test how quickly we can send one string to the other unit. I am going to " "need a much longer sentence if I want to get a long amount of data into one transmission. This is " "nearing 200 characters but needs to be near 250."; uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; #ifdef COMPILE_ESPNOW - esp_now_send(broadcastMac, (uint8_t *)&espnowData, sizeof(espnowData)); // Send packet over broadcast + esp_now_send(broadcastMac, (uint8_t *)&espNowData, sizeof(espNowData)); // Send packet over broadcast #endif } @@ -723,6 +781,8 @@ void menuRadio() 10, 600, &settings.loraSerialInteractionTimeout_s); } + else if (incoming == 'x') + break; else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) break; else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) @@ -731,7 +791,10 @@ void menuRadio() printUnknown(incoming); } - ESPNOW_START(); + wifiEspNowOn(__FILE__, __LINE__); + + // Restart Bluetooth radio if settings have changed + mmSetBluetoothProtocol(bluetoothUserChoice); // LoRa radio state machine will start/stop radio upon next updateLora in loop() diff --git a/Firmware/RTK_Everywhere/menuMessages.ino b/Firmware/RTK_Everywhere/menuMessages.ino index af6e24c3..64f52e3a 100644 --- a/Firmware/RTK_Everywhere/menuMessages.ino +++ b/Firmware/RTK_Everywhere/menuMessages.ino @@ -344,7 +344,7 @@ bool beginLogging(const char *customFileName) // SparkFun RTK Express v1.10-Feb 11 2022 char firmwareVersion[30]; // v1.3 December 31 2021 firmwareVersion[0] = 'v'; - getFirmwareVersion(&firmwareVersion[1], sizeof(firmwareVersion) - 1, true); + firmwareVersionGet(&firmwareVersion[1], sizeof(firmwareVersion) - 1, true); createNMEASentence(CUSTOM_NMEA_TYPE_SYSTEM_VERSION, nmeaMessage, sizeof(nmeaMessage), firmwareVersion); // textID, buffer, sizeOfBuffer, text logFile->println(nmeaMessage); @@ -562,7 +562,7 @@ void checkGNSSArrayDefaults() { if (settings.dynamicModel == 254) settings.dynamicModel = UM980_DYN_MODEL_SURVEY; - + if (settings.enableExtCorrRadio == 254) settings.enableExtCorrRadio = false; @@ -609,7 +609,7 @@ void checkGNSSArrayDefaults() { if (settings.dynamicModel == 254) settings.dynamicModel = MOSAIC_DYN_MODEL_QUASISTATIC; - + if (settings.enableExtCorrRadio == 254) settings.enableExtCorrRadio = true; diff --git a/Firmware/RTK_Everywhere/menuPP.ino b/Firmware/RTK_Everywhere/menuPP.ino index 49acf1cf..5eb89e0b 100644 --- a/Firmware/RTK_Everywhere/menuPP.ino +++ b/Firmware/RTK_Everywhere/menuPP.ino @@ -9,7 +9,7 @@ #define DEVELOPMENT_TOKEN 0xAA, 0xBB, 0xCC, 0xDD, 0x00, 0x11, 0x22, 0x33, 0x0A, 0x0B, 0x0C, 0x0D, 0x00, 0x01, 0x02, 0x03 #ifndef POINTPERFECT_LBAND_TOKEN -#warning Using the DEVELOPMENT_TOKEN for point perfect! +#warning Using the DEVELOPMENT_TOKEN for PointPerfect! #define POINTPERFECT_LBAND_TOKEN DEVELOPMENT_TOKEN #define POINTPERFECT_IP_TOKEN DEVELOPMENT_TOKEN #define POINTPERFECT_LBAND_IP_TOKEN DEVELOPMENT_TOKEN @@ -19,10 +19,8 @@ static const uint8_t developmentToken[16] = {DEVELOPMENT_TOKEN}; // Toke static const uint8_t ppLbandToken[16] = {POINTPERFECT_LBAND_TOKEN}; // Token in HEX form static const uint8_t ppIpToken[16] = {POINTPERFECT_IP_TOKEN}; // Token in HEX form static const uint8_t ppLbandIpToken[16] = {POINTPERFECT_LBAND_IP_TOKEN}; // Token in HEX form - -#ifdef COMPILE_NETWORK -MqttClient *menuppMqttClient; -#endif // COMPILE_NETWORK +static unsigned long provisioningStartTime_millis; +static bool provisioningRunning; //---------------------------------------- // L-Band Routines - compiled out @@ -68,7 +66,7 @@ void menuPointPerfectKeys() { long long gpsEpoch = thingstreamEpochToGPSEpoch(settings.pointPerfectCurrentKeyStart); - gpsEpoch += (settings.pointPerfectCurrentKeyDuration / 1000) - + gpsEpoch += (settings.pointPerfectCurrentKeyDuration / MILLISECONDS_IN_A_SECOND) - 1; // Add key duration back to the key start date to get key expiration systemPrintf("%s\r\n", printDateFromGPSEpoch(gpsEpoch)); @@ -94,7 +92,7 @@ void menuPointPerfectKeys() long long gpsEpoch = thingstreamEpochToGPSEpoch(settings.pointPerfectNextKeyStart); gpsEpoch += (settings.pointPerfectNextKeyDuration / - 1000); // Add key duration back to the key start date to get key expiration + MILLISECONDS_IN_A_SECOND); // Add key duration back to the key start date to get key expiration systemPrintf("%s\r\n", printDateFromGPSEpoch(gpsEpoch)); } @@ -134,7 +132,7 @@ void menuPointPerfectKeys() // The u-blox API reports key durations of 5 weeks, but the web interface reports expiration dates // that are 4 weeks. // If the user has manually entered a date, force duration down to four weeks - settings.pointPerfectCurrentKeyDuration = (1000LL * 60 * 60 * 24 * 28); + settings.pointPerfectCurrentKeyDuration = 28LL * MILLISECONDS_IN_A_DAY; // Calculate the next key expiration date settings.pointPerfectNextKeyStart = settings.pointPerfectCurrentKeyStart + @@ -215,7 +213,7 @@ const char *printDaysFromDuration(long long duration) { static char response[strlen("99.99") + 1]; // Make room for terminator - float days = duration / (1000.0 * 60 * 60 * 24); // Convert ms to days + float days = duration / MILLISECONDS_IN_A_DAY; // Convert ms to days sprintf(response, "%0.2f", days); return ((const char *)response); @@ -234,7 +232,7 @@ void createZtpRequest(String &str) // Get the firmware version string char versionString[9]; - getFirmwareVersion(versionString, sizeof(versionString), false); + firmwareVersionGet(versionString, sizeof(versionString), false); // Build the givenName: Name vxx.yy - HardwareID char givenName[100]; @@ -375,32 +373,26 @@ bool checkCertificates() char *keyContents = nullptr; // Allocate the buffers - if (online.psram == true) - { - certificateContents = (char *)ps_malloc(MQTT_CERT_SIZE); - keyContents = (char *)ps_malloc(MQTT_CERT_SIZE); - } - else - { - certificateContents = (char *)malloc(MQTT_CERT_SIZE); - keyContents = (char *)malloc(MQTT_CERT_SIZE); - } - + certificateContents = (char *)rtkMalloc(MQTT_CERT_SIZE, "Certificate buffer (certificateContents)"); + keyContents = (char *)rtkMalloc(MQTT_CERT_SIZE, "Certificate buffer (keyContents)"); if ((!certificateContents) || (!keyContents)) { if (certificateContents) - free(certificateContents); + rtkFree(certificateContents, "Certificate buffer (certificateContents)"); if (keyContents) - free(keyContents); + rtkFree(keyContents, "Certificate buffer (keyContents)"); systemPrintln("Failed to allocate content buffers!"); return (false); } // Load the certificate memset(certificateContents, 0, MQTT_CERT_SIZE); - loadFile("certificate", certificateContents, settings.debugPpCertificate); - - if (checkCertificateValidity(certificateContents, strlen(certificateContents)) == false) + if (!loadFile("certificate", certificateContents, settings.debugPpCertificate)) + { + systemPrintf("ERROR: Failed to open the certificate file\r\n"); + validCertificates = false; + } + else if (checkCertificateValidity(certificateContents, strlen(certificateContents)) == false) { if (settings.debugPpCertificate) systemPrintln("Certificate is corrupt."); @@ -409,9 +401,12 @@ bool checkCertificates() // Load the private key memset(keyContents, 0, MQTT_CERT_SIZE); - loadFile("privateKey", keyContents, settings.debugPpCertificate); - - if (checkPrivateKeyValidity(keyContents, strlen(keyContents)) == false) + if (!loadFile("privateKey", keyContents, settings.debugPpCertificate)) + { + systemPrintf("ERROR: Failed to open the private key file\r\n"); + validCertificates = false; + } + else if (checkPrivateKeyValidity(keyContents, strlen(keyContents)) == false) { if (settings.debugPpCertificate) systemPrintln("PrivateKey is corrupt."); @@ -420,15 +415,18 @@ bool checkCertificates() // Free the content buffers if (certificateContents) - free(certificateContents); + rtkFree(certificateContents, "Certificate buffer (certificateContents)"); if (keyContents) - free(keyContents); + rtkFree(keyContents, "Certificate buffer (keyContents)"); if (settings.debugPpCertificate) { systemPrintf("Stored certificates are %svalid\r\n", validCertificates ? "" : "NOT "); } + // Enable MQTT once the certificates are available and valid + if (validCertificates) + mqttClientStartEnabled(); return (validCertificates); } @@ -500,85 +498,6 @@ void erasePointperfectCredentials() strcpy(settings.pointPerfectNextKey, ""); // Clear contents } -// Get a date from a user -// Return true if validated -// https://www.includehelp.com/c-programs/validate-date.aspx -bool getDate(uint8_t &dd, uint8_t &mm, uint16_t &yy) -{ - systemPrint("Enter Day: "); - dd = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - systemPrint("Enter Month: "); - mm = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - systemPrint("Enter Year (YYYY): "); - yy = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - // check year - if (yy >= 2022 && yy <= 9999) - { - // check month - if (mm >= 1 && mm <= 12) - { - // check days - if ((dd >= 1 && dd <= 31) && (mm == 1 || mm == 3 || mm == 5 || mm == 7 || mm == 8 || mm == 10 || mm == 12)) - return (true); - else if ((dd >= 1 && dd <= 30) && (mm == 4 || mm == 6 || mm == 9 || mm == 11)) - return (true); - else if ((dd >= 1 && dd <= 28) && (mm == 2)) - return (true); - else if (dd == 29 && mm == 2 && (yy % 400 == 0 || (yy % 4 == 0 && yy % 100 != 0))) - return (true); - else - { - printf("Day is invalid.\n"); - return (false); - } - } - else - { - printf("Month is not valid.\n"); - return (false); - } - } - - printf("Year is not valid.\n"); - return (false); -} - -// Given an epoch in ms, return the number of days from given Epoch and now -int daysFromEpoch(long long endEpoch) -{ - long delta = secondsFromEpoch(endEpoch); // number of s between dates - - if (delta == -1) - return (-1); - - delta /= (60 * 60); // hours - - delta /= 24; // days - return ((int)delta); -} - -// Given an epoch in ms, return the number of seconds from given Epoch and now -long secondsFromEpoch(long long endEpoch) -{ - if (online.rtc == false) - { - // If we don't have RTC we can't calculate days to expire - if (settings.debugCorrections == true) - systemPrintln("No RTC available"); - return (-1); - } - - endEpoch /= 1000; // Convert PointPerfect ms Epoch to s - - long currentEpoch = rtc.getEpoch(); - - long delta = endEpoch - currentEpoch; // number of s between dates - return (delta); -} - // Given the key's starting epoch time, and the key's duration // Convert from ms to s // Add leap seconds (the API reports start times with GPS leap seconds removed) @@ -587,148 +506,13 @@ long secondsFromEpoch(long long endEpoch) long long thingstreamEpochToGPSEpoch(long long startEpoch) { long long epoch = startEpoch; - epoch /= 1000; // Convert PointPerfect ms Epoch to s + epoch /= MILLISECONDS_IN_A_SECOND; // Convert PointPerfect ms Epoch to s // Convert Unix Epoch time from PointPerfect to GPS Time Of Week needed for UBX message long long gpsEpoch = epoch - 315964800 + gnss->getLeapSeconds(); // Shift to GPS Epoch. return (gpsEpoch); } -// Covert a given key's expiration date to a GPS Epoch, so that we can calculate GPS Week and ToW -// Add a millisecond to roll over from 11:59UTC to midnight of the following day -// Convert from unix epoch (time lib outputs unix) to GPS epoch (the NED-D9S expects) -long long dateToGPSEpoch(uint8_t day, uint8_t month, uint16_t year) -{ - long long unixEpoch = dateToUnixEpoch(day, month, year); // Returns Unix Epoch - - // Convert Unix Epoch time from PP to GPS Time Of Week needed for UBX message - long long gpsEpoch = unixEpoch - 315964800; // Shift to GPS Epoch. - - return (gpsEpoch); -} - -// Given an epoch, set the GPSWeek and GPSToW -void epochToWeekToW(long long epoch, uint16_t *GPSWeek, uint32_t *GPSToW) -{ - *GPSWeek = (uint16_t)(epoch / (7 * 24 * 60 * 60)); - *GPSToW = (uint32_t)(epoch % (7 * 24 * 60 * 60)); -} - -// Given a GPSWeek and GPSToW, set the epoch -void WeekToWToUnixEpoch(uint64_t *unixEpoch, uint16_t GPSWeek, uint32_t GPSToW) -{ - *unixEpoch = GPSWeek * (7 * 24 * 60 * 60); // 2192 - *unixEpoch += GPSToW; // 518400 - *unixEpoch += 315964800; -} - -// Given a GPS Week and ToW, convert to an expiration date -void gpsWeekToWToDate(uint16_t keyGPSWeek, uint32_t keyGPSToW, long *expDay, long *expMonth, long *expYear) -{ - long gpsDays = gpsToMjd(0, (long)keyGPSWeek, (long)keyGPSToW); // Covert ToW and Week to # of days since Jan 6, 1980 - mjdToDate(gpsDays, expYear, expMonth, expDay); -} - -// Given a date, convert into epoch -// https://www.epochconverter.com/programming/c -long dateToUnixEpoch(uint8_t day, uint8_t month, uint16_t year) -{ - struct tm t; - time_t t_of_day; - - t.tm_year = year - 1900; - t.tm_mon = month - 1; - t.tm_mday = day; - - t.tm_hour = 0; - t.tm_min = 0; - t.tm_sec = 0; - t.tm_isdst = -1; // Is DST on? 1 = yes, 0 = no, -1 = unknown - - t_of_day = mktime(&t); - - return (t_of_day); -} - -// Given a date, calculate and return the key start in unixEpoch -void dateToKeyStart(uint8_t expDay, uint8_t expMonth, uint16_t expYear, uint64_t *settingsKeyStart) -{ - long long expireUnixEpoch = dateToUnixEpoch(expDay, expMonth, expYear); - - // Thingstream lists the date that a key expires at midnight - // So if a user types in March 7th, 2022 as exp date the key's Week and ToW need to be - // calculated from (March 7th - 27 days). - long long startUnixEpoch = expireUnixEpoch - (27 * 24 * 60 * 60); // Move back 27 days - - // Additionally, Thingstream seems to be reporting Epochs that do not have leap seconds - startUnixEpoch -= gnss->getLeapSeconds(); // Modify our Epoch to match Point Perfect - - // PointPerfect uses/reports unix epochs in milliseconds - *settingsKeyStart = startUnixEpoch * 1000L; // Convert to ms - - uint16_t keyGPSWeek; - uint32_t keyGPSToW; - long long gpsEpoch = thingstreamEpochToGPSEpoch(*settingsKeyStart); - - epochToWeekToW(gpsEpoch, &keyGPSWeek, &keyGPSToW); - - // Print ToW and Week for debugging - if (settings.debugCorrections == true) - { - systemPrintf(" expireUnixEpoch: %lld - %s\r\n", expireUnixEpoch, printDateFromUnixEpoch(expireUnixEpoch)); - systemPrintf(" startUnixEpoch: %lld - %s\r\n", startUnixEpoch, printDateFromUnixEpoch(startUnixEpoch)); - systemPrintf(" gpsEpoch: %lld - %s\r\n", gpsEpoch, printDateFromGPSEpoch(gpsEpoch)); - systemPrintf(" KeyStart: %lld - %s\r\n", *settingsKeyStart, printDateFromUnixEpoch(*settingsKeyStart)); - systemPrintf(" keyGPSWeek: %d\r\n", keyGPSWeek); - systemPrintf(" keyGPSToW: %d\r\n", keyGPSToW); - } -} - -/* - http://www.leapsecond.com/tools/gpsdate.c - Return Modified Julian Day given calendar year, - month (1-12), and day (1-31). - - Valid for Gregorian dates from 17-Nov-1858. - - Adapted from sci.astro FAQ. -*/ -long dateToMjd(long Year, long Month, long Day) -{ - return 367 * Year - 7 * (Year + (Month + 9) / 12) / 4 - 3 * ((Year + (Month - 9) / 7) / 100 + 1) / 4 + - 275 * Month / 9 + Day + 1721028 - 2400000; -} - -/* - Convert Modified Julian Day to calendar date. - - Assumes Gregorian calendar. - - Adapted from Fliegel/van Flandern ACM 11/#10 p 657 Oct 1968. -*/ -void mjdToDate(long Mjd, long *Year, long *Month, long *Day) -{ - long J, C, Y, M; - - J = Mjd + 2400001 + 68569; - C = 4 * J / 146097; - J = J - (146097 * C + 3) / 4; - Y = 4000 * (J + 1) / 1461001; - J = J - 1461 * Y / 4 + 31; - M = 80 * J / 2447; - *Day = J - 2447 * M / 80; - J = M / 11; - *Month = M + 2 - (12 * J); - *Year = 100 * (C - 49) + Y + J; -} - -/* - Convert GPS Week and Seconds to Modified Julian Day. - - Ignores UTC leap seconds. -*/ -long gpsToMjd(long GpsCycle, long GpsWeek, long GpsSeconds) -{ - long GpsDays = ((GpsCycle * 1024) + GpsWeek) * 7 + (GpsSeconds / 86400); - // GpsDays -= 1; //Correction - return dateToMjd(1980, 1, 6) + GpsDays; -} - //---------------------------------------- // Global L-Band Routines //---------------------------------------- @@ -953,7 +737,8 @@ void menuPointPerfect() systemPrintln("Menu: PointPerfect Corrections"); if (settings.debugCorrections == true) - systemPrintf("Time to first RTK Fix: %ds Restarts: %d\r\n", rtkTimeToFixMs / 1000, floatLockRestarts); + systemPrintf("Time to first RTK Fix: %ds Restarts: %d\r\n", + rtkTimeToFixMs / MILLISECONDS_IN_A_SECOND, floatLockRestarts); if (settings.debugCorrections == true) systemPrintf("settings.pointPerfectKeyDistributionTopic: %s\r\n", @@ -1159,7 +944,7 @@ void updateLBandCorrections() i2cLBand.checkCallbacks(); // Check if any L-Band callbacks are waiting to be processed. // If a certain amount of time has elapsed between last decryption, turn off L-Band icon - if (lbandCorrectionsReceived == true && millis() - lastLBandDecryption > 5000) + if (lbandCorrectionsReceived == true && millis() - lastLBandDecryption > (5 * MILLISECONDS_IN_A_SECOND)) lbandCorrectionsReceived = false; // If we don't get an L-Band fix within Timeout, hot-start ZED-F9x @@ -1168,19 +953,19 @@ void updateLBandCorrections() if (lbandTimeFloatStarted == 0) lbandTimeFloatStarted = millis(); - if (millis() - lbandLastReport > 1000) + if (millis() - lbandLastReport > MILLISECONDS_IN_A_SECOND) { lbandLastReport = millis(); if (settings.debugCorrections == true) systemPrintf("ZED restarts: %d Time remaining before Float lock forced restart: %ds\r\n", floatLockRestarts, - settings.lbandFixTimeout_seconds - ((millis() - lbandTimeFloatStarted) / 1000)); + settings.lbandFixTimeout_seconds - ((millis() - lbandTimeFloatStarted) / MILLISECONDS_IN_A_SECOND)); } if (settings.lbandFixTimeout_seconds > 0) { - if ((millis() - lbandTimeFloatStarted) > (settings.lbandFixTimeout_seconds * 1000L)) + if ((millis() - lbandTimeFloatStarted) > (settings.lbandFixTimeout_seconds * MILLISECONDS_IN_A_SECOND)) { floatLockRestarts++; @@ -1201,7 +986,7 @@ void updateLBandCorrections() rtkTimeToFixMs = millis(); if (settings.debugCorrections == true) - systemPrintf("Time to first RTK Fix: %ds\r\n", rtkTimeToFixMs / 1000); + systemPrintf("Time to first RTK Fix: %ds\r\n", rtkTimeToFixMs / MILLISECONDS_IN_A_SECOND); } else { @@ -1216,29 +1001,19 @@ void updateLBandCorrections() enum ProvisioningStates { PROVISIONING_OFF = 0, - PROVISIONING_WAIT_RTC, - PROVISIONING_NOT_STARTED, PROVISIONING_CHECK_REMAINING, - PROVISIONING_CHECK_ATTEMPT, PROVISIONING_WAIT_FOR_NETWORK, - PROVISIONING_STARTING, PROVISIONING_STARTED, PROVISIONING_KEYS_REMAINING, - PROVISIONING_WAIT_ATTEMPT, PROVISIONING_STATE_MAX, }; static volatile uint8_t provisioningState = PROVISIONING_OFF; const char *const provisioningStateName[] = {"PROVISIONING_OFF", - "PROVISIONING_WAIT_RTC", - "PROVISIONING_NOT_STARTED", "PROVISIONING_CHECK_REMAINING", - "PROVISIONING_CHECK_ATTEMPT", "PROVISIONING_WAIT_FOR_NETWORK", - "PROVISIONING_STARTING", "PROVISIONING_STARTED", - "PROVISIONING_KEYS_REMAINING", - "PROVISIONING_WAIT_ATTEMPT"}; + "PROVISIONING_KEYS_REMAINING"}; const int provisioningStateNameEntries = sizeof(provisioningStateName) / sizeof(provisioningStateName[0]); @@ -1251,7 +1026,7 @@ void provisioningVerifyTables() void provisioningSetState(uint8_t newState) { - if (settings.debugPpCertificate || PERIODIC_DISPLAY(PD_PROVISIONING_STATE)) + if (settings.debugPpCertificate) { if (provisioningState == newState) systemPrint("Provisioning: *"); @@ -1259,9 +1034,8 @@ void provisioningSetState(uint8_t newState) systemPrintf("Provisioning: %s --> ", provisioningStateName[provisioningState]); } provisioningState = newState; - if (settings.debugPpCertificate || PERIODIC_DISPLAY(PD_PROVISIONING_STATE)) + if (settings.debugPpCertificate) { - PERIODIC_CLEAR(PD_PROVISIONING_STATE); if (newState >= PROVISIONING_STATE_MAX) { systemPrintf("Unknown provisioning state: %d\r\n", newState); @@ -1272,98 +1046,156 @@ void provisioningSetState(uint8_t newState) } } -unsigned long provisioningStartTime_millis; -const unsigned long provisioningTimeout_ms = 120000; - -// Return true if we are in states that require network access -bool provisioningNeedsNetwork() +// Determine if provisioning is enabled +bool provisioningEnabled(const char ** line) { - if (provisioningState >= PROVISIONING_WAIT_FOR_NETWORK && provisioningState <= PROVISIONING_STARTED) - return true; - return false; + bool enabled; + + do + { + // Provisioning requires PointPerfect corrections + enabled = settings.enablePointPerfectCorrections; + if (enabled == false) + { + *line = ", PointPerfect corrections disabled!"; + break; + } + + // Keep running until provisioning attempt is complete + if (provisioningRunning) + break; + + // Determine if provisioning should start + provisioningRunning = settings.requestKeyUpdate // Manual update + || (provisioningStartTime_millis == 0) // Update keys at boot + || (settings.autoKeyRenewal && // Auto renewal time (24 hours expired) + ((millis() - provisioningStartTime_millis) > MILLISECONDS_IN_A_DAY)); + + // Determine if key provisioning is enabled + enabled = provisioningRunning; + if (settings.autoKeyRenewal) + *line = ", Key not requested and auto key renewal running later!"; + else + *line = ", Key not requested and auto key renewal is disabled!"; + } while (0); + return enabled; } -void provisioningUpdate() +// Determine if the keys are needed +bool provisioningKeysNeeded() { - DMW_st(provisioningSetState, provisioningState); + bool keysNeeded; - switch (provisioningState) + do { - default: - case PROVISIONING_OFF: { - provisioningStartTime_millis = millis(); // Record the start time so we can timeout - provisioningSetState(PROVISIONING_WAIT_RTC); - } - break; - case PROVISIONING_WAIT_RTC: { - // If RTC is not online after provisioningTimeout_ms, try to provision anyway - if ((online.rtc) || (millis() > (provisioningStartTime_millis + provisioningTimeout_ms)) || - (settings.requestKeyUpdate)) - provisioningSetState(PROVISIONING_NOT_STARTED); - } - break; - case PROVISIONING_NOT_STARTED: { - if (settings.enablePointPerfectCorrections && (settings.autoKeyRenewal || settings.requestKeyUpdate)) - provisioningSetState(PROVISIONING_CHECK_REMAINING); - } - break; - case PROVISIONING_CHECK_REMAINING: { + keysNeeded = true; + // If we don't have certs or keys, begin zero touch provisioning if (!checkCertificates() || strlen(settings.pointPerfectCurrentKey) == 0 || strlen(settings.pointPerfectNextKey) == 0) { if (settings.debugPpCertificate) - systemPrintln("Invalid certificates or keys. Starting provisioning"); - provisioningSetState(PROVISIONING_WAIT_FOR_NETWORK); + systemPrintln("Invalid certificates or keys."); + break; } + // If requestKeyUpdate is true, begin provisioning - else if (settings.requestKeyUpdate) + if (settings.requestKeyUpdate) { if (settings.debugPpCertificate) - systemPrintln("requestKeyUpdate is true. Starting provisioning"); - provisioningSetState(PROVISIONING_WAIT_FOR_NETWORK); + systemPrintln("requestKeyUpdate is true."); + break; } - // If RTC is not online, we have to skip PROVISIONING_CHECK_ATTEMPT - else if (!online.rtc) + + // Determine if RTC is online + if (!online.rtc) { if (settings.debugPpCertificate) - systemPrintln("No RTC. Starting provisioning"); - provisioningSetState(PROVISIONING_WAIT_FOR_NETWORK); + systemPrintln("No RTC."); + break; } - else + + // RTC is online. Determine days until next key expires + int daysRemaining = + daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1); + + if (settings.debugPpCertificate) + systemPrintf("Days until keys expire: %d\r\n", daysRemaining); + + // PointPerfect returns keys that expire at midnight so the primary key + // is still available with 0 days left, and a Next Key that has 28 days left + // If there are 28 days remaining, PointPerfect won't have new keys. + if (daysRemaining < 28) { - // RTC is online. Determine days until next key expires - int daysRemaining = - daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1); + // When did we last try to get keys? Attempt every 24 hours - or always for DEVELOPER + // if (rtc.getEpoch() - settings.lastKeyAttempt > ( ENABLE_DEVELOPER ? 0 : SECONDS_IN_A_DAY)) + // When did we last try to get keys? Attempt every 24 hours + if (rtc.getEpoch() - settings.lastKeyAttempt > SECONDS_IN_A_DAY) + { + settings.lastKeyAttempt = rtc.getEpoch(); // Mark it + recordSystemSettings(); // Record these settings to unit + break; + } if (settings.debugPpCertificate) - systemPrintf("Days until keys expire: %d\r\n", daysRemaining); - - // PointPerfect returns keys that expire at midnight so the primary key - // is still available with 0 days left, and a Next Key that has 28 days left - // If there are 28 days remaining, PointPerfect won't have new keys. - if (daysRemaining >= 28) - provisioningSetState(PROVISIONING_KEYS_REMAINING); // Don't need new keys - else - provisioningSetState(PROVISIONING_CHECK_ATTEMPT); // Do need new keys + systemPrintln("Already tried to obtain keys for today"); } + + // Don't need new keys + keysNeeded = false; + } while (0); + if (keysNeeded && settings.debugPpCertificate) + systemPrintln(" Starting provisioning"); + return keysNeeded; +} + +void provisioningStop(const char * file, uint32_t line) +{ + // Done with this request attempt + settings.requestKeyUpdate = false; + provisioningRunning = false; + + // Record the time so we can restart after 24 hours + provisioningStartTime_millis = millis(); + + // Done with the network + networkConsumerRemove(NETCONSUMER_PPL_KEY_UPDATE, NETWORK_ANY, file, line); + provisioningSetState(PROVISIONING_OFF); +} + +void provisioningUpdate() +{ + bool enabled; + const char * line = ""; + const unsigned long provisioningTimeout_ms = 2 * MILLISECONDS_IN_A_MINUTE; + static bool rtcOnline; + + // Determine if key provisioning is enabled + DMW_st(provisioningSetState, provisioningState); + enabled = provisioningEnabled(&line); + + // Determine if the RTC was properly initialized + if (rtcOnline == false) + rtcOnline = online.rtc || settings.requestKeyUpdate + || (millis() > provisioningTimeout_ms); + + switch (provisioningState) + { + default: + case PROVISIONING_OFF: { + // If RTC is not online after provisioningTimeout_ms, try to provision anyway + if (enabled && rtcOnline) + provisioningSetState(PROVISIONING_CHECK_REMAINING); } break; - case PROVISIONING_CHECK_ATTEMPT: { - // When did we last try to get keys? Attempt every 24 hours - or always for DEVELOPER - // if (rtc.getEpoch() - settings.lastKeyAttempt > ( ENABLE_DEVELOPER ? 0 : (60 * 60 * 24))) - // When did we last try to get keys? Attempt every 24 hours - if (rtc.getEpoch() - settings.lastKeyAttempt > (60 * 60 * 24)) - { - settings.lastKeyAttempt = rtc.getEpoch(); // Mark it - recordSystemSettings(); // Record these settings to unit - provisioningSetState(PROVISIONING_WAIT_FOR_NETWORK); - } + case PROVISIONING_CHECK_REMAINING: { + if (provisioningKeysNeeded() == false) + provisioningSetState(PROVISIONING_KEYS_REMAINING); else { - if (settings.debugPpCertificate) - systemPrintln("Already tried to obtain keys for today"); - provisioningSetState(PROVISIONING_KEYS_REMAINING); + // Request the network for PointPerfect key provisioning + networkConsumerAdd(NETCONSUMER_PPL_KEY_UPDATE, NETWORK_ANY, __FILE__, __LINE__); + provisioningSetState(PROVISIONING_WAIT_FOR_NETWORK); } } break; @@ -1371,41 +1203,32 @@ void provisioningUpdate() // Wait for connection to the network case PROVISIONING_WAIT_FOR_NETWORK: { // Stop waiting if PointPerfect has been disabled - if (settings.enablePointPerfectCorrections == false) - { - provisioningSetState(PROVISIONING_NOT_STARTED); - } + if (enabled == false) + provisioningStop(__FILE__, __LINE__); + // Wait until the network is available -#ifdef COMPILE_NETWORK - else if (networkHasInternet()) + else if (networkConsumerIsConnected(NETCONSUMER_PPL_KEY_UPDATE)) { if (settings.debugPpCertificate) systemPrintln("PointPerfect key update connected to network"); // Go get latest keys - provisioningSetState(PROVISIONING_STARTING); + ztpResponse = ZTP_NOT_STARTED; // HTTP_Client will update this + httpClientModeNeeded = true; // This will start the HTTP_Client + provisioningStartTime_millis = millis(); // Record the start time so we can timeout + paintGettingKeys(); + networkUserAdd(NETCONSUMER_PPL_KEY_UPDATE, __FILE__, __LINE__); + provisioningSetState(PROVISIONING_STARTED); } -#endif // COMPILE_NETWORK - - // TODO If we just booted, show keys remaining regardless of provisioning state machine - // provisioningSetState(PROVISIONING_KEYS_REMAINING); } - break; - case PROVISIONING_STARTING: { - ztpResponse = ZTP_NOT_STARTED; // HTTP_Client will update this - httpClientModeNeeded = true; // This will start the HTTP_Client - provisioningStartTime_millis = millis(); // Record the start time so we can timeout - paintGettingKeys(); - provisioningSetState(PROVISIONING_STARTED); - } case PROVISIONING_STARTED: { // Only leave this state if we timeout or ZTP is complete if (millis() > (provisioningStartTime_millis + provisioningTimeout_ms)) { httpClientModeNeeded = false; // Tell HTTP_Client to give up. (But it probably already has...) - paintKeyUpdateFail(5000); + paintKeyUpdateFail(5 * MILLISECONDS_IN_A_SECOND); provisioningSetState(PROVISIONING_KEYS_REMAINING); } else if (ztpResponse == ZTP_SUCCESS) @@ -1443,7 +1266,7 @@ void provisioningUpdate() landingPageUrl, hardwareID); httpClientModeNeeded = false; // Tell HTTP_Client to give up. - displayAccountExpired(5000); + displayAccountExpired(5 * MILLISECONDS_IN_A_SECOND); provisioningSetState(PROVISIONING_KEYS_REMAINING); } @@ -1475,7 +1298,7 @@ void provisioningUpdate() landingPageUrl, hardwareID); httpClientModeNeeded = false; // Tell HTTP_Client to give up. - displayNotListed(5000); + displayNotListed(5 * MILLISECONDS_IN_A_SECOND); provisioningSetState(PROVISIONING_KEYS_REMAINING); } @@ -1491,7 +1314,7 @@ void provisioningUpdate() hardwareID); httpClientModeNeeded = false; // Tell HTTP_Client to give up. - displayAlreadyRegistered(5000); + displayAlreadyRegistered(5 * MILLISECONDS_IN_A_SECOND); provisioningSetState(PROVISIONING_KEYS_REMAINING); } @@ -1515,7 +1338,7 @@ void provisioningUpdate() systemPrintf("Days until PointPerfect keys expire: %d\r\n", daysRemaining); if (daysRemaining >= 0) { - paintKeyDaysRemaining(daysRemaining, 2000); + paintKeyDaysRemaining(daysRemaining, 2 * MILLISECONDS_IN_A_SECOND); } else { @@ -1530,30 +1353,19 @@ void provisioningUpdate() gnss->applyPointPerfectKeys(); // Send current keys, if available, to GNSS - settings.requestKeyUpdate = false; // However we got here, clear requestKeyUpdate recordSystemSettings(); // Record these settings to unit - provisioningStartTime_millis = millis(); // Record the time so we can restart after 24 hours - provisioningSetState(PROVISIONING_WAIT_ATTEMPT); - } - break; - case PROVISIONING_WAIT_ATTEMPT: { - if (settings.requestKeyUpdate) // requestKeyUpdate can be set via the menu, mode button or web config - provisioningSetState(PROVISIONING_CHECK_REMAINING); - else if (!settings.enablePointPerfectCorrections || !settings.autoKeyRenewal) - provisioningSetState(PROVISIONING_OFF); - // When did we last try to get keys? Attempt every 24 hours - or every 15 mins for DEVELOPER - // else if (millis() > (provisioningStartTime_millis + ( ENABLE_DEVELOPER ? (1000 * 60 * 15) : (1000 * 60 * 60 * - // 24)))) - // When did we last try to get keys? Attempt every 24 hours - else if (millis() > - (provisioningStartTime_millis + (1000 * 60 * 60 * 24))) // Don't use settings.lastKeyAttempt (#419) - provisioningSetState(PROVISIONING_CHECK_REMAINING); + // Done with the network + provisioningStop(__FILE__, __LINE__); } break; } // Periodically display the provisioning state if (PERIODIC_DISPLAY(PD_PROVISIONING_STATE)) - provisioningSetState(provisioningState); + { + systemPrintf("Provisioning state: %s%s\r\n", + provisioningStateName[provisioningState], line); + PERIODIC_CLEAR(PD_PROVISIONING_STATE); + } } diff --git a/Firmware/RTK_Everywhere/menuSystem.ino b/Firmware/RTK_Everywhere/menuSystem.ino index 870bb033..eec45391 100644 --- a/Firmware/RTK_Everywhere/menuSystem.ino +++ b/Firmware/RTK_Everywhere/menuSystem.ino @@ -188,15 +188,7 @@ void menuSystem() systemPrintf("%d (%d days %d:%02d)\r\n", settings.rebootMinutes, days, hours, minutes); } - systemPrint("b) Set Bluetooth Mode: "); - if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP_AND_BLE) - systemPrintln("Dual"); - else if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP) - systemPrintln("Classic"); - else if (bluetoothUserChoice == BLUETOOTH_RADIO_BLE) - systemPrintln("BLE"); - else - systemPrintln("Off"); + mmDisplayBluetoothRadioMenu('b', bluetoothUserChoice); if (present.fuelgauge_max17048 || present.fuelgauge_bq40z50 || present.charger_mp2762a) { @@ -286,17 +278,7 @@ void menuSystem() } } else if (incoming == 'b') - { - // Change Bluetooth protocol - if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP_AND_BLE) - bluetoothUserChoice = BLUETOOTH_RADIO_SPP; - else if (bluetoothUserChoice == BLUETOOTH_RADIO_SPP) - bluetoothUserChoice = BLUETOOTH_RADIO_BLE; - else if (bluetoothUserChoice == BLUETOOTH_RADIO_BLE) - bluetoothUserChoice = BLUETOOTH_RADIO_OFF; - else if (bluetoothUserChoice == BLUETOOTH_RADIO_OFF) - bluetoothUserChoice = BLUETOOTH_RADIO_SPP_AND_BLE; - } + bluetoothUserChoice = mmChangeBluetoothProtocol(bluetoothUserChoice); else if ((incoming == 'c') && (present.fuelgauge_max17048 || present.fuelgauge_bq40z50 || present.charger_mp2762a)) { @@ -436,12 +418,7 @@ void menuSystem() } // Restart Bluetooth radio if settings have changed - if (bluetoothUserChoice != settings.bluetoothRadioType) - { - bluetoothStop(); - settings.bluetoothRadioType = bluetoothUserChoice; - bluetoothStart(); - } + mmSetBluetoothProtocol(bluetoothUserChoice); clearBuffer(); // Empty buffer of any newline chars } @@ -793,6 +770,8 @@ void menuDebugSoftware() systemPrintf("3) WiFi Connect Timeout (ms): %d\r\n", settings.wifiConnectTimeoutMs); + systemPrintf("4) Debug malloc/free and new/delete: %s\r\n", settings.debugMalloc ? "Enabled" : "Disabled"); + // Ring buffer systemPrint("10) Print ring buffer offsets: "); systemPrintf("%s\r\n", settings.enablePrintRingBufferOffsets ? "Enabled" : "Disabled"); @@ -856,6 +835,8 @@ void menuDebugSoftware() { getNewSetting("Enter WiFi connect timeout in ms", 1000, 120000, &settings.wifiConnectTimeoutMs); } + else if (incoming == 4) + settings.debugMalloc ^= 1; else if (incoming == 10) settings.enablePrintRingBufferOffsets ^= 1; else if (incoming == 11) @@ -1248,6 +1229,9 @@ void menuPeriodicPrint() systemPrint("58) UDP server broadcast data: "); systemPrintf("%s\r\n", PERIODIC_SETTING(PD_UDP_SERVER_BROADCAST_DATA) ? "Enabled" : "Disabled"); + systemPrint("59) WebServer state: "); + systemPrintf("%s\r\n", PERIODIC_SETTING(PD_WEB_SERVER_STATE) ? "Enabled" : "Disabled"); + systemPrintln("------- Tasks ------"); systemPrint("70) btReadTask state: "); systemPrintf("%s\r\n", PERIODIC_SETTING(PD_TASK_BLUETOOTH_READ) ? "Enabled" : "Disabled"); @@ -1353,6 +1337,8 @@ void menuPeriodicPrint() PERIODIC_TOGGLE(PD_UDP_SERVER_DATA); else if (incoming == 58) PERIODIC_TOGGLE(PD_UDP_SERVER_BROADCAST_DATA); + else if (incoming == 59) + PERIODIC_TOGGLE(PD_WEB_SERVER_STATE); else if (incoming == 70) PERIODIC_TOGGLE(PD_TASK_BLUETOOTH_READ); diff --git a/Firmware/RTK_Everywhere/settings.h b/Firmware/RTK_Everywhere/settings.h index ed6ace87..fd93b965 100644 --- a/Firmware/RTK_Everywhere/settings.h +++ b/Firmware/RTK_Everywhere/settings.h @@ -14,34 +14,40 @@ // https://lucid.app/lucidchart/53519501-9fa5-4352-aa40-673f88ca0c9b/edit?invitationId=inv_ebd4b988-513d-4169-93fd-c291851108f8 typedef enum { - STATE_ROVER_NOT_STARTED = 0, - STATE_ROVER_NO_FIX, - STATE_ROVER_FIX, - STATE_ROVER_RTK_FLOAT, - STATE_ROVER_RTK_FIX, - STATE_BASE_CASTER_NOT_STARTED, //Set override flag - STATE_BASE_NOT_STARTED, - STATE_BASE_TEMP_SETTLE, // User has indicated base, but current pos accuracy is too low - STATE_BASE_TEMP_SURVEY_STARTED, - STATE_BASE_TEMP_TRANSMITTING, - STATE_BASE_FIXED_NOT_STARTED, - STATE_BASE_FIXED_TRANSMITTING, - - STATE_DISPLAY_SETUP, - STATE_WEB_CONFIG_NOT_STARTED, - STATE_WEB_CONFIG_WAIT_FOR_NETWORK, - STATE_WEB_CONFIG, - STATE_TEST, - STATE_TESTING, - STATE_PROFILE, - STATE_KEYS_REQUESTED, - STATE_ESPNOW_PAIRING_NOT_STARTED, - STATE_ESPNOW_PAIRING, - STATE_NTPSERVER_NOT_STARTED, - STATE_NTPSERVER_NO_SYNC, - STATE_NTPSERVER_SYNC, - STATE_SHUTDOWN, - STATE_NOT_SET, // Must be last on list + STATE_ROVER_NOT_STARTED = 0, // 0 + STATE_ROVER_NO_FIX, // 1 + STATE_ROVER_FIX, // 2 + STATE_ROVER_RTK_FLOAT, // 3 + STATE_ROVER_RTK_FIX, // 4 + + STATE_BASE_CASTER_NOT_STARTED, // 5, Set override flag + STATE_BASE_NOT_STARTED, // 6 + STATE_BASE_TEMP_SETTLE, // 7, User has indicated base, but current pos accuracy is too low + STATE_BASE_TEMP_SURVEY_STARTED, // 8 + STATE_BASE_TEMP_TRANSMITTING, // 9 + STATE_BASE_FIXED_NOT_STARTED, // 10 + STATE_BASE_FIXED_TRANSMITTING, // 11 + + STATE_DISPLAY_SETUP, // 12 + STATE_WEB_CONFIG_NOT_STARTED, // 13 + STATE_WEB_CONFIG_WAIT_FOR_NETWORK, // 14 + STATE_WEB_CONFIG, // 15 + STATE_TEST, // 16 + STATE_TESTING, // 17 + STATE_PROFILE, // 18 + + STATE_KEYS_REQUESTED, // 19 + + STATE_ESPNOW_PAIRING_NOT_STARTED, // 20 + STATE_ESPNOW_PAIRING, // 21 + + STATE_NTPSERVER_NOT_STARTED, // 22 + STATE_NTPSERVER_NO_SYNC, // 23 + STATE_NTPSERVER_SYNC, // 24 + + STATE_SHUTDOWN, // 25 + + STATE_NOT_SET, // 26, Must be last on list } SystemState; volatile SystemState systemState = STATE_NOT_SET; SystemState lastSystemState = STATE_NOT_SET; @@ -49,19 +55,31 @@ SystemState requestedSystemState = STATE_NOT_SET; bool newSystemStateRequested = false; // Base modes set with RTK_MODE -#define RTK_MODE_BASE_FIXED 0x0001 -#define RTK_MODE_BASE_SURVEY_IN 0x0002 -#define RTK_MODE_NTP 0x0004 -#define RTK_MODE_ROVER 0x0008 -#define RTK_MODE_TESTING 0x0010 -#define RTK_MODE_WEB_CONFIG 0x0020 +#define RTK_MODE_BASE_FIXED 0x0001 // 1 << 0 +#define RTK_MODE_BASE_SURVEY_IN 0x0002 // 1 << 1 +#define RTK_MODE_NTP 0x0004 // 1 << 2 +#define RTK_MODE_ROVER 0x0008 // 1 << 3 +#define RTK_MODE_TESTING 0x0010 // 1 << 4 +#define RTK_MODE_WEB_CONFIG 0x0020 // 1 << 5 +#define RTK_MODE_MAX 6 typedef uint8_t RtkMode_t; +const char * const rtkModeName[] = +{ + "RTK_MODE_BASE_FIXED", + "RTK_MODE_BASE_SURVEY_IN", + "RTK_MODE_NTP", + "RTK_MODE_ROVER", + "RTK_MODE_TESTING", + "RTK_MODE_WEB_CONFIG", +}; +const uint8_t rtkModeNameEntries = sizeof(rtkModeName) / sizeof(rtkModeName[0]); + #define RTK_MODE(mode) rtkMode = mode; #define EQ_RTK_MODE(mode) (rtkMode && (rtkMode == (mode & rtkMode))) -#define NEQ_RTK_MODE(mode) (rtkMode && (rtkMode != (mode & rtkMode))) +#define NEQ_RTK_MODE(mode) ((rtkMode == 0) || ((mode & rtkMode) == 0)) //Used as part of device ID and whitelists. Do not reorder. typedef enum @@ -297,8 +315,8 @@ enum PeriodDisplayValues PD_CELLULAR_STATE, // 28 PD_WIFI_STATE, // 29 - PD_GNSS_DATA_RX, // 30 - PD_GNSS_DATA_TX, // 31 + PD_GNSS_DATA_RX, // 30 + PD_GNSS_DATA_TX, // 31 PD_MQTT_CLIENT_DATA, // 32 PD_MQTT_CLIENT_STATE, // 33 @@ -312,6 +330,8 @@ enum PeriodDisplayValues PD_CORRECTION_SOURCE, // 37 PD_GNSS_DATA_RX_BYTE_COUNT, // 38 + + PD_WEB_SERVER_STATE, // 39 // Add new values before this line }; @@ -375,9 +395,11 @@ typedef enum BLUETOOTH_RADIO_OFF, } BluetoothRadioType_e; +#define SSID_LENGTH 50 + typedef struct WiFiNetwork { - char ssid[50]; + char ssid[SSID_LENGTH]; char password[50]; } WiFiNetwork; @@ -552,33 +574,32 @@ const int numRegionalAreas = sizeof(Regional_Information_Table) / sizeof(Regiona #define NTRIP_SERVER_STRING_SIZE 50 -//Bitfield for describing the type of network the consumer can use -enum -{ - NETIF_NONE = 0, // No consumers - NETIF_WIFI_STA, // The consumer can use STA - NETIF_WIFI_AP, // The consumer can use AP - NETIF_CELLULAR, // The consumer can use Cellular - NETIF_ETHERNET, // The consumer can use Ethernet - NETIF_UNKNOWN -}; - -#define NETWORK_EWC ((1 << NETIF_ETHERNET) | (1 << NETIF_WIFI_STA) | (1 << NETIF_CELLULAR)) - // Bitfield for describing the network consumers enum { - NETCONSUMER_NTRIP_CLIENT = 0, + NETCONSUMER_HTTP_CLIENT = 0, + NETCONSUMER_NTP_SERVER, + NETCONSUMER_NTRIP_CLIENT, NETCONSUMER_NTRIP_SERVER, + NETCONSUMER_NTRIP_SERVER_0 = NETCONSUMER_NTRIP_SERVER, + NETCONSUMER_NTRIP_SERVER_1, + NETCONSUMER_NTRIP_SERVER_2, + NETCONSUMER_NTRIP_SERVER_3, + NETCONSUMER_OTA_CLIENT, + NETCONSUMER_PPL_KEY_UPDATE, + NETCONSUMER_PPL_MQTT_CLIENT, NETCONSUMER_TCP_CLIENT, NETCONSUMER_TCP_SERVER, NETCONSUMER_UDP_SERVER, - NETCONSUMER_PPL_KEY_UPDATE, - NETCONSUMER_PPL_MQTT_CLIENT, - NETCONSUMER_OTA_CLIENT, NETCONSUMER_WEB_CONFIG, + // Add new consumers just before this line + // Also add them to the networkConsumerTable + NETCONSUMER_MAX }; +typedef uint8_t NETCONSUMER_t; +typedef uint16_t NETCONSUMER_MASK_t; + // This is all the settings that can be set on RTK Product. It's recorded to NVM and the config file. // Avoid reordering. The order of these variables is mimicked in NVM/record/parse/create/update/get struct Settings @@ -658,7 +679,7 @@ struct Settings // Signatures to indicate how the GNSS is configured (Once, Base, Rover, etc.) // Bit 0 indicates if the GNSS has been configured previously. // Bits 1 onwards record the state of critical settings. E.g. settings.enablePointPerfectCorrections - // Configuration is reapplied if any of those critical settings have changed + // Configuration is reapplied if any of those critical settings have changed bool gnssConfiguredOnce = false; bool gnssConfiguredBase = false; bool gnssConfiguredRover = false; @@ -1004,13 +1025,15 @@ struct Settings int lg290pMessageRatesRTCMRover[MAX_LG290P_RTCM_MSG] = { 254}; // Mark first record with key so defaults will be applied. Int value for each supported message - Report // rates for RTCM Base. Default to Quectel recommended rates. - int lg290pMessageRatesPQTM[MAX_LG290P_PQTM_MSG] = {254}; // Mark first record with key so defaults will be applied. + int lg290pMessageRatesPQTM[MAX_LG290P_PQTM_MSG] = {254}; // Mark first record with key so defaults will be applied. #endif // COMPILE_LG290P bool debugSettings = false; bool enableNtripCaster = false; //When true, respond as a faux NTRIP Caster to incoming TCP connections bool baseCasterOverride = false; //When true, user has put device into 'BaseCast' mode. Change settings, but don't save to NVM. + bool debugMalloc = false; + // Add new settings to appropriate group above or create new group // Then also add to the same group in rtkSettingsEntries below } settings; @@ -1370,6 +1393,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.bluetoothInterruptsCore, "bluetoothInterruptsCore", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.btReadTaskCore, "btReadTaskCore", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.btReadTaskPriority, "btReadTaskPriority", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugMalloc, "debugMalloc", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enableHeapReport, "enableHeapReport", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enablePrintIdleTime, "enablePrintIdleTime", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enablePsram, "enablePsram", }, @@ -1795,15 +1819,18 @@ typedef uint32_t NetMask_t; // One bit for each network interface typedef int8_t NetPriority_t; // Index into networkPriorityTable // Value 0 (highest) - 255 (lowest) priority -// Types of networks, must be in same order as networkInterfaceTable +// Types of networks, these values must be in the same order as the +// entries in networkInterfaceTable since they are used as indexes into +// that table! enum NetworkTypes { NETWORK_NONE = -1, // The values below must start at zero and be sequential - NETWORK_ETHERNET = 0, - NETWORK_WIFI = 1, - NETWORK_CELLULAR = 2, - // Add new networks here - NETWORK_MAX + NETWORK_ETHERNET, // 0 + NETWORK_WIFI_STATION, // 1 + NETWORK_CELLULAR, // 2 + // Add new networks above this line in default priority order + NETWORK_ANY, // 3 + NETWORK_MAX = NETWORK_ANY, }; #ifdef COMPILE_NETWORK @@ -1822,48 +1849,52 @@ typedef const struct _NETWORK_POLL_SEQUENCE const char * description; // Description of operation } NETWORK_POLL_SEQUENCE; +// Sequence table declarations +extern NETWORK_POLL_SEQUENCE wifiStartSequence[]; +extern NETWORK_POLL_SEQUENCE wifiStopSequence[]; +extern NETWORK_POLL_SEQUENCE laraBootSequence[]; +extern NETWORK_POLL_SEQUENCE laraOffSequence[]; +extern NETWORK_POLL_SEQUENCE laraOnSequence[]; + // networkInterfaceTable entry typedef struct _NETWORK_TABLE_ENTRY { NetworkInterface * netif; // Network interface object address - const char * name; // Name of the network interface - bool * present; // Address of present bool or nullptr if always available + bool mDNS; // Set true to use mDNS service + NetIndex_t index; // Table index, also default priority uint8_t pdState; // Periodic display state value NETWORK_POLL_SEQUENCE * boot; // Boot sequence, may be nullptr NETWORK_POLL_SEQUENCE * start; // Start sequence (Off --> On), may be nullptr NETWORK_POLL_SEQUENCE * stop; // Stop routine (On --> Off), may be nullptr + const char * name; // Name of the network interface + bool * present; // Address of present bool or nullptr if always available } NETWORK_TABLE_ENTRY; -// Sequence table declarations -extern NETWORK_POLL_SEQUENCE wifiStartSequence[]; -extern NETWORK_POLL_SEQUENCE wifiStopSequence[]; -extern NETWORK_POLL_SEQUENCE laraBootSequence[]; -extern NETWORK_POLL_SEQUENCE laraOffSequence[]; -extern NETWORK_POLL_SEQUENCE laraOnSequence[]; - -// List of networks -// Only one network is turned on at a time. The start routine is called as the priority -// drops to that level. The stop routine is called as the priority rises -// above that level. The priority will continue to fall or rise until a -// network is found that is online. +// List of networks in default priority order! These entries must match +// the index values in the NetworkTypes enumeration! +// +// Only one network is turned on at a time. The start routine is called +// as the priority drops to that level. The stop routine is called as the +// priority rises above that level. The priority will continue to fall or +// rise until a network is found that is online. const NETWORK_TABLE_ENTRY networkInterfaceTable[] = -{ // Interface Name Present Periodic State Boot Sequence Start Sequence Stop Sequence +{ // Interface mDNS Index Periodic State Boot Sequence Start Sequence Stop Sequence Name Present #ifdef COMPILE_ETHERNET - {Ð, "Ethernet", &present.ethernet_ws5500, PD_ETHERNET_STATE, nullptr, nullptr, nullptr}, + {Ð, true, NETWORK_ETHERNET, PD_ETHERNET_STATE, nullptr, nullptr, nullptr, "Ethernet", &present.ethernet_ws5500}, #else - {nullptr, "Ethernet-NotCompiled", nullptr, PD_ETHERNET_STATE, nullptr, nullptr, nullptr}, + {nullptr, false, NETWORK_ETHERNET, PD_ETHERNET_STATE, nullptr, nullptr, nullptr, "Ethernet-NotCompiled", nullptr}, #endif // COMPILE_ETHERNET #ifdef COMPILE_WIFI - {&WiFi.STA, "WiFi", nullptr, PD_WIFI_STATE, nullptr, wifiStartSequence, wifiStopSequence}, + {&WiFi.STA, true, NETWORK_WIFI_STATION, PD_WIFI_STATE, nullptr, wifiStartSequence, wifiStopSequence, "WiFi", nullptr}, #else - {nullptr, "WiFi-NotCompiled", nullptr, PD_WIFI_STATE, nullptr, nullptr, nullptr}, + {nullptr, false, NETWORK_WIFI_STATION, PD_WIFI_STATE, nullptr, nullptr, nullptr, "WiFi-NotCompiled", nullptr}, #endif // COMPILE_WIFI #ifdef COMPILE_CELLULAR - {&PPP, "Cellular", &present.cellular_lara, PD_CELLULAR_STATE, laraBootSequence, laraOnSequence, laraOffSequence}, + {&PPP, false, NETWORK_CELLULAR, PD_CELLULAR_STATE, laraBootSequence, laraOnSequence, laraOffSequence, "Cellular", &present.cellular_lara}, #else - {nullptr, "Cellular-NotCompiled", nullptr, PD_CELLULAR_STATE, nullptr, nullptr, nullptr}, + {nullptr, false, NETWORK_CELLULAR, PD_CELLULAR_STATE, nullptr, nullptr, nullptr, "Cellular-NotCompiled", nullptr, }, #endif // COMPILE_CELLULAR }; const int networkInterfaceTableEntries = sizeof(networkInterfaceTable) / sizeof(networkInterfaceTable[0]); @@ -1924,30 +1955,25 @@ o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU rqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- )====="; - -#ifdef COMPILE_WIFI +#endif // COMPILE_NETWORK //**************************************** // WiFi class //**************************************** -// Handle the WiFi event -// Inputs: -// event: Arduino ESP32 event number found on -// https://github.com/espressif/arduino-esp32 -// in libraries/Network/src/NetworkEvents.h -// info: Additional data about the event -void wifiEventHandler(arduino_event_id_t event, arduino_event_info_t info); +typedef uint8_t WIFI_CHANNEL_t; + +#ifdef COMPILE_NETWORK +#ifdef COMPILE_WIFI typedef uint32_t WIFI_ACTION_t; -typedef uint8_t WIFI_CHANNEL_t; // Class to simplify WiFi handling class RTK_WIFI { private: - WIFI_CHANNEL_t _apChannel; // Channel required for soft AP, zero (0) use _channel + WIFI_CHANNEL_t _apChannel; // Channel required for soft AP, zero (0) use wifiChannel int16_t _apCount; // The number or remote APs detected in the WiFi network IPAddress _apDnsAddress; // DNS IP address to use while translating names into IP addresses IPAddress _apFirstDhcpAddress; // First IP address to use for DHCP @@ -1955,11 +1981,8 @@ class RTK_WIFI IPAddress _apIpAddress; // IP address of the soft AP uint8_t _apMacAddress[6]; // MAC address of the soft AP IPAddress _apSubnetMask; // Subnet mask for soft AP - WIFI_CHANNEL_t _channel; // Current WiFi channel number - WIFI_CHANNEL_t _espNowChannel; // Channel required for ESPNow, zero (0) use _channel - bool _espNowRunning; // ESPNow started or running + WIFI_CHANNEL_t _espNowChannel; // Channel required for ESPNow, zero (0) use wifiChannel volatile bool _scanRunning; // Scan running - bool _softApRunning; // Soft AP is starting or running int _staAuthType; // Authorization type for the remote AP bool _staConnected; // True when station is connected bool _staHasIp; // True when station has IP address @@ -1969,9 +1992,7 @@ class RTK_WIFI const char * _staRemoteApSsid; // SSID of remote AP const char * _staRemoteApPassword; // Password of remote AP volatile WIFI_ACTION_t _started; // Components that are started and running - WIFI_CHANNEL_t _stationChannel; // Channel required for station, zero (0) use _channel - bool _stationRunning; // True while station is starting or running - uint32_t _timer; // Reconnection timer + WIFI_CHANNEL_t _stationChannel; // Channel required for station, zero (0) use wifiChannel bool _usingDefaultChannel; // Using default WiFi channel bool _verbose; // True causes more debug output to be displayed @@ -2098,14 +2119,6 @@ class RTK_WIFI // Returns the channel number of the AP WIFI_CHANNEL_t stationSelectAP(uint8_t apCount, bool list); - // Stop and start WiFi components - // Inputs: - // stopping: WiFi components that need to be stopped - // starting: WiFi components that neet to be started - // Outputs: - // Returns true if the modes were successfully configured - bool stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting); - // Handle the WiFi event // Inputs: // event: Arduino ESP32 event number found on @@ -2115,12 +2128,20 @@ class RTK_WIFI void wifiEvent(arduino_event_id_t event, arduino_event_info_t info); public: + char * _apSsid; // SSID for the soft AP // Constructor // Inputs: // verbose: Set to true to display additional WiFi debug data RTK_WIFI(bool verbose = false); + // Clear some of the started components + // Inputs: + // components: Bitmask of components to clear + // Outputs: + // Returns the bitmask of started components + WIFI_ACTION_t clearStarted(WIFI_ACTION_t components); + // Attempts a connection to all provided SSIDs // Inputs: // timeout: Number of milliseconds to wait for the connection @@ -2137,20 +2158,21 @@ class RTK_WIFI // enableESPNow: Enable ESP-NOW mode // enableSoftAP: Enable soft AP mode // enableStataion: Enable station mode + // fileName: Name of file calling the enable routine + // lineNumber: Line number in the file calling the enable routine // Outputs: // Returns true if the modes were successfully configured - bool enable(bool enableESPNow, bool enableSoftAP, bool enableStation); + bool enable(bool enableESPNow, + bool enableSoftAP, + bool enableStation, + const char * fileName, + int lineNumber); // Get the ESP-NOW status // Outputs: // Returns true when ESP-NOW is online and ready for use bool espNowOnline(); - // Get the ESP-NOW status - // Outputs: - // Returns true if ESP-NOW is being started or is online - bool espNowRunning(); - // Set the ESP-NOW channel // Inputs: // channel: New ESP-NOW channel number @@ -2169,20 +2191,6 @@ class RTK_WIFI // Returns the current WiFi channel number WIFI_CHANNEL_t getChannel(); - // Restart WiFi - // Inputs: - // always: Set true if this routine should always restart WiFi, - // when false determine restart using _restartRequest - // Outputs: - // Returns true if the WiFi layer was successfully restarted and - // false upon restart failure - bool restart(bool always); - - // Determine if any use of WiFi is starting or is online - // Outputs: - // Returns true if any WiFi use is being started or is online - bool running(); - // Configure the soft AP // Inputs: // ipAddress: IP address of the soft AP @@ -2203,16 +2211,15 @@ class RTK_WIFI // display: Address of a Print object void softApConfigurationDisplay(Print * display); + // Get the soft AP IP address + // Returns the soft IP address + IPAddress softApIpAddress(); + // Get the soft AP status // Outputs: // Returns true when the soft AP is ready for use bool softApOnline(); - // Determine if the soft AP is being started or is onine - // Outputs: - // Returns true if the soft AP is being started or is online - bool softApRunning(); - // Attempt to start the soft AP mode // Inputs: // forceAP: Set to true to force AP to start, false will only start @@ -2222,18 +2229,25 @@ class RTK_WIFI // otherwise bool startAp(bool forceAP); + // Get the WiFi station IP address + // Returns the IP address of the WiFi station + IPAddress stationIpAddress(); + // Get the station status // Outputs: // Returns true when the WiFi station is online and ready for use bool stationOnline(); - // Handle WiFi station reconnection requests - void stationReconnectionRequest(); + // Get the SSID of the remote AP + const char * stationSsid(); - // Get the station status + // Stop and start WiFi components + // Inputs: + // stopping: WiFi components that need to be stopped + // starting: WiFi components that neet to be started // Outputs: - // Returns true if the WiFi station is being started or is online - bool stationRunning(); + // Returns true if the modes were successfully configured + bool stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting); // Test the WiFi modes // Inputs: diff --git a/Firmware/RTK_Everywhere/support.ino b/Firmware/RTK_Everywhere/support.ino index bb6429aa..ac112de2 100644 --- a/Firmware/RTK_Everywhere/support.ino +++ b/Firmware/RTK_Everywhere/support.ino @@ -598,6 +598,7 @@ int AsciiToNibble(int data) return -1; } +// Dump a buffer in hex and ASCII void dumpBuffer(uint8_t *buffer, uint16_t length) { int bytes; @@ -722,6 +723,9 @@ void verifyTables() mosaicVerifyTables(); correctionVerifyTables(); webServerVerifyTables(); +#ifdef COMPILE_WIFI + wifi.verifyTables(); +#endif // COMPILE_WIFI if (CORR_NUM >= (int)('x' - 'a')) reportFatalError("Too many correction sources"); @@ -950,6 +954,7 @@ void printPartitionTable(void) } } +// Find the partition in the SPI flash used for the file system bool findSpiffsPartition(void) { esp_partition_iterator_t pi = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); @@ -964,3 +969,218 @@ bool findSpiffsPartition(void) } return false; } + +// Covert a given key's expiration date to a GPS Epoch, so that we can calculate GPS Week and ToW +// Add a millisecond to roll over from 11:59UTC to midnight of the following day +// Convert from unix epoch (time lib outputs unix) to GPS epoch (the NED-D9S expects) +long long dateToGPSEpoch(uint8_t day, uint8_t month, uint16_t year) +{ + long long unixEpoch = dateToUnixEpoch(day, month, year); // Returns Unix Epoch + + // Convert Unix Epoch time from PP to GPS Time Of Week needed for UBX message + long long gpsEpoch = unixEpoch - 315964800; // Shift to GPS Epoch. + + return (gpsEpoch); +} + +// Given a date, calculate and return the key start in unixEpoch +void dateToKeyStart(uint8_t expDay, uint8_t expMonth, uint16_t expYear, uint64_t *settingsKeyStart) +{ + long long expireUnixEpoch = dateToUnixEpoch(expDay, expMonth, expYear); + + // Thingstream lists the date that a key expires at midnight + // So if a user types in March 7th, 2022 as exp date the key's Week and ToW need to be + // calculated from (March 7th - 27 days). + long long startUnixEpoch = expireUnixEpoch - (27 * SECONDS_IN_A_DAY); // Move back 27 days + + // Additionally, Thingstream seems to be reporting Epochs that do not have leap seconds + startUnixEpoch -= gnss->getLeapSeconds(); // Modify our Epoch to match PointPerfect + + // PointPerfect uses/reports unix epochs in milliseconds + *settingsKeyStart = startUnixEpoch * MILLISECONDS_IN_A_SECOND; // Convert to ms + + uint16_t keyGPSWeek; + uint32_t keyGPSToW; + long long gpsEpoch = thingstreamEpochToGPSEpoch(*settingsKeyStart); + + epochToWeekToW(gpsEpoch, &keyGPSWeek, &keyGPSToW); + + // Print ToW and Week for debugging + if (settings.debugCorrections == true) + { + systemPrintf(" expireUnixEpoch: %lld - %s\r\n", expireUnixEpoch, printDateFromUnixEpoch(expireUnixEpoch)); + systemPrintf(" startUnixEpoch: %lld - %s\r\n", startUnixEpoch, printDateFromUnixEpoch(startUnixEpoch)); + systemPrintf(" gpsEpoch: %lld - %s\r\n", gpsEpoch, printDateFromGPSEpoch(gpsEpoch)); + systemPrintf(" KeyStart: %lld - %s\r\n", *settingsKeyStart, printDateFromUnixEpoch(*settingsKeyStart)); + systemPrintf(" keyGPSWeek: %d\r\n", keyGPSWeek); + systemPrintf(" keyGPSToW: %d\r\n", keyGPSToW); + } +} + +/* + http://www.leapsecond.com/tools/gpsdate.c + Return Modified Julian Day given calendar year, + month (1-12), and day (1-31). + - Valid for Gregorian dates from 17-Nov-1858. + - Adapted from sci.astro FAQ. +*/ +long dateToMjd(long Year, long Month, long Day) +{ + return 367 * Year - 7 * (Year + (Month + 9) / 12) / 4 - 3 * ((Year + (Month - 9) / 7) / 100 + 1) / 4 + + 275 * Month / 9 + Day + 1721028 - 2400000; +} + +// Given a date, convert into epoch +// https://www.epochconverter.com/programming/c +long dateToUnixEpoch(uint8_t day, uint8_t month, uint16_t year) +{ + struct tm t; + time_t t_of_day; + + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + + t.tm_hour = 0; + t.tm_min = 0; + t.tm_sec = 0; + t.tm_isdst = -1; // Is DST on? 1 = yes, 0 = no, -1 = unknown + + t_of_day = mktime(&t); + + return (t_of_day); +} + +// Given an epoch in ms, return the number of days from given Epoch and now +int daysFromEpoch(long long endEpoch) +{ + long delta = secondsFromEpoch(endEpoch); // number of s between dates + + if (delta == -1) + return (-1); + + delta /= SECONDS_IN_AN_HOUR; // hours + + delta /= 24; // days + return ((int)delta); +} + +// Given an epoch, set the GPSWeek and GPSToW +void epochToWeekToW(long long epoch, uint16_t *GPSWeek, uint32_t *GPSToW) +{ + *GPSWeek = (uint16_t)(epoch / (7 * SECONDS_IN_A_DAY)); + *GPSToW = (uint32_t)(epoch % (7 * SECONDS_IN_A_DAY)); +} + +// Get a date from a user +// Return true if validated +// https://www.includehelp.com/c-programs/validate-date.aspx +bool getDate(uint8_t &dd, uint8_t &mm, uint16_t &yy) +{ + systemPrint("Enter Day: "); + dd = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + systemPrint("Enter Month: "); + mm = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + systemPrint("Enter Year (YYYY): "); + yy = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + // check year + if (yy >= 2022 && yy <= 9999) + { + // check month + if (mm >= 1 && mm <= 12) + { + // check days + if ((dd >= 1 && dd <= 31) && (mm == 1 || mm == 3 || mm == 5 || mm == 7 || mm == 8 || mm == 10 || mm == 12)) + return (true); + else if ((dd >= 1 && dd <= 30) && (mm == 4 || mm == 6 || mm == 9 || mm == 11)) + return (true); + else if ((dd >= 1 && dd <= 28) && (mm == 2)) + return (true); + else if (dd == 29 && mm == 2 && (yy % 400 == 0 || (yy % 4 == 0 && yy % 100 != 0))) + return (true); + else + { + printf("Day is invalid.\n"); + return (false); + } + } + else + { + printf("Month is not valid.\n"); + return (false); + } + } + + printf("Year is not valid.\n"); + return (false); +} + +/* + Convert GPS Week and Seconds to Modified Julian Day. + - Ignores UTC leap seconds. +*/ +long gpsToMjd(long GpsCycle, long GpsWeek, long GpsSeconds) +{ + long GpsDays = ((GpsCycle * 1024) + GpsWeek) * 7 + (GpsSeconds / 86400); + // GpsDays -= 1; //Correction + return dateToMjd(1980, 1, 6) + GpsDays; +} + +// Given a GPS Week and ToW, convert to an expiration date +void gpsWeekToWToDate(uint16_t keyGPSWeek, uint32_t keyGPSToW, long *expDay, long *expMonth, long *expYear) +{ + long gpsDays = gpsToMjd(0, (long)keyGPSWeek, (long)keyGPSToW); // Covert ToW and Week to # of days since Jan 6, 1980 + mjdToDate(gpsDays, expYear, expMonth, expDay); +} + +/* + Convert Modified Julian Day to calendar date. + - Assumes Gregorian calendar. + - Adapted from Fliegel/van Flandern ACM 11/#10 p 657 Oct 1968. +*/ +void mjdToDate(long Mjd, long *Year, long *Month, long *Day) +{ + long J, C, Y, M; + + J = Mjd + 2400001 + 68569; + C = 4 * J / 146097; + J = J - (146097 * C + 3) / 4; + Y = 4000 * (J + 1) / 1461001; + J = J - 1461 * Y / 4 + 31; + M = 80 * J / 2447; + *Day = J - 2447 * M / 80; + J = M / 11; + *Month = M + 2 - (12 * J); + *Year = 100 * (C - 49) + Y + J; +} + +// Given an epoch in ms, return the number of seconds from given Epoch and now +long secondsFromEpoch(long long endEpoch) +{ + if (online.rtc == false) + { + // If we don't have RTC we can't calculate days to expire + if (settings.debugCorrections == true) + systemPrintln("No RTC available"); + return (-1); + } + + endEpoch /= MILLISECONDS_IN_A_SECOND; // Convert PointPerfect ms Epoch to s + + long currentEpoch = rtc.getEpoch(); + + long delta = endEpoch - currentEpoch; // number of s between dates + return (delta); +} + +// Given a GPSWeek and GPSToW, set the epoch +void WeekToWToUnixEpoch(uint64_t *unixEpoch, uint16_t GPSWeek, uint32_t GPSToW) +{ + *unixEpoch = GPSWeek * (7 * SECONDS_IN_A_DAY); // 2192 + *unixEpoch += GPSToW; // 518400 + *unixEpoch += 315964800; +} +