diff --git a/.github/workflows/compile-rtk-everywhere.yml b/.github/workflows/compile-rtk-everywhere.yml index 7bbddda90..64132ff60 100644 --- a/.github/workflows/compile-rtk-everywhere.yml +++ b/.github/workflows/compile-rtk-everywhere.yml @@ -6,7 +6,7 @@ on: env: FILENAME_PREFIX: RTK_Everywhere_Firmware FIRMWARE_VERSION_MAJOR: 2 - FIRMWARE_VERSION_MINOR: 0 + FIRMWARE_VERSION_MINOR: 1 POINTPERFECT_LBAND_TOKEN: ${{ secrets.POINTPERFECT_LBAND_TOKEN }} POINTPERFECT_IP_TOKEN: ${{ secrets.POINTPERFECT_IP_TOKEN }} POINTPERFECT_LBAND_IP_TOKEN: ${{ secrets.POINTPERFECT_LBAND_IP_TOKEN }} @@ -94,7 +94,7 @@ jobs: "SparkFun u-blox PointPerfect Library"@1.11.4 "SparkFun IM19 IMU Arduino Library"@1.0.1 "SparkFun UM980 Triband RTK GNSS Arduino Library"@1.0.4 - "SparkFun LG290P Quadband RTK GNSS Arduino Library"@1.0.0 + "SparkFun LG290P Quadband RTK GNSS Arduino Library"@1.0.2 "SparkFun I2C Expander Arduino Library"@1.0.1 - name: Patch libmbedtls @@ -140,6 +140,10 @@ jobs: - name: Copy custom RTKEverywhere.csv run: + # Compile the source using the 16MB partition file. Other platforms (ie, the 8MB Postcard) use + # the same binary but use a different partition binary during the upload phase. + # View the different RTK partition files used during upload here: + # https://github.com/sparkfun/SparkFun_RTK_Firmware_Uploader/tree/main/RTK_Firmware_Uploader/resource cp Firmware/RTKEverywhere.csv /home/runner/.arduino15/packages/esp32/hardware/esp32/${{ env.CORE_VERSION }}/tools/partitions/RTKEverywhere.csv - name: Compile Sketch diff --git a/.github/workflows/non-release-build.yml b/.github/workflows/non-release-build.yml index dc8b11599..0c656c818 100644 --- a/.github/workflows/non-release-build.yml +++ b/.github/workflows/non-release-build.yml @@ -10,7 +10,7 @@ env: POINTPERFECT_LBAND_TOKEN: ${{ secrets.POINTPERFECT_LBAND_TOKEN }} POINTPERFECT_IP_TOKEN: ${{ secrets.POINTPERFECT_IP_TOKEN }} POINTPERFECT_LBAND_IP_TOKEN: ${{ secrets.POINTPERFECT_LBAND_IP_TOKEN }} - CORE_VERSION: 3.0.1 + CORE_VERSION: 3.0.7 jobs: build: @@ -78,7 +78,7 @@ jobs: run: arduino-cli lib install ArduinoJson@7.0.4 ESP32Time@2.0.0 - ESP32_BleSerial@1.0.4 + ESP32_BleSerial@2.0.1 "ESP32-OTA-Pull"@1.0.0 JC_Button@2.1.2 PubSubClient@2.8.0 @@ -88,18 +88,14 @@ jobs: "SparkFun u-blox GNSS v3"@3.1.8 "SparkFun Qwiic OLED Arduino Library"@1.0.13 SSLClientESP32@2.0.0 - "SparkFun Extensible Message Parser"@1.0.0 + "SparkFun Extensible Message Parser"@1.0.2 "SparkFun BQ40Z50 Battery Manager Arduino Library"@1.0.0 "ArduinoMqttClient"@0.1.8 "SparkFun u-blox PointPerfect Library"@1.11.4 "SparkFun IM19 IMU Arduino Library"@1.0.1 "SparkFun UM980 Triband RTK GNSS Arduino Library"@1.0.4 - - # https://github.com/avinabmalla/ESP32_BleSerial/issues/15 - - name: Patch ESP32_BleSerial BLECharacteristic - run: | - cd Firmware/RTK_Everywhere/Patch/ - cp BleSerial.cpp /home/runner/Arduino/libraries/ESP32_BleSerial/src/BleSerial.cpp + "SparkFun LG290P Quadband RTK GNSS Arduino Library"@1.0.0 + "SparkFun I2C Expander Arduino Library"@1.0.1 - name: Patch libmbedtls run: | @@ -109,6 +105,11 @@ jobs: cp libmbedcrypto.a /home/runner/.arduino15/packages/esp32/tools/esp32-arduino-libs/${{ env.ESP_IDF }}/esp32/lib/libmbedcrypto.a cp libmbedx509.a /home/runner/.arduino15/packages/esp32/tools/esp32-arduino-libs/${{ env.ESP_IDF }}/esp32/lib/libmbedx509.a + - name: Patch NetworkEvents + run: | + cd Firmware/RTK_Everywhere/Patch/ + cp NetworkEvents.* /home/runner/.arduino15/packages/esp32/hardware/esp32/${{ env.CORE_VERSION }}/libraries/Network/src/ + - name: Setup Python uses: actions/setup-python@v4 with: @@ -139,12 +140,13 @@ jobs: - name: Copy custom RTKEverywhere.csv run: + # Use the 16MB partitions by default. 8MB (Postcard) partitions must be compiled separately cp Firmware/RTKEverywhere.csv /home/runner/.arduino15/packages/esp32/hardware/esp32/${{ env.CORE_VERSION }}/tools/partitions/RTKEverywhere.csv - name: Compile Sketch run: arduino-cli compile --fqbn "esp32:esp32:esp32":DebugLevel=${{ env.DEBUG_LEVEL }},PSRAM=enabled ./Firmware/RTK_Everywhere/RTK_Everywhere.ino --build-property build.partitions=RTKEverywhere - --build-property upload.maximum_size=3145728 + --build-property upload.maximum_size=4055040 --build-property "compiler.cpp.extra_flags=-MMD -c \"-DPOINTPERFECT_LBAND_TOKEN=$POINTPERFECT_LBAND_TOKEN\" \"-DPOINTPERFECT_IP_TOKEN=$POINTPERFECT_IP_TOKEN\" \"-DPOINTPERFECT_LBAND_IP_TOKEN=$POINTPERFECT_LBAND_IP_TOKEN\" \"-DFIRMWARE_VERSION_MAJOR=$FIRMWARE_VERSION_MAJOR\" \"-DFIRMWARE_VERSION_MINOR=$FIRMWARE_VERSION_MINOR\" \"-DENABLE_DEVELOPER=${{ env.ENABLE_DEVELOPER }}\"" --export-binaries diff --git a/Firmware/RTK_Everywhere/AP-Config/index.html b/Firmware/RTK_Everywhere/AP-Config/index.html index e2ca23f02..2054d5535 100644 --- a/Firmware/RTK_Everywhere/AP-Config/index.html +++ b/Firmware/RTK_Everywhere/AP-Config/index.html @@ -84,8 +84,8 @@
- Battery level +67 + Battery level
@@ -242,7 +242,7 @@ @@ -257,7 +257,7 @@
@@ -282,29 +282,33 @@
-
- -
- -

+
+
+ +
+ +

+
-
- -
- -

+
+
+ +
+ +

+
@@ -331,8 +335,8 @@
- - + +
@@ -464,7 +468,8 @@
- @@ -494,7 +499,8 @@
- @@ -524,7 +530,7 @@
- +
@@ -547,7 +553,7 @@ + title="If the precise location of a base station is not known it may be obtained by ‘surveying’ the location. The base is fixed in one place and takes approximately 60 seconds worth of readings to obtain a best fit location based on the measurements. This method achieves ~30cm accurate position but can vary. Increasing the Minimum Observation Time and/or Required Mean Deviation will increase accuracy but only to a point. Better accuracy is achieved with long-term logging and post processing. Default: 60s and 5.0m. Limits: 60 to 600s, 1.0 to 5.0m.">
@@ -598,7 +604,7 @@
+ onClick="useECEFCoordinates()">Load ECEF From GNSS @@ -696,7 +702,7 @@
+ From GNSS @@ -1259,7 +1265,8 @@
- + @@ -1269,7 +1276,8 @@
- @@ -1284,7 +1292,7 @@
- +
@@ -1437,17 +1445,19 @@
- +
- +
- @@ -1645,6 +1655,15 @@

+ +
+ + + + + +

@@ -1689,10 +1708,10 @@
- + + title="The internal ESP32 can be used as a point-to-point, or point-to-multipoint radio for RTCM transmission. Default: Off">
@@ -1735,8 +1754,46 @@

+
+ + +
@@ -1756,7 +1813,7 @@ @@ -1826,7 +1883,7 @@ @@ -1950,9 +2007,10 @@ + + title="Put the system into the selected mode at next Power On or Reset."> @@ -1973,7 +2031,7 @@ @@ -1984,7 +2042,7 @@
@@ -2049,7 +2107,8 @@
- + @@ -2140,6 +2200,31 @@
+
+
+ + + + + +
+
+ +
+
+ + +

+
+
+
@@ -2253,7 +2338,7 @@
@@ -2310,7 +2395,7 @@
diff --git a/Firmware/RTK_Everywhere/AP-Config/src/main.js b/Firmware/RTK_Everywhere/AP-Config/src/main.js index 314722611..33370bd6c 100644 --- a/Firmware/RTK_Everywhere/AP-Config/src/main.js +++ b/Firmware/RTK_Everywhere/AP-Config/src/main.js @@ -121,6 +121,9 @@ function parseIncoming(msg) { show("useLocalizedDistributionCheckbox"); show("useEnableExtCorrRadio"); show("extCorrRadioSPARTNSourceDropdown"); + hide("shutdownNoChargeTimeoutMinutesCheckboxDetail"); + + hide("constellationNavic"); //Not supported on ZED } else if ((platformPrefix == "Facet v2") || (platformPrefix == "Facet v2 LBand")) { show("baseConfig"); @@ -140,6 +143,8 @@ function parseIncoming(msg) { show("useLocalizedDistributionCheckbox"); show("useEnableExtCorrRadio"); show("extCorrRadioSPARTNSourceDropdown"); + + hide("constellationNavic"); //Not supported on ZED } else if (platformPrefix == "Facet mosaicX5") { show("baseConfig"); @@ -178,9 +183,9 @@ function parseIncoming(msg) { newOption = new Option('Unlimited', '7'); select.add(newOption, undefined); - ge("messageRateInfoText").setAttribute('data-bs-original-title','The GNSS can output NMEA and RTCMv3 at different rates. For NMEA: select a stream for each message, and set an interval for each stream. For RTCMv3: set an interval for each message group, and enable individual messages.'); - ge("rtcmRateInfoText").setAttribute('data-bs-original-title','RTCM is transmitted by the base at a default of 1Hz for messages 1005, MSM4, and 0.1Hz for 1033. This can be lowered for radios with low bandwidth or tailored to transmit any/all RTCM messages. Limits: 0.1 to 600.'); - ge("enableExtCorrRadioInfoText").setAttribute('data-bs-original-title','Enable external radio corrections: RTCMv3 on mosaic COM2. Default: False'); + ge("messageRateInfoText").setAttribute('data-bs-original-title', 'The GNSS can output NMEA and RTCMv3 at different rates. For NMEA: select a stream for each message, and set an interval for each stream. For RTCMv3: set an interval for each message group, and enable individual messages.'); + ge("rtcmRateInfoText").setAttribute('data-bs-original-title', 'RTCM is transmitted by the base at a default of 1Hz for messages 1005, MSM4, and 0.1Hz for 1033. This can be lowered for radios with low bandwidth or tailored to transmit any/all RTCM messages. Limits: 0.1 to 600.'); + ge("enableExtCorrRadioInfoText").setAttribute('data-bs-original-title', 'Enable external radio corrections: RTCMv3 on mosaic COM2. Default: False'); } else if (platformPrefix == "Torch") { show("baseConfig"); @@ -198,6 +203,8 @@ function parseIncoming(msg) { show("useAssistNowCheckbox"); //Does the PPL use MGA? Not sure... show("measurementRateInput"); + show("loraConfig"); + select = ge("dynamicModel"); let newOption = new Option('Survey', '0'); select.add(newOption, undefined); @@ -206,7 +213,38 @@ function parseIncoming(msg) { newOption = new Option('Automotive', '2'); select.add(newOption, undefined); - ge("rtcmRateInfoText").setAttribute('data-bs-original-title','RTCM is transmitted by the base at a default of 1Hz for messages 1005, 1074, 1084, 1094, 1124, and 0.1Hz for 1033. This can be lowered for radios with low bandwidth or tailored to transmit any/all RTCM messages. Limits: 0 to 20. Note: The measurement rate is overridden to 1Hz when in Base mode.'); + ge("rtcmRateInfoText").setAttribute('data-bs-original-title', 'RTCM is transmitted by the base at a default of 1Hz for messages 1005, 1074, 1084, 1094, 1124, and 0.1Hz for 1033. This can be lowered for radios with low bandwidth or tailored to transmit any/all RTCM messages. Limits: 0 to 20. Note: The measurement rate is overridden to 1Hz when in Base mode.'); + + } + else if (platformPrefix == "Postcard") { + show("baseConfig"); + show("ppConfig"); + hide("ethernetConfig"); + hide("ntpConfig"); + show("portsConfig"); + show("externalPortOptions"); + show("logToSDCard"); + + hide("galileoHasSetting"); + hide("tiltConfig"); + hide("beeperControl"); + + show("useAssistNowCheckbox"); + show("measurementRateInput"); + hide("mosaicNMEAStreamDropdowns"); + show("surveyInSettings"); + show("useLocalizedDistributionCheckbox"); + show("useEnableExtCorrRadio"); + show("extCorrRadioSPARTNSourceDropdown"); + + hide("constellationSbas"); //Not supported on LG290P + show("constellationNavic"); + + hide("dynamicModelDropdown"); //Not supported on LG290P + hide("minElevConfig"); //Not supported on LG290P + hide("minCNOConfig"); //Not supported on LG290P + + ge("rtcmRateInfoText").setAttribute('data-bs-original-title', 'RTCM is transmitted by the base at a default of 1Hz for messages 1005, 1074, 1084, 1094, 1114, 1124, 1134. This can be lowered for radios with low bandwidth or tailored to transmit any/all RTCM messages. Limits: 0 to 20. Note: The measurement rate is overridden to 1Hz when in Base mode.'); } } else if (id.includes("gnssFirmwareVersionInt")) { @@ -365,7 +403,7 @@ function parseIncoming(msg) { else if (id.includes("messageRate") || id.includes("messageIntervalRTCM")) { // messageRateNMEA_GPDTM // messageRateRTCMRover_RTCM1001 - // messagRatesRTCMBase_RTCM1001 + // messageRatesRTCMBase_RTCM1001 // messageIntervalRTCMRover_RTCM1230 // messageIntervalRTCMBase_RTCM1230 var messageName = id; @@ -474,6 +512,18 @@ function parseIncoming(msg) { hide("enableAutomaticResetDetails"); } } + else if (id.includes("shutdownNoChargeTimeoutMinutes")) { + if (val > 0) { + ge("shutdownNoChargeTimeoutMinutes").value = val; + ge("shutdownNoChargeTimeoutMinutesCheckbox").checked = true; + show("shutdownNoChargeTimeoutMinutesDetails"); + } + else { + ge("shutdownNoChargeTimeoutMinutes").value = 0; + ge("shutdownNoChargeTimeoutMinutesCheckbox").checked = false; + hide("shutdownNoChargeTimeoutMinutesDetails"); + } + } //Convert incoming mm to local meters else if (id.includes("antennaHeight_mm")) { @@ -485,14 +535,14 @@ function parseIncoming(msg) { try { ge(id).checked = true; } catch (error) { - console.log("Issue with ID: " + id); + console.log("Set True issue with ID: " + id); } } else if (val == "false") { try { ge(id).checked = false; } catch (error) { - console.log("Issue with ID: " + id); + console.log("Set False issue with ID: " + id); } } @@ -528,6 +578,7 @@ function parseIncoming(msg) { ge("enableExternalPulse").dispatchEvent(new CustomEvent('change')); ge("enableExternalHardwareEventLogging").dispatchEvent(new CustomEvent('change')); ge("enableEspNow").dispatchEvent(new CustomEvent('change')); + ge("enableLora").dispatchEvent(new CustomEvent('change')); ge("antennaPhaseCenter_mm").dispatchEvent(new CustomEvent('change')); ge("enableLogging").dispatchEvent(new CustomEvent('change')); ge("enableLoggingRINEX").dispatchEvent(new CustomEvent('change')); @@ -852,6 +903,12 @@ function validateFields() { ge("autoKeyRenewal").checked = true; } + //Port Config + if (ge("enableExternalPulse").checked == true) { + checkElementValue("externalPulseTimeBetweenPulse", 1, 60000000, "Must be 1 to 60,000,000", "collapsePortsConfig"); + checkElementValue("externalPulseLength", 1, 60000000, "Must be 1 to 60,000,000", "collapsePortsConfig"); + } + //WiFi Config checkElementString("wifiNetwork_0SSID", 0, 49, "Must be 0 to 49 characters", "collapseWiFiConfig"); checkElementString("wifiNetwork_0Password", 0, 49, "Must be 0 to 49 characters", "collapseWiFiConfig"); @@ -877,6 +934,19 @@ function validateFields() { //But, on WiFi, they can be... //checkCheckboxMutex("enableTcpClient", "enableTcpServer", "TCP Client and Server can not be enabled at the same time", "collapseTCPUDPConfig"); + //Radio Config + if (ge("enableLora").checked == true) { + checkElementValue("loraCoordinationFrequency", 903, 927, "Must be 903 to 927", "collapseRadioConfig"); + checkElementValue("loraSerialInteractionTimeout_s", 10, 600, "Must be 10 to 600", "collapseRadioConfig"); + } + + //Corrections Config + checkElementValue("correctionsSourcesLifetime", 5, 120, "Must be 5 to 120", "collapseCorrectionsPriorityConfig"); + + //Instrument Config + checkElementValue("antennaHeight_m", -15, 15, "Must be -15 to 15", "collapseBaseConfig"); + checkElementValue("antennaPhaseCenter_mm", -200.0, 200.0, "Must be -200.0 to 200.0", "collapseBaseConfig"); + //System Config if (ge("enableLogging").checked == true) { checkElementValue("maxLogTime", 1, 1051200, "Must be 1 to 1,051,200", "collapseSystemConfig"); @@ -902,12 +972,18 @@ function validateFields() { } if (ge("enableAutoReset").checked == true) { - checkElementValue("rebootMinutes", 1, 4294967, "Must be 1 to 4294967", "collapseSystemConfig"); + checkElementValue("rebootMinutes", 0, 4294967, "Must be 0 to 4,294,967", "collapseSystemConfig"); } else { clearElement("rebootMinutes", 0); //0 = disable } + if (ge("shutdownNoChargeTimeoutMinutesCheckbox").checked == true) { + checkElementValue("shutdownNoChargeTimeoutMinutes", 0, 604800, "Must be 0 to 604,800", "collapseSystemConfig"); + } + else { + clearElement("shutdownNoChargeTimeoutMinutes", 0); //0 = disable + } //Ethernet if (platformPrefix == "EVK") { @@ -926,15 +1002,6 @@ function validateFields() { checkElementValue("ntpRootDispersion", 0, 10000000, "Must be 0 to 10,000,000", "collapseNTPConfig"); checkElementString("ntpReferenceId", 1, 4, "Must be 1 to 4 chars", "collapseNTPConfig"); } - - //Port Config - if (ge("enableExternalPulse").checked == true) { - checkElementValue("externalPulseTimeBetweenPulse", 1, 60000000, "Must be 1 to 60,000,000", "collapsePortsConfig"); - checkElementValue("externalPulseLength", 1, 60000000, "Must be 1 to 60,000,000", "collapsePortsConfig"); - } - - //Corrections Priorities - checkElementValue("correctionsSourcesLifetime", 5, 120, "Must be 5 to 120", "collapseCorrectionsPriorityConfig"); } var currentProfileNumber = 0; @@ -972,12 +1039,14 @@ function changeProfile() { collapseSection("collapsePortsConfig", "portsCaret"); collapseSection("collapseWiFiConfig", "wifiCaret"); collapseSection("collapseTCPUDPConfig", "tcpUdpCaret"); + collapseSection("collapseRadioConfig", "radioCaret"); collapseSection("collapseCorrectionsPriorityConfig", "correctionsCaret"); + collapseSection("collapseInstrumentConfig", "instrumentCaret"); collapseSection("collapseSystemConfig", "systemCaret"); + collapseSection("collapseEthernetConfig", "ethernetCaret"); collapseSection("collapseNTPConfig", "ntpCaret"); collapseSection("collapseFileManager", "fileManagerCaret"); - collapseSection("collapseInstrumentConfig", "instrumentCaret"); } } @@ -1523,11 +1592,17 @@ document.addEventListener("DOMContentLoaded", (event) => { hide("ecefConfig"); show("geodeticConfig"); - if ((platformPrefix == "Facet mosaicX5") || (platformPrefix == "Facet v2") || (platformPrefix == "Facet v2 LBand")) { - ge("antennaPhaseCenter_mm").value = 61.4; + if ((platformPrefix == "Facet mosaicX5") || (platformPrefix == "Facet v2 LBand")) { + ge("antennaPhaseCenter_mm").value = 68.5; //Average of L1/L2 + } + else if (platformPrefix == "Facet v2") { + ge("antennaPhaseCenter_mm").value = 69.6; //Average of L1/L2 } else if (platformPrefix == "Torch") { - ge("antennaPhaseCenter_mm").value = 116.2; + ge("antennaPhaseCenter_mm").value = 116.5; //Average of L1/L2 + } + else if (platformPrefix == "EVK") { + ge("antennaPhaseCenter_mm").value = 42.0; //Average of L1/L2 } else { ge("antennaPhaseCenter_mm").value = 0.0; @@ -1631,6 +1706,15 @@ document.addEventListener("DOMContentLoaded", (event) => { } }); + ge("enableLora").addEventListener("change", function () { + if (ge("enableLora").checked == true) { + show("loraDetails"); + } + else { + hide("loraDetails"); + } + }); + ge("enableLogging").addEventListener("change", function () { showHideLoggingDetails(); }); @@ -1671,6 +1755,15 @@ document.addEventListener("DOMContentLoaded", (event) => { } }); + ge("shutdownNoChargeTimeoutMinutesCheckbox").addEventListener("change", function () { + if (ge("shutdownNoChargeTimeoutMinutesCheckbox").checked == true) { + show("shutdownNoChargeTimeoutMinutesDetails"); + } + else { + hide("shutdownNoChargeTimeoutMinutesDetails"); + } + }); + ge("fixedAltitude").addEventListener("change", function () { adjustHAE(); }); @@ -2057,7 +2150,7 @@ function getMessageList() { ge(savedCheckboxNames[x]).checked = false; } else { - console.log("Issue with ID: " + savedCheckboxNames[x]); + console.log("getMessageList Issue with ID: " + savedCheckboxNames[x]); } } } @@ -2089,7 +2182,7 @@ function getMessageListBase() { ge(savedCheckboxNames[x]).checked = false; } else { - console.log("Issue with ID: " + savedCheckboxNames[x]); + console.log("getMessageListBase issue with ID: " + savedCheckboxNames[x]); } } } @@ -2265,7 +2358,13 @@ function checkingNewFirmware() { function newFirmwareVersion(firmwareVersion) { clearMsg('firmwareCheckNewMsg'); - if (firmwareVersion == "ERROR") { + if (firmwareVersion == "NO_INTERNET") { + showMsgError('firmwareCheckNewMsg', "No internet"); + hide("divGetNewFirmware"); + ge("btnCheckNewFirmware").disabled = false; + return; + } + else if (firmwareVersion == "NO_SERVER") { showMsgError('firmwareCheckNewMsg', "Network or Server not available"); hide("divGetNewFirmware"); ge("btnCheckNewFirmware").disabled = false; @@ -2601,4 +2700,4 @@ function printableInputType(coordinateInputType) { break; } return ("Unknown"); -} \ No newline at end of file +} diff --git a/Firmware/RTK_Everywhere/Base.ino b/Firmware/RTK_Everywhere/Base.ino index e8857715d..f55370571 100644 --- a/Firmware/RTK_Everywhere/Base.ino +++ b/Firmware/RTK_Everywhere/Base.ino @@ -28,4 +28,17 @@ void processRTCM(uint8_t *rtcmData, uint16_t dataLength) if (rtcmPacketsSent > 999) rtcmPacketsSent = 1; // Trim to three digits to avoid log icon and increasing bar } +} + +//------------------------------ +// When settings.baseCasterOverride is true, override enableTcpServer, tcpServerPort, and enableNtripCaster settings +//------------------------------ +void baseCasterEnableOverride() +{ + settings.baseCasterOverride = true; +} + +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 028962df8..a6fe4fe3d 100644 --- a/Firmware/RTK_Everywhere/Begin.ino +++ b/Firmware/RTK_Everywhere/Begin.ino @@ -208,6 +208,7 @@ void beginBoard() present.brand = BRAND_SPARKFUN; present.psram_2mb = true; present.gnss_um980 = true; + present.antennaPhaseCenter_mm = 116.5; // Default to Torch helical APC, average of L1/L2 present.radio_lora = true; present.fuelgauge_bq40z50 = true; present.charger_mp2762a = true; @@ -215,7 +216,6 @@ void beginBoard() present.button_powerHigh = true; // Button is pressed when high present.beeper = true; present.gnss_to_uart = true; - present.antennaPhaseCenter_mm = 115.7; // Default to Torch helical APC present.needsExternalPpl = true; // Uses the PointPerfect Library present.galileoHasCapable = true; present.multipathMitigation = true; // UM980 has MPM, other platforms do not @@ -316,6 +316,7 @@ void beginBoard() // Pin defs etc. for EVK v1.1 present.psram_4mb = true; present.gnss_zedf9p = true; + present.antennaPhaseCenter_mm = 42.0; // Default to NGS certified SPK6615H APC, average of L1/L2 present.lband_neo = true; present.cellular_lara = true; present.ethernet_ws5500 = true; @@ -443,13 +444,13 @@ void beginBoard() present.brand = BRAND_SPARKPNT; present.psram_4mb = true; present.gnss_zedf9p = true; + present.antennaPhaseCenter_mm = 68.5; // Default to L-Band element APC, average of L1/L2 present.lband_neo = true; present.microSd = true; present.microSdCardDetectLow = true; present.display_i2c0 = true; present.display_type = DISPLAY_64x48; present.i2c0BusSpeed_400 = true; - present.button_mode = true; present.peripheralPowerControl = true; present.button_powerLow = true; // Button is pressed when low present.charger_mcp73833 = true; @@ -525,12 +526,12 @@ void beginBoard() present.brand = BRAND_SPARKPNT; present.psram_4mb = true; present.gnss_zedf9p = true; + present.antennaPhaseCenter_mm = 69.6; // Default to NGS certified RTK Facet element APC, average of L1/L2 present.microSd = true; present.microSdCardDetectLow = true; present.display_i2c0 = true; present.display_type = DISPLAY_64x48; present.i2c0BusSpeed_400 = true; - present.button_mode = true; present.peripheralPowerControl = true; present.button_powerLow = true; // Button is pressed when low present.charger_mcp73833 = true; @@ -620,6 +621,7 @@ void beginBoard() present.brand = BRAND_SPARKPNT; present.psram_4mb = true; present.gnss_mosaicX5 = true; + present.antennaPhaseCenter_mm = 68.5; // Default to L-Band element APC, average of L1/L2 present.display_i2c0 = true; present.display_type = DISPLAY_64x48; present.i2c0BusSpeed_400 = true; @@ -690,7 +692,7 @@ void beginBoard() present.brand = BRAND_SPARKPNT; present.psram_2mb = true; present.gnss_lg290p = true; - present.antennaPhaseCenter_mm = 42.0; // Default to SPK6618H APC + present.antennaPhaseCenter_mm = 42.0; // Default to SPK6618H APC, average of L1/L2 present.needsExternalPpl = true; // Uses the PointPerfect Library present.gnss_to_uart = true; @@ -711,14 +713,14 @@ void beginBoard() pin_GnssUart_TX = 22; pin_GNSS_Reset = 33; - pin_GNSS_TimePulse = 8; // PPS on LG290P + pin_GNSS_TimePulse = 36; // PPS on LG290P pin_SCK = 32; pin_POCI = 25; pin_PICO = 26; pin_microSD_CS = 27; - pin_gpioExpanderInterrupt = 14; // Pin 'AOI' on Portability Shield + pin_gpioExpanderInterrupt = 14; // Pin 'AOI' (Analog Output Input) on Portability Shield pin_bluetoothStatusLED = 4; // Blue LED pin_gnssStatusLED = 0; // Green LED @@ -975,14 +977,6 @@ void beginGnssUart() if (present.gnss_to_uart == false) return; - // Skip if going into configure-via-ethernet mode - if (configureViaEthernet) - { - if (settings.debugNetworkLayer) - systemPrintln("configureViaEthernet: skipping beginGnssUart"); - return; - } - size_t length; TaskHandle_t taskHandle; @@ -1061,8 +1055,7 @@ void pinGnssUartTask(void *pvParameters) // Reduce threshold value above which RX FIFO full interrupt is generated // Allows more time between when the UART interrupt occurs and when the FIFO buffer overruns - // serialGNSS->setRxFIFOFull(50); //Available in >v2.0.5 - uart_set_rx_full_threshold(2, settings.serialGNSSRxFullThreshold); // uart_num, threshold + serialGNSS->setRxFIFOFull(settings.serialGNSSRxFullThreshold); // Stop notification if (settings.printTaskStartStop) @@ -1076,14 +1069,6 @@ void beginGnssUart2() if (present.gnss_to_uart2 == false) return; - // Skip if going into configure-via-ethernet mode - if (configureViaEthernet) - { - if (settings.debugNetworkLayer) - systemPrintln("configureViaEthernet: skipping beginGnssUart2"); - return; - } - serial2GNSS = new HardwareSerial(1); // Use UART1 on the ESP32 to communicate with the mosaic serial2GNSS->setRxBufferSize(1024 * 1); @@ -1113,47 +1098,6 @@ void beginFS() } } -// Check if configureViaEthernet.txt exists -bool checkConfigureViaEthernet() -{ - if (online.fs == false) - return false; - - if (LittleFS.exists("/configureViaEthernet.txt")) - { - if (settings.debugNetworkLayer) - systemPrintln("LittleFS configureViaEthernet.txt exists"); - LittleFS.remove("/configureViaEthernet.txt"); - return true; - } - - return false; -} - -// Force configure-via-ethernet mode by creating configureViaEthernet.txt in LittleFS -bool forceConfigureViaEthernet() -{ - if (online.fs == false) - return false; - - if (LittleFS.exists("/configureViaEthernet.txt")) - { - if (settings.debugNetworkLayer) - systemPrintln("LittleFS configureViaEthernet.txt already exists"); - return true; - } - - File cveFile = LittleFS.open("/configureViaEthernet.txt", FILE_WRITE); - cveFile.close(); - - if (LittleFS.exists("/configureViaEthernet.txt")) - return true; - - if (settings.debugNetworkLayer) - systemPrintln("Unable to create configureViaEthernet.txt on LittleFS"); - return false; -} - // Begin interrupts void beginInterrupts() { diff --git a/Firmware/RTK_Everywhere/Bluetooth.ino b/Firmware/RTK_Everywhere/Bluetooth.ino index 0d196ae67..a63de0f94 100644 --- a/Firmware/RTK_Everywhere/Bluetooth.ino +++ b/Firmware/RTK_Everywhere/Bluetooth.ino @@ -42,7 +42,7 @@ BTSerialInterface *bluetoothSerialBleCommands; // Second BLE serial for CLI inte #define BLE_COMMAND_RX_UUID "7e400002-b5a3-f393-e0a9-e50e24dcca9e" #define BLE_COMMAND_TX_UUID "7e400003-b5a3-f393-e0a9-e50e24dcca9e" -TaskHandle_t bluetoothCommandTaskHandle; // Task to monitor incoming CLI from BLE +TaskHandle_t bluetoothCommandTaskHandle = nullptr; // Task to monitor incoming CLI from BLE #endif // COMPILE_BT @@ -54,24 +54,32 @@ TaskHandle_t bluetoothCommandTaskHandle; // Task to monitor incoming CLI from BL void bluetoothUpdate() { #ifdef COMPILE_BT - if (bluetoothIsConnected() == true && bluetoothState == BT_NOTCONNECTED) + static uint32_t lastCheck = millis(); // Check if connected every 100ms + if (millis() > (lastCheck + 100)) { - systemPrintln("BT client connected"); - bluetoothState = BT_CONNECTED; - // LED is controlled by tickerBluetoothLedUpdate() + lastCheck = millis(); - btPrintEchoExit = false; // Reset the exiting of config menus and/or command modes - } + // If bluetoothState == BT_OFF, don't call bluetoothIsConnected() - if (bluetoothIsConnected() == false && bluetoothState == BT_CONNECTED) - { - systemPrintln("BT client disconnected"); + if ((bluetoothState == BT_NOTCONNECTED) && (bluetoothIsConnected())) + { + systemPrintln("BT client connected"); + bluetoothState = BT_CONNECTED; + // LED is controlled by tickerBluetoothLedUpdate() - btPrintEcho = false; - btPrintEchoExit = true; // Force exit all config menus and/or command modes - printEndpoint = PRINT_ENDPOINT_SERIAL; + btPrintEchoExit = false; // Reset the exiting of config menus and/or command modes + } - bluetoothState = BT_NOTCONNECTED; + else if ((bluetoothState == BT_CONNECTED) && (!bluetoothIsConnected())) + { + systemPrintln("BT client disconnected"); + + btPrintEcho = false; + btPrintEchoExit = true; // Force exit all config menus and/or command modes + printEndpoint = PRINT_ENDPOINT_SERIAL; + + bluetoothState = BT_NOTCONNECTED; + } } #endif // COMPILE_BT } @@ -199,7 +207,7 @@ uint8_t bluetoothCommandRead() } // Determine if data is available -bool bluetoothRxDataAvailable() +int bluetoothRxDataAvailable() { #ifdef COMPILE_BT if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP_AND_BLE) @@ -217,20 +225,20 @@ bool bluetoothRxDataAvailable() return (0); #else // COMPILE_BT - return false; + return 0; #endif // COMPILE_BT } // Determine if data is available on the BLE Command interface -bool bluetoothCommandAvailable() +int bluetoothCommandAvailable() { #ifdef COMPILE_BT if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP_AND_BLE || settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE) return bluetoothSerialBleCommands->available(); - return (false); + return (0); #else // COMPILE_BT - return false; + return 0; #endif // COMPILE_BT } diff --git a/Firmware/RTK_Everywhere/Buttons.ino b/Firmware/RTK_Everywhere/Buttons.ino index 5b2c22484..3f2b24e45 100644 --- a/Firmware/RTK_Everywhere/Buttons.ino +++ b/Firmware/RTK_Everywhere/Buttons.ino @@ -34,11 +34,6 @@ void powerDown(bool displayInfo) // Prevent other tasks from logging, even if access to the microSD card was denied online.logging = false; - // If we are in configureViaEthernet mode, we need to shut down the async web server - // otherwise it causes a core panic and badness at the restart - if (configureViaEthernet) - ethernetWebServerStopESP32W5500(); - if (displayInfo == true) { displayShutdown(); diff --git a/Firmware/RTK_Everywhere/Cellular.ino b/Firmware/RTK_Everywhere/Cellular.ino index 79ccff3f6..395288606 100644 --- a/Firmware/RTK_Everywhere/Cellular.ino +++ b/Firmware/RTK_Everywhere/Cellular.ino @@ -23,8 +23,6 @@ //---------------------------------------- -static bool cellularSimCardRemoved = false; -static bool cellularSimCardPresent = false; static int cellularRSSI = 0; static bool cellularIsAttached = false; @@ -86,10 +84,10 @@ void cellularEvent(arduino_event_id_t event) String module; // Take the network offline if necessary - if (networkIsInterfaceOnline(NETWORK_CELLULAR) && (event != ARDUINO_EVENT_ETH_GOT_IP) && + if (networkInterfaceHasInternet(NETWORK_CELLULAR) && (event != ARDUINO_EVENT_ETH_GOT_IP) && (event != ARDUINO_EVENT_ETH_GOT_IP6) && (event != ARDUINO_EVENT_PPP_CONNECTED)) { - networkMarkOffline(NETWORK_CELLULAR); + networkInterfaceEventInternetLost(NETWORK_CELLULAR); } // Cellular State Machine @@ -110,7 +108,7 @@ void cellularEvent(arduino_event_id_t event) case ARDUINO_EVENT_PPP_CONNECTED: systemPrintln("Cellular Connected"); - networkMarkOnline(NETWORK_CELLULAR); + networkInterfaceEventInternetAvailable(NETWORK_CELLULAR); break; case ARDUINO_EVENT_PPP_DISCONNECTED: @@ -148,43 +146,19 @@ void cellularSimCheck(NetIndex_t index, uintptr_t parameter, bool debug) { String simCardID; - if (cellularSimCardPresent) - networkSequenceNextEntry(index, debug); - else + simCardID = CELLULAR.cmd("AT+CCID", 500); + if (simCardID.length()) { - simCardID = CELLULAR.cmd("AT+CCID", 500); - if (simCardID.length()) - { - if (debug) - systemPrintf("SIM card %s installed\r\n", simCardID.c_str()); - cellularSimCardPresent = true; - networkSequenceNextEntry(index, debug); - } - else - { - if (debug) - systemPrintf("SIM card not present\r\n"); - cellularSimCardRemoved = true; - networkSequenceExit(index, debug); - } + if (debug) + systemPrintf("SIM card %s installed\r\n", simCardID.c_str()); + networkSequenceNextEntry(index, debug); } -} - -//---------------------------------------- -// Stop if SIM card not installed -void cellularSimInstalled(NetIndex_t index, uintptr_t parameter, bool debug) -{ - // Determine if the SIM card check has run - if (cellularSimCardRemoved) + else { - if (debug) - systemPrintf("SIM card was not present, leaving cellular off\r\n"); + systemPrintf("SIM card not present. Marking cellular offline.\r\n"); + present.cellular_lara = false; networkSequenceExit(index, debug); } - - // Get the next sequence entry - else - networkSequenceNextEntry(index, debug); } //---------------------------------------- diff --git a/Firmware/RTK_Everywhere/Developer.ino b/Firmware/RTK_Everywhere/Developer.ino index 50ed9f0f1..eee6ca673 100644 --- a/Firmware/RTK_Everywhere/Developer.ino +++ b/Firmware/RTK_Everywhere/Developer.ino @@ -10,10 +10,6 @@ //---------------------------------------- void menuEthernet() {systemPrintln("**Ethernet not compiled**");} -void ethernetVerifyTables() {} - -void ethernetWebServerStartESP32W5500() {} -void ethernetWebServerStopESP32W5500() {} bool ntpLogIncreasing = false; @@ -37,8 +33,10 @@ void ntpServerStop() {} void menuTcpUdp() {systemPrint("**Network not compiled**");} void networkBegin() {} +uint8_t networkConsumers() {return(0);} +uint16_t networkGetConsumerTypes() {return(0);} IPAddress networkGetIpAddress() {return("0.0.0.0");} -const uint8_t * networkGetMacAddress() +const uint8_t * networkGetMacAddress() { static const uint8_t zero[6] = {0, 0, 0, 0, 0, 0}; #ifdef COMPILE_BT @@ -47,10 +45,16 @@ const uint8_t * networkGetMacAddress() #endif return zero; } -bool networkIsOnline() {return false;} +bool networkHasInternet() {return false;} +bool networkHasInternet(NetIndex_t index) {return false;} +bool networkInterfaceHasInternet(NetIndex_t index) {return false;} +bool networkIsInterfaceStarted(NetIndex_t index) {return false;} void networkMarkOffline(NetIndex_t index) {} -void networkMarkOnline(NetIndex_t index) {} +void networkMarkHasInternet(NetIndex_t index) {} +void networkSequenceBoot(NetIndex_t index) {} +void networkSequenceNextEntry(NetIndex_t index, bool debug) {} void networkUpdate() {} +void networkValidateIndex(NetIndex_t index) {} void networkVerifyTables() {} //---------------------------------------- @@ -68,7 +72,7 @@ void ntripClientValidateTables() {} void ntripServerPrintStatus(int serverIndex) {systemPrintf("**NTRIP Server %d not compiled**\r\n", serverIndex);} void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) {} -void ntripServerStop(int serverIndex, bool clientAllocated) {online.ntripServer[serverIndex] = false;} +void ntripServerStop(int serverIndex, bool shutdown) {online.ntripServer[serverIndex] = false;} void ntripServerUpdate() {} void ntripServerValidateTables() {} bool ntripServerIsCasting(int serverIndex) { @@ -113,9 +117,10 @@ void discardUdpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET n #ifndef COMPILE_OTA_AUTO void otaAutoUpdate() {} +bool otaNeedsNetwork() {return false;} +void otaUpdate() {} void otaUpdateStop() {} void otaVerifyTables() {} -void otaUpdate() {} #endif // COMPILE_OTA_AUTO @@ -126,6 +131,7 @@ void otaUpdate() {} #ifndef COMPILE_MQTT_CLIENT bool mqttClientIsConnected() {return false;} +bool mqttClientNeedsNetwork() {return false;} void mqttClientPrintStatus() {} void mqttClientRestart() {} void mqttClientUpdate() {} @@ -151,29 +157,61 @@ void httpClientValidateTables() {} #ifndef COMPILE_AP -bool startWebServer(bool startWiFi = true, int httpPort = 80) +bool webServerStart(int httpPort = 80) { systemPrintln("**AP not compiled**"); return false; } -void stopWebServer() {} bool parseIncomingSettings() {return false;} void sendStringToWebsocket(const char* stringToSend) {} +void stopWebServer() {} +bool webServerNeedsNetwork() {return false;} +void webServerStop() {} +void webServerUpdate() {} +void webServerVerifyTables() {} #endif // COMPILE_AP -#ifndef COMPILE_WIFI + +//---------------------------------------- +// ESP-NOW +//---------------------------------------- + +#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;} +void espnowStart() {} +#define ESPNOW_START() false +void espnowStaticPairing() {} +void espnowStop() {} +#define ESPNOW_STOP() true +void updateEspnow() {} + +#endif // COMPILE_ESPNOW //---------------------------------------- // WiFi //---------------------------------------- +#ifndef COMPILE_WIFI + void menuWiFi() {systemPrintln("**WiFi not compiled**");} -bool wifiConnect(unsigned long timeout, bool useAPSTAMode, bool *wasInAPmode) {return false;} +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;} -bool wifiIsRunning() {return false;} -void wifiRestart() {} -void wifiSetApMode() {} +void wifiResetThrottleTimeout() {} +void wifiResetTimeout() {} +#define WIFI_SOFT_AP_RUNNING() {return false;} +bool wifiStart() {return false;} +bool wifiStationIsRunning() {return false;} #define WIFI_STOP() {} +bool wifiUnavailable() {return true;} #endif // COMPILE_WIFI @@ -228,7 +266,7 @@ void updatePPL() {} bool sendGnssToPpl(uint8_t *buffer, int numDataBytes) {return false;} bool sendSpartnToPpl(uint8_t *buffer, int numDataBytes) {return false;} bool sendAuxSpartnToPpl(uint8_t *buffer, int numDataBytes) {return false;} -void pointperfectPrintKeyInformation() {systemPrintln("**PPL Not Compiled**");} +void pointperfectPrintKeyInformation(const char *requestedBy) {systemPrintln("**PPL Not Compiled**");} #endif // COMPILE_POINTPERFECT_LIBRARY diff --git a/Firmware/RTK_Everywhere/Display.ino b/Firmware/RTK_Everywhere/Display.ino index 9df679b35..385d64aad 100644 --- a/Firmware/RTK_Everywhere/Display.ino +++ b/Firmware/RTK_Everywhere/Display.ino @@ -304,6 +304,7 @@ void displayUpdate() setRadioIcons(&iconPropertyList); break; + case (STATE_BASE_CASTER_NOT_STARTED): case (STATE_BASE_NOT_STARTED): // Do nothing. Static display shown during state change. break; @@ -326,6 +327,7 @@ void displayUpdate() displayFullIPAddress(&iconPropertyList); // Bottom left - 128x64 only setRadioIcons(&iconPropertyList); paintBaseTempSurveyStarted(&iconPropertyList); + displaySivVsOpenShort(&iconPropertyList); // 128x64 only break; case (STATE_BASE_TEMP_TRANSMITTING): paintLogging(&iconPropertyList); @@ -333,6 +335,7 @@ void displayUpdate() displayFullIPAddress(&iconPropertyList); // Bottom left - 128x64 only setRadioIcons(&iconPropertyList); paintRTCM(&iconPropertyList); + displaySivVsOpenShort(&iconPropertyList); // 128x64 only break; case (STATE_BASE_FIXED_NOT_STARTED): displayBatteryVsEthernet(&iconPropertyList); // Top right @@ -345,6 +348,7 @@ void displayUpdate() displayFullIPAddress(&iconPropertyList); // Bottom left - 128x64 only setRadioIcons(&iconPropertyList); paintRTCM(&iconPropertyList); + displaySivVsOpenShort(&iconPropertyList); // 128x64 only break; case (STATE_NTPSERVER_NOT_STARTED): @@ -355,7 +359,7 @@ void displayUpdate() iconPropertyBlinking prop; prop.icon = EthernetIconProperties.iconDisplay[present.display_type]; #ifdef COMPILE_ETHERNET - if (networkIsInterfaceOnline(NETWORK_ETHERNET)) + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) prop.duty = 0b11111111; else #endif // COMPILE_ETHERNET @@ -377,7 +381,7 @@ void displayUpdate() iconPropertyBlinking prop; prop.icon = EthernetIconProperties.iconDisplay[present.display_type]; #ifdef COMPILE_ETHERNET - if (networkIsInterfaceOnline(NETWORK_ETHERNET)) + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) prop.duty = 0b11111111; else #endif // COMPILE_ETHERNET @@ -391,28 +395,23 @@ void displayUpdate() } break; - case (STATE_CONFIG_VIA_ETH_NOT_STARTED): - break; - case (STATE_CONFIG_VIA_ETH_STARTED): - break; - case (STATE_CONFIG_VIA_ETH): - displayConfigViaEthernet(); - break; - case (STATE_CONFIG_VIA_ETH_RESTART_BASE): - break; - case (STATE_PROFILE): paintProfile(displayProfile); break; case (STATE_DISPLAY_SETUP): paintDisplaySetup(); break; - case (STATE_WIFI_CONFIG_NOT_STARTED): - displayWiFiConfigNotStarted(); // Display 'WiFi Config' + case (STATE_WEB_CONFIG_NOT_STARTED): + displayWebConfigNotStarted(); // Display 'Web Config' break; - case (STATE_WIFI_CONFIG): - setWiFiIcon(&iconPropertyList); // Blink WiFi in center - displayWiFiConfig(); // Display SSID and IP + case (STATE_WEB_CONFIG): + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) + displayConfigViaEthernet(); + else + { + setWiFiIcon(&iconPropertyList); // Blink WiFi in center + displayConfigViaWiFi(); // Display SSID and IP + } break; case (STATE_TEST): paintSystemTest(); @@ -655,9 +654,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 (wifiIsRunning()) + if (WIFI_IS_RUNNING()) numberOfRadios++; - if (espnowState > ESPNOW_OFF) + if (espnowGetState() > ESPNOW_OFF) numberOfRadios++; // Bluetooth only @@ -672,9 +671,9 @@ void setRadioIcons(std::vector *iconList) setBluetoothIcon_TwoRadios(iconList); // Do we have WiFi or ESP - if (wifiIsRunning()) + if (WIFI_IS_RUNNING()) setWiFiIcon_TwoRadios(iconList); - else if (espnowState > ESPNOW_OFF) + else if (espnowGetState() > ESPNOW_OFF) setESPNowIcon_TwoRadios(iconList); setModeIcon(iconList); // Turn on Rover/Base type icons @@ -693,6 +692,39 @@ 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; + static uint8_t correctionsIconYPos = 48; + // Calculate the highest (lowest!) Y position for the corrections icon + // Do it only once... + if (!correctionsIconPosCalculated) + { + for (int i = 0; i < CORR_NUM; i++) + if ((48 - (correctionIconAttributes[i].yOffset + correctionIconAttributes[i].height)) < + correctionsIconYPos) + correctionsIconYPos = + 48 - (correctionIconAttributes[i].yOffset + correctionIconAttributes[i].height); + correctionsIconPosCalculated = true; + } + + if (inRoverMode() == true) + { + 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; + prop.icon.height = correctionIconAttributes[correctionSource].height; + prop.icon.xPos = correctionsIconXPos + correctionIconAttributes[correctionSource].xOffset; + prop.icon.yPos = correctionsIconYPos + correctionIconAttributes[correctionSource].yOffset; + iconList->push_back(prop); + } + } } else if (present.display_type == DISPLAY_128x64) { @@ -707,7 +739,7 @@ void setRadioIcons(std::vector *iconList) iconList->push_back(prop); } - if (wifiIsRunning()) // WiFi : Columns 34 - 46 + if (WIFI_IS_RUNNING()) // WiFi : Columns 34 - 46 { #ifdef COMPILE_WIFI int wifiRSSI = WiFi.RSSI(); @@ -754,7 +786,7 @@ void setRadioIcons(std::vector *iconList) } #endif // /COMPILE_CELLULAR - if (espnowState == ESPNOW_PAIRED) // ESPNOW : Columns 64 - 71 + if (espnowGetState() == ESPNOW_PAIRED) // ESPNOW : Columns 64 - 71 { iconPropertyBlinking prop; prop.duty = 0b11111111; @@ -793,7 +825,7 @@ void setRadioIcons(std::vector *iconList) } } - if (espnowState == ESPNOW_PAIRED) + if (espnowGetState() == ESPNOW_PAIRED) { if (espnowIncomingRTCM == true) // Download : Columns 74 - 81 { @@ -823,24 +855,24 @@ void setRadioIcons(std::vector *iconList) usbSerialIncomingRtcm = false; } - bool networkOnline = false; + bool networkHasInternet = false; #ifdef COMPILE_ETHERNET - if (networkIsInterfaceOnline(NETWORK_ETHERNET)) - networkOnline = true; + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) + networkHasInternet = true; #endif // COMPILE_ETHERNET #ifdef COMPILE_WIFI - if (networkIsInterfaceOnline(NETWORK_WIFI)) - networkOnline = true; + if (networkInterfaceHasInternet(NETWORK_WIFI)) + networkHasInternet = true; #endif // COMPILE_WIFI #ifdef COMPILE_CELLULAR - if (networkIsInterfaceOnline(NETWORK_CELLULAR)) - networkOnline = true; + if (networkInterfaceHasInternet(NETWORK_CELLULAR)) + networkHasInternet = true; #endif // COMPILE_CELLULAR - if (networkOnline) + if (networkHasInternet) { if (netIncomingRTCM == true) // Download : Columns 74 - 81 { @@ -902,16 +934,18 @@ void setRadioIcons(std::vector *iconList) break; } - // Put the corrections source icon on the bottom, right of the IP address + // On 128x64: put the corrections source icon on the bottom, right of the IP address static bool correctionsIconPosCalculated = false; - const uint8_t correctionsIconXPos128x64 = 96; - static uint8_t correctionsIconYPos128x64 = 64; + const uint8_t correctionsIconXPos = 96; + static uint8_t correctionsIconYPos = 64; + // Calculate the highest (lowest!) Y position for the corrections icon + // Do it only once... if (!correctionsIconPosCalculated) { for (int i = 0; i < CORR_NUM; i++) if ((64 - (correctionIconAttributes[i].yOffset + correctionIconAttributes[i].height)) < - correctionsIconYPos128x64) - correctionsIconYPos128x64 = + correctionsIconYPos) + correctionsIconYPos = 64 - (correctionIconAttributes[i].yOffset + correctionIconAttributes[i].height); correctionsIconPosCalculated = true; } @@ -926,8 +960,8 @@ void setRadioIcons(std::vector *iconList) prop.icon.bitmap = correctionIconAttributes[correctionSource].pointer; prop.icon.width = correctionIconAttributes[correctionSource].width; prop.icon.height = correctionIconAttributes[correctionSource].height; - prop.icon.xPos = correctionsIconXPos128x64 + correctionIconAttributes[correctionSource].xOffset; - prop.icon.yPos = correctionsIconYPos128x64 + correctionIconAttributes[correctionSource].yOffset; + prop.icon.xPos = correctionsIconXPos + correctionIconAttributes[correctionSource].xOffset; + prop.icon.yPos = correctionsIconYPos + correctionIconAttributes[correctionSource].yOffset; iconList->push_back(prop); } } @@ -1024,7 +1058,7 @@ void setBluetoothIcon_TwoRadios(std::vector *iconList) // This is 64x48-specific void setESPNowIcon_TwoRadios(std::vector *iconList) { - if (espnowState == ESPNOW_PAIRED) + if (espnowGetState() == ESPNOW_PAIRED) { if (espnowIncomingRTCM == true || espnowOutgoingRTCM == true) { @@ -1039,7 +1073,7 @@ void setESPNowIcon_TwoRadios(std::vector *iconList) prop.icon = ESPNowSymbol1Left64x48; else // if (espnowRSSI > -255) prop.icon = - ESPNowSymbol0Left64x48; // Always show the synbol because we've got incoming or outgoing data + 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 @@ -1100,7 +1134,7 @@ void setESPNowIcon_TwoRadios(std::vector *iconList) void setWiFiIcon_TwoRadios(std::vector *iconList) { #ifdef COMPILE_WIFI - if (networkIsInterfaceOnline(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI)) { if (netIncomingRTCM || netOutgoingRTCM || mqttClientDataReceived) { @@ -1170,7 +1204,7 @@ void setWiFiIcon_TwoRadios(std::vector *iconList) void setWiFiIcon_ThreeRadios(std::vector *iconList) { #ifdef COMPILE_WIFI - if (networkIsInterfaceOnline(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI)) { if (netIncomingRTCM || netOutgoingRTCM || mqttClientDataReceived) { @@ -1249,7 +1283,7 @@ void setWiFiIcon(std::vector *iconList) icon.icon.yPos = 0; #ifdef COMPILE_WIFI - if (networkIsInterfaceOnline(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI)) icon.duty = 0b11111111; else #endif // COMPILE_WIFI @@ -1279,6 +1313,7 @@ void setModeIcon(std::vector *iconList) paintDynamicModel(iconList); break; + case (STATE_BASE_CASTER_NOT_STARTED): case (STATE_BASE_NOT_STARTED): // Do nothing. Static display shown during state change. break; @@ -1472,58 +1507,114 @@ void paintClockAccuracy(displayCoords textCoords) // Draw the rover icon depending on screen void paintDynamicModel(std::vector *iconList) { - if (online.gnss == true) + if (present.dynamicModel && online.gnss) { iconPropertyBlinking prop; + prop.icon.bitmap = nullptr; // Use this as the test a valid icon prop.duty = 0b11111111; - // Display icon associated with current Dynamic Model - switch (settings.dynamicModel) + if (present.gnss_zedf9p) { - default: - break; - #ifdef COMPILE_ZED - case (DYN_MODEL_PORTABLE): - prop.icon = DynamicModel_1_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_STATIONARY): - prop.icon = DynamicModel_2_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_PEDESTRIAN): - prop.icon = DynamicModel_3_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_AUTOMOTIVE): - prop.icon = DynamicModel_4_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_SEA): - prop.icon = DynamicModel_5_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_AIRBORNE1g): - prop.icon = DynamicModel_6_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_AIRBORNE2g): - prop.icon = DynamicModel_7_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_AIRBORNE4g): - prop.icon = DynamicModel_8_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_WRIST): - prop.icon = DynamicModel_9_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_BIKE): - prop.icon = DynamicModel_10_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_MOWER): - prop.icon = DynamicModel_11_Properties.iconDisplay[present.display_type]; - break; - case (DYN_MODEL_ESCOOTER): - prop.icon = DynamicModel_12_Properties.iconDisplay[present.display_type]; - break; + // Display icon associated with current Dynamic Model + switch (settings.dynamicModel) + { + default: + break; + + case (DYN_MODEL_PORTABLE): // 0 + prop.icon = DynamicModel_1_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_STATIONARY): // 2 + prop.icon = DynamicModel_2_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_PEDESTRIAN): + prop.icon = DynamicModel_3_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_AUTOMOTIVE): + prop.icon = DynamicModel_4_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_SEA): + prop.icon = DynamicModel_5_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_AIRBORNE1g): + prop.icon = DynamicModel_6_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_AIRBORNE2g): + prop.icon = DynamicModel_7_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_AIRBORNE4g): + prop.icon = DynamicModel_8_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_WRIST): + prop.icon = DynamicModel_9_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_BIKE): + prop.icon = DynamicModel_10_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_MOWER): + prop.icon = DynamicModel_11_Properties.iconDisplay[present.display_type]; + break; + case (DYN_MODEL_ESCOOTER): + prop.icon = DynamicModel_12_Properties.iconDisplay[present.display_type]; + break; + } #endif // COMPILE_ZED } + else if (present.gnss_um980) + { +#ifdef COMPILE_UM980 + // Display icon associated with current Dynamic Model + switch (settings.dynamicModel) + { + default: + break; - iconList->push_back(prop); + case UM980_DYN_MODEL_SURVEY: + prop.icon = DynamicModel_2_Properties.iconDisplay[present.display_type]; // Stationary + break; + case UM980_DYN_MODEL_UAV: + prop.icon = DynamicModel_6_Properties.iconDisplay[present.display_type]; // Airborne1g + break; + case UM980_DYN_MODEL_AUTOMOTIVE: + prop.icon = DynamicModel_4_Properties.iconDisplay[present.display_type]; // Automotive + break; + } +#endif // COMPILE_UM980 + } + else if (present.gnss_mosaicX5) + { +#ifdef COMPILE_MOSAICX5 + // Display icon associated with current Dynamic Model + switch (settings.dynamicModel) + { + default: + break; + + case MOSAIC_DYN_MODEL_STATIC: + case MOSAIC_DYN_MODEL_QUASISTATIC: + prop.icon = DynamicModel_2_Properties.iconDisplay[present.display_type]; // Stationary + break; + case MOSAIC_DYN_MODEL_PEDESTRIAN: + prop.icon = DynamicModel_3_Properties.iconDisplay[present.display_type]; // Pedestrian + break; + case MOSAIC_DYN_MODEL_AUTOMOTIVE: + case MOSAIC_DYN_MODEL_RACECAR: + case MOSAIC_DYN_MODEL_HEAVYMACHINERY: + prop.icon = DynamicModel_4_Properties.iconDisplay[present.display_type]; // Automotive + break; + case MOSAIC_DYN_MODEL_UAV: + prop.icon = DynamicModel_6_Properties.iconDisplay[present.display_type]; // Airborne1g + break; + case MOSAIC_DYN_MODEL_UNLIMITED: + prop.icon = DynamicModel_8_Properties.iconDisplay[present.display_type]; // Airborne4g + break; + } +#endif // COMPILE_MOSAICX5 + } + + if (prop.icon.bitmap) + iconList->push_back(prop); } } @@ -1534,7 +1625,7 @@ void displayBatteryVsEthernet(std::vector *iconList) #ifdef COMPILE_ETHERNET else // if (present.ethernet_ws5500 == true) { - if (!networkIsInterfaceOnline(NETWORK_ETHERNET)) + if (!networkInterfaceHasInternet(NETWORK_ETHERNET)) return; // Only display the Ethernet icon if we are successfully connected (no blinking) iconPropertyBlinking prop; @@ -1589,36 +1680,11 @@ void displayHorizontalAccuracy(std::vector *iconList, cons void displayRTKAccuracy(std::vector *iconList, const iconProperties *icon, bool fixed) { - CORRECTION_ID_T correctionSource = correctionGetSource(); - iconPropertyBlinking prop; - if ((present.display_type == DISPLAY_128x64) || (correctionSource == CORR_NUM)) - { - prop.icon = icon->iconDisplay[present.display_type]; - prop.duty = fixed ? 0b11111111 : 0b01010101; - iconList->push_back(prop); - } - else - { - // Display the icon (dual crosshair) for 6/8 intervals - prop.icon = icon->iconDisplay[present.display_type]; - prop.duty = fixed ? 0b00111111 : 0b00010101; - iconList->push_back(prop); - - // Display the correction source icon for 2/8 intervals - prop.icon.bitmap = correctionIconAttributes[correctionSource].pointer; - prop.icon.width = correctionIconAttributes[correctionSource].width; - prop.icon.height = correctionIconAttributes[correctionSource].height; - prop.icon.xPos += correctionIconAttributes[correctionSource].xOffset; - prop.icon.yPos += correctionIconAttributes[correctionSource].yOffset; - prop.duty = fixed ? 0b11000000 : 0b01000000; - iconList->push_back(prop); - - // Restore the position for textCoords - prop.icon.xPos -= correctionIconAttributes[correctionSource].xOffset; - prop.icon.yPos -= correctionIconAttributes[correctionSource].yOffset; - } + prop.icon = icon->iconDisplay[present.display_type]; + prop.duty = fixed ? 0b11111111 : 0b01010101; + iconList->push_back(prop); displayCoords textCoords; textCoords.x = prop.icon.xPos + 16; @@ -1658,10 +1724,24 @@ displayCoords paintSIVIcon(std::vector *iconList, const ic if (lbandCorrectionsReceived || spartnCorrectionsReceived) icon = &LBandIconProperties; else - icon = &SIVIconProperties; + { + if (inBaseMode() == false) + icon = &SIVIconProperties; + else if (systemState == STATE_BASE_TEMP_SETTLE) + icon = &SIVIconProperties; //During base settle, SIV inline with HPA + else if (inBaseMode() && present.display_type == DISPLAY_128x64) + icon = &BaseSIVIconProperties; // Move SIV down to avoid collision with 'Xmitting RTCM' text + } + + // if in base mode, don't blink + if (inBaseMode() == true) + { + // override duty - solid satellite dish icon regardless of fix state + duty = 0b11111111; + } // Determine if there is a fix - if (gnss->isFixed() == false) + else if (gnss->isFixed() == false) { // override duty - blink satellite dish icon if we don't have a fix duty = 0b01010101; @@ -1684,18 +1764,57 @@ displayCoords paintSIVIcon(std::vector *iconList, const ic return textCoords; } + +void nudgeAndPrintSIV(displayCoords textCoords, uint8_t siv) +{ + if (present.display_type == DISPLAY_64x48) + { + // Always nudge, even if 1 digit, to avoid small jump when + // siv goes into 2 digits + oled->setCursor(textCoords.x - 1, textCoords.y); // x, y + if (siv >= 10) + { + oled->print(siv / 10); + oled->setCursor(textCoords.x + 7, textCoords.y); // x, y + oled->print(siv % 10); + } + else + { + oled->print(siv); // Left-justify 1 digit + } + } + else + { + // On 128x64, there's no need to nudge + oled->setCursor(textCoords.x, textCoords.y); // x, y + oled->print(siv); // 1 or 2 digits + } +} + void paintSIVText(displayCoords textCoords) { oled->setFont(QW_FONT_8X16); // Set font to type 1: 8x16 oled->setCursor(textCoords.x, textCoords.y); // x, y - oled->print(":"); + + uint8_t siv = gnss->getSatellitesInView(); + if (siv > 99) + { + oled->print(">"); + siv = 99; // Limit SIV to two digits + } + else + oled->print(":"); + + textCoords.x += 8; if (online.gnss) { - if (gnss->isFixed() == false) - oled->print("0"); + if (inBaseMode() == true) + nudgeAndPrintSIV(textCoords, siv); + else if (gnss->isFixed() == false) + nudgeAndPrintSIV(textCoords, 0); else - oled->print(gnss->getSatellitesInView()); + nudgeAndPrintSIV(textCoords, siv); paintResets(); } // End gnss online @@ -1734,6 +1853,7 @@ void paintLogging(std::vector *iconList, bool pulse, bool loggingIconDisplayed %= LOGGING_ICON_STATES; // Wrap iconPropertyBlinking prop; + prop.icon.bitmap = nullptr; prop.duty = 0b11111111; if (((online.logging == true) && (logIncreasing || ntpLogIncreasing)) || (present.gnss_mosaicX5 && logIncreasing)) @@ -1754,14 +1874,15 @@ void paintLogging(std::vector *iconList, bool pulse, bool { prop.icon = LoggingCustomIconProperties.iconDisplay[loggingIconDisplayed][present.display_type]; } - - iconList->push_back(prop); + // Could be LOGGING_UNKNOWN } else if (pulse) { prop.icon = PulseIconProperties.iconDisplay[loggingIconDisplayed][present.display_type]; - iconList->push_back(prop); } + + if (prop.icon.bitmap) + iconList->push_back(prop); } // Survey in is running. Show 3D Mean and elapsed time. @@ -1840,9 +1961,11 @@ void paintRTCM(std::vector *iconList) uint8_t yPos = CrossHairProperties.iconDisplay[present.display_type].yPos; if (present.display_type == DISPLAY_64x48) - yPos = yPos - 1; // Move text up by 1 pixel on 64x48. Note: this is brittle. TODO: find a better solution + yPos = yPos - 1; // Move text up by 1 pixel on 64x48. Note: this is brittle. - if (casting) + if(settings.baseCasterOverride == true) + printTextAt("BaseCast", xPos + 4, yPos, QW_FONT_8X16, 1); // text, y, font type, kerning + else if (casting) printTextAt("Casting", xPos + 4, yPos, QW_FONT_8X16, 1); // text, y, font type, kerning else printTextAt("Xmitting", xPos, yPos, QW_FONT_8X16, 1); // text, y, font type, kerning @@ -1994,9 +2117,13 @@ void displayBaseStart(uint16_t displayTime) oled->erase(); uint8_t fontHeight = 15; // Assume fontsize 1 - uint8_t yPos = oled->getHeight() / 2 - fontHeight; + uint8_t yPos = oled->getHeight() / 2 - fontHeight + 1; + + if (settings.baseCasterOverride == true) + printTextCenter("BaseCast", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted + else + printTextCenter("Base", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - printTextCenter("Base", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted oled->display(); oled->display(); @@ -2007,12 +2134,18 @@ void displayBaseStart(uint16_t displayTime) void displayBaseSuccess(uint16_t displayTime) { - displayMessage("Base Started", displayTime); + if (settings.baseCasterOverride == true) + displayMessage("BaseCast Started", displayTime); + else + displayMessage("Base Started", displayTime); } void displayBaseFail(uint16_t displayTime) { - displayMessage("Base Failed", displayTime); + if (settings.baseCasterOverride == true) + displayMessage("BaseCast Failed", displayTime); + else + displayMessage("Base Failed", displayTime); } void displayGNSSFail(uint16_t displayTime) @@ -2165,118 +2298,8 @@ void displayWiFiConnect() displayMessage("WiFi Connect", 0); } -// When user enters WiFi Config mode from setup, show splash while config happens -void displayWiFiConfigNotStarted() -{ - displayMessage("WiFi Config", 0); -} - -void displayWiFiConfig() -{ - int yPos = WiFi_Symbol_Height + 2; - int fontHeight = 8; - - // 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; - - printTextCenter("SSID:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted - - yPos = yPos + fontHeight + 1; - - // 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 - if (millis() - ssidDisplayTimer > 2000) - { - ssidDisplayTimer = millis(); - ssidDisplayFirstHalf = !ssidDisplayFirstHalf; - } - - // Convert current SSID to string - char mySSID[50] = {'\0'}; - -#ifdef COMPILE_WIFI - if (settings.wifiConfigOverAP == true) - snprintf(mySSID, sizeof(mySSID), "%s", "RTK Config"); - else - { - 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", "RTK Config"); - - // If we are in AP+STA mode, still display RTK Config - else if (WiFi.getMode() == WIFI_AP_STA) - snprintf(mySSID, sizeof(mySSID), "%s", "RTK Config"); - - else - snprintf(mySSID, sizeof(mySSID), "%s", "Error"); - } -#else // COMPILE_WIFI - snprintf(mySSID, sizeof(mySSID), "%s", "!Compiled"); -#endif // COMPILE_WIFI - - char mySSIDFront[displayMaxCharacters + 1]; // 1 for null terminator - char mySSIDBack[displayMaxCharacters + 1]; // 1 for null terminator - - // 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); - - mySSIDFront[displayMaxCharacters] = '\0'; - mySSIDBack[displayMaxCharacters] = '\0'; - - if (ssidDisplayFirstHalf) - printTextCenter(mySSIDFront, yPos, QW_FONT_5X7, 1, false); - else - printTextCenter(mySSIDBack, yPos, QW_FONT_5X7, 1, false); - - yPos = yPos + fontHeight + 3; - 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 -} - // When user does a factory reset, let us know -void displaySytemReset() +void displaySystemReset() { displayMessage("Factory Reset", 0); } @@ -2419,8 +2442,8 @@ void paintProfile(uint8_t profileUnit) if (profileNumber >= 0) { - settings.updateGNSSSettings = - true; // When this profile is loaded next, force system to update GNSS settings. + settings.gnssConfiguredBase = false; // On the next boot, reapply all settings + settings.gnssConfiguredRover = false; recordSystemSettings(); // Before switching, we need to record the current settings to LittleFS and SD recordProfileNumber( @@ -2469,15 +2492,31 @@ void paintSystemTest() drawFrame(); // Outside edge oled->setFont(QW_FONT_5X7); // Set font to smallest - oled->setCursor(xOffset, yOffset); // x, y - oled->print("SD:"); - if (online.microSD == false) - beginSD(); // Test if SD is present - if (online.microSD == true) - oled->print("OK"); - else - oled->print("FAIL"); + if (present.microSd) + { + oled->setCursor(xOffset, yOffset); // x, y + oled->print("SD:"); + + if (online.microSD == false) + beginSD(); // Test if SD is present + if (online.microSD == true) + oled->print("OK"); + else + oled->print("FAIL"); + } + else if (present.gnss_mosaicX5) + { + // Facet mosaic has an SD card, but it is connected directly to the mosaic-X5 + // Calling gnss->update() during the GNSS check will cause sdCardSize to be updated + oled->setCursor(xOffset, yOffset); // x, y + oled->print("SD:"); + + if (sdCardSize > 0) + oled->print("OK"); + else + oled->print("FAIL"); + } if (present.fuelgauge_max17048 || present.fuelgauge_bq40z50) { @@ -2520,10 +2559,10 @@ void paintSystemTest() pinMode(pin_muxADC, INPUT_PULLUP); digitalWrite(pin_muxDAC, HIGH); - if (digitalRead(pin_muxADC) == HIGH) + if (readAnalogPinAsDigital(pin_muxADC) == HIGH) { digitalWrite(pin_muxDAC, LOW); - if (digitalRead(pin_muxADC) == LOW) + if (readAnalogPinAsDigital(pin_muxADC) == LOW) oled->print("OK"); else oled->print("FAIL"); @@ -2592,14 +2631,14 @@ void paintSystemTest() oled->print(" "); oled->print(gnssFirmwareVersionInt); oled->print("-"); - if ((present.gnss_zedf9p) && (gnssFirmwareVersionInt < 130)) + if ((present.gnss_zedf9p) && (gnssFirmwareVersionInt < 150)) oled->print("FAIL"); else oled->print("OK"); oled->setCursor(xOffset, yOffset + (2 * charHeight)); // x, y oled->print("LBand:"); - if (online.lband_neo == true) + if ((online.lband_neo == true) || (present.gnss_mosaicX5)) oled->print("OK"); else oled->print("FAIL"); @@ -3104,72 +3143,114 @@ void displayNTPFail(uint16_t displayTime) } } -void displayConfigViaEthStarting(uint16_t displayTime) +// When user enters Web Config mode, show splash while web server starts +void displayWebConfigNotStarted() { - if (online.display == true) - { - oled->erase(); + displayMessage("Web Config", 0); +} - uint8_t fontHeight = 12; - uint8_t yPos = fontHeight; +void displayConfigViaWiFi() +{ + int yPos = WiFi_Symbol_Height + 2; + int fontHeight = 8; - printTextCenter("Configure", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Via", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Ethernet", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Starting", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted + // 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; - oled->display(); + printTextCenter("SSID:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted - delay(displayTime); - } -} -void displayConfigViaEthExiting(uint16_t displayTime) -{ - if (online.display == true) + yPos = yPos + fontHeight + 1; + + // 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 + if (millis() - ssidDisplayTimer > 2000) { - oled->erase(); + ssidDisplayTimer = millis(); + ssidDisplayFirstHalf = !ssidDisplayFirstHalf; + } - uint8_t fontHeight = 12; - uint8_t yPos = fontHeight; + // Convert current SSID to string + char mySSID[50] = {'\0'}; - printTextCenter("Configure", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Via", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Ethernet", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Exiting", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted +#ifdef COMPILE_WIFI + if (settings.wifiConfigOverAP == true) + snprintf(mySSID, sizeof(mySSID), "%s", WiFi.softAPSSID().c_str()); + else + { + if (WiFi.getMode() == WIFI_STA) + snprintf(mySSID, sizeof(mySSID), "%s", WiFi.SSID().c_str()); - oled->display(); + // 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()); - delay(displayTime); + // 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"); } -} +#else // COMPILE_WIFI + snprintf(mySSID, sizeof(mySSID), "%s", "!Compiled"); +#endif // COMPILE_WIFI -void displayConfigViaEthStarted(uint16_t displayTime) -{ - if (online.display == true) - { - oled->erase(); + char mySSIDFront[displayMaxCharacters + 1]; // 1 for null terminator + char mySSIDBack[displayMaxCharacters + 1]; // 1 for null terminator - uint8_t fontHeight = 12; - uint8_t yPos = fontHeight; + // Trim SSID to a max length + strncpy(mySSIDFront, mySSID, displayMaxCharacters); - printTextCenter("Configure", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Via", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Ethernet", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted - yPos += fontHeight; - printTextCenter("Started", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted + if (strlen(mySSID) > displayMaxCharacters) + strncpy(mySSIDBack, mySSID + (strlen(mySSID) - displayMaxCharacters), displayMaxCharacters); + else + strncpy(mySSIDBack, mySSID, displayMaxCharacters); - oled->display(); + mySSIDFront[displayMaxCharacters] = '\0'; + mySSIDBack[displayMaxCharacters] = '\0'; - delay(displayTime); - } + if (ssidDisplayFirstHalf) + printTextCenter(mySSIDFront, yPos, QW_FONT_5X7, 1, false); + else + printTextCenter(mySSIDBack, yPos, QW_FONT_5X7, 1, false); + + yPos = yPos + fontHeight + 3; + 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() diff --git a/Firmware/RTK_Everywhere/ESPNOW.ino b/Firmware/RTK_Everywhere/ESPNOW.ino index 51de217d5..e09bf0dc0 100644 --- a/Firmware/RTK_Everywhere/ESPNOW.ino +++ b/Firmware/RTK_Everywhere/ESPNOW.ino @@ -1,3 +1,469 @@ +/********************************************************************** + ESPNOW.ino + + Handle the ESP-NOW events + 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. +**********************************************************************/ + +#ifdef COMPILE_ESPNOW + +//**************************************** +// Constants +//**************************************** + +const uint8_t espnowBroadcastAddr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +//**************************************** +// Types +//**************************************** + +// Create a struct for ESP NOW pairing +typedef struct _ESP_NOW_PAIR_MESSAGE +{ + uint8_t macAddress[6]; + bool encrypt; + uint8_t channel; + uint8_t crc; // Simple check - add MAC together and limit to 8 bit +} ESP_NOW_PAIR_MESSAGE; + +//**************************************** +// Locals +//**************************************** + +ESPNOWState espNowState; + +//********************************************************************* +// Add a peer to the ESP-NOW network +esp_err_t espNowAddPeer(const uint8_t * peerMac) +{ + esp_now_peer_info_t peerInfo; + + // Describe the peer + memcpy(peerInfo.peer_addr, peerMac, 6); + peerInfo.channel = 0; + peerInfo.ifidx = WIFI_IF_STA; + peerInfo.encrypt = false; + + // Add the peer + if (settings.debugEspNow) + systemPrintf("Calling esp_now_add_peer\r\n"); + 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); + } + 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]); + return result; +} + +//********************************************************************* +// Get the current ESP-NOW state +ESPNOWState espnowGetState() +{ + return espNowState; +} + +//---------------------------------------------------------------------- +// 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 +//---------------------------------------------------------------------- + +//********************************************************************* +// Update the state of the ESP Now state machine +// +// +--------------------+ +// | ESPNOW_OFF | +// +--------------------+ +// | | +// | | No pairs listed +// | V +// | +---------------------+ +// | | ESPNOW_BROADCASTING | +// | +---------------------+ +// | | +// | | +// | V +// | +--------------------+ +// | | ESPNOW_PAIRING | +// | +--------------------+ +// | | +// | | +// | V +// | +--------------------+ +// | | ESPNOW_MAC_RECEIVED | +// | +--------------------+ +// | | +// | | +// | V +// | +--------------------+ +// '->| ESPNOW_PAIRED | +// +--------------------+ +// +// Send RTCM in either ESPNOW_BROADCASTING or ESPNOW_PAIRED state. +// Receive RTCM in ESPNOW_BROADCASTING, ESPNOW_MAC_RECEIVED and +// ESPNOW_PAIRED states. +//********************************************************************* + +//********************************************************************* +// Callback when data is received +void espNowRxHandler(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; + + uint8_t receivedMAC[6]; + + if (espNowState == ESPNOW_PAIRING) + { + ESP_NOW_PAIR_MESSAGE pairMessage; + if (len == sizeof(pairMessage)) // First error check + { + memcpy(&pairMessage, incomingData, len); + + // 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 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); +} + +//********************************************************************* +// Update the state of the ESP-NOW subsystem +void espNowSetState(ESPNOWState newState) +{ + const char * name[] = + { + "ESPNOW_OFF", + "ESPNOW_BROADCASTING", + "ESPNOW_PAIRING", + "ESPNOW_MAC_RECEIVED", + "ESPNOW_PAIRED", + }; + const int nameCount = sizeof(name) / sizeof(name[0]); + const char * newName; + char nLine[80]; + const char * oldName; + char oLine[80]; + + if (settings.debugEspNow == true) + { + // Get the old state name + if (espNowState < ESPNOW_MAX) + oldName = name[espNowState]; + else + { + sprintf(oLine, "Unknown state: %d", espNowState); + oldName = &oLine[0]; + } + + // Get the new state name + if (newState < ESPNOW_MAX) + newName = name[newState]; + else + { + sprintf(nLine, "Unknown state: %d", newState); + newName = &nLine[0]; + } + + // Display the state change + if (newState == espNowState) + systemPrintf("ESP-NOW: *%s\r\n", newName); + else + systemPrintf("ESP-NOW: %s --> %s\r\n", oldName, newName); + } + espNowState = newState; +} + +//********************************************************************* +// Start ESP-NOW layer +bool espNowStart() +{ + int index; + bool started; + esp_err_t status; + + do + { + started = false; + + // 5. Call esp_now_init + if (settings.debugEspNow) + systemPrintf("Calling esp_now_init\r\n"); + status = esp_now_init(); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to initialize ESP-NOW, status: %d\r\n", status); + break; + } + + // 9. Set receive callback [esp_now_register_recv_cb(espnowOnDataReceived)] + if (settings.debugEspNow) + systemPrintf("Calling esp_now_register_recv_cb\r\n"); + status = esp_now_register_recv_cb(espNowRxHandler); + 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++) + { + // 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) + { + // 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) + { + 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; + } + } + + // Determine if an error occurred + if (index < ESPNOW_MAX_PEERS) + break; + } + + // ESP-NOW has started successfully + if (settings.debugEspNow) + systemPrintf("ESP-NOW online\r\n"); + + started = true; + } while (0); + + return started; +} + +//********************************************************************* +// Stop ESP-NOW layer +bool espNowStop() +{ + uint8_t mode; + esp_now_peer_num_t peerCount; + uint8_t primaryChannel; + uint8_t protocols; + wifi_second_chan_t secondaryChannel; + bool stopped; + esp_err_t status; + + do + { + stopped = false; + + // 3. esp_now_unregister_recv_cb() + if (settings.debugEspNow) + systemPrintf("Calling esp_now_unregister_recv_cb\r\n"); + status = esp_now_unregister_recv_cb(); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to clear ESP_NOW RX callback, status: %d\r\n", status); + break; + } + if (settings.debugEspNow) + systemPrintf("ESP-NOW: RX callback removed\r\n"); + + if (settings.debugEspNow) + systemPrintf("ESP-NOW offline\r\n"); + + // 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 (settings.debugEspNow) + systemPrintf("Calling esp_now_del_peer\r\n"); + status = esp_now_del_peer(espnowBroadcastAddr); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to delete broadcast peer, status: %d\r\n", status); + break; + } + if (settings.debugEspNow) + systemPrintf("ESP-NOW removed broadcast peer\r\n"); + } + + // Walk the unicast peers + while (1) + { + esp_now_peer_info_t peerInfo; + + // Get the next unicast peer + if (settings.debugEspNow) + systemPrintf("Calling esp_now_fetch_peer\r\n"); + status = esp_now_fetch_peer(true, &peerInfo); + if (status != ESP_OK) + break; + + // Remove the unicast peer + if (settings.debugEspNow) + systemPrintf("Calling esp_now_del_peer\r\n"); + status = esp_now_del_peer(peerInfo.peer_addr); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to delete peer %02x:%02x:%02x:%02x:%02x:%02x, status: %d\r\n", + peerInfo.peer_addr[0], peerInfo.peer_addr[1], + peerInfo.peer_addr[2], peerInfo.peer_addr[3], + peerInfo.peer_addr[4], peerInfo.peer_addr[5], + status); + break; + } + if (settings.debugEspNow) + systemPrintf("ESP-NOW removed peer %02x:%02x:%02x:%02x:%02x:%02x\r\n", + peerInfo.peer_addr[0], peerInfo.peer_addr[1], + peerInfo.peer_addr[2], peerInfo.peer_addr[3], + peerInfo.peer_addr[4], peerInfo.peer_addr[5]); + } + if (status != ESP_ERR_ESPNOW_NOT_FOUND) + { + systemPrintf("ERROR: Failed to get peer info for deletion, status: %d\r\n", status); + break; + } + + // Get the number of peers + if (settings.debugEspNow) + { + systemPrintf("Calling esp_now_get_peer_num\r\n"); + status = esp_now_get_peer_num(&peerCount); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to get the peer count, status: %d\r\n", status); + break; + } + systemPrintf("peerCount: %d\r\n", peerCount); + } + + // Stop the ESP-NOW state machine + espNowSetState(ESPNOW_OFF); + + // 9. Turn off ESP-NOW. call esp_now_deinit + if (settings.debugEspNow) + systemPrintf("Calling esp_now_deinit\r\n"); + status = esp_now_deinit(); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to deinit ESP-NOW, status: %d\r\n", status); + break; + } + + // 11. Restart WiFi if necessary + + // ESP-NOW has stopped successfully + if (settings.debugEspNow) + systemPrintf("ESP-NOW stopped\r\n"); + stopped = true; + } while (0); + + // Return the stopped status + return stopped; +} + /* Use ESP NOW protocol to transmit RTCM between RTK Products via 2.4GHz @@ -18,18 +484,17 @@ // Use the ESP32 to directly transmit/receive RTCM over 2.4GHz (no WiFi needed) void updateEspnow() { -#ifdef COMPILE_ESPNOW if (settings.enableEspNow == true) { - if (espnowState == ESPNOW_PAIRED || espnowState == ESPNOW_BROADCASTING) + if (espNowState == ESPNOW_PAIRED || espNowState == ESPNOW_BROADCASTING) { // 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 (espnowState == ESPNOW_PAIRED) + if (espNowState == ESPNOW_PAIRED) esp_now_send(0, (uint8_t *)&espnowOutgoing, espnowOutgoingSpot); // Send partial packet to all peers - else // if (espnowState == ESPNOW_BROADCASTING) + else // if (espNowState == ESPNOW_BROADCASTING) { uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; esp_now_send(broadcastMac, (uint8_t *)&espnowOutgoing, @@ -51,43 +516,26 @@ void updateEspnow() espnowRSSI = -255; } } -#endif // COMPILE_ESPNOW } -// Create a struct for ESP NOW pairing -typedef struct PairMessage -{ - uint8_t macAddress[6]; - bool encrypt; - uint8_t channel; - uint8_t crc; // Simple check - add MAC together and limit to 8 bit -} PairMessage; - // Callback when data is sent -#ifdef COMPILE_ESPNOW void espnowOnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { // systemPrint("Last Packet Send Status: "); - // if (status == ESP_NOW_SEND_SUCCESS) + // if (status == ESPNOW_SEND_SUCCESS) // systemPrintln("Delivery Success"); // else // systemPrintln("Delivery Fail"); } -#endif // COMPILE_ESPNOW - -#ifndef COMPILE_ESPNOW -#define esp_now_recv_info uint8_t -#endif // Callback when data is received void espnowOnDataReceived(const esp_now_recv_info *mac, const uint8_t *incomingData, int len) { -#ifdef COMPILE_ESPNOW - if (espnowState == ESPNOW_PAIRING) + if (espNowState == ESPNOW_PAIRING) { - if (len == sizeof(PairMessage)) // First error check + if (len == sizeof(ESP_NOW_PAIR_MESSAGE)) // First error check { - PairMessage pairMessage; + ESP_NOW_PAIR_MESSAGE pairMessage; memcpy(&pairMessage, incomingData, sizeof(pairMessage)); // Check CRC @@ -127,7 +575,6 @@ void espnowOnDataReceived(const esp_now_recv_info *mac, const uint8_t *incomingD espnowIncomingRTCM = true; // Display a download icon lastEspnowRssiUpdate = millis(); } -#endif // COMPILE_ESPNOW } // Callback for all RX Packets @@ -154,8 +601,6 @@ void espnowStart() return; } -#ifdef COMPILE_ESPNOW - // Before we can issue esp_wifi_() commands WiFi must be started if (WiFi.getMode() != WIFI_STA) WiFi.mode(WIFI_STA); @@ -166,7 +611,7 @@ void espnowStart() if (response != ESP_OK) systemPrintf("espnowStart: Failed to get protocols: %s\r\n", esp_err_to_name(response)); - if ((!wifiIsRunning()) && espnowState == ESPNOW_OFF) + if ((WIFI_IS_RUNNING() == false) && espNowState == ESPNOW_OFF) { // Radio is off, turn it on if (protocols != (WIFI_PROTOCOL_LR)) @@ -187,7 +632,7 @@ void espnowStart() } } // If WiFi is on but ESP NOW is off, then enable LR protocol - else if (wifiIsRunning() && espnowState == ESPNOW_OFF) + else if (WIFI_IS_RUNNING() && espNowState == ESPNOW_OFF) { // Enable WiFi + ESP-Now // Enable long range, PHY rate of ESP32 will be 512Kbps or 256Kbps @@ -235,7 +680,7 @@ void espnowStart() esp_wifi_set_promiscuous_rx_cb(&promiscuous_rx_cb); // Assign channel if not connected to an AP - if (wifiIsConnected() == false) + if (WIFI_IS_CONNECTED() == false) espnowSetChannel(settings.wifiChannel); // Register callbacks @@ -298,15 +743,13 @@ void espnowStart() } systemPrintln("ESP-Now Started"); -#endif // COMPILE_ESPNOW } // If WiFi is already enabled, simply remove the LR protocol // If WiFi is off, stop the radio entirely void espnowStop() { -#ifdef COMPILE_ESPNOW - if (espnowState == ESPNOW_OFF) + if (espNowState == ESPNOW_OFF) return; // Turn off promiscuous WiFi mode @@ -362,23 +805,21 @@ void espnowStop() espnowSetState(ESPNOW_OFF); - if (!wifiIsRunning()) + if (WIFI_IS_RUNNING() == false) { - // ESP Now was the only thing using the radio so turn WiFi radio off entirely + // 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 (wifiIsRunning()) + else if (WIFI_IS_RUNNING()) { if (settings.debugEspNow == true) systemPrintln("ESP-Now starting WiFi"); - wifiForceStart(); // Force WiFi to restart + wifiStart(); // Force WiFi to restart } - -#endif // COMPILE_ESPNOW } // Start ESP-Now if needed, put ESP-Now into broadcast state @@ -396,8 +837,7 @@ void espnowBeginPairing() // Regularly call during pairing to see if we've received a Pairing message bool espnowIsPaired() { -#ifdef COMPILE_ESPNOW - if (espnowState == ESPNOW_MAC_RECEIVED) + if (espNowState == ESPNOW_MAC_RECEIVED) { // Remove broadcast peer uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; @@ -430,16 +870,14 @@ bool espnowIsPaired() espnowSetState(ESPNOW_PAIRED); return (true); } -#endif // COMPILE_ESPNOW return (false); } // Create special pair packet to a given MAC esp_err_t espnowSendPairMessage(uint8_t *sendToMac) { -#ifdef COMPILE_ESPNOW // Assemble message to send - PairMessage pairMessage; + ESP_NOW_PAIR_MESSAGE pairMessage; // Get unit MAC address memcpy(pairMessage.macAddress, wifiMACAddress, 6); @@ -451,9 +889,6 @@ esp_err_t espnowSendPairMessage(uint8_t *sendToMac) pairMessage.crc += wifiMACAddress[x]; return (esp_now_send(sendToMac, (uint8_t *)&pairMessage, sizeof(pairMessage))); // Send packet to given MAC -#else // COMPILE_ESPNOW - return (ESP_OK); -#endif // COMPILE_ESPNOW } // Add a given MAC address to the peer list @@ -464,7 +899,6 @@ esp_err_t espnowAddPeer(uint8_t *peerMac) esp_err_t espnowAddPeer(uint8_t *peerMac, bool encrypt) { -#ifdef COMPILE_ESPNOW esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, peerMac, 6); @@ -482,15 +916,11 @@ esp_err_t espnowAddPeer(uint8_t *peerMac, bool encrypt) peerMac[3], peerMac[4], peerMac[5]); } return (result); -#else // COMPILE_ESPNOW - return (ESP_OK); -#endif // COMPILE_ESPNOW } // Remove a given MAC address from the peer list esp_err_t espnowRemovePeer(uint8_t *peerMac) { -#ifdef COMPILE_ESPNOW esp_err_t response = esp_now_del_peer(peerMac); if (response != ESP_OK) { @@ -499,21 +929,18 @@ esp_err_t espnowRemovePeer(uint8_t *peerMac) } return (response); -#else // COMPILE_ESPNOW - return (ESP_OK); -#endif // COMPILE_ESPNOW } // Update the state of the ESP Now state machine void espnowSetState(ESPNOWState newState) { - if (espnowState == newState && settings.debugEspNow == true) + if (espNowState == newState && settings.debugEspNow == true) systemPrint("*"); - espnowState = newState; + espNowState = newState; if (settings.debugEspNow == true) { - systemPrint("espnowState: "); + systemPrint("espNowState: "); switch (newState) { case ESPNOW_OFF: @@ -540,11 +967,10 @@ void espnowSetState(ESPNOWState newState) void espnowProcessRTCM(byte incoming) { -#ifdef COMPILE_ESPNOW // If we are paired, // Or if the radio is broadcasting // Then add bytes to the outgoing buffer - if (espnowState == ESPNOW_PAIRED || espnowState == ESPNOW_BROADCASTING) + if (espNowState == ESPNOW_PAIRED || espNowState == ESPNOW_BROADCASTING) { // Move this byte into ESP NOW to send buffer espnowOutgoing[espnowOutgoingSpot++] = incoming; @@ -556,9 +982,9 @@ void espnowProcessRTCM(byte incoming) { espnowOutgoingSpot = 0; // Wrap - if (espnowState == ESPNOW_PAIRED) + if (espNowState == ESPNOW_PAIRED) esp_now_send(0, (uint8_t *)&espnowOutgoing, sizeof(espnowOutgoing)); // Send packet to all peers - else // if (espnowState == ESPNOW_BROADCASTING) + else // if (espNowState == ESPNOW_BROADCASTING) { uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; esp_now_send(broadcastMac, (uint8_t *)&espnowOutgoing, @@ -571,7 +997,6 @@ void espnowProcessRTCM(byte incoming) espnowOutgoingRTCM = true; } -#endif // COMPILE_ESPNOW } // A blocking function that is used to pair two devices @@ -622,8 +1047,7 @@ void espnowStaticPairing() // Returns the current channel being used by WiFi uint8_t espnowGetChannel() { -#ifdef COMPILE_ESPNOW - if (espnowState == ESPNOW_OFF) + if (espNowState == ESPNOW_OFF) return 0; bool originalPromiscuousMode = false; @@ -647,16 +1071,12 @@ uint8_t espnowGetChannel() esp_wifi_set_promiscuous(false); return (primaryChannelNumber); -#else - return (0); -#endif } // Returns the current channel being used by WiFi bool espnowSetChannel(uint8_t channelNumber) { -#ifdef COMPILE_ESPNOW - if (wifiIsConnected() == true) + if (WIFI_IS_CONNECTED() == true) { systemPrintln("ESP-NOW channel can't be modified while WiFi is connected."); return (false); @@ -683,7 +1103,6 @@ bool espnowSetChannel(uint8_t channelNumber) esp_wifi_set_promiscuous(false); return (setSuccess); -#else - return (false); -#endif } + +#endif // COMPILE_ESPNOW diff --git a/Firmware/RTK_Everywhere/Ethernet.ino b/Firmware/RTK_Everywhere/Ethernet.ino index 661aa2eb7..5e17a081b 100644 --- a/Firmware/RTK_Everywhere/Ethernet.ino +++ b/Firmware/RTK_Everywhere/Ethernet.ino @@ -174,7 +174,7 @@ void ethernetEvent(arduino_event_id_t event, arduino_event_info_t info) if (settings.enablePrintEthernetDiag && (!inMainMenu)) systemPrintf("ETH Got IP: '%s'\r\n", ETH.localIP().toString().c_str()); - networkMarkOnline((NetIndex_t)NETWORK_ETHERNET); + networkInterfaceEventInternetAvailable((NetIndex_t)NETWORK_ETHERNET); if (settings.ethernetDHCP) paintEthernetConnected(); @@ -200,9 +200,9 @@ void ethernetEvent(arduino_event_id_t event, arduino_event_info_t info) } // Take the network offline if necessary - if (networkIsInterfaceOnline(NETWORK_ETHERNET) && (event != ARDUINO_EVENT_ETH_GOT_IP)) + if (networkInterfaceHasInternet(NETWORK_ETHERNET) && (event != ARDUINO_EVENT_ETH_GOT_IP)) { - networkMarkOffline((NetIndex_t)NETWORK_ETHERNET); + networkInterfaceEventInternetLost((NetIndex_t)NETWORK_ETHERNET); } } @@ -261,31 +261,4 @@ void ethernetStart() SPI); // SPIClass & } -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -// Web server routines -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -//---------------------------------------- -// Start Ethernet for the web server -//---------------------------------------- -void ethernetWebServerStartESP32W5500() -{ - Network.onEvent(ethernetEvent); - - // begin(ETH_PHY_TYPE, ETH_PHY_ADDR, CS, IRQ, RST, SPIClass &) - ETH.begin(ETH_PHY_W5500, 0, pin_Ethernet_CS, pin_Ethernet_Interrupt, -1, SPI); - - if (!settings.ethernetDHCP) - ETH.config(settings.ethernetIP, settings.ethernetGateway, settings.ethernetSubnet, settings.ethernetDNS); -} - -//---------------------------------------- -// Stop Ethernet for the web server -//---------------------------------------- -void ethernetWebServerStopESP32W5500() -{ - ETH.end(); - Network.removeEvent(ethernetEvent); -} - #endif // COMPILE_ETHERNET diff --git a/Firmware/RTK_Everywhere/GNSS.h b/Firmware/RTK_Everywhere/GNSS.h index ebb6d3bdb..990ab16d9 100644 --- a/Firmware/RTK_Everywhere/GNSS.h +++ b/Firmware/RTK_Everywhere/GNSS.h @@ -54,7 +54,7 @@ class GNSS public: // Constructor - GNSS() : _leapSeconds(18), _pvtArrivalMillis(0), _pvtUpdated(0) + GNSS() : _leapSeconds(18), _pvtArrivalMillis(0), _pvtUpdated(0), _satellitesInView(0) { } diff --git a/Firmware/RTK_Everywhere/GNSS.ino b/Firmware/RTK_Everywhere/GNSS.ino index 0a862dc98..e5e9884cc 100644 --- a/Firmware/RTK_Everywhere/GNSS.ino +++ b/Firmware/RTK_Everywhere/GNSS.ino @@ -4,6 +4,14 @@ GNSS.ino GNSS layer implementation ------------------------------------------------------------------------------*/ +extern int NTRIPCLIENT_MS_BETWEEN_GGA; + +#ifdef COMPILE_NETWORK +extern NetworkClient *ntripClient; +#endif // COMPILE_NETWORK + +extern unsigned long lastGGAPush; + //---------------------------------------- // Setup the general configuration of the GNSS // Not Rover or Base specific (ie, baud rates) @@ -46,3 +54,31 @@ bool GNSS::setMinCno(uint8_t cnoValue) // Pass the value to the GNSS receiver return gnss->setMinCnoRadio(cnoValue); } + +// Periodically push GGA sentence over NTRIP Client, to Caster, if enabled +void pushGPGGA(char *ggaData) +{ +#ifdef COMPILE_NETWORK + // Wait until the client has been created + if (ntripClient != nullptr) + { + // Provide the caster with our current position as needed + if (ntripClient->connected() && settings.ntripClient_TransmitGGA == true) + { + if (millis() - lastGGAPush > NTRIPCLIENT_MS_BETWEEN_GGA) + { + lastGGAPush = millis(); + + if (settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_GGA)) + { + PERIODIC_CLEAR(PD_NTRIP_CLIENT_GGA); + systemPrintf("NTRIP Client pushing GGA to server: %s", (const char *)ggaData); + } + + // Push our current GGA sentence to caster + ntripClient->print((const 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 500a5b761..efbd13e1e 100644 --- a/Firmware/RTK_Everywhere/GNSS_LG290P.h +++ b/Firmware/RTK_Everywhere/GNSS_LG290P.h @@ -25,28 +25,40 @@ typedef struct { const char msgTextName[11]; const float msgDefaultRate; + const uint8_t firmwareVersionSupported; // The minimum version this message is supported. + // 0 = all versions. + // 9999 = Not supported } lg290pMsg; // Static array containing all the compatible messages // Rate = Output once every N position fix(es). const lg290pMsg lgMessagesNMEA[] = { - {"RMC", 1}, {"GGA", 1}, {"GSV", 1}, {"GSA", 1}, {"VTG", 1}, {"GLL", 1}, + {"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}, }; const lg290pMsg lgMessagesRTCM[] = { - {"RTCM3-1005", 1}, {"RTCM3-1006", 0}, + {"RTCM3-1005", 1, 0}, {"RTCM3-1006", 0, 0}, - {"RTCM3-1019", 0}, + {"RTCM3-1019", 0, 0}, - {"RTCM3-1020", 0}, + {"RTCM3-1020", 0, 0}, - {"RTCM3-1041", 0}, {"RTCM3-1042", 0}, {"RTCM3-1044", 0}, {"RTCM3-1046", 0}, + {"RTCM3-1033", 0, 4}, //v4 and above - {"RTCM3-107X", 1}, {"RTCM3-108X", 1}, {"RTCM3-109X", 1}, {"RTCM3-111X", 1}, {"RTCM3-112X", 1}, {"RTCM3-113X", 1}, + {"RTCM3-1041", 0, 0}, {"RTCM3-1042", 0, 0}, {"RTCM3-1044", 0, 0}, {"RTCM3-1046", 0, 0}, + + {"RTCM3-107X", 1, 0}, {"RTCM3-108X", 1, 0}, {"RTCM3-109X", 1, 0}, {"RTCM3-111X", 1, 0}, {"RTCM3-112X", 1, 0}, {"RTCM3-113X", 1, 0}, +}; + +// Quectel Proprietary messages +const lg290pMsg lgMessagesPQTM[] = { + {"EPE", 0, 0}, {"PVT", 0, 0}, }; #define MAX_LG290P_NMEA_MSG (sizeof(lgMessagesNMEA) / sizeof(lg290pMsg)) #define MAX_LG290P_RTCM_MSG (sizeof(lgMessagesRTCM) / sizeof(lg290pMsg)) +#define MAX_LG290P_PQTM_MSG (sizeof(lgMessagesPQTM) / sizeof(lg290pMsg)) enum lg290p_Models { @@ -154,7 +166,7 @@ class GNSS_LG290P : GNSS void debuggingEnable(); - bool disableSurveyIn(); + bool disableSurveyIn(bool saveAndReset); void enableGgaForNtrip(); @@ -164,7 +176,7 @@ class GNSS_LG290P : GNSS // Returns true if successfully started and false upon failure bool enableRTCMTest(); - bool enterConfigMode(); + bool enterConfigMode(unsigned long waitForSemaphoreTimeout_millis); bool exitConfigMode(); diff --git a/Firmware/RTK_Everywhere/GNSS_LG290P.ino b/Firmware/RTK_Everywhere/GNSS_LG290P.ino index db91ea72a..6f45871d0 100644 --- a/Firmware/RTK_Everywhere/GNSS_LG290P.ino +++ b/Firmware/RTK_Everywhere/GNSS_LG290P.ino @@ -8,6 +8,8 @@ GNSS_LG290P.ino #ifdef COMPILE_LG290P +uint8_t lg290pFirmwareVersion = 0; + //---------------------------------------- // If we have decryption keys, configure module // Note: don't check online.lband_neo here. We could be using ip corrections @@ -99,14 +101,29 @@ void GNSS_LG290P::begin() online.gnss = true; // Check firmware version and print info - printModuleInfo(); - std::string version, buildDate, buildTime; if (_lg290p->getVersionInfo(version, buildDate, buildTime)) snprintf(gnssFirmwareVersion, sizeof(gnssFirmwareVersion), "%s", version.c_str()); - if (sscanf(gnssFirmwareVersion, "%d", &gnssFirmwareVersionInt) != 1) - gnssFirmwareVersionInt = 99; + // Version strings look like LG290P03AANR01A03S and is version 03 + char *spot = strnstr(gnssFirmwareVersion, "LG290P03AANR01A", sizeof(gnssFirmwareVersion)); + if (spot != NULL) + { + spot += strlen("LG290P03AANR01A"); + if (sscanf(spot, "%d", &lg290pFirmwareVersion) != 1) + lg290pFirmwareVersion = 99; + } + + if (lg290pFirmwareVersion < 4) + { + systemPrintf( + "Current LG290P firmware: v%d (full form: %s). GST and DATA port configuration require v4 or newer. Please " + "update the " + "firmware on your LG290P to allow for these features. Please see https://bit.ly/sfe-rtk-lg290p-update\r\n", + lg290pFirmwareVersion, gnssFirmwareVersion); + } + + printModuleInfo(); snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", getId()); } @@ -148,13 +165,6 @@ bool GNSS_LG290P::checkPPPRates() //---------------------------------------- bool GNSS_LG290P::configureGNSS() { - // Skip configuring the GNSS receiver if no new changes are necessary - if (settings.updateGNSSSettings == false) - { - systemPrintln("LG290P configuration maintained"); - return (true); - } - for (int x = 0; x < 3; x++) { // Wait up to 5 seconds for device to come online @@ -201,6 +211,12 @@ bool GNSS_LG290P::configureOnce() Enable selected RTCM messages on COM2 */ + if (settings.gnssConfiguredOnce) + { + systemPrintln("LG290P configuration maintained"); + return (true); + } + if (settings.debugGnss) debuggingEnable(); // Print all debug to Serial @@ -208,24 +224,32 @@ bool GNSS_LG290P::configureOnce() bool response = true; - response &= enterConfigMode(); + uint8_t retries = 4; + + while ((retries > 0) && (!enterConfigMode(500))) + { + retries--; + systemPrintf("configureOnce: Enter config mode failed. %d retries remaining\r\n", retries); + } + + response &= (retries > 0); if (settings.debugGnss && response == false) - systemPrintln("ConfigureOnce: Enter config mode failed"); + systemPrintln("configureOnce: Enter config mode failed"); response &= setDataBaudRate(settings.dataPortBaud); // LG290P UART1 is connected to CH342 (Port B) response &= _lg290p->setPortBaudrate(2, 115200 * 4); // LG290P UART2 is connected to the ESP32 UART1 response &= setRadioBaudRate(settings.radioPortBaud); // LG290P UART3 is connected to the locking JST connector if (settings.debugGnss && response == false) - systemPrintln("ConfigureOnce: setBauds failed"); + systemPrintln("configureOnce: setBauds failed"); // Enable PPS signal with a width of 200ms response &= _lg290p->setPPS(200, false, true); // duration time ms, alwaysOutput, polarity if (settings.debugGnss && response == false) - systemPrintln("ConfigureOnce: setPPS failed"); + systemPrintln("configureOnce: setPPS failed"); response &= setConstellations(); if (settings.debugGnss && response == false) - systemPrintln("ConfigureOnce: setConstellations failed"); + systemPrintln("configureOnce: setConstellations failed"); // We do not set Rover or fix rate here because fix rate only applies in rover mode. @@ -238,14 +262,13 @@ bool GNSS_LG290P::configureOnce() systemPrintln("LG290P configuration updated"); // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the LG290P at next boot - bool settingsWereSaved = saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= saveConfiguration(); } else online.gnss = false; // Take it offline + settings.gnssConfiguredOnce = response; + return (response); } @@ -265,13 +288,28 @@ bool GNSS_LG290P::configureRover() return (false); } + // If our settings haven't changed, trust GNSS's settings + if (settings.gnssConfiguredRover) + { + systemPrintln("Skipping LG290P Rover configuration"); + return (true); + } + bool response = true; serialGNSS->flush(); // Remove any incoming characters - response &= enterConfigMode(); + uint8_t retries = 4; + + while ((retries > 0) && (!enterConfigMode(500))) + { + retries--; + systemPrintf("configureRover: Enter config mode failed. %d retries remaining\r\n", retries); + } + + response &= (retries > 0); if (settings.debugGnss && response == false) - systemPrintln("Rover: Enter config mode failed"); + systemPrintln("configureRover: Enter config mode failed"); // We must force receiver into Rover mode so that we can set fix rate int currentMode = getMode(); @@ -282,21 +320,21 @@ bool GNSS_LG290P::configureRover() // fail because NMEA is not present. _lg290p->setModeRover(); // Wait for save and reset if (settings.debugGnss && response == false) - systemPrintln("Rover: Set mode rover failed"); + systemPrintln("configureRover: Set mode rover failed"); } // Set the fix rate. Default on LG290P is 10Hz so set accordingly. - response &= setRate(settings.measurementRateMs / 1000.0); + response &= setRate(settings.measurementRateMs / 1000.0); // May require save/reset if (settings.debugGnss && response == false) - systemPrintln("Rover: Set rate failed"); + systemPrintln("configureRover: Set rate failed"); response &= enableRTCMRover(); if (settings.debugGnss && response == false) - systemPrintln("Rover: Enable RTCM failed"); + systemPrintln("configureRover: Enable RTCM failed"); response &= enableNMEA(); if (settings.debugGnss && response == false) - systemPrintln("Rover: Enable NMEA failed"); + systemPrintln("configureRover: Enable NMEA failed"); response &= exitConfigMode(); // We must exit config before we save otherwise we will save with NMEA/RTCM off @@ -307,15 +345,17 @@ bool GNSS_LG290P::configureRover() else { // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the LG290P at next boot - bool settingsWereSaved = saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= saveConfiguration(); - if (settings.debugGnss) + // For RTCM and MSM messages to take effect (ie, PointPerfect is active) we must save/reset + softwareReset(); + + if (settings.debugGnss && response) systemPrintln("LG290P Rover configured"); } + settings.gnssConfiguredRover = response; + return (response); } @@ -336,13 +376,28 @@ bool GNSS_LG290P::configureBase() return (false); } + if (settings.gnssConfiguredBase) + { + if (settings.debugGnss) + systemPrintln("Skipping LG290P Base configuration"); + return true; + } + bool response = true; serialGNSS->flush(); // Remove any incoming characters - response &= enterConfigMode(); + uint8_t retries = 4; + + while ((retries > 0) && (!enterConfigMode(500))) + { + retries--; + systemPrintf("configureBase: Enter config mode failed. %d retries remaining\r\n", retries); + } + + response &= (retries > 0); if (settings.debugGnss && response == false) - systemPrintln("Base: Enter config mode failed"); + systemPrintln("configureBase: Enter config mode failed"); // "When set to Base Station mode, the receiver will automatically disable NMEA message output and enable RTCM MSM4 // and RTCM3-1005 message output." @@ -358,9 +413,9 @@ bool GNSS_LG290P::configureBase() // response &= _lg290p->setModeBase(); // Wait for save and reset // Ignore result for now. enterConfigMode disables NMEA, which causes the built-in write/save/reset methods to // fail because NMEA is not present. - _lg290p->setModeBase(); // Wait for save and reset + _lg290p->setModeBase(false); // Don't save and reset if (settings.debugGnss && response == false) - systemPrintln("Base: Set mode base failed"); + systemPrintln("configureBase: Set mode base failed"); // Device should now have survey mode disabled } @@ -372,18 +427,18 @@ bool GNSS_LG290P::configureBase() // response &= disableSurveyIn(); // Wait for save and reset // Ignore result for now. enterConfigMode disables NMEA, which causes the built-in write/save/reset methods to // fail because NMEA is not present. - disableSurveyIn(); // Wait for save and reset + disableSurveyIn(false); // Don't save and reset if (settings.debugGnss && response == false) - systemPrintln("Base: disable survey in failed"); + systemPrintln("configureBase: disable survey in failed"); } response &= enableRTCMBase(); // Set RTCM messages if (settings.debugGnss && response == false) - systemPrintln("Base: Enable RTCM failed"); + systemPrintln("configureBase: Enable RTCM failed"); response &= enableNMEA(); // Set NMEA messages if (settings.debugGnss && response == false) - systemPrintln("Base: Enable NMEA failed"); + systemPrintln("configureBase: Enable NMEA failed"); response &= exitConfigMode(); // We must exit config before we save otherwise we will save with NMEA/RTCM off @@ -394,17 +449,20 @@ bool GNSS_LG290P::configureBase() else { // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the LG290P at next boot - bool settingsWereSaved = saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= saveConfiguration(); softwareReset(); - if (settings.debugGnss) + // When a device is changed from Rover to Base, NMEA messages settings do not survive PQTMSAVEPAR + // Re-enable NMEA post reset + response &= enableNMEA(); // Set NMEA messages + + if (settings.debugGnss && response) systemPrintln("LG290P Base configured"); } + settings.gnssConfiguredBase = response; + return (response); } @@ -430,11 +488,20 @@ bool GNSS_LG290P::configureNtpMode() //---------------------------------------- // Disable NMEA and RTCM on UART2 to reduce the serial traffic //---------------------------------------- -bool GNSS_LG290P::enterConfigMode() +bool GNSS_LG290P::enterConfigMode(unsigned long waitForSemaphoreTimeout_millis) { if (online.gnss) + { + unsigned long start = millis(); + bool isBlocking; + do { // Wait for up to waitForSemaphoreTimeout for library to stop blocking + isBlocking = _lg290p->isBlocking(); + } while (isBlocking && (millis() < (start + waitForSemaphoreTimeout_millis))); + + // This will fail if the library is still blocking, but it is worth a punt... return (_lg290p->sendOkCommand("$PQTMCFGPROT", ",W,1,2,00000000,00000000")); // Disable NMEA and RTCM on the LG290P UART2 + } return (false); } @@ -452,7 +519,7 @@ bool GNSS_LG290P::exitConfigMode() //---------------------------------------- // Disable Survey-In //---------------------------------------- -bool GNSS_LG290P::disableSurveyIn() +bool GNSS_LG290P::disableSurveyIn(bool saveAndReset) { bool response = false; if (online.gnss) @@ -460,12 +527,16 @@ bool GNSS_LG290P::disableSurveyIn() response = _lg290p->sendOkCommand("$PQTMCFGSVIN", ",W,0,0,0,0,0,0"); // Disable survey mode if (settings.debugGnss && response == false) systemPrintln("disableSurveyIn: sendOkCommand failed"); - response &= saveConfiguration(); - if (settings.debugGnss && response == false) - systemPrintln("disableSurveyIn: save failed"); - response &= softwareReset(); - if (settings.debugGnss && response == false) - systemPrintln("disableSurveyIn: reset failed"); + + if (saveAndReset) + { + response &= saveConfiguration(); + if (settings.debugGnss && response == false) + systemPrintln("disableSurveyIn: save failed"); + response &= softwareReset(); + if (settings.debugGnss && response == false) + systemPrintln("disableSurveyIn: reset failed"); + } } return (response); @@ -503,28 +574,52 @@ bool GNSS_LG290P::enableNMEA() bool gpggaEnabled = false; bool gpzdaEnabled = false; - for (int messageNumber = 0; messageNumber < MAX_LG290P_NMEA_MSG; messageNumber++) + int portNumber = 1; + + while (portNumber < 4) { - if (_lg290p->setMessageRate(lgMessagesNMEA[messageNumber].msgTextName, - settings.lg290pMessageRatesNMEA[messageNumber]) == false) + for (int messageNumber = 0; messageNumber < MAX_LG290P_NMEA_MSG; messageNumber++) { - if (settings.debugGnss) - systemPrintf("Enable NMEA failed at messageNumber %d %s.\r\n", messageNumber, - lgMessagesNMEA[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall + // Check if this NMEA message is supported by the current LG290P firmware + if (lg290pFirmwareVersion >= lgMessagesNMEA[messageNumber].firmwareVersionSupported) + { + // If firmware is 4 or higher, use setMessageRateOnPort, otherwise setMessageRate + if (lg290pFirmwareVersion >= 4) + // Enable this message, at this rate, on this port + response &= + _lg290p->setMessageRateOnPort(lgMessagesNMEA[messageNumber].msgTextName, + settings.lg290pMessageRatesNMEA[messageNumber], portNumber); + else + // Enable this message, at this rate + response &= _lg290p->setMessageRate(lgMessagesNMEA[messageNumber].msgTextName, + settings.lg290pMessageRatesNMEA[messageNumber]); + if (response == false && settings.debugGnss) + systemPrintf("Enable NMEA failed at messageNumber %d %s.\r\n", messageNumber, + lgMessagesNMEA[messageNumber].msgTextName); + + // If we are using IP based corrections, we need to send local data to the PPL + // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 + if (settings.enablePointPerfectCorrections) + { + // Mark PPL required messages as enabled if rate > 0 + if (settings.lg290pMessageRatesNMEA[messageNumber] > 0) + { + if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "GGA") == 0) + gpggaEnabled = true; + + // ZDA not supported on LG290P + // if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "ZDA") == 0) + // gpzdaEnabled = true; + } + } + } } - // If we are using IP based corrections, we need to send local data to the PPL - // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 - if (strstr(settings.pointPerfectKeyDistributionTopic, "/ip") != nullptr) - { - // Mark PPL required messages as enabled if rate > 0 - if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "GGA") == 0) - gpggaEnabled = true; - // ZDA not supported on LG290P - // if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "ZDA") == 0) - gpzdaEnabled = true; - } + portNumber++; + + // setMessageRateOnPort only supported on v4 and above + if (lg290pFirmwareVersion < 4) + break; // Don't step through portNumbers } if (settings.enablePointPerfectCorrections) @@ -532,6 +627,7 @@ bool GNSS_LG290P::enableNMEA() // Force on any messages that are needed for PPL if (gpggaEnabled == false) response &= _lg290p->setMessageRate("GGA", 1); + // if (gpzdaEnabled == false) // response &= _lg290p->setMessageRate("ZDA", 1); } @@ -547,42 +643,74 @@ bool GNSS_LG290P::enableRTCMBase() bool response = true; bool enableMSM = false; // Goes true if we need to enable MSM output reporting - for (int messageNumber = 0; messageNumber < MAX_LG290P_RTCM_MSG; messageNumber++) + int portNumber = 1; + + while (portNumber < 4) { - // Setting RTCM-1005 must have only the rate - // Setting RTCM-107X must have rate and offset - if (strchr(lgMessagesRTCM[messageNumber].msgTextName, 'X') == nullptr) + for (int messageNumber = 0; messageNumber < MAX_LG290P_RTCM_MSG; messageNumber++) { - // No X found. This is RTCM-1??? message. No offset. - if (_lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, - settings.lg290pMessageRatesRTCMBase[messageNumber]) == false) + // Check if this RTCM message is supported by the current LG290P firmware + if (lg290pFirmwareVersion >= lgMessagesRTCM[messageNumber].firmwareVersionSupported) { - // if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, - lgMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } - } - else - { - // X found. This is RTCM-1??X message. Assign 'offset' of 0 - if (_lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, - settings.lg290pMessageRatesRTCMBase[messageNumber], 0) == false) - { - // if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, - lgMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall + // Setting RTCM-1005 must have only the rate + // Setting RTCM-107X must have rate and offset + if (strchr(lgMessagesRTCM[messageNumber].msgTextName, 'X') == nullptr) + { + // No X found. This is RTCM-1??? message. No offset. + + // If firmware is 4 or higher, use setMessageRateOnPort, otherwise setMessageRate + if (lg290pFirmwareVersion >= 4) + // Enable this message, at this rate, on this port + response &= _lg290p->setMessageRateOnPort(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMBase[messageNumber], + portNumber); + else + // Enable this message, at this rate + response &= _lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMBase[messageNumber]); + + if (response == false && settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, + lgMessagesRTCM[messageNumber].msgTextName); + } + else + { + // X found. This is RTCM-1??X message. Assign 'offset' of 0 + + // If firmware is 4 or higher, use setMessageRateOnPort, otherwise setMessageRate + if (lg290pFirmwareVersion >= 4) + // Enable this message, at this rate, on this port + response &= _lg290p->setMessageRateOnPort(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMBase[messageNumber], + portNumber, 0); + else + // Enable this message, at this rate + response &= _lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMBase[messageNumber], 0); + + if (response == false && settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, + lgMessagesRTCM[messageNumber].msgTextName); + } + + // If any message is enabled, enable MSM output + if (settings.lg290pMessageRatesRTCMBase[messageNumber] > 0) + enableMSM = true; } } - // If any message is enabled, enable MSM output - if (lgMessagesRTCM[messageNumber].msgTextName, settings.lg290pMessageRatesRTCMRover[messageNumber] == true) - enableMSM = true; + portNumber++; + + // setMessageRateOnPort only supported on v4 and above + if (lg290pFirmwareVersion < 4) + break; // Don't step through portNumbers } if (enableMSM == true) { + if (settings.debugGnss) + systemPrintln("Enabling Base RTCM MSM output"); + // PQTMCFGRTCM fails to respond with OK over UART2 of LG290P, so don't look for it _lg290p->sendOkCommand( "PQTMCFGRTCM,W,7,0,-90,07,06,2,1"); // Enable MSM7, output regular intervals, interval (seconds) @@ -603,71 +731,126 @@ bool GNSS_LG290P::enableRTCMRover() bool rtcm1046Enabled = false; bool enableMSM = false; // Goes true if we need to enable MSM output reporting - for (int messageNumber = 0; messageNumber < MAX_LG290P_RTCM_MSG; messageNumber++) + int portNumber = 1; + + while (portNumber < 4) { - // Setting RTCM-1005 must have only the rate - // Setting RTCM-107X must have rate and offset - if (strchr(lgMessagesRTCM[messageNumber].msgTextName, 'X') == nullptr) + for (int messageNumber = 0; messageNumber < MAX_LG290P_RTCM_MSG; messageNumber++) { - // No X found. This is RTCM-1??? message. No offset. - if (_lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, - settings.lg290pMessageRatesRTCMRover[messageNumber]) == false) + // Check if this RTCM message is supported by the current LG290P firmware + if (lg290pFirmwareVersion >= lgMessagesRTCM[messageNumber].firmwareVersionSupported) { - if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, - lgMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } - } - else - { - // X found. This is RTCM-1??X message. Assign 'offset' of 0 - if (_lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, - settings.lg290pMessageRatesRTCMRover[messageNumber], 0) == false) - { - if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, - lgMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall + // Setting RTCM-1005 must have only the rate + // Setting RTCM-107X must have rate and offset + if (strchr(lgMessagesRTCM[messageNumber].msgTextName, 'X') == nullptr) + { + // No X found. This is RTCM-1??? message. No offset. + + // If firmware is 4 or higher, use setMessageRateOnPort, otherwise setMessageRate + if (lg290pFirmwareVersion >= 4) + { + // If any one of the commands fails, report failure overall + response &= _lg290p->setMessageRateOnPort(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMRover[messageNumber], + portNumber); + } + else + response &= _lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMRover[messageNumber]); + + if (response == false && settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, + lgMessagesRTCM[messageNumber].msgTextName); + } + else + { + // X found. This is RTCM-1??X message. Assign 'offset' of 0 + + // If firmware is 4 or higher, use setMessageRateOnPort, otherwise setMessageRate + if (lg290pFirmwareVersion >= 4) + { + response &= _lg290p->setMessageRateOnPort(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMRover[messageNumber], + portNumber, 0); + } + else + response &= _lg290p->setMessageRate(lgMessagesRTCM[messageNumber].msgTextName, + settings.lg290pMessageRatesRTCMRover[messageNumber], 0); + + if (response == false && settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s\r\n", messageNumber, + lgMessagesRTCM[messageNumber].msgTextName); + } + + // If any message is enabled, enable MSM output + if (settings.lg290pMessageRatesRTCMRover[messageNumber] > 0) + enableMSM = true; + + // If we are using IP based corrections, we need to send local data to the PPL + // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 + if (settings.enablePointPerfectCorrections) + { + // Mark PPL required messages as enabled if rate > 0 + if (settings.lg290pMessageRatesRTCMRover[messageNumber] > 0) + { + if (strcmp(lgMessagesRTCM[messageNumber].msgTextName, "RTCM3-1019") == 0) + rtcm1019Enabled = true; + else if (strcmp(lgMessagesRTCM[messageNumber].msgTextName, "RTCM3-1020") == 0) + rtcm1020Enabled = true; + else if (strcmp(lgMessagesRTCM[messageNumber].msgTextName, "RTCM3-1042") == 0) + rtcm1042Enabled = true; + else if (strcmp(lgMessagesRTCM[messageNumber].msgTextName, "RTCM3-1046") == 0) + rtcm1046Enabled = true; + } + } } } - // If any message is enabled, enable MSM output - if (lgMessagesRTCM[messageNumber].msgTextName, settings.lg290pMessageRatesRTCMRover[messageNumber] == true) - enableMSM = true; + portNumber++; - // If we are using IP based corrections, we need to send local data to the PPL - // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 - if (settings.enablePointPerfectCorrections) - { - // Mark PPL required messages as enabled if rate > 0 - if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "RTCM3-1019") == 0) - rtcm1019Enabled = true; - if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "RTCM3-1020") == 0) - rtcm1020Enabled = true; - if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "RTCM3-1042") == 0) - rtcm1042Enabled = true; - if (strcmp(lgMessagesNMEA[messageNumber].msgTextName, "RTCM3-1046") == 0) - rtcm1046Enabled = true; - enableMSM = true; // Force enable MSM output - } + // setMessageRateOnPort only supported on v4 and above + if (lg290pFirmwareVersion < 4) + break; // Don't step through portNumbers } if (settings.enablePointPerfectCorrections) { + enableMSM = true; // Force enable MSM output + // Force on any messages that are needed for PPL if (rtcm1019Enabled == false) + { + if (settings.debugCorrections) + systemPrintln("PPL Enabling RTCM3-1019"); response &= _lg290p->setMessageRate("RTCM3-1019", 1); + } if (rtcm1020Enabled == false) + { + if (settings.debugCorrections) + systemPrintln("PPL Enabling RTCM3-1020"); + response &= _lg290p->setMessageRate("RTCM3-1020", 1); + } if (rtcm1042Enabled == false) + { + if (settings.debugCorrections) + systemPrintln("PPL Enabling RTCM3-1042"); + response &= _lg290p->setMessageRate("RTCM3-1042", 1); + } if (rtcm1046Enabled == false) + { + if (settings.debugCorrections) + systemPrintln("PPL Enabling RTCM3-1046"); response &= _lg290p->setMessageRate("RTCM3-1046", 1); + } } if (enableMSM == true) { + if (settings.debugCorrections) + systemPrintln("Enabling Rover RTCM MSM output"); + // PQTMCFGRTCM fails to respond with OK over UART2 of LG290P, so don't look for it _lg290p->sendOkCommand( "PQTMCFGRTCM,W,7,0,-90,07,06,2,1"); // Enable MSM7, output regular intervals, interval (seconds) @@ -738,7 +921,8 @@ bool GNSS_LG290P::fixedBaseStart() if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF) { - _lg290p->setSurveyFixedMode(settings.fixedEcefX, settings.fixedEcefY, settings.fixedEcefZ); + // Waits for save and reset + response &= _lg290p->setSurveyFixedMode(settings.fixedEcefX, settings.fixedEcefY, settings.fixedEcefZ); } else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC) { @@ -755,7 +939,8 @@ bool GNSS_LG290P::fixedBaseStart() // Convert LLA to ECEF geodeticToEcef(settings.fixedLat, settings.fixedLong, totalFixedAltitude, &ecefX, &ecefY, &ecefZ); - _lg290p->setSurveyFixedMode(ecefX, ecefY, ecefZ); + // Waits for save and reset + response &= _lg290p->setSurveyFixedMode(ecefX, ecefY, ecefZ); } return (response); @@ -938,7 +1123,6 @@ uint8_t GNSS_LG290P::getFixType() //---------------------------------------- float GNSS_LG290P::getHorizontalAccuracy() { - // Coming soon from EPE if (online.gnss) return (_lg290p->get2DError()); return 0; @@ -994,8 +1178,18 @@ uint8_t GNSS_LG290P::getLoggingType() { LoggingType logType = LOGGING_CUSTOM; - if (getActiveNmeaMessageCount() == 6 && getActiveRtcmMessageCount() == 0) - logType = LOGGING_STANDARD; + if (lg290pFirmwareVersion <= 3) + { + // GST is not available/default + if (getActiveNmeaMessageCount() == 6 && getActiveRtcmMessageCount() == 0) + logType = LOGGING_STANDARD; + } + else + { + // GST *is* available/default + if (getActiveNmeaMessageCount() == 7 && getActiveRtcmMessageCount() == 0) + logType = LOGGING_STANDARD; + } // What RTCM messages need to be logged to reach LOGGING_PPP? @@ -1099,9 +1293,7 @@ uint8_t GNSS_LG290P::getRtcmMessageNumberByName(const char *msgName) uint8_t GNSS_LG290P::getSatellitesInView() { if (online.gnss) - // return (_lg290p->getSatellitesInView()); - // Use getSatellitesUsed until SIV works correctly - return (_lg290p->getSatellitesUsedCount()); + return (_lg290p->getSatellitesInViewCount()); return 0; } @@ -1227,8 +1419,8 @@ bool GNSS_LG290P::isDgpsFixed() } //---------------------------------------- -// Some functions (L-Band area frequency determination) merely need to know if we have a valid fix, not what type of fix -// This function checks to see if the given platform has reached sufficient fix type to be considered valid +// Some functions (L-Band area frequency determination) merely need to know if we have a valid fix, not what type of +// fix This function checks to see if the given platform has reached sufficient fix type to be considered valid //---------------------------------------- bool GNSS_LG290P::isFixed() { @@ -1424,6 +1616,7 @@ void GNSS_LG290P::menuMessages() systemPrintln("1) Set NMEA Messages"); systemPrintln("2) Set Rover RTCM Messages"); systemPrintln("3) Set Base RTCM Messages"); + systemPrintln("4) Set PQTM Messages"); systemPrintln("10) Reset to Defaults"); @@ -1437,6 +1630,8 @@ void GNSS_LG290P::menuMessages() menuMessagesSubtype(settings.lg290pMessageRatesRTCMRover, "RTCMRover"); else if (incoming == 3) menuMessagesSubtype(settings.lg290pMessageRatesRTCMBase, "RTCMBase"); + else if (incoming == 4) + menuMessagesSubtype(settings.lg290pMessageRatesPQTM, "PQTM"); else if (incoming == 10) { // Reset rates to defaults @@ -1451,6 +1646,10 @@ void GNSS_LG290P::menuMessages() for (int x = 0; x < MAX_LG290P_RTCM_MSG; x++) settings.lg290pMessageRatesRTCMBase[x] = lgMessagesRTCM[x].msgDefaultRate; + // Reset PQTM rates to defaults + for (int x = 0; x < MAX_LG290P_PQTM_MSG; x++) + settings.lg290pMessageRatesPQTM[x] = lgMessagesPQTM[x].msgDefaultRate; + systemPrintln("Reset to Defaults"); } @@ -1488,25 +1687,57 @@ void GNSS_LG290P::menuMessagesSubtype(int *localMessageRate, const char *message { endOfBlock = MAX_LG290P_NMEA_MSG; - for (int x = 0; x < MAX_LG290P_NMEA_MSG; x++) - systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesNMEA[x].msgTextName, - settings.lg290pMessageRatesNMEA[x]); + for (int x = 0; x < endOfBlock; x++) + { + if (lg290pFirmwareVersion <= lgMessagesNMEA[x].firmwareVersionSupported) + systemPrintf("%d) Message %s: %d - Requires firmware update\r\n", x + 1, + lgMessagesNMEA[x].msgTextName, settings.lg290pMessageRatesNMEA[x]); + else + systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesNMEA[x].msgTextName, + settings.lg290pMessageRatesNMEA[x]); + } } else if (strcmp(messageType, "RTCMRover") == 0) { endOfBlock = MAX_LG290P_RTCM_MSG; - for (int x = 0; x < MAX_LG290P_RTCM_MSG; x++) - systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesRTCM[x].msgTextName, - settings.lg290pMessageRatesRTCMRover[x]); + for (int x = 0; x < endOfBlock; x++) + { + if (lg290pFirmwareVersion <= lgMessagesRTCM[x].firmwareVersionSupported) + systemPrintf("%d) Message %s: %d - Requires firmware update\r\n", x + 1, + lgMessagesRTCM[x].msgTextName, settings.lg290pMessageRatesRTCMRover[x]); + else + systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesRTCM[x].msgTextName, + settings.lg290pMessageRatesRTCMRover[x]); + } } else if (strcmp(messageType, "RTCMBase") == 0) { endOfBlock = MAX_LG290P_RTCM_MSG; - for (int x = 0; x < MAX_LG290P_RTCM_MSG; x++) - systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesRTCM[x].msgTextName, - settings.lg290pMessageRatesRTCMBase[x]); + for (int x = 0; x < endOfBlock; x++) + { + if (lg290pFirmwareVersion <= lgMessagesRTCM[x].firmwareVersionSupported) + systemPrintf("%d) Message %s: %d - Requires firmware update\r\n", x + 1, + lgMessagesRTCM[x].msgTextName, settings.lg290pMessageRatesRTCMBase[x]); + else + systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesRTCM[x].msgTextName, + settings.lg290pMessageRatesRTCMBase[x]); + } + } + else if (strcmp(messageType, "PQTM") == 0) + { + endOfBlock = MAX_LG290P_PQTM_MSG; + + for (int x = 0; x < endOfBlock; x++) + { + if (lg290pFirmwareVersion <= lgMessagesPQTM[x].firmwareVersionSupported) + systemPrintf("%d) Message %s: %d - Requires firmware update\r\n", x + 1, + lgMessagesPQTM[x].msgTextName, settings.lg290pMessageRatesPQTM[x]); + else + systemPrintf("%d) Message %s: %d\r\n", x + 1, lgMessagesPQTM[x].msgTextName, + settings.lg290pMessageRatesPQTM[x]); + } } systemPrintln("x) Exit"); @@ -1530,6 +1761,11 @@ void GNSS_LG290P::menuMessagesSubtype(int *localMessageRate, const char *message sprintf(messageString, "Enter number of fixes required before %s is reported (0 to disable)", lgMessagesRTCM[incoming].msgTextName); } + else if (strcmp(messageType, "PQTM") == 0) + { + sprintf(messageString, "Enter number of fixes required before %s is reported (0 to disable)", + lgMessagesPQTM[incoming].msgTextName); + } int newSetting = 0; @@ -1550,6 +1786,11 @@ void GNSS_LG290P::menuMessagesSubtype(int *localMessageRate, const char *message if (getNewSetting(messageString, 0, 1200, &newSetting) == INPUT_RESPONSE_VALID) settings.lg290pMessageRatesRTCMBase[incoming] = newSetting; } + if (strcmp(messageType, "PQTM") == 0) + { + if (getNewSetting(messageString, 0, 1, &newSetting) == INPUT_RESPONSE_VALID) + settings.lg290pMessageRatesPQTM[incoming] = newSetting; + } } else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) break; @@ -1559,7 +1800,9 @@ void GNSS_LG290P::menuMessagesSubtype(int *localMessageRate, const char *message printUnknown(incoming); } - settings.updateGNSSSettings = true; // Update the GNSS config at the next boot + settings.gnssConfiguredOnce = false; // Update the GNSS config at the next boot + settings.gnssConfiguredBase = false; + settings.gnssConfiguredRover = false; clearBuffer(); // Empty buffer of any newline chars } @@ -1574,7 +1817,7 @@ void GNSS_LG290P::printModuleInfo() std::string version, buildDate, buildTime; if (_lg290p->getVersionInfo(version, buildDate, buildTime)) { - systemPrintf("LG290P version: %s %s %s\r\n", version.c_str(), buildDate.c_str(), buildTime.c_str()); + systemPrintf("LG290P version: v%02d - %s %s %s - v%d\r\n", lg290pFirmwareVersion, version.c_str(), buildDate.c_str(), buildTime.c_str()); } else { @@ -1780,7 +2023,7 @@ bool GNSS_LG290P::setRate(double secondsBetweenSolutions) int currentMode = getMode(); if (currentMode == 2) // Base { - if (settings.debugGnss) + if (settings.debugGnss || settings.debugCorrections) systemPrintln("Error: setRate can only be used in Rover mode"); return (false); } @@ -1801,7 +2044,7 @@ bool GNSS_LG290P::setRate(double secondsBetweenSolutions) { if (fixInterval != msBetweenSolutions) { - if (settings.debugGnss) + if (settings.debugGnss || settings.debugCorrections) systemPrintf("Modifying fix interval to %d\r\n", msBetweenSolutions); // Set the fix interval @@ -1810,7 +2053,7 @@ bool GNSS_LG290P::setRate(double secondsBetweenSolutions) // Reboot receiver to apply changes if (response == true) { - if (settings.debugGnss) + if (settings.debugGnss || settings.debugCorrections) systemPrintln("Rebooting LG290P"); response &= saveConfiguration(); @@ -1965,4 +2208,57 @@ void lg290pBoot() void lg290pReset() { digitalWrite(pin_GNSS_Reset, LOW); -} \ No newline at end of file +} + +// Given a NMEA or PQTM sentence, determine if it is enabled in settings +// This is used to signal to the processUart1Message() task to remove messages that are needed +// by the library to function (ie, PQTMEPE, PQTMPVT, GNGSV) but should not be logged or passed to other consumers +// If unknown, allow messages through. Filtering and suppression should be selectively added in. +bool lg290pMessageEnabled(char *nmeaSentence, int sentenceLength) +{ + // Identify message type: PQTM or NMEA + char messageType[strlen("PQTM") + 1] = {0}; + strncpy(messageType, &nmeaSentence[1], + 4); // Copy four letters, starting in spot 1. Null terminated from array initializer. + + if (strncmp(messageType, "PQTM", sizeof(messageType)) == 0) + { + // Identify sentence type + char sentenceType[strlen("EPE") + 1] = {0}; + strncpy(sentenceType, &nmeaSentence[5], + 3); // Copy three letters, starting in spot 5. Null terminated from array initializer. + + // Find this sentence type in the settings array + for (int messageNumber = 0; messageNumber < MAX_LG290P_PQTM_MSG; messageNumber++) + { + if (strncmp(lgMessagesPQTM[messageNumber].msgTextName, sentenceType, sizeof(sentenceType)) == 0) + { + if (settings.lg290pMessageRatesPQTM[messageNumber] > 0) + return (true); + return (false); + } + } + } + + else // We have to assume $G???? + { + // Identify sentence type + char sentenceType[strlen("GSV") + 1] = {0}; + strncpy(sentenceType, &nmeaSentence[3], + 3); // Copy three letters, starting in spot 3. Null terminated from array initializer. + + // Find this sentence type in the settings array + for (int messageNumber = 0; messageNumber < MAX_LG290P_NMEA_MSG; messageNumber++) + { + if (strncmp(lgMessagesNMEA[messageNumber].msgTextName, sentenceType, sizeof(sentenceType)) == 0) + { + if (settings.lg290pMessageRatesNMEA[messageNumber] > 0) + return (true); + return (false); + } + } + } + + // If we can't ID this message, allow it by default. The device configuration should control most message flow. + return (true); +} diff --git a/Firmware/RTK_Everywhere/GNSS_Mosaic.h b/Firmware/RTK_Everywhere/GNSS_Mosaic.h index 0c0be8097..a2e41310c 100644 --- a/Firmware/RTK_Everywhere/GNSS_Mosaic.h +++ b/Firmware/RTK_Everywhere/GNSS_Mosaic.h @@ -18,6 +18,8 @@ typedef struct { const mosaicExpectedID mosaicExpectedIDs[] = { { 4007, true, 96, "PVTGeodetic" }, + { 4013, false, 0, "ChannelStatus" }, + { 4059, false, 0, "DiskStatus" }, { 4090, false, 0, "InputLink" }, { 4097, false, 0, "EncapsulatedOutput" }, { 5914, true, 24, "ReceiverTime" }, @@ -46,6 +48,11 @@ const mosaicExpectedID mosaicExpectedIDs[] = { // This indicates if RTCM corrections are being received - on COM2 #define MOSAIC_SBF_INPUTLINK_STREAM (MOSAIC_SBF_EXTEVENT_STREAM + 1) +// Output SBF ChannelStatus and DiskStatus messages on this stream - on COM1 only +// ChannelStatus provides the count of satellites being tracked +// DiskStatus provides the disk usage +#define MOSAIC_SBF_STATUS_STREAM (MOSAIC_SBF_INPUTLINK_STREAM + 1) + // TODO: allow the user to define their own SBF stream for logging to DSK1 - through the menu / web config // But, in the interim, the user can define their own SBF stream (>= Stream3) via the X5 web page over USB-C // The updated configuration can be saved by entering and exiting the main menu, or by: @@ -222,7 +229,7 @@ const mosaicSignalConstellation mosaicSignalConstellations[] = { {"SBAS","SBAS"}, {"BEIDOU","BeiDou"}, {"QZSS","QZSS"}, - {"NAVIC","NAVIC"}, + {"NAVIC","NavIC"}, }; #define MAX_MOSAIC_CONSTELLATIONS (sizeof(mosaicSignalConstellations) / sizeof(mosaicSignalConstellation)) @@ -542,6 +549,14 @@ bool mosaicX5waitCR(unsigned long timeout = 25); // Header class GNSS_MOSAIC : GNSS { + // The mosaic-X5 does not have self-contained interface library. + // But the ZED-F9P, UM980 and LG290P all do. + // On the X5, we communicate manually over serial2GNSS using functions like + // sendWithResponse and sendAndWaitForIdle. + // In essence, the interface library is wholly contained in this class. + // TODO: consider breaking the mosaic comms functions out into their own library + // and add a private library class instance here. + protected: // These globals are updated regularly via the SBF parser @@ -575,13 +590,18 @@ class GNSS_MOSAIC : GNSS float _latStdDev; float _lonStdDev; bool _receiverSetupSeen; + bool _diskStatusSeen; + std::vector svInTracking; + //std::vector svInPVT; // Constructor GNSS_MOSAIC() : _determiningFixedPosition(true), _clkBias_ms(0), _latStdDev(999.9), _lonStdDev(999.9), _receiverSetupSeen(false), - _radioExtBytesReceived_millis(0), + _radioExtBytesReceived_millis(0), _diskStatusSeen(false), GNSS() { + svInTracking.clear(); + //svInPVT.clear(); } // If we have decryption keys, configure module @@ -617,6 +637,12 @@ class GNSS_MOSAIC : GNSS // Returns true if successfully configured and false upon failure bool configureBase(); + // Configure mosaic-X5 COM1 for Encapsulated RTCMv3 + SBF + NMEA, plus L-Band + bool configureGNSSCOM(bool enableLBand); + + // Configure mosaic-X5 L-Band + bool configureLBand(bool enableLBand, uint32_t LBandFreq = 0); + bool configureLogging(); // Configure specific aspects of the receiver for NTP mode @@ -1010,6 +1036,12 @@ class GNSS_MOSAIC : GNSS // Save the data from the SBF Block 4007 void storeBlock4007(SEMP_PARSE_STATE *parse); + // Save the data from the SBF Block 4013 + void storeBlock4013(SEMP_PARSE_STATE *parse); + + // Save the data from the SBF Block 4059 + void storeBlock4059(SEMP_PARSE_STATE *parse); + // Save the data from the SBF Block 4090 void storeBlock4090(SEMP_PARSE_STATE *parse); @@ -1030,7 +1062,7 @@ class GNSS_MOSAIC : GNSS // Poll routine to update the GNSS state void update(); - bool updateSD(); + void updateSD(); void waitSBFReceiverSetup(unsigned long timeout); }; diff --git a/Firmware/RTK_Everywhere/GNSS_Mosaic.ino b/Firmware/RTK_Everywhere/GNSS_Mosaic.ino index 61e8251c9..40e8701ef 100644 --- a/Firmware/RTK_Everywhere/GNSS_Mosaic.ino +++ b/Firmware/RTK_Everywhere/GNSS_Mosaic.ino @@ -135,9 +135,10 @@ void menuLogMosaic() { GNSS_MOSAIC *mosaic = (GNSS_MOSAIC *)gnss; - mosaic->configureLogging(); // This will enable / disable RINEX logging - mosaic->enableNMEA(); // Enable NMEA messages - this will enable/disable the DSK1 streams - setLoggingType(); // Update Standard, PPP, or custom for icon selection + mosaic->configureLogging(); // This will enable / disable RINEX logging + mosaic->enableNMEA(); // Enable NMEA messages - this will enable/disable the DSK1 streams + mosaic->saveConfiguration(); // Save the configuration + setLoggingType(); // Update Standard, PPP, or custom for icon selection } clearBuffer(); // Empty buffer of any newline chars @@ -225,8 +226,26 @@ void GNSS_MOSAIC::begin() if (retries == retryLimit) { - systemPrintln("Could not communicate with mosaic-X5!"); - return; + systemPrintln("Could not communicate with mosaic-X5! Attempting a soft reset..."); + + sendWithResponse("erst,soft,none\n\r", "ResetReceiver"); + + retries = 0; + + // Set COM4 to: CMD input (only), SBF output (only) + while (!sendWithResponse("sdio,COM4,CMD,SBF\n\r", "DataInOut")) + { + if (retries == retryLimit) + break; + retries++; + sendWithResponse("SSSSSSSSSSSSSSSSSSSS\n\r", "COM4>"); // Send escape sequence + } + + if (retries == retryLimit) + { + systemPrintln("Could not communicate with mosaic-X5!"); + return; + } } retries = 0; @@ -248,11 +267,11 @@ void GNSS_MOSAIC::begin() } // Set COM2 (Radio) and COM3 (Data) baud rates - gnss->setRadioBaudRate(settings.radioPortBaud); - gnss->setDataBaudRate(settings.dataPortBaud); + setRadioBaudRate(settings.radioPortBaud); + setDataBaudRate(settings.dataPortBaud); // Set COM2 (Radio) protocol(s) - gnss->setCorrRadioExtPort(settings.enableExtCorrRadio, true); // Force the setting + setCorrRadioExtPort(settings.enableExtCorrRadio, true); // Force the setting updateSD(); // Check card size and free space @@ -296,13 +315,6 @@ bool GNSS_MOSAIC::beginExternalEvent() if (online.gnss == false) return (false); - // If our settings haven't changed, trust GNSS's settings - if (settings.updateGNSSSettings == false) - { - systemPrintln("Skipping mosaic-X5 event configuration"); - return (true); - } - if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) return (true); // No need to configure PPS if port is not selected @@ -326,13 +338,6 @@ bool GNSS_MOSAIC::beginPPS() if (online.gnss == false) return (false); - // If our settings haven't changed, trust GNSS's settings - if (settings.updateGNSSSettings == false) - { - systemPrintln("Skipping mosaicX5BeginPPS"); - return (true); - } - if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) return (true); // No need to configure PPS if port is not selected @@ -402,12 +407,23 @@ bool GNSS_MOSAIC::configureBase() return (false); } + if (settings.gnssConfiguredBase) + { + systemPrintln("Skipping mosaic Base configuration"); + setLoggingType(); // Needed because logUpdate exits early and never calls setLoggingType + return true; + } + bool response = true; response &= setModel(MOSAIC_DYN_MODEL_STATIC); response &= setElevation(settings.minElev); + response &= setMinCnoRadio(settings.minCNO); + + response &= setConstellations(); + response &= enableRTCMBase(); response &= enableNMEA(); @@ -417,16 +433,15 @@ bool GNSS_MOSAIC::configureBase() setLoggingType(); // Update Standard, PPP, or custom for icon selection // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the MOSAICX5 at next boot - bool settingsWereSaved = saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= saveConfiguration(); if (response == false) { systemPrintln("mosaic-X5 Base failed to configure"); } + settings.gnssConfiguredBase = response; + return (response); } @@ -436,8 +451,6 @@ bool GNSS_MOSAIC::configureLogging() bool response = true; String setting; - // TODO: use sdCardPresent() here? Except the mosaic seems pretty good at figuring out if the microSD is present... - // Configure logging if ((settings.enableLogging == true) || (settings.enableLoggingRINEX == true)) { @@ -462,8 +475,6 @@ bool GNSS_MOSAIC::configureLogging() response &= sendWithResponse(setting, "RINEXLogging"); } - // TODO: use sdCardPresent() here? Except the mosaic seems pretty good at figuring out if the microSD is present... - if (settings.enableExternalHardwareEventLogging) { setting = String("sso,Stream" + String(MOSAIC_SBF_EXTEVENT_STREAM) + @@ -488,6 +499,46 @@ bool GNSS_MOSAIC::configureNtpMode() return false; } +//---------------------------------------- +// Configure mosaic-X5 COM1 for Encapsulated RTCMv3 + SBF + NMEA, plus L-Band +//---------------------------------------- +bool GNSS_MOSAIC::configureGNSSCOM(bool enableLBand) +{ + // Configure COM1. NMEA and RTCMv3 will be encapsulated in SBF format + String setting = String("sdio,COM1,auto,RTCMv3+SBF+NMEA+Encapsulate"); + if (enableLBand) + setting += String("+LBandBeam1"); + setting += String("\n\r"); + return sendWithResponse(setting, "DataInOut"); +} + +//---------------------------------------- +// Configure mosaic-X5 L-Band +//---------------------------------------- +bool GNSS_MOSAIC::configureLBand(bool enableLBand, uint32_t LBandFreq) +{ + bool result = sendWithResponse("slsm,off\n\r", "LBandSelectMode"); // Turn L-Band off + + if (!enableLBand) + return result; + + static uint32_t storedLBandFreq = 0; + if (LBandFreq > 0) + storedLBandFreq = LBandFreq; + + // US SPARTN 1.8 service is on 1556290000 Hz + // EU SPARTN 1.8 service is on 1545260000 Hz + result &= + sendWithResponse(String("slbb,User1," + String(storedLBandFreq) + ",baud2400,PPerfect,EU,Enabled\n\r"), + "LBandBeams"); // Set Freq, baud rate + result &= + sendWithResponse("slcs,5555,6959\n\r", "LBandCustomServiceID"); // 21845 = 0x5555; 26969 = 0x6959 + result &= sendWithResponse("slsm,manual,Inmarsat,User1,\n\r", + "LBandSelectMode"); // Set L-Band demodulator to manual + + return result; +} + //---------------------------------------- // Perform the GNSS configuration // Outputs: @@ -505,14 +556,16 @@ bool GNSS_MOSAIC::configureOnce() RTCMv3 messages are enabled by enableRTCMRover / enableRTCMBase */ + if (settings.gnssConfiguredOnce) + { + systemPrintln("mosaic configuration maintained"); + return (true); + } + bool response = true; // Configure COM1. NMEA and RTCMv3 will be encapsulated in SBF format - String setting = String("sdio,COM1,auto,RTCMv3+SBF+NMEA+Encapsulate"); - if (settings.enablePointPerfectCorrections) - setting += String("+LBandBeam1"); - setting += String("\n\r"); - response &= sendWithResponse(setting, "DataInOut"); + response &= configureGNSSCOM(settings.enablePointPerfectCorrections); // COM2 is configured by setCorrRadioExtPort @@ -522,18 +575,18 @@ bool GNSS_MOSAIC::configureOnce() // Output SBF PVTGeodetic and ReceiverTime on their own stream - on COM1 only // TODO : make the interval adjustable // TODO : do we need to enable SBF LBandTrackerStatus so we can get CN0 ? - setting = String("sso,Stream" + String(MOSAIC_SBF_PVT_STREAM) + ",COM1,PVTGeodetic+ReceiverTime,msec500\n\r"); + String setting = String("sso,Stream" + String(MOSAIC_SBF_PVT_STREAM) + ",COM1,PVTGeodetic+ReceiverTime,msec500\n\r"); response &= sendWithResponse(setting, "SBFOutput"); // Output SBF InputLink on its own stream - at 1Hz - on COM1 only setting = String("sso,Stream" + String(MOSAIC_SBF_INPUTLINK_STREAM) + ",COM1,InputLink,sec1\n\r"); response &= sendWithResponse(setting, "SBFOutput"); - response &= setElevation(settings.minElev); - - response &= setMinCnoRadio(settings.minCNO); - - response &= setConstellations(); + // Output SBF ChannelStatus and DiskStatus on their own stream - at 0.5Hz - on COM1 only + // For ChannelStatus: OnChange is too often. The message is typically 1000 bytes in size. + // For DiskStatus: DiskUsage is slow to update. 0.5Hz is plenty fast enough. + setting = String("sso,Stream" + String(MOSAIC_SBF_STATUS_STREAM) + ",COM1,ChannelStatus+DiskStatus,sec2\n\r"); + response &= sendWithResponse(setting, "SBFOutput"); // Mark L5 as healthy response &= sendWithResponse("shm,Tracking,off\n\r", "HealthMask"); @@ -550,14 +603,13 @@ bool GNSS_MOSAIC::configureOnce() systemPrintln("mosaic-X5 configuration updated"); // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the MOSAICX5 at next boot - bool settingsWereSaved = saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= saveConfiguration(); } else online.gnss = false; // Take it offline + settings.gnssConfiguredOnce = response; + return (response); } @@ -569,13 +621,6 @@ bool GNSS_MOSAIC::configureOnce() //---------------------------------------- bool GNSS_MOSAIC::configureGNSS() { - // Skip configuring the MOSAICX5 if no new changes are necessary - if (settings.updateGNSSSettings == false) - { - systemPrintln("mosaic-X5 configuration maintained"); - return (true); - } - // Attempt 3 tries on MOSAICX5 config for (int x = 0; x < 3; x++) { @@ -606,13 +651,25 @@ bool GNSS_MOSAIC::configureRover() return (false); } + // If our settings haven't changed, trust GNSS's settings + if (settings.gnssConfiguredRover) + { + systemPrintln("Skipping mosaic Rover configuration"); + setLoggingType(); // Needed because logUpdate exits early and never calls setLoggingType + return (true); + } + bool response = true; response &= sendWithResponse("spm,Rover,all,auto\n\r", "PVTMode"); - response &= setModel(settings.dynamicModel); + response &= setModel(settings.dynamicModel); // Set by menuGNSS which calls gnss->setModel - response &= setElevation(settings.minElev); + response &= setElevation(settings.minElev); // Set by menuGNSS which calls gnss->setElevation + + response &= setMinCnoRadio(settings.minCNO); + + response &= setConstellations(); response &= enableRTCMRover(); @@ -623,16 +680,15 @@ bool GNSS_MOSAIC::configureRover() setLoggingType(); // Update Standard, PPP, or custom for icon selection // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the MOSAICX5 at next boot - bool settingsWereSaved = saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= saveConfiguration(); if (response == false) { systemPrintln("mosaic-X5 Rover failed to configure"); } + settings.gnssConfiguredRover = response; + return (response); } @@ -765,9 +821,6 @@ bool GNSS_MOSAIC::enableNMEA() response &= sendWithResponse(setting, "NMEAOutput"); } - // TODO: use sdCardPresent() here? Except the mosaic seems pretty good at figuring out if the microSD is - // present... - if (settings.enableLogging) { setting = @@ -964,8 +1017,15 @@ bool GNSS_MOSAIC::enableRTCMTest() //---------------------------------------- void GNSS_MOSAIC::factoryReset() { - sendWithResponse("eccf,RxDefault,Boot\n\r", "CopyConfigFile"); - sendWithResponse("eccf,RxDefault,Current\n\r", "CopyConfigFile"); + unsigned long start = millis(); + bool result = sendWithResponse("eccf,RxDefault,Boot\n\r", "CopyConfigFile", 5000); + if (settings.debugGnss) + systemPrintf("factoryReset: sendWithResponse eccf,RxDefault,Boot returned %s after %d ms\r\n", result ? "true" : "false", millis() - start); + + start = millis(); + result = sendWithResponse("eccf,RxDefault,Current\n\r", "CopyConfigFile", 5000); + if (settings.debugGnss) + systemPrintf("factoryReset: sendWithResponse eccf,RxDefault,Current returned %s after %d ms\r\n", result ? "true" : "false", millis() - start); } //---------------------------------------- @@ -1213,10 +1273,10 @@ uint8_t GNSS_MOSAIC::getLoggingType() { if (checkNMEARates()) { - loggingType = LOGGING_STANDARD; + logType = LOGGING_STANDARD; if (checkPPPRates()) - loggingType = LOGGING_PPP; + logType = LOGGING_PPP; } } @@ -1598,7 +1658,9 @@ void GNSS_MOSAIC::menuConstellations() } // Apply current settings to module - gnss->setConstellations(); + setConstellations(); + + saveConfiguration(); // Save the updated constellations clearBuffer(); // Empty buffer of any newline chars } @@ -1674,7 +1736,8 @@ void GNSS_MOSAIC::menuMessagesNMEA() printUnknown(incoming); } - settings.updateGNSSSettings = true; // Update the GNSS config at the next boot + settings.gnssConfiguredBase = false; // Update the GNSS config at the next boot + settings.gnssConfiguredRover = false; clearBuffer(); // Empty buffer of any newline chars } @@ -1745,7 +1808,8 @@ void GNSS_MOSAIC::menuMessagesRTCM(bool rover) printUnknown(incoming); } - settings.updateGNSSSettings = true; // Update the GNSS config at the next boot + settings.gnssConfiguredBase = false; // Update the GNSS config at the next boot + settings.gnssConfiguredRover = false; clearBuffer(); // Empty buffer of any newline chars } @@ -1760,7 +1824,7 @@ void GNSS_MOSAIC::menuMessages() systemPrintln(); systemPrintln("Menu: GNSS Messages"); - systemPrintf("Active messages: %d\r\n", gnss->getActiveMessageCount()); + systemPrintf("Active messages: %d\r\n", getActiveMessageCount()); systemPrintln("1) Set NMEA Messages"); systemPrintln("2) Set Rover RTCM Messages"); @@ -1883,7 +1947,11 @@ uint16_t GNSS_MOSAIC::rtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead) //---------------------------------------- bool GNSS_MOSAIC::saveConfiguration() { - return sendWithResponse("eccf,Current,Boot\n\r", "CopyConfigFile"); + unsigned long start = millis(); + bool result = sendWithResponse("eccf,Current,Boot\n\r", "CopyConfigFile", 5000); + if (settings.debugGnss) + systemPrintf("saveConfiguration: sendWithResponse returned %s after %d ms\r\n", result ? "true" : "false", millis() - start); + return result; } //---------------------------------------- @@ -2300,6 +2368,8 @@ bool GNSS_MOSAIC::setTalkerGNGGA() //---------------------------------------- bool GNSS_MOSAIC::softwareReset() { + // We could restart L-Band here if needed, but gnss->softwareReset is never called on the X5 + // Instead, update() does it when spartnCorrectionsReceived times out return false; } @@ -2318,7 +2388,12 @@ void GNSS_MOSAIC::storeBlock4007(SEMP_PARSE_STATE *parse) _longitude = sempSbfGetF8(parse, 24) * 180.0 / PI; _altitude = (float)sempSbfGetF8(parse, 32); _horizontalAccuracy = ((float)sempSbfGetU2(parse, 90)) / 100.0; // Convert from cm to m - _satellitesInView = sempSbfGetU1(parse, 74); + + // NrSV is the total number of satellites used in the PVT computation. + //_satellitesInView = sempSbfGetU1(parse, 74); + //if (_satellitesInView == 255) // 255 indicates "Do-Not-Use" + // _satellitesInView = 0; + _fixType = sempSbfGetU1(parse, 14) & 0x0F; _determiningFixedPosition = (sempSbfGetU1(parse, 14) >> 6) & 0x01; _clkBias_ms = sempSbfGetF8(parse, 60); @@ -2326,6 +2401,125 @@ void GNSS_MOSAIC::storeBlock4007(SEMP_PARSE_STATE *parse) _pvtUpdated = true; } +//---------------------------------------- +// Save the data from the SBF Block 4013 +//---------------------------------------- +void GNSS_MOSAIC::storeBlock4013(SEMP_PARSE_STATE *parse) +{ + uint16_t N = (uint16_t)sempSbfGetU1(parse, 14); + uint16_t SB1Length = (uint16_t)sempSbfGetU1(parse, 15); + uint16_t SB2Length = (uint16_t)sempSbfGetU1(parse, 16); + uint16_t ChannelInfoBytes = 0; + for (uint16_t i = 0; i < N; i++) + { + uint8_t SVID = sempSbfGetU1(parse, 20 + ChannelInfoBytes + 0); + + uint16_t N2 = (uint16_t)sempSbfGetU1(parse, 20 + ChannelInfoBytes + 9); + + for (uint16_t j = 0; j < N2; j++) + { + uint16_t TrackingStatus = sempSbfGetU2(parse, 20 + ChannelInfoBytes + SB1Length + (j * SB2Length) + 2); + + bool Tracking = false; + for (uint16_t shift = 0; shift < 16; shift += 2) // Step through each 2-bit status field + { + if ((TrackingStatus & (0x0003 << shift)) == (0x0003 << shift)) // 3 : Tracking + { + Tracking = true; + } + } + + if (Tracking) + { + // SV is being tracked. If it is not in svInTracking, add it + std::vector::iterator pos = + std::find(svInTracking.begin(), svInTracking.end(), SVID); + if (pos == svInTracking.end()) + svInTracking.push_back(SVID); + } + else + { + // SV is not being tracked. If it is in svInTracking, remove it + std::vector::iterator pos = + std::find(svInTracking.begin(), svInTracking.end(), SVID); + if (pos != svInTracking.end()) + svInTracking.erase(pos); + } + + // uint16_t PVTStatus = sempSbfGetU2(parse, 20 + ChannelInfoBytes + SB1Length + (j * SB2Length) + 4); + + // bool Used = false; + // for (uint16_t shift = 0; shift < 16; shift += 2) // Step through each 2-bit status field + // { + // if ((PVTStatus & (0x0003 << shift)) == (0x0002 << shift)) // 2 : Used + // { + // Used = true; + // } + // } + + // if (Used) + // { + // // SV is being used for PVT. If it is not in svInPVT, add it + // std::vector::iterator pos = + // std::find(svInPVT.begin(), svInPVT.end(), SVID); + // if (pos == svInPVT.end()) + // svInPVT.push_back(SVID); + // } + // else + // { + // // SV is not being used for PVT. If it is in svInPVT, remove it + // std::vector::iterator pos = + // std::find(svInPVT.begin(), svInPVT.end(), SVID); + // if (pos != svInPVT.end()) + // svInPVT.erase(pos); + // } + + } + + ChannelInfoBytes += SB1Length + (N2 * SB2Length); + } + + _satellitesInView = (uint8_t)std::distance(svInTracking.begin(), svInTracking.end()); + + // if (settings.debugGnss && !inMainMenu) + // { + // uint8_t _inPVT = (uint8_t)std::distance(svInPVT.begin(), svInPVT.end()); + // systemPrintf("ChannelStatus: InTracking %d, InPVT %d\r\n", _satellitesInView, _inPVT); + // } + +} + +//---------------------------------------- +// Save the data from the SBF Block 4059 +//---------------------------------------- +void GNSS_MOSAIC::storeBlock4059(SEMP_PARSE_STATE *parse) +{ + if (sempSbfGetU1(parse, 14) < 1) // Check N is at least 1 + return; + + if (sempSbfGetU1(parse, 20 + 0) != 1) // Check DiskID is 1 + return; + + uint64_t diskUsageMSB = sempSbfGetU2(parse, 20 + 2); // DiskUsageMSB + uint64_t diskUsageLSB = sempSbfGetU4(parse, 20 + 4); // DiskUsageLSB + + if ((diskUsageMSB == 65535) && (diskUsageLSB == 4294967295)) // Do-Not-Use + return; + + uint64_t diskSizeMB = sempSbfGetU4(parse, 20 + 8); // DiskSize in megabytes + + if (diskSizeMB == 0) // Do-Not-Use + return; + + uint64_t diskUsage = (diskUsageMSB * 4294967296) + diskUsageLSB; + + sdCardSize = diskSizeMB * 1048576; // Convert to bytes + + sdFreeSpace = sdCardSize - diskUsage; + + _diskStatusSeen = true; +} + //---------------------------------------- // Save the data from the SBF Block 4090 //---------------------------------------- @@ -2416,6 +2610,12 @@ bool GNSS_MOSAIC::surveyInStart() //---------------------------------------- void GNSS_MOSAIC::update() { + // The mosaic-X5 supports microSD logging. But the microSD is connected directly to the X5, not the ESP32. + // present.microSd is false, but present.microSdCardDetectLow is true. + // If the microSD card is not inserted at power-on, the X5 does not recognise it if it is inserted later. + // The only way to get the X5 to recognise the card seems to be to perform a soft reset. + // Where should we perform the soft reset? updateSD seems the best place... + // Update the SD card size, free space and logIncreasing static unsigned long sdCardSizeLastCheck = 0; const unsigned long sdCardSizeCheckInterval = 5000; // Matches the interval in logUpdate @@ -2423,51 +2623,100 @@ void GNSS_MOSAIC::update() static uint64_t previousFreeSpace = 0; if (millis() > (sdCardSizeLastCheck + sdCardSizeCheckInterval)) { - updateSD(); - if (previousFreeSpace == 0) - previousFreeSpace = sdFreeSpace; - if (sdFreeSpace < previousFreeSpace) + updateSD(); // Check if the card has been removed / inserted + + if (_diskStatusSeen) // Check if the DiskStatus SBF message has been seen { - previousFreeSpace = sdFreeSpace; - logIncreasing = true; - sdCardLastFreeChange = millis(); + // If previousFreeSpace hasn't been initialized, initialize it + if (previousFreeSpace == 0) + previousFreeSpace = sdFreeSpace; + + if (sdFreeSpace < previousFreeSpace) + { + // The free space is decreasing, so set logIncreasing to true + previousFreeSpace = sdFreeSpace; + logIncreasing = true; + sdCardLastFreeChange = millis(); + } + else if (sdFreeSpace == previousFreeSpace) + { + // The free space has not changed + // X5 is slow to update free. Seems to be about every ~20s? + // So only set logIncreasing to false after 30s + if (millis() > (sdCardLastFreeChange + 30000)) + logIncreasing = false; + } + else // if (sdFreeSpace > previousFreeSpace) + { + // User must have inserted a new SD card? + previousFreeSpace = sdFreeSpace; + } } else { - if (millis() > (sdCardLastFreeChange + 30000)) // X5 is slow to update free. Seems to be about every ~20s? - logIncreasing = false; + // Disk status not seen + // (Unmounting the SD card will prevent _diskStatusSeen from going true) + logIncreasing = false; } - sdCardSizeLastCheck = millis(); + + sdCardSizeLastCheck = millis(); // Update the timer + _diskStatusSeen = false; // Clear the flag } // Update spartnCorrectionsReceived - if (millis() > (lastSpartnReception + 5000)) - spartnCorrectionsReceived = false; + // Does this need if(online.lband_gnss) ? Not sure... TODO + if (millis() > (lastSpartnReception + (settings.correctionsSourcesLifetime_s * 1000))) // Timeout + { + if (spartnCorrectionsReceived) // If corrections were being received + { + configureLBand(settings.enablePointPerfectCorrections); // Restart L-Band using stored frequency + spartnCorrectionsReceived = false; + } + } } //---------------------------------------- -bool GNSS_MOSAIC::updateSD() +void GNSS_MOSAIC::updateSD() { - // TODO: use sdCardPresent() here? Except the mosaic seems pretty good at figuring out if the microSD is present... + // See comments in update() above + // updateSD() is probably the best place to check if an SD card has been inserted / removed + static bool previousCardPresent = sdCardPresent(); // This only gets called once - char diskInfo[200]; - bool response = - sendAndWaitForIdle("ldi,DSK1\n\r", "DiskInfo", 1000, 25, &diskInfo[0], sizeof(diskInfo), false); // No debug - if (response) + // Check if card has been inserted / removed + // In both cases, perform a soft reset + // The X5 is not good at recognizing if a card is inserted after power-on, or was present but has been removed + if (previousCardPresent != sdCardPresent()) { - char *ptr = strstr(diskInfo, " total=\""); - if (ptr == nullptr) - return false; - ptr += strlen(" total=\""); - sscanf(ptr, "%llu\"", &sdCardSize); - ptr = strstr(ptr, " free=\""); - if (ptr == nullptr) - return false; - ptr += strlen(" free=\""); - sscanf(ptr, "%llu\"", &sdFreeSpace); - } + previousCardPresent = sdCardPresent(); - return response; + systemPrint("microSD card has been "); + systemPrint(previousCardPresent ? "inserted" : "removed"); + systemPrintln(". Performing a soft reset..."); + + sendWithResponse("erst,soft,none\n\r", "ResetReceiver"); + + // Allow many retries + int retries = 0; + int retryLimit = 30; + + // If the card has been removed, the soft reset takes extra time. Allow more retries + if (!previousCardPresent) + retryLimit = 40; + + // Set COM4 to: CMD input (only), SBF output (only) + while (!sendWithResponse("sdio,COM4,CMD,SBF\n\r", "DataInOut")) + { + if (retries == retryLimit) + break; + retries++; + sendWithResponse("SSSSSSSSSSSSSSSSSSSS\n\r", "COM4>"); // Send escape sequence + } + + if (retries == retryLimit) + { + systemPrintln("Soft reset failed!"); + } + } } //---------------------------------------- @@ -2584,9 +2833,17 @@ void processSBFReceiverSetup(SEMP_PARSE_STATE *parse, uint16_t type) if (sempSbfGetBlockNumber(parse) == 5902) { snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", sempSbfGetString(parse, 156)); // Extract RxSerialNumber + snprintf(gnssFirmwareVersion, sizeof(gnssFirmwareVersion), "%s", sempSbfGetString(parse, 196)); // Extract RXVersion + // gnssFirmwareVersion is 4.14.4, 4.14.10.1, etc. + // Create gnssFirmwareVersionInt from the first two fields only, so it will fit on the OLED + int verMajor = 0; + int verMinor = 0; + sscanf(gnssFirmwareVersion, "%d.%d.", &verMajor, &verMinor); // Do we care if this fails? + gnssFirmwareVersionInt = (verMajor * 100) + verMinor; + GNSS_MOSAIC *mosaic = (GNSS_MOSAIC *)gnss; mosaic->_receiverSetupSeen = true; } @@ -2605,14 +2862,25 @@ void processUart1SBF(SEMP_PARSE_STATE *parse, uint16_t type) { GNSS_MOSAIC *mosaic = (GNSS_MOSAIC *)gnss; - // if ((settings.debugGnss == true) && !inMainMenu) + // if (((settings.debugGnss == true) || PERIODIC_DISPLAY(PD_GNSS_DATA_RX)) && !inMainMenu) + // { + // // Don't call PERIODIC_CLEAR(PD_GNSS_DATA_RX); here. Let processUart1Message do it via rtkParse // systemPrintf("Processing SBF Block %d (%d bytes) from mosaic-X5\r\n", sempSbfGetBlockNumber(parse), // parse->length); + // } // If this is PVTGeodetic, extract some data if (sempSbfGetBlockNumber(parse) == 4007) mosaic->storeBlock4007(parse); + // If this is ChannelStatus, extract some data + if (sempSbfGetBlockNumber(parse) == 4013) + mosaic->storeBlock4013(parse); + + // If this is DiskStatus, extract some data + if (sempSbfGetBlockNumber(parse) == 4059) + mosaic->storeBlock4059(parse); + // If this is InputLink, extract some data if (sempSbfGetBlockNumber(parse) == 4090) mosaic->storeBlock4090(parse); @@ -2636,8 +2904,11 @@ void processUart1SBF(SEMP_PARSE_STATE *parse, uint16_t type) //---------------------------------------- void processUart1SPARTN(SEMP_PARSE_STATE *parse, uint16_t type) { - if ((settings.debugCorrections == true) && !inMainMenu) + if (((settings.debugGnss == true) || PERIODIC_DISPLAY(PD_GNSS_DATA_RX)) && !inMainMenu) + { + // Don't call PERIODIC_CLEAR(PD_GNSS_DATA_RX); here. Let processUart1Message do it via rtkParse systemPrintf("Pushing %d SPARTN (L-Band) bytes to PPL for mosaic-X5\r\n", parse->length); + } if (online.ppl == false && settings.debugCorrections == true && (!inMainMenu)) systemPrintln("Warning: PPL is offline"); diff --git a/Firmware/RTK_Everywhere/GNSS_None.h b/Firmware/RTK_Everywhere/GNSS_None.h index 9eeec57b5..8d717286f 100644 --- a/Firmware/RTK_Everywhere/GNSS_None.h +++ b/Firmware/RTK_Everywhere/GNSS_None.h @@ -429,6 +429,10 @@ class GNSS_None : public GNSS { } + void modifyGst(char *nmeaSentence, uint16_t *sentenceLength) + { + } + // Print the module type and firmware version void printModuleInfo() { diff --git a/Firmware/RTK_Everywhere/GNSS_UM980.ino b/Firmware/RTK_Everywhere/GNSS_UM980.ino index ebd61f1af..07e035801 100644 --- a/Firmware/RTK_Everywhere/GNSS_UM980.ino +++ b/Firmware/RTK_Everywhere/GNSS_UM980.ino @@ -166,6 +166,17 @@ bool GNSS_UM980::configureBase() return (false); } + // Trusting the saved configuration does not seem to work on the UM980. + // It looks like the GPGGA NMEA output does not restart...? + // (Re)configuration is quick. Doing this every time is not much of an overhead. + // + // if (settings.gnssConfiguredBase) + // { + // if (settings.debugGnss) + // systemPrintln("Skipping UM980 Base configuration"); + // return true; + // } + disableAllOutput(); bool response = true; @@ -186,10 +197,7 @@ bool GNSS_UM980::configureBase() response &= enableNMEA(); // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the UM980 at next boot - bool settingsWereSaved = _um980->saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= _um980->saveConfiguration(); if (response == false) { @@ -199,6 +207,8 @@ bool GNSS_UM980::configureBase() if (settings.debugGnss) systemPrintln("UM980 Base configured"); + settings.gnssConfiguredBase = response; + return (response); } @@ -219,6 +229,13 @@ bool GNSS_UM980::configureOnce() Enable selected RTCM messages on COM3 */ + // // If our settings haven't changed, trust GNSS's settings + if (settings.gnssConfiguredOnce) + { + systemPrintln("UM980 configuration maintained"); + return (true); + } + if (settings.debugGnss) debuggingEnable(); // Print all debug to Serial @@ -269,14 +286,13 @@ bool GNSS_UM980::configureOnce() systemPrintln("UM980 configuration updated"); // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the UM980 at next boot - bool settingsWereSaved = _um980->saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= _um980->saveConfiguration(); } else online.gnss = false; // Take it offline + settings.gnssConfiguredOnce = response; + return (response); } @@ -294,13 +310,6 @@ bool GNSS_UM980::configureNtpMode() //---------------------------------------- bool GNSS_UM980::configureGNSS() { - // Skip configuring the UM980 if no new changes are necessary - if (settings.updateGNSSSettings == false) - { - systemPrintln("UM980 configuration maintained"); - return (true); - } - for (int x = 0; x < 3; x++) { if (configureOnce()) @@ -338,6 +347,16 @@ bool GNSS_UM980::configureRover() return (false); } + // Trusting the saved configuration does not seem to work on the UM980. + // It looks like the GPGGA NMEA output does not restart...? + // (Re)configuration is quick. Doing this every time is not much of an overhead. + // + // if (settings.gnssConfiguredRover) + // { + // systemPrintln("Skipping UM980 Rover configuration"); + // return (true); + // } + disableAllOutput(); bool response = true; @@ -368,16 +387,15 @@ bool GNSS_UM980::configureRover() response &= enableNMEA(); // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the UM980 at next boot - bool settingsWereSaved = _um980->saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; + response &= _um980->saveConfiguration(); if (response == false) { systemPrintln("UM980 Rover failed to configure"); } + settings.gnssConfiguredRover = response; + return (response); } @@ -450,24 +468,26 @@ bool GNSS_UM980::enableNMEA() // has UNLOG or similar. if (settings.um980MessageRatesNMEA[messageNumber] > 0) { - if (_um980->setNMEAPortMessage(umMessagesNMEA[messageNumber].msgTextName, "COM3", - settings.um980MessageRatesNMEA[messageNumber]) == false) - { - if (settings.debugGnss) - systemPrintf("Enable NMEA failed at messageNumber %d %s.\r\n", messageNumber, - umMessagesNMEA[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } + // If any one of the commands fails, report failure overall + response &= _um980->setNMEAPortMessage(umMessagesNMEA[messageNumber].msgTextName, "COM3", + settings.um980MessageRatesNMEA[messageNumber]); + + if (response == false && settings.debugGnss) + systemPrintf("Enable NMEA failed at messageNumber %d %s.\r\n", messageNumber, + umMessagesNMEA[messageNumber].msgTextName); // If we are using IP based corrections, we need to send local data to the PPL // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 - if (strstr(settings.pointPerfectKeyDistributionTopic, "/ip") != nullptr) + if (settings.enablePointPerfectCorrections) { // Mark PPL required messages as enabled if rate > 0 - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPGGA") == 0) - gpggaEnabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPZDA") == 0) - gpzdaEnabled = true; + if (settings.um980MessageRatesNMEA[messageNumber] > 0) + { + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPGGA") == 0) + gpggaEnabled = true; + else if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPZDA") == 0) + gpzdaEnabled = true; + } } } } @@ -497,14 +517,13 @@ bool GNSS_UM980::enableRTCMBase() // has UNLOG or similar. if (settings.um980MessageRatesRTCMBase[messageNumber] > 0) { - if (_um980->setRTCMPortMessage(umMessagesRTCM[messageNumber].msgTextName, "COM3", - settings.um980MessageRatesRTCMBase[messageNumber]) == false) - { - if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s.", messageNumber, - umMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } + // If any one of the commands fails, report failure overall + response &= _um980->setRTCMPortMessage(umMessagesRTCM[messageNumber].msgTextName, "COM3", + settings.um980MessageRatesRTCMBase[messageNumber]); + + if (response == false && settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s.", messageNumber, + umMessagesRTCM[messageNumber].msgTextName); } } @@ -542,14 +561,17 @@ bool GNSS_UM980::enableRTCMRover() if (settings.enablePointPerfectCorrections) { // Mark PPL required messages as enabled if rate > 0 - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1019") == 0) - rtcm1019Enabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1020") == 0) - rtcm1020Enabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1042") == 0) - rtcm1042Enabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1046") == 0) - rtcm1046Enabled = true; + if (settings.um980MessageRatesRTCMRover[messageNumber] > 0) + { + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1019") == 0) + rtcm1019Enabled = true; + else if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1020") == 0) + rtcm1020Enabled = true; + else if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1042") == 0) + rtcm1042Enabled = true; + else if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1046") == 0) + rtcm1046Enabled = true; + } } } } @@ -1419,7 +1441,9 @@ void GNSS_UM980::menuMessagesSubtype(float *localMessageRate, const char *messag printUnknown(incoming); } - settings.updateGNSSSettings = true; // Update the GNSS config at the next boot + settings.gnssConfiguredOnce = false; // Update the GNSS config at the next boot + settings.gnssConfiguredBase = false; + settings.gnssConfiguredRover = false; clearBuffer(); // Empty buffer of any newline chars } @@ -1961,9 +1985,9 @@ void um980FirmwareBeginUpdate() } } - if (digitalRead(pin_powerButton) == HIGH) + if (readAnalogPinAsDigital(pin_powerButton) == HIGH) { - while (digitalRead(pin_powerButton) == HIGH) + while (readAnalogPinAsDigital(pin_powerButton) == HIGH) delay(100); // Remove file and reset to exit pass-through mode diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.h b/Firmware/RTK_Everywhere/GNSS_ZED.h index 53090bbe5..584469238 100644 --- a/Firmware/RTK_Everywhere/GNSS_ZED.h +++ b/Firmware/RTK_Everywhere/GNSS_ZED.h @@ -365,7 +365,7 @@ class GNSS_ZED : GNSS // one core? bool iAmLocked = false; - SFE_UBLOX_GNSS_SUPER *_zed = nullptr; // Don't instantiate until we know what gnssPlatform we're on + SFE_UBLOX_GNSS_SUPER *_zed = nullptr; // Library class instance // Record rxBytes 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 diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.ino b/Firmware/RTK_Everywhere/GNSS_ZED.ino index 38e541a06..dc7b2e1ce 100644 --- a/Firmware/RTK_Everywhere/GNSS_ZED.ino +++ b/Firmware/RTK_Everywhere/GNSS_ZED.ino @@ -6,14 +6,6 @@ GNSS_ZED.ino #ifdef COMPILE_ZED -extern int NTRIPCLIENT_MS_BETWEEN_GGA; - -#ifdef COMPILE_NETWORK -extern NetworkClient *ntripClient; -#endif // COMPILE_NETWORK - -extern unsigned long lastGGAPush; - //---------------------------------------- // If we have decryption keys, configure module // Note: don't check online.lband_neo here. We could be using ip corrections @@ -64,10 +56,10 @@ void GNSS_ZED::applyPointPerfectKeys() updateCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed } - _zed->setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C + _zed->setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1, VAL_LAYER_ALL); // Enable UBX-RXM-COR messages on I2C _zed->setVal8(UBLOX_CFG_NAVHPG_DGNSSMODE, - 3); // Set the differential mode - ambiguities are fixed whenever possible + 3, VAL_LAYER_ALL); // Set the differential mode - ambiguities are fixed whenever possible bool response = _zed->setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW, settings.pointPerfectCurrentKey, nextKeyLengthBytes, nextKeyGPSWeek, @@ -142,8 +134,6 @@ void GNSS_ZED::begin() if (_zed == nullptr) _zed = new SFE_UBLOX_GNSS_SUPER(); - // Note: we don't need to skip this for configureViaEthernet because the ZED is on I2C only - not SPI - if (_zed->begin(*i2c_0) == false) { systemPrintln("GNSS Failed to begin. Trying again."); @@ -217,94 +207,94 @@ void GNSS_ZED::begin() } printModuleInfo(); // Print module type and firmware version - } - UBX_SEC_UNIQID_data_t chipID; - if (_zed->getUniqueChipId(&chipID)) - { - snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", _zed->getUniqueChipIdStr(&chipID)); - } + UBX_SEC_UNIQID_data_t chipID; + if (_zed->getUniqueChipId(&chipID)) + { + snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", _zed->getUniqueChipIdStr(&chipID)); - systemPrintln("GNSS ZED online"); + systemPrintln("GNSS ZED online"); + online.gnss = true; + return; + } + } - online.gnss = true; + systemPrintln("GNSS ZED offline"); + displayGNSSFail(1000); } //---------------------------------------- -// Setup the timepulse output on the PPS pin for external triggering -// Setup TM2 time stamp input as need +// Setup the timepulse output on the PPS pin for external triggering. +// Setup TM2 time stamp input as need. +// Allow this to be called multiple times so that the callback can be +// set or cleared as needed. +// This will be called once by setup and possibly multiple times by +// menuPortsMultiplexed. //---------------------------------------- bool GNSS_ZED::beginExternalEvent() { if (online.gnss == false) return (false); - // If our settings haven't changed, trust ZED's settings - if (settings.updateGNSSSettings == false) - { - log_d("Skipping ZED Trigger configuration"); - return (true); - } - - if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) - return (true); // No need to configure PPS if port is not selected - bool response = true; - if (settings.enableExternalHardwareEventLogging) + // Assumes EXTINT is always routed through the multiplexer... + if (settings.enableExternalHardwareEventLogging && (settings.dataPortChannel == MUX_PPS_EVENTTRIGGER)) { - _zed->setAutoTIMTM2callbackPtr( - &eventTriggerReceived); // Enable automatic TIM TM2 messages with callback to eventTriggerReceived + response &= _zed->setAutoTIMTM2callbackPtr( + &eventTriggerReceived, VAL_LAYER_ALL); // Enable automatic TIM TM2 messages with callback to eventTriggerReceived } else - _zed->setAutoTIMTM2callbackPtr(nullptr); + { + response &= _zed->setAutoTIMTM2callbackPtr(nullptr, VAL_LAYER_ALL); + } + + if (response == false) + systemPrintln("beginExternalEvent failed"); return (response); } //---------------------------------------- // Setup the timepulse output on the PPS pin for external triggering +// Allow this to be called multiple times. +// This will be called once by setup and possibly multiple times by +// menuPortsMultiplexed. //---------------------------------------- bool GNSS_ZED::beginPPS() { if (online.gnss == false) return (false); - // If our settings haven't changed, trust ZED's settings - if (settings.updateGNSSSettings == false) - { - systemPrintln("Skipping ZED Trigger configuration"); - return (true); - } - - if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) - return (true); // No need to configure PPS if port is not selected - bool response = true; - response &= _zed->newCfgValset(); - response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us) - response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio) - response &= - _zed->addCfgValset(UBLOX_CFG_TP_USE_LOCKED_TP1, - 1); // Use CFG-TP-PERIOD_LOCK_TP1 and CFG-TP-LEN_LOCK_TP1 as soon as GNSS time is valid - response &= _zed->addCfgValset(UBLOX_CFG_TP_TP1_ENA, settings.enableExternalPulse); // Enable/disable timepulse - response &= - _zed->addCfgValset(UBLOX_CFG_TP_POL_TP1, settings.externalPulsePolarity); // 0 = falling, 1 = rising edge + // Assumes PPS is always routed through the multiplexer... + if (settings.dataPortChannel == MUX_PPS_EVENTTRIGGER) + { + response &= _zed->newCfgValset(VAL_LAYER_ALL); + response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio) + response &= + _zed->addCfgValset(UBLOX_CFG_TP_USE_LOCKED_TP1, + 1); // Use CFG-TP-PERIOD_LOCK_TP1 and CFG-TP-LEN_LOCK_TP1 as soon as GNSS time is valid + response &= _zed->addCfgValset(UBLOX_CFG_TP_TP1_ENA, settings.enableExternalPulse); // Enable/disable timepulse + response &= + _zed->addCfgValset(UBLOX_CFG_TP_POL_TP1, settings.externalPulsePolarity); // 0 = falling, 1 = rising edge - // While the module is _locking_ to GNSS time, turn off pulse - response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us - response &= _zed->addCfgValset(UBLOX_CFG_TP_LEN_TP1, 0); // Set the pulse length in us + // While the module is _locking_ to GNSS time, turn off pulse + response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us + response &= _zed->addCfgValset(UBLOX_CFG_TP_LEN_TP1, 0); // Set the pulse length in us - // When the module is _locked_ to GNSS time, make it generate 1Hz (Default is 100ms high, 900ms low) - response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, - settings.externalPulseTimeBetweenPulse_us); // Set the period between pulses is us - response &= - _zed->addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, settings.externalPulseLength_us); // Set the pulse length in us - response &= _zed->sendCfgValset(); + // When the module is _locked_ to GNSS time, make it generate 1Hz (Default is 100ms high, 900ms low) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, + settings.externalPulseTimeBetweenPulse_us); // Set the period between pulses is us + response &= + _zed->addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, settings.externalPulseLength_us); // Set the pulse length in us + response &= _zed->sendCfgValset(); + } if (response == false) - systemPrintln("beginExternalTriggers config failed"); + systemPrintln("beginPPS failed"); return (response); } @@ -337,16 +327,13 @@ bool GNSS_ZED::configureBase() if (online.gnss == false) return (false); - // If our settings haven't changed, and this is first config since power on, trust ZED's settings - if (settings.updateGNSSSettings == false && firstPowerOn) + if (settings.gnssConfiguredBase) { - firstPowerOn = false; // Next time user switches modes, new settings will be applied - log_d("Skipping ZED Base configuration"); - return (true); + if (settings.debugGnss) + systemPrintln("Skipping ZED Base configuration"); + return true; } - firstPowerOn = false; // If we switch between rover/base in the future, force config of module. - update(); // Regularly poll to get latest data _zed->setNMEAGPGGAcallbackPtr( @@ -364,7 +351,7 @@ bool GNSS_ZED::configureBase() bool response = true; // In Base mode we force 1Hz - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, 1); @@ -429,6 +416,10 @@ bool GNSS_ZED::configureBase() if (!success) systemPrintln("Base config fail"); + // The configuration should be saved to RAM+BBR+FLASH. No need to saveConfiguration here. + + settings.gnssConfiguredBase = success; + return (success); } @@ -442,7 +433,12 @@ bool GNSS_ZED::configureNtpMode() if (online.gnss == false) return (false); - gnss->update(); // Regularly poll to get latest data + // This is only called by STATE_NTPSERVER_NOT_STARTED + // I guess it is OK to always do the configuration? + // stateUpdate clear the Base and Rover configuration flags + // to ensure the configuration is re-applied when we exit this mode + + update(); // Regularly poll to get latest data // Disable GPGGA call back that may have been set during Rover NTRIP Client mode _zed->setNMEAGPGGAcallbackPtr(nullptr); @@ -458,7 +454,7 @@ bool GNSS_ZED::configureNtpMode() bool response = true; // In NTP mode we force 1Hz - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, 1); @@ -509,12 +505,15 @@ 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); } //---------------------------------------- // Setup the u-blox module for any setup (base or rover) +// This is the equivalent of configureOnce on the other platforms // In general we check if the setting is incorrect before writing it. Otherwise, the set commands have, on rare // occasion, become corrupt. The worst is when the I2C port gets turned off or the I2C address gets borked. //---------------------------------------- @@ -524,6 +523,7 @@ bool GNSS_ZED::configureGNSS() return (false); bool response = true; + bool success = true; // Turn on/off debug messages if (settings.debugGnss) @@ -535,12 +535,12 @@ bool GNSS_ZED::configureGNSS() // Redundant - also done by gnssConfigure // checkGNSSArrayDefaults(); - // Always configure the callbacks - even if settings.updateGNSSSettings is false + // Configure the callbacks response &= - _zed->setAutoPVTcallbackPtr(&storePVTdata); // Enable automatic NAV PVT messages with callback to storePVTdata + _zed->setAutoPVTcallbackPtr(&storePVTdata, VAL_LAYER_ALL); // Enable automatic NAV PVT messages with callback to storePVTdata response &= _zed->setAutoHPPOSLLHcallbackPtr( - &storeHPdata); // Enable automatic NAV HPPOSLLH messages with callback to storeHPdata + &storeHPdata, VAL_LAYER_ALL); // Enable automatic NAV HPPOSLLH messages with callback to storeHPdata _zed->setRTCM1005InputcallbackPtr( &storeRTCM1005data); // Configure a callback for RTCM 1005 - parsed from pushRawData _zed->setRTCM1006InputcallbackPtr( @@ -548,22 +548,22 @@ bool GNSS_ZED::configureGNSS() if (present.timePulseInterrupt) response &= _zed->setAutoTIMTPcallbackPtr( - &storeTIMTPdata); // Enable automatic TIM TP messages with callback to storeTIMTPdata + &storeTIMTPdata, VAL_LAYER_ALL); // Enable automatic TIM TP messages with callback to storeTIMTPdata if (present.antennaShortOpen) { - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_HW_ANT_CFG_SHORTDET, 1); // Enable antenna short detection response &= _zed->addCfgValset(UBLOX_CFG_HW_ANT_CFG_OPENDET, 1); // Enable antenna open detection response &= _zed->sendCfgValset(); response &= _zed->setAutoMONHWcallbackPtr( - &storeMONHWdata); // Enable automatic MON HW messages with callback to storeMONHWdata + &storeMONHWdata, VAL_LAYER_ALL); // Enable automatic MON HW messages with callback to storeMONHWdata } // Add a callback for UBX-MON-COMMS - response &= _zed->setAutoMONCOMMScallbackPtr(&storeMONCOMMSdata); + response &= _zed->setAutoMONCOMMScallbackPtr(&storeMONCOMMSdata, VAL_LAYER_ALL); // Enable RTCM3 if needed - if not enable NMEA IN to keep skipped updated response &= setCorrRadioExtPort(settings.enableExtCorrRadio, true); // Force the setting @@ -572,12 +572,13 @@ bool GNSS_ZED::configureGNSS() { systemPrintln("GNSS initial configuration (callbacks, short detection, radio port) failed"); } + success &= response; response = true; // Reset - // Configuring the ZED can take more than 2000ms. We save configuration to - // ZED so there is no need to update settings unless user has modified + // Configuring the ZED can take more than 2000ms. Configuration is saved to + // ZED RAM+BBR so there is no need to update settings unless user has modified // the settings file or internal settings. - if (settings.updateGNSSSettings == false) + if (settings.gnssConfiguredOnce) { systemPrintln("ZED-F9x configuration maintained"); return (true); @@ -599,7 +600,7 @@ bool GNSS_ZED::configureGNSS() } // The first thing we do is go to 1Hz to lighten any I2C traffic from a previous configuration - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, 1); @@ -701,22 +702,25 @@ bool GNSS_ZED::configureGNSS() if (response == false) systemPrintln("Module failed config block 0"); + success &= response; response = true; // Reset // Enable the constellations the user has set response &= setConstellations(); // 19 messages. Send newCfg or sendCfg with value set if (response == false) systemPrintln("Module failed config block 1"); + success &= response; response = true; // Reset // Make sure the appropriate messages are enabled response &= setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set if (response == false) systemPrintln("Module failed config block 2"); + success &= response; response = true; // Reset // Disable NMEA messages on all but UART1 - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, 0); response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C, 0); @@ -747,10 +751,18 @@ bool GNSS_ZED::configureGNSS() if (response == false) systemPrintln("Module failed config block 3"); - if (response) - systemPrintln("ZED-F9x configuration update"); + success &= response; - return (response); + if (success) + { + systemPrintln("ZED-F9x configuration updated"); + } + + settings.gnssConfiguredOnce = success; + + // The configuration should be saved to RAM+BBR+FLASH. No need to saveConfiguration here. + + return (success); } //---------------------------------------- @@ -764,16 +776,13 @@ bool GNSS_ZED::configureRover() return (false); } - // If our settings haven't changed, and this is first config since power on, trust GNSS's settings - if (settings.updateGNSSSettings == false && firstPowerOn) + // If our settings haven't changed, trust GNSS's settings + if (settings.gnssConfiguredRover) { - firstPowerOn = false; // Next time user switches modes, new settings will be applied - log_d("Skipping ZED Rover configuration"); + systemPrintln("Skipping ZED Rover configuration"); return (true); } - firstPowerOn = false; // If we switch between rover/base in the future, force config of module. - update(); // Regularly poll to get latest data bool success = false; @@ -788,7 +797,7 @@ bool GNSS_ZED::configureRover() bool response = true; // Set output rate - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, settings.measurementRateMs); response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, settings.navigationRate); @@ -841,6 +850,10 @@ 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. return (success); } @@ -866,15 +879,16 @@ void GNSS_ZED::enableGgaForNtrip() if (online.gnss) { // Set the Main Talker ID to "GP". The NMEA GGA messages will be GPGGA instead of GNGGA - _zed->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 1); - _zed->setNMEAGPGGAcallbackPtr(&pushGPGGA); // Set up the callback for GPGGA + _zed->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 1, VAL_LAYER_ALL); + _zed->setNMEAGPGGAcallbackPtr(&zedPushGPGGA); // Set up the callback for GPGGA float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; if (measurementFrequency < 0.2) measurementFrequency = 0.2; // 0.2Hz * 5 = 1 measurement every 5 seconds - log_d("Adjusting GGA setting to %f", measurementFrequency); + if (settings.debugGnss) + systemPrintf("Adjusting GGA setting to %f\r\n", measurementFrequency); _zed->setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, - measurementFrequency); // Enable GGA over I2C. Tell the module to output GGA every second + measurementFrequency, VAL_LAYER_ALL); // Enable GGA over I2C. Tell the module to output GGA every second } } @@ -887,7 +901,7 @@ bool GNSS_ZED::enableRTCMTest() { if (online.gnss) { - _zed->newCfgValset(); // Create a new Configuration Item VALSET message + _zed->newCfgValset(VAL_LAYER_RAM); // Create a new Configuration Item VALSET message _zed->addCfgValset(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART2, 1); // Enable message 1230 every second _zed->sendCfgValset(); // Send the VALSET return true; @@ -902,8 +916,16 @@ void GNSS_ZED::factoryReset() { if (online.gnss) { - _zed->factoryDefault(); // Reset everything: baud rate, I2C address, update rate, everything. And save to BBR. - _zed->saveConfiguration(); + // Set the clearMask and loadMask to 0xFFFF. Set deviceMask to devBBR | devFlash + uint8_t clearAndLoadMask[] = { 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0x03 }; + _zed->cfgCfg(clearAndLoadMask, 13); + + delay(2000); + + // Set the saveMask to 0xFFFF. Set deviceMask to devBBR | devFlash + uint8_t saveMask[] = { 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0x03 }; + _zed->cfgCfg(saveMask, 13); + _zed->hardReset(); // Perform a reset leading to a cold start (zero info start-up) } } @@ -959,7 +981,7 @@ bool GNSS_ZED::fixedBaseStart() // Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed response &= _zed->addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 0); // Position in ECEF response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_X, majorEcefX); @@ -999,7 +1021,7 @@ bool GNSS_ZED::fixedBaseStart() // systemPrintf("major (should be 156022): %ld\r\n", majorAlt); // systemPrintf("minor (should be 84): %ld\r\n", minorAlt); - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed response &= _zed->addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 1); // Position in LLH response &= _zed->addCfgValset(UBLOX_CFG_TMODE_LAT, majorLat); @@ -1946,7 +1968,7 @@ bool GNSS_ZED::setBaudrate(uint32_t baudRate) { if (online.gnss) return _zed->setVal32(UBLOX_CFG_UART1_BAUDRATE, - (115200 * 2)); // Defaults to 230400 to maximize message output support + (115200 * 2), VAL_LAYER_ALL); // Defaults to 230400 to maximize message output support return false; } @@ -1962,7 +1984,7 @@ bool GNSS_ZED::setConstellations() bool response = true; - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); // GPS int gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS); @@ -2034,7 +2056,7 @@ bool GNSS_ZED::setCorrRadioExtPort(bool enable, bool force) if (force || (enable != _corrRadioExtPortEnabled)) { - bool response = _zed->newCfgValset(); + bool response = _zed->newCfgValset(VAL_LAYER_ALL); // Leave NMEA IN (poll requests) enabled so MON-COMMS skipped keeps updating response &= _zed->addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, enable ? 0 : 1); @@ -2072,7 +2094,7 @@ bool GNSS_ZED::setCorrRadioExtPort(bool enable, bool force) bool GNSS_ZED::setDataBaudRate(uint32_t baud) { if (online.gnss) - return _zed->setVal32(UBLOX_CFG_UART1_BAUDRATE, baud); + return _zed->setVal32(UBLOX_CFG_UART1_BAUDRATE, baud, VAL_LAYER_ALL); return false; } @@ -2083,7 +2105,7 @@ bool GNSS_ZED::setElevation(uint8_t elevationDegrees) { if (online.gnss) { - _zed->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINELEV, elevationDegrees); // Set minimum elevation + _zed->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINELEV, elevationDegrees, VAL_LAYER_ALL); // Set minimum elevation return true; } return false; @@ -2161,7 +2183,7 @@ bool GNSS_ZED::setMessages(int maxRetries) while (messageNumber < MAX_UBX_MSG) { - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); do { @@ -2215,7 +2237,7 @@ bool GNSS_ZED::setMessagesUsb(int maxRetries) while (messageNumber < MAX_UBX_MSG) { - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); do { @@ -2244,7 +2266,7 @@ bool GNSS_ZED::setMinCnoRadio(uint8_t cnoValue) { if (online.gnss) { - _zed->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINCNO, cnoValue); + _zed->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINCNO, cnoValue, VAL_LAYER_ALL); return true; } return false; @@ -2257,7 +2279,7 @@ bool GNSS_ZED::setModel(uint8_t modelNumber) { if (online.gnss) { - _zed->setVal8(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)modelNumber); // Set dynamic model + _zed->setVal8(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)modelNumber, VAL_LAYER_ALL); // Set dynamic model return true; } return false; @@ -2267,7 +2289,7 @@ bool GNSS_ZED::setModel(uint8_t modelNumber) bool GNSS_ZED::setRadioBaudRate(uint32_t baud) { if (online.gnss) - return _zed->setVal32(UBLOX_CFG_UART2_BAUDRATE, baud); + return _zed->setVal32(UBLOX_CFG_UART2_BAUDRATE, baud, VAL_LAYER_ALL); return false; } @@ -2310,7 +2332,7 @@ bool GNSS_ZED::setRate(double secondsBetweenSolutions) // systemPrintf("measurementRate / navRate: %d / %d\r\n", measRate, navRate); bool response = true; - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, measRate); response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, navRate); @@ -2354,7 +2376,7 @@ bool GNSS_ZED::setTalkerGNGGA() { bool success = true; success &= - _zed->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA + _zed->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 3, VAL_LAYER_ALL); // Return talker ID to GNGGA after NTRIP Client set to GPGGA success &= _zed->setNMEAGPGGAcallbackPtr(nullptr); // Remove callback return success; } @@ -2569,11 +2591,11 @@ bool GNSS_ZED::surveyInReset() return (false); // Disable survey-in mode - response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0); + response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0, VAL_LAYER_ALL); delay(1000); // Enable Survey in with bogus values - response &= _zed->newCfgValset(); + response &= _zed->newCfgValset(VAL_LAYER_ALL); response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable response &= _zed->addCfgValset(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, 40 * 10000); // 40.0m response &= _zed->addCfgValset(UBLOX_CFG_TMODE_SVIN_MIN_DUR, 1000); // 1000s @@ -2581,7 +2603,7 @@ bool GNSS_ZED::surveyInReset() delay(1000); // Disable survey-in mode - response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0); + response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0, VAL_LAYER_ALL); if (response == false) return (response); @@ -2608,7 +2630,7 @@ bool GNSS_ZED::surveyInStart() if (online.gnss == false) return (false); - _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode + _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0, VAL_LAYER_ALL); // Disable survey-in mode delay(100); bool needSurveyReset = false; @@ -2636,9 +2658,9 @@ bool GNSS_ZED::surveyInStart() } bool response = true; - response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable - response &= _zed->setVal32(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, settings.observationPositionAccuracy * 10000); - response &= _zed->setVal32(UBLOX_CFG_TMODE_SVIN_MIN_DUR, settings.observationSeconds); + response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 1, VAL_LAYER_ALL); // Survey-in enable + response &= _zed->setVal32(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, settings.observationPositionAccuracy * 10000, VAL_LAYER_ALL); + response &= _zed->setVal32(UBLOX_CFG_TMODE_SVIN_MIN_DUR, settings.observationSeconds, VAL_LAYER_ALL); if (response == false) { @@ -2704,7 +2726,7 @@ void GNSS_ZED::updateCorrectionsSource(uint8_t source) // This is important. Retry if needed int retries = 0; - while ((!_zed->setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, source)) && (retries < 3)) + while ((!_zed->setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, source, VAL_LAYER_ALL)) && (retries < 3)) retries++; if (retries < 3) { @@ -2770,30 +2792,6 @@ void checkRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct) } } -// Call back for incoming GGA data to be pushed to NTRIP Caster -void pushGPGGA(NMEA_GGA_data_t *nmeaData) -{ -#ifdef COMPILE_NETWORK - // Provide the caster with our current position as needed - if (ntripClient->connected() && settings.ntripClient_TransmitGGA == true) - { - if (millis() - lastGGAPush > NTRIPCLIENT_MS_BETWEEN_GGA) - { - lastGGAPush = millis(); - - if (settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_GGA)) - { - PERIODIC_CLEAR(PD_NTRIP_CLIENT_GGA); - systemPrintf("NTRIP Client pushing GGA to server: %s", (const char *)nmeaData->nmea); - } - - // Push our current GGA sentence to caster - ntripClient->print((const char *)nmeaData->nmea); - } - } -#endif // COMPILE_NETWORK -} - // ZED-F9x call back void eventTriggerReceived(UBX_TIM_TM2_data_t *ubxDataStruct) { @@ -2913,4 +2911,11 @@ void inputMessageRate(uint8_t &localMessageRate, uint8_t messageNumber) localMessageRate = rate; } +// Push GGA sentence over NTRIP Client, to Caster, if enabled +// ZED uses callback, other platforms call this from processUart1Message() +void zedPushGPGGA(NMEA_GGA_data_t *nmeaData) +{ + pushGPGGA((char *)nmeaData->nmea); +} + #endif // COMPILE_ZED diff --git a/Firmware/RTK_Everywhere/HTTP_Client.ino b/Firmware/RTK_Everywhere/HTTP_Client.ino index c646636c8..7e14eb962 100644 --- a/Firmware/RTK_Everywhere/HTTP_Client.ino +++ b/Firmware/RTK_Everywhere/HTTP_Client.ino @@ -59,7 +59,6 @@ static uint32_t httpClientConnectionAttemptTimeout = 5 * 1000L; // Wait 5s static int httpClientConnectionAttemptsTotal; // Count the number of connection attempts absolutely static volatile uint32_t httpClientLastDataReceived; // Last time data was received via HTTP -static NetPriority_t httpClientPriority = NETWORK_OFFLINE; static NetworkClientSecure *httpSecureClient; @@ -276,7 +275,6 @@ void httpClientUpdate() case HTTP_CLIENT_ON: { if ((millis() - httpClientTimer) > httpClientConnectionAttemptTimeout) { - httpClientPriority = NETWORK_OFFLINE; httpClientSetState(HTTP_CLIENT_NETWORK_STARTED); } break; @@ -288,8 +286,8 @@ void httpClientUpdate() if (!httpClientModeNeeded) httpClientStop(true); - // Wait until the network is connected to the media - else if (networkIsConnected(&httpClientPriority)) + // Wait until the network is connected + else if (networkHasInternet()) httpClientSetState(HTTP_CLIENT_CONNECTING_2_SERVER); break; } @@ -297,7 +295,7 @@ void httpClientUpdate() // Connect to the HTTP server case HTTP_CLIENT_CONNECTING_2_SERVER: { // Determine if the network has failed - if (!networkIsConnected(&httpClientPriority)) + if (networkHasInternet() == false) { // Failed to connect to the network, attempt to restart the network httpClientStop(true); // Was httpClientRestart(); - #StopVsRestart @@ -347,13 +345,14 @@ void httpClientUpdate() } reportHeapNow(settings.debugHttpClientState); + online.httpClient = true; httpClientSetState(HTTP_CLIENT_CONNECTED); break; } case HTTP_CLIENT_CONNECTED: { // Determine if the network has failed - if (!networkIsConnected(&httpClientPriority)) + if (networkHasInternet() == false) { // Failed to connect to the network, attempt to restart the network httpClientStop(true); // Was httpClientRestart(); - #StopVsRestart @@ -541,14 +540,14 @@ void httpClientUpdate() settings.pointPerfectNextKeyStart = (*jsonZtp)["dynamickeys"]["next"]["start"]; if (settings.debugCorrections || settings.debugHttpClientData) - pointperfectPrintKeyInformation(); + pointperfectPrintKeyInformation("HTTP Client"); // displayKeysUpdated(); ztpResponse = ZTP_SUCCESS; httpClientSetState(HTTP_CLIENT_COMPLETE); } - } // JSON Derialized correctly + } // JSON Deserialized correctly } // HTTP Response was 200 break; } @@ -557,7 +556,7 @@ void httpClientUpdate() // Hang here until httpClientModeNeeded is set to false by updateProvisioning case HTTP_CLIENT_COMPLETE: { // Determine if the network has failed - if (!networkIsConnected(&httpClientPriority)) + if (networkHasInternet() == false) // Failed to connect to the network, attempt to restart the network httpClientStop(true); // Was httpClientRestart(); - #StopVsRestart break; @@ -576,11 +575,4 @@ void httpClientValidateTables() reportFatalError("Fix httpClientStateNameEntries to match HTTPClientState"); } -bool httpClientIsConnected() -{ - if (httpClientState == HTTP_CLIENT_CONNECTED) - return true; - return false; -} - #endif // COMPILE_HTTP_CLIENT diff --git a/Firmware/RTK_Everywhere/LARA.ino b/Firmware/RTK_Everywhere/LARA.ino index aed99d24d..3a6bc1a48 100644 --- a/Firmware/RTK_Everywhere/LARA.ino +++ b/Firmware/RTK_Everywhere/LARA.ino @@ -128,7 +128,7 @@ NETWORK_POLL_SEQUENCE laraBootSequence[] = {laraPowerHigh, LARA_SETTLE_TIME, "Finish power off sequence"}, {networkDelay, (uintptr_t)&laraTimer, "Delay for power off"}, - // After a short delay if on other network device is available, turn on LARA + // After a short delay if no other network device is available, turn on LARA // State Parameter Description {networkStartDelayed, (30 * 1000), "Attempt to start cellular if necessary"}, {nullptr, 0, "Termination"}, @@ -138,7 +138,6 @@ NETWORK_POLL_SEQUENCE laraBootSequence[] = // (Remember that LARA_PWR is inverted by the RTK EVK level-shifter) NETWORK_POLL_SEQUENCE laraOnSequence[] = { // State Parameter Description - {cellularSimInstalled, 0, "SIM card validation"}, {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/LoRa.ino b/Firmware/RTK_Everywhere/LoRa.ino index 6e6e4ab34..7901fc3a7 100644 --- a/Firmware/RTK_Everywhere/LoRa.ino +++ b/Firmware/RTK_Everywhere/LoRa.ino @@ -482,9 +482,9 @@ void beginLoraFirmwareUpdate() while (serialGNSS->available()) Serial.write(serialGNSS->read()); - if (digitalRead(pin_powerButton) == HIGH) + if (readAnalogPinAsDigital(pin_powerButton) == HIGH) { - while (digitalRead(pin_powerButton) == HIGH) + while (readAnalogPinAsDigital(pin_powerButton) == HIGH) delay(100); // Remove file and reset to exit LoRa update pass-through mode diff --git a/Firmware/RTK_Everywhere/MQTT_Client.ino b/Firmware/RTK_Everywhere/MQTT_Client.ino index 64fc75483..bc3acfd8d 100644 --- a/Firmware/RTK_Everywhere/MQTT_Client.ino +++ b/Firmware/RTK_Everywhere/MQTT_Client.ino @@ -96,7 +96,7 @@ enum MQTTClientState { MQTT_CLIENT_OFF = 0, // Using Bluetooth or NTRIP server MQTT_CLIENT_ON, // WIFI_STATE_START state - MQTT_CLIENT_NETWORK_STARTED, // Connecting to WiFi access point or Ethernet + MQTT_CLIENT_WAIT_FOR_NETWORK, // Connecting to WiFi access point or Ethernet MQTT_CLIENT_CONNECTING_2_SERVER, // Connecting to the MQTT server MQTT_CLIENT_SERVICES_CONNECTED, // Connected to the MQTT services // Insert new states here @@ -106,7 +106,7 @@ enum MQTTClientState const char *const mqttClientStateName[] = { "MQTT_CLIENT_OFF", "MQTT_CLIENT_ON", - "MQTT_CLIENT_NETWORK_STARTED", + "MQTT_CLIENT_WAIT_FOR_NETWORK", "MQTT_CLIENT_CONNECTING_2_SERVER", "MQTT_CLIENT_SERVICES_CONNECTED", }; @@ -138,7 +138,6 @@ static uint32_t mqttClientConnectionAttemptTimeout; static int mqttClientConnectionAttemptsTotal; // Count the number of connection attempts absolutely static volatile uint32_t mqttClientLastDataReceived; // Last time data was received via MQTT -static NetPriority_t mqttClientPriority = NETWORK_OFFLINE; static NetworkClientSecure *mqttSecureClient; @@ -155,6 +154,34 @@ 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() { @@ -164,9 +191,7 @@ bool mqttClientConnectLimitReached() // Retry the connection a few times limitReached = (mqttClientConnectionAttempts >= MAX_MQTT_CLIENT_CONNECTION_ATTEMPTS); - bool enableMqttClient = true; - if (!settings.enablePointPerfectCorrections) - enableMqttClient = false; + bool enableMqttClient = mqttClientIsNeeded(); // Restart the MQTT client MQTT_CLIENT_STOP(limitReached || (!enableMqttClient)); @@ -220,7 +245,7 @@ void mqttClientPrintStateSummary() break; case MQTT_CLIENT_ON: - case MQTT_CLIENT_NETWORK_STARTED: + case MQTT_CLIENT_WAIT_FOR_NETWORK: systemPrint("Disconnected"); break; @@ -243,18 +268,7 @@ void mqttClientPrintStatus() byte minutes; byte seconds; - bool enableMqttClient = true; - if (!settings.enablePointPerfectCorrections) - enableMqttClient = 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, 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) - enableMqttClient = false; + bool enableMqttClient = mqttClientIsNeeded(); // Display MQTT Client status and uptime if (enableMqttClient && (EQ_RTK_MODE(mqttClientMode))) @@ -475,7 +489,7 @@ void mqttClientReceiveMessage(int messageSize) recordSystemSettings(); // Record these settings to unit if (settings.debugCorrections == true) - pointperfectPrintKeyInformation(); + pointperfectPrintKeyInformation("MQTT Topic"); } // Correction data from PP can go direct to GNSS @@ -543,6 +557,8 @@ void mqttClientReceiveMessage(int messageSize) sendSpartnToPpl(mqttData, mqttCount); bytesPushed += mqttCount; + + mqttClientDataReceived = true; } // Record the arrival of data over MQTT @@ -586,6 +602,23 @@ 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(); } } @@ -663,23 +696,20 @@ void mqttClientStop(bool shutdown) 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; +} + // 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 = true; - if (!settings.enablePointPerfectCorrections) - enableMqttClient = 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, 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) - enableMqttClient = false; + bool enableMqttClient = mqttClientIsNeeded(); // Shutdown the MQTT client when the mode or setting changes DMW_st(mqttClientSetState, mqttClientState); @@ -715,20 +745,19 @@ void mqttClientUpdate() case MQTT_CLIENT_ON: { if ((millis() - mqttClientTimer) > mqttClientConnectionAttemptTimeout) { - mqttClientPriority = NETWORK_OFFLINE; - mqttClientSetState(MQTT_CLIENT_NETWORK_STARTED); + mqttClientSetState(MQTT_CLIENT_WAIT_FOR_NETWORK); } break; } // Wait for a network media connection - case MQTT_CLIENT_NETWORK_STARTED: { + 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 (networkIsConnected(&mqttClientPriority)) + else if (networkHasInternet()) mqttClientSetState(MQTT_CLIENT_CONNECTING_2_SERVER); break; } @@ -736,7 +765,7 @@ void mqttClientUpdate() // Connect to the MQTT server case MQTT_CLIENT_CONNECTING_2_SERVER: { // Determine if the network has failed - if (!networkIsConnected(&mqttClientPriority)) + if (networkHasInternet() == false) { // Failed to connect to the network, attempt to restart the network mqttClientStop(true); // Was mqttClientRestart(); - #StopVsRestart @@ -853,8 +882,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) { @@ -882,7 +913,7 @@ void mqttClientUpdate() case MQTT_CLIENT_SERVICES_CONNECTED: { // Determine if the network has failed - if (!networkIsConnected(&mqttClientPriority)) + if (networkHasInternet() == false) { // Failed to connect to the network, attempt to restart the network mqttClientStop(true); // Was mqttClientRestart(); - #StopVsRestart diff --git a/Firmware/RTK_Everywhere/NTP.ino b/Firmware/RTK_Everywhere/NTP.ino index b2794b583..e35da17dd 100644 --- a/Firmware/RTK_Everywhere/NTP.ino +++ b/Firmware/RTK_Everywhere/NTP.ino @@ -75,7 +75,6 @@ const RtkMode_t ntpServerMode = RTK_MODE_NTP; // Locals //---------------------------------------- -static NetPriority_t ntpServerPriority = NETWORK_OFFLINE; static NetworkUDP *ntpServer; // This will be instantiated when we know the NTP port static uint8_t ntpServerState; static uint32_t lastLoggedNTPRequest; @@ -701,16 +700,6 @@ bool configureUbloxModuleNTP() if (online.gnss == false) return (false); - // If our settings haven't changed, and this is first config since power on, trust GNSS's settings - // Unless this is an EVK - where the GNSS has no battery-backed RAM - if ((productVariant != RTK_EVK) && (settings.updateGNSSSettings == false) && (firstPowerOn == true)) - { - firstPowerOn = false; // Next time user switches modes, new settings will be applied - log_d("Skipping ZED NTP configuration"); - return (true); - } - firstPowerOn = false; // If we switch between rover/base in the future, force config of module. - gnss->update(); // Regularly poll to get latest data return gnss->configureNtpMode(); } @@ -791,9 +780,8 @@ void ntpServerUpdate() if (EQ_RTK_MODE(ntpServerMode)) { // The NTP server only works over Ethernet - if (networkIsInterfaceOnline(NETWORK_ETHERNET)) + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) { - ntpServerPriority = NETWORK_OFFLINE; ntpServerSetState(NTP_STATE_NETWORK_CONNECTED); } } @@ -801,7 +789,7 @@ void ntpServerUpdate() case NTP_STATE_NETWORK_CONNECTED: // Determine if the network has failed - if (!networkIsConnected(&ntpServerPriority)) + if (networkHasInternet() == false) // Stop the NTP server, restart it if possible ntpServerStop(); @@ -825,7 +813,7 @@ void ntpServerUpdate() case NTP_STATE_SERVER_RUNNING: // Determine if the network has failed - if (!networkIsConnected(&ntpServerPriority)) + if (networkHasInternet() == false) // Stop the NTP server, restart it if possible ntpServerStop(); diff --git a/Firmware/RTK_Everywhere/NVM.ino b/Firmware/RTK_Everywhere/NVM.ino index e017a398c..4ed0e052e 100644 --- a/Firmware/RTK_Everywhere/NVM.ino +++ b/Firmware/RTK_Everywhere/NVM.ino @@ -18,7 +18,7 @@ createSettingsString(); In menuCommands.ino Generates a CSV string of all settings and their values - if they are inWebConfig - Called by startWebServer, onWsEvent, updateSettingWithValue (when setting / resetting a profile), + Called by webServerStart, onWsEvent, updateSettingWithValue (when setting / resetting a profile), Calls the stringRecord methods - also in menuCommands.ino Note: there is a _lot_ of commonality between this and recordSystemSettingsToFile. It may be possible to share code between the two. @@ -60,27 +60,19 @@ void loadSettings() // Temp store any variables from LFS that should override SD int resetCount = settings.resetCount; - SystemState stateFromLFS = settings.lastState; + bool gnssConfiguredOnce = settings.gnssConfiguredOnce; + bool gnssConfiguredRover = settings.gnssConfiguredRover; + bool gnssConfiguredBase = settings.gnssConfiguredBase; loadSystemSettingsFromFileSD(settingsFileName); settings.resetCount = resetCount; // resetCount from LFS should override SD - // SD is not accessible during configure-via-Ethernet - // If stateFromLFS indicates that the firmware is not in configure-via-Ethernet mode - if (!((stateFromLFS >= STATE_CONFIG_VIA_ETH_NOT_STARTED) && (stateFromLFS <= STATE_CONFIG_VIA_ETH_RESTART_BASE))) - { - // and lastState from SD indicates the firmware was previously in configure-via-Ethernet mode - if ((settings.lastState >= STATE_CONFIG_VIA_ETH_NOT_STARTED) && - (settings.lastState <= STATE_CONFIG_VIA_ETH_RESTART_BASE)) - { - // then reload the settings from LFS since they will include any changes made during configure-via-Ethernet - // mode - systemPrintln("Restart after config-via-ethernet. Overwriting SD settings with those from LittleFS"); - loadSystemSettingsFromFileLFS(settingsFileName); - recordSystemSettings(); // Overwrite SD settings with those from LFS - } - } + // Trust gnssConfigured from LittleFS over SD. + // LittleFS may have been erased, SD could be stale. + settings.gnssConfiguredOnce = gnssConfiguredOnce; + settings.gnssConfiguredRover = gnssConfiguredRover; + settings.gnssConfiguredBase = gnssConfiguredBase; // Change empty profile name to 'Profile1' etc if (strlen(settings.profileName) == 0) @@ -238,11 +230,6 @@ void recordSystemSettingsToFile(File *settingsFile) if (settingAvailableOnPlatform(i) == false) continue; - // Exceptions: - // runLogTest not stored in NVM - if (strcmp(rtkSettingsEntries[i].name, "runLogTest") == 0) - continue; - switch (rtkSettingsEntries[i].type) { default: @@ -637,6 +624,17 @@ void recordSystemSettingsToFile(File *settingsFile) } } break; + case tLgMRPqtm: { + // Record LG290P PQTM rates + for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) + { + char tempString[50]; // lg290pMessageRatesPQTM_EPE=1 + snprintf(tempString, sizeof(tempString), "%s%s=%d", rtkSettingsEntries[i].name, + lgMessagesPQTM[x].msgTextName, settings.lg290pMessageRatesPQTM[x]); + settingsFile->println(tempString); + } + } + break; case tLgConst: { // Record LG290P Constellations for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) @@ -1006,7 +1004,7 @@ bool parseLine(char *str) { const char *table[] = { "gnssFirmwareVersion", "gnssUniqueId", "neoFirmwareVersion", - "rtkFirmwareVersion", "rtkIdentifier", "runLogTest", + "rtkFirmwareVersion", "rtkIdentifier", }; const int tableEntries = sizeof(table) / sizeof(table[0]); @@ -1502,6 +1500,19 @@ bool parseLine(char *str) } } break; + case tLgMRPqtm: { + for (int x = 0; x < qualifier; x++) + { + if ((suffix[0] == lgMessagesPQTM[x].msgTextName[0]) && + (strcmp(suffix, lgMessagesPQTM[x].msgTextName) == 0)) + { + settings.lg290pMessageRatesPQTM[x] = d; + knownSetting = true; + break; + } + } + } + break; case tLgConst: { for (int x = 0; x < qualifier; x++) { @@ -1594,27 +1605,33 @@ void loadProfileNumber() } else { - log_d("profileNumber.txt not found"); - settings.updateGNSSSettings = true; // Force module update - recordProfileNumber(0); // Record profile + systemPrintln("profileNumber.txt not found"); + settings.gnssConfiguredOnce = false; // On the next boot, reapply all settings + settings.gnssConfiguredBase = false; + settings.gnssConfiguredRover = false; + recordProfileNumber(0); // Record profile } } else { - log_d("profileNumber.txt not found"); - settings.updateGNSSSettings = true; // Force module update - recordProfileNumber(0); // Record profile + systemPrintln("profileNumber.txt not found"); + settings.gnssConfiguredOnce = false; // On the next boot, reapply all settings + settings.gnssConfiguredBase = false; + settings.gnssConfiguredRover = false; + recordProfileNumber(0); // Record profile } // We have arbitrary limit of user profiles if (profileNumber >= MAX_PROFILE_COUNT) { - log_d("ProfileNumber invalid. Going to zero."); - settings.updateGNSSSettings = true; // Force module update - recordProfileNumber(0); // Record profile + systemPrintln("ProfileNumber invalid. Going to zero."); + settings.gnssConfiguredOnce = false; // On the next boot, reapply all settings + settings.gnssConfiguredBase = false; + settings.gnssConfiguredRover = false; + recordProfileNumber(0); // Record profile } - log_d("Using profile #%d", profileNumber); + systemPrintf("Using profile #%d\r\n", profileNumber); } // Record the given profile number as well as a config bool diff --git a/Firmware/RTK_Everywhere/Network.ino b/Firmware/RTK_Everywhere/Network.ino index 93f1e5872..03f5ffb53 100644 --- a/Firmware/RTK_Everywhere/Network.ino +++ b/Firmware/RTK_Everywhere/Network.ino @@ -113,9 +113,12 @@ NetIndex_t networkIndexTable[NETWORK_OFFLINE]; // Priority of the default network interface NetPriority_t networkPriority = NETWORK_OFFLINE; // Index into networkPriorityTable +// Interface event handlers set these flags, networkUpdate performs action +bool networkEventStop[NETWORK_OFFLINE]; + // The following entries have one bit per interface // Each bit represents an index into the networkInterfaceTable -NetMask_t networkOnline; // Track the online networks +NetMask_t networkHasInternet_bm; // Track the online networks NetMask_t networkSeqStarting; // Track the starting sequences NetMask_t networkSeqStopping; // Track the stopping sequences @@ -128,6 +131,8 @@ NETWORK_POLL_SEQUENCE *networkSequence[NETWORK_OFFLINE]; NetMask_t networkMdnsRunning; // Non-zero when mDNS is running +extern bool restartWiFi; // From WiFi.ino + //---------------------------------------- // Menu for configuring TCP/UDP interfaces //---------------------------------------- @@ -165,6 +170,9 @@ void menuTcpUdp() if (settings.enableUdpServer) systemPrintf("7) UDP Server Port: %ld\r\n", settings.udpServerPort); + if (settings.enableTcpServer) + systemPrintf("8) Enable NTRIP Caster: %s\r\n", settings.enableNtripCaster ? "Enabled" : "Disabled"); + //------------------------------ // Display the mDNS server menu items //------------------------------ @@ -223,8 +231,10 @@ void menuTcpUdp() //------------------------------ else if (incoming == 4) + { // Toggle TCP server settings.enableTcpServer ^= 1; + } else if (incoming == 5) { @@ -243,6 +253,8 @@ void menuTcpUdp() { getNewSetting("Enter the UDP port to use", 0, 65535, &settings.udpServerPort); } + else if (incoming == 8 && settings.enableTcpServer) + settings.enableNtripCaster ^= 1; //------------------------------ // Get the mDNS server parameters @@ -299,7 +311,7 @@ void networkBegin() ethernetStart(); #endif // COMPILE_ETHERNET - // WiFi and cellular networks are started/stopped as consumers come online/offline in networkUpdate() + // WiFi and cellular networks are started/stopped as consumers and come online/offline in networkUpdate() } //---------------------------------------- @@ -355,9 +367,9 @@ void networkDisplayInterface(NetIndex_t index) if (hasIP) { if (netif->hasGlobalIPv6()) - systemPrintf(" Global IPv6 Adress: %s\r\n", netif->globalIPv6().toString().c_str()); + systemPrintf(" Global IPv6 Address: %s\r\n", netif->globalIPv6().toString().c_str()); if (netif->hasLinkLocalIPv6()) - systemPrintf(" Link Local IPv6 Adress: %s\r\n", netif->linkLocalIPv6().toString().c_str()); + systemPrintf(" Link Local IPv6 Address: %s\r\n", netif->linkLocalIPv6().toString().c_str()); systemPrintf(" IPv4 Address: %s (%s)\r\n", netif->localIP().toString().c_str(), settings.ethernetDHCP ? "DHCP" : "Static"); systemPrintf(" Subnet Mask: %s\r\n", netif->subnetMask().toString().c_str()); @@ -509,6 +521,10 @@ IPAddress networkGetIpAddress() NetIndex_t index; IPAddress ip; + // NETIF doesn't capture the IP address of a soft AP + if (WIFI_SOFT_AP_RUNNING() == true && wifiStationIsRunning() == false) + return WiFi.softAPIP(); + // Get the networkInterfaceTable index index = networkPriority; if (index < NETWORK_OFFLINE) @@ -518,6 +534,9 @@ IPAddress networkGetIpAddress() } // No IP address available + if (settings.debugNetworkLayer) + systemPrintln("No network online - No IP available"); + return IPAddress(0, 0, 0, 0); } @@ -533,11 +552,11 @@ const uint8_t *networkGetMacAddress() return btMACAddress; #endif // COMPILE_BT #ifdef COMPILE_WIFI - if (networkIsInterfaceOnline(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI)) return wifiMACAddress; #endif // COMPILE_WIFI #ifdef COMPILE_ETHERNET - if (networkIsInterfaceOnline(NETWORK_ETHERNET)) + if (networkInterfaceHasInternet(NETWORK_ETHERNET)) return ethernetMACAddress; #endif // COMPILE_ETHERNET return zero; @@ -568,69 +587,38 @@ const char *networkGetNameByPriority(NetPriority_t priority) } //---------------------------------------- -// Determine if the network is available +// Determine if any network interface has access to the internet //---------------------------------------- -bool networkIsConnected(NetPriority_t *clientPriority) +bool networkHasInternet() { - // If the client is using the highest priority network and that - // network is still available then continue as normal - if (networkOnline && (*clientPriority == networkPriority)) - return true; - - // The network has changed, notify the client of the change - *clientPriority = networkPriority; - return false; + // Return the network state + return networkHasInternet_bm ? true : false; } //---------------------------------------- -// Determine if the network interface is online +// Interface stop event //---------------------------------------- -bool networkIsInterfaceOnline(NetIndex_t index) +void networkInterfaceEventStop(NetIndex_t index) { - // Validate the index - networkValidateIndex(index); - - // Return the network interface state - return (networkOnline & (1 << index)) ? true : false; + networkEventStop[index] = true; } //---------------------------------------- -// Determine if the network interface has completed its start routine +// Determine the specified network interface access to the internet //---------------------------------------- -bool networkIsInterfaceStarted(NetIndex_t index) +bool networkInterfaceHasInternet(NetIndex_t index) { // Validate the index networkValidateIndex(index); - // Return the network started state - return (networkStarted & (1 << index)) ? true : false; -} - -//---------------------------------------- -// Determine if any network interface is online -//---------------------------------------- -bool networkIsOnline() -{ - // Return the network state - return networkOnline ? true : false; -} - -//---------------------------------------- -// Determine if the network is present on the platform -//---------------------------------------- -bool networkIsPresent(NetIndex_t index) -{ - // Validate the index - networkValidateIndex(index); - - // Present if nullptr or bool set to true - return ((!networkInterfaceTable[index].present) || *(networkInterfaceTable[index].present)); + // Return the network interface state + return (networkHasInternet_bm & (1 << index)) ? true : false; } //---------------------------------------- -// Mark network offline +// Mark network interface as having NO access to the internet //---------------------------------------- -void networkMarkOffline(NetIndex_t index) +void networkInterfaceEventInternetLost(NetIndex_t index) { NetMask_t bitMask; NetPriority_t previousPriority; @@ -641,12 +629,12 @@ void networkMarkOffline(NetIndex_t index) // Check for network offline bitMask = 1 << index; - if (!(networkOnline & bitMask)) + if (!(networkHasInternet_bm & bitMask)) // Already offline, nothing to do return; // Mark this network as offline - networkOnline &= ~bitMask; + networkHasInternet_bm &= ~bitMask; // Disable mDNS if necessary networkMulticastDNSStop(index); @@ -662,14 +650,14 @@ void networkMarkOffline(NetIndex_t index) // Leave this network on in hopes that it will regain a connection previousPriority = networkPriority; - // Search in decending priority order for the next online network + // 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 (networkOnline & bitMask) + if (networkHasInternet_bm & bitMask) { // Successfully found an online network networkMulticastDNSStart(index); @@ -697,9 +685,9 @@ void networkMarkOffline(NetIndex_t index) } //---------------------------------------- -// Mark network online +// Mark network interface as having access to the internet //---------------------------------------- -void networkMarkOnline(NetIndex_t index) +void networkInterfaceEventInternetAvailable(NetIndex_t index) { NetMask_t bitMask; NetIndex_t previousIndex; @@ -712,12 +700,12 @@ void networkMarkOnline(NetIndex_t index) // Check for network online bitMask = 1 << index; previousIndex = index; - if (networkOnline & bitMask) + if (networkHasInternet_bm & bitMask) // Already online, nothing to do return; // Mark this network as online - networkOnline |= bitMask; + networkHasInternet_bm |= bitMask; // Raise the network priority if necessary previousPriority = networkPriority; @@ -768,9 +756,32 @@ void networkMarkOnline(NetIndex_t index) networkMulticastDNSStart(previousIndex); } +//---------------------------------------- +// Determine if the network interface has completed its start routine +//---------------------------------------- +bool networkIsInterfaceStarted(NetIndex_t index) +{ + // Validate the index + networkValidateIndex(index); + + // Return the network started state + return (networkStarted & (1 << index)) ? true : false; +} + +//---------------------------------------- +// Determine if the network is present on the platform +//---------------------------------------- +bool networkIsPresent(NetIndex_t index) +{ + // Validate the index + networkValidateIndex(index); + + // Present if nullptr or bool set to true + return ((!networkInterfaceTable[index].present) || *(networkInterfaceTable[index].present)); +} + //---------------------------------------- // Change multicast DNS to a given network -// //---------------------------------------- void networkMulticastDNSSwitch(NetIndex_t startIndex) { @@ -780,17 +791,19 @@ void networkMulticastDNSSwitch(NetIndex_t startIndex) networkMulticastDNSStop(index); // Start mDNS on the requested network - networkMulticastDNSStart(startIndex); //Start DNS on the selected network, either WiFi or Ethernet + networkMulticastDNSStart(startIndex); // Start DNS on the selected network, either WiFi or Ethernet } //---------------------------------------- // Start multicast DNS //---------------------------------------- -void networkMulticastDNSStart(NetIndex_t index) +bool networkMulticastDNSStart(NetIndex_t index) { NetMask_t bitMask; + bool started; // Start mDNS if it is enabled and not running on this network + started = false; bitMask = 1 << index; if (settings.mdnsEnable && (!(networkMdnsRunning & bitMask)) && (mDNSUse & bitMask)) { @@ -801,11 +814,13 @@ void networkMulticastDNSStart(NetIndex_t index) { MDNS.addService("http", "tcp", settings.httpPort); // Add service to MDNS networkMdnsRunning |= bitMask; + started = true; if (settings.debugNetworkLayer) systemPrintf("mDNS started as %s.local\r\n", settings.mdnsHostName); } } + return started; } //---------------------------------------- @@ -866,7 +881,7 @@ void networkPrintStatus(uint8_t priority) bitMask = (1 << index); highestPriority = (networkPriority == priority) ? '*' : ' '; status = "Starting"; - if (networkOnline & bitMask) + if (networkHasInternet_bm & bitMask) status = "Online"; else if (networkInterfaceTable[index].boot) { @@ -1282,7 +1297,7 @@ void networkStartDelayed(NetIndex_t index, uintptr_t parameter, bool debug) // Only lower priority networks running, start this network interface name = networkGetNameByIndex(index); currentInterfaceName = networkGetCurrentInterfaceName(); - if ((networkOnline & highPriorityBitMask) == 0) + if ((networkHasInternet_bm & highPriorityBitMask) == 0) { if (debug) systemPrintf("%s online, Starting %s\r\n", currentInterfaceName, name); @@ -1323,37 +1338,141 @@ void networkUpdate() uint8_t priority; NETWORK_POLL_SEQUENCE *sequence; - // If there are no consumers, do not start the network - if (networkConsumers() == 0 && networkIsOnline() == false) + // 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) { - return; + 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 (networkConsumers() == 0 && networkIsOnline() == true) + if (consumerCount == 0 && networkHasInternet() == true) { - if (systemState == STATE_WIFI_CONFIG) - { - // The STATE_WIFI_CONFIG is in control of WiFi. Don't allow the network layer to shut it down - // As OTA exits, we need to keep AP running if we are in Web Config mode - // We don't want to make STATE_WIFI_CONFIG a consumer until startWebServer() uses the network layer - } - else + 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); + } + + // 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) { - // Shutdown all networks - for (int index = 0; index < NETWORK_OFFLINE; index++) - networkStop(index, settings.debugNetworkLayer); + systemPrintf("Changing network from consumer type: 0x%02X (", previousConsumerTypes); + + if (previousConsumerTypes == NETIF_NONE) + systemPrint("None"); + else + { + 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"); + } + + 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 (networkConsumers() > 0 && networkIsOnline() == false) + if (consumerCount > 0 && networkHasInternet() == false) { - // Start network as needed - for (int index = 0; index < NETWORK_OFFLINE; index++) - networkStart(index, settings.debugNetworkLayer); + // Attempt to start any network that is needed, in the order Ethernet/WiFi/Cellular + + if (consumerTypes & (1 << NETIF_ETHERNET)) + { + networkStart(NETWORK_ETHERNET, settings.debugNetworkLayer); + } + + // 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))) + { + // 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); + } + } + } } // Walk the list of network priorities in descending order @@ -1371,24 +1490,6 @@ void networkUpdate() } } - // 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 - // Periodically display the network interface state displayIpAddress = PERIODIC_DISPLAY(PD_IP_ADDRESS); for (int index = 0; index < NETWORK_OFFLINE; index++) @@ -1409,6 +1510,14 @@ void networkUpdate() systemPrintf("%s: Link Up\r\n", networkInterfaceTable[index].name); 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)) + { + // NETIF doesn't capture the IP address of a soft AP + ipAddress = WiFi.softAPIP(); + systemPrintf("%s: %s%s\r\n", networkInterfaceTable[index].name, ipAddress.toString().c_str(), + networkInterfaceTable[index].netif->isDefault() ? " (default)" : ""); + } else systemPrintf("%s: Stopped\r\n", networkInterfaceTable[index].name); } @@ -1459,89 +1568,139 @@ void networkVerifyTables() reportFatalError("Fix networkInterfaceTable to match NetworkType"); } -#endif // COMPILE_NETWORK +// Return the bitfield containing the type of consumers currently using the network +uint16_t networkGetConsumerTypes() +{ + uint16_t consumerTypes = 0; + + networkConsumers(&consumerTypes); + + return (consumerTypes); +} // 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 an exception and is not considered a network consumer +// ESP-NOW is not considered a network consumer and is blended during wifiConnect() uint8_t networkConsumers() +{ + uint16_t consumerTypes = 0; + + return (networkConsumers(&consumerTypes)); +} + +uint8_t networkConsumers(uint16_t *consumerTypes) { uint8_t consumerCount = 0; - uint16_t consumerType = 0; + uint16_t consumerId = 0; // Used to debug print who is asking for access + + *consumerTypes = NETIF_NONE; // Clear bitfield + + // If a consumer needs the network or is currently consuming the network (is online) then increment + // consumer count // Network needed for NTRIP Client - if ((inRoverMode() == true && settings.enableNtripClient == true) || online.ntripClient) + if (ntripClientNeedsNetwork() || online.ntripClient) { consumerCount++; - consumerType |= (1 << 0); + consumerId |= (1 << NETCONSUMER_NTRIP_CLIENT); + + *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular } // Network needed for NTRIP Server - bool ntripServerConnected = false; + bool ntripServerOnline = false; for (int index = 0; index < NTRIP_SERVER_MAX; index++) { if (online.ntripServer[index]) { - ntripServerConnected = true; + ntripServerOnline = true; break; } } - if ((inBaseMode() == true && settings.enableNtripServer == true) || ntripServerConnected) + if (ntripServerNeedsNetwork() || ntripServerOnline) { consumerCount++; - consumerType |= (1 << 1); + consumerId |= (1 << NETCONSUMER_NTRIP_SERVER); + + *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular } // Network needed for TCP Client - if (settings.enableTcpClient == true || online.tcpClient) + if (tcpClientNeedsNetwork() || online.tcpClient) { consumerCount++; - consumerType |= (1 << 2); + consumerId |= (1 << NETCONSUMER_TCP_CLIENT); + *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular } // Network needed for TCP Server - if (settings.enableTcpServer == true || online.tcpServer) + if (tcpServerNeedsNetwork() || online.tcpServer) { consumerCount++; - consumerType |= (1 << 3); + 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); } // Network needed for UDP Server - if (settings.enableUdpServer == true || online.udpServer) + if (udpServerNeedsNetwork() || online.udpServer) { consumerCount++; - consumerType |= (1 << 4); + consumerId |= (1 << NETCONSUMER_UDP_SERVER); + *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular } // Network needed for PointPerfect ZTP or key update requested by scheduler, from menu, or display menu - if (settings.requestKeyUpdate == true) + if (provisioningNeedsNetwork() || online.httpClient) { consumerCount++; - consumerType |= (1 << 5); + consumerId |= (1 << NETCONSUMER_PPL_KEY_UPDATE); + *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular } // Network needed for PointPerfect Corrections MQTT client - if ((settings.enablePointPerfectCorrections == true && strlen(settings.pointPerfectCurrentKey) > 0) || - online.mqttClient) + if (mqttClientNeedsNetwork() || online.mqttClient) { // PointPerfect is enabled, allow MQTT to begin consumerCount++; - consumerType |= (1 << 6); + consumerId |= (1 << NETCONSUMER_PPL_MQTT_CLIENT); + *consumerTypes = NETWORK_EWC; // Ask for eth/wifi/cellular } - // Network needed to obtain the latest firmware version - if (otaRequestFirmwareVersionCheck == true) + // Network needed to obtain the latest firmware version or do a firmware update + if (otaNeedsNetwork() || online.otaClient) { consumerCount++; - consumerType |= (1 << 7); + consumerId |= (1 << NETCONSUMER_OTA_CLIENT); + *consumerTypes = (1 << NETIF_WIFI_STA); // OTA Pull library only supports WiFi } - // Network needed to start a firmware update - if (otaRequestFirmwareUpdate == true) + // Network needed for Web Config + if (webServerNeedsNetwork() || online.webServer) { consumerCount++; - consumerType |= (1 << 8); + 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) + + // 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)) + { + *consumerTypes |= (1 << NETIF_WIFI_AP); // Re-allow Webconfig over AP + } } // Debug @@ -1557,29 +1716,150 @@ uint8_t networkConsumers() { systemPrintf("- Consumers: ", consumerCount); - if (consumerType & (1 << 0)) + if (consumerId & (1 << NETCONSUMER_NTRIP_CLIENT)) systemPrint("Rover NTRIP Client, "); - if (consumerType & (1 << 1)) + if (consumerId & (1 << NETCONSUMER_NTRIP_SERVER)) systemPrint("Base NTRIP Server, "); - if (consumerType & (1 << 2)) + if (consumerId & (1 << NETCONSUMER_TCP_CLIENT)) systemPrint("TCP Client, "); - if (consumerType & (1 << 3)) + if (consumerId & (1 << NETCONSUMER_TCP_SERVER)) systemPrint("TCP Server, "); - if (consumerType & (1 << 4)) + if (consumerId & (1 << NETCONSUMER_UDP_SERVER)) systemPrint("UDP Server, "); - if (consumerType & (1 << 5)) + if (consumerId & (1 << NETCONSUMER_PPL_KEY_UPDATE)) systemPrint("PPL Key Update Request, "); - if (consumerType & (1 << 6)) + if (consumerId & (1 << NETCONSUMER_PPL_MQTT_CLIENT)) systemPrint("PPL MQTT Client, "); - if (consumerType & (1 << 7)) - systemPrint("OTA Version Check, "); - if (consumerType & (1 << 8)) - systemPrint("OTA Firmware Update, "); + if (consumerId & (1 << NETCONSUMER_OTA_CLIENT)) + systemPrint("OTA Version Check or Update, "); + if (consumerId & (1 << NETCONSUMER_WEB_CONFIG)) + systemPrint("Web Config, "); } - systemPrintln(); } } 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() +{ + 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); + } + + // Network needed to obtain the latest firmware version or do update + if (online.otaClient) + { + consumerCountOnline++; + consumerId |= (1 << NETCONSUMER_OTA_CLIENT); + } + + // Network needed for Web Config + if (online.webServer) + { + consumerCountOnline++; + consumerId |= (1 << NETCONSUMER_WEB_CONFIG); + } + + // Debug + if (settings.debugNetworkLayer) + { + 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(); + } + } + + return (consumerCountOnline); +} + +#endif // COMPILE_NETWORK diff --git a/Firmware/RTK_Everywhere/NtripClient.ino b/Firmware/RTK_Everywhere/NtripClient.ino index 652945d90..9b54aaaf4 100644 --- a/Firmware/RTK_Everywhere/NtripClient.ino +++ b/Firmware/RTK_Everywhere/NtripClient.ino @@ -83,7 +83,7 @@ NtripClient.ino NTRIP Client States: NTRIP_CLIENT_OFF: Network off or using NTRIP server NTRIP_CLIENT_ON: WIFI_STATE_START state - NTRIP_CLIENT_NETWORK_STARTED: Connecting to the network + NTRIP_CLIENT_WAIT_FOR_NETWORK: Connecting to the network NTRIP_CLIENT_NETWORK_CONNECTED: Connected to the network NTRIP_CLIENT_CONNECTING: Attempting a connection to the NTRIP caster NTRIP_CLIENT_WAIT_RESPONSE: Wait for a response from the NTRIP caster @@ -97,7 +97,7 @@ NtripClient.ino | | | | ntripClientRestart() v Fail | - NTRIP_CLIENT_NETWORK_STARTED ------->+ + NTRIP_CLIENT_WAIT_FOR_NETWORK ------->+ | ^ | | v | @@ -153,7 +153,7 @@ enum NTRIPClientState { NTRIP_CLIENT_OFF = 0, // Using Bluetooth or NTRIP server NTRIP_CLIENT_ON, // WIFI_STATE_START state - NTRIP_CLIENT_NETWORK_STARTED, // Connecting to WiFi access point or Ethernet + NTRIP_CLIENT_WAIT_FOR_NETWORK, // Connecting to WiFi access point or Ethernet NTRIP_CLIENT_NETWORK_CONNECTED, // Connected to an access point or Ethernet NTRIP_CLIENT_CONNECTING, // Attempting a connection to the NTRIP caster NTRIP_CLIENT_WAIT_RESPONSE, // Wait for a response from the NTRIP caster @@ -164,7 +164,7 @@ enum NTRIPClientState const char *const ntripClientStateName[] = {"NTRIP_CLIENT_OFF", "NTRIP_CLIENT_ON", - "NTRIP_CLIENT_NETWORK_STARTED", + "NTRIP_CLIENT_WAIT_FOR_NETWORK", "NTRIP_CLIENT_NETWORK_CONNECTED", "NTRIP_CLIENT_CONNECTING", "NTRIP_CLIENT_WAIT_RESPONSE", @@ -187,7 +187,6 @@ static volatile uint8_t ntripClientState = NTRIP_CLIENT_OFF; static int ntripClientConnectionAttempts; // Count the number of connection attempts between restarts static uint32_t ntripClientConnectionAttemptTimeout; static int ntripClientConnectionAttemptsTotal; // Count the number of connection attempts absolutely -static NetPriority_t ntripClientPriority = NETWORK_OFFLINE; // NTRIP client timer usage: // * Reconnection delay @@ -357,7 +356,7 @@ void ntripClientPrintStateSummary() systemPrint("Disconnected"); break; case NTRIP_CLIENT_ON: - case NTRIP_CLIENT_NETWORK_STARTED: + case NTRIP_CLIENT_WAIT_FOR_NETWORK: case NTRIP_CLIENT_NETWORK_CONNECTED: case NTRIP_CLIENT_CONNECTING: case NTRIP_CLIENT_WAIT_RESPONSE: @@ -526,6 +525,14 @@ 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 @@ -548,25 +555,24 @@ void ntripClientUpdate() switch (ntripClientState) { case NTRIP_CLIENT_OFF: - // Don't allow the client to restart if a forced shutdown occured + // Don't allow the client to restart if a forced shutdown occurred if (ntripClientForcedShutdown == false && EQ_RTK_MODE(ntripClientMode) && settings.enableNtripClient) ntripClientStart(); break; // Start the network case NTRIP_CLIENT_ON: - ntripClientPriority = NETWORK_OFFLINE; - ntripClientSetState(NTRIP_CLIENT_NETWORK_STARTED); + ntripClientSetState(NTRIP_CLIENT_WAIT_FOR_NETWORK); break; // Wait for a network media connection - case NTRIP_CLIENT_NETWORK_STARTED: + 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 to the media - else if (networkIsConnected(&ntripClientPriority)) + // Wait until the network is connected + else if (networkHasInternet()) { // Allocate the ntripClient structure ntripClient = new NetworkClient(); @@ -588,7 +594,7 @@ void ntripClientUpdate() case NTRIP_CLIENT_NETWORK_CONNECTED: // Determine if the network has failed - if (!networkIsConnected(&ntripClientPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripClientStop(true); // Was ntripClientRestart(); - #StopVsRestart @@ -621,7 +627,7 @@ void ntripClientUpdate() case NTRIP_CLIENT_WAIT_RESPONSE: // Determine if the network has failed - if (!networkIsConnected(&ntripClientPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripClientStop(true); // Was ntripClientRestart(); - #StopVsRestart @@ -742,7 +748,7 @@ void ntripClientUpdate() case NTRIP_CLIENT_CONNECTED: // Determine if the network has failed - if (!networkIsConnected(&ntripClientPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripClientStop(true); // Was ntripClientRestart(); - #StopVsRestart diff --git a/Firmware/RTK_Everywhere/NtripServer.ino b/Firmware/RTK_Everywhere/NtripServer.ino index 7eb7d9d35..edb72a424 100644 --- a/Firmware/RTK_Everywhere/NtripServer.ino +++ b/Firmware/RTK_Everywhere/NtripServer.ino @@ -83,7 +83,7 @@ NtripServer.ino NTRIP Server States: NTRIP_SERVER_OFF: Network off or using NTRIP Client NTRIP_SERVER_ON: WIFI_STATE_START state - NTRIP_SERVER_NETWORK_STARTED: Connecting to the network + NTRIP_SERVER_WAIT_FOR_NETWORK: Connecting to the network NTRIP_SERVER_NETWORK_CONNECTED: Connected to the network NTRIP_SERVER_WAIT_GNSS_DATA: Waiting for correction data from GNSS NTRIP_SERVER_CONNECTING: Attempting a connection to the NTRIP caster @@ -98,7 +98,7 @@ NtripServer.ino | | | | | | ntripServerRestart() | v Fail | - | NTRIP_SERVER_NETWORK_STARTED ------------->+ + | NTRIP_SERVER_WAIT_FOR_NETWORK ------------->+ | | ^ | | | | v Fail | @@ -146,7 +146,7 @@ enum NTRIPServerState { NTRIP_SERVER_OFF = 0, // Using Bluetooth or NTRIP client NTRIP_SERVER_ON, // WIFI_STATE_START state - NTRIP_SERVER_NETWORK_STARTED, // Connecting to WiFi access point + NTRIP_SERVER_WAIT_FOR_NETWORK, // Connecting to WiFi access point NTRIP_SERVER_NETWORK_CONNECTED, // WiFi connected to an access point NTRIP_SERVER_WAIT_GNSS_DATA, // Waiting for correction data from GNSS NTRIP_SERVER_CONNECTING, // Attempting a connection to the NTRIP caster @@ -158,7 +158,7 @@ enum NTRIPServerState const char *const ntripServerStateName[] = {"NTRIP_SERVER_OFF", "NTRIP_SERVER_ON", - "NTRIP_SERVER_NETWORK_STARTED", + "NTRIP_SERVER_WAIT_FOR_NETWORK", "NTRIP_SERVER_NETWORK_CONNECTED", "NTRIP_SERVER_WAIT_GNSS_DATA", "NTRIP_SERVER_CONNECTING", @@ -175,7 +175,6 @@ const RtkMode_t ntripServerMode = RTK_MODE_BASE_FIXED; // NTRIP Servers static NTRIP_SERVER_DATA ntripServerArray[NTRIP_SERVER_MAX]; -static NetPriority_t ntripServerPriority = NETWORK_OFFLINE; //---------------------------------------- // NTRIP Server Routines @@ -293,7 +292,7 @@ void ntripServerPrintStateSummary(int serverIndex) systemPrint("Disconnected"); break; case NTRIP_SERVER_ON: - case NTRIP_SERVER_NETWORK_STARTED: + case NTRIP_SERVER_WAIT_FOR_NETWORK: case NTRIP_SERVER_NETWORK_CONNECTED: case NTRIP_SERVER_WAIT_GNSS_DATA: case NTRIP_SERVER_CONNECTING: @@ -559,6 +558,22 @@ 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) { @@ -592,12 +607,11 @@ void ntripServerUpdate(int serverIndex) // Start the network case NTRIP_SERVER_ON: - ntripServerPriority = NETWORK_OFFLINE; - ntripServerSetState(ntripServer, NTRIP_SERVER_NETWORK_STARTED); + ntripServerSetState(ntripServer, NTRIP_SERVER_WAIT_FOR_NETWORK); break; // Wait for a network media connection - case NTRIP_SERVER_NETWORK_STARTED: + 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) || @@ -607,8 +621,8 @@ void ntripServerUpdate(int serverIndex) ntripServerStop(serverIndex, true); } - // Wait until the network is connected to the media - else if (networkIsConnected(&ntripServerPriority)) + // Wait until the network is connected + else if (networkHasInternet()) { // Allocate the networkClient structure ntripServer->networkClient = new NetworkClient(); @@ -631,7 +645,7 @@ void ntripServerUpdate(int serverIndex) // Network available case NTRIP_SERVER_NETWORK_CONNECTED: // Determine if the network has failed - if (!networkIsConnected(&ntripServerPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart @@ -649,7 +663,7 @@ void ntripServerUpdate(int serverIndex) // Wait for GNSS correction data case NTRIP_SERVER_WAIT_GNSS_DATA: // Determine if the network has failed - if (!networkIsConnected(&ntripServerPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart @@ -659,7 +673,7 @@ void ntripServerUpdate(int serverIndex) // Initiate the connection to the NTRIP caster case NTRIP_SERVER_CONNECTING: // Determine if the network has failed - if (!networkIsConnected(&ntripServerPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart @@ -687,7 +701,7 @@ void ntripServerUpdate(int serverIndex) // Wait for authorization response case NTRIP_SERVER_AUTHORIZATION: // Determine if the network has failed - if (!networkIsConnected(&ntripServerPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart @@ -781,7 +795,7 @@ 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 (!networkIsConnected(&ntripServerPriority)) + if (networkHasInternet() == false) // Failed to connect to the network, attempt to restart the network ntripServerStop(serverIndex, true); // Was ntripServerRestart(serverIndex); - #StopVsRestart diff --git a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino index d3de1410a..0dad72627 100644 --- a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino +++ b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino @@ -19,14 +19,11 @@ void updatePplTask(void *e) systemPrintln("UpdatePplTask running"); } - if (pplNewRtcmNmea || pplNewSpartnMqtt || pplNewSpartnMqtt) // Decide when to call PPL_GetRTCMOutput + if (pplNewRtcmNmea || pplNewSpartnMqtt || pplNewSpartnLBand) // Decide when to call PPL_GetRTCMOutput { - if (pplNewRtcmNmea) - pplNewRtcmNmea = false; - if (pplNewSpartnMqtt) - pplNewSpartnMqtt = false; - if (pplNewSpartnLBand) - pplNewSpartnLBand = false; + pplNewRtcmNmea = false; + pplNewSpartnMqtt = false; + pplNewSpartnLBand = false; uint32_t rtcmLength; @@ -211,17 +208,17 @@ void stopPPL() if (task.updatePplTaskRunning) task.updatePplTaskStopRequest = true; + // Wait for task to stop running + do + delay(10); + while (task.updatePplTaskRunning); + if (pplRtcmBuffer != nullptr) { free(pplRtcmBuffer); pplRtcmBuffer = nullptr; } - // Wait for task to stop running - do - delay(10); - while (task.updatePplTaskRunning); - online.ppl = false; } @@ -364,7 +361,7 @@ bool getUsablePplKey(char *keyBuffer, int keyBufferSize) if (settings.debugCorrections == true) { - pointperfectPrintKeyInformation(); + pointperfectPrintKeyInformation("getUsablePplKey"); systemPrintf("Days remaining until current key expires: %d\r\n", daysRemainingCurrent); systemPrintf("Days remaining until next key expires: %d\r\n", daysRemainingNext); } @@ -417,7 +414,7 @@ bool sendGnssToPpl(uint8_t *buffer, int numDataBytes) } else { - if(settings.debugCorrections & !inMainMenu) + if (settings.debugCorrections & !inMainMenu) systemPrintln("sendGnssToPpl available"); pplGnssOutput = true; // Notify updatePPL() that GNSS is outputting NMEA/RTCM } @@ -444,7 +441,7 @@ bool sendSpartnToPpl(uint8_t *buffer, int numDataBytes) } else { - if(settings.debugCorrections & !inMainMenu) + if (settings.debugCorrections & !inMainMenu) systemPrintln("pplMqttCorrections available"); pplMqttCorrections = true; // Notify updatePPL() that MQTT is online } @@ -525,8 +522,10 @@ const char *PPLReturnStatusToStr(ePPL_ReturnStatus status) } } -void pointperfectPrintKeyInformation() +void pointperfectPrintKeyInformation(const char *requestedBy) { + // All calls to pointperfectPrintKeyInformation are guarded by settings.debugCorrections + systemPrintf(" pointPerfect keys print requested by %s\r\n", requestedBy); systemPrintf(" pointPerfectCurrentKey: %s\r\n", settings.pointPerfectCurrentKey); systemPrintf( " pointPerfectCurrentKeyStart: %lld - %s\r\n", settings.pointPerfectCurrentKeyStart, diff --git a/Firmware/RTK_Everywhere/RTK_Everywhere.ino b/Firmware/RTK_Everywhere/RTK_Everywhere.ino index 0819fdcd1..9a487e5b2 100644 --- a/Firmware/RTK_Everywhere/RTK_Everywhere.ino +++ b/Firmware/RTK_Everywhere/RTK_Everywhere.ino @@ -32,7 +32,7 @@ #endif // COMPILE_WIFI #define COMPILE_ZED // Comment out to remove ZED-F9x functionality - #define COMPILE_L_BAND // Comment out to remove L-Band functionality +#define COMPILE_L_BAND // Comment out to remove L-Band functionality #define COMPILE_UM980 // Comment out to remove UM980 functionality #define COMPILE_MOSAICX5 // Comment out to remove mosaic-X5 functionality #define COMPILE_LG290P // Comment out to remove LG290P functionality @@ -41,12 +41,12 @@ #define COMPILE_POINTPERFECT_LIBRARY // Comment out to remove PPL support #define COMPILE_BQ40Z50 // Comment out to remove BQ40Z50 functionality -#if defined(COMPILE_WIFI) || defined(COMPILE_ETHERNET) +#if defined(COMPILE_WIFI) || defined(COMPILE_ETHERNET) || defined(COMPILE_CELLULAR) #define COMPILE_NETWORK #define COMPILE_MQTT_CLIENT // Comment out to remove MQTT Client functionality #define COMPILE_OTA_AUTO // Comment out to disable automatic over-the-air firmware update #define COMPILE_HTTP_CLIENT // Comment out to disable HTTP Client (PointPerfect ZTP) functionality -#endif // COMPILE_WIFI || COMPILE_ETHERNET +#endif // COMPILE_WIFI || COMPILE_ETHERNET || COMPILE_CELLULAR // Always define ENABLE_DEVELOPER to enable its use in conditional statements #ifndef ENABLE_DEVELOPER @@ -280,7 +280,7 @@ typedef enum LoggingType LOGGING_PPP, LOGGING_CUSTOM } LoggingType; -LoggingType loggingType; +LoggingType loggingType = LOGGING_UNKNOWN; SdFile *managerTempFile = nullptr; // File used for uploading or downloading in the file manager section of AP config bool managerFileOpen = false; @@ -303,6 +303,12 @@ char logFileName[sizeof("SFE_Reference_Station_230101_120101.ubx_plusExtraSpace" #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) \ @@ -443,7 +449,7 @@ float batteryChargingPercentPerHour; // serial. // // Switching from status and debug messages to GNSS output is done in two -// places, at the end of setup and at the end of maenuMain. In both of +// places, at the end of setup and at the end of menuMain. In both of // these places the new value comes from settings.enableGnssToUsbSerial. // Upon boot status and debug messages are output at least until the end // of setup. Upon entry into menuMain, this value is set false to again @@ -461,7 +467,6 @@ volatile bool forwardGnssDataToUsbSerial; #define platformPrefix platformPrefixTable[productVariant] // Sets the prefix for broadcast names -#include //Required for uart_set_rx_full_threshold() on cores setupButtons; // A vector (linked list) of the setup 'butttons' +std::vector setupButtons; // A vector (linked list) of the setup 'buttons' bool firstRoverStart; // Used to detect if the user is toggling the power button at POR to enter the test menu @@ -747,7 +754,6 @@ uint32_t triggerTowMsR; // Global copy - Time Of Week of rising edge (ms) uint32_t triggerTowSubMsR; // Global copy - Millisecond fraction of Time Of Week of rising edge in nanoseconds uint32_t triggerAccEst; // Global copy - Accuracy estimate in nanoseconds -bool firstPowerOn = true; // After boot, apply new settings to GNSS if the user switches between base or rover unsigned long splashStart; // Controls how long the splash is displayed for. Currently min of 2s. bool restartBase; // If the user modifies any NTRIP Server settings, we need to restart the base bool restartRover; // If the user modifies any NTRIP Client or PointPerfect settings, we need to restart the rover @@ -785,13 +791,6 @@ uint16_t failedParserMessages_NMEA; // Corrections Priorities Support CORRECTION_ID_T pplCorrectionsSource = CORR_NUM; // Record which source is feeding the PPL -// configureViaEthernet: -// Set to true if configureViaEthernet.txt exists in LittleFS. -// Previously, the SparkFun_WebServer_ESP32_W5500 needed _exclusive_ access to SPI and Interrupts. -// That's no longer true - thanks to Espressif adding full support for the W5500 within the -// arduino-esp32 core (v3.0.0+). But it's easier to leave the code as it is. -bool configureViaEthernet; - int floatLockRestarts; unsigned long rtkTimeToFixMs; @@ -821,7 +820,7 @@ unsigned long loraLastIncomingSerial; // Last time a user sent a serial command. // Display boot times //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -#define MAX_BOOT_TIME_ENTRIES 42 +#define MAX_BOOT_TIME_ENTRIES 41 uint8_t bootTimeIndex; uint32_t bootTime[MAX_BOOT_TIME_ENTRIES]; const char *bootTimeString[MAX_BOOT_TIME_ENTRIES]; @@ -866,7 +865,6 @@ volatile bool deadManWalking; deadManWalking = true; \ \ /* Output as much as possible to identify the location of the failure */ \ - settings.debugGnss = true; \ settings.enableHeapReport = true; \ settings.enableTaskReports = true; \ settings.enablePrintPosition = true; \ @@ -883,16 +881,26 @@ volatile bool deadManWalking; settings.enablePrintSDBuffers = true; \ settings.periodicDisplay = (PeriodicDisplay_t) - 1; \ settings.enablePrintEthernetDiag = true; \ - settings.debugWifiState = true; \ + settings.debugCorrections = true; \ + settings.debugGnss = true; \ + settings.debugHttpClientData = true; \ + settings.debugHttpClientState = true; \ + settings.debugLora = true; \ + settings.debugMqttClientData = true; \ + settings.debugMqttClientState = true; \ settings.debugNetworkLayer = true; \ settings.printNetworkStatus = true; \ settings.debugNtripClientRtcm = true; \ settings.debugNtripClientState = true; \ settings.debugNtripServerRtcm = true; \ settings.debugNtripServerState = true; \ + settings.debugPpCertificate = true; \ + settings.debugSettings = true; \ settings.debugTcpClient = true; \ settings.debugTcpServer = true; \ settings.debugUdpServer = true; \ + settings.debugWebServer = true; \ + settings.debugWifiState = true; \ settings.printBootTimes = true; \ } @@ -919,6 +927,97 @@ volatile bool deadManWalking; #endif // 0 +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +// Debug nearly everything. Include DEBUG_NEARLY_EVERYTHING in the setup after loadSettings to print many things. +// Similar but less verbose than DEAD_MAN_WALKING +// If the updated settings are saved to NVM, you will need to do a factory reset to clear them +#define DEBUG_NEARLY_EVERYTHING \ + { \ + \ + /* Turn on nearly all the debug prints */ \ + settings.debugCorrections = true; \ + settings.debugGnss = false; \ + settings.debugHttpClientData = true; \ + settings.debugHttpClientState = true; \ + settings.debugLora = true; \ + settings.debugMqttClientData = true; \ + settings.debugMqttClientState = true; \ + settings.debugNetworkLayer = true; \ + settings.debugNtripClientRtcm = true; \ + settings.debugNtripClientState = true; \ + settings.debugNtripServerRtcm = true; \ + settings.debugNtripServerState = true; \ + settings.debugPpCertificate = true; \ + settings.debugSettings = true; \ + settings.debugTcpClient = true; \ + settings.debugTcpServer = true; \ + settings.debugUdpServer = true; \ + settings.debugWebServer = true; \ + settings.debugWifiState = true; \ + settings.enableHeapReport = true; \ + settings.enablePrintBatteryMessages = true; \ + settings.enablePrintBufferOverrun = true; \ + settings.enablePrintDuplicateStates = true; \ + settings.enablePrintEthernetDiag = true; \ + settings.enablePrintIdleTime = true; \ + settings.enablePrintLogFileMessages = false; \ + settings.enablePrintLogFileStatus = true; \ + settings.enablePrintPosition = true; \ + settings.enablePrintRingBufferOffsets = false; \ + settings.enablePrintRoverAccuracy = true; \ + settings.enablePrintRtcSync = true; \ + settings.enablePrintSDBuffers = false; \ + settings.enablePrintStates = true; \ + settings.printBootTimes = true; \ + settings.printNetworkStatus = true; \ + settings.printTaskStartStop = true; \ + } + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +// Debug the essentials. Include DEBUG_THE_ESSENTIALS in the setup after loadSettings to print the essentials. +// If the updated settings are saved to NVM, you will need to do a factory reset to clear them +#define DEBUG_THE_ESSENTIALS \ + { \ + \ + /* Turn on nearly all the debug prints */ \ + settings.debugCorrections = true; \ + settings.debugGnss = false; \ + settings.debugHttpClientData = false; \ + settings.debugHttpClientState = true; \ + settings.debugLora = false; \ + settings.debugMqttClientData = false; \ + settings.debugMqttClientState = true; \ + settings.debugNetworkLayer = true; \ + settings.debugNtripClientRtcm = false; \ + settings.debugNtripClientState = true; \ + settings.debugNtripServerRtcm = false; \ + settings.debugNtripServerState = true; \ + settings.debugPpCertificate = false; \ + settings.debugSettings = false; \ + settings.debugTcpClient = true; \ + settings.debugTcpServer = true; \ + settings.debugUdpServer = true; \ + settings.debugWebServer = true; \ + settings.debugWifiState = true; \ + settings.enableHeapReport = false; \ + settings.enablePrintBatteryMessages = false; \ + settings.enablePrintBufferOverrun = true; \ + settings.enablePrintDuplicateStates = false; \ + settings.enablePrintEthernetDiag = true; \ + settings.enablePrintIdleTime = false; \ + settings.enablePrintLogFileMessages = false; \ + settings.enablePrintLogFileStatus = true; \ + settings.enablePrintPosition = false; \ + settings.enablePrintRingBufferOffsets = false; \ + settings.enablePrintRoverAccuracy = true; \ + settings.enablePrintRtcSync = true; \ + settings.enablePrintSDBuffers = false; \ + settings.enablePrintStates = true; \ + settings.printBootTimes = true; \ + settings.printNetworkStatus = true; \ + settings.printTaskStartStop = true; \ + } + //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /* +---------------------------------------+ +----------+ @@ -1044,12 +1143,8 @@ void setup() if (um980FirmwareCheckUpdate() == true) // Check if updateUm980Firmware.txt exists um980FirmwareBeginUpdate(); - DMW_b("checkConfigureViaEthernet"); - configureViaEthernet = - checkConfigureViaEthernet(); // Check if going into dedicated configureViaEthernet (STATE_CONFIG_VIA_ETH) mode - DMW_b("beginPsram"); - beginPsram(); // Inialize PSRAM (if available). Needs to occur before beginGnssUart and other malloc users. + beginPsram(); // Initialize PSRAM (if available). Needs to occur before beginGnssUart and other malloc users. DMW_b("beginMux"); beginMux(); // Must come before I2C activity to avoid external devices from corrupting the bus. See issue #474: @@ -1085,6 +1180,9 @@ void setup() DMW_b("loadSettings"); loadSettings(); // Attempt to load settings after SD is started so we can read the settings file if available + //DEBUG_NEARLY_EVERYTHING // Debug nearly all the things + //DEBUG_THE_ESSENTIALS // Debug the essentials - handy for measuring the boot time after a factory reset + DMW_b("checkArrayDefaults"); checkArrayDefaults(); // Check for uninitialized arrays that won't be initialized by gnssConfigure // (checkGNSSArrayDefaults) @@ -1100,7 +1198,7 @@ void setup() networkBegin(); DMW_b("beginFuelGauge"); - beginFuelGauge(); // Configure battery fuel guage monitor + beginFuelGauge(); // Configure battery fuel gauge monitor DMW_b("beginCharger"); beginCharger(); // Configure battery charger @@ -1108,9 +1206,6 @@ void setup() DMW_b("gnss->configure"); gnss->configure(); // Requires settings. Configure GNSS module - DMW_b("beginLBand"); - beginLBand(); // Begin L-Band - DMW_b("beginExternalEvent"); gnss->beginExternalEvent(); // Configure the event input @@ -1238,7 +1333,10 @@ void loop() networkUpdate(); // Maintain the network connections DMW_c("updateLBand"); - updateLBand(); // Check if we've recently received PointPerfect corrections or not + updateLBand(); // Update L-Band + + DMW_c("updateLBandCorrections"); + updateLBandCorrections(); // Check if we've recently received PointPerfect corrections or not DMW_c("tiltUpdate"); tiltUpdate(); // Check if new lat/lon/alt have been calculated @@ -1256,13 +1354,14 @@ void loop() printReports(); // Periodically print GNSS coordinates and accuracy if enabled DMW_c("otaAutoUpdate"); - otaUpdate(); // Initiate firmware version checks, scheduled automatic updates, or requested firmware over-the-air updates + otaUpdate(); // Initiate firmware version checks, scheduled automatic updates, or requested firmware over-the-air + // updates DMW_c("correctionUpdateSource"); correctionUpdateSource(); // Retire expired sources DMW_c("updateProvisioning"); - updateProvisioning(); // Check if we should attempt to connect to PointPerfect to get keys / certs / correction + provisioningUpdate(); // Check if we should attempt to connect to PointPerfect to get keys / certs / correction // topic etc. loopDelay(); // A small delay prevents panic if no other I2C or functions are called @@ -1285,8 +1384,8 @@ void logUpdate() // max log window. systemTime_minutes = millis() / 1000L / 60; - // If we are in AP config, don't touch the SD card - if (systemState == STATE_WIFI_CONFIG_NOT_STARTED || systemState == STATE_WIFI_CONFIG) + // If we are in Web Config, don't touch the SD card + if (inWebConfigMode()) return; if (online.microSD == false) @@ -1314,10 +1413,7 @@ void logUpdate() else if (online.logging == true && settings.enableLogging == true && (systemTime_minutes - startCurrentLogTime_minutes) >= settings.maxLogLength_minutes) { - if (settings.runLogTest == false) - endSD(false, true); // Close down file. A new one will be created at the next calling of updateLogs(). - else if (settings.runLogTest == true) - updateLogTest(); + endSD(false, true); // Close down file. A new one will be created at the next calling of updateLogs(). } if (online.logging == true) @@ -1404,18 +1500,19 @@ void logUpdate() // Report file sizes to show recording is working if ((millis() - lastFileReport) > 5000) { - if (fileSize > 0) + if (logFileSize > 0) { lastFileReport = millis(); + if (settings.enablePrintLogFileStatus) { - systemPrintf("Log file size: %ld", fileSize); + systemPrintf("Log file size: %lld", logFileSize); if ((systemTime_minutes - startLogTime_minutes) < settings.maxLogTime_minutes) { // Calculate generation and write speeds every 5 seconds - uint32_t fileSizeDelta = fileSize - lastLogSize; - systemPrintf(" - Generation rate: %0.1fkB/s", fileSizeDelta / 5.0 / 1000.0); + uint64_t fileSizeDelta = logFileSize - lastLogSize; + systemPrintf(" - Generation rate: %0.1fkB/s", ((float)fileSizeDelta) / 5.0 / 1000.0); } else { @@ -1425,9 +1522,9 @@ void logUpdate() systemPrintln(); } - if (fileSize > lastLogSize) + if (logFileSize > lastLogSize) { - lastLogSize = fileSize; + lastLogSize = logFileSize; logIncreasing = true; } else @@ -1496,7 +1593,13 @@ void rtcUpdate() } else { - systemPrintln("No GNSS date/time available for system RTC."); + // Reduce the output frequency + static uint32_t lastErrorMsec = -1000 * 1000 * 1000; + if ((millis() - lastErrorMsec) > (30 * 1000)) + { + lastErrorMsec = millis(); + systemPrintln("No GNSS date/time available for system RTC."); + } } // End timeValid } // End lastRTCAttempt } // End online.gnss @@ -1604,9 +1707,6 @@ void getSemaphoreFunction(char *functionName) case FUNCTION_FINDLOG: strcpy(functionName, "Find Log"); break; - case FUNCTION_LOGTEST: - strcpy(functionName, "Log Test"); - break; case FUNCTION_FILELIST: strcpy(functionName, "File List"); break; diff --git a/Firmware/RTK_Everywhere/SD.ino b/Firmware/RTK_Everywhere/SD.ino index 7e5112981..2cf8e48c7 100644 --- a/Firmware/RTK_Everywhere/SD.ino +++ b/Firmware/RTK_Everywhere/SD.ino @@ -84,13 +84,13 @@ bool sdCardPresent(void) { if (present.microSdCardDetectLow == true) { - if (digitalRead(pin_microSD_CardDetect) == LOW) + if (readAnalogPinAsDigital(pin_microSD_CardDetect) == LOW) return (true); // Card detect low = SD in place return (false); // Card detect high = No SD } else if (present.microSdCardDetectHigh == true) { - if (digitalRead(pin_microSD_CardDetect) == HIGH) + if (readAnalogPinAsDigital(pin_microSD_CardDetect) == HIGH) return (true); // Card detect high = SD in place return (false); // Card detect low = No SD } diff --git a/Firmware/RTK_Everywhere/States.ino b/Firmware/RTK_Everywhere/States.ino index 3f2795585..094817eae 100644 --- a/Firmware/RTK_Everywhere/States.ino +++ b/Firmware/RTK_Everywhere/States.ino @@ -7,6 +7,8 @@ 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() @@ -100,8 +102,8 @@ void stateUpdate() displayRoverStart(0); if (gnss->configureRover() == false) { - settings.updateGNSSSettings = true; // On the next boot, update the GNSS receiver - recordSystemSettings(); // Record this state for next POR + settings.gnssConfiguredRover = false; // On the next boot, reapply all settings + recordSystemSettings(); // Record this state for next POR systemPrintln("Rover config failed"); displayRoverFail(1000); @@ -111,14 +113,18 @@ void stateUpdate() setMuxport(settings.dataPortChannel); // Return mux to original channel bluetoothStart(); // Turn on Bluetooth with 'Rover' name - espnowStart(); // Start internal radio if enabled, otherwise disable + ESPNOW_START(); // Start internal radio if enabled, otherwise disable + + webServerStop(); // Stop the web config server + baseCasterDisableOverride(); // Disable casting overrides // Start the UART connected to the GNSS receiver for NMEA and UBX data (enables logging) if (tasksStartGnssUart() == false) displayRoverFail(1000); else { - settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS receiver + //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 @@ -203,6 +209,25 @@ void stateUpdate() */ + case (STATE_BASE_CASTER_NOT_STARTED): { + baseCasterEnableOverride(); + +#ifdef COMPILE_WIFI + // If the AP is already running, check that the name is correct + if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) + { + if (strcmp(WiFi.softAPSSID().c_str(), "RTK Caster") != 0) + { + // The AP name cannot be changed while it is running. WiFi must be restarted. + restartWiFi = true; // Tell network layer to restart WiFi + } + } +#endif // COMPILE_WIFI + + changeState(STATE_BASE_NOT_STARTED); + } + break; + case (STATE_BASE_NOT_STARTED): { RTK_MODE(RTK_MODE_BASE_SURVEY_IN); firstRoverStart = false; // If base is starting, no test menu, normal button use. @@ -217,12 +242,15 @@ void stateUpdate() bluetoothStop(); bluetoothStart(); // Restart Bluetooth with 'Base' identifier + webServerStop(); // Stop the web config server + // Start the UART connected to the GNSS receiver for NMEA and UBX data (enables logging) if (tasksStartGnssUart() && gnss->configureBase()) { - settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS on this profile + // settings.gnssConfiguredBase is set by gnss->configureBase() + settings.gnssConfiguredRover = false; // When the mode changes, reapply all settings settings.lastState = STATE_BASE_NOT_STARTED; // Record this state for next POR - recordSystemSettings(); // Record this state for next POR + recordSystemSettings(); // Record this state for next POR displayBaseSuccess(500); // Show 'Base Started' @@ -233,8 +261,8 @@ void stateUpdate() } else { - settings.updateGNSSSettings = true; // On the next boot, update the GNSS receiver - recordSystemSettings(); // Record this state for next POR + settings.gnssConfiguredBase = false; // On the next boot, reapply all settings + recordSystemSettings(); // Record this state for next POR displayBaseFail(1000); } @@ -306,7 +334,7 @@ void stateUpdate() // Start the NTRIP server if requested RTK_MODE(RTK_MODE_BASE_FIXED); - espnowStart(); // Start internal radio if enabled, otherwise disable + ESPNOW_START(); // Start internal radio if enabled, otherwise disable rtcmPacketsSent = 0; // Reset any previous number changeState(STATE_BASE_TEMP_TRANSMITTING); @@ -373,7 +401,7 @@ void stateUpdate() { baseStatusLedOn(); // Turn on the base/status LED - espnowStart(); // Start internal radio if enabled, otherwise disable + ESPNOW_START(); // Start internal radio if enabled, otherwise disable changeState(STATE_BASE_FIXED_TRANSMITTING); } @@ -401,7 +429,7 @@ void stateUpdate() } break; - case (STATE_WIFI_CONFIG_NOT_STARTED): { + case (STATE_WEB_CONFIG_NOT_STARTED): { if (pin_bluetoothStatusLED != PIN_UNDEFINED) { // Start BT LED Fade to indicate the start of WiFi @@ -412,24 +440,38 @@ void stateUpdate() baseStatusLedOff(); // Turn off the status LED - displayWiFiConfigNotStarted(); // Display immediately during SD cluster pause + displayWebConfigNotStarted(); // Display immediately while we wait for server to start - WIFI_STOP(); // Notify the network layer that it should stop so we can take over control of WiFi - bluetoothStop(); - espnowStop(); + 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 - tasksStopGnssUart(); // Delete serial tasks if running - if (!startWebServer()) // Start web server in WiFi mode and show config html page - changeState(STATE_ROVER_NOT_STARTED); - else + // The GNSS UART task is left running to allow GNSS receivers to obtain LLh data for 1Hz page updates + +#ifdef COMPILE_WIFI + // If the AP is already running, check that the name is correct + if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA)) { - RTK_MODE(RTK_MODE_WIFI_CONFIG); - changeState(STATE_WIFI_CONFIG); + if (strcmp(WiFi.softAPSSID().c_str(), "RTK Config") != 0) + { + // The AP name cannot be changed while it is running. WiFi must be restarted. + restartWiFi = true; // Tell network layer to restart WiFi + } } +#endif // COMPILE_WIFI + + // Stop any running NTRIP Client or Server + ntripClientStop(true); // Do not allocate new wifiClient + for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) + ntripServerStop(serverIndex, true); // Do not allocate new wifiClient + + webServerStart(); // Start the webserver state machine for web config + + RTK_MODE(RTK_MODE_WEB_CONFIG); + changeState(STATE_WEB_CONFIG); } break; - case (STATE_WIFI_CONFIG): { + case (STATE_WEB_CONFIG): { if (incomingSettingsSpot > 0) { // Allow for 750ms before we parse buffer for all data to arrive @@ -444,8 +486,10 @@ void stateUpdate() systemPrintln(); parseIncomingSettings(); - settings.updateGNSSSettings = true; // New settings; update GNSS receiver on next boot - recordSystemSettings(); // Record these settings to unit + settings.gnssConfiguredOnce = false; // On the next boot, reapply all settings + settings.gnssConfiguredBase = false; + settings.gnssConfiguredRover = false; + recordSystemSettings(); // Record these settings to unit // Clear buffer incomingSettingsSpot = 0; @@ -474,8 +518,8 @@ void stateUpdate() { createFirmwareVersionString(settingsCSV); - if (settings.debugWebConfig) - systemPrintf("Webconfig: Firmware version requested. Sending: %s\r\n", settingsCSV); + if (settings.debugWebServer) + systemPrintf("WebServer: Firmware version requested. Sending: %s\r\n", settingsCSV); sendStringToWebsocket(settingsCSV); @@ -564,8 +608,9 @@ void stateUpdate() // Start UART connected to the GNSS receiver for NMEA and UBX data (enables logging) if (tasksStartGnssUart() && configureUbloxModuleNTP()) { - settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS on this profile settings.lastState = STATE_NTPSERVER_NOT_STARTED; // Record this state for next POR + settings.gnssConfiguredBase = false; // On the next boot, reapply all settings + settings.gnssConfiguredRover = false; recordSystemSettings(); if (online.ethernetNTPServer) @@ -613,109 +658,6 @@ void stateUpdate() } break; - case (STATE_CONFIG_VIA_ETH_NOT_STARTED): { - displayConfigViaEthStarting(1500); - - settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS on this profile - settings.lastState = STATE_CONFIG_VIA_ETH_STARTED; // Record the _next_ state for POR - recordSystemSettings(); - - forceConfigureViaEthernet(); // Create a file in LittleFS to force code into configure-via-ethernet mode - - ESP.restart(); // Restart to go into the dedicated configure-via-ethernet mode - } - break; - - case (STATE_CONFIG_VIA_ETH_STARTED): { - RTK_MODE(RTK_MODE_ETHERNET_CONFIG); - // The code should only be able to enter this state if configureViaEthernet is true. - // If configureViaEthernet is not true, we need to restart again. - if (!configureViaEthernet) - { - displayConfigViaEthStarting(1500); - settings.lastState = STATE_CONFIG_VIA_ETH_STARTED; // Re-record this state for POR - recordSystemSettings(); - - forceConfigureViaEthernet(); // Create a file in LittleFS to force code into configure-via-ethernet mode - - ESP.restart(); // Restart to go into the dedicated configure-via-ethernet mode - } - - displayConfigViaEthStarted(1500); - - bluetoothStop(); // Should be redundant - but just in case - espnowStop(); // Should be redundant - but just in case - tasksStopGnssUart(); // Delete F9 serial tasks if running - - ethernetWebServerStartESP32W5500(); // Start Ethernet in dedicated configure-via-ethernet mode - - if (!startWebServer(false, settings.httpPort)) // Start the web server - changeState(STATE_ROVER_NOT_STARTED); - else - changeState(STATE_CONFIG_VIA_ETH); - } - break; - - case (STATE_CONFIG_VIA_ETH): { - // Display will show the IP address (displayConfigViaEthernet) - - if (incomingSettingsSpot > 0) - { - // Allow for 750ms before we parse buffer for all data to arrive - if (millis() - timeSinceLastIncomingSetting > 750) - { - currentlyParsingData = - true; // Disallow new data to flow from websocket while we are parsing the current data - - systemPrint("Parsing: "); - for (int x = 0; x < incomingSettingsSpot; x++) - systemWrite(incomingSettings[x]); - systemPrintln(); - - parseIncomingSettings(); - settings.updateGNSSSettings = - true; // When this profile is loaded next, force system to update GNSS settings. - recordSystemSettings(); // Record these settings to unit - - // Clear buffer - incomingSettingsSpot = 0; - memset(incomingSettings, 0, AP_CONFIG_SETTING_SIZE); - - currentlyParsingData = false; // Allow new data from websocket - } - } - -#ifdef COMPILE_WIFI -#ifdef COMPILE_AP - // Dynamically update the coordinates on the AP page - if (websocketConnected == true) - { - if (millis() - lastDynamicDataUpdate > 1000) - { - lastDynamicDataUpdate = millis(); - createDynamicDataString(settingsCSV); - - // log_d("Sending coordinates: %s", settingsCSV); - sendStringToWebsocket(settingsCSV); - } - } -#endif // COMPILE_AP -#endif // COMPILE_WIFI - } - break; - - case (STATE_CONFIG_VIA_ETH_RESTART_BASE): { - displayConfigViaEthExiting(1000); - - ethernetWebServerStopESP32W5500(); - - settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS on this profile - settings.lastState = STATE_BASE_NOT_STARTED; // Record the _next_ state for POR - recordSystemSettings(); - - ESP.restart(); - } - break; #endif // COMPILE_ETHERNET case (STATE_SHUTDOWN): { @@ -757,6 +699,8 @@ const char *getState(SystemState state, char *buffer) return "STATE_ROVER_RTK_FLOAT"; case (STATE_ROVER_RTK_FIX): return "STATE_ROVER_RTK_FIX"; + case (STATE_BASE_CASTER_NOT_STARTED): + return "STATE_BASE_CASTER_NOT_STARTED"; case (STATE_BASE_NOT_STARTED): return "STATE_BASE_NOT_STARTED"; case (STATE_BASE_TEMP_SETTLE): @@ -771,10 +715,10 @@ const char *getState(SystemState state, char *buffer) return "STATE_BASE_FIXED_TRANSMITTING"; case (STATE_DISPLAY_SETUP): return "STATE_DISPLAY_SETUP"; - case (STATE_WIFI_CONFIG_NOT_STARTED): - return "STATE_WIFI_CONFIG_NOT_STARTED"; - case (STATE_WIFI_CONFIG): - return "STATE_WIFI_CONFIG"; + case (STATE_WEB_CONFIG_NOT_STARTED): + return "STATE_WEB_CONFIG_NOT_STARTED"; + case (STATE_WEB_CONFIG): + return "STATE_WEB_CONFIG"; case (STATE_TEST): return "STATE_TEST"; case (STATE_TESTING): @@ -797,15 +741,6 @@ const char *getState(SystemState state, char *buffer) case (STATE_NTPSERVER_SYNC): return "STATE_NTPSERVER_SYNC"; - case (STATE_CONFIG_VIA_ETH_NOT_STARTED): - return "STATE_CONFIG_VIA_ETH_NOT_STARTED"; - case (STATE_CONFIG_VIA_ETH_STARTED): - return "STATE_CONFIG_VIA_ETH_STARTED"; - case (STATE_CONFIG_VIA_ETH): - return "STATE_CONFIG_VIA_ETH"; - case (STATE_CONFIG_VIA_ETH_RESTART_BASE): - return "STATE_CONFIG_VIA_ETH_RESTART_BASE"; - case (STATE_SHUTDOWN): return "STATE_SHUTDOWN"; case (STATE_NOT_SET): @@ -878,14 +813,12 @@ typedef struct _RTK_MODE_ENTRY SystemState last; } RTK_MODE_ENTRY; -const RTK_MODE_ENTRY stateModeTable[] = { - {"Rover", STATE_ROVER_NOT_STARTED, STATE_ROVER_RTK_FIX}, - {"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}, - {"NTP", STATE_NTPSERVER_NOT_STARTED, STATE_NTPSERVER_SYNC}, - {"Ethernet Config", STATE_CONFIG_VIA_ETH_NOT_STARTED, STATE_CONFIG_VIA_ETH_RESTART_BASE}, - {"Shutdown", STATE_SHUTDOWN, STATE_SHUTDOWN}}; +const RTK_MODE_ENTRY stateModeTable[] = {{"Rover", STATE_ROVER_NOT_STARTED, STATE_ROVER_RTK_FIX}, + {"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}, + {"NTP", STATE_NTPSERVER_NOT_STARTED, STATE_NTPSERVER_SYNC}, + {"Shutdown", STATE_SHUTDOWN, STATE_SHUTDOWN}}; const int stateModeTableEntries = sizeof(stateModeTable) / sizeof(stateModeTable[0]); const char *stateToRtkMode(SystemState state) @@ -918,9 +851,9 @@ bool inBaseMode() return (false); } -bool inWiFiConfigMode() +bool inWebConfigMode() { - if (systemState >= STATE_WIFI_CONFIG_NOT_STARTED && systemState <= STATE_WIFI_CONFIG) + if (systemState >= STATE_WEB_CONFIG_NOT_STARTED && systemState <= STATE_WEB_CONFIG) return (true); return (false); } @@ -945,19 +878,16 @@ void constructSetupDisplay(std::vector *buttons) { buttons->clear(); - // It looks like we don't need "Mark"? TODO: check this! addSetupButton(buttons, "Base", STATE_BASE_NOT_STARTED); + addSetupButton(buttons, "BaseCast", STATE_BASE_CASTER_NOT_STARTED); addSetupButton(buttons, "Rover", STATE_ROVER_NOT_STARTED); if (present.ethernet_ws5500 == true) { addSetupButton(buttons, "NTP", STATE_NTPSERVER_NOT_STARTED); - addSetupButton(buttons, "Cfg Eth", STATE_CONFIG_VIA_ETH_NOT_STARTED); - addSetupButton(buttons, "Cfg WiFi", STATE_WIFI_CONFIG_NOT_STARTED); - } - else - { - addSetupButton(buttons, "Config", STATE_WIFI_CONFIG_NOT_STARTED); } + + addSetupButton(buttons, "Config", STATE_WEB_CONFIG_NOT_STARTED); + if (settings.enablePointPerfectCorrections) { addSetupButton(buttons, "Get Keys", STATE_KEYS_REQUESTED); diff --git a/Firmware/RTK_Everywhere/System.ino b/Firmware/RTK_Everywhere/System.ino index 00799cf6a..99c9dc290 100644 --- a/Firmware/RTK_Everywhere/System.ino +++ b/Firmware/RTK_Everywhere/System.ino @@ -98,7 +98,7 @@ void updateBattery() ((uint8_t)readAnalogPinAsDigital(pin_chargerLED)); systemPrint("MCP73833 Charger: "); if (combinedStat == 3) - systemPrintln("standby / fault"); + systemPrintln("standby"); else if (combinedStat == 2) systemPrintln("battery is charging"); else if (combinedStat == 1) @@ -162,12 +162,12 @@ void updateBattery() } // Check if we need to shutdown due to no charging - if (settings.shutdownNoChargeTimeout_s > 0) + if (settings.shutdownNoChargeTimeoutMinutes > 0) { if (isCharging() == false) { - int secondsSinceLastCharger = (millis() - shutdownNoChargeTimer) / 1000; - if (secondsSinceLastCharger > settings.shutdownNoChargeTimeout_s) + int minutesSinceLastCharge = ((millis() - shutdownNoChargeTimer) / 1000) / 60; + if (minutesSinceLastCharge > settings.shutdownNoChargeTimeoutMinutes) powerDown(true); } else @@ -394,7 +394,10 @@ void printReports() // If we are in base mode, display SIV only else if (inBaseMode() == true) { - systemPrintf("Base Mode - SIV: %d\r\n", gnss->getSatellitesInView()); + if (settings.baseCasterOverride == true) + systemPrintf("Base Caster Mode - SIV: %d\r\n", gnss->getSatellitesInView()); + else + systemPrintf("Base Mode - SIV: %d\r\n", gnss->getSatellitesInView()); } } } @@ -767,7 +770,7 @@ bool isUsbAttached() { if (pin_powerAdapterDetect != PIN_UNDEFINED) // Pin goes low when wall adapter is detected - if (digitalRead(pin_powerAdapterDetect) == HIGH) + if (readAnalogPinAsDigital(pin_powerAdapterDetect) == HIGH) return false; return true; } diff --git a/Firmware/RTK_Everywhere/Tasks.ino b/Firmware/RTK_Everywhere/Tasks.ino index 896d894e6..434747391 100644 --- a/Firmware/RTK_Everywhere/Tasks.ino +++ b/Firmware/RTK_Everywhere/Tasks.ino @@ -159,7 +159,7 @@ void btReadTask(void *e) rxBytes = 0; if (bluetoothGetState() == BT_CONNECTED) { - while (btPrintEcho == false && bluetoothRxDataAvailable()) + while (btPrintEcho == false && (bluetoothRxDataAvailable() > 0)) { // Check stream for command characters byte incoming = bluetoothRead(); @@ -289,18 +289,18 @@ void sendGnssBuffer() { if (gnss->pushRawData(bluetoothOutgoingToGnss, bluetoothOutgoingToGnssHead)) { - if ((settings.debugCorrections || PERIODIC_DISPLAY(PD_ZED_DATA_TX)) && !inMainMenu) + if ((settings.debugCorrections || PERIODIC_DISPLAY(PD_GNSS_DATA_TX)) && !inMainMenu) { - PERIODIC_CLEAR(PD_ZED_DATA_TX); + PERIODIC_CLEAR(PD_GNSS_DATA_TX); systemPrintf("Sent %d BT bytes to GNSS\r\n", bluetoothOutgoingToGnssHead); } } } else { - if ((settings.debugCorrections || PERIODIC_DISPLAY(PD_ZED_DATA_TX)) && !inMainMenu) + if ((settings.debugCorrections || PERIODIC_DISPLAY(PD_GNSS_DATA_TX)) && !inMainMenu) { - PERIODIC_CLEAR(PD_ZED_DATA_TX); + PERIODIC_CLEAR(PD_GNSS_DATA_TX); systemPrintf("%d BT bytes NOT sent due to priority\r\n", bluetoothOutgoingToGnssHead); } } @@ -424,6 +424,14 @@ void gnssReadTask(void *e) if ((settings.enableTaskReports == true) && (!inMainMenu)) systemPrintf("SerialReadTask High watermark: %d\r\n", uxTaskGetStackHighWaterMark(nullptr)); + // Display the RX byte count + static uint32_t totalRxByteCount = 0; + if (PERIODIC_DISPLAY(PD_GNSS_DATA_RX_BYTE_COUNT)) + { + PERIODIC_CLEAR(PD_GNSS_DATA_RX_BYTE_COUNT); + systemPrintf("gnssReadTask total byte count: %d\r\n", totalRxByteCount); + } + // Two methods are accessing the hardware serial port (um980Config) at the // same time: gnssReadTask() (to harvest incoming serial data) and um980 (the unicore library to configure the // device) To allow the Unicore library to send/receive serial commands, we need to block the gnssReadTask @@ -454,6 +462,7 @@ void gnssReadTask(void *e) // Read the data from UART1 uint8_t incomingData[500]; int bytesIncoming = serialGNSS->read(incomingData, sizeof(incomingData)); + totalRxByteCount += bytesIncoming; for (int x = 0; x < bytesIncoming; x++) { @@ -573,9 +582,9 @@ void processUart1Message(SEMP_PARSE_STATE *parse, uint16_t type) int32_t use; // Display the message - if ((settings.enablePrintLogFileMessages || PERIODIC_DISPLAY(PD_ZED_DATA_RX)) && (!parse->crc) && (!inMainMenu)) + if ((settings.enablePrintLogFileMessages || PERIODIC_DISPLAY(PD_GNSS_DATA_RX)) && (!inMainMenu)) { - PERIODIC_CLEAR(PD_ZED_DATA_RX); + PERIODIC_CLEAR(PD_GNSS_DATA_RX); if (settings.enablePrintLogFileMessages) { printTimeStamp(); @@ -632,6 +641,16 @@ void processUart1Message(SEMP_PARSE_STATE *parse, uint16_t type) { // Give this data to the library to update its internal variables lg290pHandler(parse->buffer, parse->length); + + if (type == RTK_NMEA_PARSER_INDEX) + { + // Suppress PQTM/NMEA messages as needed + if (lg290pMessageEnabled((char *)parse->buffer, parse->length) == false) + { + parse->buffer[0] = 0; + parse->length = 0; + } + } } // Handle LLA compensation due to tilt or outputTipAltitude setting @@ -725,6 +744,25 @@ void processUart1Message(SEMP_PARSE_STATE *parse, uint16_t type) } } + // If BaseCasterOverride is enabled, remove everything but RTCM from the circular buffer + // to avoid saturating the downstream radio link that is consuming over a TCP (NTRIP Caster) connection + // Remove NMEA, etc after passing to the GNSS receiver library so that we still have SIV and other stats available + if (settings.baseCasterOverride == true) + { + if (type != RTK_RTCM_PARSER_INDEX) + { + // Erase buffer + parse->buffer[0] = 0; + parse->length = 0; + } + } + + // Push GGA to Caster if enabled + if (type == RTK_NMEA_PARSER_INDEX && strstr(sempNmeaGetSentenceName(parse), "GGA") != nullptr) + { + pushGPGGA((char *)parse->buffer); + } + // Determine if this message will fit into the ring buffer bytesToCopy = parse->length; space = availableHandlerSpace; @@ -1254,7 +1292,7 @@ void handleGnssDataTask(void *e) systemPrintf("SD %d bytes written to log file\r\n", bytesToSend); } - fileSize = logFile->fileSize(); // Update file size + logFileSize = logFile->fileSize(); // Update file size sdFreeSpace -= bytesToSend; // Update remaining space on SD @@ -1282,7 +1320,7 @@ void handleGnssDataTask(void *e) if (endTime - startTime > 150) systemPrintf("Long Write! Time: %ld ms / Location: %ld / Recorded %d bytes / " "spaceRemaining %d bytes\r\n", - endTime - startTime, fileSize, bytesToSend, combinedSpaceRemaining); + endTime - startTime, logFileSize, bytesToSend, combinedSpaceRemaining); } xSemaphoreGive(sdCardSemaphore); @@ -1371,7 +1409,7 @@ void handleGnssDataTask(void *e) void tickerBluetoothLedUpdate() { // If we are in WiFi config mode, fade LED - if (inWiFiConfigMode() == true) + if (inWebConfigMode() == true) { // Fade in/out the BT LED during WiFi AP mode btFadeLevel += pwmFadeAmount; @@ -1651,11 +1689,11 @@ void buttonCheckTask(void *e) } forceSystemStateUpdate = true; // Immediately go to this new state - changeState(STATE_WIFI_CONFIG_NOT_STARTED); + changeState(STATE_WEB_CONFIG_NOT_STARTED); } // If we are in WiFi Config Mode, exit to Rover - else if (inWiFiConfigMode()) + else if (inWebConfigMode()) { // Beep if we are not locally compiled or a release candidate if (ENABLE_DEVELOPER == false) @@ -1671,7 +1709,6 @@ void buttonCheckTask(void *e) forceSystemStateUpdate = true; // Immediately go to this new state changeState(STATE_ROVER_NOT_STARTED); - wifiRestart(); } } @@ -1745,14 +1782,13 @@ void buttonCheckTask(void *e) case STATE_BASE_TEMP_TRANSMITTING: case STATE_BASE_FIXED_NOT_STARTED: case STATE_BASE_FIXED_TRANSMITTING: - case STATE_WIFI_CONFIG_NOT_STARTED: - case STATE_WIFI_CONFIG: + case STATE_WEB_CONFIG_NOT_STARTED: + case STATE_WEB_CONFIG: case STATE_ESPNOW_PAIRING_NOT_STARTED: case STATE_ESPNOW_PAIRING: case STATE_NTPSERVER_NOT_STARTED: case STATE_NTPSERVER_NO_SYNC: case STATE_NTPSERVER_SYNC: - case STATE_CONFIG_VIA_ETH_NOT_STARTED: lastSystemState = systemState; // Remember this state to return if needed requestChangeState(STATE_DISPLAY_SETUP); lastSetupMenuChange = millis(); @@ -1802,6 +1838,7 @@ void buttonCheckTask(void *e) case STATE_TESTING: // If we are in testing, return to Base Not Started lastSetupMenuChange = millis(); // Prevent a timeout during state change + baseCasterDisableOverride(); // Leave Caster mode requestChangeState(STATE_BASE_NOT_STARTED); break; @@ -1810,14 +1847,6 @@ void buttonCheckTask(void *e) // Allow system to return to lastSystemState break; - case STATE_CONFIG_VIA_ETH_STARTED: - case STATE_CONFIG_VIA_ETH: - // If the user presses the button during configure-via-ethernet, - // do a complete restart into Base mode - lastSetupMenuChange = millis(); // Prevent a timeout during state change - requestChangeState(STATE_CONFIG_VIA_ETH_RESTART_BASE); - break; - default: systemPrintf("buttonCheckTask single tap - untrapped system state: %d\r\n", systemState); // requestChangeState(STATE_BASE_NOT_STARTED); @@ -1848,6 +1877,12 @@ void buttonCheckTask(void *e) firstButtonThrownOut = false; requestChangeState(lastSystemState); } + else if (it->newState == + STATE_BASE_NOT_STARTED) // User selected Base, clear BaseCast override + { + baseCasterDisableOverride(); + requestChangeState(it->newState); + } else requestChangeState(it->newState); @@ -2109,7 +2144,7 @@ void bluetoothCommandTask(void *pvParameters) } // Check stream for incoming characters - if (bluetoothCommandAvailable()) + if (bluetoothCommandAvailable() > 0) { byte incoming = bluetoothCommandRead(); diff --git a/Firmware/RTK_Everywhere/TcpClient.ino b/Firmware/RTK_Everywhere/TcpClient.ino index 6b895f848..d59ff2e3c 100644 --- a/Firmware/RTK_Everywhere/TcpClient.ino +++ b/Firmware/RTK_Everywhere/TcpClient.ino @@ -128,14 +128,14 @@ TcpClient.ino enum tcpClientStates { TCP_CLIENT_STATE_OFF = 0, - TCP_CLIENT_STATE_NETWORK_STARTED, + TCP_CLIENT_STATE_WAIT_FOR_NETWORK, TCP_CLIENT_STATE_CLIENT_STARTING, TCP_CLIENT_STATE_CONNECTED, // Insert new states here TCP_CLIENT_STATE_MAX // Last entry in the state list }; -const char *const tcpClientStateName[] = {"TCP_CLIENT_STATE_OFF", "TCP_CLIENT_STATE_NETWORK_STARTED", +const char *const tcpClientStateName[] = {"TCP_CLIENT_STATE_OFF", "TCP_CLIENT_STATE_WAIT_FOR_NETWORK", "TCP_CLIENT_STATE_CLIENT_STARTING", "TCP_CLIENT_STATE_CONNECTED"}; const int tcpClientStateNameEntries = sizeof(tcpClientStateName) / sizeof(tcpClientStateName[0]); @@ -148,7 +148,6 @@ const RtkMode_t tcpClientMode = RTK_MODE_BASE_FIXED | RTK_MODE_BASE_SURVEY_IN | static NetworkClient *tcpClient; static IPAddress tcpClientIpAddress; -static NetPriority_t tcpClientPriority = NETWORK_OFFLINE; static uint8_t tcpClientState; static volatile RING_BUFFER_OFFSET tcpClientTail; static volatile bool tcpClientWriteError; @@ -355,6 +354,14 @@ void tcpClientStop() 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; +} + // Update the TCP client state void tcpClientUpdate() { @@ -383,7 +390,7 @@ void tcpClientUpdate() | tcpClientStop | settings.enableTcpClient | | | V - +<----------TCP_CLIENT_STATE_NETWORK_STARTED + +<----------TCP_CLIENT_STATE_WAIT_FOR_NETWORK ^ | | | networkUserConnected | | @@ -409,23 +416,22 @@ void tcpClientUpdate() if (EQ_RTK_MODE(tcpClientMode) && settings.enableTcpClient) { timer = 0; - tcpClientPriority = NETWORK_OFFLINE; - tcpClientSetState(TCP_CLIENT_STATE_NETWORK_STARTED); + tcpClientSetState(TCP_CLIENT_STATE_WAIT_FOR_NETWORK); } break; // Wait until the network is connected - case TCP_CLIENT_STATE_NETWORK_STARTED: + 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 to the media - else if (networkIsConnected(&tcpClientPriority)) + // Wait until the network is connected + else if (networkHasInternet()) { #ifdef COMPILE_WIFI // Determine if WiFi is required - if ((!strlen(settings.tcpClientHost)) && (!networkIsInterfaceOnline(NETWORK_WIFI))) + if ((!strlen(settings.tcpClientHost)) && (!networkInterfaceHasInternet(NETWORK_WIFI))) { // Wrong network type, WiFi is required but another network is being used if ((millis() - timer) >= (15 * 1000)) @@ -457,7 +463,7 @@ void tcpClientUpdate() // Attempt the connection ot the TCP server case TCP_CLIENT_STATE_CLIENT_STARTING: // Determine if the network has failed - if (!networkIsConnected(&tcpClientPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network tcpClientStop(); @@ -502,7 +508,7 @@ 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 (!networkIsConnected(&tcpClientPriority)) + if (networkHasInternet() == false) // Failed to connect to to the network, attempt to restart the network tcpClientStop(); diff --git a/Firmware/RTK_Everywhere/TcpServer.ino b/Firmware/RTK_Everywhere/TcpServer.ino index 80cbbe931..fac038922 100644 --- a/Firmware/RTK_Everywhere/TcpServer.ino +++ b/Firmware/RTK_Everywhere/TcpServer.ino @@ -44,7 +44,7 @@ tcpServer.ino enum tcpServerStates { TCP_SERVER_STATE_OFF = 0, - TCP_SERVER_STATE_NETWORK_STARTED, + TCP_SERVER_STATE_WAIT_FOR_NETWORK, TCP_SERVER_STATE_RUNNING, // Insert new states here TCP_SERVER_STATE_MAX // Last entry in the state list @@ -52,7 +52,7 @@ enum tcpServerStates const char *const tcpServerStateName[] = { "TCP_SERVER_STATE_OFF", - "TCP_SERVER_STATE_NETWORK_STARTED", + "TCP_SERVER_STATE_WAIT_FOR_NETWORK", "TCP_SERVER_STATE_RUNNING", }; @@ -76,7 +76,6 @@ static volatile uint8_t tcpServerClientWriteError; static NetworkClient *tcpServerClient[TCP_SERVER_MAX_CLIENTS]; static IPAddress tcpServerClientIpAddress[TCP_SERVER_MAX_CLIENTS]; static volatile RING_BUFFER_OFFSET tcpServerClientTails[TCP_SERVER_MAX_CLIENTS]; -static NetPriority_t tcpServerPriority = NETWORK_OFFLINE; //---------------------------------------- // TCP Server handleGnssDataTask Support Routines @@ -232,15 +231,25 @@ bool tcpServerStart() if (settings.debugTcpServer && (!inMainMenu)) systemPrintln("TCP server starting the server"); + uint16_t tcpPort = settings.tcpServerPort; + if(settings.baseCasterOverride == true) + tcpPort = 2101; + // Start the TCP server - tcpServer = new NetworkServer(settings.tcpServerPort, TCP_SERVER_MAX_CLIENTS); + tcpServer = new NetworkServer(tcpPort, TCP_SERVER_MAX_CLIENTS); if (!tcpServer) return false; tcpServer->begin(); online.tcpServer = true; + localIp = networkGetIpAddress(); - systemPrintf("TCP server online, IP address %s:%d\r\n", localIp.toString().c_str(), settings.tcpServerPort); + if (settings.enableNtripCaster || settings.baseCasterOverride) + systemPrintf("TCP server online, IP address %s:%d, responding as NTRIP Caster\r\n", localIp.toString().c_str(), + tcpPort); + else + systemPrintf("TCP server online, IP address %s:%d\r\n", localIp.toString().c_str(), tcpPort); + return true; } @@ -314,6 +323,14 @@ void tcpServerStopClient(int 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() { @@ -324,7 +341,7 @@ void tcpServerUpdate() // Shutdown the TCP server when the mode or setting changes DMW_st(tcpServerSetState, tcpServerState); - if (NEQ_RTK_MODE(tcpServerMode) || (!settings.enableTcpServer)) + if (NEQ_RTK_MODE(tcpServerMode) || (!settings.enableTcpServer && !settings.baseCasterOverride)) { if (tcpServerState > TCP_SERVER_STATE_OFF) tcpServerStop(); @@ -338,7 +355,7 @@ void tcpServerUpdate() | tcpServerStop | settings.enableTcpServer | | | V - +<----------TCP_SERVER_STATE_NETWORK_STARTED + +<----------TCP_SERVER_STATE_WAIT_FOR_NETWORK ^ | | | networkUserConnected | | @@ -359,23 +376,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) + if (EQ_RTK_MODE(tcpServerMode) && (settings.enableTcpServer || settings.baseCasterOverride)) { if (settings.debugTcpServer && (!inMainMenu)) systemPrintln("TCP server start"); - tcpServerPriority = NETWORK_OFFLINE; - tcpServerSetState(TCP_SERVER_STATE_NETWORK_STARTED); + tcpServerSetState(TCP_SERVER_STATE_WAIT_FOR_NETWORK); } break; // Wait until the network is connected - case TCP_SERVER_STATE_NETWORK_STARTED: + case TCP_SERVER_STATE_WAIT_FOR_NETWORK: // Determine if the TCP server was turned off - if (NEQ_RTK_MODE(tcpServerMode) || !settings.enableTcpServer) + if (NEQ_RTK_MODE(tcpServerMode) || (!settings.enableTcpServer && !settings.baseCasterOverride)) tcpServerStop(); // Wait until the network is connected to the media - else if (networkIsConnected(&tcpServerPriority)) + else if (networkHasInternet() || WIFI_SOFT_AP_RUNNING()) { // Delay before starting the TCP server if ((millis() - tcpServerTimer) >= (1 * 1000)) @@ -393,7 +409,7 @@ void tcpServerUpdate() // Handle client connections and link failures case TCP_SERVER_STATE_RUNNING: // Determine if the network has failed - if ((!settings.enableTcpServer) || (!networkIsConnected(&tcpServerPriority))) + if ((networkHasInternet() == false && WIFI_SOFT_AP_RUNNING() == false) || (!settings.enableTcpServer && !settings.baseCasterOverride)) { if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_DATA)) && (!inMainMenu)) { @@ -440,27 +456,77 @@ void tcpServerUpdate() // Determine if the client data structure is in use if (!(tcpServerClientConnected & (1 << index))) { - NetworkClient client; + if(tcpServerClient[index] == nullptr) + tcpServerClient[index] = new NetworkClient; // Data structure not in use // Check for another TCP server client - client = tcpServer->accept(); + *tcpServerClient[index] = tcpServer->accept(); - // Done if no TCP server client found - if (!client) + // Exit if no TCP server client found + if (! *tcpServerClient[index]) break; // Start processing the new TCP server client connection - tcpServerClient[index] = new NetworkClient; tcpServerClientIpAddress[index] = tcpServerClient[index]->remoteIP(); - tcpServerClientConnected = tcpServerClientConnected | (1 << index); - tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); + if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_DATA)) && (!inMainMenu)) { PERIODIC_CLEAR(PD_TCP_SERVER_DATA); systemPrintf("TCP server client %d connected to %s\r\n", index, tcpServerClientIpAddress[index].toString().c_str()); } + + // If we are acting as an NTRIP Caster, intercept the initial communication from the client + // and respond accordingly + if (settings.enableNtripCaster || settings.baseCasterOverride) + { + // Read response from client + char response[512]; + int spot = 0; + while (tcpServerClient[index]->available()) + { + response[spot++] = tcpServerClient[index]->read(); + if (spot == sizeof(response)) + spot = 0; // Wrap + } + response[spot] = '\0'; // Terminate string + + if (strnstr(response, "GET / ", sizeof(response)) != NULL) // No mount point in header + { + if (settings.debugTcpServer) + systemPrintln("Mount point table requested."); + + // Respond with a single mountpoint + const char fakeSourceTable[] = + "SOURCETABLE 200 OK\r\nServer: SparkPNT Caster/1.0\r\nContent-Type: " + "text/plain\r\nContent-Length: 96\r\n\r\nSTR;SparkBase;none;RTCM " + "3.0;none;none;none;none;none;none;none;none;none;none;none;B;N;none;none"; + + tcpServerClient[index]->write(fakeSourceTable, strlen(fakeSourceTable)); + + tcpServerStopClient(index); // Disconnect from client + } + else if (strnstr(response, "GET /", sizeof(response)) != NULL) // Mount point in header + { + // NTRIP Client is sending us their mount point. Begin sending RTCM. + if (settings.debugTcpServer) + systemPrintln("NTRIP Client connected - Sending ICY 200 OK"); + + char confirmConnection[] = "ICY 200 OK\r\n"; + tcpServerClient[index]->write(confirmConnection, strlen(confirmConnection)); + } + else + { + // Unknown response + if (settings.debugTcpServer) + systemPrintf("Unknown response: %s\r\n", response); + } + } // settings.enableNtripCaster == true || settings.baseCasterOverride == true + + // Make client online after any NTRIP injections so ring buffer can start outputting data to it + tcpServerClientConnected = tcpServerClientConnected | (1 << index); + tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); } } diff --git a/Firmware/RTK_Everywhere/UdpServer.ino b/Firmware/RTK_Everywhere/UdpServer.ino index 8272bcba3..3a9a8d27f 100644 --- a/Firmware/RTK_Everywhere/UdpServer.ino +++ b/Firmware/RTK_Everywhere/UdpServer.ino @@ -69,7 +69,7 @@ UdpServer.ino enum udpServerStates { UDP_SERVER_STATE_OFF = 0, - UDP_SERVER_STATE_NETWORK_STARTED, + UDP_SERVER_STATE_WAIT_FOR_NETWORK, UDP_SERVER_STATE_RUNNING, // Insert new states here UDP_SERVER_STATE_MAX // Last entry in the state list @@ -77,7 +77,7 @@ enum udpServerStates const char *const udpServerStateName[] = { "UDP_SERVER_STATE_OFF", - "UDP_SERVER_STATE_NETWORK_STARTED", + "UDP_SERVER_STATE_WAIT_FOR_NETWORK", "UDP_SERVER_STATE_RUNNING", }; @@ -94,7 +94,6 @@ static NetworkUDP *udpServer = nullptr; static uint8_t udpServerState; static uint32_t udpServerTimer; static volatile RING_BUFFER_OFFSET udpServerTail; -static NetPriority_t udpServerPriority; //---------------------------------------- // UDP Server handleGnssDataTask Support Routines @@ -107,7 +106,7 @@ int32_t udpServerSendDataBroadcast(uint8_t *data, uint16_t length) return 0; // Send the data as broadcast - if (settings.enableUdpServer && online.udpServer && networkIsConnected(&udpServerPriority)) + if (settings.enableUdpServer && online.udpServer && networkHasInternet()) { IPAddress broadcastAddress = networkGetBroadcastIpAddress(); udpServer->beginPacket(broadcastAddress, settings.udpServerPort); @@ -276,6 +275,14 @@ void udpServerStop() } } +// 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() { @@ -297,7 +304,7 @@ void udpServerUpdate() | udpServerStop | settings.enableUdpServer | | | V - +<---------UDP_SERVER_STATE_NETWORK_STARTED + +<---------UDP_SERVER_STATE_WAIT_FOR_NETWORK ^ | | | networkUserConnected | | @@ -317,19 +324,18 @@ void udpServerUpdate() { if (settings.debugUdpServer && (!inMainMenu)) systemPrintln("UDP server starting the network"); - udpServerPriority = NETWORK_OFFLINE; - udpServerSetState(UDP_SERVER_STATE_NETWORK_STARTED); + udpServerSetState(UDP_SERVER_STATE_WAIT_FOR_NETWORK); } break; // Wait until the network is connected - case UDP_SERVER_STATE_NETWORK_STARTED: + 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 to the media - else if (networkIsConnected(&udpServerPriority)) + // Wait until the network is connected + else if (networkHasInternet() || WIFI_SOFT_AP_RUNNING()) { // Delay before starting the UDP server if ((millis() - udpServerTimer) >= (1 * 1000)) @@ -347,7 +353,7 @@ void udpServerUpdate() // Handle client connections and link failures case UDP_SERVER_STATE_RUNNING: // Determine if the network has failed - if ((!settings.enableUdpServer) || (!networkIsConnected(&udpServerPriority))) + if ((networkHasInternet() == false && WIFI_SOFT_AP_RUNNING() == false) || (!settings.enableUdpServer && !settings.baseCasterOverride)) { if ((settings.debugUdpServer || PERIODIC_DISPLAY(PD_UDP_SERVER_DATA)) && (!inMainMenu)) { diff --git a/Firmware/RTK_Everywhere/Form.ino b/Firmware/RTK_Everywhere/WebServer.ino similarity index 68% rename from Firmware/RTK_Everywhere/Form.ino rename to Firmware/RTK_Everywhere/WebServer.ino index 4f3fe7a9c..59dadd7e5 100644 --- a/Firmware/RTK_Everywhere/Form.ino +++ b/Firmware/RTK_Everywhere/WebServer.ino @@ -1,11 +1,34 @@ /*------------------------------------------------------------------------------ -Form.ino +WebServer.ino Start and stop the web-server, provide the form and handle browser input. ------------------------------------------------------------------------------*/ #ifdef COMPILE_AP +// State machine to allow web server access to network layer +enum WebServerState +{ + WEBSERVER_STATE_OFF = 0, + WEBSERVER_STATE_WAIT_FOR_NETWORK, + WEBSERVER_STATE_NETWORK_CONNECTED, + WEBSERVER_STATE_RUNNING, + + // Add new states here + WEBSERVER_STATE_MAX +}; + +static const char *const webServerStateNames[] = { + "WEBSERVER_STATE_OFF", + "WEBSERVER_STATE_WAIT_FOR_NETWORK", + "WEBSERVER_STATE_NETWORK_CONNECTED", + "WEBSERVER_STATE_RUNNING", +}; + +static const int webServerStateEntries = sizeof(webServerStateNames) / sizeof(webServerStateNames[0]); + +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. @@ -30,7 +53,7 @@ void sendStringToWebsocket(const char *stringToSend) return; } - // To send content to the webserver, we would call: webserver->sendContent(stringToSend); + // 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; @@ -53,7 +76,7 @@ void sendStringToWebsocket(const char *stringToSend) } else { - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("sendStringToWebsocket: %s\r\n", stringToSend); } } @@ -70,7 +93,7 @@ static esp_err_t ws_handler(httpd_req_t *req) // TODO: do we need to be cleverer about this? last_ws_fd = httpd_req_to_sockfd(req); - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("Handshake done, the new ws connection was opened with fd %d\r\n", last_ws_fd); websocketConnected = true; @@ -91,7 +114,7 @@ static esp_err_t ws_handler(httpd_req_t *req) systemPrintf("httpd_ws_recv_frame failed to get frame len with %d\r\n", ret); return ret; } - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("frame len is %d\r\n", ws_pkt.len); if (ws_pkt.len) { @@ -116,7 +139,7 @@ static esp_err_t ws_handler(httpd_req_t *req) return ret; } } - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("Packet type: %d\r\n", ws_pkt.type); // HTTPD_WS_TYPE_CONTINUE = 0x0, // HTTPD_WS_TYPE_TEXT = 0x1, @@ -127,8 +150,11 @@ static esp_err_t ws_handler(httpd_req_t *req) if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) { - if (settings.debugWebConfig == true) + 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) { @@ -139,10 +165,15 @@ static esp_err_t ws_handler(httpd_req_t *req) } 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.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Client closed or refreshed the web page"); createSettingsString(settingsCSV); @@ -161,18 +192,18 @@ static const httpd_uri_t ws = {.uri = "/ws", .handle_ws_control_frames = true, .supported_subprotocol = NULL}; -static void start_wsserver(void) +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 + // 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.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("Starting wsserver on port: %d\r\n", config.server_port); if (wsserver == nullptr) @@ -181,26 +212,14 @@ static void start_wsserver(void) if (httpd_start(wsserver, &config) == ESP_OK) { // Registering the ws handler - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Registering URI handlers"); httpd_register_uri_handler(*wsserver, &ws); - return; + return true; } systemPrintln("Error starting wsserver!"); -} - -void stop_wsserver() -{ - createSettingsString(settingsCSV); - websocketConnected = false; - - if (*wsserver) - { - // Stop the httpd server - esp_err_t ret = httpd_stop(*wsserver); - //*wsserver = nullptr; - } + return false; } // ===== Request Handler class used to answer more complex requests ===== @@ -213,7 +232,7 @@ class CaptiveRequestHandler : public RequestHandler "/check_network_status.txt"}; CaptiveRequestHandler() { - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("CaptiveRequestHandler is registered"); } virtual ~CaptiveRequestHandler() @@ -233,7 +252,7 @@ class CaptiveRequestHandler : public RequestHandler bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) { String logmessage = "Captive Portal Client:" + server.client().remoteIP().toString() + " " + requestUri; - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln(logmessage); String response = "RTK Config"; response += "
"; @@ -250,26 +269,12 @@ class CaptiveRequestHandler : public RequestHandler } }; -// Start webserver in AP mode -bool startWebServer(bool startWiFi = true, int httpPort = 80) +// Create the web server and web sockets +bool webServerAssignResources(int httpPort = 80) { do { - ntripClientStop(true); // Do not allocate new wifiClient - for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) - ntripServerStop(serverIndex, true); // Do not allocate new wifiClient - - if (startWiFi) - if (wifiStartAP() == false) - break; - - // Start the multicast DNS server - if (startWiFi == true) - networkMulticastDNSSwitch(NETWORK_WIFI); - else - networkMulticastDNSSwitch(NETWORK_ETHERNET); - - // Freed by stopWebServer + // Freed by webServerStop if (online.psram == true) incomingSettings = (char *)ps_malloc(AP_CONFIG_SETTING_SIZE); else @@ -283,7 +288,7 @@ bool startWebServer(bool startWiFi = true, int httpPort = 80) memset(incomingSettings, 0, AP_CONFIG_SETTING_SIZE); // Pre-load settings CSV - // Freed by stopWebServer + // Freed by webServerStop if (online.psram == true) settingsCSV = (char *)ps_malloc(AP_CONFIG_SETTING_SIZE); else @@ -296,28 +301,24 @@ bool startWebServer(bool startWiFi = true, int httpPort = 80) } createSettingsString(settingsCSV); - // https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino + // + https: // github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino if (settings.enableCaptivePortal == true) { dnsserver = new DNSServer; dnsserver->start(); } - webserver = new WebServer(httpPort); - // TODO: webserver = new WebServer(WiFi.localIP(), httpPort); - // TODO: webserver = new WebServer(ETH.localIP(), httpPort); - - if (!webserver) + webServer = new WebServer(httpPort); + if (!webServer) { - systemPrintln("ERROR: Failed to allocate webserver"); + systemPrintln("ERROR: Failed to allocate webServer"); break; } if (settings.enableCaptivePortal == true) { - webserver->addHandler(new CaptiveRequestHandler()); - - // TODO: add a handler for /connecttest.txt + webServer->addHandler(new CaptiveRequestHandler()); } // * index.html (not gz'd) @@ -341,165 +342,169 @@ bool startWebServer(bool startWiFi = true, int httpPort = 80) // * /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); + webServer->onNotFound(notFound); - webserver->on("/", HTTP_GET, []() { - webserver->sendHeader("Content-Encoding", "gzip"); - webserver->send_P(200, "text/html", (const char *)index_html, sizeof(index_html)); + webServer->on("/", HTTP_GET, []() { + webServer->sendHeader("Content-Encoding", "gzip"); + webServer->send_P(200, "text/html", (const char *)index_html, sizeof(index_html)); }); - webserver->on("/favicon.ico", HTTP_GET, []() { - webserver->sendHeader("Content-Encoding", "gzip"); - webserver->send_P(200, "text/plain", (const char *)favicon_ico, sizeof(favicon_ico)); + webServer->on("/favicon.ico", HTTP_GET, []() { + webServer->sendHeader("Content-Encoding", "gzip"); + webServer->send_P(200, "text/plain", (const char *)favicon_ico, sizeof(favicon_ico)); }); - 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, + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); - webserver->on("/src/rtk-setup.png", HTTP_GET, []() { - webserver->sendHeader("Content-Encoding", "gzip"); + 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)); + 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)); + webServer->send_P(200, "image/png", (const char *)rtkSetupWiFi_png, sizeof(rtkSetupWiFi_png)); }); // 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/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/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/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/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)); + 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)); }); - 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/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/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/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)); + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); - 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)); + 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)); }); + // 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", ""); }, + 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); + 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.debugWebConfig == true) + 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); + 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.debugWebConfig == true) + 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.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln(messages); - webserver->send(200, "text/plain", 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.debugWebConfig == true) + 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.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln(messageList); - webserver->send(200, "text/plain", messageList); + webServer->send(200, "text/plain", messageList); }); // Handler for file manager - webserver->on("/file", HTTP_GET, handleFileManager); + webServer->on("/file", HTTP_GET, handleFileManager); - webserver->begin(); + webServer->begin(); - // Starts task for updating webserver with handleClient + // Starts task for updating webServer with handleClient if (task.updateWebServerTaskRunning == false) xTaskCreate( updateWebServerTask, @@ -509,14 +514,23 @@ bool startWebServer(bool startWiFi = true, int httpPort = 80) updateWebServerTaskPriority, &updateWebServerTaskHandle); // Task handle - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Web Server Started"); reportHeapNow(false); // Start the web socket server on port 81 using - start_wsserver(); + if (websocketServerStart() == false) + { + if (settings.debugWebServer == true) + systemPrintln("Web Sockets failed to start"); + + webServerStopSockets(); + webServerReleaseResources(); - if (settings.debugWebConfig == true) + return (false); + } + + if (settings.debugWebServer == true) systemPrintln("Web Socket Server Started"); reportHeapNow(false); @@ -524,11 +538,129 @@ bool startWebServer(bool startWiFi = true, int httpPort = 80) } while (0); // Release the resources - stop_wsserver(); - stopWebServer(); + webServerStopSockets(); + webServerReleaseResources(); return false; } +// Start the Web Server state machine +void webServerStart() +{ + // Display the heap state + reportHeapNow(settings.debugWebServer); + + systemPrintln("Web Server start"); + webServerSetState(WEBSERVER_STATE_WAIT_FOR_NETWORK); +} + +// Stop the web config state machine +void webServerStop() +{ + online.webServer = false; + + if (settings.debugWebServer) + systemPrintln("webServerStop called"); + + if (webServerState != WEBSERVER_STATE_OFF) + { + webServerStopSockets(); // Release socket resources + webServerReleaseResources(); // Release web server resources + + // Stop network + systemPrintln("Web Server releasing network request"); + + // Stop the machine + webServerSetState(WEBSERVER_STATE_OFF); + } +} + +// 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; +} + +void webServerStopSockets() +{ + websocketConnected = false; + + if (wsserver != nullptr) + { + // Stop the httpd server + esp_err_t ret = httpd_stop(*wsserver); + wsserver = nullptr; + } +} + +// 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; + + // Display the state transition + if (settings.debugWebServer) + { + arrow = ""; + asterisk = ""; + initialState = ""; + if (newState == webServerState) + asterisk = "*"; + else + { + initialState = webServerGetStateName(webServerState, string1); + arrow = " --> "; + } + } + + // Set the new state + webServerState = newState; + if (settings.debugWebServer) + { + // 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()); + } + } + + // Validate the state + if (newState >= WEBSERVER_STATE_MAX) + reportFatalError("Invalid web config state"); +} + +// 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; +} + +bool webServerIsRunning() +{ + if (webServerState == WEBSERVER_STATE_RUNNING) + return (true); + return (false); +} + void updateWebServerTask(void *e) { // Start notification @@ -547,7 +679,7 @@ void updateWebServerTask(void *e) systemPrintln("updateWebServerTask running"); } - webserver->handleClient(); + webServer->handleClient(); feedWdt(); taskYIELD(); @@ -565,11 +697,16 @@ void stopWebServer() if (task.updateWebServerTaskRunning) task.updateWebServerTaskStopRequest = true; - if (webserver != nullptr) + // Wait for task to stop running + do + delay(10); + while (task.updateWebServerTaskRunning); + + if (webServer != nullptr) { - webserver->close(); - free(webserver); - webserver = nullptr; + webServer->close(); + free(webServer); + webServer = nullptr; } // Stop the multicast DNS server @@ -586,17 +723,103 @@ void stopWebServer() free(incomingSettings); incomingSettings = nullptr; } +} + +void webServerReleaseResources() +{ + 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(); + + if (settingsCSV != nullptr) + { + free(settingsCSV); + settingsCSV = nullptr; + } + + if (incomingSettings != nullptr) + { + free(incomingSettings); + incomingSettings = nullptr; + } +} + +// State machine to handle the starting/stopping of the web server +void webServerUpdate() +{ + // Walk the state machine + switch (webServerState) + { + 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 (networkHasInternet() || WIFI_SOFT_AP_RUNNING()) + { + if (settings.debugWebServer) + systemPrintln("Web Server connected to network"); + + webServerSetState(WEBSERVER_STATE_NETWORK_CONNECTED); + } + break; + + // 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"); + + if (webServerAssignResources(settings.httpPort) == true) + { + online.webServer = true; + webServerSetState(WEBSERVER_STATE_RUNNING); + } + } + 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 + + break; + } } void notFound() { - String logmessage = "notFound: Client:" + webserver->client().remoteIP().toString() + " " + webserver->uri(); + String logmessage = "notFound: Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); systemPrintln(logmessage); - webserver->send(404, "text/plain", "Not found"); + webServer->send(404, "text/plain", "Not found"); } // Handler for firmware file downloads @@ -604,14 +827,14 @@ static void handleFileManager() { // This section does not tolerate semaphore transactions String logmessage = - "handleFileManager: Client:" + webserver->client().remoteIP().toString() + " " + webserver->uri(); + "handleFileManager: Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri(); - if (webserver->hasArg("name") && webserver->hasArg("action")) + if (webServer->hasArg("name") && webServer->hasArg("action")) { - String fileName = webserver->arg("name"); - String fileAction = webserver->arg("action"); + String fileName = webServer->arg("name"); + String fileAction = webServer->arg("action"); - logmessage = "Client:" + webserver->client().remoteIP().toString() + " " + webserver->uri() + + logmessage = "Client:" + webServer->client().remoteIP().toString() + " " + webServer->uri() + "?name=" + fileName + "&action=" + fileAction; char slashFileName[60]; @@ -624,7 +847,7 @@ static void handleFileManager() logmessage += " ERROR: file "; logmessage += slashFileName; logmessage += " does not exist"; - webserver->send(400, "text/plain", "ERROR: file does not exist"); + webServer->send(400, "text/plain", "ERROR: file does not exist"); } else { @@ -634,7 +857,7 @@ static void handleFileManager() { logmessage += " downloaded"; - // This would be SO much easier with webserver->streamFile + // 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 @@ -659,7 +882,7 @@ static void handleFileManager() else { // File is already in use. Wait your turn. - webserver->send(202, "text/plain", "ERROR: File already downloading"); + webServer->send(202, "text/plain", "ERROR: File already downloading"); } const size_t maxLen = 8192; @@ -684,15 +907,15 @@ static void handleFileManager() 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", ""); + 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); + webServer->sendContent((const char *)buf, sending); if (sending < maxLen) // Last send? { @@ -712,19 +935,19 @@ static void handleFileManager() { logmessage += " deleted"; sd->remove(slashFileName); - webserver->send(200, "text/plain", "Deleted File: " + fileName); + 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"); + webServer->send(400, "text/plain", "ERROR: invalid action param supplied"); } } systemPrintln(logmessage); } else { - webserver->send(400, "text/plain", "ERROR: name and action params required"); + webServer->send(400, "text/plain", "ERROR: name and action params required"); } } @@ -733,7 +956,7 @@ static void handleFirmwareFileUpload() { String fileName = ""; - HTTPUpload &upload = webserver->upload(); + HTTPUpload &upload = webServer->upload(); if (upload.status == UPLOAD_FILE_START) { @@ -758,21 +981,21 @@ static void handleFirmwareFileUpload() if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { Update.printError(Serial); - webserver->send(400, "text/plain", "OTA could not begin"); + 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"); + webServer->send(400, "text/html", "Error: Unknown file type"); return; } } else { systemPrintf("handleFirmwareFileUpload: Unknown: %s\r\n", fname); - webserver->send(400, "text/html", "Error: Unknown file type"); + webServer->send(400, "text/html", "Error: Unknown file type"); return; } } @@ -782,7 +1005,7 @@ static void handleFirmwareFileUpload() { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - webserver->send(400, "text/plain", "OTA could not begin"); + webServer->send(400, "text/plain", "OTA could not begin"); return; } else @@ -812,7 +1035,7 @@ static void handleFirmwareFileUpload() if (!Update.end(true)) { Update.printError(Serial); - webserver->send(400, "text/plain", "Could not end OTA"); + webServer->send(400, "text/plain", "Could not end OTA"); return; } else @@ -825,7 +1048,7 @@ static void handleFirmwareFileUpload() } } -// Report back to the web config page with a CSV that contains the either CURRENT or +// 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) { @@ -840,13 +1063,13 @@ void createFirmwareVersionString(char *settingsCSV) // Compare the unit's version against the reported version from OTA if (isReportedVersionNewer(otaReportedVersion, currentVersion) == true) { - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("New version detected"); snprintf(newVersionCSV, sizeof(newVersionCSV), "%s,", otaReportedVersion); } else { - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("No new firmware available"); snprintf(newVersionCSV, sizeof(newVersionCSV), "CURRENT,"); } @@ -903,6 +1126,10 @@ void createDynamicDataString(char *settingsCSV) stringRecord(settingsCSV, "batteryIconFileName", batteryIconFileName); + // Limit batteryLevelPercent to sane levels + if (batteryLevelPercent > 100) + batteryLevelPercent = 100; + // Determine battery percent char batteryPercent[sizeof("+100%__")]; if (isCharging()) @@ -947,7 +1174,7 @@ bool parseIncomingSettings() headPtr = commaPtr + 1; } - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("settingName: %s value: %s\r\n", settingName, valueStr); updateSettingWithValue(false, settingName, valueStr); @@ -964,7 +1191,7 @@ bool parseIncomingSettings() if (counter < maxAttempts) { // Confirm receipt - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Sending receipt confirmation of settings"); sendStringToWebsocket("confirmDataReceipt,1,"); } @@ -1027,7 +1254,7 @@ void getFileList(String &returnText) systemPrintf("sdCardSemaphore failed to yield, held by %s, Form.ino line %d\r\n", semaphoreHolder, __LINE__); } - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); } @@ -1091,7 +1318,7 @@ void createMessageList(String &returnText) } #endif // COMPILE_MOSAICX5 - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); } @@ -1143,7 +1370,7 @@ void createMessageListBase(String &returnText) } #endif // COMPILE_MOSAICX5 - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str()); } @@ -1151,7 +1378,7 @@ void createMessageListBase(String &returnText) // https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino void handleUpload() { - HTTPUpload &upload = webserver->upload(); + HTTPUpload &upload = webServer->upload(); if (upload.status == UPLOAD_FILE_START) { @@ -1193,7 +1420,7 @@ void handleUpload() else { // File is already in use. Wait your turn. - webserver->send(202, "text/plain", "ERROR: File already uploading"); + webServer->send(202, "text/plain", "ERROR: File already uploading"); } systemPrintln(logmessage); @@ -1232,9 +1459,16 @@ void handleUpload() systemPrintln(logmessage); // Redirect to "/" - webserver->sendHeader("Location", "/"); - webserver->send(302, "text/plain", ""); + webServer->sendHeader("Location", "/"); + webServer->send(302, "text/plain", ""); } } +// Verify the web server tables +void webServerVerifyTables() +{ + if (webServerStateEntries != WEBSERVER_STATE_MAX) + reportFatalError("Fix webServerStateNames to match WebServerState"); +} + #endif // COMPILE_AP diff --git a/Firmware/RTK_Everywhere/WiFi.ino b/Firmware/RTK_Everywhere/WiFi.ino index e23b3ac38..6e65eed65 100644 --- a/Firmware/RTK_Everywhere/WiFi.ino +++ b/Firmware/RTK_Everywhere/WiFi.ino @@ -1,68 +1,448 @@ -/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - 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.ino -//---------------------------------------- -// Globals -//---------------------------------------- + WiFi layer, supports use by ESP-NOW, soft AP and WiFi station +**********************************************************************/ -int wifiConnectionAttempts; // Count the number of connection attempts between restarts +#ifdef COMPILE_WIFI -bool restartWiFi = false; // Restart WiFi if user changes anything +//**************************************** +// Constants +//**************************************** -#ifdef COMPILE_WIFI +#define WIFI_RECONNECTION_DELAY 1000 +#define WIFI_DEFAULT_CHANNEL 1 +#define WIFI_IP_ADDRESS_TIMEOUT_MSEC (15 * 1000) -WiFiMulti *wifiMulti; +static const char * wifiAuthorizationName[] = +{ + "Open", + "WEP", + "WPA_PSK", + "WPA2_PSK", + "WPA_WPA2_PSK", + "WPA2_Enterprise", + "WPA3_PSK", + "WPA2_WPA3_PSK", + "WAPI_PSK", + "OWE", + "WPA3_ENT_192", +}; +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 +// 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(ESPNOW_BROADCASTING) +// B. If peers exist, +// 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 +// +// In espnowOnDataReceived +// 11. Save ESP-NOW RSSI +// 12. Set lastEspnowRssiUpdate = 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) +// 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 +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- +// Soft AP startup from example 4_11 +// 1. Get mode +// 2. Start WiFi event handler +// 3. Set SSID and password, call softApCreate +// 4. Set IP addresses, call softApConfiguration +// 5. esp_wifi_get_protocol +// 6. Set AP protocols, call esp_wifi_set_protocol(b, g, n) +// 7. Set AP mode +// 8. Set host name +// 9. Set mDNS +// +// Soft AP shutdown from example 4_11 +// 1. Stop mDNS +// 2. Get mode +// 3. Disable AP mode +// 4. Stop WiFi event handler +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- +// WiFi station startup from example 4_8 +// 1. Get mode +// 2. Start WiFi event handler +// 3. Set WiFi station mode +// 4. Start Wifi scan +// +// In WiFi event handler for scan complete +// 5. Select AP and channel +// 6. Get mode +// 7. Start WiFi station mode +// 8. Set host name +// 9. Disable auto reconnect +// 10. Connect to AP +// +// In WiFi event handler for station connected +// 11. Set stationConnected +// +// In WiFi event handler for GOT_IP or GOT_IP6 +// 12. Set stationHasIp +// 13. Save IP address +// 14. Save IP address type +// 15. Display IP address +// 16. Start mDNS +// +// In WiFi event handler for LOST_IP +// 1. Clear stationHasIp +// 2. Display IP address +// +// In WiFi event handler for all other WiFi station events except +// GOT_IP, GOT_IP6 or LOST_IP +// 1. Clear WiFi channel +// 2. Clear stationConnected +// 3. Clear stationHasIp +// 4. For STOP +// A. Clear timer +// Else for DISCONNECTED +// A. Start timer (set to non-zero value) +// +// In wifiUpdate +// 5. When timer fires +// A. Start scan +// +// WiFi station shutdown from example 4-8 +// 1. Get mode +// 2. Exit if WiFi station is not running +// 3. Stop mDNS +// 4. Disconnect from remote AP +// 5. Stop WiFi station mode +// 6. Stop the event handler +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- +// Combining Soft AP shutdown with WiFi shutdown +// 1. Stop mDNS +// 2. Get mode +// 3. Disconnect from remote AP +// 4. Stop necessary modes (AP and station) +// 5. Stop the event handler +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- +// Combining soft AP starting with WiFi station starting, do the following +// synchronously: +// 1. Get mode +// 2. Start WiFi event handler +// 3. Set AP SSID and password, call softApCreate +// 4. Set AP IP addresses, call softApConfiguration +// 5. Get the AP protocols, call esp_wifi_get_protocol +// 6. Set AP protocols, call esp_wifi_set_protocol(b, g, n) +// 7. Set the necessary WiFi modes +// 8. Set AP host name +// 9. If starting AP +// Start mDNS +// 10. If starting WiFi station +// Start Wifi scan +// +// The rest of the startup is handled asynchronously by the WiFi +// event handler: +// +// For SCAN_COMPLETE +// 11. Select AP and channel +// 12. Set station host name +// 13. Disable auto reconnect +// 14. Connect to AP +// +// For STA_CONNECTED +// 15. Set stationConnected +// +// For GOT_IP or GOT_IP6 +// 16. Set stationHasIp +// 17. Save IP address +// 18. Save IP address type +// 19. Display IP address +// 20. If AP mode not running +// Start mDNS +// +// For LOST_IP +// 1. Clear stationHasIp +// 2. Display IP address +// +// In WiFi event handler for all other WiFi station events except +// STA_GOT_IP, STA_GOT_IP6 or STA_LOST_IP +// 1. If ESP-NOW and soft AP not running +// Clear WiFi channel +// 2. Clear stationConnected +// 3. Clear stationHasIp +// 4. For STA_STOP +// A. Clear timer +// Else for STA_DISCONNECTED +// A. Start timer (set to non-zero value) +// +// In wifiUpdate +// 5. When timer fires +// A. Start scan +//---------------------------------------------------------------------- + +//**************************************** // Constants -//---------------------------------------- +//**************************************** + +// Radio operations +#define WIFI_AP_SET_MODE 1 +#define WIFI_EN_SET_MODE 2 +#define WIFI_STA_SET_MODE 4 +#define WIFI_AP_SET_PROTOCOLS 8 +#define WIFI_EN_SET_PROTOCOLS 0x00000010 +#define WIFI_STA_SET_PROTOCOLS 0x00000020 +#define WIFI_STA_START_SCAN 0x00000040 +#define WIFI_STA_SELECT_REMOTE_AP 0x00000080 +#define WIFI_AP_SELECT_CHANNEL 0x00000100 +#define WIFI_EN_SELECT_CHANNEL 0x00000200 +#define WIFI_STA_SELECT_CHANNEL 0x00000400 + +// Soft AP +#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 + +// 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 + +// 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 + +// WIFI_MAX_START must be the last value in the define list +#define WIFI_MAX_START 0x08000000 + +const char * const wifiStartNames[] = +{ + "WIFI_AP_SET_MODE", + "WIFI_EN_SET_MODE", + "WIFI_STA_SET_MODE", + "WIFI_AP_SET_PROTOCOLS", + "WIFI_EN_SET_PROTOCOLS", + "WIFI_STA_SET_PROTOCOLS", + "WIFI_STA_START_SCAN", + "WIFI_STA_SELECT_REMOTE_AP", + "WIFI_AP_SELECT_CHANNEL", + "WIFI_EN_SELECT_CHANNEL", + "WIFI_STA_SELECT_CHANNEL", + + "WIFI_AP_SET_SSID_PASSWORD", + "WIFI_AP_SET_IP_ADDR", + "WIFI_AP_SET_HOST_NAME", + "WIFI_AP_START_MDNS", + "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", + "WIFI_EN_SET_PROMISCUOUS_MODE", + "WIFI_EN_PROMISCUOUS_RX_CALLBACK", + "WIFI_EN_START_ESP_NOW", + "WIFI_EN_ESP_NOW_ONLINE", +}; +const int wifiStartNamesEntries = sizeof(wifiStartNames) / sizeof(wifiStartNames[0]); + +#define WIFI_START_ESP_NOW (WIFI_EN_SET_MODE \ + | WIFI_EN_SET_PROTOCOLS \ + | WIFI_EN_SELECT_CHANNEL \ + | WIFI_EN_SET_CHANNEL \ + | WIFI_EN_PROMISCUOUS_RX_CALLBACK \ + | WIFI_EN_SET_PROMISCUOUS_MODE \ + | WIFI_EN_START_ESP_NOW \ + | WIFI_EN_ESP_NOW_ONLINE) + +#define WIFI_START_SOFT_AP (WIFI_AP_SET_MODE \ + | WIFI_AP_SET_PROTOCOLS \ + | WIFI_AP_SELECT_CHANNEL \ + | 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) + +#define WIFI_START_STATION (WIFI_STA_SET_MODE \ + | WIFI_STA_SET_PROTOCOLS \ + | WIFI_STA_START_SCAN \ + | WIFI_STA_SELECT_CHANNEL \ + | WIFI_STA_SELECT_REMOTE_AP \ + | 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 \ + | WIFI_STA_SELECT_CHANNEL \ + | WIFI_STA_SELECT_REMOTE_AP \ + | 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 \ + | WIFI_EN_SELECT_CHANNEL \ + | WIFI_STA_SELECT_CHANNEL) + +#define WIFI_STA_NO_REMOTE_AP (WIFI_STA_SELECT_CHANNEL \ + | 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 \ + | WIFI_STA_SELECT_REMOTE_AP \ + | WIFI_STA_NO_REMOTE_AP) + +#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; + +//**************************************** +// Globals +//**************************************** -#define WIFI_MAX_TIMEOUT (15 * 60 * 1000) -#define WIFI_MIN_TIMEOUT (15 * 1000) +bool restartWiFi = false; // Restart WiFi if user changes anything -//---------------------------------------- +//**************************************** // Locals -//---------------------------------------- - -static uint32_t wifiLastConnectionAttempt; +//**************************************** -// Throttle the time between connection attempts -// ms - Max of 4,294,967,295 or 4.3M seconds or 71,000 minutes or 1193 hours or 49 days between attempts -static uint32_t wifiConnectionAttemptsTotal; // Count the number of connection attempts absolutely -static uint32_t wifiConnectionAttemptTimeout; +// 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; -// DNS server for Captive Portal -static DNSServer dnsServer; +// WiFi interface status +static bool wifiApRunning; +static bool wifiStationRunning; -static bool wifiRunning; - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -// WiFi Routines -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +static int wifiFailedConnectionAttempts = 0; // Count the number of connection attempts between restarts +static WiFiMulti *wifiMulti; -//---------------------------------------- +//********************************************************************* // Set WiFi credentials // Enable TCP connections -//---------------------------------------- void menuWiFi() { while (1) @@ -97,15 +477,16 @@ void menuWiFi() systemPrintf("Enter SSID network %d: ", arraySlot + 1); getUserInputString(settings.wifiNetworks[arraySlot].ssid, sizeof(settings.wifiNetworks[arraySlot].ssid)); - restartWiFi = true; // If we are modifying the SSID table, force restart of WiFi } else { systemPrintf("Enter Password for %s: ", settings.wifiNetworks[arraySlot].ssid); getUserInputString(settings.wifiNetworks[arraySlot].password, sizeof(settings.wifiNetworks[arraySlot].password)); - restartWiFi = true; // If we are modifying the SSID table, force restart of WiFi } + + // If we are modifying the SSID table, force restart of WiFi + restartWiFi = true; } else if (incoming == 'a') { @@ -136,122 +517,14 @@ void menuWiFi() clearBuffer(); // Empty buffer of any newline chars } -//---------------------------------------- -// Connect to a remote WiFi access point -//---------------------------------------- -bool wifiConnect(unsigned long timeout) -{ - return wifiConnect(timeout, false, nullptr); -} - -//---------------------------------------- -// Attempts a connection to all provided SSIDs -// Returns true if successful -// Gives up if no SSID detected or connection times out -// If useAPSTAMode is true, do an extra check and go from WIFI_AP mode to WIFI_AP_STA mode -//---------------------------------------- -bool wifiConnect(unsigned long timeout, bool useAPSTAMode, bool *wasInAPmode) -{ - // If WiFi is already connected and AP_STA mode is not needed, then return true now - if (wifiIsRunning() && !useAPSTAMode) - { - return (true); // Nothing to do - } - - displayWiFiConnect(); - - // If otaUpdate wants to use WIFI_AP_STA mode - if (useAPSTAMode && (wasInAPmode != nullptr)) - { - *wasInAPmode = (WiFi.getMode() == WIFI_AP); - - if (*wasInAPmode) - { - systemPrintln("wifiConnect: changing from WIFI_AP to WIFI_AP_STA"); - WiFi.mode(WIFI_AP_STA); // Change mode from WIFI_AP to WIFI_AP_STA - } - else - { - systemPrintln("wifiConnect: was not in WIFI_AP mode. Going to WIFI_STA"); - WiFi.mode(WIFI_STA); // Must have been off - or already in STA mode? - } - } - else - { - // 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("wifiConnect: Failed to get protocols: %s\r\n", esp_err_to_name(response)); - - // If ESP-NOW is running, blend in ESP-NOW protocol. - if (espnowState > ESPNOW_OFF) - { - if (protocols != (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR)) - { - esp_err_t response = - esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | - WIFI_PROTOCOL_LR); // Enable WiFi + ESP-Now. - if (response != ESP_OK) - systemPrintf("wifiConnect: 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(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | - WIFI_PROTOCOL_11N); // Enable WiFi. - if (response != ESP_OK) - systemPrintf("wifiConnect: Error setting WiFi protocols: %s\r\n", esp_err_to_name(response)); - } - } - - systemPrintln("Connecting 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) - { - wifiRunning = 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)); - } - wifiRunning = false; - return false; -} - -//---------------------------------------- +//********************************************************************* // Display the WiFi state -//---------------------------------------- void wifiDisplayState() { - systemPrintf("WiFi: %s\r\n", networkIsInterfaceOnline(NETWORK_WIFI) ? "Online" : "Offline"); + systemPrintf("WiFi: %s\r\n", networkInterfaceHasInternet(NETWORK_WIFI) ? "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 (networkIsInterfaceOnline(NETWORK_WIFI)) + if (networkInterfaceHasInternet(NETWORK_WIFI)) { // Get the DNS addresses IPAddress dns1 = WiFi.STA.dnsIP(0); @@ -276,23 +549,30 @@ void wifiDisplayState() else systemPrintf(" DNS Address: %s\r\n", dns1.toString().c_str()); systemPrintf(" WiFi Strength: %d dBm\r\n", WiFi.RSSI()); - systemPrintf(" WiFi Status: %d\r\n", wifiStatusString); + systemPrintf(" WiFi Status: %d (%s)\r\n", wifiStatus, wifiStatusString); } } -//---------------------------------------- +//********************************************************************* // Process the WiFi events -//---------------------------------------- void wifiEvent(arduino_event_id_t event, arduino_event_info_t info) { char ssid[sizeof(info.wifi_sta_connected.ssid) + 1]; IPAddress ipAddress; - // Take the network offline if necessary - if (networkIsInterfaceOnline(NETWORK_WIFI) && (event != ARDUINO_EVENT_WIFI_STA_GOT_IP) && - (event != ARDUINO_EVENT_WIFI_STA_GOT_IP6)) + // 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) { - networkStop(NETWORK_WIFI, settings.debugNetworkLayer); // Stop WiFi to allow it to restart + // 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 @@ -316,27 +596,35 @@ void wifiEvent(arduino_event_id_t event, arduino_event_info_t info) break; case ARDUINO_EVENT_WIFI_READY: - systemPrintln("WiFi Ready"); + if (settings.debugWifiState) + systemPrintln("WiFi Ready"); WiFi.setHostname(settings.mdnsHostName); break; case ARDUINO_EVENT_WIFI_SCAN_DONE: - systemPrintln("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: - systemPrintln("WiFi STA Started"); + if (settings.debugWifiState) + systemPrintln("WiFi STA Started"); break; case ARDUINO_EVENT_WIFI_STA_STOP: - systemPrintln("WiFi STA Stopped"); + 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; - systemPrintf("WiFi STA connected to %s\r\n", ssid); + + ipAddress = WiFi.localIP(); + systemPrintf("WiFi STA connected to %s with IP address: ", ssid); + systemPrintln(ipAddress); + WiFi.setHostname(settings.mdnsHostName); break; @@ -353,16 +641,23 @@ void wifiEvent(arduino_event_id_t event, arduino_event_info_t info) break; case ARDUINO_EVENT_WIFI_STA_GOT_IP: - ipAddress = WiFi.localIP(); - systemPrint("WiFi STA Got IPv4: "); - systemPrintln(ipAddress); - networkMarkOnline(NETWORK_WIFI); + if (settings.debugWifiState) + { + ipAddress = WiFi.localIP(); + systemPrint("WiFi STA Got IPv4: "); + systemPrintln(ipAddress); + } + networkInterfaceEventInternetAvailable(NETWORK_WIFI); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - systemPrint("WiFi STA Got IPv6: "); - systemPrintln(ipAddress); - networkMarkOnline(NETWORK_WIFI); + if (settings.debugWifiState) + { + ipAddress = WiFi.localIP(); + systemPrint("WiFi STA Got IPv6: "); + systemPrintln(ipAddress); + } + networkInterfaceEventInternetAvailable(NETWORK_WIFI); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: @@ -371,28 +666,8 @@ void wifiEvent(arduino_event_id_t event, arduino_event_info_t info) } } -//---------------------------------------- -// Determine if WIFI is connected -//---------------------------------------- -bool wifiIsConnected() -{ - bool connected; - - connected = (WiFi.status() == WL_CONNECTED); - return connected; -} - -//---------------------------------------- -// Determine if WIFI is running -//---------------------------------------- -bool wifiIsRunning() -{ - return wifiRunning; -} - -//---------------------------------------- +//********************************************************************* // Counts the number of entered SSIDs -//---------------------------------------- int wifiNetworkCount() { // Count SSIDs @@ -405,267 +680,2487 @@ int wifiNetworkCount() return networkCount; } -//---------------------------------------- +//********************************************************************* // Given a status, return the associated state or error -//---------------------------------------- const char *wifiPrintState(wl_status_t wifiStatus) { switch (wifiStatus) { - case WL_NO_SHIELD: - return ("WL_NO_SHIELD"); // 255 - case WL_STOPPED: + case WL_NO_SHIELD: // 255 + return ("WL_NO_SHIELD"); + case WL_STOPPED: // 254 return ("WL_STOPPED"); - case WL_IDLE_STATUS: // 0 + case WL_IDLE_STATUS: // 0 return ("WL_IDLE_STATUS"); - case WL_NO_SSID_AVAIL: // 1 + case WL_NO_SSID_AVAIL: // 1 return ("WL_NO_SSID_AVAIL"); - case WL_SCAN_COMPLETED: // 2 + case WL_SCAN_COMPLETED: // 2 return ("WL_SCAN_COMPLETED"); - case WL_CONNECTED: // 3 + case WL_CONNECTED: // 3 return ("WL_CONNECTED"); - case WL_CONNECT_FAILED: // 4 + case WL_CONNECT_FAILED: // 4 return ("WL_CONNECT_FAILED"); case WL_CONNECTION_LOST: // 5 return ("WL_CONNECTION_LOST"); - case WL_DISCONNECTED: // 6 + case WL_DISCONNECTED: // 6 return ("WL_DISCONNECTED"); } return ("WiFi Status Unknown"); } -//---------------------------------------- -// Restart WiFi -//---------------------------------------- -void wifiRestart() +//********************************************************************* +// Callback for all WiFi RX Packets +// Get RSSI of all incoming management packets: https://esp32.com/viewtopic.php?t=13889 +void wifiPromiscuousRxHandler(void *buf, wifi_promiscuous_pkt_type_t type) { - // Restart the AP webserver if we are in that state - if (systemState == STATE_WIFI_CONFIG) - requestChangeState(STATE_WIFI_CONFIG_NOT_STARTED); - else - { - // Restart WiFi if we are not in AP config mode - WIFI_STOP(); + const wifi_promiscuous_pkt_t *ppkt; // Defined in esp_wifi_types_native.h - if (networkConsumers() == 0) - { - // Don't restart WiFi if there is no need for it - return; - } + // 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; - wifiForceStart(); - } + ppkt = (wifi_promiscuous_pkt_t *)buf; + packetRSSI = ppkt->rx_ctrl.rssi; } -//---------------------------------------- +//********************************************************************* +// Set WiFi timeout back to zero +// Useful if other things (such as a successful ethernet connection) need +// to reset wifi timeout +void wifiResetTimeout() +{ + wifiStartTimeout = 0; + if (settings.debugWifiState == true) + systemPrintln("WiFi: Start timeout reset to zero"); +} + +//********************************************************************* // 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 wifiForceStart() +bool wifiStart() { int wifiStatus; - if (wifiNetworkCount() == 0) + // Determine which parts of WiFi need to be started + bool startWiFiStation = false; + bool startWiFiAp = false; + + uint16_t consumerTypes = networkGetConsumerTypes(); + + // The consumers need station + if (consumerTypes & (1 << NETIF_WIFI_STA)) + startWiFiStation = true; + + // The consumers need AP + if (consumerTypes & (1 << NETIF_WIFI_AP)) + startWiFiAp = true; + + if (startWiFiStation == false && startWiFiAp == false) { - systemPrintln("Error: Please enter at least one SSID before using WiFi"); - displayNoSSIDs(2000); + systemPrintln("wifiStart() requested without any NETCONSUMER combination"); WIFI_STOP(); - return false; + return (false); } // Determine if WiFi is already running - if (!wifiRunning) + if (startWiFiStation == wifiStationRunning && startWiFiAp == wifiApRunning) { if (settings.debugWifiState == true) - systemPrintln("Starting WiFi"); + systemPrintln("WiFi is already running with requested setup"); + return (true); + } - // Start WiFi - if (wifiConnect(settings.wifiConnectTimeoutMs)) + // Handle special cases if no networks have been entered + if (wifiNetworkCount() == 0) + { + if (startWiFiStation == true && startWiFiAp == false) { - wifiResetTimeout(); - if (settings.debugWifiState == true) - systemPrintln("WiFi: Start timeout reset to zero"); + 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; } } + + // Start WiFi + wifiConnect(startWiFiStation, startWiFiAp, settings.wifiConnectTimeoutMs); + + // 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(); + + // If we are in STA or AP+STA mode, return if the station connected successfully wifiStatus = WiFi.status(); return (wifiStatus == WL_CONNECTED); } -//---------------------------------------- -// Set WiFi timeout back to zero -// Useful if other things (such as a successful ethernet connection) need to reset wifi timeout -//---------------------------------------- -void wifiResetTimeout() +//********************************************************************* +// Stop WiFi and release all resources +void wifiStop() { - wifiStartTimeout = 0; -} + // Stop the web server + stopWebServer(); -//---------------------------------------- -// Start WiFi with throttling -//---------------------------------------- -void wifiStart(NetIndex_t index, uintptr_t parameter, bool debug) -{ - static uint32_t wifiStartLastTry; - int seconds; - int minutes; + // 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(); - // Restart delay - if ((millis() - wifiStartLastTry) < wifiStartTimeout) - return; - wifiStartLastTry = millis(); + wifiFailedConnectionAttempts = 0; // Reset the counter - // Start WiFi - if (wifiForceStart()) - networkSequenceNextEntry(NETWORK_WIFI, settings.debugNetworkLayer); - else + // If ESP-Now is active, change protocol to only Long Range + if (espnowGetState() > ESPNOW_OFF) { - // Increase the timeout - wifiStartTimeout <<= 1; - if (!wifiStartTimeout) - wifiStartTimeout = WIFI_MIN_TIMEOUT; - else if (wifiStartTimeout > WIFI_MAX_TIMEOUT) - wifiStartTimeout = WIFI_MAX_TIMEOUT; + if (WiFi.getMode() != WIFI_STA) + WiFi.mode(WIFI_STA); - // 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); + // 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"); + } } -} - -//---------------------------------------- -// WiFi start sequence -//---------------------------------------- -NETWORK_POLL_SEQUENCE wifiStartSequence[] = { - // State Parameter Description - {wifiStart, 0, "Initialize WiFi"}, - {nullptr, 0, "Termination"}, -}; + else + { + WiFi.mode(WIFI_OFF); + if (settings.debugWifiState == true) + systemPrintln("WiFi Stopped"); + } + + // Take the network offline + networkInterfaceEventInternetLost(NETWORK_WIFI); + + if (wifiMulti != nullptr) + wifiMulti = nullptr; + + // Display the heap state + reportHeapNow(settings.debugWifiState); + wifiStationRunning = false; + wifiApRunning = false; +} + +//********************************************************************* +// Needed for wifiStopSequence +void wifiStop(NetIndex_t index, uintptr_t parameter, bool debug) +{ + wifiStop(); + networkSequenceNextEntry(NETWORK_WIFI, settings.debugNetworkLayer); +} + +//********************************************************************* +// Constructor +// Inputs: +// verbose: Set to true to display additional WiFi debug data +RTK_WIFI::RTK_WIFI(bool verbose) + : _apChannel{0}, _apCount{0}, _apDnsAddress{IPAddress((uint32_t)0)}, + _apFirstDhcpAddress{IPAddress("192.168.4.32")}, + _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}, + _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} +{ +} + +//********************************************************************* +// Attempts a connection to all provided SSIDs +bool RTK_WIFI::connect(unsigned long timeout, + bool startAP) +{ + bool started; + + // Display warning + log_w("WiFi: Not using timeout parameter for connect!\r\n"); + + // Enable WiFi station if necessary + if (_stationRunning == false) + { + displayWiFiConnect(); + started = enable(_espNowRunning, _softApRunning, true); + } + else if (startAP && !_softApRunning) + started = enable(_espNowRunning, true, _stationRunning); + + // Determine the WiFi station status + if (started) + { + wl_status_t wifiStatus = WiFi.STA.status(); + started = (wifiStatus == WL_CONNECTED); + if (wifiStatus == WL_DISCONNECTED) + systemPrint("No friendly WiFi networks detected.\r\n"); + else if (wifiStatus != WL_CONNECTED) + systemPrintf("WiFi failed to connect: error #%d - %s\r\n", + wifiStatus, wifiPrintState(wifiStatus)); + } + return started; +} + +//********************************************************************* +// Display components begin started or stopped +// Inputs: +// text: Text describing the component list +// components: A bit mask of the components +void RTK_WIFI::displayComponents(const char * text, WIFI_ACTION_t components) +{ + WIFI_ACTION_t mask; + + systemPrintf("%s: 0x%08x\r\n", text, components); + for (int index = wifiStartNamesEntries - 1; index >= 0; index--) + { + mask = 1 << index; + if (components & mask) + systemPrintf(" 0x%08lx: %s\r\n", mask, wifiStartNames[index]); + } +} + +//********************************************************************* +// Enable or disable the WiFi modes +// Inputs: +// enableESPNow: Enable ESP-NOW mode +// enableSoftAP: Enable soft AP mode +// enableStataion: Enable station mode +// Outputs: +// Returns true if the modes were successfully configured +bool RTK_WIFI::enable(bool enableESPNow, bool enableSoftAP, bool enableStation) +{ + int authIndex; + WIFI_ACTION_t starting; + WIFI_ACTION_t stopping; + + // Determine the next actions + starting = 0; + stopping = 0; + + // Display the parameters + if (settings.debugWifiState && _verbose) + { + systemPrintf("enableESPNow: %s\r\n", enableESPNow ? "true" : "false"); + systemPrintf("enableSoftAP: %s\r\n", enableSoftAP ? "true" : "false"); + systemPrintf("enableStation: %s\r\n", enableStation ? "true" : "false"); + } + + // Update the ESP-NOW state + if (enableESPNow) + { + starting |= WIFI_START_ESP_NOW; + _espNowRunning = true; + } + else + { + stopping |= WIFI_START_ESP_NOW; + _espNowRunning = false; + } + + // Update the soft AP state + if (enableSoftAP) + { + // Verify that the SSID is set + if (wifiSoftApSsid && strlen(wifiSoftApSsid) && wifiSoftApPassword) + { + starting |= WIFI_START_SOFT_AP; + _softApRunning = true; + } + else + systemPrintf("ERROR: AP SSID or password is missing\r\n"); + } + else + { + stopping |= WIFI_START_SOFT_AP; + _softApRunning = false; + } + + // Update the station state + if (enableStation) + { + // Verify that at least one WiFi access point is in the list + if (MAX_WIFI_NETWORKS == 0) + { + systemPrintf("ERROR: No entries in wiFiSsidPassword\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; + } + } + } + else + { + // Stop the WiFi station + stopping |= WIFI_START_STATION; + _stationRunning = false; + } + + // Stop and start the WiFi components + return stopStart(stopping, starting); +} + +//********************************************************************* +// Get the ESP-NOW status +// Outputs: +// Returns true when ESP-NOW is online and ready for use +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: +// channel: New ESP-NOW channel number +void RTK_WIFI::espNowSetChannel(WIFI_CHANNEL_t channel) +{ + _espNowChannel = channel; +} + +//********************************************************************* +// Handle the WiFi event +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]); + + // Handle the event + switch (event) + { + + //------------------------------ + // Controller events + //------------------------------ + + case ARDUINO_EVENT_WIFI_OFF: + case ARDUINO_EVENT_WIFI_READY: + break; + + //---------------------------------------- + // Scan events + //---------------------------------------- + + case ARDUINO_EVENT_WIFI_SCAN_DONE: + stationEventHandler(event, info); + break; + + //------------------------------ + // Station events + //------------------------------ + case ARDUINO_EVENT_WIFI_STA_START: + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + case ARDUINO_EVENT_WIFI_STA_STOP: + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + stationEventHandler(event, info); + break; + + //---------------------------------------- + // Soft AP events + //---------------------------------------- + + case ARDUINO_EVENT_WIFI_AP_START: + case ARDUINO_EVENT_WIFI_AP_STOP: + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + softApEventHandler(event, info); + break; + } +} + +//********************************************************************* +// Get the current WiFi channel +// Outputs: +// 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; +} + +//********************************************************************* +// Set the WiFi mode +// Inputs: +// setMode: Modes to set +// xorMode: Modes to toggle +// +// Math: result = (mode | setMode) ^ xorMode +// +// setMode +// 0 1 +// xorMode 0 No change Set bit +// 1 Toggle bit Clear bit +// +// Outputs: +// Returns true if successful and false upon failure +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 + { + // Get the current mode + mode = (uint8_t)WiFi.getMode(); + if (settings.debugWifiState && _verbose) + 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")))); + + // Determine the new mode + newMode = (mode | setMode) ^ xorMode; + started = (newMode == mode); + if (started) + break; + + // Set the new mode + started = WiFi.mode((wifi_mode_t)newMode); + if (!started) + { + systemPrintf("ERROR: Failed to set %d (%s), status: %d!\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); + break; + } + if (settings.debugWifiState && _verbose) + systemPrintf("Set WiFi: %d (%s)\r\n", + newMode, + ((newMode == 0) ? "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")))); + } while (0); + + // Return the final status + return started; +} + +//********************************************************************* +// Set the WiFi radio protocols +// Inputs: +// interface: Interface on which to set the protocols +// enableWiFiProtocols: When true, enable the WiFi protocols +// enableLongRangeProtocol: When true, enable the long range protocol +// Outputs: +// Returns true if successful and false upon failure +bool RTK_WIFI::setWiFiProtocols(wifi_interface_t interface, + bool enableWiFiProtocols, + bool enableLongRangeProtocol) +{ + uint8_t newProtocols; + uint8_t oldProtocols; + bool started; + esp_err_t status; + + started = false; + do + { + // Get the current protocols + status = esp_wifi_get_protocol(interface, &oldProtocols); + started = (status == ESP_OK); + if (!started) + { + systemPrintf("ERROR: Failed to get the WiFi %s radio protocols!\r\n", + (interface == WIFI_IF_AP) ? "soft AP" : "station"); + break; + } + if (settings.debugWifiState && _verbose) + systemPrintf("Current WiFi protocols (%d%s%s%s%s%s)\r\n", + oldProtocols, + oldProtocols & WIFI_PROTOCOL_11AX ? ", 11AX" : "", + oldProtocols & WIFI_PROTOCOL_11B ? ", 11B" : "", + oldProtocols & WIFI_PROTOCOL_11G ? ", 11G" : "", + oldProtocols & WIFI_PROTOCOL_11N ? ", 11N" : "", + oldProtocols & WIFI_PROTOCOL_LR ? ", LR" : ""); + + // Determine which protocols to enable + newProtocols = oldProtocols; + if (enableLongRangeProtocol || enableWiFiProtocols) + { + // Enable the WiFi protocols + newProtocols |= WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N; + + // Enable the ESP-NOW long range protocol + if (enableLongRangeProtocol) + newProtocols |= WIFI_PROTOCOL_LR; + else + newProtocols &= ~WIFI_PROTOCOL_LR; + } + + // Disable the protocols + else + newProtocols = 0; + + // Display the new protocols + if (settings.debugWifiState && _verbose) + systemPrintf("Setting WiFi protocols (%d%s%s%s%s%s)\r\n", + newProtocols, + newProtocols & WIFI_PROTOCOL_11AX ? ", 11AX" : "", + newProtocols & WIFI_PROTOCOL_11B ? ", 11B" : "", + newProtocols & WIFI_PROTOCOL_11G ? ", 11G" : "", + newProtocols & WIFI_PROTOCOL_11N ? ", 11N" : "", + newProtocols & WIFI_PROTOCOL_LR ? ", LR" : ""); + + // Set the new protocols + started = true; + if (newProtocols != oldProtocols) + { + status = esp_wifi_set_protocol(interface, newProtocols); + started = (status == ESP_OK); + } + if (!started) + { + systemPrintf("Current WiFi protocols (%d%s%s%s%s%s)\r\n", + oldProtocols, + oldProtocols & WIFI_PROTOCOL_11AX ? ", 11AX" : "", + oldProtocols & WIFI_PROTOCOL_11B ? ", 11B" : "", + oldProtocols & WIFI_PROTOCOL_11G ? ", 11G" : "", + oldProtocols & WIFI_PROTOCOL_11N ? ", 11N" : "", + oldProtocols & WIFI_PROTOCOL_LR ? ", LR" : ""); + systemPrintf("Setting WiFi protocols (%d%s%s%s%s%s)\r\n", + newProtocols, + newProtocols & WIFI_PROTOCOL_11AX ? ", 11AX" : "", + newProtocols & WIFI_PROTOCOL_11B ? ", 11B" : "", + newProtocols & WIFI_PROTOCOL_11G ? ", 11G" : "", + newProtocols & WIFI_PROTOCOL_11N ? ", 11N" : "", + newProtocols & WIFI_PROTOCOL_LR ? ", LR" : ""); + systemPrintf("ERROR: Failed to set the WiFi %s radio protocols!\r\n", + (interface == WIFI_IF_AP) ? "soft AP" : "station"); + break; + } + } while (0); + + // Return the final status + return started; +} + +//********************************************************************* +// Configure the soft AP +// Inputs: +// ipAddress: IP address of the soft AP +// subnetMask: Subnet mask for the soft AP network +// firstDhcpAddress: First IP address to use in the DHCP range +// dnsAddress: IP address to use for DNS lookup (translate name to IP address) +// gatewayAddress: IP address of the gateway to a larger network (internet?) +// Outputs: +// Returns true if the soft AP was successfully configured. +bool RTK_WIFI::softApConfiguration(IPAddress ipAddress, + IPAddress subnetMask, + IPAddress firstDhcpAddress, + IPAddress dnsAddress, + IPAddress gatewayAddress) +{ + bool success; + + _apIpAddress = ipAddress; + _apSubnetMask = subnetMask; + _apFirstDhcpAddress = firstDhcpAddress; + _apDnsAddress = dnsAddress; + _apGatewayAddress = gatewayAddress; + + // Restart the soft AP if necessary + success = true; + if (softApOnline()) + { + success = enable(false, false, stationRunning()); + if (success) + success = enable(false, true, stationRunning()); + } + return success; +} + +//********************************************************************* +// Display the soft AP configuration +// Inputs: +// display: Address of a Print object +void RTK_WIFI::softApConfigurationDisplay(Print * display) +{ + display->printf("Soft AP configuration:\r\n"); + display->printf(" %s: IP Address\r\n", _apIpAddress.toString().c_str()); + display->printf(" %s: Subnet mask\r\n", _apSubnetMask.toString().c_str()); + if ((uint32_t)_apFirstDhcpAddress) + display->printf(" %s: First DHCP address\r\n", _apFirstDhcpAddress.toString().c_str()); + if ((uint32_t)_apDnsAddress) + display->printf(" %s: DNS address\r\n", _apDnsAddress.toString().c_str()); + if ((uint32_t)_apGatewayAddress) + display->printf(" %s: Gateway address\r\n", _apGatewayAddress.toString().c_str()); +} + +//********************************************************************* +// Handle the soft AP events +void RTK_WIFI::softApEventHandler(arduino_event_id_t event, arduino_event_info_t info) +{ + // Handle the event + switch (event) + { + case ARDUINO_EVENT_WIFI_AP_STOP: + // Mark the soft AP as offline + if (settings.debugWifiState && softApOnline()) + systemPrintf("AP: Offline\r\n"); + _started = _started & ~WIFI_AP_ONLINE; + if (settings.debugWifiState && _verbose) + systemPrintf("_started: 0x%08x\r\n", _started); + break; + } +} + +//********************************************************************* +// Set the soft AP host name +// Inputs: +// hostName: Zero terminated host name character string +// Outputs: +// Returns true if successful and false upon failure +bool RTK_WIFI::softApSetHostName(const char * hostName) +{ + bool nameSet; + + do + { + // Verify that a host name was specified + nameSet = (hostName != nullptr) && (strlen(hostName) != 0); + if (!nameSet) + { + systemPrintf("ERROR: No host name specified!\r\n"); + break; + } + + // Set the host name + if (settings.debugWifiState) + systemPrintf("WiFI setting AP host name\r\n"); + nameSet = WiFi.AP.setHostname(hostName); + if (!nameSet) + { + systemPrintf("ERROR: Failed to set the Wifi AP host name!\r\n"); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi AP hostname: %s\r\n", hostName); + } while (0); + return nameSet; +} + +//********************************************************************* +// Set the soft AP configuration +// Inputs: +// ipAddress: IP address of the server, nullptr or empty string causes +// default 192.168.4.1 to be used +// subnetMask: Subnet mask for local network segment, nullptr or empty +// string causes default 0.0.0.0 to be used, unless ipAddress +// is not specified, in which case 255.255.255.0 is used +// gatewayAddress: Gateway to internet IP address, nullptr or empty string +// causes default 0.0.0.0 to be used (no access to internet) +// dnsAddress: Domain name server (name to IP address translation) IP address, +// nullptr or empty string causes 0.0.0.0 to be used (only +// mDNS name translation, if started) +// dhcpStartAddress: Start of DHCP IP address assignments for the local +// network segment, nullptr or empty string causes default +// 0.0.0.0 to be used (disable DHCP server) unless ipAddress +// was not specified in which case 192.168.4.2 +// Outputs: +// Returns true if successful and false upon failure +bool RTK_WIFI::softApSetIpAddress(const char * ipAddress, + const char * subnetMask, + const char * gatewayAddress, + const char * dnsAddress, + const char * dhcpFirstAddress) +{ + bool configured; + uint32_t uDhcpFirstAddress; + uint32_t uDnsAddress; + uint32_t uGatewayAddress; + uint32_t uIpAddress; + uint32_t uSubnetMask; + + // Convert the IP address + if ((!ipAddress) || (strlen(ipAddress) == 0)) + uIpAddress = 0; + else + uIpAddress = (uint32_t)IPAddress(ipAddress); + + // Convert the subnet mask + if ((!subnetMask) || (strlen(subnetMask) == 0)) + { + if (uIpAddress == 0) + uSubnetMask = IPAddress("255.255.255.0"); + else + uSubnetMask = 0; + } + else + uSubnetMask = (uint32_t)IPAddress(subnetMask); + + // Convert the gateway address + if ((!gatewayAddress) || (strlen(gatewayAddress) == 0)) + uGatewayAddress = 0; + else + uGatewayAddress = (uint32_t)IPAddress(gatewayAddress); + + // Convert the first DHCP address + if ((!dhcpFirstAddress) || (strlen(dhcpFirstAddress) == 0)) + { + if (uIpAddress == 0) + uDhcpFirstAddress = IPAddress("192.168.4.32"); + else + uDhcpFirstAddress = uIpAddress + 0x1f000000; + } + else + uDhcpFirstAddress = (uint32_t)IPAddress(dhcpFirstAddress); + + // Convert the DNS address + if ((!dnsAddress) || (strlen(dnsAddress) == 0)) + uDnsAddress = 0; + else + uDnsAddress = (uint32_t)IPAddress(dnsAddress); + + // Use the default IP address if not specified + if (uIpAddress == 0) + uIpAddress = IPAddress("192.168.4.1"); + + // Display the soft AP configuration + if (settings.debugWifiState) + softApConfigurationDisplay(&Serial); + + // Configure the soft AP + configured = WiFi.AP.config(IPAddress(uIpAddress), + IPAddress(uGatewayAddress), + IPAddress(uSubnetMask), + IPAddress(uDhcpFirstAddress), + IPAddress(uDnsAddress)); + if (!configured) + systemPrintf("ERROR: Failed to configure the soft AP with IP addresses!\r\n"); + 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: +// Returns true if successful and false upon failure +bool RTK_WIFI::softApSetSsidPassword(const char * ssid, const char * password) +{ + bool created; + + // Set the WiFi soft AP SSID and password + if (settings.debugWifiState) + systemPrintf("WiFi AP: Attempting to set AP SSID and password\r\n"); + created = WiFi.AP.create(ssid, 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); + return created; +} + +//********************************************************************* +// Attempt to start the soft AP mode +// Inputs: +// forceAP: Set to true to force AP to start, false will only start +// soft AP if settings.wifiConfigOverAP is true +// Outputs: +// Returns true if the soft AP was started successfully and false +// otherwise +bool RTK_WIFI::startAp(bool forceAP) +{ + return enable(_espNowRunning, forceAP | settings.wifiConfigOverAP, _stationRunning); +} + +//********************************************************************* +// Connect the station to a remote AP +// Return true if the connection was successful and false upon failure. +bool RTK_WIFI::stationConnectAP() +{ + bool connected; + + do + { + // Connect to the remote AP + if (settings.debugWifiState) + systemPrintf("WiFi connecting to %s on channel %d with %s authorization\r\n", + _staRemoteApSsid, + _channel, + (_staAuthType < WIFI_AUTH_MAX) ? wifiAuthorizationName[_staAuthType] : "Unknown"); + connected = (WiFi.STA.connect(_staRemoteApSsid, _staRemoteApPassword, _channel)); + if (!connected) + { + if (settings.debugWifiState) + systemPrintf("WIFI failed to connect to SSID %s with password %s\r\n", + _staRemoteApSsid, _staRemoteApPassword); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi station connected to %s on channel %d with %s authorization\r\n", + _staRemoteApSsid, + _channel, + (_staAuthType < WIFI_AUTH_MAX) ? wifiAuthorizationName[_staAuthType] : "Unknown"); + + // Don't delay the next WiFi start request + wifiResetTimeout(); + } while (0); + return connected; +} + +//********************************************************************* +// Disconnect the station from an AP +// Outputs: +// Returns true if successful and false upon failure +bool RTK_WIFI::stationDisconnect() +{ + bool disconnected; + + do + { + // Determine if station is connected to a remote AP + disconnected = !_staConnected; + if (disconnected) + { + if (settings.debugWifiState) + systemPrintf("Station already disconnected from remote AP\r\n"); + break; + } + + // Disconnect from the remote AP + if (settings.debugWifiState) + systemPrintf("WiFI disconnecting station from remote AP\r\n"); + disconnected = WiFi.STA.disconnect(); + if (!disconnected) + { + systemPrintf("ERROR: Failed to disconnect WiFi from the remote AP!\r\n"); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi disconnected from the remote AP\r\n"); + } while (0); + return disconnected; +} + +//********************************************************************* +// Handle the WiFi station events +void RTK_WIFI::stationEventHandler(arduino_event_id_t event, arduino_event_info_t info) +{ + WIFI_CHANNEL_t channel; + IPAddress ipAddress; + char ssid[sizeof(info.wifi_sta_connected.ssid) + 1]; + 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 + // 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 Station State Machine + // + // .--------+<----------+<-----------+<-------------+<----------+<----------+<------------. + // v | | | | | | | + // STOP --> READY --> STA_START --> SCAN_DONE --> CONNECTED --> GOT_IP --> LOST_IP --> DISCONNECTED + // ^ ^ ^ | | + // | | '-----------' | + // OFF -------' '-------------------------------------' + // + // Notify the upper layers that WiFi is no longer available + //------------------------------ + + switch (event) + { + case ARDUINO_EVENT_WIFI_STA_START: + WiFi.STA.macAddress((uint8_t *)_staMacAddress); + if (settings.debugWifiState) + systemPrintf("WiFi Event: Station start: MAC: %02x:%02x:%02x:%02x:%02x:%02x\r\n", + _staMacAddress[0], _staMacAddress[1], _staMacAddress[2], + _staMacAddress[3], _staMacAddress[4], _staMacAddress[5]); + + // Fall through + // | + // | + // V + + case ARDUINO_EVENT_WIFI_STA_STOP: + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + // 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; + } + + // Fall through + // | + // | + // V + + case ARDUINO_EVENT_WIFI_SCAN_DONE: + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + // The WiFi station is no longer connected to the remote AP + if (settings.debugWifiState && _staConnected) + systemPrintf("WiFi: Station disconnected from %s\r\n", + _staRemoteApSsid); + _staConnected = false; + + // Fall through + // | + // | + // V + + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + // Mark the WiFi station offline + if (_started & WIFI_STA_ONLINE) + systemPrintf("WiFi: Station offline!\r\n"); + _started = _started & ~WIFI_STA_ONLINE; + + // Notify user of loss of IP address + if (settings.debugWifiState && _staHasIp) + 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; + + //------------------------------ + // Bring the WiFi station back online + //------------------------------ + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + _staConnected = true; + if (settings.debugWifiState) + { + memcpy(ssid, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); + ssid[info.wifi_sta_connected.ssid_len] = 0; + systemPrintf("WiFi: STA connected to %s\r\n", ssid); + } + break; + + break; + + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + _staIpAddress = WiFi.STA.localIP(); + type = (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) ? '4' : '6'; + + // Display the IP address + if (settings.debugWifiState) + systemPrintf("WiFi: Got IPv%c address %s\r\n", + type, _staIpAddress.toString().c_str()); + networkInterfaceEventInternetAvailable(NETWORK_WIFI); + break; + } // End of switch +} + +//********************************************************************* +// Set the station's host name +// Inputs: +// hostName: Zero terminated host name character string +// Outputs: +// Returns true if successful and false upon failure +bool RTK_WIFI::stationHostName(const char * hostName) +{ + bool nameSet; + + do + { + // Verify that a host name was specified + nameSet = (hostName != nullptr) && (strlen(hostName) != 0); + if (!nameSet) + { + systemPrintf("ERROR: No host name specified!\r\n"); + break; + } + + // Set the host name + if (settings.debugWifiState) + systemPrintf("WiFI setting station host name\r\n"); + nameSet = WiFi.STA.setHostname(hostName); + if (!nameSet) + { + systemPrintf("ERROR: Failed to set the Wifi station host name!\r\n"); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi station hostname: %s\r\n", hostName); + } while (0); + return nameSet; +} + +//********************************************************************* +// Get the station status +bool RTK_WIFI::stationOnline() +{ + return (_started & WIFI_STA_ONLINE) ? true : false; +} + +//********************************************************************* +// Handle WiFi station reconnection requests +void RTK_WIFI::stationReconnectionRequest() +{ + 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); + } + } + } +} + +//********************************************************************* +// Scan the WiFi network for remote APs +// Inputs: +// channel: Channel number for the scan, zero (0) scan all channels +// Outputs: +// Returns the number of access points +int16_t RTK_WIFI::stationScanForAPs(WIFI_CHANNEL_t channel) +{ + int16_t apCount; + int16_t status; + + do + { + // Determine if the WiFi scan is already running + if (_scanRunning) + { + if (settings.debugWifiState) + systemPrintf("WiFi scan already running"); + break; + } + + // Determine if scanning a single channel or all channels + if (settings.debugWifiState) + { + if (channel) + systemPrintf("WiFI starting scan for remote APs on channel %d\r\n", channel); + else + systemPrintf("WiFI starting scan for remote APs\r\n"); + } + + // Start the WiFi scan + apCount = WiFi.scanNetworks(false, // async + false, // show_hidden + false, // passive + 300, // max_ms_per_chan + channel, // channel number + nullptr, // ssid * + nullptr); // bssid * + if (settings.debugWifiState && _verbose) + Serial.printf("apCount: %d\r\n", apCount); + if (apCount < 0) + { + systemPrintf("ERROR: WiFi scan failed, status: %d!\r\n", apCount); + break; + } + _apCount = apCount; + if (settings.debugWifiState) + systemPrintf("WiFi scan complete, found %d remote APs\r\n", _apCount); + } while (0); + return apCount; +} + +//********************************************************************* +// Get the station status +bool RTK_WIFI::stationRunning() +{ + return _stationRunning; +} + +//********************************************************************* +// Select the AP and channel to use for WiFi station +// Inputs: +// apCount: Number to APs detected by the WiFi scan +// list: Determine if the APs should be listed +// Outputs: +// Returns the channel number of the AP +WIFI_CHANNEL_t RTK_WIFI::stationSelectAP(uint8_t apCount, bool list) +{ + int ap; + WIFI_CHANNEL_t apChannel; + bool apFound; + int authIndex; + WIFI_CHANNEL_t channel; + const char * ssid; + String ssidString; + int type; + + // Verify that an AP was found + if (apCount == 0) + return 0; + + // Print the header + // 1 1 2 3 + // 1234 1234 123456789012345 12345678901234567890123456789012 + if (settings.debugWifiState || list) + { + systemPrintf(" dBm Chan Authorization SSID\r\n"); + systemPrintf("---- ---- --------------- --------------------------------\r\n"); + } + + // Walk the list of APs that were found during the scan + apFound = false; + apChannel = 0; + for (ap = 0; ap < apCount; ap++) + { + // The APs are listed in decending signal strength order + // Check for a requested AP + ssidString = WiFi.SSID(ap); + ssid = ssidString.c_str(); + type = WiFi.encryptionType(ap); + channel = WiFi.channel(ap); + if (!apFound) + { + for (authIndex = 0; authIndex < MAX_WIFI_NETWORKS; authIndex++) + { + // Determine if this authorization matches the AP's SSID + if (strlen(settings.wifiNetworks[authIndex].ssid) + && (strcmp(ssid, settings.wifiNetworks[authIndex].ssid) == 0) + && ((type == WIFI_AUTH_OPEN) + || (strlen(settings.wifiNetworks[authIndex].password)))) + { + if (settings.debugWifiState) + systemPrintf("WiFi: Found remote AP: %s\r\n", ssid); + + // A match was found, save it and stop looking + _staRemoteApSsid = settings.wifiNetworks[authIndex].ssid; + _staRemoteApPassword = settings.wifiNetworks[authIndex].password; + apChannel = channel; + _staAuthType = type; + apFound = true; + break; + } + } + + // Check for done + if (apFound && (!(settings.debugWifiState | list))) + break; + } + + // Display the list of APs + if (settings.debugWifiState || list) + systemPrintf("%4d %4d %s %s\r\n", + WiFi.RSSI(ap), + channel, + (type < WIFI_AUTH_MAX) ? wifiAuthorizationName[type] : "Unknown", + ssid); + } + + // Return the channel number + return apChannel; +} + +//********************************************************************* +// 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 RTK_WIFI::stopStart(WIFI_ACTION_t stopping, WIFI_ACTION_t starting) +{ + int authIndex; + WIFI_CHANNEL_t channel; + bool defaultChannel; + WIFI_ACTION_t delta; + bool enabled; + WIFI_ACTION_t mask; + WIFI_ACTION_t notStarted; + uint8_t primaryChannel; + WIFI_ACTION_t restarting; + wifi_second_chan_t secondaryChannel; + WIFI_ACTION_t startingNow; + esp_err_t status; + WIFI_ACTION_t stillRunning; + + // Determine the next actions + notStarted = 0; + enabled = true; + + // Display the parameters + if (settings.debugWifiState && _verbose) + { + systemPrintf("stopping: 0x%08x\r\n", stopping); + systemPrintf("starting: 0x%08x\r\n", starting); + } + + //**************************************** + // Select the channel + // + // The priority order for the channel is: + // 1. Active channel (not using default channel) + // 2. _stationChannel + // 3. Remote AP channel determined by scan + // 4. _espNowChannel + // 5. _apChannel + // 6. Channel 1 + //**************************************** + + // 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) + { + // Continue to use the active channel + channel = _channel; + if (settings.debugWifiState && _verbose) + systemPrintf("channel: %d, active channel\r\n", channel); + } + + // Use the station channel if specified + else if (_stationChannel && (starting & WIFI_STA_ONLINE)) + { + channel = _stationChannel; + if (settings.debugWifiState && _verbose) + systemPrintf("channel: %d, WiFi station channel\r\n", channel); + } + + // Determine if a scan for remote APs is needed + else if (starting & WIFI_STA_START_SCAN) + { + channel = 0; + if (settings.debugWifiState && _verbose) + systemPrintf("channel: Determine by remote AP scan\r\n"); + + // Restart ESP-NOW if necessary + if (espNowRunning()) + stopping |= WIFI_START_ESP_NOW; + + // Restart soft AP if necessary + if (softApRunning()) + stopping |= WIFI_START_SOFT_AP; + } + + // Determine if the ESP-NOW channel was specified + else if (_espNowChannel & ((starting | _started) & WIFI_EN_ESP_NOW_ONLINE)) + { + channel = _espNowChannel; + if (settings.debugWifiState && _verbose) + systemPrintf("channel: %d, ESP-NOW channel\r\n", channel); + } + + // Determine if the AP channel was specified + else if (_apChannel && ((starting | _started) & WIFI_AP_ONLINE)) + { + channel = _apChannel; + if (settings.debugWifiState && _verbose) + systemPrintf("channel: %d, soft AP channel\r\n", channel); + } + + // No channel specified and scan not being done, use the default channel + else + { + channel = WIFI_DEFAULT_CHANNEL; + _usingDefaultChannel = true; + if (settings.debugWifiState && _verbose) + 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 + //**************************************** + + if (starting & WIFI_AP_START_DNS_SERVER) + { + // Only start the DNS server when the captive portal is enabled + if (!settings.enableCaptivePortal) + starting &= ~WIFI_AP_START_DNS_SERVER; + } + + //**************************************** + // Perform some optimizations + //**************************************** + + // Only stop the started components + stopping &= _started; + + // Determine which components are being restarted + restarting = _started & stopping & starting; + if (settings.debugWifiState && _verbose) + { + 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); + } + + // Don't start components that are already running and are not being + // stopped + starting &= ~(_started & ~stopping); + + // Display the starting and stopping + if (settings.debugWifiState) + { + bool startEspNow = starting & WIFI_EN_ESP_NOW_ONLINE; + bool startSoftAP = starting & WIFI_AP_ONLINE; + bool startStation = starting & WIFI_STA_ONLINE; + bool start = startEspNow || startSoftAP || startStation; + bool stopEspNow = stopping & WIFI_EN_ESP_NOW_ONLINE; + bool stopSoftAP = stopping & WIFI_AP_ONLINE; + bool stopStation = stopping & WIFI_STA_ONLINE; + bool stop = stopEspNow || stopSoftAP || stopStation; + + if (start || stop) + systemPrintf("WiFi:%s%s%s%s%s%s%s%s%s%s%s%s%s%s\r\n", + start ? " Starting (" : "", + startEspNow ? "ESP-NOW" : "", + (startEspNow && (startSoftAP || startStation)) ? ", " : "", + startSoftAP ? "Soft AP" : "", + (startSoftAP && startStation) ? ", " : "", + startStation ? "Station" : "", + start ? ")" : "", + stop ? " Stopping (" : "", + stopEspNow ? "ESP-NOW" : "", + (stopEspNow && (stopSoftAP || stopStation)) ? ", " : "", + stopSoftAP ? "Soft AP" : "", + (stopSoftAP && stopStation) ? ", " : "", + stopStation ? "Station" : "", + stop ? ")" : ""); + } + + //**************************************** + // Return the use of mDNS to soft AP when WiFi STA stops + //**************************************** + + // 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 + { + //**************************************** + // Display the items being stopped + //**************************************** + + if (settings.debugWifiState && _verbose && stopping) + displayComponents("Stopping", stopping); + + //**************************************** + // Stop the ESP-NOW components + //**************************************** + + // Mark the ESP-NOW offline + if (stopping & WIFI_EN_ESP_NOW_ONLINE) + { + if (settings.debugWifiState) + systemPrintf("WiFi: ESP-NOW offline!\r\n"); + _started = _started & ~WIFI_EN_ESP_NOW_ONLINE; + } + + // Stop the ESP-NOW layer + if (stopping & WIFI_EN_START_ESP_NOW) + { + if (settings.debugWifiState && _verbose) + systemPrintf("WiFi: Stopping ESP-NOW\r\n"); + if (!espNowStop()) + { + systemPrintf("ERROR: Failed to stop ESP-NOW!\r\n"); + break; + } + _started = _started & ~WIFI_EN_START_ESP_NOW; + if (settings.debugWifiState && _verbose) + systemPrintf("WiFi: ESP-NOW stopped\r\n"); + } + + // Stop the promiscuous RX callback handler + if (stopping & WIFI_EN_PROMISCUOUS_RX_CALLBACK) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling esp_wifi_set_promiscuous_rx_cb\r\n"); + status = esp_wifi_set_promiscuous_rx_cb(nullptr); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to stop WiFi promiscuous RX callback\r\n"); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi: Stopped WiFi promiscuous RX callback\r\n"); + _started = _started & ~WIFI_EN_PROMISCUOUS_RX_CALLBACK; + } + + // Stop promiscuous mode + if (stopping & WIFI_EN_SET_PROMISCUOUS_MODE) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling esp_wifi_set_promiscuous\r\n"); + status = esp_wifi_set_promiscuous(false); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to stop WiFi promiscuous mode, status: %d\r\n", status); + break; + } + if (settings.debugWifiState && _verbose) + systemPrintf("WiFi: Promiscuous mode stopped\r\n"); + _started = _started & ~WIFI_EN_SET_PROMISCUOUS_MODE; + } + + // Handle WiFi set channel + if (stopping & WIFI_EN_SET_CHANNEL) + _started = _started & ~WIFI_EN_SET_CHANNEL; + + // Stop the long range radio protocols + if (stopping & WIFI_EN_SET_PROTOCOLS) + { + if (!setWiFiProtocols(WIFI_IF_STA, true, false)) + break; + _started = _started & ~WIFI_EN_SET_PROTOCOLS; + } + + //**************************************** + // Stop the WiFi station components + //**************************************** + + // Mark the WiFi station offline + if (stopping & WIFI_STA_ONLINE) + { + if (_started & WIFI_STA_ONLINE) + systemPrintf("WiFi: Station offline!\r\n"); + _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) + { + if (!stationDisconnect()) + break; + _started = _started & ~WIFI_STA_CONNECT_TO_REMOTE_AP; + } + + // Handle auto reconnect + if (stopping & WIFI_STA_DISABLE_AUTO_RECONNECT) + _started = _started & ~WIFI_STA_DISABLE_AUTO_RECONNECT; + + // Handle WiFi station host name + if (stopping & WIFI_STA_SET_HOST_NAME) + _started = _started & ~WIFI_STA_SET_HOST_NAME; + + // Handle WiFi select channel + if (stopping & WIFI_SELECT_CHANNEL) + _started = _started & ~(stopping & WIFI_SELECT_CHANNEL); + + // Handle WiFi station select remote AP + if (stopping & WIFI_STA_SELECT_REMOTE_AP) + _started = _started & ~WIFI_STA_SELECT_REMOTE_AP; + + // Handle WiFi station scan + if (stopping & WIFI_STA_START_SCAN) + _started = _started & ~WIFI_STA_START_SCAN; + + // Stop the WiFi station radio protocols + if (stopping & WIFI_STA_SET_PROTOCOLS) + _started = _started & ~WIFI_STA_SET_PROTOCOLS; + + // Stop station mode + if (stopping & (WIFI_EN_SET_MODE | WIFI_STA_SET_MODE)) + { + // Determine which bits to clear + mask = ~(stopping & (WIFI_EN_SET_MODE | WIFI_STA_SET_MODE)); + + // Stop WiFi station if users are gone + if (!(_started & mask & (WIFI_EN_SET_MODE | WIFI_STA_SET_MODE))) + { + if (!setWiFiMode(WIFI_MODE_STA, WIFI_MODE_STA)) + break; + } + + // Remove this WiFi station user + _started = _started & mask; + } + + //**************************************** + // Stop the soft AP components + //**************************************** + + // Stop soft AP mode + // Mark the soft AP offline + if (stopping & WIFI_AP_ONLINE) + { + if (softApOnline()) + systemPrintf("WiFi: Soft AP offline!\r\n"); + _started = _started & ~WIFI_AP_ONLINE; + } + + // Stop the DNS server + if (stopping & _started & WIFI_AP_START_DNS_SERVER) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling dnsServer.stop for soft AP\r\n"); + dnsServer.stop(); + _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; + + // Stop soft AP mode + if (stopping & WIFI_AP_SET_MODE) + { + if (!setWiFiMode(WIFI_MODE_AP, WIFI_MODE_AP)) + break; + _started = _started & ~WIFI_AP_SET_MODE; + } + + // Disable the radio protocols for soft AP + if (stopping & WIFI_AP_SET_PROTOCOLS) + _started = _started & ~WIFI_AP_SET_PROTOCOLS; + + // Stop using the soft AP IP address + if (stopping & WIFI_AP_SET_IP_ADDR) + _started = _started & ~WIFI_AP_SET_IP_ADDR; + + // Stop use of SSID and password + if (stopping & WIFI_AP_SET_SSID_PASSWORD) + _started = _started & ~WIFI_AP_SET_SSID_PASSWORD; + + stillRunning = _started; + + //**************************************** + // Channel reset + //**************************************** + + // Reset the channel if all components are stopped + if ((softApOnline() == false) && (stationOnline() == false)) + { + _channel = 0; + _usingDefaultChannel = true; + } + + //**************************************** + // Delay to allow mDNS to shutdown and restart properly + //**************************************** + + delay(100); + + //**************************************** + // Display the items already started and being started + //**************************************** + + if (settings.debugWifiState && _verbose && _started) + displayComponents("Started", _started); + + if (settings.debugWifiState && _verbose && startingNow) + displayComponents("Starting", startingNow); + + //**************************************** + // Start the radio operations + //**************************************** + + // Start the soft AP mode + if (starting & WIFI_AP_SET_MODE) + { + if (!setWiFiMode(WIFI_MODE_AP, 0)) + break; + _started = _started | WIFI_AP_SET_MODE; + } + + // Start the station mode + if (starting & (WIFI_EN_SET_MODE | WIFI_STA_SET_MODE)) + { + if (!setWiFiMode(WIFI_MODE_STA, 0)) + break; + _started = _started | (starting & (WIFI_EN_SET_MODE | WIFI_STA_SET_MODE)); + } + + // Start the soft AP protocols + if (starting & WIFI_AP_SET_PROTOCOLS) + { + if (!setWiFiProtocols(WIFI_IF_AP, true, false)) + break; + _started = _started | WIFI_AP_SET_PROTOCOLS; + } + + // Start the WiFi station radio protocols + if (starting & (WIFI_EN_SET_PROTOCOLS | WIFI_STA_SET_PROTOCOLS)) + { + bool lrEnable = (starting & WIFI_EN_SET_PROTOCOLS) ? true : false; + if (!setWiFiProtocols(WIFI_IF_STA, true, lrEnable)) + break; + _started = _started | (starting & (WIFI_EN_SET_PROTOCOLS | WIFI_STA_SET_PROTOCOLS)); + } + + // Start the WiFi scan for remote APs + if (starting & WIFI_STA_START_SCAN) + { + if (settings.debugWifiState && _verbose) + systemPrintf("channel: %d\r\n", channel); + _started = _started | WIFI_STA_START_SCAN; + + // 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; + } + } + + // Select an AP from the list + if (starting & WIFI_STA_SELECT_REMOTE_AP) + { + channel = stationSelectAP(_apCount, false); + _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); + else + systemPrintf("WiFi STA: No compatible remote AP found!\r\n"); + + // 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; + } + } + + // Finish the channel selection + if (starting & WIFI_SELECT_CHANNEL) + { + _started = _started | starting & WIFI_SELECT_CHANNEL; + if (channel & (starting & WIFI_STA_START_SCAN)) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Channel: %d, determined by remote AP scan\r\n", + channel); + } + + // Use the default channel if necessary + if (!channel) + channel = WIFI_DEFAULT_CHANNEL; + _channel = channel; + + // Display the selected channel + if (settings.debugWifiState) + systemPrintf("Channel: %d selected\r\n", _channel); + } + + //**************************************** + // Start the soft AP components + //**************************************** + + // Set the soft AP SSID and password + if (starting & WIFI_AP_SET_SSID_PASSWORD) + { + if (!softApSetSsidPassword(wifiSoftApSsid, wifiSoftApPassword)) + break; + _started = _started | WIFI_AP_SET_SSID_PASSWORD; + } + + // Set the soft AP subnet mask, IP, gateway, DNS, and first DHCP addresses + if (starting & WIFI_AP_SET_IP_ADDR) + { + if (!softApSetIpAddress(_apIpAddress.toString().c_str(), + _apSubnetMask.toString().c_str(), + _apGatewayAddress.toString().c_str(), + _apDnsAddress.toString().c_str(), + _apFirstDhcpAddress.toString().c_str())) + { + break; + } + _started = _started | WIFI_AP_SET_IP_ADDR; + } + + // Get the soft AP MAC address + WiFi.AP.macAddress(_apMacAddress); + + // Set the soft AP host name + if (starting & WIFI_AP_SET_HOST_NAME) + { + // Display the host name + if (settings.debugWifiState && _verbose) + systemPrintf("Host name: %s\r\n", &settings.mdnsHostName[0]); + + // Set the host name + if (!softApSetHostName(&settings.mdnsHostName[0])) + 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) + { + if (settings.debugWifiState) + systemPrintf("Starting DNS on soft AP\r\n"); + if (dnsServer.start(53, "*", WiFi.softAPIP()) == false) + { + systemPrintf("ERROR: Failed to start DNS Server for soft AP\r\n"); + break; + } + if (settings.debugWifiState) + systemPrintf("DNS Server started for soft AP\r\n"); + _started = _started | WIFI_AP_START_DNS_SERVER; + } + + // Mark the soft AP as online + if (starting & WIFI_AP_ONLINE) + { + _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, + _apIpAddress.toString().c_str(), + mdnsName.c_str(), + wifiSoftApPassword); + } + + //**************************************** + // Start the WiFi station components + //**************************************** + + // Set the host name + if (starting & WIFI_STA_SET_HOST_NAME) + { + // Display the host name + if (settings.debugWifiState && _verbose) + systemPrintf("Host name: %s\r\n", &settings.mdnsHostName[0]); + + // Set the host name + if (!stationHostName(&settings.mdnsHostName[0])) + break; + _started = _started | WIFI_STA_SET_HOST_NAME; + } + + // Disable auto reconnect + if (starting & WIFI_STA_DISABLE_AUTO_RECONNECT) + { + if (!WiFi.setAutoReconnect(false)) + { + systemPrintf("ERROR: Failed to disable auto-reconnect!\r\n"); + break; + } + _started = _started | WIFI_STA_DISABLE_AUTO_RECONNECT; + if (settings.debugWifiState) + systemPrintf("WiFi auto-reconnect disabled\r\n"); + } + + // Connect to the remote AP + if (starting & WIFI_STA_CONNECT_TO_REMOTE_AP) + { + IPAddress ipAddress; + uint32_t timer; + + if (!stationConnectAP()) + break; + _started = _started | WIFI_STA_CONNECT_TO_REMOTE_AP; + + // Wait for an IP address + ipAddress = WiFi.STA.localIP(); + timer = millis(); + while (!(uint32_t)ipAddress) + { + if ((millis() - timer) >= WIFI_IP_ADDRESS_TIMEOUT_MSEC) + break; + delay(10); + ipAddress = WiFi.STA.localIP(); + } + if ((millis() - timer) >= WIFI_IP_ADDRESS_TIMEOUT_MSEC) + { + systemPrintf("ERROR: Failed to get WiFi station IP address!\r\n"); + break; + } + + // Wait for the station MAC address to be set + while (!_staMacAddress[0]) + delay(1); + + // Save the IP address + _staHasIp = true; + _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) + { + _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()); + } + + //**************************************** + // Start the ESP-NOW components + //**************************************** + + // Select the ESP-NOW channel + if (starting & WIFI_EN_SET_CHANNEL) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling esp_wifi_get_channel\r\n"); + status = esp_wifi_get_channel(&primaryChannel, &secondaryChannel); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to get the WiFi channels, status: %d\r\n", status); + break; + } + if (settings.debugWifiState && _verbose) + { + systemPrintf("primaryChannel: %d\r\n", primaryChannel); + systemPrintf("secondaryChannel: %d (%s)\r\n", secondaryChannel, + (secondaryChannel == WIFI_SECOND_CHAN_NONE) ? "None" + : ((secondaryChannel == WIFI_SECOND_CHAN_ABOVE) ? "Above" + : "Below")); + } + + // Set the ESP-NOW channel + if (primaryChannel != _channel) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling esp_wifi_set_channel\r\n"); + status = esp_wifi_set_channel(primaryChannel, secondaryChannel); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to set WiFi primary channel to %d, status: %d\r\n", primaryChannel, status); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi: Set channel %d\r\n", primaryChannel); + } + _started = _started | WIFI_EN_SET_CHANNEL; + } + + // Set promiscuous mode + if (starting & WIFI_EN_SET_PROMISCUOUS_MODE) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling esp_wifi_set_promiscuous\r\n"); + status = esp_wifi_set_promiscuous(true); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to set WiFi promiscuous mode, status: %d\r\n", status); + break; + } + if (settings.debugWifiState && _verbose) + systemPrintf("WiFi: Enabled promiscuous mode\r\n"); + _started = _started | WIFI_EN_SET_PROMISCUOUS_MODE; + } + + // Set promiscuous receive callback to get RSSI of action frames + if (starting & WIFI_EN_PROMISCUOUS_RX_CALLBACK) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling esp_wifi_set_promiscuous_rx_cb\r\n"); + status = esp_wifi_set_promiscuous_rx_cb(wifiPromiscuousRxHandler); + if (status != ESP_OK) + { + systemPrintf("ERROR: Failed to set the WiFi promiscuous RX callback, status: %d\r\n", status); + break; + } + if (settings.debugWifiState && _verbose) + systemPrintf("WiFi: Promiscuous RX callback established\r\n"); + _started = _started | WIFI_EN_PROMISCUOUS_RX_CALLBACK; + } + + // Start ESP-NOW + if (starting & WIFI_EN_START_ESP_NOW) + { + if (settings.debugWifiState && _verbose) + systemPrintf("Calling espNowStart\r\n"); + if (!espNowStart()) + { + systemPrintf("ERROR: Failed to start ESP-NOW\r\n"); + break; + } + if (settings.debugWifiState) + systemPrintf("WiFi: ESP-NOW started\r\n"); + _started = _started | WIFI_EN_START_ESP_NOW; + } + + // Mark ESP-NOW online + if (starting & WIFI_EN_ESP_NOW_ONLINE) + { + // Wait for the station MAC address to be set + while (!_staMacAddress[0]) + delay(1); + + // Display the ESP-NOW MAC address + _started = _started | WIFI_EN_ESP_NOW_ONLINE; + 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); + } + + // All components started successfully + enabled = true; + } while (0); + + //**************************************** + // Display the items that were not stopped + //**************************************** + if (settings.debugWifiState && _verbose) + { + systemPrintf("0x%08x: stopping\r\n", stopping); + systemPrintf("0x%08x: stillRunning\r\n", stillRunning); + } + + // Determine which components were not stopped + stopping &= stillRunning; + if (settings.debugWifiState && stopping) + displayComponents("ERROR: Items NOT stopped", stopping); + + //**************************************** + // Display the items that were not started + //**************************************** + + if (settings.debugWifiState && _verbose && _verbose) + { + systemPrintf("0x%08x: startingNow\r\n", startingNow); + systemPrintf("0x%08x: _started\r\n", _started); + } + startingNow &= ~_started; + if (settings.debugWifiState && startingNow) + displayComponents("ERROR: Items NOT started", startingNow); + + //**************************************** + // Display the items that were not started + //**************************************** + + if (settings.debugWifiState && _verbose) + { + systemPrintf("0x%08x: startingNow\r\n", startingNow); + systemPrintf("0x%08x: _started\r\n", _started); + } + + // Clear the items that were not started + _started = _started & ~notStarted; + + if (settings.debugWifiState && _verbose && _started) + displayComponents("Started items", _started); + + // Return the enable status + if (!enabled) + systemPrintf("ERROR: RTK_WIFI::enable failed!\r\n"); + return enabled; +} + +//********************************************************************* +// Test the WiFi modes +void RTK_WIFI::test(uint32_t testDurationMsec) +{ + uint32_t currentMsec; + bool disconnectFirst; + static uint32_t lastScanMsec = - (1000 * 1000); + int rand; + + // Delay the mode change until after the WiFi scan completes + currentMsec = millis(); + if (_scanRunning) + lastScanMsec = currentMsec; + + // Check if it time for a mode change + else if ((currentMsec - lastScanMsec) < testDurationMsec) + return; + + // Perform the test + lastScanMsec = currentMsec; + + // Get a random number + rand = random() & 0x1f; + disconnectFirst = (rand & 0x10) ? true : false; + + // Determine the next actions + switch (rand) + { + default: + lastScanMsec = currentMsec - (1000 * 1000); + break; + + case 0: + systemPrintf("-------------------- %d: All Stop --------------------\r\n", rand); + enable(false, false, false); + break; + + case 1: + systemPrintf("-------------------- %d: STA Start -------------------\r\n", rand); + enable(false, false, true); + break; + + case 2: + systemPrintf("-------------------- %d: STA Disconnect --------------\r\n", rand); + wifi.stationDisconnect(); + break; + + case 4: + systemPrintf("-------------------- %d: Soft AP Start -------------------\r\n", rand); + enable(false, true, false); + break; + + case 5: + systemPrintf("-------------------- %d: Soft AP & STA Start --------------------\r\n", rand); + enable(false, true, true); + break; + + case 6: + systemPrintf("-------------------- %d: Soft AP Start, STA Disconnect -------------------\r\n", rand); + if (disconnectFirst) + wifi.stationDisconnect(); + enable(false, true, false); + if (!disconnectFirst) + wifi.stationDisconnect(); + break; + + case 8: + systemPrintf("-------------------- %d: ESP-NOW Start --------------------\r\n", rand); + enable(true, false, false); + break; + + case 9: + systemPrintf("-------------------- %d: ESP-NOW & STA Start -------------------\r\n", rand); + enable(true, false, true); + break; + + case 0xa: + systemPrintf("-------------------- %d: ESP-NOW Start, STA Disconnect --------------\r\n", rand); + if (disconnectFirst) + wifi.stationDisconnect(); + enable(true, false, false); + if (!disconnectFirst) + wifi.stationDisconnect(); + break; -//---------------------------------------- -// WiFi stop sequence -//---------------------------------------- -NETWORK_POLL_SEQUENCE wifiStopSequence[] = { - // State Parameter Description - {wifiStop, 0, "Shutdown WiFi"}, - {nullptr, 0, "Termination"}, -}; + case 0xc: + systemPrintf("-------------------- %d: ESP-NOW & Soft AP Start -------------------\r\n", rand); + enable(true, true, false); + break; -//---------------------------------------- -// Stop WiFi and release all resources -//---------------------------------------- -void wifiStop() -{ - // Stop the web server - stopWebServer(); + case 0xd: + systemPrintf("-------------------- %d: ESP-NOW, Soft AP & STA Start --------------------\r\n", rand); + enable(true, true, true); + break; - // 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(); + case 0xe: + systemPrintf("-------------------- %d: ESP-NOW & Soft AP Start, STA Disconnect -------------------\r\n", rand); + if (disconnectFirst) + wifi.stationDisconnect(); + enable(true, true, false); + if (!disconnectFirst) + wifi.stationDisconnect(); + break; + } +} - wifiConnectionAttempts = 0; // Reset the timeout +//********************************************************************* +// Enable or disable verbose debug output +// Inputs: +// enable: Set true to enable verbose debug output +// Outputs: +// Return the previous enable value +bool RTK_WIFI::verbose(bool enable) +{ + bool oldVerbose; - // If ESP-Now is active, change protocol to only Long Range - if (espnowState > ESPNOW_OFF) - { - if (WiFi.getMode() != WIFI_STA) - WiFi.mode(WIFI_STA); + oldVerbose = _verbose; + _verbose = enable; + return oldVerbose; +} - // 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 +//********************************************************************* +// Verify the WiFi tables +void RTK_WIFI::verifyTables() +{ + // Verify the authorization name table + 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) { - if (settings.debugWifiState == true) - systemPrintln("WiFi disabled, ESP-Now left in place"); } } - else + + // Verify the Arduino event name table + if (ARDUINO_EVENT_MAX != arduinoEventNameEntries) { - WiFi.mode(WIFI_OFF); - if (settings.debugWifiState == true) - systemPrintln("WiFi Stopped"); + systemPrintf("ERROR: Fix arduinoEventName list to match arduino_event_id_t in NetworkEvents.h!\r\n"); + while (1) + { + } } - // Take the network offline - networkMarkOffline(NETWORK_WIFI); - - if (wifiMulti != nullptr) - wifiMulti = nullptr; - - // Display the heap state - reportHeapNow(settings.debugWifiState); - wifiRunning = false; + // 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) + { + } + } } -// Needed for wifiStopSequence -void wifiStop(NetIndex_t index, uintptr_t parameter, bool debug) -{ - wifiStop(); - networkSequenceNextEntry(NETWORK_WIFI, settings.debugNetworkLayer); -} +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + 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 Config Support Routines - compiled out +// WiFi Routines //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //---------------------------------------- -// Set AP mode +// Starts WiFi in STA, AP, or STA_AP mode depending on bools +// Returns true if STA connects, or if AP is started //---------------------------------------- -void wifiSetApMode() +bool wifiConnect(bool startWiFiStation, bool startWiFiAP, unsigned long timeout) { - WiFi.mode(WIFI_AP); -} + // 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 -//---------------------------------------- -// Start the WiFi access point -//---------------------------------------- -bool wifiStartAP() -{ - return (wifiStartAP(false)); // Don't force AP mode -} + if (startWiFiStation && WiFi.getMode() == WIFI_STA && WiFi.status() == WL_CONNECTED) + return (true); // There is nothing needing to be changed -//---------------------------------------- -// Start the access point for user to connect to and configure device -// We can also start as a WiFi station and attempt to connect to local WiFi for config -//---------------------------------------- -bool wifiStartAP(bool forceAP) -{ - if (settings.wifiConfigOverAP == true || forceAP) + 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) { - // Stop any current WiFi activity - WIFI_STOP(); + 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; + } - // Start in AP mode - WiFi.mode(WIFI_AP); + displayWiFiConnect(); - // Before starting AP mode, be sure we have default WiFi protocols enabled. - // esp_wifi_set_protocol requires WiFi to be started - esp_err_t response = - esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N); - if (response != ESP_OK) - systemPrintf("wifiStartAP: Error setting WiFi protocols: %s\r\n", esp_err_to_name(response)); - else + 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)) { - if (settings.debugWifiState == true) - systemPrintln("WiFi protocols set"); + 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); - const char *softApSsid = "RTK Config"; - if (WiFi.softAP(softApSsid) == false) // Must be short enough to fit OLED Width + // 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); @@ -684,31 +3179,166 @@ bool wifiStartAP(bool forceAP) 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 { - // Start webserver on local WiFi instead of AP + systemPrintf("WiFi failed to connect: error #%d - %s\r\n", wifiStatus, wifiPrintState((wl_status_t)wifiStatus)); + } + wifiStationRunning = false; + return false; +} - // Attempt to connect to local WiFi with increasing timeouts - int x = 0; - const int maxTries = 2; - for (; x < maxTries; x++) - { - if (wifiConnect(settings.wifiConnectTimeoutMs) == true) // Attempt to connect to any SSID on settings list - { - wifiDisplayState(); - break; - } - } - if (x == maxTries) - { - displayNoWiFi(2000); - return (wifiStartAP(true)); // Because there is no local WiFi available, force AP mode so user can still get - // access/configure it - } +//---------------------------------------- +// 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); } +} + +//---------------------------------------- +// WiFi start sequence +//---------------------------------------- +NETWORK_POLL_SEQUENCE wifiStartSequence[] = { + // State Parameter Description + {wifiThrottledStart, 0, "Initialize WiFi"}, + {nullptr, 0, "Termination"}, +}; + +//---------------------------------------- +// WiFi stop sequence +//---------------------------------------- +NETWORK_POLL_SEQUENCE wifiStopSequence[] = { + // State Parameter Description + {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 (true); + return false; } -#endif // COMPILE_WIFI +#endif // COMPILE_WIFI diff --git a/Firmware/RTK_Everywhere/bluetoothSelect.h b/Firmware/RTK_Everywhere/bluetoothSelect.h index 9f55f190a..d3964d8a0 100644 --- a/Firmware/RTK_Everywhere/bluetoothSelect.h +++ b/Firmware/RTK_Everywhere/bluetoothSelect.h @@ -103,7 +103,7 @@ class BTLESerial : public virtual BTSerialInterface, public BleSerial bool begin(String deviceName, bool isMaster, bool disableBLE, uint16_t rxQueueSize, uint16_t txQueueSize, const char *serviceID, const char *rxID, const char *txID) { - BleSerial::begin(deviceName.c_str(), serviceID, rxID, txID); + BleSerial::begin(deviceName.c_str(), serviceID, rxID, txID, -1); // name, service_uuid, rx_uuid, tx_uuid, led_pin return true; } diff --git a/Firmware/RTK_Everywhere/form.h b/Firmware/RTK_Everywhere/form.h index 6d0b003c1..f25b49950 100644 --- a/Firmware/RTK_Everywhere/form.h +++ b/Firmware/RTK_Everywhere/form.h @@ -26,993 +26,1022 @@ // python main_js_zipper.py static const uint8_t main_js[] PROGMEM = { - 0x1F, 0x8B, 0x08, 0x08, 0xD8, 0xB9, 0x5C, 0x67, 0x02, 0xFF, 0x6D, 0x61, 0x69, 0x6E, 0x2E, 0x6A, - 0x73, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0xED, 0x7D, 0xFD, 0x5E, 0x1B, 0xB9, 0x92, 0xE8, 0xFF, - 0x79, 0x0A, 0x1D, 0xDF, 0x73, 0xC7, 0xE6, 0x60, 0x8C, 0x6D, 0x3E, 0x12, 0x42, 0xC8, 0x5E, 0x02, - 0x24, 0xE1, 0x9E, 0x40, 0xB8, 0x98, 0xCC, 0x4C, 0x26, 0x27, 0xCB, 0x36, 0x6E, 0x61, 0x7A, 0x63, - 0x77, 0x7B, 0xBB, 0xDB, 0x10, 0xE6, 0x2C, 0xEF, 0xB4, 0xCF, 0xB0, 0x4F, 0x76, 0xAB, 0x24, 0x75, - 0xB7, 0xA4, 0x56, 0x7F, 0xDA, 0x26, 0x9C, 0xB3, 0xC3, 0xEF, 0x37, 0x19, 0x68, 0xA9, 0x3E, 0x54, - 0x2A, 0x95, 0x4A, 0x52, 0xA9, 0x74, 0x6B, 0xF9, 0x64, 0x64, 0x85, 0xF4, 0xCE, 0xBA, 0x27, 0x7B, - 0xE4, 0xDF, 0xEE, 0x82, 0x97, 0xEB, 0xEB, 0x7F, 0xFE, 0xFB, 0x9D, 0xE3, 0xDA, 0xDE, 0x5D, 0x67, - 0xEC, 0x0D, 0xAD, 0xD0, 0xF1, 0xDC, 0xCE, 0x8D, 0x17, 0x84, 0xAE, 0x35, 0xA1, 0x0F, 0x2F, 0x5F, - 0xF4, 0xD6, 0xEF, 0x82, 0x7F, 0xDB, 0x7D, 0x76, 0x0B, 0x70, 0x77, 0xF4, 0x2A, 0xF0, 0x86, 0xDF, - 0x68, 0xB8, 0xFB, 0xEC, 0x99, 0x80, 0xB0, 0x6C, 0xFB, 0xE8, 0x96, 0xBA, 0xE1, 0x07, 0x27, 0x08, - 0xA9, 0x4B, 0xFD, 0x56, 0x73, 0xEC, 0x59, 0x76, 0xB3, 0x4D, 0x3C, 0xF7, 0x03, 0xFC, 0xB2, 0x02, - 0x35, 0xAF, 0x67, 0xEE, 0x10, 0x91, 0x8A, 0x4F, 0x2D, 0x8A, 0xF5, 0x57, 0xC8, 0xDF, 0x9F, 0x11, - 0xF8, 0x71, 0x5C, 0x27, 0xFC, 0x85, 0x5E, 0x0D, 0x18, 0xDA, 0x16, 0x54, 0x7F, 0x90, 0x00, 0xB4, - 0x42, 0x01, 0x12, 0x73, 0x01, 0xFC, 0xBB, 0xF4, 0x8E, 0x24, 0x35, 0x44, 0xBB, 0x00, 0x8B, 0x52, - 0xAF, 0xE3, 0xB9, 0x13, 0x1A, 0x04, 0xD6, 0x88, 0x02, 0x44, 0x8C, 0xBC, 0x35, 0x09, 0x46, 0x11, - 0x4A, 0xFC, 0x99, 0x5A, 0x7E, 0x40, 0x8F, 0xDD, 0xA1, 0x37, 0x71, 0xDC, 0x11, 0x16, 0x76, 0x6C, - 0x2B, 0xB4, 0x04, 0xAE, 0x07, 0x95, 0xB1, 0x11, 0x6D, 0xD1, 0x08, 0xD6, 0xA7, 0xE1, 0xCC, 0x77, - 0x89, 0xED, 0x0D, 0x67, 0x13, 0x68, 0x58, 0x67, 0x44, 0xC3, 0xA3, 0x31, 0xC5, 0x5F, 0xDF, 0xDC, - 0x1F, 0x43, 0x6B, 0x79, 0x9B, 0x50, 0x7C, 0xD7, 0xCE, 0x77, 0x6A, 0x7F, 0xB0, 0x90, 0xEF, 0xEE, - 0xAE, 0xF4, 0xC5, 0x73, 0x47, 0xC9, 0xA7, 0xE9, 0xD8, 0x0A, 0xAF, 0x3D, 0x7F, 0x72, 0xE6, 0x53, - 0x28, 0x85, 0xEF, 0x8D, 0x06, 0x2F, 0x18, 0x51, 0xCF, 0xA6, 0xA1, 0x33, 0xE4, 0x08, 0x36, 0xBB, - 0x9D, 0x6E, 0x4F, 0x2B, 0x00, 0xC6, 0xF6, 0xC8, 0x5A, 0xAF, 0xBB, 0xD5, 0xE9, 0xED, 0xA8, 0x45, - 0xFB, 0x63, 0x84, 0xE9, 0x6D, 0x75, 0xBB, 0x1D, 0x01, 0x44, 0x87, 0xF4, 0xFA, 0x57, 0x56, 0xBD, - 0xFF, 0xA2, 0xDB, 0xEF, 0x6E, 0x77, 0xB6, 0xB6, 0x5F, 0x24, 0x25, 0x9F, 0xB1, 0x64, 0xF3, 0x79, - 0x6F, 0xFB, 0x45, 0x77, 0xB3, 0xB3, 0xD9, 0xDD, 0x48, 0x4A, 0x7E, 0x63, 0xB4, 0x5F, 0x6C, 0x6F, - 0x6F, 0x6F, 0x75, 0x36, 0x5F, 0x6C, 0xF2, 0x82, 0xB1, 0x15, 0x84, 0x6F, 0x9D, 0x31, 0x3D, 0x05, - 0x8D, 0x91, 0x38, 0xBE, 0xC6, 0x4F, 0xB3, 0xC9, 0x15, 0xF5, 0x93, 0xE6, 0xB9, 0xEC, 0xEF, 0x8F, - 0xD7, 0x58, 0x3D, 0x18, 0xD0, 0x31, 0x1D, 0x86, 0xD4, 0x4E, 0x8A, 0x03, 0xF1, 0x85, 0x15, 0x4B, - 0xA8, 0x82, 0x1B, 0x0F, 0x14, 0x6E, 0x84, 0x9F, 0x51, 0xD3, 0xB0, 0x1F, 0xAD, 0x71, 0x40, 0x79, - 0xA1, 0x77, 0x15, 0x5A, 0x8E, 0x4B, 0xED, 0x13, 0xDE, 0xC9, 0xA5, 0x2A, 0xBC, 0xB1, 0x02, 0xAA, - 0x56, 0x12, 0x24, 0x44, 0x9D, 0xF3, 0x8B, 0x83, 0x93, 0x34, 0x22, 0x6C, 0xD1, 0x85, 0x75, 0x05, - 0xFF, 0xD0, 0xEF, 0xA1, 0xC4, 0xDE, 0xD0, 0xF3, 0x7D, 0xCA, 0x54, 0x43, 0x2B, 0x10, 0x7A, 0xA7, - 0x7D, 0x45, 0x79, 0x09, 0x3A, 0x17, 0xF7, 0x53, 0x9A, 0x5D, 0x22, 0xB8, 0xC4, 0x52, 0xCE, 0xA3, - 0x75, 0x1B, 0xB7, 0x02, 0x85, 0x8D, 0x22, 0xFA, 0xF2, 0x75, 0x37, 0x55, 0xF6, 0xB3, 0x35, 0x9E, - 0xA5, 0x0B, 0x0F, 0x6E, 0xE8, 0xF0, 0xDB, 0x95, 0xF7, 0xDD, 0x08, 0x19, 0x15, 0x2A, 0xA0, 0xAC, - 0x18, 0x5A, 0xE6, 0xF9, 0x76, 0x70, 0x74, 0x70, 0xF4, 0x56, 0x02, 0x12, 0x5F, 0xDF, 0x09, 0x0D, - 0x93, 0x4A, 0xAE, 0x67, 0xE3, 0xF1, 0x19, 0x30, 0xF1, 0x69, 0x0A, 0x03, 0x48, 0x12, 0xB2, 0x00, - 0x0B, 0x68, 0x78, 0xE1, 0x4C, 0xA8, 0x37, 0x0B, 0xA3, 0x2E, 0x77, 0xED, 0x43, 0x18, 0x69, 0xCA, - 0xC7, 0x21, 0x72, 0x73, 0x4A, 0xEF, 0xDE, 0x3A, 0xFE, 0xE4, 0xCE, 0xF2, 0xA9, 0x52, 0x08, 0x23, - 0xCC, 0x54, 0xF4, 0x6C, 0xE8, 0xB9, 0xD0, 0x61, 0xA0, 0x61, 0x07, 0x71, 0x77, 0x04, 0x03, 0x6F, - 0xE6, 0x0F, 0x59, 0x7B, 0x5E, 0xE8, 0x5D, 0x25, 0xCA, 0x74, 0x69, 0xA4, 0x2A, 0x9C, 0xF9, 0x8E, - 0xE7, 0x3B, 0xA1, 0x93, 0x48, 0x85, 0x13, 0x3A, 0xF0, 0x40, 0x00, 0x8E, 0x0B, 0x6D, 0xC4, 0xAE, - 0xC2, 0x42, 0x6E, 0x10, 0x0E, 0x3E, 0x7E, 0x3C, 0x3F, 0x3C, 0x3E, 0xDD, 0xBF, 0x38, 0xBA, 0x3C, - 0x3E, 0x3D, 0xFB, 0x74, 0x71, 0x79, 0xF1, 0xF9, 0xEC, 0xE8, 0xF2, 0xF0, 0xF0, 0x25, 0xE9, 0xB6, - 0xC9, 0xFA, 0xFA, 0x21, 0xBD, 0xB6, 0x66, 0x30, 0x1E, 0x0F, 0x0F, 0x3B, 0x76, 0xF4, 0x93, 0x0B, - 0x77, 0x72, 0xF2, 0x92, 0xF4, 0x18, 0x24, 0xFC, 0xDA, 0x99, 0xE0, 0x4F, 0x6E, 0xFD, 0x4B, 0x04, - 0xE8, 0x73, 0x00, 0x52, 0x16, 0xE2, 0xF2, 0x70, 0x7F, 0xF0, 0xFE, 0x25, 0xD9, 0xE0, 0x60, 0x6B, - 0xA5, 0xC1, 0x06, 0x9F, 0x4F, 0xDE, 0x7C, 0xFC, 0xF0, 0x92, 0x6C, 0x72, 0xC0, 0xFF, 0xFE, 0xAF, - 0x08, 0x72, 0x32, 0x69, 0x16, 0xB4, 0x6A, 0x30, 0x78, 0x49, 0xB6, 0x62, 0x36, 0xC9, 0x60, 0xD0, - 0x09, 0xD8, 0x4F, 0x31, 0x4D, 0x00, 0xDC, 0xAE, 0x07, 0x28, 0x9A, 0xF9, 0x3C, 0x6E, 0xE6, 0x5A, - 0x25, 0xE8, 0xA8, 0xB5, 0x2F, 0x92, 0xD6, 0x36, 0x63, 0x04, 0x8D, 0xC2, 0xF6, 0x5E, 0x9E, 0x7E, - 0xBC, 0x3C, 0x3C, 0x3A, 0x38, 0x3E, 0xD9, 0x07, 0x1C, 0x3B, 0x51, 0x97, 0x0E, 0x06, 0x64, 0x8D, - 0x9C, 0x7A, 0xC4, 0xA6, 0x43, 0x67, 0x62, 0x8D, 0xCB, 0xF0, 0x21, 0xE3, 0xE9, 0x75, 0x65, 0x51, - 0x54, 0x46, 0x85, 0x02, 0x51, 0xF1, 0xF5, 0x64, 0xE1, 0x94, 0xC5, 0x77, 0x7C, 0xFA, 0xF3, 0xFE, - 0x87, 0xE3, 0xC3, 0xCB, 0x4F, 0xA7, 0x7F, 0x3D, 0xFD, 0xF8, 0xCB, 0x29, 0xA0, 0xE9, 0xB7, 0xA3, - 0x79, 0x0F, 0x86, 0xCB, 0x2D, 0xF5, 0xC1, 0xAA, 0x27, 0x23, 0x06, 0x2D, 0x7E, 0xA7, 0x1B, 0x0D, - 0xB8, 0xE8, 0xEB, 0xB1, 0x3B, 0x9D, 0x85, 0xC2, 0x22, 0x6A, 0xA3, 0xAB, 0x93, 0xD5, 0x0C, 0xD9, - 0xC1, 0x48, 0x4D, 0xE1, 0xD1, 0x1C, 0xBD, 0xBE, 0x8E, 0x43, 0xD6, 0x1B, 0x53, 0xF0, 0x70, 0x46, - 0xAD, 0x46, 0x54, 0x25, 0x32, 0xCF, 0x2F, 0x49, 0x83, 0xAC, 0x12, 0xAC, 0x0F, 0xD8, 0xB0, 0x3E, - 0xB2, 0x85, 0xB3, 0x3F, 0xF0, 0x81, 0x8E, 0x40, 0x30, 0x1D, 0x3B, 0x61, 0xAB, 0xD9, 0x6E, 0x0A, - 0x6F, 0x00, 0xE6, 0x67, 0xD2, 0x1A, 0x83, 0x03, 0xF2, 0x9D, 0xCD, 0x5C, 0xF0, 0xBF, 0x57, 0xAC, - 0x7A, 0x67, 0x4C, 0xDD, 0x51, 0x78, 0x03, 0x32, 0xEB, 0xE1, 0xC7, 0xD5, 0x3D, 0xD2, 0x97, 0x3D, - 0x0C, 0xC4, 0xEA, 0xE0, 0x64, 0x87, 0x75, 0xBF, 0x7C, 0xFF, 0xBA, 0xAB, 0x94, 0xDC, 0x5A, 0xE3, - 0xB8, 0x08, 0xB8, 0xE9, 0x49, 0xC5, 0x1A, 0xFB, 0x8E, 0xCD, 0x19, 0x06, 0x5C, 0xAB, 0xA4, 0xD1, - 0x46, 0x48, 0xFE, 0x01, 0x7E, 0x89, 0x5A, 0xC0, 0xC1, 0x06, 0x53, 0xE8, 0x38, 0xC0, 0x0B, 0xCD, - 0x9D, 0x58, 0xAE, 0x1D, 0xC4, 0x45, 0xCE, 0x35, 0x69, 0x39, 0x76, 0xC7, 0x71, 0x87, 0xE3, 0x99, - 0x4D, 0x83, 0x56, 0x23, 0xB0, 0x4F, 0xBC, 0x99, 0x0B, 0x7D, 0xD4, 0x58, 0x91, 0x59, 0xE6, 0x68, - 0x2E, 0xD0, 0xC1, 0xF1, 0xDC, 0x75, 0xEF, 0xFA, 0x9A, 0x0C, 0x0E, 0x09, 0x18, 0x5D, 0x4B, 0xA9, - 0x81, 0xD8, 0x18, 0xFB, 0x30, 0x55, 0x31, 0x63, 0xDF, 0xD0, 0x71, 0xE0, 0xCF, 0x8D, 0x63, 0xD3, - 0x56, 0x03, 0xA7, 0xD0, 0x13, 0xCB, 0x05, 0xA9, 0xFB, 0x8D, 0x95, 0x5D, 0xA5, 0xD2, 0x83, 0xF2, - 0x17, 0x05, 0x3C, 0x0A, 0xE6, 0xD0, 0x9F, 0x99, 0x11, 0xE3, 0xCC, 0x5D, 0x12, 0x71, 0xF2, 0x5B, - 0x8C, 0x1E, 0x7B, 0x04, 0xB0, 0xAB, 0x7E, 0x57, 0x8A, 0x4E, 0xCA, 0x2D, 0x03, 0xAE, 0x54, 0x22, - 0xB1, 0x03, 0x18, 0x3A, 0xE1, 0x98, 0xCD, 0xDA, 0xE7, 0x17, 0x7F, 0x65, 0xDD, 0xA2, 0xC1, 0x42, - 0x9F, 0x91, 0x01, 0x38, 0x8D, 0xD3, 0x86, 0x8A, 0x20, 0x35, 0x6F, 0x62, 0x83, 0xD5, 0x2A, 0x46, - 0x3F, 0x23, 0xD5, 0x15, 0x3A, 0xAF, 0x50, 0xED, 0xE8, 0xE7, 0xBF, 0xE6, 0x88, 0xEE, 0x0A, 0xDC, - 0x8C, 0x03, 0xCF, 0xBD, 0x76, 0x46, 0xBA, 0xE4, 0x92, 0x3A, 0xD3, 0x69, 0x51, 0x0D, 0x1A, 0xDE, - 0x50, 0xDF, 0xA5, 0x61, 0x51, 0x3D, 0x37, 0x2C, 0x44, 0x35, 0xF5, 0xFC, 0x30, 0xC8, 0xAE, 0xC4, - 0x35, 0x09, 0x24, 0x00, 0xF4, 0xAC, 0xF1, 0x19, 0x54, 0xFE, 0x38, 0x65, 0x13, 0x76, 0x36, 0x46, - 0x18, 0x38, 0x17, 0xDE, 0xE0, 0xF0, 0xC0, 0xF2, 0xED, 0x6C, 0x8C, 0x23, 0x6B, 0x0C, 0x4A, 0xE4, - 0xBD, 0xB7, 0xC0, 0x33, 0x0D, 0x43, 0x30, 0x10, 0xD9, 0x55, 0x43, 0x67, 0x1C, 0x16, 0x31, 0x78, - 0x45, 0xE9, 0x94, 0xFA, 0x50, 0x2B, 0xF4, 0xBD, 0x71, 0x36, 0x6B, 0xB3, 0x80, 0xEE, 0x07, 0x01, - 0xF8, 0x9A, 0xA7, 0xDE, 0x5D, 0xE4, 0x85, 0x65, 0xD7, 0x9E, 0x50, 0x2B, 0x98, 0xF9, 0x6C, 0x7D, - 0x71, 0x1E, 0x99, 0xCC, 0x6C, 0x16, 0x26, 0x5E, 0x60, 0x39, 0xC3, 0xD3, 0x93, 0xA3, 0xFD, 0x41, - 0x08, 0xA3, 0x76, 0x72, 0xE8, 0x7B, 0x53, 0x58, 0xB6, 0xE5, 0x49, 0x0A, 0xB0, 0xDF, 0xD2, 0xFB, - 0x63, 0x57, 0x88, 0x20, 0xC8, 0x65, 0xFC, 0x03, 0x2C, 0x18, 0xC7, 0xCE, 0xEF, 0xD4, 0x3E, 0x04, - 0xFE, 0x7D, 0xE7, 0x6A, 0x86, 0xDD, 0x50, 0xDC, 0x08, 0x80, 0x3C, 0x72, 0xD1, 0x8F, 0x3E, 0xFA, - 0x1E, 0xA2, 0xB3, 0x76, 0x6E, 0xD9, 0x8E, 0x97, 0xA3, 0x59, 0x52, 0xAD, 0xC1, 0xD9, 0xFE, 0xF9, - 0xC5, 0x29, 0xF7, 0xCC, 0xA2, 0xD6, 0x94, 0x34, 0x24, 0xA6, 0x81, 0xF1, 0xD6, 0x1A, 0x82, 0x1D, - 0xBF, 0xED, 0xC3, 0xE8, 0xF8, 0xCF, 0xFF, 0x24, 0x79, 0x35, 0xC8, 0x87, 0x37, 0x60, 0x42, 0xD3, - 0xE6, 0x71, 0x71, 0xC3, 0x48, 0xA8, 0x75, 0xE1, 0x30, 0xE2, 0xF5, 0xE6, 0x1F, 0x46, 0xB1, 0x70, - 0xFF, 0x18, 0x46, 0x7F, 0x0C, 0xA3, 0xF2, 0xC3, 0x28, 0x73, 0x8C, 0x70, 0x29, 0xFD, 0xBA, 0xD5, - 0xF8, 0x63, 0x88, 0x3C, 0xB5, 0x21, 0xC2, 0xAB, 0x95, 0x1D, 0x22, 0x42, 0xE9, 0x4B, 0x0E, 0x11, - 0x31, 0xA0, 0xAA, 0x0C, 0x11, 0x4E, 0xA0, 0xCC, 0x10, 0x89, 0x19, 0x5F, 0xF6, 0x10, 0x89, 0x7D, - 0x8A, 0xC2, 0x21, 0x92, 0xA6, 0xC4, 0x36, 0xAD, 0xC0, 0x1B, 0x1B, 0x01, 0x06, 0xFB, 0xDE, 0xB5, - 0x26, 0xCE, 0xF0, 0xC4, 0xB3, 0xA9, 0xB1, 0x27, 0x70, 0xD9, 0xE0, 0xD2, 0x3B, 0xAE, 0x46, 0x62, - 0x07, 0x93, 0xFF, 0xD1, 0x6A, 0x0E, 0x42, 0x2B, 0x74, 0x86, 0xCD, 0x36, 0x69, 0x76, 0x9B, 0xA6, - 0x16, 0x31, 0x3A, 0xB8, 0xE5, 0xDA, 0x8A, 0x31, 0xB4, 0xC9, 0xCC, 0xB5, 0x61, 0x1C, 0xBA, 0xD4, - 0x36, 0x40, 0x64, 0x11, 0xFA, 0x7F, 0x33, 0x0B, 0xD5, 0x20, 0xA2, 0xD6, 0x5B, 0x2E, 0xB5, 0x33, - 0x0A, 0x4B, 0x0C, 0xE8, 0x33, 0xCB, 0x45, 0x62, 0xFD, 0xE5, 0x12, 0xDB, 0x9F, 0x85, 0xDE, 0xC4, - 0x0B, 0x9D, 0x5B, 0x8A, 0xC4, 0x36, 0x96, 0x4B, 0xEC, 0x1C, 0xEC, 0x1E, 0x81, 0x91, 0x8E, 0xA4, - 0x36, 0x97, 0x4B, 0xEA, 0x3D, 0xB5, 0x6E, 0xEF, 0xC9, 0x89, 0x35, 0xBC, 0x01, 0x40, 0xFF, 0x1E, - 0x29, 0x6E, 0x2D, 0x97, 0xE2, 0xA7, 0xFD, 0x9F, 0x91, 0xCA, 0xF6, 0x92, 0xA9, 0xB8, 0x63, 0x67, - 0xE2, 0xC0, 0xC2, 0x13, 0x69, 0x3D, 0xAF, 0x47, 0x2B, 0x05, 0x32, 0x62, 0xE6, 0x8B, 0x6F, 0xE4, - 0x32, 0xD3, 0x75, 0xED, 0xE1, 0x82, 0xA9, 0xB1, 0xD2, 0x09, 0x68, 0xB8, 0x1F, 0x72, 0xFB, 0x41, - 0x5B, 0x4D, 0x5C, 0x6D, 0xAF, 0x5D, 0x05, 0x6B, 0x9E, 0xEF, 0x8C, 0x1C, 0x30, 0xF0, 0x6B, 0x6C, - 0xED, 0x06, 0x0B, 0xFD, 0x8B, 0x1B, 0x4A, 0xDE, 0x9D, 0x0E, 0x06, 0x64, 0x68, 0xC1, 0xCA, 0x77, - 0x16, 0x82, 0xE9, 0x23, 0x68, 0xDB, 0x08, 0xB8, 0x80, 0x04, 0xF7, 0x86, 0x6F, 0x37, 0x88, 0x15, - 0x12, 0xDB, 0xB9, 0xBE, 0xA6, 0x3E, 0x18, 0x48, 0xE2, 0x03, 0x99, 0xA0, 0x43, 0xDE, 0x7A, 0x3E, - 0xAB, 0xF7, 0x32, 0x32, 0x0A, 0x16, 0x09, 0x98, 0x39, 0x64, 0x7B, 0x06, 0x14, 0x7A, 0x2F, 0xDA, - 0x73, 0x68, 0x33, 0x54, 0xC0, 0x0E, 0xFC, 0x9F, 0x38, 0xB0, 0xF2, 0xF6, 0x71, 0x9D, 0x1B, 0xD7, - 0xE2, 0x50, 0x1C, 0x21, 0xA7, 0xF7, 0x32, 0xBB, 0x72, 0x74, 0xBA, 0x31, 0xF2, 0xBD, 0xD9, 0x94, - 0x23, 0xA6, 0xCC, 0xE8, 0x41, 0x5D, 0xDB, 0xB9, 0x75, 0xEC, 0x19, 0xD4, 0x16, 0x95, 0x82, 0x8E, - 0x49, 0xC6, 0x28, 0x30, 0x3F, 0x1C, 0x4E, 0xEA, 0x49, 0x0B, 0x19, 0x24, 0x4E, 0x00, 0x0B, 0x56, - 0xCB, 0x0D, 0xA0, 0x33, 0x71, 0x4B, 0xFF, 0xEA, 0x9E, 0xC0, 0x2C, 0x4D, 0x70, 0xC2, 0x47, 0x49, - 0x59, 0xC4, 0x16, 0xFB, 0x9E, 0xDE, 0x35, 0xE9, 0xBD, 0xFF, 0x9D, 0x31, 0x1F, 0xB1, 0x44, 0x7A, - 0xDD, 0xEE, 0x56, 0x9B, 0x9C, 0x0C, 0x4E, 0x36, 0x39, 0xF7, 0xDD, 0x4E, 0x54, 0xA5, 0xD7, 0xDD, - 0xD8, 0xE8, 0x90, 0x8B, 0x1B, 0xC0, 0x8E, 0x5D, 0x71, 0x45, 0xC9, 0xD8, 0xBB, 0x03, 0x91, 0xDB, - 0xAC, 0xD4, 0x47, 0x3B, 0x1D, 0x90, 0x3B, 0x27, 0xBC, 0xC1, 0xEF, 0x40, 0xCD, 0xB5, 0xEF, 0x1C, - 0x1B, 0xFE, 0x82, 0xC2, 0xD0, 0x72, 0xC6, 0x1E, 0xD6, 0x0C, 0xBD, 0x98, 0x33, 0xC0, 0x7E, 0xBF, - 0x6E, 0x8D, 0xC7, 0x4C, 0xA6, 0x89, 0x48, 0xC8, 0x07, 0xD4, 0xC1, 0xE0, 0x25, 0x12, 0xC6, 0xEA, - 0xDB, 0xDD, 0x6E, 0xA6, 0x98, 0x68, 0x6A, 0x3A, 0xA9, 0x2E, 0x30, 0x3E, 0x25, 0x91, 0xC8, 0xAF, - 0xE0, 0xED, 0x90, 0x77, 0x9E, 0x5F, 0x46, 0x4A, 0x06, 0xE3, 0x85, 0xCF, 0xAC, 0xE4, 0xE0, 0xE3, - 0x49, 0xBF, 0x43, 0xC4, 0xEE, 0xF1, 0x4B, 0xF2, 0x16, 0xF7, 0x59, 0x9A, 0xB5, 0x3D, 0xB6, 0x0B, - 0xCF, 0x1F, 0xDE, 0xFC, 0x13, 0x39, 0x6A, 0xB9, 0x5B, 0x02, 0x19, 0xB5, 0x35, 0x4F, 0x2D, 0xA3, - 0x16, 0xDB, 0xEC, 0xA7, 0xE3, 0x31, 0x3B, 0x18, 0x1D, 0x80, 0x5C, 0xA0, 0x2E, 0x59, 0x5F, 0x3F, - 0xF5, 0x42, 0x12, 0xCC, 0xA6, 0xC8, 0x16, 0xA8, 0x18, 0x74, 0xD3, 0xA7, 0x93, 0x9D, 0x17, 0xDD, - 0x32, 0x38, 0x4E, 0xAD, 0x5B, 0x67, 0x98, 0x8B, 0xA4, 0xDA, 0x0A, 0x07, 0x77, 0x6B, 0x3D, 0x18, - 0x44, 0x38, 0xDC, 0xCE, 0xCE, 0x3E, 0x10, 0xA8, 0x46, 0x4E, 0xDE, 0xED, 0xFF, 0x0B, 0xE1, 0xD8, - 0x7D, 0xDA, 0xE9, 0x74, 0x2A, 0xAE, 0x82, 0x96, 0xE8, 0xE2, 0x30, 0x9F, 0xEF, 0x11, 0x5C, 0x1C, - 0x31, 0x7B, 0xF5, 0x1E, 0xD3, 0xDB, 0xE8, 0x2F, 0x70, 0xFA, 0xFA, 0xE1, 0xD6, 0xB8, 0xD7, 0x7D, - 0xBE, 0x89, 0xFF, 0xBE, 0x60, 0xFF, 0xEE, 0xE0, 0xBF, 0xBD, 0xFE, 0x0F, 0xB7, 0xCF, 0x58, 0xB9, - 0xDF, 0xED, 0xA0, 0x6E, 0xD3, 0x97, 0x04, 0xA7, 0x69, 0x49, 0x87, 0xD9, 0x2C, 0x8C, 0x4D, 0xF7, - 0x6E, 0xA9, 0xEF, 0x3B, 0xB6, 0x4D, 0x5D, 0xAC, 0x8F, 0xBC, 0xDE, 0xDD, 0x50, 0x9C, 0x39, 0x09, - 0x3B, 0x51, 0x9D, 0x80, 0xE6, 0x76, 0x9A, 0xD5, 0x36, 0x89, 0xA5, 0xBD, 0xF2, 0x91, 0x1B, 0x04, - 0xD1, 0xE9, 0xE3, 0xCF, 0xD4, 0x0F, 0xA0, 0x43, 0x8F, 0xDD, 0xD0, 0xB4, 0x71, 0x0E, 0x43, 0xC4, - 0xB9, 0xBE, 0xC7, 0x89, 0x9B, 0x2D, 0x70, 0x88, 0x3D, 0xA3, 0xC8, 0xD0, 0xB5, 0x00, 0x26, 0xCC, - 0xF5, 0x61, 0x46, 0x21, 0x48, 0xED, 0xE2, 0x66, 0x6F, 0xE3, 0x16, 0x6C, 0x54, 0xCD, 0xB9, 0x95, - 0xB5, 0xA8, 0x91, 0x8E, 0x36, 0x18, 0x67, 0xB9, 0x47, 0x18, 0xEB, 0x03, 0x2E, 0x43, 0x8B, 0xBB, - 0xC5, 0xFD, 0xC7, 0x5C, 0xCD, 0x6C, 0x3C, 0xA6, 0x7D, 0x59, 0xF2, 0x12, 0x63, 0x40, 0xAD, 0x47, - 0x58, 0x56, 0xEC, 0x3B, 0xFE, 0x95, 0x07, 0x4E, 0x01, 0xE9, 0x8D, 0x1E, 0x61, 0x79, 0x11, 0x53, - 0xEB, 0x8F, 0xE6, 0x58, 0x60, 0x54, 0xA6, 0xB6, 0xC9, 0xA8, 0xBD, 0x58, 0x2E, 0xB5, 0x5F, 0x7C, - 0xF0, 0x05, 0x90, 0xCE, 0xCE, 0x72, 0xE9, 0xBC, 0x71, 0xBE, 0x31, 0xF5, 0xEB, 0x2D, 0x68, 0x18, - 0x47, 0x87, 0x7A, 0xAF, 0xF7, 0x48, 0xAF, 0xDF, 0x33, 0x19, 0xA1, 0xCA, 0x86, 0xA8, 0xC8, 0x18, - 0x9D, 0xE0, 0xA4, 0xC4, 0xDA, 0x60, 0x74, 0x08, 0xEA, 0xB5, 0x23, 0x4F, 0x66, 0x47, 0x6B, 0x83, - 0xA1, 0x07, 0x73, 0x14, 0x27, 0xDA, 0x5F, 0x1C, 0xD1, 0x87, 0xC2, 0x49, 0x6B, 0x7D, 0x7D, 0x00, - 0xE6, 0x09, 0xA7, 0x9B, 0x11, 0x46, 0xE4, 0x59, 0x62, 0xFE, 0xC7, 0xD3, 0xC8, 0x99, 0xEB, 0x84, - 0x05, 0x93, 0x5B, 0x60, 0xBF, 0xF5, 0x29, 0x1D, 0x4C, 0x61, 0xAA, 0x68, 0xAC, 0x28, 0xB4, 0x60, - 0x52, 0xD1, 0x6A, 0x0E, 0x9C, 0xDF, 0x8B, 0x2A, 0xDD, 0x80, 0x73, 0x8D, 0x53, 0xDD, 0xF1, 0x61, - 0x41, 0x45, 0xC3, 0xA4, 0x5A, 0x00, 0x61, 0x5B, 0xF7, 0xC1, 0x39, 0x9D, 0x58, 0x8E, 0xCB, 0xF6, - 0x4F, 0x73, 0xEB, 0x4E, 0x7D, 0x0F, 0xCF, 0x87, 0xBB, 0x18, 0xEC, 0x53, 0xAE, 0x6A, 0xAF, 0x7C, - 0xD5, 0x7E, 0xF9, 0xAA, 0x1B, 0xE5, 0xAB, 0x6E, 0x96, 0xAF, 0xBA, 0x55, 0xBE, 0xEA, 0x76, 0xF9, - 0xAA, 0xCF, 0x4B, 0x54, 0x65, 0x0E, 0xDE, 0xC9, 0xFE, 0x41, 0x51, 0x57, 0x51, 0x58, 0xF2, 0xD0, - 0x37, 0x17, 0x85, 0x5A, 0x00, 0x4B, 0xB2, 0x28, 0x90, 0xB0, 0xA0, 0xE6, 0x95, 0x05, 0x8E, 0xAD, - 0x7F, 0x7F, 0x46, 0xFD, 0x21, 0x45, 0xE7, 0x2B, 0xAE, 0xAC, 0xDB, 0x13, 0x30, 0x1F, 0x8E, 0xBD, - 0x02, 0x90, 0x30, 0x16, 0xDE, 0x5F, 0x9C, 0x7C, 0xD0, 0xCF, 0xEE, 0x8B, 0xBC, 0x3D, 0x3F, 0xFC, - 0x96, 0xD2, 0x4B, 0x03, 0x0D, 0x63, 0xBD, 0x6C, 0xAA, 0xD9, 0x50, 0x9F, 0xA6, 0x23, 0x90, 0x2A, - 0x6D, 0xCC, 0xC1, 0xF2, 0x10, 0x97, 0xC9, 0xFE, 0xE4, 0x1C, 0x63, 0xED, 0xD2, 0xCC, 0xB2, 0x10, - 0xBC, 0x03, 0x6F, 0x32, 0x05, 0x83, 0x49, 0x5B, 0x2B, 0x95, 0xD1, 0x62, 0xB0, 0xDE, 0x39, 0x1D, - 0x52, 0x67, 0x6A, 0x40, 0x9E, 0xAE, 0xA3, 0x52, 0x28, 0x20, 0x21, 0x54, 0x8F, 0x47, 0x8D, 0x1A, - 0xB0, 0xCF, 0x7C, 0xDC, 0x6D, 0x3B, 0x93, 0x6B, 0x99, 0x24, 0xFB, 0xE7, 0x56, 0xC3, 0xC1, 0xB5, - 0xEC, 0x17, 0x8C, 0x61, 0xDE, 0x13, 0x48, 0xD9, 0x5E, 0xCD, 0xD7, 0x2F, 0xB7, 0x18, 0xE0, 0xB8, - 0x87, 0x71, 0x19, 0x46, 0x6C, 0xAB, 0xA4, 0xF1, 0x15, 0x64, 0x0F, 0x30, 0xD3, 0x56, 0x93, 0x85, - 0x21, 0xB2, 0xAD, 0x49, 0x8C, 0xC5, 0xA8, 0x20, 0xAA, 0xC8, 0xC3, 0xFF, 0x34, 0xC5, 0x70, 0xE8, - 0x48, 0xD8, 0xE9, 0x06, 0x99, 0xEB, 0xB5, 0x6A, 0x53, 0x42, 0x4F, 0x78, 0x16, 0x14, 0xD1, 0xE1, - 0xB5, 0x5A, 0x3C, 0x5C, 0xA8, 0xF4, 0xAA, 0x27, 0x89, 0x3F, 0x36, 0x0D, 0x00, 0x39, 0x38, 0xD9, - 0xA4, 0xE9, 0x73, 0x8D, 0x41, 0x29, 0xC4, 0x39, 0x87, 0x36, 0x9B, 0x84, 0x97, 0x46, 0x7B, 0x7F, - 0x9C, 0xD3, 0x6E, 0x1E, 0x60, 0xBD, 0x70, 0xDA, 0x2C, 0x4A, 0x3B, 0x4D, 0x35, 0x0A, 0xDE, 0x5E, - 0x0A, 0xBD, 0xCF, 0x66, 0x7A, 0x9F, 0x97, 0x45, 0xEF, 0x37, 0x33, 0xBD, 0xDF, 0x96, 0x42, 0x2F, - 0x98, 0xBA, 0xDE, 0xDD, 0x19, 0xC5, 0xB3, 0xD2, 0x99, 0x69, 0xC9, 0x1E, 0xBB, 0xA6, 0xA4, 0xBB, - 0x62, 0xDC, 0x96, 0x99, 0x02, 0x2C, 0xCC, 0x77, 0x81, 0x66, 0x9E, 0x1B, 0x8D, 0x3A, 0x4C, 0x5C, - 0x66, 0x32, 0xF0, 0xA5, 0xFB, 0x95, 0xFC, 0x09, 0xD0, 0x76, 0x1B, 0xE4, 0xA7, 0x9F, 0xB0, 0x8D, - 0x5F, 0x7A, 0xD1, 0x07, 0x93, 0xBF, 0x9C, 0xC9, 0xD9, 0x2A, 0x13, 0x10, 0x1A, 0xB5, 0x57, 0x57, - 0xFE, 0xEB, 0x46, 0xDD, 0x1D, 0x8F, 0x80, 0x2F, 0xB0, 0x31, 0x04, 0xDC, 0x34, 0x9F, 0xC4, 0xF1, - 0xE1, 0x9D, 0xE9, 0x2C, 0xB8, 0xA9, 0x68, 0x57, 0x04, 0xEE, 0x28, 0x90, 0x3C, 0x13, 0x7F, 0x54, - 0xA1, 0x0E, 0x8D, 0xEB, 0x09, 0xF7, 0x28, 0x74, 0xD4, 0xDA, 0xC5, 0x85, 0x4A, 0xBA, 0x74, 0x3D, - 0xE1, 0xEE, 0x6F, 0xDA, 0xD4, 0xCA, 0x77, 0x04, 0xA0, 0x03, 0x1A, 0xAF, 0x42, 0x9F, 0x58, 0x63, - 0x67, 0xE4, 0xEE, 0x35, 0xC7, 0xF4, 0x3A, 0x6C, 0xEA, 0xFD, 0x60, 0x82, 0xB0, 0x5F, 0xE3, 0x0C, - 0xA5, 0xF0, 0x87, 0x9D, 0xB8, 0x8E, 0x05, 0x65, 0xA1, 0xA3, 0x9E, 0x2F, 0x0D, 0xF4, 0x8A, 0x4D, - 0x9A, 0x24, 0xBC, 0x9F, 0xD2, 0x3D, 0x3E, 0xF7, 0x5D, 0x79, 0xDF, 0x9B, 0xE0, 0x77, 0xED, 0x35, - 0x4D, 0xDC, 0x34, 0x09, 0x9B, 0x5E, 0x9B, 0x88, 0xEC, 0xF8, 0xB0, 0x49, 0x86, 0x50, 0x21, 0x80, - 0x3F, 0x3D, 0x7F, 0xB2, 0xC6, 0xA0, 0xD7, 0x38, 0x3E, 0x29, 0x32, 0x93, 0x6D, 0x5B, 0x37, 0x5F, - 0x97, 0x64, 0x69, 0x3D, 0x54, 0x74, 0xB6, 0x44, 0x2F, 0xB3, 0x8D, 0x52, 0xBD, 0x47, 0xF0, 0x86, - 0x01, 0xF2, 0x5D, 0x65, 0x5A, 0x9D, 0x5D, 0x7D, 0x3F, 0x49, 0x0E, 0x10, 0x19, 0x4E, 0x58, 0x58, - 0x11, 0xF5, 0xF3, 0x25, 0x1E, 0xF5, 0x5D, 0x1E, 0x5E, 0x9C, 0x68, 0xDF, 0x71, 0x93, 0xF1, 0x12, - 0x77, 0x30, 0x2F, 0x71, 0x2B, 0x55, 0xE1, 0x45, 0xBA, 0x14, 0x22, 0x94, 0xCE, 0xB1, 0x77, 0xB3, - 0x6A, 0x9C, 0xF3, 0x38, 0xD0, 0x94, 0x19, 0xD4, 0x90, 0x7C, 0xB0, 0xAE, 0xE8, 0xD8, 0x14, 0x0C, - 0x2A, 0xD5, 0x3B, 0x14, 0xD1, 0xCC, 0x09, 0x54, 0x14, 0xD5, 0x7C, 0xA9, 0xAF, 0x4D, 0x51, 0x16, - 0x12, 0x54, 0x14, 0xD4, 0x0C, 0xAB, 0xF5, 0x0D, 0x93, 0xED, 0x91, 0xAF, 0xB9, 0xF0, 0xD0, 0x6D, - 0x09, 0x18, 0xCC, 0xD6, 0x6E, 0x9E, 0xDC, 0xF6, 0xD8, 0x69, 0xA9, 0x71, 0x87, 0x40, 0xBF, 0x24, - 0xF3, 0xA7, 0x3D, 0x99, 0x4C, 0xD6, 0xAE, 0x41, 0xFA, 0x6A, 0x8D, 0x04, 0x64, 0x5E, 0x83, 0xCB, - 0x77, 0x74, 0x98, 0xDA, 0xDD, 0xA4, 0x2C, 0xA5, 0xE6, 0xBB, 0x6A, 0x90, 0x72, 0x1F, 0xA8, 0x6D, - 0xC7, 0x51, 0x72, 0xC9, 0x62, 0xCB, 0xA5, 0xCF, 0x7D, 0x93, 0x48, 0x54, 0x95, 0x01, 0x44, 0x66, - 0xF5, 0xC9, 0xE9, 0x9F, 0xBD, 0x3D, 0xB2, 0x99, 0x25, 0x95, 0xBA, 0x8C, 0x1A, 0x3F, 0x6F, 0x18, - 0xBB, 0x94, 0xF1, 0xBB, 0xD9, 0x7D, 0xDE, 0xBF, 0xEC, 0x46, 0xDC, 0xF3, 0xBF, 0x0A, 0x77, 0x30, - 0x9E, 0xE5, 0x76, 0x86, 0xED, 0xDC, 0x2A, 0x86, 0x85, 0x9D, 0x71, 0x13, 0xDF, 0xBB, 0xE3, 0x76, - 0x69, 0x12, 0x8C, 0x24, 0xFE, 0x62, 0xCB, 0xA4, 0x77, 0x60, 0x0A, 0xED, 0x98, 0x89, 0x01, 0x30, - 0x72, 0xD3, 0xA6, 0xC3, 0x47, 0x24, 0x87, 0xDE, 0x78, 0x2D, 0x98, 0xAC, 0x6D, 0x12, 0xFC, 0x65, - 0x9B, 0xFD, 0xCB, 0xB8, 0x60, 0xE0, 0xCD, 0xD7, 0x1A, 0x28, 0x97, 0x2D, 0xC0, 0xBF, 0x7C, 0xB5, - 0xCE, 0x6A, 0x14, 0xB2, 0x21, 0xB5, 0x4E, 0x21, 0xB5, 0xD9, 0x54, 0x8D, 0x31, 0xBF, 0x36, 0xA7, - 0x99, 0x58, 0x1E, 0xEE, 0xD5, 0x2C, 0xA0, 0x11, 0x9B, 0xEF, 0x54, 0x1B, 0xF9, 0x4A, 0x48, 0x2E, - 0x63, 0x06, 0xA7, 0x94, 0xFC, 0xA6, 0x59, 0x68, 0x8F, 0x7C, 0xDF, 0x4B, 0x18, 0x75, 0xDC, 0xB1, - 0xE3, 0x52, 0xFE, 0x0D, 0xAC, 0xFE, 0xB4, 0x10, 0xEF, 0x3A, 0x48, 0xE4, 0x35, 0xFF, 0xB7, 0x82, - 0xE5, 0x9F, 0xC8, 0xA6, 0x3A, 0xB5, 0x55, 0x20, 0x4A, 0x8F, 0x45, 0xDC, 0x04, 0xEA, 0xA6, 0xE9, - 0xB0, 0x46, 0x16, 0x02, 0x33, 0x50, 0xEF, 0xCE, 0xC0, 0x44, 0xE5, 0x54, 0x42, 0x44, 0xE7, 0x78, - 0xD4, 0xC4, 0x94, 0x1F, 0xC6, 0x6A, 0xCF, 0x5C, 0x19, 0xEB, 0x06, 0x58, 0x25, 0x1E, 0xE4, 0xD9, - 0x75, 0x15, 0x2E, 0x25, 0xE4, 0xFD, 0x8D, 0x6E, 0x09, 0x80, 0x84, 0x80, 0x5E, 0xBF, 0xCE, 0xBC, - 0xC3, 0xAE, 0xD3, 0xBC, 0x85, 0x05, 0x63, 0xA8, 0xB9, 0x5B, 0x05, 0x73, 0x90, 0x5E, 0xED, 0x3F, - 0x66, 0xE0, 0xFD, 0x5C, 0x3B, 0x6C, 0xA9, 0xAE, 0x97, 0x67, 0x75, 0x65, 0x7E, 0x67, 0xE1, 0x8F, - 0x82, 0x35, 0x0E, 0x8A, 0x69, 0xE4, 0x19, 0x97, 0x7A, 0xF3, 0x61, 0xA1, 0xF1, 0xDC, 0xFD, 0x27, - 0x34, 0x61, 0x89, 0x74, 0xFF, 0x30, 0x67, 0x3F, 0xCA, 0x9C, 0xF1, 0x40, 0x5A, 0x34, 0x45, 0x79, - 0xE6, 0x2A, 0xA9, 0x75, 0xF9, 0xEE, 0xDD, 0x7E, 0xFD, 0x41, 0xBF, 0x04, 0x4F, 0x72, 0x21, 0x23, - 0xE7, 0x9F, 0x60, 0x96, 0x17, 0x47, 0x59, 0x7C, 0xB5, 0x64, 0x64, 0x24, 0x53, 0xA9, 0xE5, 0x11, - 0x62, 0x8B, 0x20, 0x64, 0x32, 0xB9, 0x5A, 0xEB, 0x17, 0x37, 0xDE, 0xE3, 0xE7, 0x52, 0x62, 0x58, - 0x74, 0x9B, 0xAF, 0x3F, 0x5E, 0x5F, 0xBF, 0x5A, 0xE7, 0x5F, 0x2B, 0x02, 0xF7, 0x9A, 0xAF, 0xB9, - 0x9A, 0xF5, 0x6A, 0x22, 0xE8, 0x47, 0x08, 0xFA, 0xA5, 0x11, 0xAC, 0x73, 0xA9, 0x95, 0x1B, 0x62, - 0xBA, 0xBE, 0xC2, 0xF0, 0x18, 0x58, 0xB7, 0x94, 0x05, 0xBD, 0xA0, 0xD8, 0x59, 0xD4, 0x0A, 0xE3, - 0x85, 0x58, 0x01, 0xB9, 0xA3, 0x18, 0xAC, 0xD2, 0x0C, 0x59, 0x44, 0x25, 0x56, 0xE1, 0x25, 0xF7, - 0x34, 0xEC, 0x28, 0xF8, 0x6F, 0xA0, 0xAE, 0xEB, 0x85, 0x58, 0x40, 0xAE, 0x28, 0x75, 0x89, 0x65, - 0xDB, 0x3C, 0x5E, 0x25, 0xDE, 0x61, 0x51, 0x17, 0x9A, 0xFA, 0xF5, 0x7C, 0xBE, 0x57, 0x21, 0x75, - 0xAA, 0x36, 0x40, 0xD2, 0x77, 0xF6, 0xEB, 0xEC, 0x6E, 0x08, 0xFC, 0x3C, 0xA8, 0xD0, 0x2E, 0x72, - 0x6F, 0xA4, 0x6A, 0xA5, 0xFC, 0x0B, 0xA9, 0xFE, 0x82, 0xDC, 0x8B, 0xA7, 0x6A, 0x69, 0x52, 0xFB, - 0x17, 0x64, 0x12, 0xAE, 0x6D, 0x94, 0xB5, 0x32, 0xE9, 0xDD, 0x0F, 0x6E, 0x3E, 0xB2, 0xCD, 0xCF, - 0x6B, 0x11, 0x06, 0x9A, 0x65, 0x5F, 0xCA, 0x9A, 0x17, 0x3E, 0xB3, 0x66, 0xEC, 0xBE, 0x34, 0x33, - 0xB7, 0x73, 0x2A, 0x5B, 0xD3, 0x79, 0x47, 0x5A, 0x7C, 0x9C, 0xB2, 0xA0, 0x91, 0xA6, 0xE4, 0xB3, - 0x28, 0x35, 0xD4, 0xD4, 0x24, 0x17, 0x75, 0xC6, 0x9A, 0x14, 0xA3, 0x2B, 0xF2, 0x42, 0xDC, 0xA7, - 0x07, 0x9B, 0x9A, 0x46, 0x22, 0x67, 0x40, 0x24, 0x95, 0x22, 0x64, 0x91, 0xDB, 0x0D, 0xFE, 0x6F, - 0x86, 0xD3, 0x9D, 0xC0, 0x88, 0xF1, 0xA1, 0x52, 0xCA, 0x1C, 0x22, 0x69, 0xA6, 0xA2, 0x91, 0xA2, - 0x62, 0x4C, 0x0F, 0x16, 0x14, 0x82, 0x39, 0x6D, 0x46, 0xB4, 0xF7, 0xF0, 0xCA, 0x9C, 0x75, 0xC3, - 0xE4, 0xB6, 0x67, 0x20, 0x62, 0x5D, 0x61, 0x60, 0xCF, 0x10, 0x01, 0x91, 0x93, 0xA0, 0x43, 0x47, - 0x13, 0x49, 0xB5, 0x30, 0x4C, 0xDA, 0x40, 0x44, 0xBA, 0x70, 0x7F, 0xE1, 0x79, 0x64, 0x62, 0xB9, - 0xF7, 0x32, 0x69, 0x12, 0xF0, 0x36, 0x36, 0xAA, 0x28, 0x0F, 0xAA, 0x9F, 0xE3, 0x8E, 0xA4, 0xEC, - 0x25, 0x86, 0x33, 0xCE, 0x74, 0xA5, 0x2A, 0x1B, 0x97, 0x6E, 0x02, 0x96, 0x79, 0x5C, 0x9D, 0xAE, - 0x53, 0xF9, 0x2C, 0x90, 0x05, 0x34, 0xE6, 0xB6, 0x23, 0x5D, 0xA7, 0x4A, 0x33, 0xBC, 0xD0, 0x8A, - 0xC0, 0xB2, 0xCE, 0x34, 0x53, 0x55, 0x2A, 0x36, 0x42, 0x44, 0x10, 0x1C, 0x43, 0x57, 0x27, 0x31, - 0x07, 0xA6, 0x93, 0x7D, 0x63, 0xC5, 0x4E, 0xE0, 0x0F, 0xAB, 0x9F, 0xCB, 0xA7, 0x32, 0x5D, 0x98, - 0x4E, 0xD0, 0x4D, 0xE9, 0x30, 0xAA, 0x1D, 0x4D, 0x88, 0x44, 0x53, 0xA6, 0xC3, 0x89, 0x38, 0x05, - 0x55, 0x0D, 0x8C, 0x1E, 0xC6, 0xD9, 0x98, 0x51, 0xF2, 0x1C, 0x56, 0xD5, 0x02, 0x2B, 0xE8, 0x95, - 0xE7, 0x85, 0x27, 0x8E, 0x3B, 0x0B, 0x69, 0x90, 0x7F, 0x14, 0x97, 0x71, 0xE4, 0xA5, 0x61, 0xE8, - 0xF0, 0x69, 0xC7, 0xB0, 0x61, 0xAE, 0xDE, 0xDD, 0xC0, 0xA0, 0x4A, 0x11, 0x1B, 0xD1, 0x11, 0x53, - 0x92, 0x31, 0xDB, 0x82, 0x74, 0x01, 0x34, 0x86, 0x9B, 0xE0, 0x9D, 0x39, 0x06, 0x7C, 0x48, 0x31, - 0x80, 0x39, 0x28, 0x71, 0x69, 0xB6, 0x1A, 0xF3, 0xDD, 0xEA, 0xAC, 0x8B, 0x04, 0x4B, 0x19, 0x77, - 0x22, 0xEA, 0xF0, 0xFE, 0x20, 0x67, 0x11, 0x39, 0xE0, 0xF9, 0x5B, 0x60, 0x2A, 0x8E, 0xD2, 0xA6, - 0x4C, 0x70, 0x66, 0xC6, 0x64, 0x71, 0x78, 0xAD, 0x08, 0xC6, 0x46, 0x50, 0xD0, 0xD5, 0x96, 0x1B, - 0x52, 0xD7, 0xB5, 0xDE, 0x53, 0x67, 0x74, 0x13, 0x5E, 0x4E, 0x26, 0xE6, 0x71, 0xA6, 0xD5, 0x52, - 0x3B, 0x94, 0xAC, 0x63, 0x5C, 0x3A, 0x4B, 0x1A, 0x63, 0x66, 0x92, 0x39, 0x6C, 0x30, 0xB7, 0xD3, - 0x00, 0xAA, 0xF2, 0x0B, 0x35, 0x57, 0xB3, 0x30, 0x94, 0x43, 0xAB, 0xCB, 0xA5, 0x14, 0x09, 0xFD, - 0x7B, 0x73, 0x97, 0xE1, 0xD1, 0x73, 0x9E, 0xBE, 0x3C, 0x80, 0xAF, 0x13, 0x0E, 0x6F, 0x48, 0x8B, - 0xE2, 0x06, 0x81, 0x79, 0xF2, 0x93, 0xB3, 0xD0, 0x04, 0x01, 0x34, 0x8E, 0xC5, 0xC6, 0x1F, 0x1F, - 0x46, 0x19, 0x5D, 0xAA, 0x04, 0xA5, 0xE7, 0xA7, 0x5C, 0x29, 0xD9, 0x0C, 0x83, 0xEE, 0x2C, 0xB9, - 0x1D, 0x52, 0xA7, 0xED, 0x8F, 0xC7, 0xC4, 0xA7, 0xA3, 0xD9, 0x18, 0x33, 0xE3, 0x30, 0x2F, 0x96, - 0x77, 0x60, 0xEC, 0x43, 0x6A, 0x7D, 0x57, 0xA1, 0x85, 0x99, 0xB6, 0xE0, 0x11, 0x7A, 0xE9, 0xC1, - 0x94, 0x74, 0x28, 0xBA, 0xE5, 0x4C, 0x30, 0x02, 0x87, 0x26, 0x37, 0x8E, 0xD8, 0xD9, 0x3D, 0x0F, - 0x41, 0x3A, 0xB8, 0xB1, 0xDC, 0x11, 0x15, 0xEB, 0x41, 0x2D, 0xC2, 0xA0, 0xD9, 0xDC, 0x7D, 0x96, - 0x98, 0x0E, 0x18, 0xC3, 0x22, 0x6C, 0xE9, 0x24, 0x18, 0x19, 0x6A, 0x0A, 0x06, 0x0E, 0x3D, 0xF4, - 0xBD, 0x67, 0x3C, 0xA1, 0x0C, 0xA8, 0x0C, 0xDE, 0xAC, 0x00, 0x97, 0xDC, 0xC7, 0xC8, 0x2C, 0x70, - 0x87, 0xC9, 0x1D, 0xB8, 0xDD, 0xC9, 0x7C, 0x03, 0x5D, 0x70, 0xED, 0x3D, 0x8B, 0x6C, 0xAF, 0x9E, - 0x8E, 0x86, 0x6B, 0xBC, 0x2C, 0xAF, 0xCC, 0x44, 0x6F, 0x49, 0x07, 0xBF, 0xF5, 0xC0, 0x47, 0x82, - 0xBE, 0xE3, 0xD7, 0x33, 0x38, 0x23, 0x49, 0x9F, 0x8E, 0xD2, 0xB7, 0xCC, 0xDF, 0xFF, 0x0E, 0xAD, - 0xB1, 0x9D, 0x60, 0x8A, 0x7D, 0xC4, 0x32, 0x4B, 0x62, 0x60, 0x2C, 0x39, 0x98, 0x05, 0x60, 0xBF, - 0xF8, 0xDF, 0xB0, 0x8C, 0x40, 0x31, 0x35, 0x57, 0xA4, 0x5E, 0x30, 0x60, 0x1A, 0xD0, 0x61, 0x6D, - 0x54, 0x78, 0x4F, 0x06, 0x27, 0xDE, 0x81, 0xB8, 0xA1, 0x3E, 0x37, 0xA2, 0xB7, 0x38, 0x49, 0xD6, - 0xC6, 0xC2, 0xA6, 0xD8, 0x37, 0xEC, 0x46, 0x9E, 0x9C, 0x14, 0x8B, 0x87, 0x63, 0x2C, 0x16, 0xE7, - 0x3B, 0xEA, 0xD5, 0x46, 0xC9, 0x67, 0x9A, 0xD3, 0xD0, 0x77, 0xA6, 0x03, 0x0A, 0x82, 0xF3, 0x17, - 0x81, 0xE9, 0x60, 0xEC, 0xB0, 0xF8, 0xCE, 0x9A, 0x98, 0xF0, 0x96, 0x14, 0xDE, 0x44, 0xC1, 0xA1, - 0xE5, 0x62, 0xEC, 0xF8, 0x5C, 0x1C, 0x9D, 0x79, 0x8E, 0x1B, 0x9E, 0x51, 0xFF, 0x1A, 0x16, 0x01, - 0xD2, 0x72, 0x67, 0x4E, 0xAC, 0x47, 0xD1, 0xAD, 0xC5, 0x19, 0xB3, 0xE3, 0x0B, 0xC1, 0xF5, 0x5E, - 0xC4, 0x5D, 0xF3, 0xE4, 0xAC, 0xDE, 0x68, 0xC4, 0x62, 0xA4, 0xE7, 0x43, 0x1D, 0x4C, 0x4F, 0xBD, - 0xBB, 0xDA, 0x48, 0xC4, 0xEC, 0x7E, 0x76, 0x83, 0x3A, 0x47, 0xF1, 0xB8, 0x86, 0x39, 0x02, 0x73, - 0xB1, 0xB4, 0x98, 0x86, 0x09, 0x2C, 0xE7, 0xC7, 0xA7, 0x47, 0xBF, 0xCE, 0x89, 0x6A, 0xFF, 0xFC, - 0x6C, 0x31, 0x3C, 0xA1, 0xBF, 0xF6, 0x36, 0x0E, 0xDA, 0xB4, 0xD9, 0xC1, 0xE6, 0xDC, 0x08, 0x23, - 0xCF, 0xB1, 0x1E, 0x9E, 0xAC, 0x24, 0x1C, 0x95, 0x10, 0xC6, 0x18, 0xF9, 0x5C, 0x80, 0x26, 0x0C, - 0xD3, 0xAC, 0xCA, 0x4B, 0x43, 0x5E, 0x12, 0x85, 0x7B, 0xE9, 0xA5, 0xE1, 0x50, 0x98, 0x84, 0x37, - 0xE8, 0x29, 0x68, 0x25, 0xDC, 0xEC, 0xA4, 0x4A, 0x66, 0xF6, 0x34, 0xF5, 0xCD, 0xBE, 0x19, 0x4E, - 0x8F, 0xC4, 0xF5, 0xE4, 0x34, 0x75, 0x58, 0x22, 0xE1, 0x92, 0x26, 0x5D, 0x70, 0x90, 0xDA, 0xFF, - 0x71, 0x12, 0xBC, 0x0F, 0x4A, 0x92, 0x60, 0xE6, 0x7E, 0x83, 0xAF, 0x20, 0x66, 0x4C, 0xE1, 0x97, - 0x04, 0xE1, 0x3D, 0x78, 0x04, 0x28, 0xB0, 0x31, 0xCB, 0xC0, 0xDC, 0x70, 0x3D, 0x97, 0x36, 0xD4, - 0xF4, 0xC2, 0x6C, 0xD1, 0x51, 0x0C, 0x79, 0x05, 0x1E, 0xF8, 0x37, 0x0E, 0x0A, 0xFE, 0xAF, 0x4F, - 0x71, 0x12, 0x3E, 0x18, 0xFC, 0x8C, 0x77, 0x3A, 0x71, 0xA2, 0x17, 0xB7, 0x0E, 0x59, 0x4A, 0x41, - 0x09, 0xB7, 0x48, 0xB7, 0x1A, 0xA7, 0x53, 0xE6, 0x39, 0x58, 0x59, 0x55, 0x04, 0x96, 0xB6, 0x64, - 0xD7, 0xD7, 0x8F, 0x13, 0x9F, 0x2C, 0xAE, 0x3B, 0x1C, 0x07, 0x22, 0xBF, 0x31, 0xA6, 0x3C, 0x8D, - 0xB3, 0xDE, 0xFD, 0xC7, 0x0C, 0x96, 0xC8, 0x3C, 0xA1, 0xAF, 0xE7, 0x83, 0x5F, 0xD7, 0x6A, 0x74, - 0xE4, 0x63, 0xBD, 0x36, 0xE9, 0x28, 0x67, 0x18, 0x8D, 0x9C, 0x54, 0x8A, 0x12, 0x05, 0xB1, 0xC1, - 0x04, 0xDF, 0x57, 0x57, 0x65, 0xD7, 0x43, 0x62, 0x78, 0x75, 0x4F, 0x06, 0xF8, 0xF2, 0xFD, 0x6B, - 0x47, 0xE4, 0x45, 0x64, 0x21, 0xDE, 0x6A, 0x09, 0x77, 0x0A, 0x59, 0x61, 0xD4, 0x63, 0xCF, 0x52, - 0xCB, 0x87, 0xB6, 0x61, 0xF5, 0xB0, 0xBE, 0x7E, 0x4E, 0x27, 0xDE, 0x2D, 0x65, 0x51, 0x6A, 0xB8, - 0x17, 0x84, 0xC1, 0x6C, 0xEC, 0x0F, 0x5E, 0x5E, 0x55, 0x28, 0xC9, 0xFE, 0xEC, 0x4B, 0xD7, 0x0B, - 0x5B, 0x1D, 0x3D, 0x44, 0x6E, 0x25, 0x92, 0x97, 0x2F, 0xE7, 0xAB, 0x79, 0x5C, 0x61, 0x45, 0x6B, - 0x84, 0xB4, 0xB8, 0x4C, 0x7C, 0xC8, 0x51, 0xA0, 0xA5, 0xF8, 0x50, 0x82, 0x4A, 0x81, 0x08, 0x26, - 0x46, 0x6C, 0xB6, 0x9B, 0xF0, 0xAF, 0x84, 0x0A, 0xF8, 0xA8, 0x46, 0x3F, 0x8E, 0x12, 0xAD, 0xC4, - 0x43, 0x1C, 0x7C, 0x6A, 0xE2, 0x23, 0x2A, 0x2C, 0xCF, 0x4B, 0xEE, 0x66, 0x69, 0x11, 0x4B, 0x86, - 0x1D, 0x66, 0x16, 0x82, 0x65, 0x46, 0xCA, 0x99, 0xE2, 0xFC, 0xE6, 0x6C, 0x8C, 0x9A, 0x79, 0xD7, - 0xD6, 0x27, 0x2E, 0xB8, 0x81, 0x23, 0xBE, 0xC4, 0x49, 0x78, 0x4A, 0x25, 0x53, 0x47, 0x03, 0xD2, - 0x52, 0xCA, 0x9F, 0x45, 0x41, 0x96, 0x52, 0x1A, 0x67, 0x10, 0x46, 0x92, 0xE8, 0xB9, 0x15, 0x15, - 0xB6, 0x49, 0x1F, 0xD6, 0xF0, 0x2B, 0x69, 0x6B, 0xC7, 0x8E, 0xD4, 0xC1, 0xCA, 0xB5, 0x09, 0x5B, - 0x95, 0xE1, 0x99, 0x80, 0x62, 0xFD, 0xB0, 0x8D, 0xFC, 0xD8, 0x5D, 0x5B, 0xEB, 0x60, 0x58, 0x33, - 0x2B, 0x78, 0x49, 0x50, 0x04, 0x31, 0x34, 0x92, 0x88, 0x29, 0x0C, 0xC7, 0xD4, 0xF2, 0x23, 0x12, - 0xE5, 0xF0, 0x36, 0xD3, 0x3C, 0x0E, 0x66, 0xC3, 0x21, 0x2C, 0xCD, 0x18, 0x97, 0x52, 0x6E, 0xD9, - 0x18, 0x8F, 0x28, 0x37, 0x71, 0xD8, 0xE4, 0xE9, 0x65, 0xD3, 0x4C, 0x25, 0x38, 0xCB, 0xA2, 0x33, - 0x30, 0x06, 0x4B, 0xC0, 0x88, 0x29, 0x21, 0xBF, 0x68, 0x2D, 0x16, 0x67, 0xD9, 0xBF, 0x16, 0xCB, - 0x5D, 0xD3, 0x32, 0x2E, 0xBD, 0x8C, 0x8E, 0x36, 0x09, 0xF0, 0x44, 0x09, 0xE7, 0xDF, 0x8E, 0xCF, - 0x8C, 0x5F, 0x4B, 0xC4, 0x3F, 0xC4, 0x9C, 0xED, 0xE6, 0x43, 0xE1, 0xAD, 0x46, 0x25, 0x64, 0xC2, - 0xB8, 0x39, 0xCB, 0x57, 0xE3, 0x7F, 0x57, 0x96, 0xCE, 0xDA, 0x62, 0xBF, 0x32, 0x87, 0x29, 0x72, - 0x45, 0xFC, 0x19, 0x9A, 0x94, 0xCF, 0xA1, 0xE1, 0x26, 0x42, 0x66, 0x4F, 0x8B, 0x3E, 0x4A, 0x74, - 0x5C, 0xD2, 0x1E, 0xB9, 0xFF, 0x1A, 0x42, 0x91, 0x45, 0x32, 0xE2, 0xF8, 0xD2, 0x91, 0xAE, 0x34, - 0x52, 0x7F, 0x6B, 0x2E, 0x82, 0x41, 0x53, 0x58, 0xE2, 0x7E, 0xC4, 0xCB, 0x6E, 0x3C, 0xF0, 0xFD, - 0x45, 0x09, 0x1F, 0xDA, 0x78, 0xF9, 0x00, 0xFA, 0xD3, 0x9B, 0x5F, 0x25, 0x65, 0x64, 0xC5, 0x62, - 0x4E, 0x60, 0xC5, 0x8C, 0x6E, 0x17, 0x86, 0xF1, 0xD6, 0x16, 0xF0, 0x7B, 0x02, 0x4E, 0x1E, 0xA6, - 0x7C, 0xB8, 0xA2, 0xE1, 0x1D, 0x1E, 0xDA, 0x75, 0xD9, 0xC6, 0x0D, 0x94, 0x35, 0xDA, 0x68, 0xC7, - 0xC6, 0x63, 0x6B, 0x1A, 0x50, 0x4C, 0xA3, 0xC4, 0xF3, 0xB6, 0xB0, 0xAD, 0x0A, 0x55, 0x7D, 0x4D, - 0xF4, 0x71, 0x51, 0xBA, 0x4C, 0x1E, 0x10, 0x7F, 0x31, 0x1F, 0x98, 0x88, 0xA5, 0x0C, 0x17, 0xDB, - 0xD9, 0x4C, 0x6C, 0xCF, 0x23, 0x07, 0xA4, 0x5F, 0x56, 0x12, 0xF5, 0x78, 0x30, 0xCA, 0x41, 0x54, - 0x1C, 0xF0, 0x79, 0x04, 0x0C, 0xF7, 0x90, 0x5F, 0x49, 0x1E, 0xC2, 0x3A, 0x44, 0x36, 0xC9, 0xA2, - 0xC0, 0x34, 0x04, 0x51, 0xA5, 0xA3, 0x81, 0x04, 0x35, 0x39, 0xA4, 0x69, 0xA8, 0xC2, 0xF4, 0xB3, - 0xC6, 0x4A, 0xD7, 0xD0, 0x23, 0xAC, 0x08, 0x32, 0x9B, 0xE6, 0x01, 0xF0, 0xA1, 0x9D, 0x26, 0x20, - 0xB7, 0x15, 0x9C, 0x41, 0x07, 0x5D, 0xFA, 0xB7, 0x0E, 0x1D, 0xDB, 0x41, 0x2B, 0xC9, 0x15, 0x7E, - 0x20, 0x84, 0x20, 0xBC, 0xE8, 0x61, 0x92, 0x9F, 0x43, 0x17, 0x4F, 0x2C, 0x58, 0xB1, 0x19, 0x27, - 0xF2, 0x13, 0x81, 0xC4, 0xA3, 0x1D, 0x3D, 0xA4, 0x1E, 0xB9, 0x6F, 0x99, 0xD0, 0x49, 0xB7, 0x20, - 0x28, 0xDE, 0x84, 0xAE, 0x0A, 0x87, 0x2A, 0x25, 0x40, 0xE1, 0xD7, 0x72, 0xD0, 0x6F, 0x92, 0x14, - 0x4F, 0x00, 0xCA, 0x12, 0x3E, 0x55, 0xA7, 0xCA, 0x94, 0x48, 0x80, 0x97, 0xA6, 0x7C, 0x76, 0x26, - 0x09, 0x4A, 0xDE, 0x52, 0x29, 0x07, 0x2D, 0x65, 0x82, 0x62, 0x08, 0xF0, 0xCF, 0x52, 0x90, 0xBF, - 0x38, 0x6F, 0x9D, 0x04, 0xF0, 0xCE, 0xB9, 0x76, 0xCA, 0xC1, 0x5D, 0x1C, 0x9C, 0x7D, 0x3A, 0x94, - 0x78, 0x86, 0x95, 0xE6, 0x27, 0x7B, 0x5A, 0x0E, 0x96, 0xDD, 0x3A, 0x4D, 0x40, 0x99, 0x3F, 0x5F, - 0x0E, 0x32, 0xBD, 0xD4, 0xBC, 0x4F, 0xF0, 0x48, 0xBE, 0x5E, 0x39, 0x6C, 0x83, 0xFB, 0x20, 0xA4, - 0x93, 0x04, 0x41, 0xC0, 0xFF, 0x2E, 0x05, 0x7B, 0xA4, 0xA6, 0xF1, 0x02, 0xE8, 0x38, 0xB1, 0x57, - 0x29, 0xF8, 0xD3, 0x0B, 0x49, 0x78, 0x98, 0xEB, 0xAB, 0x14, 0xD4, 0x5B, 0x29, 0xA1, 0x3B, 0xC0, - 0xC9, 0x4B, 0xA4, 0x08, 0x9E, 0x3B, 0x0B, 0xFA, 0xEC, 0xC6, 0x47, 0xB1, 0x18, 0x92, 0x84, 0x53, - 0x4E, 0x19, 0x50, 0x9E, 0x23, 0x21, 0xB9, 0x71, 0x8C, 0x87, 0xBA, 0x6D, 0x7C, 0x67, 0x63, 0x73, - 0x47, 0x32, 0xA6, 0x2C, 0x1B, 0xDC, 0xE6, 0x0E, 0x40, 0x5A, 0xBE, 0x35, 0xC4, 0x93, 0x2E, 0xD9, - 0x9C, 0xAA, 0xA3, 0x7E, 0x25, 0x26, 0xCD, 0x32, 0x07, 0x66, 0xD0, 0xE5, 0x86, 0xDB, 0xB0, 0xED, - 0xDD, 0xC6, 0x17, 0x11, 0xBA, 0xDD, 0x5E, 0xBF, 0xCD, 0x9E, 0x74, 0x48, 0xDB, 0x73, 0x5E, 0xCA, - 0xAC, 0x7A, 0xAF, 0xCB, 0x00, 0x4C, 0x06, 0x24, 0x92, 0x2A, 0x92, 0x3C, 0x90, 0xD3, 0x8E, 0xB1, - 0x5D, 0x8A, 0x4C, 0x76, 0x1C, 0x17, 0x3E, 0xDC, 0x36, 0xD8, 0x74, 0xB2, 0xD3, 0xCD, 0x9C, 0x4E, - 0x76, 0xBA, 0x25, 0xC8, 0xA6, 0x50, 0x1F, 0x9C, 0x7E, 0x9C, 0x0B, 0x73, 0xEC, 0xC3, 0x66, 0xED, - 0x0D, 0xC7, 0xC7, 0x59, 0x69, 0xEF, 0xD6, 0xD4, 0xE7, 0x6E, 0x02, 0x7C, 0x60, 0x81, 0x84, 0xFC, - 0xF7, 0x5E, 0x10, 0x8A, 0xDE, 0xDF, 0x4A, 0xF5, 0xFE, 0x56, 0x46, 0xEF, 0xA7, 0x5B, 0x9F, 0x21, - 0x81, 0x14, 0x39, 0xB4, 0x60, 0x9C, 0xDC, 0x0E, 0xFE, 0xE8, 0x14, 0xD9, 0xC7, 0x6A, 0x94, 0x0C, - 0x2D, 0x63, 0xEF, 0x39, 0xB0, 0x0D, 0x6B, 0x4E, 0x6A, 0xA3, 0xAB, 0xD3, 0xD9, 0xE8, 0xD6, 0x6E, - 0x19, 0x6F, 0xC7, 0xA7, 0x80, 0xFA, 0xD9, 0xD2, 0x4C, 0x17, 0x60, 0x7D, 0x66, 0x04, 0xC3, 0x6F, - 0xFD, 0x91, 0xD7, 0x19, 0x7A, 0x13, 0xFC, 0x0B, 0xBF, 0x92, 0x09, 0x32, 0x86, 0x99, 0xEB, 0xC0, - 0xB0, 0x38, 0xE0, 0xB1, 0x4E, 0x2C, 0x67, 0x8C, 0x41, 0x60, 0x3E, 0xF8, 0x44, 0xF9, 0xAC, 0x45, - 0x87, 0x6D, 0x84, 0x1F, 0x76, 0x61, 0x48, 0xE3, 0x1D, 0xD8, 0x4A, 0x71, 0x66, 0x87, 0x61, 0x69, - 0x22, 0xD7, 0x59, 0x40, 0xC0, 0x66, 0xD1, 0x0E, 0xE1, 0xF4, 0xAC, 0x7B, 0x72, 0x67, 0x81, 0xC9, - 0x00, 0x39, 0xD8, 0x4E, 0xC0, 0xE2, 0xF0, 0x4E, 0x2F, 0xCE, 0x8F, 0xCF, 0x70, 0xDB, 0x87, 0x85, - 0xA2, 0x8D, 0x3D, 0xCE, 0x0D, 0xA1, 0xDF, 0xC1, 0xA3, 0xC0, 0x8D, 0xB4, 0x28, 0x8D, 0x57, 0x27, - 0xA2, 0x28, 0x2D, 0x53, 0xE0, 0x2F, 0xBE, 0x0F, 0x84, 0x2B, 0x4D, 0x2E, 0xA3, 0x1C, 0xC9, 0x48, - 0x02, 0x10, 0xAD, 0x28, 0x07, 0x2F, 0x14, 0xA7, 0xDF, 0xEB, 0xF6, 0x4A, 0xC2, 0x29, 0x5A, 0xD0, - 0xB8, 0x1A, 0xDB, 0xFE, 0xE5, 0x60, 0x6A, 0xF9, 0xDF, 0xDE, 0xCE, 0xDC, 0x5E, 0xA3, 0x32, 0x8E, - 0xB3, 0x5F, 0xAA, 0xF1, 0x1B, 0x75, 0x78, 0x48, 0x83, 0xF0, 0xFF, 0xE0, 0x3F, 0xD5, 0x9B, 0x8C, - 0x28, 0x80, 0x2C, 0x20, 0xD1, 0xE0, 0x46, 0xEA, 0xC8, 0xBA, 0x10, 0x19, 0xE4, 0xDE, 0xBD, 0xDB, - 0xCF, 0x08, 0xEB, 0x00, 0x38, 0x6D, 0xBB, 0x0F, 0xFD, 0x3B, 0x58, 0x70, 0xC4, 0xF9, 0x53, 0x93, - 0x8D, 0xCE, 0xF5, 0xF5, 0x09, 0x5B, 0xF4, 0x61, 0x0D, 0xC7, 0x0E, 0x48, 0x10, 0x5A, 0x3E, 0xD3, - 0x01, 0xA6, 0x54, 0xDA, 0xD5, 0xB7, 0x67, 0x3F, 0x28, 0x65, 0x1B, 0x2E, 0xED, 0x12, 0x56, 0xF2, - 0x37, 0x1E, 0x9B, 0x3C, 0x1F, 0x86, 0x63, 0xFF, 0xEB, 0x9E, 0xC6, 0xFD, 0x57, 0x79, 0xD1, 0x6B, - 0xDA, 0xCD, 0x92, 0x48, 0x64, 0x6D, 0x61, 0x99, 0x43, 0x80, 0x25, 0x40, 0xBE, 0xD3, 0xB8, 0x9B, - 0x0E, 0x74, 0xD3, 0xD7, 0x9E, 0xE6, 0x00, 0x4E, 0x39, 0xB9, 0x51, 0xF9, 0x8E, 0x61, 0xE1, 0xCA, - 0x8B, 0x95, 0x16, 0x43, 0xF9, 0xA4, 0x24, 0xC6, 0xD6, 0x88, 0x79, 0x52, 0x33, 0xE9, 0x3C, 0x2E, - 0x2E, 0x0D, 0x5A, 0x5F, 0x2D, 0x61, 0xAC, 0xC4, 0x7E, 0x69, 0x69, 0xEA, 0x57, 0xEC, 0x0A, 0x45, - 0x39, 0xA9, 0x25, 0xC7, 0x49, 0x25, 0x21, 0xB2, 0xA5, 0x7E, 0x91, 0xE2, 0xCD, 0xD9, 0xDA, 0x24, - 0xDC, 0xFE, 0x7F, 0x54, 0x93, 0xCB, 0x8D, 0x97, 0xC7, 0x6A, 0x71, 0xD5, 0xB1, 0xA2, 0x3E, 0x6B, - 0xC1, 0x73, 0x92, 0x46, 0x17, 0x0D, 0x8B, 0x47, 0x4C, 0xCE, 0xA3, 0x18, 0x73, 0x48, 0x36, 0x75, - 0x3F, 0xF4, 0x47, 0x49, 0x57, 0xF1, 0x71, 0x25, 0x58, 0x5C, 0xC7, 0x80, 0xBB, 0x89, 0x29, 0xB4, - 0xCD, 0xCB, 0x98, 0x1E, 0xDF, 0x98, 0xC2, 0xF2, 0xDC, 0xFD, 0xB1, 0x85, 0x2A, 0xA4, 0x7E, 0x4B, - 0xF6, 0x9F, 0x52, 0x6A, 0xD1, 0x8E, 0x5E, 0x86, 0x52, 0xB3, 0xDC, 0xB7, 0xD2, 0xB2, 0x34, 0x5A, - 0x51, 0x19, 0xA2, 0x9D, 0x4A, 0x2E, 0xA8, 0xC4, 0x02, 0xC7, 0xBB, 0x0A, 0x50, 0xB8, 0x2C, 0x41, - 0x36, 0x1D, 0x7A, 0xAE, 0x8D, 0x2E, 0xFB, 0x76, 0x97, 0x35, 0xC7, 0xD0, 0x98, 0xED, 0xAE, 0xC8, - 0xB1, 0x2E, 0x37, 0xE4, 0x8D, 0x31, 0xE1, 0x78, 0x3E, 0xB1, 0x33, 0x2F, 0x70, 0xF0, 0xFF, 0xFB, - 0xC3, 0xE1, 0x0C, 0x96, 0x31, 0xF7, 0x7C, 0xA1, 0xB3, 0x85, 0x82, 0x4C, 0x11, 0xED, 0x75, 0x18, - 0xD5, 0xAD, 0x4E, 0x0E, 0xD5, 0x84, 0xAC, 0xE2, 0x93, 0xB2, 0x50, 0xA9, 0x23, 0x96, 0x8A, 0xA9, - 0xAD, 0x3C, 0x98, 0x2B, 0x33, 0x6A, 0x86, 0xF8, 0x8C, 0x10, 0xD2, 0x43, 0xBA, 0xC5, 0x10, 0xBF, - 0x01, 0x84, 0xF4, 0xC0, 0x6E, 0x3E, 0xC0, 0x07, 0x2B, 0x64, 0xB9, 0xA1, 0xDB, 0xEC, 0x3D, 0xE0, - 0x9D, 0x6E, 0x7F, 0x67, 0xF3, 0xF9, 0x4E, 0x01, 0x88, 0xE7, 0x8E, 0x04, 0x0C, 0x7F, 0x2A, 0xF8, - 0xC5, 0x56, 0x77, 0xEB, 0xF9, 0x76, 0x2F, 0x1F, 0x6A, 0x7F, 0x1C, 0x3A, 0xE1, 0xCC, 0x66, 0x5B, - 0x24, 0x5B, 0xDB, 0x40, 0xEB, 0xC5, 0x8E, 0xB2, 0xFC, 0xD2, 0x0E, 0x6C, 0x54, 0x0C, 0x19, 0xEA, - 0x91, 0x49, 0x31, 0xBF, 0x87, 0xA1, 0x07, 0xE5, 0xAE, 0x8A, 0x94, 0x38, 0x3F, 0x46, 0x2E, 0x47, - 0x9F, 0x6B, 0x8A, 0xB6, 0xBE, 0x78, 0x2B, 0x89, 0xB8, 0xC0, 0x82, 0x68, 0xBA, 0xF9, 0xBC, 0xCB, - 0x7E, 0xDA, 0x24, 0xFE, 0x25, 0x1E, 0x07, 0x51, 0x19, 0x0E, 0x02, 0xF1, 0x6B, 0xF1, 0xF0, 0x2B, - 0x22, 0xFA, 0xF9, 0x47, 0x10, 0xFD, 0x6D, 0x19, 0x44, 0x1F, 0xF2, 0x02, 0x8D, 0xAB, 0x9B, 0x83, - 0x7A, 0x26, 0xA1, 0x92, 0x59, 0x48, 0xCB, 0x2B, 0x09, 0x5A, 0x02, 0x63, 0xFF, 0x33, 0xF5, 0x31, - 0xF5, 0x38, 0x7C, 0x5B, 0x67, 0x97, 0x33, 0x92, 0xEB, 0xF7, 0xA5, 0x04, 0x2D, 0x69, 0xE3, 0x5A, - 0xAF, 0xD7, 0xDD, 0xD8, 0x6C, 0x93, 0x17, 0x2F, 0x94, 0xAD, 0x51, 0xFE, 0x19, 0x85, 0x8C, 0x05, - 0x25, 0x8C, 0x6A, 0x06, 0x39, 0x3D, 0xEA, 0x1F, 0x09, 0x62, 0xFA, 0xF9, 0x2D, 0x85, 0xD6, 0x16, - 0x4B, 0xE4, 0xBE, 0x55, 0x5B, 0x7B, 0xCC, 0xD1, 0x87, 0x40, 0xAB, 0xCF, 0x67, 0xDC, 0xBE, 0x36, - 0xF1, 0xF2, 0xEF, 0x3C, 0xDB, 0x7C, 0xB7, 0x53, 0x5A, 0x81, 0x1E, 0x32, 0xF7, 0x2A, 0xE3, 0x88, - 0xD8, 0x3A, 0x7B, 0x95, 0x1C, 0x38, 0xD9, 0x43, 0xBA, 0xEC, 0x96, 0xDE, 0xAB, 0x96, 0xE1, 0x19, - 0xD3, 0xDD, 0x32, 0xBB, 0x95, 0x32, 0x41, 0xDC, 0x74, 0x8A, 0x08, 0xE6, 0xEF, 0x57, 0x96, 0xA7, - 0x95, 0xD9, 0x3A, 0xDC, 0xEE, 0x61, 0xC4, 0xBA, 0x5A, 0xEB, 0xBA, 0x73, 0xB6, 0x2E, 0x97, 0xE2, - 0xD9, 0x2F, 0x8F, 0x45, 0x33, 0xD9, 0x4B, 0x5B, 0x4A, 0x1F, 0xE6, 0x52, 0x7C, 0xBC, 0x56, 0x4A, - 0x9A, 0xDA, 0xAB, 0x4F, 0xB1, 0x57, 0x4B, 0x53, 0x05, 0x41, 0x5D, 0x53, 0xBB, 0xF9, 0x9A, 0xDA, - 0xAB, 0xA7, 0xA9, 0xCB, 0x68, 0x5D, 0x91, 0xA6, 0x3E, 0x12, 0x4D, 0x49, 0x53, 0x1F, 0x9D, 0xE2, - 0xE3, 0xB5, 0x52, 0xD2, 0xD4, 0x7E, 0x7D, 0x8A, 0xFD, 0x5A, 0x9A, 0xDA, 0xAF, 0xA5, 0xA9, 0xFD, - 0x7A, 0x9A, 0xBA, 0x8C, 0xD6, 0x15, 0x69, 0xEA, 0x23, 0xD1, 0x94, 0x34, 0xF5, 0xD1, 0x29, 0x3E, - 0x5E, 0x2B, 0x25, 0x4D, 0xDD, 0xA8, 0x4F, 0x71, 0xA3, 0x96, 0xA6, 0x6E, 0xD4, 0xD2, 0xD4, 0x8D, - 0x7A, 0x9A, 0xBA, 0x8C, 0xD6, 0x15, 0x69, 0xEA, 0x23, 0xD1, 0x94, 0x34, 0xF5, 0xD1, 0x29, 0x2E, - 0xA8, 0x95, 0x4F, 0xFD, 0xB8, 0xD5, 0xE8, 0x2A, 0x57, 0x3A, 0x70, 0x35, 0xFA, 0xBE, 0xA5, 0x8E, - 0x5C, 0x8D, 0x8E, 0x6C, 0xD5, 0xC3, 0xCF, 0x0C, 0xDF, 0xB4, 0x51, 0x16, 0x52, 0xF3, 0x30, 0xF9, - 0x99, 0xAF, 0x7D, 0xE7, 0x86, 0x77, 0x6E, 0xBF, 0x3A, 0x8E, 0x88, 0xFA, 0x2F, 0xE7, 0x5B, 0x77, - 0xE7, 0xDE, 0xE6, 0xFB, 0x6A, 0xFC, 0xC7, 0x1E, 0x60, 0xA3, 0x86, 0xD8, 0x99, 0x07, 0x50, 0x5D, - 0xE6, 0x35, 0xC8, 0x49, 0x8E, 0x55, 0x1D, 0x31, 0xD7, 0x84, 0xAB, 0x4A, 0x51, 0x73, 0x55, 0xEA, - 0x88, 0xB4, 0x5F, 0x4F, 0xA4, 0xFD, 0xDA, 0x22, 0xED, 0xD7, 0x14, 0x69, 0xBF, 0xB6, 0x48, 0xFB, - 0x35, 0x45, 0xBA, 0x51, 0x53, 0xA4, 0x1B, 0xF5, 0x44, 0xBA, 0x51, 0x5B, 0xA4, 0x1B, 0x35, 0x45, - 0xBA, 0x51, 0x5B, 0xA4, 0x3A, 0x64, 0x7C, 0xA0, 0x20, 0x5F, 0x87, 0x35, 0x1D, 0x2C, 0x14, 0x5D, - 0x9A, 0xCD, 0xD9, 0x0A, 0x89, 0xAE, 0xEF, 0xB3, 0xDB, 0xF1, 0x12, 0x82, 0x43, 0xF6, 0x92, 0x8C, - 0x08, 0xBC, 0xBB, 0xF0, 0xBE, 0x51, 0x37, 0x4A, 0x13, 0xB1, 0xFB, 0x4C, 0xCB, 0x1C, 0x32, 0xA3, - 0x71, 0xEA, 0x6A, 0x3D, 0x9B, 0xBF, 0x31, 0x10, 0x30, 0x9F, 0x4A, 0x9B, 0x6C, 0x6C, 0xF3, 0xFF, - 0xE2, 0xA9, 0x73, 0x63, 0x3B, 0x2B, 0x2E, 0xF0, 0xCC, 0x14, 0xA2, 0x94, 0xBB, 0x47, 0x5E, 0x48, - 0xBD, 0xD1, 0xD0, 0xEF, 0xD3, 0xCE, 0x42, 0xEF, 0xAF, 0xF4, 0xFE, 0x9C, 0xBA, 0xF4, 0xCE, 0x1A, - 0x67, 0xC4, 0xBC, 0xC4, 0x5D, 0x85, 0xA1, 0xAF, 0x45, 0xA1, 0x90, 0x18, 0x10, 0x7B, 0x4A, 0xC3, - 0x3B, 0xCF, 0xFF, 0x76, 0xD9, 0x1D, 0x0C, 0x8E, 0x0F, 0x4B, 0xBB, 0x0B, 0x86, 0x00, 0x5B, 0x43, - 0x40, 0xA0, 0x91, 0xCE, 0x99, 0x15, 0x04, 0xF0, 0x9B, 0xFD, 0x08, 0xB4, 0x7A, 0x8F, 0xD4, 0xA6, - 0xDE, 0x23, 0xB6, 0xA9, 0xFF, 0x48, 0x6D, 0xEA, 0x3F, 0x62, 0x9B, 0x36, 0x1E, 0xA9, 0x4D, 0x1B, - 0x0B, 0x6A, 0x93, 0x18, 0x62, 0x17, 0x07, 0x67, 0xEB, 0x9F, 0x0E, 0xCF, 0xB2, 0x0D, 0xE1, 0xC5, - 0xB0, 0x72, 0xC4, 0xAA, 0x58, 0x95, 0xC5, 0x97, 0x9E, 0x93, 0xC8, 0xD1, 0xED, 0xAD, 0xAD, 0x8D, - 0x54, 0xAC, 0x2A, 0xFB, 0x28, 0x33, 0xAA, 0x44, 0xAE, 0x17, 0xAC, 0x1A, 0x62, 0x22, 0x52, 0x34, - 0x6C, 0xF9, 0x58, 0x68, 0x13, 0xA5, 0x07, 0xB3, 0x10, 0xAA, 0x6E, 0x85, 0x27, 0x42, 0xE0, 0x90, - 0x8B, 0x10, 0x82, 0x89, 0xB5, 0x4F, 0x76, 0x5D, 0xD6, 0x66, 0xF6, 0xC2, 0x59, 0x5B, 0x5F, 0xFF, - 0xE8, 0x92, 0x28, 0xF0, 0xBE, 0x4D, 0xA0, 0x22, 0xE1, 0xBD, 0xC3, 0xE2, 0x00, 0x38, 0x35, 0xF6, - 0xE4, 0x2E, 0x2E, 0x9E, 0x00, 0x3F, 0x6F, 0x82, 0x8D, 0xAF, 0xFB, 0xE2, 0x2A, 0x2A, 0xC0, 0x78, - 0x84, 0xD0, 0x99, 0xD0, 0xE8, 0xEC, 0x7F, 0x06, 0x48, 0x3C, 0x97, 0xA0, 0xE6, 0xB6, 0xB1, 0xC6, - 0xBD, 0x78, 0xAF, 0x37, 0x7A, 0x1C, 0x7A, 0x7D, 0x9D, 0x87, 0x8C, 0x8B, 0x0C, 0x89, 0x27, 0xB3, - 0x90, 0x7E, 0x4F, 0x2B, 0x2E, 0x86, 0xFE, 0x6B, 0xDD, 0x08, 0x9F, 0x6A, 0x33, 0x97, 0x27, 0x0A, - 0xC1, 0x15, 0xBF, 0xB7, 0x90, 0x3D, 0xA8, 0x92, 0x74, 0x0B, 0xD5, 0xBA, 0x6C, 0x62, 0x7D, 0x07, - 0xD0, 0x0B, 0x27, 0x0A, 0xFB, 0xEF, 0x75, 0xB7, 0x7A, 0xFD, 0x6E, 0x2A, 0x46, 0xBA, 0xD7, 0x86, - 0xEF, 0xED, 0xBE, 0x7A, 0x84, 0xA8, 0xDC, 0xA5, 0xC8, 0xDF, 0x4F, 0xE1, 0x64, 0x3E, 0x30, 0x3F, - 0x64, 0x31, 0x84, 0x4A, 0x38, 0x13, 0x4A, 0xDB, 0xB6, 0xBB, 0xE4, 0x2F, 0xA4, 0x9F, 0x1D, 0x48, - 0xA0, 0x71, 0xA8, 0x56, 0x37, 0x9E, 0x67, 0x29, 0x49, 0x2E, 0xAA, 0x49, 0x3D, 0x01, 0x8D, 0x02, - 0x72, 0xC4, 0x68, 0x49, 0x0B, 0x64, 0x7B, 0x21, 0xA2, 0x30, 0x13, 0xEC, 0xE6, 0x36, 0xCF, 0x94, - 0x7A, 0xA3, 0x5A, 0x33, 0x2D, 0x09, 0x05, 0x1B, 0x51, 0x51, 0x9A, 0xB7, 0xE4, 0x10, 0xCD, 0x7C, - 0x8A, 0xB6, 0xB3, 0x80, 0x26, 0xE7, 0x10, 0x2F, 0x6C, 0x77, 0x2A, 0xB7, 0x5C, 0xA9, 0xE6, 0xAA, - 0xA9, 0xEC, 0xF8, 0xCC, 0xD1, 0xDF, 0xD9, 0xDC, 0xD9, 0x7E, 0x9E, 0x9A, 0x3E, 0xF8, 0xE7, 0x05, - 0xB4, 0x52, 0xA7, 0xD9, 0x65, 0xA7, 0xDE, 0xF8, 0xF6, 0x88, 0xD8, 0x5F, 0x8A, 0x1A, 0x2A, 0xEC, - 0x48, 0x64, 0x4A, 0xE3, 0x86, 0x67, 0xC6, 0x6A, 0x9B, 0x9B, 0x7A, 0x7C, 0xB6, 0xCF, 0x6F, 0x23, - 0xB4, 0xE2, 0xCB, 0x4F, 0xC7, 0x67, 0x0D, 0xA9, 0x7D, 0xAE, 0xEB, 0x76, 0xA4, 0xFF, 0xE4, 0x26, - 0x6A, 0xF7, 0xA7, 0x32, 0x2C, 0x86, 0x81, 0xC2, 0xE1, 0xE9, 0x60, 0xD9, 0x24, 0xDE, 0x81, 0x7E, - 0xDF, 0x59, 0xF7, 0xCB, 0x26, 0x33, 0x98, 0x5D, 0xC1, 0xBF, 0x0B, 0xA5, 0x22, 0x94, 0x2F, 0xA2, - 0x70, 0x1A, 0x4E, 0xC5, 0xEC, 0xDB, 0x4D, 0xCF, 0xBE, 0x5D, 0xE3, 0xEC, 0x6B, 0xA6, 0x13, 0xAF, - 0x99, 0x4E, 0x2F, 0xCE, 0xEA, 0x6A, 0x4B, 0xBC, 0x9B, 0x0E, 0x3C, 0x8D, 0xC7, 0x47, 0xDF, 0xA7, - 0x9E, 0xCB, 0xA7, 0xCF, 0x0D, 0x18, 0x1D, 0xF2, 0xA8, 0xD8, 0x60, 0xB6, 0x5F, 0x19, 0x10, 0xC9, - 0x6D, 0xB9, 0xA2, 0xAD, 0xFA, 0x29, 0x70, 0x33, 0x74, 0x58, 0x8E, 0xD7, 0x36, 0x59, 0xC3, 0x0B, - 0x3E, 0x4A, 0x08, 0xC3, 0x06, 0x6B, 0x75, 0xB7, 0x26, 0xEE, 0x73, 0x18, 0x5E, 0x87, 0x74, 0xCC, - 0x54, 0xA3, 0xDB, 0x66, 0xC9, 0x17, 0xB5, 0x40, 0x1B, 0x86, 0xBE, 0xD7, 0x6D, 0xE3, 0x57, 0x2D, - 0xC8, 0xA6, 0x2A, 0x1D, 0x27, 0x98, 0x8A, 0x64, 0xB5, 0xCB, 0x20, 0x96, 0xEC, 0x8B, 0x4F, 0xCF, - 0xE9, 0x35, 0xF5, 0xA9, 0x3B, 0xA4, 0xC7, 0xB6, 0x30, 0x54, 0x29, 0x13, 0xC5, 0x1C, 0xDC, 0x20, - 0x97, 0x80, 0xB4, 0x03, 0xE2, 0xE7, 0xEC, 0x7C, 0xE8, 0x89, 0xBD, 0xAA, 0x59, 0x55, 0x2A, 0x43, - 0xE3, 0x64, 0xFE, 0x86, 0x87, 0x32, 0x72, 0x6C, 0xD1, 0xB4, 0xA9, 0xCB, 0x49, 0xCC, 0x9D, 0x26, - 0x39, 0xC9, 0x17, 0x67, 0x0B, 0xC6, 0x94, 0x4C, 0x5A, 0x76, 0x5E, 0xE6, 0x26, 0x98, 0x44, 0x58, - 0x4B, 0xA9, 0x94, 0x93, 0x3C, 0x25, 0x59, 0x97, 0x08, 0x53, 0xA9, 0x4D, 0x82, 0x0F, 0xCE, 0x35, - 0x15, 0x2E, 0x24, 0x46, 0x0B, 0xF5, 0x65, 0x96, 0x78, 0xB0, 0x50, 0x5F, 0xE1, 0x25, 0xFB, 0x36, - 0xED, 0x4A, 0x9C, 0x27, 0x21, 0xE3, 0x79, 0x59, 0x2D, 0x63, 0x02, 0xE6, 0xB2, 0x12, 0x55, 0xA4, - 0x24, 0x4A, 0xEA, 0x5D, 0x72, 0xE9, 0xCE, 0xA2, 0x7C, 0x3D, 0x75, 0x8F, 0x28, 0xCF, 0x9D, 0x27, - 0x99, 0x4F, 0x9A, 0x98, 0x9E, 0xFC, 0x4D, 0xE8, 0x36, 0x81, 0xE3, 0x33, 0x98, 0xE7, 0x60, 0xE2, - 0x63, 0xD3, 0x1D, 0xCB, 0x05, 0x21, 0x61, 0x58, 0x85, 0x0F, 0xEC, 0xCF, 0x86, 0xEE, 0xCF, 0x45, - 0x99, 0x44, 0x62, 0x4C, 0x52, 0x85, 0xA5, 0x3E, 0x87, 0xFB, 0xA0, 0x06, 0xC2, 0x4B, 0xCC, 0xBE, - 0x5E, 0x58, 0x6B, 0x83, 0x27, 0xD9, 0xDC, 0xBF, 0x2B, 0xFB, 0x71, 0x65, 0x72, 0x78, 0x7E, 0xF0, - 0x2C, 0xCC, 0xB1, 0xD3, 0x21, 0xA2, 0xD5, 0x77, 0x96, 0x13, 0xC2, 0x02, 0xAC, 0x29, 0x87, 0x1E, - 0x9B, 0x75, 0xD0, 0x1C, 0xEB, 0x1E, 0x05, 0xBA, 0xA7, 0x1B, 0xFA, 0x32, 0x62, 0x3E, 0xDE, 0x1D, - 0x7D, 0x26, 0x3F, 0x87, 0xC8, 0x33, 0x80, 0xE9, 0x4B, 0x02, 0xB5, 0x73, 0xA4, 0x62, 0x39, 0xFD, - 0x8D, 0xDC, 0x79, 0x03, 0xEB, 0x16, 0x9B, 0xD3, 0xE9, 0x28, 0x81, 0x7E, 0x5A, 0xAE, 0xA0, 0x46, - 0x92, 0xAC, 0xB4, 0x9D, 0x27, 0xF8, 0xB6, 0x82, 0x64, 0x44, 0xB3, 0xB2, 0x2B, 0xA4, 0xF2, 0x3C, - 0xC8, 0x09, 0x27, 0xE6, 0xC9, 0xB3, 0x30, 0x7F, 0xAE, 0x85, 0x79, 0xF2, 0x2D, 0x2C, 0x26, 0xE7, - 0xC2, 0xFC, 0x79, 0x17, 0xE6, 0xCB, 0xBD, 0x30, 0x4F, 0xFE, 0x85, 0x79, 0x73, 0x30, 0x2C, 0x3E, - 0x9B, 0xC2, 0xBC, 0x19, 0x15, 0x16, 0x91, 0x55, 0xA1, 0x7E, 0x66, 0x85, 0x79, 0xB3, 0x2B, 0x14, - 0xE2, 0x38, 0x76, 0x03, 0xB0, 0x8C, 0xEC, 0x3E, 0x78, 0xCC, 0x84, 0x93, 0x7C, 0x93, 0xF1, 0xA8, - 0xA9, 0x11, 0xD1, 0x84, 0x70, 0x90, 0x7F, 0x80, 0x19, 0xF4, 0x69, 0x4D, 0x70, 0xC6, 0xF9, 0x67, - 0x59, 0x26, 0x5D, 0x78, 0x6C, 0xA9, 0x4C, 0x45, 0x7A, 0x62, 0x09, 0x29, 0xCD, 0x58, 0x8B, 0x1B, - 0x6E, 0xA9, 0xFC, 0xF2, 0xDD, 0xD9, 0x40, 0x75, 0x7E, 0x79, 0x6E, 0xB2, 0x98, 0x95, 0x9F, 0x7E, - 0x22, 0x26, 0x28, 0x50, 0x88, 0x31, 0xCB, 0x46, 0x5C, 0x15, 0xF2, 0x0D, 0x75, 0x0E, 0xBD, 0x59, - 0x1D, 0x92, 0x1F, 0x3E, 0x9E, 0xEE, 0x0F, 0x8C, 0xCC, 0xAE, 0x68, 0xB3, 0xBD, 0x29, 0x1F, 0x41, - 0xD1, 0xBC, 0x24, 0xE9, 0x07, 0x9B, 0x7B, 0x14, 0x21, 0xCA, 0xAA, 0x72, 0xE3, 0x61, 0x30, 0x0C, - 0x2C, 0x19, 0x89, 0xC2, 0x9F, 0xAC, 0x23, 0x89, 0x12, 0xAD, 0xAE, 0xEA, 0x8A, 0x61, 0x52, 0x81, - 0x46, 0x9A, 0xA2, 0x31, 0x07, 0xD5, 0x1B, 0x27, 0x3C, 0xB1, 0xA6, 0x49, 0x76, 0xA9, 0x89, 0xE3, - 0xC2, 0x3F, 0xD6, 0xF7, 0x36, 0xB9, 0x62, 0x25, 0x52, 0x82, 0xBE, 0x76, 0x6C, 0x18, 0x8E, 0x0F, - 0xA5, 0x51, 0x1C, 0x1D, 0xDB, 0xC6, 0x49, 0xD8, 0x39, 0x7B, 0x13, 0x2B, 0xF8, 0xC6, 0x0B, 0x38, - 0x22, 0xA5, 0x90, 0x29, 0x0E, 0x87, 0x7D, 0x85, 0x24, 0xF9, 0x75, 0x78, 0xFE, 0xE1, 0x35, 0x92, - 0xE7, 0x1F, 0x5A, 0x0C, 0x09, 0x74, 0x5D, 0x8F, 0xBC, 0x7A, 0xC5, 0x69, 0x41, 0xC7, 0x40, 0x1F, - 0x75, 0xF5, 0xFE, 0xC9, 0xC9, 0xEC, 0x67, 0xCE, 0x16, 0x28, 0xC1, 0x4A, 0xAD, 0x2A, 0xEA, 0xD1, - 0xBC, 0x5E, 0x48, 0xED, 0x5C, 0x45, 0xF9, 0x07, 0x25, 0x83, 0x18, 0x5D, 0x34, 0x85, 0xF6, 0x1B, - 0x2E, 0x6A, 0x04, 0x04, 0x2C, 0x28, 0xB1, 0x61, 0x89, 0x3F, 0x85, 0x79, 0x81, 0xED, 0x69, 0xA9, - 0x9D, 0x15, 0x5F, 0xF8, 0x90, 0x52, 0xB9, 0x3A, 0x78, 0x00, 0xAC, 0x5E, 0x61, 0xDA, 0x4D, 0x72, - 0xB7, 0xC6, 0x4D, 0x23, 0x7B, 0xC6, 0x0B, 0x0D, 0x49, 0x8E, 0x79, 0x9B, 0x86, 0x60, 0xEE, 0xA9, - 0xFD, 0xD6, 0xF3, 0x27, 0x02, 0x4F, 0x92, 0x54, 0x3F, 0x4E, 0x11, 0x7B, 0x1B, 0x25, 0xEE, 0xC7, - 0xBB, 0x56, 0xFC, 0x5D, 0x11, 0xC7, 0x06, 0xDB, 0xEF, 0x5C, 0xDF, 0xC7, 0xAF, 0x97, 0xB4, 0x64, - 0x65, 0x58, 0x89, 0xFB, 0x5B, 0x05, 0xDB, 0x23, 0xEA, 0xBD, 0xAD, 0xA0, 0x73, 0xF0, 0xF1, 0xE3, - 0xF9, 0xE1, 0xF1, 0xE9, 0xFE, 0xC5, 0xD1, 0xE5, 0xF1, 0xE9, 0xD9, 0xA7, 0x8B, 0xCB, 0x8B, 0xCF, - 0x67, 0xF8, 0xEB, 0xCF, 0xFB, 0x1F, 0x8E, 0x0F, 0x2F, 0x3F, 0x9D, 0xFE, 0xF5, 0xF4, 0xE3, 0x2F, - 0xA7, 0xFA, 0xA5, 0xDA, 0xB8, 0x47, 0x91, 0xC9, 0x04, 0x23, 0xDE, 0xF0, 0x84, 0x56, 0x90, 0x99, - 0xFB, 0xCD, 0xC5, 0xE4, 0xB3, 0xBB, 0x4F, 0x4D, 0x57, 0xD8, 0x00, 0x18, 0xF2, 0x77, 0x36, 0xA8, - 0x2D, 0x31, 0xFE, 0x8A, 0xAC, 0xF5, 0x5E, 0x74, 0xB9, 0xFA, 0x9B, 0xCA, 0x61, 0x0A, 0x7A, 0xD1, - 0x5D, 0xC9, 0x15, 0x43, 0x72, 0x89, 0xE6, 0x05, 0xDF, 0x44, 0x79, 0xD1, 0x7D, 0x72, 0x02, 0xC8, - 0x1C, 0x2A, 0x5C, 0x5D, 0x24, 0x9D, 0x8E, 0xEE, 0xD7, 0xED, 0x1A, 0xF4, 0x8F, 0x3F, 0x42, 0x53, - 0x5D, 0x01, 0x19, 0xDC, 0x1F, 0x1A, 0xF8, 0x87, 0x06, 0x66, 0x6B, 0x60, 0x4A, 0x5F, 0xFE, 0xB4, - 0xA7, 0x18, 0xBE, 0xDC, 0xF6, 0x73, 0x1B, 0x1A, 0xF0, 0x24, 0x47, 0x2C, 0x7F, 0xC9, 0x13, 0x69, - 0x7E, 0xAE, 0xB1, 0x97, 0xF9, 0x98, 0xFA, 0xB0, 0x5A, 0xC4, 0x19, 0x28, 0x19, 0x54, 0xF5, 0x46, - 0x4B, 0xA6, 0xD8, 0xEB, 0xB2, 0xA1, 0x74, 0x82, 0xC8, 0x85, 0x8E, 0x13, 0x9A, 0x8D, 0xA7, 0xDD, - 0x78, 0xD8, 0x6C, 0x78, 0x4F, 0xAB, 0x1D, 0x8D, 0x46, 0x2C, 0x07, 0x6F, 0x68, 0x7D, 0x8C, 0x3D, - 0x1A, 0x62, 0x6F, 0xF1, 0x64, 0x28, 0xF1, 0x34, 0xAB, 0x65, 0x83, 0x4F, 0xF2, 0x5F, 0xAA, 0x33, - 0x6C, 0x32, 0x33, 0x8A, 0x11, 0xC2, 0x48, 0xB5, 0xA2, 0x3A, 0x6D, 0x13, 0x13, 0x49, 0xF6, 0x4A, - 0xCD, 0xB2, 0xE5, 0x22, 0x83, 0x4A, 0x79, 0xD8, 0xD2, 0xF7, 0x40, 0xF9, 0x09, 0x66, 0x20, 0x22, - 0xB3, 0xB9, 0x7C, 0x45, 0xF3, 0xD3, 0xDE, 0x5F, 0x2A, 0xB9, 0x68, 0xE2, 0xFE, 0xD5, 0x74, 0xFB, - 0x4A, 0x7A, 0x76, 0x02, 0x01, 0x7A, 0x16, 0x3F, 0xC8, 0x95, 0xE3, 0x8F, 0x1C, 0x26, 0xEE, 0xD1, - 0x5E, 0x66, 0x66, 0x0A, 0xC3, 0xCB, 0x55, 0x75, 0x56, 0x03, 0x0F, 0x95, 0x49, 0xF3, 0xF4, 0x0E, - 0x39, 0xE4, 0xE5, 0x9B, 0xA9, 0xE5, 0xC9, 0xD7, 0xB2, 0x8A, 0x59, 0xAA, 0x23, 0x8E, 0x5A, 0x16, - 0xAC, 0x3B, 0xC9, 0x3B, 0x93, 0xAA, 0x0A, 0x25, 0x01, 0x9E, 0xA8, 0x49, 0x8B, 0x52, 0x1C, 0x43, - 0x7F, 0x98, 0x6E, 0x43, 0x2E, 0xA8, 0x23, 0x94, 0x5C, 0x66, 0x32, 0x89, 0x39, 0x40, 0xBB, 0x95, - 0xFA, 0x3F, 0x9A, 0xFE, 0x8B, 0xDA, 0xDC, 0x7B, 0xD2, 0x6D, 0xEE, 0x2D, 0xA5, 0xCD, 0xFD, 0x27, - 0xDD, 0xE6, 0xFE, 0x52, 0xDA, 0xBC, 0xF1, 0xA4, 0xDB, 0xBC, 0x51, 0xB5, 0xCD, 0x3A, 0xCA, 0x65, - 0x3B, 0x8C, 0x59, 0xA6, 0x31, 0x89, 0x8A, 0x50, 0xDE, 0x39, 0xA8, 0x66, 0x13, 0xD1, 0xB7, 0xB4, - 0xF9, 0xBB, 0xBF, 0xDC, 0x02, 0x8A, 0xE7, 0x7E, 0x3B, 0x11, 0xDB, 0xCC, 0x68, 0x62, 0x8D, 0xC8, - 0x36, 0x82, 0xA3, 0xBA, 0x99, 0x6C, 0x7C, 0xB1, 0x6D, 0x14, 0x2C, 0xFE, 0xD2, 0xFD, 0x2A, 0xE6, - 0x5B, 0xF6, 0xCD, 0x09, 0x4E, 0xAD, 0xD3, 0x16, 0x3F, 0x8C, 0x89, 0xCA, 0x57, 0x56, 0x78, 0x59, - 0x54, 0xFD, 0x15, 0xE9, 0xAA, 0x1F, 0x5E, 0x63, 0x9E, 0xF5, 0x15, 0x13, 0xF2, 0x5E, 0x01, 0xF2, - 0x9E, 0x8A, 0xBC, 0xA7, 0x23, 0xEF, 0xE5, 0x21, 0xEF, 0x17, 0x20, 0xEF, 0xAB, 0xC8, 0xFB, 0x3A, - 0xF2, 0x7E, 0x1E, 0xF2, 0x8D, 0x02, 0xE4, 0x1B, 0x2A, 0xF2, 0x0D, 0x1D, 0xF9, 0x46, 0x8C, 0xFC, - 0xE9, 0x6D, 0x49, 0x55, 0x56, 0x59, 0x29, 0xCD, 0xEB, 0x8D, 0x17, 0x80, 0xAA, 0xCE, 0xE0, 0x57, - 0xF8, 0xD7, 0x1F, 0x17, 0x28, 0xB0, 0x88, 0x83, 0x40, 0x20, 0xA1, 0xBE, 0x9D, 0xD0, 0xFB, 0xE0, - 0xDD, 0xB1, 0xBB, 0x2C, 0xB4, 0xB5, 0x92, 0xBC, 0x0D, 0x0A, 0xB8, 0x56, 0x32, 0x2E, 0x7E, 0x20, - 0x31, 0xE3, 0xE5, 0x0E, 0xDD, 0x29, 0xE8, 0x19, 0x5D, 0x82, 0xCD, 0x1D, 0xD3, 0x2B, 0xA3, 0x88, - 0xB4, 0x66, 0x47, 0x54, 0xEF, 0x8C, 0xEC, 0x55, 0x57, 0x8E, 0x89, 0x92, 0x3A, 0x87, 0x49, 0xA0, - 0x4C, 0x2F, 0x8A, 0x8A, 0xE9, 0x8D, 0x7A, 0x25, 0x9C, 0xD7, 0xB1, 0x7B, 0x6D, 0xE2, 0xD8, 0xFD, - 0x12, 0x9D, 0xC7, 0xF7, 0x4C, 0x7A, 0x86, 0x98, 0x15, 0xB1, 0x75, 0x0E, 0x68, 0xD2, 0x85, 0x29, - 0x95, 0xEF, 0xD5, 0xD7, 0x79, 0x20, 0xF0, 0x64, 0xF7, 0x70, 0x7B, 0xE6, 0x73, 0x16, 0x94, 0x89, - 0xE9, 0xB8, 0x4B, 0x89, 0x5C, 0x44, 0xE3, 0xCF, 0x77, 0xA2, 0xD4, 0xB7, 0x3B, 0xA4, 0x07, 0x4B, - 0x23, 0x7D, 0xCF, 0x1B, 0xA6, 0xEC, 0x0D, 0xD0, 0x0B, 0xEF, 0xAD, 0x85, 0x71, 0x00, 0xF7, 0x87, - 0xE2, 0xCE, 0xAD, 0xBA, 0x44, 0x55, 0xCB, 0xD2, 0x2F, 0x85, 0x36, 0xA2, 0x22, 0xB2, 0x3F, 0x85, - 0x69, 0x84, 0xDA, 0x4A, 0x60, 0x02, 0x4B, 0x8B, 0x67, 0xB3, 0xBB, 0x48, 0x9C, 0x1A, 0x1E, 0x0A, - 0x19, 0x5F, 0x03, 0xD2, 0x28, 0xB1, 0x20, 0xD2, 0x76, 0xAF, 0xDD, 0x30, 0xB3, 0x6C, 0x38, 0x89, - 0x4D, 0xB1, 0x5F, 0xEA, 0x55, 0x25, 0xE9, 0xDD, 0xF7, 0x8C, 0x24, 0x7D, 0x05, 0x2F, 0x22, 0xED, - 0x91, 0xEF, 0x4A, 0x54, 0x52, 0xC1, 0x13, 0x6F, 0x72, 0x5B, 0x7E, 0xA7, 0xBE, 0x17, 0xA5, 0x6A, - 0x95, 0x9E, 0x8E, 0x90, 0x92, 0xD3, 0xB2, 0x6D, 0xFC, 0xC2, 0xDC, 0xC1, 0xA4, 0x85, 0x21, 0xF2, - 0x86, 0x24, 0xB3, 0x2B, 0xCF, 0x96, 0x90, 0xE2, 0xB7, 0x66, 0xB2, 0xDA, 0x4A, 0x89, 0x6A, 0x47, - 0x6A, 0x8A, 0x4D, 0xFD, 0x5D, 0xEB, 0x07, 0x45, 0x52, 0x7A, 0x3E, 0xD8, 0x67, 0x0B, 0x4F, 0x2C, - 0x5B, 0x27, 0xB7, 0x63, 0xF9, 0xBC, 0x8E, 0x99, 0x8D, 0xED, 0x74, 0x8B, 0xDA, 0x9B, 0x64, 0x84, - 0xE5, 0xE7, 0x63, 0x0B, 0xCD, 0x2D, 0xFB, 0xB4, 0x5A, 0xCD, 0xDF, 0x51, 0xC1, 0x66, 0x18, 0x06, - 0x84, 0xC0, 0x34, 0x08, 0x7D, 0x6A, 0x4D, 0x12, 0x25, 0x28, 0x27, 0x0F, 0x8E, 0x55, 0x16, 0x88, - 0x84, 0xE7, 0x47, 0x4B, 0x23, 0x47, 0x01, 0x52, 0x59, 0x5C, 0xE7, 0x52, 0x82, 0x34, 0xB6, 0x1F, - 0xDC, 0xF4, 0x5E, 0x27, 0xAF, 0xF1, 0x47, 0xFC, 0x36, 0xD0, 0x62, 0xDA, 0x9E, 0x42, 0xF6, 0x83, - 0x9A, 0x6E, 0x7C, 0x51, 0xFD, 0x21, 0x35, 0x67, 0xA0, 0x6D, 0xCF, 0x9F, 0x37, 0xCA, 0x24, 0x35, - 0x37, 0xCD, 0x1A, 0x8B, 0x9C, 0x31, 0xD4, 0xB4, 0xCD, 0x4F, 0x79, 0xD6, 0x88, 0x93, 0x4C, 0x2F, - 0x62, 0xE6, 0x48, 0x67, 0xAC, 0x7E, 0x92, 0xB3, 0x87, 0x9E, 0xCB, 0x78, 0x61, 0xB6, 0xE3, 0x29, - 0xB4, 0xBD, 0xB4, 0xE9, 0x98, 0xBB, 0xE5, 0x3A, 0xAE, 0xA7, 0x6B, 0x38, 0x84, 0xE3, 0xCC, 0xF3, - 0x33, 0x83, 0x5D, 0x48, 0xB9, 0xCB, 0xAA, 0x37, 0xBA, 0xFB, 0xA3, 0x1E, 0xA4, 0x60, 0xAF, 0x44, - 0xAB, 0x46, 0x89, 0x4D, 0xC4, 0xFC, 0x4D, 0x8E, 0xB8, 0x87, 0x77, 0x8B, 0x01, 0x06, 0x95, 0x01, - 0x2E, 0xAA, 0x02, 0xFC, 0x2C, 0x01, 0x6C, 0x16, 0x03, 0x9C, 0x9F, 0x1C, 0xA4, 0x29, 0x3C, 0xD4, - 0x78, 0x36, 0x61, 0xC4, 0x5E, 0x7F, 0x52, 0xBD, 0xD5, 0x77, 0x67, 0xAA, 0x88, 0xBA, 0x9D, 0xAD, - 0xDD, 0x22, 0x80, 0x41, 0x65, 0x80, 0x8B, 0xAA, 0x00, 0xB2, 0x88, 0xE2, 0x71, 0x99, 0x0D, 0xA0, - 0x8A, 0x28, 0xA6, 0xF0, 0x30, 0x4F, 0xA6, 0x7C, 0xA4, 0x14, 0x30, 0x97, 0x2E, 0x32, 0x54, 0x8C, - 0x58, 0x57, 0x22, 0xB4, 0x8D, 0xC7, 0x9C, 0x93, 0x80, 0x0E, 0xB7, 0xBA, 0xDD, 0x22, 0xB8, 0x9E, - 0x04, 0xF7, 0x1C, 0xE1, 0x00, 0xAC, 0x67, 0x6A, 0x96, 0xE4, 0x46, 0xE6, 0x6A, 0xAF, 0xA1, 0xFA, - 0xA0, 0x62, 0xF5, 0x8B, 0x6A, 0xD5, 0xE5, 0x4E, 0xE9, 0x17, 0x55, 0x4F, 0x69, 0xAD, 0xA9, 0x7E, - 0xDA, 0x7D, 0xC4, 0xDF, 0x7A, 0xDD, 0x8D, 0x0D, 0x19, 0xB6, 0x9B, 0x18, 0xE6, 0x87, 0x94, 0x69, - 0x12, 0x77, 0x6C, 0xFF, 0x30, 0x4C, 0x4F, 0xC0, 0x30, 0xE5, 0x41, 0x9C, 0xFF, 0x7A, 0x72, 0x79, - 0xBE, 0xFF, 0xCB, 0xAF, 0xA5, 0x79, 0x42, 0x80, 0xC1, 0xDB, 0xF3, 0x37, 0xBF, 0xFE, 0x61, 0xFC, - 0x2A, 0x1B, 0xBF, 0x2C, 0x08, 0x7D, 0x9C, 0xF5, 0x76, 0x4A, 0x92, 0xD2, 0x01, 0xFB, 0xDD, 0x9A, - 0x80, 0x9B, 0xFD, 0xBA, 0x80, 0xDB, 0x8D, 0x4C, 0x57, 0xED, 0x0F, 0x0B, 0xFF, 0xCF, 0x60, 0xE1, - 0x4D, 0xE0, 0xA9, 0x35, 0xB6, 0xA4, 0xB7, 0xA6, 0xAC, 0x53, 0xE5, 0xC0, 0x99, 0xF6, 0xD6, 0x07, - 0x67, 0x3A, 0x3C, 0x07, 0xF8, 0x76, 0x66, 0xCA, 0x2C, 0x83, 0xEB, 0x8D, 0x30, 0xC6, 0xC9, 0x4D, - 0x5D, 0xCF, 0x3F, 0xA1, 0x09, 0x8E, 0x2D, 0x69, 0x90, 0xED, 0xCB, 0x5E, 0xB7, 0xBB, 0x55, 0xD6, - 0xDE, 0xCB, 0x50, 0xCF, 0x37, 0x6B, 0x41, 0x3D, 0x6F, 0xA4, 0x56, 0xEF, 0xC5, 0x50, 0x2F, 0x6A, - 0xD1, 0x7A, 0xA1, 0xD1, 0x2A, 0x09, 0xB6, 0x53, 0x8B, 0xD8, 0x4E, 0x9D, 0x86, 0xF5, 0xFA, 0x75, - 0x68, 0xF5, 0xFA, 0x75, 0x68, 0xF5, 0x37, 0x94, 0xE9, 0xA0, 0xAC, 0x3C, 0x36, 0xBB, 0xCF, 0xFB, - 0x8A, 0xB5, 0x2D, 0x49, 0x8F, 0xC1, 0xF5, 0x1A, 0x19, 0x5B, 0x35, 0x73, 0xB9, 0x05, 0xF1, 0x92, - 0x9C, 0x0F, 0x55, 0x55, 0x7D, 0x0B, 0x66, 0x2B, 0x09, 0x2E, 0xC3, 0x7F, 0x2D, 0x06, 0x54, 0x35, - 0xBF, 0x3C, 0xDC, 0x8B, 0x9A, 0x70, 0x3B, 0xF5, 0xE0, 0x34, 0xE5, 0x5A, 0xD8, 0x6C, 0x9C, 0xB5, - 0x33, 0x54, 0x77, 0xCE, 0xD0, 0xBB, 0xB2, 0x9A, 0xD1, 0xD6, 0x49, 0xD7, 0x80, 0x3E, 0x19, 0x9C, - 0x6C, 0x56, 0xB5, 0xF6, 0x1F, 0xBC, 0x3B, 0x34, 0xB3, 0x77, 0x8E, 0x1D, 0xDE, 0xFC, 0xA3, 0x5B, - 0xFC, 0x6E, 0x2D, 0x93, 0xDF, 0x7F, 0x44, 0x93, 0xDF, 0x7F, 0x4C, 0x93, 0xDF, 0x7F, 0x44, 0x93, - 0xDF, 0xFF, 0xC3, 0xE4, 0xCF, 0x6D, 0xF2, 0xFB, 0x8F, 0x6D, 0xF2, 0xFB, 0x35, 0x4D, 0x7E, 0xBF, - 0xA6, 0xC9, 0xEF, 0xD7, 0x34, 0xF9, 0xFD, 0x47, 0x32, 0xF9, 0xDD, 0xAD, 0xFF, 0xDC, 0x2E, 0x23, - 0xD7, 0xF2, 0x33, 0x46, 0x19, 0x60, 0x61, 0xB5, 0x95, 0xC6, 0xFE, 0xD3, 0xCC, 0x34, 0xB3, 0x80, - 0x3D, 0x4D, 0x97, 0x5C, 0xA2, 0x09, 0xD2, 0xB7, 0x4B, 0xF8, 0x1B, 0x63, 0x89, 0x04, 0x28, 0xFE, - 0xBD, 0x9B, 0xAE, 0xF3, 0x59, 0xAB, 0xF3, 0xD9, 0x50, 0xE7, 0x37, 0xAD, 0xCE, 0x6F, 0xBB, 0xF2, - 0x2E, 0x1E, 0x70, 0xF3, 0x8E, 0x7A, 0x36, 0x0D, 0x9D, 0x61, 0x2E, 0x47, 0xE9, 0xFB, 0x2E, 0x23, - 0x01, 0x06, 0x25, 0xC5, 0xF7, 0x59, 0xE2, 0xCA, 0x9E, 0xAB, 0x55, 0x7E, 0xBF, 0x7F, 0xB4, 0x7F, - 0x76, 0x60, 0xA8, 0xBA, 0x3F, 0x0E, 0x45, 0xAF, 0xAB, 0x59, 0x5D, 0x26, 0x96, 0xFF, 0x4D, 0x49, - 0xE9, 0xD2, 0x2B, 0xC8, 0xDB, 0x52, 0x00, 0xDE, 0x37, 0x81, 0xF3, 0xAB, 0xDF, 0x82, 0xBE, 0x65, - 0xFF, 0xFB, 0x2C, 0x08, 0x81, 0x51, 0x3D, 0x14, 0x88, 0x9D, 0xD6, 0x9E, 0xD2, 0xBB, 0x0F, 0x5E, - 0x72, 0x43, 0x28, 0x95, 0x02, 0x25, 0xA9, 0x63, 0x08, 0x8C, 0xA2, 0xDF, 0x9D, 0x50, 0x4B, 0x87, - 0x70, 0xE3, 0xD8, 0x2C, 0x7B, 0xA4, 0xE3, 0x9E, 0xB1, 0x3C, 0x32, 0xBC, 0x11, 0x18, 0xBB, 0x86, - 0xA9, 0xEF, 0xC0, 0x4B, 0x39, 0x76, 0xCF, 0x7C, 0x0F, 0x6F, 0xEF, 0xC7, 0x59, 0x53, 0x74, 0x9A, - 0x88, 0x75, 0xDF, 0xB5, 0xE5, 0x68, 0x2C, 0xAC, 0xC6, 0x7D, 0x1C, 0x67, 0x42, 0xBD, 0x19, 0x5E, - 0x4E, 0x4B, 0xFE, 0x68, 0x25, 0x6C, 0xB0, 0xF7, 0xD0, 0xBA, 0xA6, 0xF0, 0xAD, 0x03, 0x6F, 0x32, - 0x1D, 0xD3, 0x30, 0xC9, 0x7C, 0xC4, 0x62, 0xD4, 0x22, 0x0C, 0x32, 0x6E, 0x41, 0xCE, 0xDC, 0x10, - 0xFE, 0x35, 0xDD, 0x10, 0xAD, 0x91, 0x11, 0xB5, 0x46, 0x74, 0xB3, 0xEB, 0xC0, 0x1A, 0x63, 0x1E, - 0xD1, 0xBB, 0x1B, 0xCA, 0x2F, 0x77, 0x1D, 0x0D, 0xCE, 0x36, 0xFA, 0xE4, 0xC6, 0x0A, 0xF0, 0xAE, - 0xD4, 0xB5, 0xE3, 0x4F, 0xA0, 0xD0, 0x07, 0xFD, 0x76, 0xA6, 0x21, 0xF1, 0xAE, 0x79, 0xA4, 0x34, - 0x2E, 0xFA, 0x13, 0xE1, 0x90, 0x6B, 0xDF, 0x9B, 0x90, 0xFD, 0x33, 0x0E, 0x30, 0x22, 0x53, 0xE0, - 0x4A, 0x8A, 0xD4, 0xE3, 0x58, 0x30, 0x97, 0xC2, 0x39, 0x47, 0x23, 0x1D, 0xD2, 0x1F, 0x02, 0x27, - 0xFE, 0xC4, 0x71, 0x29, 0xD0, 0x77, 0x86, 0x37, 0x24, 0xD1, 0x00, 0xCC, 0x7C, 0x8A, 0xEC, 0x78, - 0xBE, 0x33, 0x82, 0xB1, 0x33, 0x66, 0x84, 0x63, 0xCF, 0x30, 0xCA, 0xCE, 0x10, 0x09, 0xFD, 0x4F, - 0x7B, 0xC4, 0x9D, 0x8D, 0xC7, 0x2B, 0x7A, 0x74, 0x61, 0x24, 0x45, 0xAD, 0x7E, 0x99, 0xA4, 0x0D, - 0xFC, 0xDB, 0x4B, 0xB2, 0x3F, 0x1E, 0x93, 0x01, 0x7C, 0xB7, 0xF3, 0x33, 0x29, 0x7A, 0x6E, 0xE0, - 0x8D, 0x69, 0x67, 0x0C, 0x1A, 0xDB, 0xF8, 0xC4, 0x6F, 0xA4, 0x12, 0xF8, 0x0F, 0xE4, 0x04, 0x42, - 0x4B, 0xCB, 0xC0, 0x9C, 0xC4, 0xE3, 0x3A, 0xCE, 0x91, 0x39, 0xF6, 0x2C, 0xFB, 0x17, 0xCB, 0x09, - 0x95, 0x8B, 0xE8, 0x98, 0x51, 0x44, 0x24, 0x70, 0x0F, 0x66, 0x57, 0x13, 0x27, 0x8C, 0x72, 0x52, - 0x62, 0xE6, 0x11, 0x18, 0x6C, 0x58, 0x1E, 0x7C, 0xE9, 0x7E, 0x4D, 0xE2, 0xDA, 0x71, 0xCA, 0x12, - 0xB1, 0xED, 0x2E, 0xBD, 0x23, 0x78, 0x0B, 0x50, 0x4E, 0x6A, 0x11, 0x15, 0x77, 0xAC, 0xE9, 0x94, - 0x0F, 0xAA, 0x34, 0xDA, 0x36, 0x23, 0xBB, 0x92, 0xE0, 0xB4, 0xFE, 0xDD, 0xFA, 0x2E, 0xF0, 0xFD, - 0x7A, 0xF2, 0xE1, 0x7D, 0x88, 0x19, 0xE0, 0xFE, 0x63, 0x46, 0x83, 0x30, 0xC2, 0x8A, 0x15, 0x3A, - 0x1E, 0x20, 0x6C, 0x35, 0xCE, 0x3E, 0x0E, 0x2E, 0x30, 0x85, 0xC9, 0xFA, 0x8C, 0xB5, 0x28, 0x42, - 0xDC, 0x90, 0x6B, 0xB2, 0xA1, 0x15, 0x71, 0x12, 0x8D, 0x3C, 0x6E, 0xC6, 0x64, 0x69, 0x18, 0x62, - 0x2D, 0x5F, 0x5D, 0xF9, 0xAF, 0x79, 0xA1, 0x83, 0x77, 0xF8, 0xA6, 0x4A, 0x06, 0xA8, 0xC6, 0x6E, - 0x8E, 0x68, 0x07, 0xA1, 0x15, 0xCE, 0x02, 0x0C, 0x6F, 0x56, 0x0D, 0x72, 0x3E, 0x41, 0xA8, 0x9E, - 0x87, 0x34, 0x35, 0x90, 0xF9, 0xB0, 0x33, 0x57, 0x6A, 0x64, 0x0E, 0x66, 0x85, 0x80, 0xE7, 0x8F, - 0x68, 0x78, 0x66, 0x39, 0x3E, 0xCC, 0x88, 0x68, 0x5C, 0xD5, 0x19, 0xE4, 0x2A, 0x74, 0xDF, 0xB2, - 0x1A, 0xBC, 0xCC, 0x20, 0x22, 0xD4, 0x5E, 0x9F, 0x15, 0x32, 0x5C, 0x5E, 0x18, 0x52, 0xB7, 0x23, - 0xE5, 0x24, 0x98, 0x52, 0xEA, 0x9F, 0xEC, 0x1F, 0x04, 0x3A, 0xDC, 0xA9, 0xE7, 0xD2, 0xAC, 0xC8, - 0x54, 0x46, 0xF1, 0x28, 0x98, 0x9E, 0x7A, 0x77, 0x67, 0x00, 0x1E, 0x18, 0xCC, 0x2F, 0x30, 0xC6, - 0x8C, 0xA4, 0x9E, 0xD4, 0x6D, 0x14, 0x99, 0x28, 0x51, 0x60, 0xE0, 0x98, 0xC1, 0xB1, 0x50, 0x1D, - 0x91, 0x7D, 0x2B, 0x2B, 0x42, 0x56, 0xC6, 0x93, 0xB0, 0x10, 0x87, 0x2B, 0x58, 0xB6, 0x7D, 0x74, - 0x0B, 0xBF, 0x60, 0x98, 0x32, 0x05, 0xFC, 0xAD, 0xC6, 0xE1, 0xC7, 0x13, 0xB0, 0xC5, 0x21, 0x7E, - 0x83, 0x8E, 0xA0, 0x98, 0xAD, 0xB0, 0x45, 0xB1, 0xCA, 0x0A, 0xD9, 0x7B, 0x0D, 0x0C, 0xC6, 0x1A, - 0x2E, 0x04, 0x56, 0x22, 0xF6, 0x21, 0x9D, 0x25, 0x4C, 0x89, 0x78, 0x60, 0x77, 0xE9, 0xD1, 0xA9, - 0x67, 0xB7, 0xF6, 0xE0, 0x17, 0x8E, 0x39, 0x8E, 0x78, 0x70, 0x30, 0x0C, 0xC2, 0xFA, 0x0E, 0xBF, - 0xA8, 0xA1, 0x0F, 0xBC, 0xDA, 0x17, 0xE7, 0x6B, 0xC7, 0x73, 0x87, 0x63, 0x67, 0x88, 0x69, 0x3E, - 0x62, 0xD9, 0xB6, 0x52, 0x4F, 0xED, 0xAA, 0xF9, 0xF3, 0x32, 0x1E, 0xD0, 0xE4, 0x2E, 0x96, 0x15, - 0xCC, 0x7C, 0x16, 0x34, 0x8D, 0x8E, 0xF0, 0xFB, 0xDF, 0x41, 0xF8, 0x69, 0x39, 0x71, 0x7C, 0x38, - 0xF0, 0x8D, 0x24, 0x0D, 0x88, 0x06, 0x74, 0xA8, 0x6E, 0x9B, 0x90, 0xF5, 0x4C, 0x7A, 0x52, 0x30, - 0xF6, 0x83, 0x3C, 0xDA, 0x8D, 0x28, 0x17, 0xC1, 0x5C, 0x42, 0x35, 0x8F, 0x37, 0xA9, 0x09, 0x06, - 0xE6, 0x0C, 0xEF, 0x79, 0x57, 0x66, 0xAD, 0xDE, 0xDB, 0xE0, 0x89, 0x1D, 0x09, 0x44, 0x7D, 0xF3, - 0x03, 0xAD, 0xDC, 0x8E, 0x30, 0xCF, 0x2F, 0xF3, 0x25, 0x55, 0x53, 0x8B, 0xDE, 0x22, 0xC4, 0x22, - 0x9A, 0x13, 0x21, 0x2A, 0xD5, 0x96, 0x0C, 0x3E, 0x93, 0x86, 0x64, 0x37, 0xD6, 0xD0, 0x96, 0xFC, - 0x87, 0xAA, 0x6B, 0xB7, 0x6C, 0xAE, 0xF7, 0xAF, 0x79, 0x3B, 0x71, 0x5D, 0x90, 0xD7, 0xCC, 0x51, - 0xBC, 0x44, 0xA8, 0xDF, 0xCC, 0x77, 0x2C, 0xA3, 0xD3, 0xA2, 0x5B, 0xF9, 0x4E, 0xCF, 0x13, 0x65, - 0x6A, 0x24, 0x6F, 0x44, 0x76, 0x23, 0xB9, 0x10, 0xD2, 0x8D, 0x54, 0x2A, 0x65, 0xED, 0xF4, 0xA5, - 0xD6, 0xD7, 0x4B, 0xD9, 0xF4, 0x53, 0xDE, 0x7F, 0x31, 0xBE, 0x68, 0x2C, 0x1D, 0x9A, 0xF6, 0x3A, - 0x9B, 0x6A, 0x03, 0x1F, 0xD4, 0x7B, 0x49, 0x95, 0xB6, 0x6B, 0xAA, 0x50, 0xEE, 0xF5, 0xB6, 0x3B, - 0xFD, 0x42, 0xD2, 0xF5, 0x70, 0x2B, 0xBB, 0x07, 0x2A, 0x66, 0x83, 0x12, 0x9A, 0xDE, 0x5F, 0xAE, - 0xAD, 0x7B, 0x15, 0x1F, 0x73, 0x4E, 0x54, 0xCA, 0x70, 0xFB, 0xB5, 0xE4, 0xC3, 0xE3, 0x5C, 0x65, - 0x4B, 0xC0, 0x67, 0xB5, 0x3A, 0x7E, 0x6F, 0x64, 0x11, 0xAD, 0x2E, 0xF3, 0x78, 0x89, 0xD6, 0x6A, - 0x0E, 0x52, 0xBF, 0xD5, 0xF9, 0xF0, 0x86, 0x56, 0x6B, 0xF7, 0xA1, 0xE6, 0x6F, 0x79, 0x1A, 0x61, - 0x41, 0xEB, 0x0D, 0x37, 0xAF, 0x00, 0x48, 0x24, 0xA5, 0xD7, 0xC2, 0x3C, 0x0B, 0x45, 0x51, 0x88, - 0x4C, 0xDD, 0x99, 0x32, 0x88, 0x05, 0xD7, 0x29, 0x98, 0xB5, 0x13, 0xF3, 0xD2, 0xBA, 0x74, 0x3C, - 0x8F, 0x40, 0xD2, 0xA8, 0xE2, 0xB4, 0x1D, 0x5D, 0xB3, 0x0E, 0x44, 0x10, 0x6F, 0xAC, 0x99, 0x7D, - 0xE8, 0x7B, 0x53, 0x1B, 0x53, 0x1F, 0x19, 0xE7, 0x16, 0x25, 0xE3, 0x74, 0xA1, 0xBE, 0x94, 0xE0, - 0xA8, 0x67, 0x36, 0xFF, 0x65, 0x38, 0x12, 0xB3, 0x61, 0x25, 0x8E, 0xEA, 0xD2, 0xAA, 0xD0, 0x7A, - 0x83, 0xB6, 0x67, 0x3F, 0xAD, 0x36, 0xA7, 0xD6, 0x17, 0xBE, 0xD9, 0x66, 0xEE, 0xEF, 0x29, 0x98, - 0x29, 0xFE, 0x68, 0x67, 0x2D, 0x81, 0x15, 0x83, 0x2B, 0x32, 0x98, 0x05, 0xF4, 0x83, 0x37, 0xB4, - 0xC6, 0xCE, 0xEF, 0xD4, 0x3E, 0x84, 0x76, 0xFA, 0xCE, 0xD5, 0x8C, 0xE7, 0x49, 0xAC, 0xDF, 0xFC, - 0x1C, 0x9C, 0xB9, 0x2D, 0x1F, 0x9B, 0x80, 0x2E, 0x60, 0x89, 0xF3, 0x01, 0x16, 0x6D, 0x63, 0x53, - 0xEF, 0x97, 0x90, 0x47, 0x5D, 0xA4, 0x06, 0x4D, 0xD1, 0x53, 0xD1, 0xCF, 0xA9, 0x1F, 0x15, 0x32, - 0xDB, 0xE7, 0x8E, 0xA8, 0x43, 0x1A, 0x5A, 0xCE, 0x38, 0xA8, 0x28, 0x97, 0x2A, 0x88, 0x72, 0x64, - 0xF1, 0xDE, 0xF2, 0x6D, 0xDC, 0xEB, 0xE0, 0x82, 0x88, 0x5F, 0xB4, 0x59, 0x90, 0x68, 0x32, 0xB0, - 0x57, 0x92, 0x94, 0x0C, 0x3B, 0x9F, 0xA8, 0x4A, 0x61, 0x32, 0xC9, 0x8A, 0xED, 0x9C, 0x2C, 0x40, - 0x2A, 0x11, 0x9E, 0x52, 0xED, 0x67, 0x5B, 0x0A, 0xF5, 0x1A, 0x9C, 0x0F, 0x6A, 0xF2, 0x18, 0xA4, - 0x3D, 0xA9, 0x05, 0xB8, 0x0B, 0x2A, 0xB6, 0x0C, 0x9B, 0x61, 0xD8, 0x0E, 0x9B, 0xCF, 0x47, 0xC8, - 0x45, 0x56, 0xE8, 0x23, 0xE8, 0x2F, 0x69, 0x55, 0x96, 0x01, 0xF6, 0xDA, 0x7B, 0x10, 0xBF, 0xAA, - 0x60, 0xD1, 0xEE, 0x4E, 0x36, 0xAD, 0xF3, 0xE3, 0xD3, 0xA3, 0x5F, 0xE7, 0x17, 0xBA, 0x86, 0xAD, - 0xDC, 0x10, 0x4B, 0x41, 0xD6, 0x1C, 0x60, 0xE5, 0xF1, 0x18, 0xE4, 0xA0, 0x3C, 0xA5, 0x35, 0xA7, - 0x14, 0xCA, 0x3E, 0xCB, 0xA5, 0xCB, 0x20, 0x81, 0x9B, 0x47, 0x02, 0x25, 0xB0, 0x98, 0xDA, 0x6F, - 0x7A, 0x6B, 0x6B, 0x5E, 0x39, 0x54, 0x7C, 0xBF, 0x2B, 0x25, 0x0F, 0x80, 0x9F, 0x58, 0xA1, 0x33, - 0x54, 0x91, 0xCC, 0x25, 0x9C, 0x8A, 0x28, 0x33, 0x24, 0x15, 0xBD, 0xCE, 0xB5, 0x00, 0x01, 0x95, - 0x78, 0xE8, 0x2B, 0x53, 0x2E, 0x0C, 0x76, 0x21, 0xE2, 0xC8, 0xC7, 0x94, 0xDE, 0x4F, 0xDA, 0x1F, - 0x87, 0x4E, 0x38, 0xB3, 0x6B, 0x29, 0x89, 0x72, 0x7E, 0x9B, 0x42, 0x2F, 0xF6, 0x1D, 0xDE, 0x53, - 0x67, 0x74, 0x13, 0x5E, 0x4E, 0x96, 0x46, 0x20, 0xB5, 0xB1, 0xB1, 0x60, 0x32, 0xEA, 0x69, 0xFA, - 0x02, 0x91, 0xC7, 0x97, 0x22, 0xEF, 0xF9, 0xA5, 0xC8, 0x7B, 0xF2, 0x8A, 0xB8, 0xB3, 0xC9, 0x41, - 0xEA, 0x61, 0x1E, 0x28, 0x4A, 0x5F, 0x8D, 0x04, 0xD7, 0x35, 0xF4, 0x5C, 0x71, 0x33, 0x92, 0x3D, - 0xB7, 0x80, 0xA9, 0x35, 0xDE, 0xB0, 0xAF, 0x98, 0x13, 0xFF, 0x5E, 0x89, 0xAE, 0x48, 0x6A, 0x1B, - 0x9B, 0x80, 0x67, 0x0C, 0x99, 0x2D, 0x88, 0xD2, 0x7E, 0x24, 0xF8, 0x0F, 0xB0, 0x7E, 0xEB, 0x5E, - 0x56, 0x2F, 0x29, 0xB5, 0xFD, 0x8A, 0x7C, 0x92, 0x9F, 0x31, 0x8D, 0x49, 0x79, 0x71, 0xAA, 0x85, - 0xD6, 0x14, 0x3C, 0x55, 0x59, 0x62, 0x66, 0x12, 0x3C, 0x9C, 0x30, 0xEC, 0xF3, 0x4C, 0x4D, 0x05, - 0x88, 0x8C, 0xE7, 0xC4, 0x0B, 0x63, 0x7F, 0x01, 0x8C, 0x9B, 0x58, 0x56, 0x02, 0x6A, 0xF2, 0x72, - 0xB7, 0xE4, 0x64, 0x95, 0xC9, 0xD0, 0x61, 0xED, 0x7A, 0x6F, 0x7D, 0xED, 0x4F, 0x12, 0x4A, 0x66, - 0xA7, 0xA2, 0xB9, 0x67, 0x39, 0xC7, 0xBE, 0x9B, 0xF6, 0x5B, 0xCB, 0x0C, 0x9E, 0xEF, 0xBB, 0xA6, - 0xAD, 0x54, 0x79, 0x10, 0x61, 0x6A, 0x5B, 0x71, 0x9A, 0xC8, 0xD2, 0xCA, 0x6A, 0xAC, 0x60, 0x25, - 0xE4, 0xA2, 0x60, 0x67, 0x55, 0xCD, 0x69, 0x64, 0x18, 0x63, 0xF8, 0x8D, 0xFF, 0x9D, 0x04, 0x4D, - 0x90, 0x63, 0x77, 0xE8, 0xB3, 0xA3, 0xEE, 0xA9, 0x48, 0xF6, 0x43, 0xD6, 0x48, 0x70, 0x67, 0x4D, - 0xF9, 0x4B, 0xB0, 0xEC, 0x94, 0x12, 0xE6, 0x9F, 0x84, 0x27, 0x12, 0x30, 0x9E, 0x78, 0xEE, 0x03, - 0xAC, 0xE3, 0x62, 0x5E, 0xDE, 0x1B, 0xB0, 0xCB, 0x34, 0x48, 0x5E, 0x6F, 0x4C, 0x48, 0xB1, 0xD7, - 0xDD, 0xF5, 0x9B, 0xD8, 0xD6, 0x37, 0x7A, 0x42, 0xDF, 0x23, 0x8C, 0xBF, 0x6B, 0x28, 0x61, 0x99, - 0xC8, 0x76, 0xD3, 0xBD, 0x5B, 0x59, 0x2F, 0xCA, 0xF4, 0xEE, 0x77, 0xD6, 0xBB, 0x69, 0xD9, 0xC8, - 0x3F, 0x32, 0xC3, 0x49, 0x5A, 0x22, 0xF3, 0xEE, 0x79, 0x39, 0x7A, 0xB2, 0x84, 0xD6, 0x48, 0x6F, - 0x25, 0x9B, 0x2A, 0x13, 0x46, 0x1E, 0xD1, 0x87, 0x05, 0x0A, 0x8A, 0x99, 0x4C, 0xB9, 0xB5, 0x26, - 0xBE, 0x0A, 0x5A, 0xB7, 0x86, 0x37, 0x29, 0x64, 0xD5, 0x2A, 0x10, 0x96, 0x44, 0x93, 0xB5, 0xD5, - 0x44, 0x12, 0xB0, 0x9D, 0x7A, 0x21, 0x7D, 0x49, 0x1A, 0x05, 0xC4, 0x57, 0x81, 0x78, 0x83, 0x78, - 0xEE, 0xF8, 0x9E, 0xE0, 0x23, 0xE2, 0x41, 0xC4, 0x2C, 0xFC, 0xED, 0x5C, 0x9B, 0xD0, 0xE6, 0xE0, - 0x63, 0xEF, 0x3B, 0x38, 0x6E, 0xD8, 0x66, 0xEF, 0x24, 0x07, 0x2C, 0x75, 0x6E, 0x87, 0x7C, 0xC2, - 0xB1, 0x62, 0xF9, 0xF8, 0xBC, 0x4E, 0x88, 0x63, 0x85, 0x52, 0x62, 0x5D, 0x79, 0xB7, 0xB4, 0xAA, - 0x98, 0x56, 0x23, 0x31, 0x1D, 0xD2, 0x5C, 0x31, 0x3D, 0x28, 0xE9, 0x16, 0xC8, 0xF1, 0x35, 0xB1, - 0xC6, 0x50, 0xDF, 0xBE, 0x67, 0xA3, 0x4F, 0x0C, 0xBC, 0x78, 0xF4, 0xB6, 0x99, 0x24, 0x89, 0x13, - 0x92, 0x31, 0xC8, 0x52, 0x8C, 0x48, 0xCD, 0x92, 0xB3, 0x48, 0x82, 0xFD, 0xC9, 0x7B, 0x0E, 0xFA, - 0xB8, 0xE3, 0x0C, 0x6D, 0x01, 0x36, 0xA3, 0x4B, 0x9C, 0x80, 0xBC, 0x97, 0xAC, 0x86, 0x82, 0x2A, - 0x66, 0x2E, 0xAD, 0xF7, 0xF8, 0x73, 0x05, 0xED, 0xFF, 0xF6, 0x38, 0xC3, 0x01, 0xF3, 0xC8, 0xC7, - 0xDC, 0x2C, 0x79, 0x30, 0x64, 0x9C, 0xBB, 0x15, 0x66, 0x49, 0x33, 0xB6, 0x09, 0xAD, 0x0A, 0x23, - 0xFC, 0x21, 0xD1, 0x83, 0x6C, 0xE5, 0xAA, 0x9A, 0x5F, 0x0D, 0xDD, 0xBE, 0x83, 0xA3, 0xB7, 0xF1, - 0xD4, 0x2D, 0xBF, 0x01, 0x15, 0xC7, 0xFD, 0xBB, 0x30, 0xE9, 0x60, 0x50, 0x0B, 0xD6, 0x8C, 0x4F, - 0x08, 0x7D, 0x3A, 0x81, 0xC1, 0xF2, 0xC6, 0xB2, 0x0F, 0xF0, 0x19, 0xCF, 0x56, 0xBA, 0x4E, 0xE4, - 0xD1, 0x1A, 0x5F, 0x08, 0x95, 0x6A, 0x8B, 0xF7, 0x41, 0x53, 0x0F, 0x35, 0x6F, 0xEE, 0xB0, 0x17, - 0x42, 0x2D, 0x98, 0xB7, 0xD4, 0x67, 0x42, 0xE5, 0xCC, 0xB8, 0xBB, 0x59, 0x2F, 0x59, 0x4A, 0x91, - 0xBD, 0x6D, 0xB2, 0xF6, 0x3C, 0x7A, 0x4E, 0xF3, 0x79, 0xEA, 0x5D, 0xCD, 0xA8, 0x0C, 0x49, 0x8A, - 0x5F, 0xEB, 0x13, 0xFB, 0xFC, 0x98, 0xC4, 0x7E, 0x5B, 0x24, 0xB1, 0x8C, 0x57, 0xC0, 0x94, 0x29, - 0x3F, 0x7A, 0x46, 0x07, 0x7B, 0x8E, 0x58, 0x23, 0x0B, 0xDF, 0x20, 0xE3, 0xB9, 0xFD, 0x1D, 0x49, - 0x31, 0xF9, 0x5B, 0x21, 0x36, 0xFD, 0xAE, 0x5E, 0x78, 0x60, 0x83, 0x78, 0x57, 0x94, 0xBC, 0xC2, - 0xE8, 0x51, 0xCF, 0xB7, 0x03, 0xA6, 0x2E, 0x51, 0xE4, 0xD3, 0xEA, 0x2A, 0x2B, 0xD5, 0x47, 0x27, - 0x22, 0x04, 0x43, 0x1D, 0x06, 0x4C, 0xEB, 0x62, 0xB0, 0x2F, 0xAC, 0xF2, 0xD7, 0x28, 0x33, 0x2F, - 0xD1, 0x13, 0x82, 0x46, 0x5E, 0xB5, 0xA2, 0x6B, 0xD2, 0xC9, 0x15, 0x43, 0x89, 0xE9, 0x77, 0x0D, - 0x43, 0x35, 0x4D, 0x06, 0x87, 0x67, 0x7A, 0x18, 0xAC, 0x92, 0x26, 0xCB, 0x8E, 0x99, 0x68, 0x5B, - 0x56, 0xC1, 0xE7, 0xAC, 0x82, 0xDF, 0xF4, 0xCC, 0xAB, 0x15, 0x8D, 0x23, 0x7F, 0x32, 0x83, 0x89, - 0x7B, 0xCF, 0x20, 0xD5, 0x95, 0x67, 0x19, 0xED, 0xEA, 0x4C, 0x67, 0xC1, 0x4D, 0x6B, 0xA9, 0x6D, - 0x5A, 0x31, 0x64, 0x7D, 0x44, 0x42, 0xB8, 0xC6, 0xD4, 0x0D, 0x91, 0x4D, 0x31, 0x00, 0x32, 0xB6, - 0x45, 0x71, 0xD8, 0x1D, 0x20, 0x12, 0xD1, 0xAD, 0x18, 0x9E, 0x09, 0x35, 0xA5, 0xC0, 0x79, 0xB9, - 0x47, 0x93, 0x0B, 0x62, 0x08, 0xF1, 0x9A, 0x65, 0x16, 0x56, 0x67, 0x4A, 0x93, 0x0A, 0x41, 0x5D, - 0xB3, 0x02, 0x21, 0x00, 0xCA, 0x46, 0xAC, 0x06, 0x22, 0x5D, 0x91, 0xA2, 0x54, 0xF8, 0x34, 0xC9, - 0x42, 0x78, 0x5B, 0x8D, 0x43, 0xC6, 0x3D, 0xC1, 0x83, 0x24, 0xD6, 0x1A, 0x5C, 0x2E, 0xC4, 0xE0, - 0xAB, 0xA4, 0xF1, 0x2F, 0x8D, 0x95, 0xAC, 0xAD, 0x1F, 0xB9, 0x4B, 0x90, 0x93, 0x21, 0xC5, 0x16, - 0x80, 0x4D, 0xCC, 0x58, 0x3B, 0xE6, 0x8B, 0x11, 0xA3, 0x49, 0x15, 0x83, 0xFE, 0x03, 0x65, 0x98, - 0x79, 0xC1, 0x82, 0x4B, 0xB3, 0xF7, 0x35, 0xA3, 0xEA, 0xE7, 0x54, 0xD5, 0x7E, 0x56, 0xD5, 0xDF, - 0x52, 0x55, 0x37, 0xB4, 0xAA, 0xE6, 0xD1, 0x2F, 0x75, 0xA8, 0xE9, 0x11, 0x37, 0x99, 0xEB, 0x82, - 0x1A, 0x9F, 0x0B, 0x6B, 0xFC, 0x96, 0x55, 0x43, 0x65, 0x4D, 0x79, 0xAE, 0x2C, 0x7E, 0x59, 0x45, - 0x12, 0x34, 0x78, 0xB1, 0xBE, 0x05, 0x7E, 0x21, 0x57, 0x00, 0x62, 0xB9, 0x36, 0x99, 0x78, 0xAE, - 0x17, 0x4C, 0x2D, 0x58, 0xC5, 0xB1, 0x40, 0x59, 0x66, 0x86, 0xB5, 0x95, 0x7A, 0xA2, 0x28, 0x52, - 0xB4, 0x6D, 0xA6, 0x1A, 0x88, 0xA4, 0xCE, 0xC9, 0xD4, 0x8F, 0xCA, 0x90, 0x36, 0x2A, 0xA9, 0x99, - 0x81, 0xEF, 0x27, 0x64, 0xA1, 0x4D, 0x9A, 0x2F, 0x37, 0x19, 0x2F, 0xAD, 0xE8, 0xF1, 0xC5, 0xD2, - 0x93, 0x2B, 0x98, 0x9E, 0xCF, 0xF3, 0xA9, 0xDD, 0xC8, 0x7B, 0x8F, 0x91, 0xED, 0x84, 0xCC, 0x4D, - 0x56, 0x94, 0xBE, 0x24, 0xBF, 0xAE, 0x7F, 0x5E, 0xFF, 0xAD, 0xA1, 0x26, 0xC1, 0x36, 0xB4, 0xFE, - 0x15, 0xD9, 0x5A, 0x49, 0x1D, 0x0D, 0x65, 0xCA, 0x34, 0x70, 0x7E, 0xA7, 0xC4, 0x64, 0x9A, 0x15, - 0xEB, 0x18, 0xFB, 0xB8, 0xC9, 0xD4, 0x59, 0x75, 0xAE, 0xC4, 0x01, 0xEA, 0x4D, 0x59, 0xDF, 0x4B, - 0x51, 0xCA, 0xE8, 0xAD, 0x82, 0x1A, 0x88, 0xAC, 0xC8, 0x4D, 0x5E, 0x41, 0x1E, 0xA6, 0xFC, 0x0B, - 0xDB, 0xBB, 0x30, 0x4E, 0xB0, 0xA9, 0x9A, 0xD1, 0x18, 0x62, 0xC5, 0xEA, 0x68, 0xCB, 0x14, 0x02, - 0xA6, 0x84, 0xE6, 0xE0, 0xEA, 0xA4, 0xF0, 0xE7, 0x56, 0xE3, 0x7F, 0x99, 0x81, 0x04, 0x35, 0x80, - 0xA5, 0xD6, 0xF0, 0xA6, 0x65, 0xDE, 0x85, 0x94, 0x6D, 0xD2, 0x9F, 0x5B, 0xE1, 0x8D, 0x13, 0xF0, - 0x4D, 0x98, 0xD6, 0x4A, 0xAE, 0x55, 0x77, 0x55, 0xAB, 0xDE, 0x09, 0x66, 0x57, 0x7C, 0x55, 0xD8, - 0x02, 0xFF, 0xA9, 0xB7, 0xA5, 0xBC, 0x71, 0x2D, 0x21, 0x8D, 0x41, 0x61, 0xC2, 0xE3, 0x59, 0xB1, - 0x23, 0x4B, 0x16, 0x4F, 0x81, 0x91, 0xBD, 0xD2, 0x3E, 0x6C, 0x7C, 0xE5, 0x28, 0x92, 0xCD, 0x5E, - 0xCD, 0x09, 0x8F, 0xAE, 0x85, 0x95, 0x73, 0xC4, 0xA3, 0xDA, 0x45, 0xCE, 0xB8, 0x5A, 0xAF, 0x8C, - 0x43, 0x1E, 0x41, 0x2C, 0xCA, 0x29, 0x4F, 0xDE, 0x48, 0xCA, 0x75, 0x67, 0xE3, 0x03, 0x07, 0xF0, - 0x68, 0x7B, 0xBD, 0xEE, 0xC6, 0x66, 0x9B, 0xBC, 0x78, 0xA1, 0x50, 0xE7, 0x9F, 0x91, 0x05, 0x2C, - 0xA8, 0xEC, 0x34, 0xEB, 0xE7, 0x0E, 0x48, 0x07, 0xDF, 0xB3, 0xDF, 0x52, 0x48, 0xF0, 0x07, 0xED, - 0xB7, 0xEA, 0x62, 0xD7, 0x0E, 0x1D, 0x80, 0x46, 0xBF, 0xDB, 0xED, 0x74, 0xD9, 0x2D, 0xAF, 0x8E, - 0xE2, 0x99, 0xB3, 0x0F, 0x48, 0x8C, 0xFD, 0xF2, 0xE4, 0xBC, 0xF2, 0x58, 0x6F, 0xAA, 0x7B, 0xE6, - 0x11, 0x68, 0x55, 0xEF, 0x3C, 0x56, 0xBC, 0x6A, 0x1E, 0xBA, 0x46, 0x4E, 0xF2, 0xD2, 0xB5, 0x31, - 0xA2, 0xF8, 0xA8, 0xE2, 0x7E, 0xA5, 0xB1, 0x48, 0x5C, 0xA7, 0x34, 0x95, 0x45, 0x5A, 0xAA, 0x95, - 0x69, 0xBA, 0x65, 0x2E, 0x55, 0x75, 0x63, 0x09, 0x3E, 0xBF, 0xD6, 0x67, 0x46, 0xBF, 0x3F, 0xAE, - 0xA3, 0xF8, 0xFE, 0xFF, 0x00, 0x92, 0x32, 0xAD, 0x24, 0x22, 0xB6, 0xB3, 0x57, 0x13, 0x29, 0xA3, - 0x5A, 0xE4, 0x0C, 0xEB, 0x4A, 0x58, 0xD9, 0x21, 0x8E, 0xD5, 0xF1, 0xE9, 0x2C, 0x2C, 0xE2, 0xDE, - 0x2D, 0xBF, 0xB8, 0xC8, 0x93, 0xAC, 0x74, 0x72, 0x29, 0x2F, 0xD3, 0x6E, 0x2C, 0x7A, 0x42, 0xC3, - 0x1B, 0xCF, 0xCE, 0xBC, 0x20, 0xA5, 0xDC, 0x8E, 0x4A, 0xEE, 0x05, 0xBF, 0x8C, 0xAE, 0x01, 0xA7, - 0x9E, 0xDF, 0x01, 0x94, 0x89, 0xFC, 0x25, 0xFC, 0x5A, 0xCC, 0x69, 0xFA, 0x34, 0x36, 0x3B, 0xBE, - 0xC7, 0x74, 0xC0, 0x9D, 0x19, 0xC0, 0x03, 0x24, 0xE1, 0x9B, 0x78, 0x93, 0x26, 0xE3, 0x06, 0xF5, - 0x0A, 0x59, 0x23, 0x2D, 0xA9, 0x4A, 0xFA, 0x78, 0x5B, 0x54, 0x5B, 0x25, 0xE9, 0x5A, 0xE6, 0xE0, - 0xFB, 0x15, 0xB2, 0x4E, 0x7A, 0xFC, 0x56, 0x70, 0x1E, 0xDB, 0xD1, 0xCC, 0x0F, 0x5C, 0x76, 0x30, - 0x0E, 0x03, 0x4A, 0x5B, 0x1B, 0x79, 0x97, 0x51, 0x73, 0xE5, 0x94, 0xBE, 0x78, 0x9F, 0x23, 0x26, - 0x4D, 0xA8, 0x66, 0x39, 0xE9, 0xAC, 0xA2, 0x08, 0x1E, 0x51, 0x52, 0xA9, 0x8B, 0xEE, 0x66, 0x39, - 0xE9, 0x4B, 0xE7, 0x27, 0x6B, 0x31, 0x66, 0x93, 0x33, 0x01, 0xC7, 0xE0, 0x95, 0x15, 0x44, 0x44, - 0x31, 0xAE, 0xF4, 0x7A, 0x8F, 0x6C, 0x9B, 0xA6, 0xEA, 0xB1, 0x15, 0x46, 0x58, 0x92, 0xCA, 0x6B, - 0x64, 0x13, 0x05, 0xD9, 0x37, 0x3C, 0x09, 0x96, 0x39, 0x35, 0x1B, 0x96, 0xCF, 0x05, 0xF9, 0x0B, - 0xD2, 0x4B, 0xFE, 0x88, 0xEB, 0x98, 0xA9, 0xD7, 0xE9, 0x58, 0x72, 0x65, 0x61, 0x34, 0x81, 0xD5, - 0x60, 0xC4, 0x7E, 0x6F, 0x57, 0xFA, 0xF3, 0x55, 0xDC, 0x30, 0xE9, 0x6B, 0xFA, 0x1C, 0xA0, 0x90, - 0xCB, 0xD5, 0x3D, 0xC5, 0x67, 0x4F, 0x08, 0xAC, 0x12, 0x9D, 0xF1, 0xF4, 0x9E, 0xFF, 0x43, 0x86, - 0x24, 0xD2, 0xD9, 0x19, 0x84, 0x28, 0x00, 0x6B, 0xC4, 0xF6, 0xD3, 0x92, 0x8A, 0xCE, 0x71, 0x9E, - 0x58, 0xB2, 0x1B, 0x51, 0x5A, 0x42, 0x06, 0x9B, 0xC6, 0x09, 0x49, 0x1A, 0xBA, 0x61, 0x50, 0xB4, - 0x0C, 0x03, 0x62, 0x02, 0xEF, 0x67, 0x83, 0x67, 0x5E, 0x80, 0x4A, 0x21, 0xE9, 0x7D, 0xD5, 0xAE, - 0xA8, 0xD5, 0x49, 0x94, 0x11, 0x65, 0xBA, 0xA8, 0x80, 0xA7, 0x9F, 0x93, 0x70, 0x43, 0xC1, 0xA3, - 0xC4, 0x14, 0x65, 0xBD, 0x74, 0x65, 0x18, 0xD4, 0xBB, 0x99, 0x75, 0xD5, 0x51, 0x52, 0x54, 0x2F, - 0xD6, 0x9B, 0x82, 0x8A, 0x49, 0x87, 0x67, 0x57, 0x4C, 0x75, 0x6E, 0x61, 0x55, 0xBD, 0x23, 0x4B, - 0x06, 0xC2, 0x28, 0x29, 0x1A, 0x02, 0x6E, 0xDE, 0x23, 0xD1, 0x10, 0x66, 0x8D, 0xF9, 0x4A, 0x3C, - 0x23, 0x2C, 0x26, 0xBD, 0x53, 0x17, 0x03, 0xD7, 0xDA, 0xAD, 0x53, 0x3D, 0xAF, 0xDC, 0x1D, 0x3B, - 0xC9, 0x2A, 0xE7, 0xED, 0xDA, 0x69, 0xCB, 0x82, 0xD2, 0x3B, 0x77, 0x26, 0xFD, 0xD0, 0x75, 0x67, - 0x89, 0x3B, 0x78, 0x35, 0xC9, 0xC7, 0x3B, 0x79, 0xA0, 0xB5, 0xEB, 0xA8, 0x91, 0xEB, 0xA0, 0x6D, - 0xE6, 0x0D, 0x3D, 0x5D, 0x30, 0x25, 0x37, 0xF5, 0x24, 0xB1, 0xAB, 0x1B, 0x7B, 0xFA, 0x9A, 0xB9, - 0xE2, 0xE6, 0x5E, 0x99, 0x25, 0xF7, 0x22, 0x37, 0xF8, 0xB4, 0x85, 0xF3, 0x7C, 0x9B, 0x7C, 0x92, - 0x50, 0xAA, 0x6D, 0xF4, 0xC5, 0xA3, 0xE5, 0x87, 0x6E, 0xF6, 0x29, 0xEA, 0x21, 0xFB, 0x57, 0xE8, - 0x49, 0x3D, 0xD7, 0x67, 0xCC, 0x85, 0x6D, 0x09, 0xA6, 0xE6, 0x4B, 0xB5, 0xC2, 0xA6, 0x0E, 0xB1, - 0xA5, 0x7F, 0xD8, 0x2E, 0x42, 0xF1, 0x5C, 0xD9, 0x76, 0x2C, 0x34, 0x86, 0x4B, 0xD9, 0xEC, 0x94, - 0x74, 0xE0, 0x21, 0x95, 0xE9, 0x48, 0xD9, 0xB5, 0x94, 0x12, 0xAF, 0x70, 0x97, 0x1B, 0xFE, 0x95, - 0x7A, 0xB6, 0xF3, 0xEF, 0x9E, 0xE3, 0xB6, 0x9A, 0xCD, 0x38, 0xEB, 0x8C, 0x56, 0xA5, 0x5D, 0x5C, - 0xE5, 0x6F, 0x7F, 0x4B, 0xD5, 0xF1, 0x69, 0x38, 0xF3, 0x5D, 0xE6, 0xAE, 0x6B, 0xDC, 0x8D, 0x68, - 0x88, 0xB9, 0x6E, 0x14, 0x53, 0xCC, 0xD2, 0x0C, 0x81, 0xC9, 0x02, 0xED, 0x89, 0xCA, 0xD0, 0xA2, - 0xF2, 0x69, 0x5D, 0x33, 0x6B, 0x4A, 0x1D, 0xB1, 0xC2, 0x92, 0x36, 0xED, 0x8E, 0xAF, 0xD9, 0x26, - 0x5D, 0x68, 0x5D, 0x91, 0x3B, 0x2B, 0x20, 0x38, 0x77, 0x13, 0xCC, 0x8F, 0x43, 0xED, 0x36, 0xE1, - 0x43, 0x1A, 0xCB, 0xC6, 0x94, 0x27, 0x52, 0x0A, 0x86, 0x3E, 0xBE, 0xCF, 0xA2, 0x2D, 0x74, 0xC6, - 0xF4, 0xC4, 0x72, 0xAD, 0x11, 0xF5, 0x2F, 0xB0, 0x66, 0x2A, 0x13, 0x0E, 0x83, 0x7F, 0xFD, 0x2A, - 0xF4, 0x89, 0x35, 0x76, 0x46, 0xEE, 0x5E, 0x73, 0x4C, 0xAF, 0xC3, 0x26, 0x7C, 0xB8, 0x79, 0x8D, - 0xFB, 0x07, 0xAF, 0xD6, 0xE1, 0x17, 0xFC, 0x63, 0x00, 0x86, 0x4C, 0xFC, 0x61, 0xBF, 0x7E, 0xC5, - 0x5F, 0xD4, 0x0B, 0xEF, 0xA7, 0x74, 0x8F, 0x7B, 0x1C, 0x57, 0xDE, 0xF7, 0x26, 0x71, 0xEC, 0xBD, - 0x26, 0x12, 0xE4, 0xCB, 0xF9, 0xFD, 0xF1, 0xB8, 0x49, 0xD8, 0x83, 0x8F, 0xF0, 0xD5, 0xF3, 0x27, - 0x6B, 0xAC, 0xE2, 0x1A, 0x07, 0x95, 0xF8, 0x62, 0x3B, 0x93, 0x4D, 0x22, 0x02, 0x17, 0x39, 0x86, - 0x88, 0x65, 0x6F, 0x34, 0xC2, 0xCC, 0x24, 0xC0, 0xCF, 0x3A, 0x92, 0x5D, 0x0F, 0xFD, 0xF8, 0x1F, - 0xC6, 0xB7, 0x64, 0xB6, 0x11, 0x8C, 0x35, 0xF1, 0x82, 0xDB, 0xB0, 0x46, 0x43, 0x12, 0xE5, 0xF7, - 0xC9, 0xF8, 0x26, 0x0C, 0xA7, 0xB9, 0xD9, 0x87, 0xA4, 0x7A, 0x22, 0x09, 0xD1, 0xBB, 0x23, 0x9E, - 0x83, 0x08, 0x67, 0x61, 0x96, 0x22, 0xA9, 0x91, 0xF6, 0xCE, 0x22, 0x08, 0x96, 0x5A, 0x46, 0xB6, - 0x13, 0x22, 0x6C, 0x6C, 0xE8, 0x4D, 0xD0, 0x8C, 0x44, 0xD5, 0x7C, 0x1A, 0x4C, 0xC1, 0x9D, 0x60, - 0x4C, 0xAE, 0x60, 0xC8, 0x8E, 0x48, 0xB8, 0x45, 0x0E, 0x06, 0x3F, 0xF3, 0x14, 0x59, 0x8E, 0x1B, - 0x7A, 0x6C, 0xF2, 0x7F, 0x56, 0xBE, 0x23, 0xC1, 0xFD, 0x56, 0x9A, 0x5F, 0x30, 0x99, 0xAA, 0x5A, - 0x97, 0xF9, 0xD4, 0x0D, 0x28, 0xB7, 0x48, 0x3D, 0x99, 0xD2, 0x6F, 0xEF, 0x2A, 0xB4, 0x1C, 0x50, - 0x43, 0xA9, 0xDC, 0xA8, 0xE3, 0xC6, 0x7A, 0xBA, 0x9E, 0x4B, 0x39, 0xFC, 0xB0, 0x82, 0xAE, 0xA2, - 0x52, 0x1F, 0x8B, 0x5A, 0x52, 0x0F, 0xC7, 0xCD, 0xC2, 0x44, 0x5B, 0x27, 0xC9, 0x5B, 0x3E, 0x68, - 0xFF, 0xBF, 0x7C, 0x35, 0x97, 0xB3, 0x0D, 0x79, 0x63, 0x85, 0xE8, 0x89, 0xD6, 0x4C, 0x0C, 0x51, - 0x05, 0x05, 0xC5, 0x02, 0xB5, 0x2C, 0x4A, 0x66, 0xFB, 0x03, 0x15, 0x2D, 0xAB, 0x27, 0x56, 0xF7, - 0x64, 0xF1, 0x2B, 0x66, 0x8A, 0x9C, 0xD3, 0xA9, 0xC0, 0x6E, 0xB9, 0xF7, 0x5C, 0x52, 0x51, 0x65, - 0x76, 0xE4, 0x10, 0x30, 0xDF, 0xF6, 0x96, 0x0B, 0x6D, 0x8D, 0x38, 0x1D, 0xDA, 0x61, 0x86, 0x4D, - 0x7C, 0x41, 0xD7, 0x07, 0xFF, 0xC4, 0x24, 0xFA, 0x84, 0xBF, 0x23, 0x40, 0x6C, 0x71, 0xA3, 0x36, - 0xC8, 0x8D, 0xF0, 0x4B, 0x75, 0x7B, 0xD6, 0x9B, 0x50, 0xA2, 0x75, 0xA9, 0xFA, 0x5F, 0xBE, 0x7F, - 0x4D, 0xD6, 0x73, 0x69, 0x25, 0xF9, 0x22, 0x7B, 0x3D, 0x0F, 0x85, 0x6D, 0x8E, 0x4C, 0x61, 0x95, - 0x46, 0x63, 0xBE, 0x4A, 0xC2, 0xA3, 0xEC, 0x13, 0x04, 0xB4, 0x44, 0xB3, 0x15, 0x65, 0xCD, 0x6B, - 0x37, 0x9B, 0x90, 0xD2, 0xDA, 0x2B, 0x82, 0x34, 0x1B, 0x38, 0x1A, 0xB3, 0xF2, 0x8B, 0xA4, 0x09, - 0x31, 0x81, 0x65, 0x65, 0xE3, 0xCC, 0xC9, 0x6C, 0x92, 0xC3, 0x00, 0xD3, 0xF4, 0xDA, 0x1C, 0x68, - 0x3B, 0x8D, 0x15, 0x22, 0x2D, 0xA5, 0x55, 0xDD, 0x71, 0x10, 0xCC, 0x44, 0x98, 0xFB, 0xF1, 0xE1, - 0x4B, 0xB6, 0x83, 0x6E, 0x26, 0x5C, 0x25, 0x2C, 0x5F, 0xB5, 0x9F, 0x6F, 0xD8, 0x63, 0xDB, 0xF9, - 0x36, 0x14, 0xEB, 0x94, 0xB5, 0xA3, 0xBC, 0x6E, 0x81, 0x2D, 0xC5, 0x4A, 0xF5, 0xED, 0xE9, 0x93, - 0x31, 0x87, 0xAC, 0x19, 0x4F, 0xC2, 0x24, 0xA6, 0x04, 0x5A, 0xD5, 0x2C, 0xFE, 0x61, 0x22, 0xFE, - 0x30, 0x11, 0x72, 0x1E, 0xC8, 0xD8, 0xAB, 0x3B, 0x84, 0xC9, 0x0E, 0x8F, 0x17, 0x92, 0x24, 0x90, - 0xE2, 0x3E, 0x0E, 0xBA, 0x6A, 0xE5, 0x73, 0x0A, 0x22, 0xC6, 0xE3, 0x43, 0xE9, 0xC8, 0x8C, 0xB3, - 0xE3, 0xB2, 0xF3, 0x92, 0x8F, 0xD7, 0x0C, 0xD9, 0x20, 0xBA, 0xE9, 0x53, 0x1B, 0xA9, 0xB2, 0x81, - 0x81, 0xA5, 0xFC, 0x3C, 0x26, 0x89, 0x1B, 0xC0, 0x21, 0xF9, 0x96, 0x67, 0x15, 0x84, 0x91, 0x30, - 0xC0, 0x94, 0xBB, 0x98, 0xF4, 0x12, 0xDC, 0x3F, 0x2C, 0x51, 0x73, 0xF6, 0xC6, 0x55, 0x25, 0xE3, - 0x28, 0xE3, 0xDC, 0x33, 0x73, 0xBF, 0x22, 0xD6, 0x65, 0xC9, 0x69, 0xE0, 0xCC, 0x1F, 0x87, 0x98, - 0xA6, 0x03, 0x57, 0x74, 0x8D, 0x75, 0x44, 0xF1, 0x2F, 0x8C, 0x7D, 0xD6, 0x57, 0xB2, 0x34, 0xBF, - 0x24, 0xE8, 0xF1, 0x41, 0x48, 0x3C, 0x24, 0xFD, 0xC9, 0x62, 0xDC, 0xEC, 0xD9, 0xA2, 0x1B, 0x84, - 0x2D, 0x94, 0x55, 0x20, 0x46, 0x2F, 0x64, 0x0A, 0x7E, 0x34, 0xD4, 0xEE, 0x44, 0xE7, 0xAE, 0x9D, - 0x1B, 0x9F, 0x5E, 0xE3, 0xCB, 0xAA, 0x51, 0xB5, 0xE8, 0xB6, 0x66, 0x4C, 0x0B, 0x1F, 0xB5, 0xCF, - 0xE8, 0xFD, 0x68, 0xA5, 0x23, 0x1D, 0x29, 0x25, 0x03, 0xBD, 0x6A, 0x3F, 0x65, 0xA5, 0x92, 0xC4, - 0x2D, 0xA1, 0x04, 0xAB, 0x92, 0x4C, 0xD2, 0x4D, 0xA5, 0x92, 0x4C, 0x2A, 0x62, 0x3A, 0xC9, 0x64, - 0x30, 0x45, 0x8B, 0x91, 0x78, 0x91, 0x97, 0x5C, 0x42, 0xDC, 0x2D, 0xD0, 0x70, 0xAA, 0x26, 0x39, - 0x5D, 0x94, 0x7E, 0x3F, 0xCB, 0x3A, 0x11, 0x4F, 0xF5, 0x7B, 0xB4, 0x65, 0x03, 0xFD, 0xCD, 0x18, - 0x0B, 0xC4, 0xD1, 0x78, 0x6A, 0xBE, 0x95, 0x35, 0x4B, 0xDF, 0x9F, 0x93, 0x8D, 0xAC, 0x01, 0xB9, - 0xF1, 0x39, 0xD2, 0xF2, 0x9A, 0xF9, 0x3D, 0xA5, 0x90, 0xAC, 0x2D, 0x8D, 0xDD, 0xD2, 0xD3, 0x6A, - 0xEE, 0xBC, 0x1A, 0x33, 0x52, 0x3C, 0x9B, 0x4A, 0x6D, 0x5F, 0x5F, 0x3F, 0xA7, 0xD7, 0x30, 0x7D, - 0xDE, 0xF0, 0x4C, 0xC2, 0x71, 0xD0, 0x50, 0xEE, 0x62, 0x52, 0xD9, 0x1B, 0xD9, 0xD5, 0x2E, 0x88, - 0xF2, 0xA4, 0xBE, 0x9A, 0xC6, 0x4B, 0x69, 0x8A, 0xF1, 0xD7, 0xDE, 0x42, 0x32, 0x13, 0x73, 0x4C, - 0x35, 0x93, 0x11, 0x73, 0x46, 0x0D, 0x17, 0x9F, 0x61, 0x62, 0x1F, 0xF9, 0x98, 0x28, 0xBB, 0x4D, - 0xA2, 0x5F, 0xDF, 0xC3, 0x5C, 0x3E, 0xA6, 0xBE, 0x2A, 0x58, 0x86, 0x25, 0x0D, 0xCE, 0x2C, 0x4C, - 0x1B, 0xAC, 0x0B, 0xCF, 0xE9, 0x5B, 0x05, 0x94, 0x1F, 0x35, 0xB4, 0xF9, 0x91, 0x43, 0x15, 0x40, - 0xEB, 0xCA, 0xF3, 0x43, 0x00, 0x64, 0xFF, 0xCF, 0x06, 0xCC, 0xC8, 0xBF, 0x3C, 0xCE, 0xCF, 0xBD, - 0x2C, 0x25, 0xB0, 0xD7, 0xE4, 0x11, 0x65, 0xCA, 0x4D, 0xBA, 0x79, 0x4A, 0xFD, 0x21, 0xBF, 0xFD, - 0xCA, 0xCB, 0x3A, 0x63, 0x96, 0x55, 0x97, 0xAC, 0x13, 0xFE, 0x67, 0xE8, 0x85, 0xB8, 0xCB, 0xF7, - 0x17, 0x3C, 0xB8, 0x97, 0x12, 0x0E, 0x0B, 0xB4, 0x6F, 0x2C, 0x5F, 0x3A, 0x82, 0x3B, 0xB1, 0xC2, - 0x9B, 0x8E, 0xEF, 0xCD, 0x80, 0x1B, 0x81, 0x76, 0x25, 0x01, 0x99, 0x49, 0x29, 0x9B, 0x35, 0x2F, - 0xD8, 0x00, 0x87, 0x83, 0xEE, 0x7F, 0x0B, 0xD5, 0xA4, 0x36, 0x4F, 0x04, 0x1D, 0x6F, 0xF8, 0x0A, - 0x96, 0x5F, 0xE3, 0xA3, 0x09, 0xDD, 0xD4, 0xDB, 0x2F, 0xD9, 0x74, 0x1A, 0x67, 0x49, 0x6E, 0xE9, - 0x36, 0xB9, 0xC3, 0x0B, 0x56, 0xEE, 0x88, 0x2B, 0x3A, 0xB8, 0x9A, 0x4C, 0xBB, 0xEF, 0xA1, 0x83, - 0x26, 0x0D, 0xC3, 0x7B, 0x9E, 0x9A, 0x76, 0xA8, 0x82, 0x2C, 0xA2, 0xCB, 0x93, 0x46, 0x93, 0x38, - 0x6B, 0x74, 0x91, 0x20, 0xA3, 0x63, 0xA1, 0x45, 0x0C, 0x75, 0xF6, 0x2D, 0x36, 0xE7, 0x98, 0xF3, - 0x99, 0x1F, 0x3C, 0xBC, 0xB9, 0x3F, 0xB6, 0xAB, 0x72, 0x2D, 0xC9, 0x43, 0xD6, 0xF8, 0x5A, 0xC2, - 0x78, 0x6B, 0x01, 0x93, 0xB6, 0x8A, 0x54, 0x1E, 0x0D, 0xB5, 0x90, 0xEE, 0x23, 0x02, 0x81, 0x35, - 0x41, 0x1B, 0x0E, 0x45, 0xBE, 0xC4, 0x37, 0x38, 0x83, 0x2A, 0xAE, 0x4D, 0x72, 0x8B, 0xFF, 0x62, - 0x58, 0x2A, 0x8F, 0x23, 0x3F, 0xFF, 0x8A, 0x31, 0xAA, 0x91, 0xA6, 0xC6, 0x4D, 0x3E, 0x7E, 0x60, - 0x97, 0x01, 0x11, 0xB5, 0x2D, 0x2E, 0xC6, 0xBC, 0x78, 0xF2, 0xE3, 0x1E, 0x3B, 0x9B, 0x2F, 0x4C, - 0x33, 0x39, 0xD4, 0xE7, 0x89, 0x2F, 0x0B, 0x9A, 0x54, 0x26, 0x21, 0x67, 0xDC, 0x24, 0x53, 0x2A, - 0xCD, 0xFC, 0x26, 0x65, 0x25, 0xDF, 0x1C, 0xC9, 0xC5, 0x25, 0x9B, 0x34, 0xB3, 0xA7, 0x79, 0x8D, - 0xF9, 0x64, 0x57, 0x68, 0xCC, 0xCC, 0xCE, 0xC8, 0x98, 0x97, 0xD3, 0x9C, 0x4C, 0x98, 0x58, 0xFF, - 0x6C, 0x73, 0x83, 0x7A, 0xDD, 0x5E, 0xAF, 0x6B, 0x6A, 0x91, 0x7D, 0x33, 0x9C, 0x1E, 0xC1, 0x9A, - 0xD1, 0x77, 0x69, 0x98, 0x6E, 0x95, 0x28, 0x38, 0x7C, 0x7F, 0x70, 0x96, 0xDF, 0x24, 0x29, 0x29, - 0xF4, 0xF1, 0x99, 0xCA, 0x62, 0x84, 0x3D, 0xB7, 0x79, 0x52, 0xB2, 0xE6, 0x62, 0x78, 0x99, 0x7F, - 0x28, 0xC0, 0xAB, 0xDC, 0x2C, 0x8A, 0x59, 0xF5, 0x86, 0xB5, 0x30, 0x77, 0xFC, 0x76, 0xE7, 0x5C, - 0x3B, 0xA7, 0x1C, 0xA0, 0xF4, 0x83, 0xF3, 0x12, 0xCC, 0xD7, 0x26, 0xF9, 0xE9, 0x27, 0x12, 0x97, - 0xFD, 0x79, 0x6F, 0x30, 0xD0, 0x9D, 0x67, 0xCD, 0xDF, 0x93, 0x09, 0x66, 0xB9, 0x7B, 0x28, 0x6C, - 0xB9, 0x1E, 0x7A, 0x74, 0xAC, 0xDB, 0xE2, 0xF3, 0x42, 0xD2, 0x5D, 0xD1, 0xE2, 0x0B, 0xA0, 0x69, - 0xB8, 0x36, 0x90, 0x3C, 0xAE, 0xE8, 0xC8, 0x89, 0x95, 0x69, 0xDE, 0x13, 0xEB, 0xB7, 0x53, 0x7A, - 0x17, 0x25, 0x30, 0x52, 0xFA, 0x59, 0xCF, 0x8A, 0xF2, 0xA7, 0xF8, 0xE9, 0x33, 0x68, 0x6B, 0x4B, - 0x13, 0x2F, 0x3B, 0xE8, 0xD7, 0x55, 0xF9, 0x24, 0x18, 0xF1, 0xB0, 0x89, 0x66, 0xF4, 0x00, 0xC1, - 0x81, 0x20, 0x08, 0x25, 0xF8, 0xC2, 0xC5, 0x2F, 0xCE, 0x5B, 0x87, 0x4D, 0x0C, 0x78, 0xAD, 0x9B, - 0x4E, 0xA6, 0xE1, 0xBD, 0xAC, 0xB6, 0x06, 0xD7, 0x59, 0x64, 0x45, 0x3B, 0xD0, 0xF8, 0xCE, 0x0A, - 0x2F, 0x14, 0x4C, 0x64, 0xD2, 0x07, 0x3D, 0x72, 0xF1, 0xD2, 0x32, 0x4C, 0xA4, 0x30, 0x87, 0x8A, - 0x26, 0x35, 0xB4, 0x27, 0x62, 0x50, 0x3F, 0xC4, 0x7B, 0x00, 0xB8, 0xBD, 0x23, 0x9D, 0x07, 0xC1, - 0x72, 0x14, 0x9C, 0x17, 0x32, 0x9C, 0xF9, 0x3E, 0x4E, 0xE9, 0xAC, 0x35, 0xD8, 0xF3, 0x6C, 0x2F, - 0xE6, 0xEC, 0x97, 0x20, 0x51, 0xB9, 0x71, 0x20, 0x26, 0xAE, 0x7A, 0xDA, 0x95, 0xA3, 0x48, 0x12, - 0xEA, 0x2C, 0x3D, 0x92, 0x98, 0x5F, 0xDD, 0x93, 0x01, 0x92, 0x25, 0x42, 0x1B, 0x97, 0x0F, 0x5A, - 0x49, 0x14, 0x2E, 0x0D, 0x85, 0x4A, 0x1F, 0xA8, 0x96, 0xED, 0xFC, 0x40, 0xEA, 0x04, 0xDD, 0x0E, - 0x64, 0xB0, 0x90, 0x02, 0x6D, 0x63, 0xED, 0x88, 0x0C, 0x1A, 0x80, 0xD2, 0x80, 0xAC, 0xA7, 0xDA, - 0x51, 0x87, 0x68, 0xB5, 0x75, 0xF5, 0xC6, 0x27, 0x1A, 0xA2, 0x5B, 0x29, 0xF2, 0x86, 0x4C, 0x54, - 0xCE, 0x36, 0x10, 0x00, 0x5E, 0xEC, 0xC9, 0xC4, 0xC8, 0x56, 0x8C, 0xAF, 0x3F, 0x28, 0xE5, 0xC9, - 0x5D, 0x0D, 0x89, 0x9E, 0xF1, 0x3D, 0x9E, 0x14, 0x53, 0xA6, 0x57, 0x79, 0x58, 0x25, 0x40, 0x6E, - 0x1A, 0x9B, 0xCA, 0xAB, 0x32, 0x19, 0x24, 0x57, 0xD2, 0x7B, 0x0E, 0x8D, 0x03, 0x84, 0x63, 0xAA, - 0x2E, 0xD8, 0x42, 0x75, 0xCA, 0x80, 0x8F, 0x6F, 0x83, 0x14, 0x8E, 0x20, 0xC1, 0x68, 0xFC, 0x0E, - 0x09, 0x81, 0x29, 0x26, 0x60, 0xC1, 0x15, 0xBB, 0x9A, 0x39, 0x8E, 0x69, 0xFC, 0xCC, 0x6B, 0xB4, - 0xAE, 0xD5, 0xBF, 0x95, 0xF6, 0x65, 0xD2, 0x94, 0xDE, 0x66, 0xD4, 0xE0, 0xF9, 0xDB, 0x8C, 0xE7, - 0xE7, 0x1F, 0xCF, 0x1B, 0x95, 0x0D, 0x91, 0x18, 0x69, 0x04, 0x24, 0xC2, 0x67, 0x49, 0x96, 0xBB, - 0xC3, 0xBA, 0x05, 0x37, 0x8F, 0x1F, 0x91, 0xEE, 0x6A, 0x13, 0x9A, 0xED, 0xDC, 0xBE, 0xA3, 0xA1, - 0x62, 0x82, 0xD4, 0xF9, 0xB6, 0xD0, 0x4A, 0x69, 0x5B, 0x89, 0xAA, 0xB1, 0x53, 0x76, 0x33, 0x4D, - 0xED, 0x3C, 0xF8, 0x74, 0x7E, 0x7E, 0x74, 0x7A, 0x61, 0x6A, 0x69, 0x66, 0x23, 0x63, 0x35, 0x07, - 0x63, 0x3B, 0x9B, 0xA2, 0xC5, 0xE3, 0x69, 0xF4, 0x1E, 0xB9, 0x6D, 0xB2, 0x21, 0xD7, 0xE9, 0xE8, - 0x7E, 0x31, 0x8B, 0x66, 0x03, 0x46, 0x6F, 0x1B, 0xEC, 0xAE, 0x87, 0x22, 0x87, 0xDD, 0x5C, 0x44, - 0x66, 0x6E, 0xD4, 0x27, 0x76, 0x10, 0xFB, 0x59, 0xD6, 0x1A, 0xC6, 0xAC, 0x8A, 0x2A, 0x90, 0xA4, - 0x90, 0x22, 0x35, 0xB6, 0x49, 0x74, 0xDA, 0x49, 0x8E, 0x3E, 0xA6, 0x9F, 0xCE, 0x84, 0xFB, 0xE3, - 0xBB, 0xDF, 0x38, 0x8B, 0x97, 0x32, 0x08, 0x29, 0xE5, 0x4F, 0xF7, 0x14, 0xB4, 0xFF, 0x9D, 0x78, - 0xD7, 0x07, 0x37, 0x6B, 0xAE, 0xA5, 0xE6, 0xFD, 0x31, 0xD1, 0xAB, 0x13, 0xBD, 0x61, 0x32, 0x55, - 0x35, 0x77, 0xD9, 0x53, 0xA9, 0x4A, 0xCD, 0x38, 0x91, 0x6A, 0x0C, 0x99, 0xA6, 0xD1, 0x11, 0xC7, - 0x2B, 0x8F, 0x38, 0x29, 0xEC, 0x2B, 0xBA, 0x37, 0x81, 0xF6, 0xB4, 0xD7, 0xC8, 0x7C, 0xB8, 0xCD, - 0xC8, 0xCA, 0x8A, 0xD1, 0x52, 0x47, 0xD8, 0xD2, 0xB3, 0xD0, 0x92, 0x86, 0x56, 0x91, 0xC9, 0x67, - 0xD6, 0x20, 0x12, 0x83, 0xAA, 0xF5, 0xCA, 0x3B, 0x8A, 0xEA, 0xD2, 0xC9, 0x0B, 0xAD, 0x88, 0xBA, - 0x78, 0xA5, 0x4C, 0xEC, 0x5A, 0x45, 0xBB, 0x29, 0x46, 0x57, 0x24, 0x4B, 0x4E, 0xE5, 0x87, 0xA7, - 0x46, 0x85, 0x6F, 0xA3, 0xE9, 0xAF, 0x95, 0x95, 0x35, 0xE1, 0x1A, 0x32, 0x69, 0x73, 0x5F, 0x27, - 0xB3, 0x97, 0xDA, 0x8A, 0xD3, 0x1E, 0x45, 0x54, 0x92, 0x25, 0xBC, 0x73, 0x6E, 0xA9, 0x4B, 0x2C, - 0x7C, 0x5D, 0xD3, 0x6F, 0x06, 0x22, 0xBF, 0x17, 0x0A, 0xF3, 0x1E, 0x27, 0x2A, 0xE8, 0x66, 0x37, - 0x74, 0xAE, 0x79, 0x96, 0x2D, 0x8C, 0x6A, 0x63, 0x26, 0x42, 0xAC, 0xC1, 0xF0, 0x5B, 0x12, 0xB0, - 0x4C, 0x1C, 0x97, 0x1C, 0x1E, 0x76, 0xEC, 0xE8, 0x87, 0x6D, 0x38, 0x5B, 0x52, 0x90, 0x76, 0x84, - 0xEB, 0x18, 0x6D, 0x05, 0x3E, 0x88, 0xD3, 0x42, 0x9A, 0x47, 0x2E, 0x90, 0x52, 0xD7, 0xB3, 0x11, - 0xC6, 0xB8, 0x22, 0x34, 0x5F, 0x7D, 0x4A, 0x27, 0xE8, 0x1C, 0x7C, 0xFC, 0x78, 0x7E, 0x78, 0x7C, - 0xBA, 0x7F, 0x71, 0x74, 0x79, 0x7C, 0x7A, 0xF6, 0xE9, 0xE2, 0xF2, 0xE2, 0xF3, 0x19, 0xFE, 0xFA, - 0xF3, 0xFE, 0x87, 0xE3, 0xC3, 0xCB, 0x4F, 0xA7, 0x7F, 0x3D, 0xFD, 0xF8, 0xCB, 0x69, 0xB2, 0xA9, - 0x6D, 0x5B, 0xC1, 0x8D, 0x74, 0x1F, 0x3C, 0x36, 0x8F, 0x18, 0x50, 0x6E, 0xF8, 0x6E, 0xD3, 0xA1, - 0x33, 0xB1, 0xC6, 0x86, 0x12, 0x6E, 0xAA, 0x3E, 0x5E, 0x7F, 0xA0, 0xEC, 0x89, 0x3C, 0xFD, 0x74, - 0x0F, 0x8C, 0x07, 0x78, 0x5B, 0x21, 0xBE, 0xC4, 0x14, 0xCB, 0x85, 0xBF, 0x07, 0x83, 0x37, 0x8D, - 0x51, 0xBD, 0xC8, 0xDD, 0x8D, 0xC5, 0xA6, 0xAB, 0x11, 0x13, 0x3C, 0xC8, 0x78, 0x16, 0xC4, 0x56, - 0x79, 0x68, 0xB9, 0x84, 0xA2, 0x40, 0x98, 0xC9, 0x74, 0x5C, 0xE8, 0x7C, 0xC7, 0x66, 0x77, 0xC6, - 0x03, 0x51, 0x65, 0x9F, 0xF0, 0x6F, 0xBC, 0x16, 0xBE, 0x29, 0xC9, 0xB2, 0xBC, 0xF1, 0x73, 0xC0, - 0xA0, 0x4D, 0xD6, 0xDA, 0x18, 0x4D, 0xDA, 0x66, 0xDD, 0xD4, 0x51, 0xCF, 0xBA, 0x24, 0xE3, 0x1B, - 0x0B, 0x5E, 0x37, 0xBD, 0xEA, 0x25, 0xD9, 0xC0, 0x76, 0x46, 0x4E, 0x98, 0x74, 0x13, 0x1E, 0xE2, - 0x66, 0x5D, 0x98, 0x44, 0x00, 0x55, 0x6C, 0x2C, 0x56, 0xDE, 0x28, 0xAF, 0xD5, 0xD5, 0xBC, 0x37, - 0x33, 0x64, 0x72, 0x88, 0xA5, 0xB9, 0xD6, 0x5C, 0x49, 0x3A, 0x70, 0x75, 0x15, 0x45, 0x89, 0x8F, - 0xF0, 0x8D, 0x3C, 0xCF, 0x2E, 0x01, 0x4D, 0x00, 0x3A, 0xE9, 0xE7, 0xCA, 0xE0, 0x1D, 0x24, 0x2E, - 0xB5, 0x2B, 0x0F, 0x41, 0xB4, 0x37, 0x51, 0x4B, 0x59, 0xC5, 0x79, 0x30, 0x5B, 0xD2, 0x80, 0x1A, - 0x61, 0x58, 0x4B, 0xA0, 0xA8, 0x00, 0x4B, 0x1B, 0xA0, 0x1E, 0x3E, 0x81, 0x77, 0x8F, 0x4A, 0x34, - 0xF5, 0x82, 0xC0, 0xC1, 0x68, 0x57, 0xAE, 0x15, 0x38, 0x4E, 0x23, 0x7D, 0x49, 0x86, 0x64, 0xF2, - 0xE1, 0xE4, 0xA4, 0x33, 0xE1, 0x3F, 0xC9, 0x37, 0x62, 0xFA, 0xB8, 0x66, 0xFA, 0x78, 0x72, 0x32, - 0x18, 0x74, 0x02, 0xF6, 0xA3, 0x80, 0x13, 0xC3, 0x67, 0x40, 0xB0, 0x96, 0x7C, 0x7E, 0x66, 0xD4, - 0x13, 0x76, 0x47, 0x6B, 0x7E, 0xC9, 0xFD, 0x5F, 0x0C, 0x01, 0x76, 0xBD, 0x0E, 0xD9, 0x84, 0xD1, - 0xB6, 0xD3, 0xDD, 0xD8, 0xD8, 0x7C, 0xCE, 0xB2, 0xEB, 0x31, 0xF9, 0x75, 0x92, 0xF8, 0xE3, 0x64, - 0xC4, 0xBF, 0x26, 0xFD, 0x45, 0x10, 0xFE, 0x88, 0xE3, 0xAF, 0xCB, 0x32, 0x3E, 0xC0, 0x48, 0xEB, - 0x13, 0x6B, 0x8C, 0x39, 0x07, 0x6D, 0x64, 0x84, 0x74, 0xB7, 0x48, 0x7F, 0xAB, 0xD3, 0xEF, 0x6E, - 0xEE, 0x18, 0x58, 0x49, 0x6C, 0xD2, 0x6B, 0xB2, 0xB1, 0x60, 0x4E, 0xFA, 0x8C, 0x99, 0x8D, 0x84, - 0x99, 0xB5, 0x5E, 0x77, 0x6B, 0xAD, 0xD7, 0x5B, 0xEB, 0x6E, 0x75, 0x7A, 0xDB, 0x7D, 0x13, 0x3B, - 0x66, 0xCB, 0xF6, 0x1A, 0x43, 0xF8, 0x17, 0xC4, 0xDA, 0x73, 0xE4, 0xE9, 0x9A, 0xDE, 0x51, 0x9F, - 0xF3, 0xD3, 0xEB, 0x21, 0x37, 0x2F, 0x5E, 0xEC, 0xEC, 0xF4, 0x49, 0xEB, 0x90, 0x6B, 0x16, 0x56, - 0xE1, 0xBF, 0xAD, 0xC4, 0x3C, 0x26, 0xEE, 0xAC, 0x4B, 0x47, 0x56, 0x08, 0x76, 0x73, 0xE0, 0x8C, - 0x5C, 0xD5, 0x7B, 0x50, 0xC7, 0x6E, 0x37, 0x31, 0x1C, 0x89, 0x95, 0x8A, 0x8B, 0xB9, 0xDF, 0x85, - 0x61, 0xF3, 0xFB, 0x92, 0x71, 0x6B, 0xA3, 0xE8, 0x30, 0xB2, 0x9D, 0x9D, 0xF4, 0x60, 0x6C, 0x3D, - 0x18, 0x2F, 0x26, 0x0A, 0x6E, 0x3E, 0x92, 0x9B, 0x34, 0x2A, 0x13, 0x6A, 0x14, 0x4F, 0xDC, 0xA9, - 0x6B, 0x6B, 0x88, 0x08, 0xD3, 0x64, 0x26, 0xFD, 0x0C, 0xD6, 0x1A, 0xE7, 0x4C, 0xC7, 0xE5, 0xCF, - 0x5D, 0xB0, 0x12, 0xCA, 0x2D, 0x38, 0xCF, 0xAE, 0xC9, 0x12, 0x4F, 0x08, 0xA2, 0x11, 0x19, 0x12, - 0x00, 0x1D, 0x7D, 0xC3, 0x4A, 0xD6, 0x63, 0x6E, 0x68, 0x71, 0x4D, 0x25, 0xCD, 0x72, 0xF1, 0xB7, - 0x8C, 0x7E, 0xDD, 0x63, 0x77, 0x33, 0xF0, 0x75, 0xB7, 0xCC, 0xF2, 0xED, 0x15, 0x58, 0x90, 0xAD, - 0xAF, 0xA7, 0x47, 0xBC, 0xFC, 0x46, 0x6F, 0xFD, 0xB9, 0x9A, 0xE3, 0x95, 0x4E, 0xE5, 0x79, 0x92, - 0x0D, 0x76, 0xE8, 0xC2, 0x2F, 0xE9, 0xB0, 0xA3, 0x48, 0x10, 0xAF, 0x3B, 0x8C, 0xEE, 0x3E, 0x27, - 0x0E, 0x03, 0xEB, 0x26, 0x70, 0x3E, 0x89, 0xD0, 0x1B, 0x05, 0x8B, 0xB0, 0x2F, 0x2A, 0x0A, 0x09, - 0x35, 0xBF, 0xF9, 0xDC, 0x95, 0x71, 0x24, 0x13, 0x5F, 0x52, 0x6D, 0x6D, 0x2F, 0x36, 0x55, 0xFC, - 0xCC, 0x55, 0xB9, 0x2D, 0xCD, 0x12, 0xF5, 0x3A, 0xEE, 0x2C, 0x64, 0x31, 0x1A, 0x39, 0x84, 0x62, - 0x32, 0x27, 0x27, 0x72, 0x10, 0xDC, 0x5B, 0x07, 0xA6, 0x68, 0x98, 0x5E, 0x94, 0x29, 0x37, 0x99, - 0x98, 0xD9, 0xD5, 0xA1, 0x8F, 0xD7, 0x2D, 0x36, 0x01, 0x41, 0x6F, 0xAC, 0xF5, 0xF4, 0xBD, 0xF1, - 0x79, 0x45, 0x7F, 0x79, 0xFA, 0xF1, 0xF2, 0xF0, 0xE8, 0xE0, 0xF8, 0x64, 0xFF, 0x83, 0xD6, 0x0B, - 0x01, 0x85, 0x09, 0xC8, 0xC6, 0x66, 0xC5, 0xFC, 0x68, 0xE2, 0x96, 0xD5, 0x81, 0x2F, 0xAE, 0x38, - 0x84, 0x51, 0x64, 0xC9, 0x80, 0x92, 0x05, 0x2D, 0x83, 0x44, 0x72, 0xFC, 0x4B, 0x2C, 0x2F, 0x01, - 0x00, 0x22, 0x93, 0x62, 0xCE, 0x0C, 0xAE, 0x55, 0x44, 0x6C, 0x35, 0x41, 0xB2, 0x4E, 0xB6, 0x61, - 0x0A, 0x60, 0x57, 0xE6, 0x23, 0x1A, 0xEB, 0x64, 0x63, 0x1B, 0x73, 0xC5, 0xAC, 0x68, 0x77, 0xBE, - 0x95, 0x91, 0x6C, 0xF6, 0x6B, 0x4C, 0x64, 0xFF, 0x82, 0xDD, 0x21, 0xBF, 0xF1, 0xF7, 0x60, 0x5A, - 0x3D, 0x69, 0x43, 0x14, 0x47, 0xA3, 0x3A, 0x40, 0xD9, 0xF8, 0xCC, 0x1C, 0x7E, 0x5B, 0x38, 0x3A, - 0x33, 0x4B, 0x37, 0xE3, 0xB1, 0xA9, 0xCC, 0xD0, 0x8B, 0x1B, 0x9A, 0x25, 0x07, 0x66, 0x32, 0x22, - 0x15, 0x0D, 0xC9, 0x18, 0x8E, 0xFA, 0xD0, 0xA8, 0x3A, 0x00, 0x33, 0x87, 0x9F, 0x59, 0x4F, 0x15, - 0xD1, 0xB0, 0xB0, 0x5E, 0x01, 0x60, 0x40, 0x6C, 0x56, 0xD2, 0xAA, 0x4A, 0xB7, 0x64, 0xFD, 0x52, - 0x15, 0x4C, 0xD1, 0xA6, 0x1E, 0xD7, 0x87, 0xB5, 0x25, 0x29, 0xC4, 0xE5, 0xC9, 0xC9, 0xE5, 0xE1, - 0xFE, 0xE0, 0xBD, 0xA6, 0x16, 0x22, 0xDE, 0x28, 0x31, 0x5B, 0xE2, 0xAE, 0xD8, 0x9A, 0x7E, 0x97, - 0x30, 0xD1, 0x01, 0x61, 0xCA, 0x11, 0x12, 0xB3, 0x13, 0x25, 0x1D, 0x96, 0xD1, 0xB5, 0x72, 0xFD, - 0xDE, 0x57, 0xC9, 0x98, 0xA6, 0x7A, 0xF7, 0x89, 0x75, 0x56, 0x76, 0x5F, 0xF5, 0xE3, 0xBE, 0x8A, - 0xBC, 0xE3, 0x85, 0xF7, 0x15, 0x98, 0xF7, 0x27, 0xD6, 0x5D, 0x4F, 0x66, 0xEE, 0x93, 0xC4, 0x53, - 0x66, 0x0A, 0x94, 0x1B, 0xD4, 0x4F, 0x1A, 0x94, 0x9E, 0x03, 0xFF, 0xD1, 0x27, 0x28, 0xAE, 0x94, - 0xC9, 0x06, 0xCE, 0x42, 0x75, 0x72, 0xB7, 0x40, 0x4C, 0x89, 0x01, 0xFF, 0x61, 0x02, 0x10, 0x16, - 0x94, 0x2C, 0xCF, 0x82, 0x96, 0x1C, 0x8D, 0xE4, 0x0F, 0xE3, 0x99, 0xD7, 0x4F, 0xFD, 0xB8, 0x9F, - 0xC8, 0x72, 0xD6, 0x25, 0xDC, 0x40, 0x3C, 0xA1, 0xCE, 0x7A, 0x6A, 0xA6, 0xF3, 0x7F, 0x9A, 0xD5, - 0x14, 0x1B, 0x5A, 0xCA, 0xC9, 0x90, 0x01, 0x01, 0x3F, 0x1C, 0x32, 0x14, 0xC4, 0xB9, 0xB3, 0x76, - 0x56, 0xB4, 0x8B, 0xF3, 0x86, 0xEE, 0xE0, 0x67, 0x3E, 0xC9, 0xC6, 0xBC, 0xB4, 0xC3, 0x8E, 0x9B, - 0xBA, 0xC9, 0x05, 0xF3, 0x36, 0xF1, 0x66, 0x21, 0xFE, 0x61, 0x89, 0x5D, 0x7B, 0x00, 0x1A, 0x78, - 0xEA, 0x1E, 0x3C, 0x6E, 0x28, 0x5F, 0x81, 0xB4, 0x26, 0x94, 0x34, 0xB5, 0x21, 0xD3, 0x6C, 0x13, - 0x1A, 0x0E, 0xE5, 0xA0, 0x5A, 0xC6, 0x37, 0xE3, 0x43, 0xE2, 0xAB, 0x6D, 0x52, 0x19, 0xF3, 0x7E, - 0xBD, 0xD8, 0xBC, 0x94, 0x0E, 0x1A, 0xF9, 0xDD, 0x03, 0x83, 0xC6, 0x95, 0x57, 0xB9, 0x15, 0xE3, - 0xB0, 0x4E, 0x34, 0x2C, 0xF9, 0xB6, 0x22, 0x89, 0x59, 0x3F, 0xAD, 0x95, 0xD9, 0x30, 0x9F, 0x75, - 0xCD, 0xC9, 0xE6, 0xA5, 0xB4, 0xE6, 0x81, 0x75, 0xDB, 0x9C, 0xD8, 0x16, 0x8A, 0x2C, 0x72, 0xDD, - 0x17, 0x8C, 0x72, 0xF0, 0xF9, 0xE4, 0xCD, 0xC7, 0x0F, 0x0C, 0xA9, 0x7E, 0xA9, 0x63, 0xEC, 0xB9, - 0x23, 0x96, 0xA5, 0xE8, 0x90, 0x8E, 0x7C, 0xAA, 0xEF, 0x8F, 0xA4, 0x7A, 0x42, 0xEB, 0x5A, 0x58, - 0x9C, 0xE9, 0x08, 0x8C, 0xF5, 0x60, 0xB8, 0x6E, 0x77, 0x77, 0xB5, 0xCC, 0x8F, 0x71, 0xE9, 0x2B, - 0x92, 0x69, 0xF9, 0xF8, 0x40, 0x97, 0x4C, 0x97, 0xA2, 0x57, 0xC3, 0xF4, 0xB0, 0x7D, 0xBE, 0x92, - 0xCA, 0x30, 0x39, 0x67, 0xF7, 0x66, 0xB1, 0x16, 0x0F, 0xA1, 0x94, 0x08, 0x57, 0x61, 0x54, 0x31, - 0xEB, 0x12, 0x55, 0xDD, 0x4D, 0x1F, 0x5E, 0x2C, 0x48, 0x51, 0x6A, 0x31, 0xB7, 0xF6, 0x18, 0xDC, - 0x71, 0x9D, 0xAB, 0xC5, 0xDF, 0x7F, 0xFF, 0x97, 0xCA, 0x20, 0x7E, 0x6B, 0x36, 0x54, 0x36, 0xEB, - 0xE0, 0x25, 0xA6, 0x76, 0x6B, 0xB6, 0x65, 0x21, 0x4D, 0x1F, 0xAC, 0xC8, 0x03, 0x78, 0x7E, 0x15, - 0x5C, 0x30, 0x42, 0x69, 0x55, 0xB5, 0x0C, 0xBC, 0x7A, 0xCF, 0x2F, 0x48, 0x04, 0x92, 0x2B, 0xB3, - 0x0C, 0xAE, 0x97, 0x8B, 0x5D, 0x5B, 0xC1, 0xAE, 0xFC, 0xA3, 0x9B, 0x61, 0x85, 0xE7, 0x13, 0xD3, - 0xD6, 0x7A, 0x15, 0x9E, 0x05, 0x82, 0x3C, 0x9E, 0x4B, 0x4F, 0x00, 0xDB, 0x0B, 0x9F, 0x00, 0xE4, - 0xD1, 0x57, 0x71, 0x0A, 0x48, 0x89, 0xE8, 0x91, 0xA6, 0x86, 0xD4, 0xE8, 0xAE, 0x3A, 0x3B, 0x98, - 0x18, 0x5F, 0x7B, 0x24, 0xCE, 0xE7, 0x9E, 0x39, 0x4C, 0xCC, 0x37, 0xD3, 0x53, 0xCA, 0xDF, 0x1A, - 0x8D, 0x65, 0xB5, 0x61, 0xA5, 0xF6, 0xF4, 0x64, 0xE2, 0x9D, 0x2C, 0x5B, 0xF0, 0x99, 0xE6, 0x75, - 0x41, 0xEA, 0x2E, 0x5D, 0x54, 0x34, 0x9A, 0x85, 0x05, 0x2B, 0xD0, 0x9C, 0x2D, 0xC9, 0xEF, 0x87, - 0x47, 0x6E, 0x8B, 0x3E, 0x71, 0x2C, 0x7E, 0x44, 0xE7, 0x35, 0x28, 0x75, 0x91, 0x47, 0xA5, 0xA7, - 0xC5, 0x3C, 0x46, 0x41, 0x48, 0x43, 0x7C, 0x32, 0x51, 0xC0, 0xAC, 0xFF, 0xCD, 0x5E, 0xEF, 0x84, - 0x78, 0x25, 0x79, 0xB8, 0xB2, 0x4B, 0xD4, 0x94, 0x09, 0xD1, 0xB9, 0x3E, 0x2C, 0x89, 0xDB, 0x3C, - 0xFF, 0x22, 0xAC, 0x60, 0x6F, 0x7C, 0x39, 0x48, 0x92, 0x27, 0x8F, 0x7C, 0x8D, 0xAB, 0xE6, 0xE8, - 0xEA, 0xD1, 0x9A, 0x14, 0x84, 0x02, 0x9F, 0x95, 0x45, 0x3A, 0x56, 0x53, 0xD2, 0x1E, 0x8A, 0xD4, - 0x92, 0xAB, 0x88, 0x17, 0xA3, 0x41, 0x95, 0x72, 0x8E, 0x7C, 0x95, 0x25, 0x89, 0x97, 0xD7, 0xF1, - 0xAE, 0xB2, 0x72, 0x17, 0xA8, 0x2D, 0x7C, 0xB1, 0xD2, 0xE5, 0x99, 0xEA, 0xC4, 0x1A, 0x5E, 0xBA, - 0x2D, 0x2C, 0x4A, 0x92, 0x78, 0xB9, 0x9C, 0x95, 0x78, 0x70, 0xE7, 0x84, 0xC3, 0x1B, 0x92, 0x57, - 0x85, 0x85, 0x27, 0xD0, 0x6B, 0x6B, 0x36, 0x0E, 0x5F, 0x6A, 0xB9, 0xEE, 0x79, 0x4F, 0x34, 0x3E, - 0xB9, 0xDF, 0x5C, 0xEF, 0xCE, 0xD5, 0x13, 0xC6, 0x6A, 0xEF, 0x2A, 0x0C, 0xF1, 0xDA, 0x6E, 0xAB, - 0xF4, 0xFA, 0x3D, 0x83, 0x96, 0xBC, 0x4D, 0xB1, 0x50, 0x82, 0xB0, 0xB8, 0xCA, 0x24, 0x99, 0xEC, - 0x7B, 0x2E, 0x94, 0xE4, 0x65, 0x1E, 0x4D, 0xB2, 0x3C, 0xA2, 0x7C, 0x3E, 0xCE, 0xA4, 0xBC, 0xB6, - 0x24, 0xCA, 0xCC, 0x7B, 0xC9, 0x91, 0x71, 0xBC, 0xC5, 0xB4, 0xF0, 0xF6, 0xE6, 0xD1, 0x55, 0x36, - 0xB7, 0x96, 0x40, 0xB9, 0x58, 0xD8, 0x6B, 0xCB, 0x21, 0xAF, 0x4F, 0xA4, 0xB9, 0x92, 0x5F, 0x46, - 0xC3, 0xCB, 0xD0, 0xE6, 0xD2, 0x5F, 0x96, 0xD8, 0x4B, 0xB1, 0xC0, 0x7B, 0x20, 0x87, 0x85, 0x87, - 0x67, 0x59, 0xD6, 0xEE, 0xE1, 0xFF, 0x03, 0x56, 0x33, 0x60, 0xED, 0x71, 0x8A, 0x00 + 0x1F, 0x8B, 0x08, 0x08, 0x30, 0x53, 0xB7, 0x67, 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, + 0x6D, 0xF7, 0xD8, 0x6E, 0x08, 0x33, 0xCB, 0x3B, 0xED, 0x33, 0xEC, 0x93, 0xDD, 0x2A, 0x49, 0xB6, + 0x25, 0x59, 0xFE, 0xEC, 0x86, 0x30, 0xB3, 0xC9, 0xEF, 0x77, 0x38, 0x60, 0xA9, 0x3E, 0x54, 0x2A, + 0x95, 0x4A, 0x52, 0xA9, 0x74, 0x6B, 0x07, 0x64, 0x68, 0x47, 0xF4, 0xCE, 0xBE, 0x27, 0xBB, 0xE4, + 0xDF, 0xEF, 0xC2, 0x57, 0xAB, 0xAB, 0x7F, 0xFC, 0xFB, 0x9D, 0xEB, 0x39, 0xFE, 0x5D, 0x67, 0xE4, + 0x0F, 0xEC, 0xC8, 0xF5, 0xBD, 0xCE, 0x8D, 0x1F, 0x46, 0x9E, 0x3D, 0xA6, 0x0F, 0xAF, 0xB6, 0xBA, + 0xAB, 0x77, 0xE1, 0xBF, 0xEF, 0xBC, 0xB8, 0x05, 0xB8, 0x3B, 0x7A, 0x15, 0xFA, 0x83, 0x6F, 0x34, + 0xDA, 0x79, 0xF1, 0x42, 0x40, 0xD8, 0x8E, 0x73, 0x78, 0x4B, 0xBD, 0xE8, 0xD8, 0x0D, 0x23, 0xEA, + 0xD1, 0x60, 0x71, 0x61, 0xE4, 0xDB, 0xCE, 0x42, 0x9B, 0xF8, 0xDE, 0x31, 0xFC, 0xB2, 0x04, 0x35, + 0xAF, 0xA7, 0xDE, 0x00, 0x91, 0x8A, 0x4F, 0x8B, 0x14, 0xEB, 0x2F, 0x91, 0xBF, 0xBF, 0x20, 0xF0, + 0xCF, 0xF5, 0xDC, 0xE8, 0x57, 0x7A, 0xD5, 0x67, 0x68, 0x17, 0xA1, 0xFA, 0x83, 0x04, 0xA0, 0x15, + 0x0A, 0x90, 0x84, 0x0B, 0xE0, 0xDF, 0xA3, 0x77, 0x24, 0xAD, 0x21, 0xDA, 0x05, 0x58, 0x94, 0x7A, + 0x1D, 0xDF, 0x1B, 0xD3, 0x30, 0xB4, 0x87, 0x14, 0x20, 0x12, 0xE4, 0x8B, 0xE3, 0x70, 0x18, 0xA3, + 0xC4, 0x7F, 0x13, 0x3B, 0x08, 0xE9, 0x91, 0x37, 0xF0, 0xC7, 0xAE, 0x37, 0xC4, 0xC2, 0x8E, 0x63, + 0x47, 0xB6, 0xC0, 0xF5, 0xA0, 0x32, 0x36, 0xA4, 0x8B, 0x34, 0x86, 0x0D, 0x68, 0x34, 0x0D, 0x3C, + 0xE2, 0xF8, 0x83, 0xE9, 0x18, 0x1A, 0xD6, 0x19, 0xD2, 0xE8, 0x70, 0x44, 0xF1, 0xD7, 0xB7, 0xF7, + 0x47, 0xD0, 0x5A, 0xDE, 0x26, 0x14, 0xDF, 0xB5, 0xFB, 0x9D, 0x3A, 0xC7, 0x36, 0xF2, 0x6D, 0xED, + 0x48, 0x5F, 0x7C, 0x6F, 0x98, 0x7E, 0x9A, 0x8C, 0xEC, 0xE8, 0xDA, 0x0F, 0xC6, 0x67, 0x01, 0x85, + 0x52, 0xF8, 0xDE, 0x6A, 0xF1, 0x82, 0x21, 0xF5, 0x1D, 0x1A, 0xB9, 0x03, 0x8E, 0x60, 0xDD, 0xEA, + 0x58, 0x5D, 0xAD, 0x00, 0x18, 0xDB, 0x25, 0x2B, 0x5D, 0x6B, 0xA3, 0xD3, 0xDD, 0x56, 0x8B, 0xF6, + 0x46, 0x08, 0xD3, 0xDD, 0xB0, 0xAC, 0x8E, 0x00, 0xA2, 0x03, 0x7A, 0xFD, 0x1B, 0xAB, 0xDE, 0xDB, + 0xB2, 0x7A, 0xD6, 0x66, 0x67, 0x63, 0x73, 0x2B, 0x2D, 0xF9, 0x8C, 0x25, 0xEB, 0x2F, 0xBB, 0x9B, + 0x5B, 0xD6, 0x7A, 0x67, 0xDD, 0x5A, 0x4B, 0x4B, 0x7E, 0x67, 0xB4, 0xB7, 0x36, 0x37, 0x37, 0x37, + 0x3A, 0xEB, 0x5B, 0xEB, 0xBC, 0x60, 0x64, 0x87, 0xD1, 0x3B, 0x77, 0x44, 0x4F, 0x41, 0x63, 0x24, + 0x8E, 0xAF, 0xF1, 0xD3, 0x74, 0x7C, 0x45, 0x83, 0xB4, 0x79, 0x1E, 0xFB, 0xFB, 0xE3, 0x35, 0x56, + 0x0F, 0xFB, 0x74, 0x44, 0x07, 0x11, 0x75, 0xD2, 0xE2, 0x50, 0x7C, 0x61, 0xC5, 0x12, 0xAA, 0xF0, + 0xC6, 0x07, 0x85, 0x1B, 0xE2, 0x67, 0xD4, 0x34, 0xEC, 0x47, 0x7B, 0x14, 0x52, 0x5E, 0xE8, 0x5F, + 0x45, 0xB6, 0xEB, 0x51, 0xE7, 0x84, 0x77, 0x72, 0xA5, 0x0A, 0x6F, 0xED, 0x90, 0xAA, 0x95, 0x04, + 0x09, 0x51, 0xE7, 0xFC, 0x62, 0xFF, 0x24, 0x8B, 0x08, 0x5B, 0x74, 0x61, 0x5F, 0xC1, 0x0F, 0xFA, + 0x3D, 0x92, 0xD8, 0x1B, 0xF8, 0x41, 0x40, 0x99, 0x6A, 0x68, 0x05, 0x42, 0xEF, 0xB4, 0xAF, 0x28, + 0x2F, 0x41, 0xE7, 0xE2, 0x7E, 0x42, 0xF3, 0x4B, 0x04, 0x97, 0x58, 0xCA, 0x79, 0xB4, 0x6F, 0x93, + 0x56, 0xA0, 0xB0, 0x51, 0x44, 0x5F, 0xBE, 0xEE, 0x64, 0xCA, 0x7E, 0xB1, 0x47, 0xD3, 0x6C, 0xE1, + 0xFE, 0x0D, 0x1D, 0x7C, 0xBB, 0xF2, 0xBF, 0x1B, 0x21, 0xE3, 0x42, 0x05, 0x94, 0x15, 0x43, 0xCB, + 0xFC, 0xC0, 0x09, 0x0F, 0xF7, 0x0F, 0xDF, 0x49, 0x40, 0xE2, 0xEB, 0x7B, 0xA1, 0x61, 0x52, 0xC9, + 0xF5, 0x74, 0x34, 0x3A, 0x03, 0x26, 0x3E, 0x4D, 0x60, 0x00, 0x49, 0x42, 0x16, 0x60, 0x21, 0x8D, + 0x2E, 0xDC, 0x31, 0xF5, 0xA7, 0x51, 0xDC, 0xE5, 0x9E, 0x73, 0x00, 0x23, 0x4D, 0xF9, 0x38, 0x40, + 0x6E, 0x4E, 0xE9, 0xDD, 0x3B, 0x37, 0x18, 0xDF, 0xD9, 0x01, 0x55, 0x0A, 0x61, 0x84, 0x99, 0x8A, + 0x5E, 0x0C, 0x7C, 0x0F, 0x3A, 0x0C, 0x34, 0x6C, 0x3F, 0xE9, 0x8E, 0xB0, 0xEF, 0x4F, 0x83, 0x01, + 0x6B, 0xCF, 0x96, 0xDE, 0x55, 0xA2, 0x4C, 0x97, 0x46, 0xA6, 0xC2, 0x59, 0xE0, 0xFA, 0x81, 0x1B, + 0xB9, 0xA9, 0x54, 0x38, 0xA1, 0x7D, 0x1F, 0x04, 0xE0, 0x7A, 0xD0, 0x46, 0xEC, 0x2A, 0x2C, 0xE4, + 0x06, 0x61, 0xFF, 0xE3, 0xC7, 0xF3, 0x83, 0xA3, 0xD3, 0xBD, 0x8B, 0xC3, 0xCB, 0xA3, 0xD3, 0xB3, + 0x4F, 0x17, 0x97, 0x17, 0x9F, 0xCF, 0x0E, 0x2F, 0x0F, 0x0E, 0x5E, 0x11, 0xAB, 0x4D, 0x56, 0x57, + 0x0F, 0xE8, 0xB5, 0x3D, 0x85, 0xF1, 0x78, 0x70, 0xD0, 0x71, 0xE2, 0x7F, 0x85, 0x70, 0x27, 0x27, + 0xAF, 0x48, 0x97, 0x41, 0xC2, 0xAF, 0x9D, 0x31, 0xFE, 0x2B, 0xAC, 0x7F, 0x89, 0x00, 0x3D, 0x0E, + 0x40, 0xAA, 0x42, 0x5C, 0x1E, 0xEC, 0xF5, 0x3F, 0xBC, 0x22, 0x6B, 0x1C, 0x6C, 0xA5, 0x32, 0x58, + 0xFF, 0xF3, 0xC9, 0xDB, 0x8F, 0xC7, 0xAF, 0xC8, 0x3A, 0x07, 0xFC, 0xEF, 0xFF, 0x8A, 0x21, 0xC7, + 0xE3, 0x85, 0x92, 0x56, 0xF5, 0xFB, 0xAF, 0xC8, 0x46, 0xC2, 0x26, 0xE9, 0xF7, 0x3B, 0x21, 0xFB, + 0x57, 0x4E, 0x13, 0x00, 0x37, 0x9B, 0x01, 0x8A, 0x66, 0xBE, 0x4C, 0x9A, 0xB9, 0x52, 0x0B, 0x3A, + 0x6E, 0xED, 0x56, 0xDA, 0xDA, 0x85, 0x04, 0x41, 0xAB, 0xB4, 0xBD, 0x97, 0xA7, 0x1F, 0x2F, 0x0F, + 0x0E, 0xF7, 0x8F, 0x4E, 0xF6, 0x00, 0xC7, 0x76, 0xDC, 0xA5, 0xFD, 0x3E, 0x59, 0x21, 0xA7, 0x3E, + 0x71, 0xE8, 0xC0, 0x1D, 0xDB, 0xA3, 0x2A, 0x7C, 0xC8, 0x78, 0xBA, 0x96, 0x2C, 0x8A, 0xDA, 0xA8, + 0x50, 0x20, 0x2A, 0xBE, 0xAE, 0x2C, 0x9C, 0xAA, 0xF8, 0x8E, 0x4E, 0x7F, 0xD9, 0x3B, 0x3E, 0x3A, + 0xB8, 0xFC, 0x74, 0xFA, 0x97, 0xD3, 0x8F, 0xBF, 0x9E, 0x02, 0x9A, 0x5E, 0x3B, 0x9E, 0xF7, 0x60, + 0xB8, 0xDC, 0xD2, 0x00, 0xAC, 0x7A, 0x3A, 0x62, 0xD0, 0xE2, 0x77, 0xAC, 0x78, 0xC0, 0xC5, 0x5F, + 0x8F, 0xBC, 0xC9, 0x34, 0x12, 0x16, 0x51, 0x1B, 0x5D, 0x9D, 0xBC, 0x66, 0xC8, 0x0E, 0x46, 0x66, + 0x0A, 0x8F, 0xE7, 0xE8, 0xD5, 0x55, 0x1C, 0xB2, 0xFE, 0x88, 0x82, 0x87, 0x33, 0x5C, 0x6C, 0xC5, + 0x55, 0x62, 0xF3, 0xFC, 0x8A, 0xB4, 0xC8, 0x32, 0xC1, 0xFA, 0x80, 0x0D, 0xEB, 0x23, 0x5B, 0x38, + 0xFB, 0x03, 0x1F, 0xE8, 0x08, 0x84, 0x93, 0x91, 0x1B, 0x2D, 0x2E, 0xB4, 0x17, 0x84, 0x37, 0x00, + 0xF3, 0x33, 0x59, 0x1C, 0x81, 0x03, 0xF2, 0x9D, 0xCD, 0x5C, 0xF0, 0xBF, 0xD7, 0xAC, 0x7A, 0x67, + 0x44, 0xBD, 0x61, 0x74, 0x03, 0x32, 0xEB, 0xE2, 0xC7, 0xE5, 0x5D, 0xD2, 0x93, 0x3D, 0x0C, 0xC4, + 0xEA, 0xE2, 0x64, 0x87, 0x75, 0xBF, 0x7C, 0xFF, 0xBA, 0xA3, 0x94, 0xDC, 0xDA, 0xA3, 0xA4, 0x08, + 0xB8, 0xE9, 0x4A, 0xC5, 0x1A, 0xFB, 0xAE, 0xC3, 0x19, 0x06, 0x5C, 0xCB, 0xA4, 0xD5, 0x46, 0x48, + 0xFE, 0x01, 0x7E, 0x89, 0x5B, 0xC0, 0xC1, 0xFA, 0x13, 0xE8, 0x38, 0xC0, 0x0B, 0xCD, 0x1D, 0xDB, + 0x9E, 0x13, 0x26, 0x45, 0xEE, 0x35, 0x59, 0x74, 0x9D, 0x8E, 0xEB, 0x0D, 0x46, 0x53, 0x87, 0x86, + 0x8B, 0xAD, 0xD0, 0x39, 0xF1, 0xA7, 0x1E, 0xF4, 0x51, 0x6B, 0x49, 0x66, 0x99, 0xA3, 0xB9, 0x40, + 0x07, 0xC7, 0xF7, 0x56, 0xFD, 0xEB, 0x6B, 0xD2, 0x3F, 0x20, 0x60, 0x74, 0x6D, 0xA5, 0x06, 0x62, + 0x63, 0xEC, 0xC3, 0x54, 0xC5, 0x8C, 0x7D, 0x4B, 0xC7, 0x81, 0xFF, 0x6E, 0x5C, 0x87, 0x2E, 0xB6, + 0x70, 0x0A, 0x3D, 0xB1, 0x3D, 0x90, 0x7A, 0xD0, 0x5A, 0xDA, 0x51, 0x2A, 0x3D, 0x28, 0x7F, 0x51, + 0xC0, 0xA3, 0x60, 0x8E, 0x82, 0xA9, 0x19, 0x31, 0xCE, 0xDC, 0x15, 0x11, 0xA7, 0xBF, 0x25, 0xE8, + 0xB1, 0x47, 0x00, 0xBB, 0xEA, 0x77, 0x65, 0xE8, 0x64, 0xDC, 0x32, 0xE0, 0x4A, 0x25, 0x92, 0x38, + 0x80, 0x91, 0x1B, 0x8D, 0xD8, 0xAC, 0x7D, 0x7E, 0xF1, 0x17, 0xD6, 0x2D, 0x1A, 0x2C, 0xF4, 0x19, + 0xE9, 0x83, 0xD3, 0x38, 0x69, 0xA9, 0x08, 0x32, 0xF3, 0x26, 0x36, 0x58, 0xAD, 0x62, 0xF4, 0x33, + 0x32, 0x5D, 0xA1, 0xF3, 0x0A, 0xD5, 0x0E, 0x7F, 0xF9, 0x4B, 0x81, 0xE8, 0xAE, 0xC0, 0xCD, 0xD8, + 0xF7, 0xBD, 0x6B, 0x77, 0xA8, 0x4B, 0x2E, 0xAD, 0x33, 0x99, 0x94, 0xD5, 0xA0, 0xD1, 0x0D, 0x0D, + 0x3C, 0x1A, 0x95, 0xD5, 0xF3, 0xA2, 0x52, 0x54, 0x13, 0x3F, 0x88, 0xC2, 0xFC, 0x4A, 0x5C, 0x93, + 0x40, 0x02, 0x40, 0xCF, 0x1E, 0x9D, 0x41, 0xE5, 0x8F, 0x13, 0x36, 0x61, 0xE7, 0x63, 0x84, 0x81, + 0x73, 0xE1, 0xF7, 0x0F, 0xF6, 0xED, 0xC0, 0xC9, 0xC7, 0x38, 0xB4, 0x47, 0xA0, 0x44, 0xFE, 0x07, + 0x1B, 0x3C, 0xD3, 0x28, 0x02, 0x03, 0x91, 0x5F, 0x35, 0x72, 0x47, 0x51, 0x19, 0x83, 0x57, 0x94, + 0x4E, 0x68, 0x00, 0xB5, 0xA2, 0xC0, 0x1F, 0xE5, 0xB3, 0x36, 0x0D, 0xE9, 0x5E, 0x18, 0x82, 0xAF, + 0x79, 0xEA, 0xDF, 0xC5, 0x5E, 0x58, 0x7E, 0xED, 0x31, 0xB5, 0xC3, 0x69, 0xC0, 0xD6, 0x17, 0xE7, + 0xB1, 0xC9, 0xCC, 0x67, 0x61, 0xEC, 0x87, 0xB6, 0x3B, 0x38, 0x3D, 0x39, 0xDC, 0xEB, 0x47, 0x30, + 0x6A, 0xC7, 0x07, 0x81, 0x3F, 0x81, 0x65, 0x5B, 0x91, 0xA4, 0x00, 0xFB, 0x2D, 0xBD, 0x3F, 0xF2, + 0x84, 0x08, 0xC2, 0x42, 0xC6, 0x8F, 0x61, 0xC1, 0x38, 0x72, 0xFF, 0x46, 0x9D, 0x03, 0xE0, 0x3F, + 0x70, 0xAF, 0xA6, 0xD8, 0x0D, 0xE5, 0x8D, 0x00, 0xC8, 0x43, 0x0F, 0xFD, 0xE8, 0xC3, 0xEF, 0x11, + 0x3A, 0x6B, 0xE7, 0xB6, 0xE3, 0xFA, 0x05, 0x9A, 0x25, 0xD5, 0xEA, 0x9F, 0xED, 0x9D, 0x5F, 0x9C, + 0x72, 0xCF, 0x2C, 0x6E, 0x4D, 0x7E, 0xFB, 0xC3, 0x9B, 0x69, 0x84, 0x35, 0x4E, 0xFD, 0xFD, 0x1B, + 0x3B, 0x18, 0xC6, 0xCE, 0xE2, 0x89, 0xEB, 0x4D, 0x23, 0x1A, 0xC6, 0x8C, 0x1E, 0x50, 0x58, 0x1F, + 0xB0, 0x1E, 0xCA, 0x41, 0xC3, 0x1C, 0x3E, 0x3A, 0x1A, 0xB1, 0xC5, 0xF1, 0xA9, 0x7D, 0xEB, 0x0E, + 0xA0, 0x32, 0x18, 0xC6, 0x53, 0x3F, 0x22, 0xE1, 0x74, 0x82, 0xEA, 0x0A, 0xAB, 0x18, 0x98, 0x81, + 0x7E, 0x3F, 0x3C, 0xA8, 0x62, 0xD2, 0x4C, 0x43, 0xF4, 0x9D, 0x3D, 0x80, 0x19, 0xE5, 0xB6, 0x07, + 0xE3, 0xF4, 0x3F, 0xFF, 0x93, 0x14, 0xD5, 0x20, 0xC7, 0x6F, 0xC1, 0x98, 0x67, 0x0D, 0xF5, 0xFC, + 0x06, 0xB4, 0x18, 0x60, 0xA5, 0x03, 0x9A, 0xD7, 0x9B, 0x7D, 0x40, 0x27, 0xDD, 0xFC, 0x73, 0x40, + 0xFF, 0x1C, 0xD0, 0x62, 0x40, 0x3F, 0xD1, 0x50, 0xCC, 0x1D, 0x67, 0x5C, 0xD2, 0xBF, 0x6D, 0xB4, + 0x7E, 0x0E, 0xB3, 0xE7, 0x36, 0xCC, 0x78, 0xB5, 0xAA, 0xC3, 0x4C, 0x0C, 0x9C, 0x8A, 0xC3, 0x4C, + 0x0C, 0xCA, 0x3A, 0xC3, 0x4C, 0x4C, 0x35, 0x15, 0x86, 0x59, 0xC2, 0xF8, 0x63, 0x0F, 0xB3, 0xC4, + 0x43, 0xAA, 0x3F, 0xCC, 0xF8, 0x16, 0x1C, 0xF8, 0x96, 0x43, 0xC0, 0xE0, 0xDC, 0x7B, 0xF6, 0xD8, + 0x1D, 0x9C, 0xF8, 0x0E, 0x35, 0xF6, 0x04, 0x2E, 0x82, 0x3C, 0x7A, 0xC7, 0xD5, 0x48, 0xEC, 0xC7, + 0xF2, 0x3F, 0x16, 0x17, 0xFA, 0x11, 0x8C, 0xD1, 0xC1, 0x42, 0x9B, 0x2C, 0x58, 0x0B, 0xA6, 0x16, + 0x31, 0x3A, 0xB8, 0x81, 0xBC, 0x98, 0x60, 0x68, 0x93, 0xA9, 0xE7, 0xC0, 0x38, 0xF4, 0xA8, 0x63, + 0x80, 0xC8, 0x23, 0xF4, 0xFF, 0xA6, 0x36, 0xAA, 0x41, 0x4C, 0xAD, 0xFB, 0xB8, 0xD4, 0xCE, 0x28, + 0x2C, 0x98, 0xA0, 0xCF, 0x6C, 0x0F, 0x89, 0xF5, 0x1E, 0x97, 0xD8, 0xDE, 0x34, 0xF2, 0xC7, 0x7E, + 0xE4, 0xDE, 0x52, 0x24, 0xB6, 0xF6, 0xB8, 0xC4, 0xCE, 0xC1, 0xEE, 0x11, 0x18, 0xE9, 0x48, 0x6A, + 0xFD, 0x71, 0x49, 0x7D, 0xA0, 0xF6, 0xED, 0x3D, 0x39, 0xB1, 0x07, 0x37, 0x00, 0x18, 0xDC, 0x23, + 0xC5, 0x8D, 0xC7, 0xA5, 0xF8, 0x69, 0xEF, 0x17, 0xA4, 0xB2, 0xF9, 0xC8, 0x54, 0xBC, 0x91, 0x3B, + 0x76, 0x61, 0x06, 0x42, 0x5A, 0x2F, 0x9B, 0xD1, 0xCA, 0x80, 0x0C, 0x99, 0xF9, 0xE2, 0xDB, 0xD2, + 0xCC, 0x74, 0x5D, 0xFB, 0xB8, 0xFC, 0x6B, 0x2D, 0x75, 0x42, 0x1A, 0xED, 0x45, 0xDC, 0x7E, 0xD0, + 0xC5, 0x05, 0xDC, 0x3B, 0x58, 0xB9, 0x0A, 0x57, 0xFC, 0xC0, 0x1D, 0xBA, 0x60, 0xE0, 0x57, 0xD8, + 0x4A, 0x14, 0x39, 0xB9, 0xB8, 0xA1, 0xE4, 0xFD, 0x69, 0xBF, 0x4F, 0x06, 0x36, 0x2C, 0xE4, 0xA7, + 0x11, 0xD8, 0x3E, 0x82, 0xC6, 0x8D, 0x80, 0x1F, 0x49, 0x70, 0xAB, 0xFB, 0x76, 0x8D, 0xD8, 0x11, + 0x71, 0xDC, 0xEB, 0x6B, 0x1A, 0x80, 0x85, 0x24, 0x01, 0xD0, 0x09, 0x3B, 0xE4, 0x9D, 0x1F, 0xB0, + 0x7A, 0xAF, 0x62, 0xAB, 0x60, 0x93, 0x90, 0xD9, 0x43, 0xB6, 0x05, 0x42, 0xA1, 0xFB, 0xE2, 0x2D, + 0x94, 0x36, 0x43, 0x05, 0xFC, 0xC0, 0xFF, 0x89, 0xEB, 0xC1, 0x0C, 0x83, 0xCB, 0xF6, 0xA4, 0x16, + 0x87, 0xE2, 0x08, 0x39, 0xBD, 0x57, 0xF9, 0x95, 0xE3, 0xC3, 0x9A, 0x61, 0xE0, 0x4F, 0x27, 0x1C, + 0x31, 0x65, 0x56, 0x0F, 0xEA, 0x3A, 0xEE, 0xAD, 0xEB, 0x4C, 0xA1, 0xB6, 0xA8, 0x14, 0x76, 0x4C, + 0x42, 0x46, 0x89, 0x05, 0xD1, 0x60, 0xDC, 0x50, 0x5C, 0xC8, 0x21, 0x71, 0x43, 0x58, 0x80, 0xDB, + 0x5E, 0x08, 0xDD, 0x89, 0x1E, 0xC5, 0xD5, 0x3D, 0x81, 0x79, 0x9A, 0xE0, 0x94, 0x8F, 0xA2, 0xB2, + 0x89, 0x23, 0xF6, 0x71, 0xFD, 0x6B, 0xD2, 0xFD, 0xF0, 0x37, 0xC6, 0x7D, 0xCC, 0x13, 0xE9, 0x5A, + 0xD6, 0x46, 0x9B, 0x9C, 0xF4, 0x4F, 0xD6, 0x39, 0xFB, 0x56, 0x27, 0xAE, 0xD2, 0xB5, 0xD6, 0xD6, + 0x3A, 0xE4, 0xE2, 0x06, 0xB0, 0x63, 0x5F, 0x5C, 0x51, 0x32, 0xF2, 0xEF, 0x40, 0xE6, 0x0E, 0x2B, + 0x0D, 0xD0, 0x52, 0x87, 0xE4, 0xCE, 0x8D, 0x6E, 0xF0, 0x3B, 0x50, 0xF3, 0x9C, 0x3B, 0xD7, 0x81, + 0xBF, 0xA0, 0x10, 0xD7, 0x28, 0x3E, 0xD6, 0x8C, 0xFC, 0x84, 0x33, 0xC0, 0x7E, 0xBF, 0x6A, 0x8F, + 0x46, 0x4C, 0xA8, 0xA9, 0x4C, 0xC8, 0x31, 0x6A, 0x61, 0xF8, 0x0A, 0x09, 0x63, 0xF5, 0x4D, 0xCB, + 0xCA, 0x95, 0x13, 0xCD, 0x4C, 0x28, 0x0D, 0x24, 0xC6, 0x67, 0x25, 0x12, 0xBB, 0x16, 0xBC, 0x21, + 0xF2, 0x56, 0xFA, 0xAB, 0x58, 0xCD, 0x60, 0xC8, 0xF0, 0xC9, 0x95, 0xEC, 0x7F, 0x3C, 0xE9, 0x75, + 0x88, 0xD8, 0x0E, 0x7F, 0x45, 0xDE, 0xE1, 0xC6, 0xD1, 0x42, 0xB5, 0x2D, 0x21, 0x83, 0xD3, 0x76, + 0xE1, 0x07, 0x83, 0x9B, 0x7F, 0x22, 0x5F, 0xAD, 0x70, 0x8F, 0x23, 0xA7, 0xB6, 0xE6, 0xAC, 0x55, + 0xF1, 0xA0, 0xFB, 0x20, 0x97, 0x1C, 0x07, 0xFA, 0xD3, 0xC9, 0xF6, 0x96, 0x35, 0xAB, 0x17, 0xCE, + 0x91, 0xD4, 0x5B, 0x28, 0xE1, 0xF6, 0xB3, 0x0F, 0xA3, 0x08, 0xC7, 0xDB, 0xD9, 0xD9, 0x31, 0x81, + 0x6A, 0xE4, 0xE4, 0xFD, 0xDE, 0xBF, 0x10, 0x8E, 0x3D, 0xA0, 0x9D, 0x4E, 0xA7, 0xE6, 0x62, 0x2A, + 0xD7, 0xBB, 0x0D, 0xEC, 0xB4, 0x0B, 0x1E, 0xD1, 0x15, 0x62, 0xBE, 0xE1, 0x13, 0xB8, 0x42, 0x62, + 0x96, 0xEB, 0x3E, 0xA5, 0x57, 0xD2, 0x9B, 0xE3, 0x34, 0xF7, 0xE3, 0x8D, 0x76, 0xD7, 0x7A, 0xB9, + 0x8E, 0x3F, 0xB7, 0xD8, 0xCF, 0x6D, 0xFC, 0xD9, 0xED, 0xFD, 0x70, 0x33, 0x8E, 0x95, 0x7B, 0x56, + 0x07, 0x47, 0x00, 0x7D, 0x45, 0x70, 0x3A, 0x97, 0x34, 0x9D, 0xCD, 0xD6, 0xD8, 0x74, 0xFF, 0x96, + 0x06, 0x81, 0xEB, 0x38, 0xD4, 0xC3, 0xFA, 0xC8, 0xEB, 0xDD, 0x0D, 0xC5, 0x19, 0x96, 0xB0, 0x83, + 0xE4, 0x31, 0xA8, 0x2E, 0x9B, 0x06, 0x1A, 0x9A, 0xD8, 0x33, 0x3F, 0x8C, 0x06, 0xCC, 0xB6, 0xFC, + 0x5C, 0x11, 0x97, 0x19, 0xD9, 0xC7, 0x5E, 0x12, 0xFF, 0xDC, 0x7A, 0xFA, 0xC1, 0x5B, 0x4F, 0x05, + 0x13, 0xE7, 0xF1, 0xFB, 0xDE, 0xB6, 0x75, 0x96, 0x43, 0xDF, 0x38, 0x73, 0xE6, 0x51, 0x94, 0xE7, + 0x1C, 0x89, 0xC1, 0x1A, 0x44, 0x45, 0xD7, 0xB9, 0xDE, 0xE1, 0x88, 0xDE, 0x26, 0x5A, 0xD6, 0x04, + 0xC1, 0xFE, 0xE9, 0xC7, 0x2A, 0xF0, 0xFF, 0x40, 0x66, 0xBD, 0x9B, 0x18, 0xF7, 0x6E, 0x77, 0x6D, + 0xFD, 0x1F, 0xD7, 0x9C, 0xD7, 0x38, 0xEA, 0x94, 0x4E, 0x7C, 0x87, 0x5E, 0x18, 0xC6, 0x31, 0x34, + 0xBF, 0xD0, 0x20, 0x04, 0x75, 0x3C, 0xF2, 0x22, 0xD3, 0xF1, 0x2F, 0xA8, 0x9F, 0x7B, 0x7D, 0x8F, + 0xEB, 0x35, 0x36, 0x88, 0x89, 0x33, 0xA5, 0xC8, 0xD0, 0xB5, 0x00, 0x26, 0x6C, 0xC9, 0xCB, 0xF4, + 0x39, 0xCC, 0x9C, 0x45, 0xE6, 0x1F, 0x46, 0x96, 0x1C, 0x72, 0xCC, 0x78, 0x0C, 0x32, 0x2F, 0xCF, + 0x0D, 0xA7, 0x04, 0x34, 0x2E, 0x4F, 0xE0, 0xBB, 0xF5, 0xB9, 0x0C, 0x6D, 0xBE, 0x1D, 0xD2, 0x7B, + 0xCA, 0x5D, 0xAC, 0xB5, 0xA7, 0xF4, 0x17, 0x1F, 0x79, 0x6B, 0xA9, 0x4F, 0xED, 0x27, 0xD8, 0x4E, + 0xDA, 0x73, 0x83, 0x2B, 0x1F, 0x7C, 0x14, 0xD2, 0x1D, 0x3E, 0xC1, 0xB6, 0x52, 0x42, 0xAD, 0x37, + 0x9C, 0x61, 0x63, 0xA9, 0x36, 0xB5, 0x75, 0x46, 0x6D, 0xEB, 0x71, 0xA9, 0xFD, 0x1A, 0xC0, 0x74, + 0x8F, 0x74, 0xB6, 0x1F, 0x97, 0xCE, 0x5B, 0xF7, 0x1B, 0x53, 0xBF, 0xEE, 0x9C, 0x86, 0x71, 0x1C, + 0x9A, 0xF2, 0x66, 0x97, 0x74, 0x7B, 0x5D, 0x93, 0x11, 0xAA, 0x6D, 0x88, 0xCA, 0x8C, 0xD1, 0x09, + 0x4E, 0x4A, 0xAC, 0x0D, 0xC6, 0x05, 0x5E, 0xB3, 0x76, 0x14, 0xC9, 0xEC, 0x70, 0xA5, 0x3F, 0xF0, + 0x61, 0x8E, 0xE2, 0x44, 0x7B, 0xF3, 0x23, 0xFA, 0x50, 0x3A, 0x69, 0xAD, 0xAE, 0x82, 0x97, 0xC9, + 0xA6, 0x9B, 0x21, 0xC6, 0x95, 0xDB, 0x62, 0xDE, 0xC7, 0x98, 0x9A, 0xA9, 0xE7, 0x46, 0x25, 0x93, + 0x5B, 0xE8, 0xBC, 0x0B, 0x28, 0xED, 0x4F, 0x60, 0xAA, 0x68, 0x2D, 0x29, 0xB4, 0x60, 0x52, 0xD1, + 0x6A, 0xF6, 0xC1, 0xE7, 0x2C, 0xA9, 0x74, 0x03, 0xCE, 0x3E, 0x4E, 0x75, 0x47, 0x07, 0x25, 0x15, + 0x0D, 0x93, 0x6A, 0x09, 0x84, 0x63, 0xDF, 0x87, 0xE7, 0x74, 0x6C, 0xBB, 0x1E, 0x5B, 0x24, 0x14, + 0xD6, 0x9D, 0x04, 0x3E, 0x46, 0x39, 0x59, 0x18, 0xB2, 0x5A, 0xAD, 0x6A, 0xB7, 0x7A, 0xD5, 0x5E, + 0xF5, 0xAA, 0x6B, 0xD5, 0xAB, 0xAE, 0x57, 0xAF, 0xBA, 0x51, 0xBD, 0xEA, 0x66, 0xF5, 0xAA, 0x2F, + 0x2B, 0x54, 0x65, 0x0E, 0xDE, 0xC9, 0xDE, 0x7E, 0x59, 0x57, 0x51, 0xF0, 0xD6, 0xE9, 0xDB, 0x8B, + 0x52, 0x2D, 0x80, 0x25, 0x62, 0x1C, 0x0E, 0x5F, 0x52, 0xF3, 0xCA, 0x06, 0x87, 0x36, 0xB8, 0x3F, + 0xA3, 0xB0, 0x02, 0x41, 0xE7, 0x2B, 0xA9, 0xAC, 0xDB, 0x13, 0x30, 0x1F, 0xAE, 0xB3, 0x04, 0x90, + 0x30, 0x16, 0x3E, 0x5C, 0x9C, 0x1C, 0xEB, 0x11, 0x68, 0x65, 0xDE, 0x5E, 0x10, 0x7D, 0xCB, 0xE8, + 0xA5, 0x81, 0x86, 0xB1, 0x5E, 0x3E, 0xD5, 0x7C, 0xA8, 0x4F, 0x93, 0x21, 0x48, 0x95, 0xB6, 0x66, + 0x60, 0x79, 0x80, 0xAB, 0x8D, 0x60, 0x7C, 0x8E, 0x11, 0xE3, 0x59, 0x66, 0x59, 0x20, 0xF9, 0xBE, + 0x3F, 0x9E, 0x80, 0xC1, 0xA4, 0x8B, 0x4B, 0xB5, 0xD1, 0x62, 0xC8, 0xF9, 0x39, 0x1D, 0x50, 0x77, + 0x62, 0x40, 0x9E, 0xAD, 0xA3, 0x52, 0x28, 0x21, 0x21, 0x54, 0x8F, 0xDF, 0x7D, 0x30, 0x60, 0x9F, + 0x06, 0x78, 0xC8, 0x72, 0x26, 0xD7, 0x32, 0x49, 0xF6, 0x8F, 0x8B, 0x2D, 0x17, 0x97, 0xE4, 0x5F, + 0xF0, 0x26, 0xCE, 0xAE, 0x40, 0xCA, 0x16, 0xAE, 0x5F, 0xBF, 0xDC, 0x62, 0x98, 0xFE, 0x2E, 0x46, + 0x17, 0x1A, 0xB1, 0x2D, 0x93, 0xD6, 0x57, 0x90, 0x3D, 0xC0, 0x4C, 0x16, 0x17, 0x58, 0x30, 0x3D, + 0x3B, 0x92, 0xC2, 0x88, 0xC2, 0x1A, 0xA2, 0x8A, 0x3D, 0xFC, 0x4F, 0x13, 0xBC, 0xD4, 0x13, 0x0B, + 0x3B, 0xDB, 0x20, 0x73, 0xBD, 0xC5, 0xC6, 0x94, 0xD0, 0x13, 0x9E, 0x86, 0x65, 0x74, 0x78, 0xAD, + 0x45, 0x1E, 0xF4, 0x5A, 0x79, 0xD5, 0x93, 0xDE, 0xA2, 0x31, 0x0D, 0x00, 0xF9, 0x8A, 0x8D, 0x49, + 0xD3, 0x67, 0x1A, 0x83, 0xD2, 0x45, 0x9D, 0x02, 0xDA, 0x6C, 0x12, 0x7E, 0x34, 0xDA, 0x7B, 0xA3, + 0x82, 0x76, 0xF3, 0x6B, 0x42, 0x73, 0xA7, 0xCD, 0xEE, 0x1A, 0x65, 0xA9, 0xC6, 0x57, 0x90, 0x1E, + 0x85, 0xDE, 0x67, 0x33, 0xBD, 0xCF, 0x8F, 0x45, 0xEF, 0x77, 0x33, 0xBD, 0xDF, 0x1F, 0x85, 0x5E, + 0x38, 0xF1, 0xFC, 0xBB, 0x33, 0x8A, 0x1B, 0x82, 0x53, 0xD3, 0x92, 0x3D, 0x71, 0x4D, 0x89, 0xB5, + 0x64, 0xDC, 0x8F, 0x99, 0x00, 0x2C, 0xCC, 0x77, 0xA1, 0x66, 0x9E, 0x5B, 0xAD, 0x26, 0x4C, 0x5C, + 0xE6, 0x32, 0xF0, 0xC5, 0xFA, 0x4A, 0xFE, 0x00, 0x68, 0xAD, 0x16, 0xF9, 0xD3, 0x9F, 0xB0, 0x8D, + 0x5F, 0xBA, 0xF1, 0x07, 0x93, 0xBF, 0x9C, 0xCB, 0xD9, 0x32, 0x13, 0x10, 0x1A, 0xB5, 0xD7, 0x57, + 0xC1, 0x9B, 0x56, 0xD3, 0x1D, 0x8F, 0x90, 0x2F, 0xB0, 0xF1, 0x22, 0x93, 0x69, 0x3E, 0x49, 0x6E, + 0x39, 0x75, 0x26, 0xD3, 0xF0, 0xA6, 0xA6, 0x5D, 0x11, 0xB8, 0xE3, 0xEB, 0x50, 0xB9, 0xF8, 0xE3, + 0x0A, 0x4D, 0x68, 0x5C, 0x8F, 0xB9, 0x47, 0xA1, 0xA3, 0xD6, 0xAE, 0xDF, 0xD5, 0xD2, 0xA5, 0xEB, + 0x31, 0x77, 0x7F, 0xB3, 0xA6, 0x56, 0xBE, 0xE9, 0x06, 0x1D, 0xD0, 0x7A, 0x1D, 0x05, 0xC4, 0x1E, + 0xB9, 0x43, 0x6F, 0x77, 0x61, 0x44, 0xAF, 0xA3, 0x05, 0xBD, 0x1F, 0x4C, 0x10, 0xCE, 0x1B, 0x9C, + 0xA1, 0x14, 0xFE, 0xB0, 0x13, 0x57, 0xB1, 0xA0, 0x2A, 0x74, 0xDC, 0xF3, 0x95, 0x81, 0x5E, 0xB3, + 0x49, 0x93, 0x44, 0xF7, 0x13, 0xBA, 0xCB, 0xE7, 0xBE, 0x2B, 0xFF, 0xFB, 0x02, 0xF8, 0x5D, 0xBB, + 0x0B, 0x26, 0x6E, 0x16, 0x08, 0x9B, 0x5E, 0x17, 0x10, 0xD9, 0xD1, 0xC1, 0x02, 0x19, 0x40, 0x85, + 0x10, 0xFE, 0xF4, 0x83, 0xF1, 0x0A, 0x83, 0x5E, 0xE1, 0xF8, 0xA4, 0xFB, 0x05, 0x6C, 0x53, 0x7A, + 0xE1, 0x4D, 0x45, 0x96, 0x56, 0x23, 0x45, 0x67, 0x2B, 0xF4, 0x32, 0xDB, 0x21, 0xD5, 0x7B, 0x04, + 0xEF, 0xC9, 0x21, 0xDF, 0x75, 0xA6, 0xD5, 0xE9, 0xD5, 0xF7, 0x93, 0x34, 0x70, 0x84, 0xE1, 0x84, + 0x85, 0x15, 0x51, 0x3F, 0x5F, 0xE2, 0x96, 0xFE, 0xE5, 0xC1, 0xC5, 0x89, 0xF6, 0x1D, 0x37, 0x19, + 0x2F, 0x71, 0x07, 0xF3, 0x12, 0xB7, 0x50, 0x15, 0x5E, 0xA4, 0xAB, 0x8D, 0x42, 0xE9, 0x5C, 0x67, + 0x27, 0xAF, 0xC6, 0x39, 0xBF, 0xCD, 0x90, 0x31, 0x83, 0x1A, 0x92, 0x63, 0xFB, 0x8A, 0x8E, 0x4C, + 0x57, 0x1A, 0xA4, 0x7A, 0x07, 0xE2, 0x4E, 0x4E, 0x0A, 0x15, 0xDF, 0xCD, 0xB9, 0xD4, 0xD7, 0xA6, + 0x28, 0x0B, 0x09, 0x2A, 0xBE, 0x9A, 0x03, 0xAB, 0xF5, 0x35, 0x93, 0xED, 0x91, 0x2F, 0x6B, 0xF2, + 0x0B, 0x48, 0x12, 0x30, 0x98, 0xAD, 0x9D, 0x22, 0xB9, 0xED, 0xB2, 0x20, 0x19, 0xE3, 0x0E, 0x81, + 0x7E, 0xD5, 0xF3, 0x0F, 0xBB, 0x32, 0x99, 0xBC, 0x5D, 0x83, 0xEC, 0x05, 0x51, 0x09, 0xC8, 0xBC, + 0x06, 0x97, 0x6F, 0x9A, 0x32, 0xB5, 0xBB, 0xC9, 0x58, 0x4A, 0xCD, 0x77, 0xD5, 0x20, 0xE5, 0x3E, + 0x50, 0xDB, 0x8E, 0xA3, 0xE4, 0x92, 0xDD, 0x90, 0x92, 0x3E, 0xF7, 0x4C, 0x22, 0x51, 0x55, 0x06, + 0x10, 0x99, 0xD5, 0xA7, 0xA0, 0x7F, 0x76, 0x77, 0xC9, 0x7A, 0x9E, 0x54, 0x9A, 0x32, 0x6A, 0xFC, + 0xBC, 0x66, 0xEC, 0x52, 0xC6, 0xEF, 0xBA, 0xF5, 0xB2, 0x77, 0x69, 0xC5, 0xDC, 0xF3, 0xBF, 0x4A, + 0x77, 0x30, 0x5E, 0x14, 0x76, 0x86, 0xE3, 0xDE, 0x2A, 0x86, 0x85, 0x85, 0x36, 0x91, 0xC0, 0xBF, + 0xE3, 0x76, 0x69, 0x1C, 0x0E, 0x25, 0xFE, 0x12, 0xCB, 0xA4, 0x77, 0x60, 0x06, 0xED, 0x88, 0x89, + 0x01, 0x30, 0x72, 0xD3, 0xA6, 0xC3, 0xC7, 0x24, 0x07, 0xFE, 0x68, 0x25, 0x1C, 0xAF, 0xAC, 0x13, + 0xFC, 0x65, 0x93, 0xFD, 0x64, 0x5C, 0x30, 0xF0, 0x85, 0x37, 0x1A, 0x28, 0x97, 0x2D, 0xC0, 0xBF, + 0x7A, 0xBD, 0xCA, 0x6A, 0x94, 0xB2, 0x21, 0xB5, 0x4E, 0x21, 0xB5, 0xBE, 0xA0, 0x1A, 0x63, 0x7E, + 0xF9, 0x5B, 0x33, 0xB1, 0xFC, 0x4C, 0x73, 0xA1, 0x84, 0x46, 0x62, 0xBE, 0x33, 0x6D, 0xE4, 0x2B, + 0x21, 0xB9, 0x8C, 0x19, 0x9C, 0x4A, 0xF2, 0x9B, 0xE4, 0xA1, 0x3D, 0x0C, 0x02, 0x3F, 0x65, 0xD4, + 0xF5, 0x46, 0xAE, 0x47, 0xF9, 0x37, 0xB0, 0xFA, 0x93, 0x52, 0xBC, 0xAB, 0x20, 0x91, 0x37, 0xFC, + 0x67, 0x0D, 0xCB, 0x3F, 0x96, 0x4D, 0x75, 0x66, 0xAB, 0x40, 0x94, 0x1E, 0x89, 0x70, 0x39, 0xD4, + 0x4D, 0xD3, 0x61, 0x8D, 0x2C, 0x04, 0x66, 0xA0, 0xDE, 0x9F, 0x81, 0x89, 0x2A, 0xA8, 0x84, 0x88, + 0xCE, 0xF1, 0xA8, 0x89, 0x29, 0x3F, 0x8C, 0xD5, 0x6E, 0x41, 0xE5, 0x10, 0xEB, 0x24, 0xA3, 0xBC, + 0xA0, 0xB2, 0xCC, 0xA6, 0x84, 0xBD, 0xB7, 0x66, 0x55, 0x00, 0x48, 0x09, 0xE8, 0xF5, 0x9B, 0x4C, + 0x3C, 0xEC, 0x56, 0xE8, 0x3B, 0x58, 0x31, 0x46, 0x9A, 0xBF, 0x55, 0x32, 0x09, 0xE9, 0xD5, 0xFE, + 0x3A, 0x05, 0xF7, 0xE7, 0xDA, 0x65, 0x6B, 0x75, 0xBD, 0x3C, 0xAF, 0x2F, 0x8B, 0x7B, 0x0B, 0xFF, + 0x29, 0x58, 0x93, 0x60, 0xC8, 0x56, 0x91, 0x75, 0x69, 0x36, 0x21, 0x96, 0x5A, 0xCF, 0x9D, 0x7F, + 0x42, 0x1B, 0x96, 0x4A, 0xF7, 0xA7, 0x3D, 0xFB, 0x51, 0xF6, 0x8C, 0x47, 0x8B, 0xA0, 0x2D, 0x2A, + 0xB2, 0x57, 0x69, 0xAD, 0xCB, 0xF7, 0xEF, 0xF7, 0x9A, 0x0F, 0xFA, 0x47, 0x70, 0x25, 0xE7, 0x32, + 0x72, 0xFE, 0x09, 0xA6, 0x79, 0x71, 0x96, 0xC5, 0x97, 0x4B, 0x46, 0x46, 0x72, 0x95, 0x5A, 0x1E, + 0x21, 0x8E, 0x08, 0x64, 0x21, 0xE3, 0xAB, 0x95, 0x5E, 0x79, 0xE3, 0x7D, 0x7E, 0x30, 0x25, 0x86, + 0x85, 0xB5, 0xF0, 0xE6, 0xE3, 0xF5, 0xF5, 0xEB, 0x55, 0xFE, 0xB5, 0x26, 0x70, 0x77, 0xE1, 0x0D, + 0x57, 0xB3, 0x6E, 0x43, 0x04, 0xBD, 0x18, 0x41, 0xAF, 0x32, 0x82, 0x55, 0x2E, 0xB5, 0x6A, 0x43, + 0x4C, 0xD7, 0x57, 0x18, 0x1E, 0x7D, 0xFB, 0x96, 0xB2, 0x68, 0x17, 0x14, 0x3B, 0x8B, 0x42, 0x64, + 0xBC, 0x10, 0x3B, 0x24, 0x77, 0x14, 0xA3, 0x55, 0x16, 0x22, 0x16, 0x49, 0x8F, 0x55, 0x78, 0xC9, + 0x3D, 0x8D, 0x3A, 0x0A, 0xFE, 0x1B, 0xA8, 0xEB, 0xF9, 0x11, 0x16, 0x90, 0x2B, 0x4A, 0x3D, 0x62, + 0x3B, 0x0E, 0x0F, 0x58, 0x49, 0xB6, 0x58, 0xD4, 0x95, 0xA6, 0x9E, 0x65, 0x86, 0x6F, 0x56, 0x48, + 0x9D, 0xAA, 0x0D, 0x90, 0x6C, 0xEA, 0x99, 0x26, 0xDB, 0x1B, 0x02, 0x3F, 0x0F, 0xE6, 0x72, 0xCA, + 0xFC, 0x1B, 0xA9, 0x5A, 0x25, 0xFF, 0x42, 0xAA, 0x3F, 0x27, 0xF7, 0xE2, 0xB9, 0x5A, 0x9A, 0xCC, + 0x06, 0x06, 0x19, 0x47, 0x2B, 0x6B, 0x55, 0xAD, 0x4C, 0x76, 0xFB, 0x83, 0x9B, 0x8F, 0x7C, 0xF3, + 0xF3, 0x46, 0x04, 0xFF, 0xE7, 0xD9, 0x97, 0xAA, 0xE6, 0x85, 0xCF, 0xAC, 0x39, 0xDB, 0x2F, 0x0B, + 0xB9, 0xFB, 0x39, 0xB5, 0xAD, 0xE9, 0xAC, 0x23, 0x2D, 0x39, 0x4F, 0x99, 0xD3, 0x48, 0x53, 0xD2, + 0x32, 0x55, 0x1A, 0x6A, 0x6A, 0xAE, 0xA6, 0x26, 0x63, 0x4D, 0xBA, 0x99, 0x21, 0xD2, 0x1B, 0xDD, + 0x67, 0x07, 0x9B, 0x9A, 0x0D, 0xA9, 0x60, 0x40, 0xA4, 0x95, 0x62, 0x64, 0xB1, 0xDB, 0x0D, 0xFE, + 0x6F, 0x8E, 0xD3, 0x9D, 0xC2, 0x88, 0xF1, 0xA1, 0x52, 0xCA, 0x1D, 0x22, 0x59, 0xA6, 0xE2, 0x91, + 0xA2, 0x62, 0xCC, 0x0E, 0x16, 0x14, 0x82, 0x39, 0xFB, 0x53, 0xBC, 0xF9, 0xF0, 0xDA, 0x9C, 0x3C, + 0xCA, 0xE4, 0xB6, 0xE7, 0x20, 0x62, 0x5D, 0x61, 0x60, 0xCF, 0x10, 0x02, 0x51, 0x90, 0x67, 0x4A, + 0x47, 0x13, 0x4B, 0xB5, 0xF4, 0x72, 0x8C, 0x81, 0x88, 0x94, 0x37, 0xE6, 0xC2, 0xF7, 0xC9, 0xD8, + 0xF6, 0xEE, 0x65, 0xD2, 0x24, 0xE4, 0x6D, 0x6C, 0xD5, 0x51, 0x1E, 0x54, 0x3F, 0xD7, 0x1B, 0x4A, + 0x49, 0xB8, 0x0C, 0x87, 0x9C, 0xD9, 0x4A, 0x75, 0x76, 0x2E, 0xBD, 0x14, 0x2C, 0xF7, 0xBC, 0x3A, + 0x5B, 0xA7, 0xF6, 0x61, 0x20, 0x8B, 0x68, 0x2C, 0x6C, 0x47, 0xB6, 0x4E, 0x9D, 0x66, 0xF8, 0x91, + 0x1D, 0x83, 0xE5, 0x1D, 0x6A, 0x66, 0xAA, 0xD4, 0x6C, 0x84, 0x08, 0x21, 0x38, 0x82, 0xAE, 0x4E, + 0x83, 0x0E, 0x4C, 0x47, 0xFB, 0xC6, 0x8A, 0x9D, 0x30, 0x18, 0xD4, 0x3F, 0x98, 0xCF, 0x24, 0x6C, + 0x32, 0x1D, 0xA1, 0x9B, 0xB2, 0x3A, 0xD5, 0x3B, 0x9B, 0x10, 0xF9, 0x12, 0x4D, 0xA7, 0x13, 0x49, + 0x26, 0xC5, 0x06, 0x18, 0x7D, 0x0C, 0xB4, 0x31, 0xA3, 0xE4, 0xA9, 0x18, 0xEB, 0x45, 0x56, 0xD0, + 0x2B, 0xDF, 0x8F, 0x33, 0x8A, 0x14, 0x9F, 0xC5, 0xE5, 0x9C, 0x79, 0x69, 0x18, 0x3A, 0x7C, 0xDA, + 0x31, 0xEC, 0x98, 0xAB, 0x57, 0xF6, 0x30, 0xAA, 0x52, 0x04, 0x47, 0x74, 0xC4, 0x94, 0x64, 0x4C, + 0x1A, 0x24, 0xC5, 0xBE, 0x27, 0x70, 0x63, 0xBC, 0x2C, 0xCD, 0x80, 0x79, 0xEE, 0x93, 0xB0, 0x42, + 0x2E, 0xA6, 0x7A, 0xCC, 0x5B, 0xF5, 0x59, 0x17, 0x79, 0x02, 0x73, 0xEE, 0x84, 0x34, 0xE1, 0xBD, + 0xF4, 0xD4, 0xAE, 0x30, 0x3D, 0x4C, 0xA3, 0xCE, 0x2C, 0x43, 0x59, 0xDE, 0xBB, 0xD5, 0x72, 0xD6, + 0x54, 0xEE, 0xF4, 0x62, 0x74, 0xB3, 0x76, 0x7F, 0xE5, 0xE6, 0x5A, 0x73, 0x6B, 0x6C, 0xA1, 0x9A, + 0xCC, 0xD8, 0xDA, 0x07, 0x39, 0x7B, 0xDA, 0x3E, 0xCF, 0x5B, 0x07, 0xBE, 0x5B, 0x9C, 0x2E, 0x6E, + 0x8C, 0xAE, 0x1C, 0x26, 0xC9, 0xC5, 0xFB, 0xC7, 0x60, 0x4C, 0xC3, 0x12, 0xFD, 0xB2, 0xBD, 0x88, + 0x7A, 0x9E, 0xFD, 0x81, 0xBA, 0xC3, 0x9B, 0xE8, 0x72, 0x3C, 0x36, 0x1B, 0x66, 0xAD, 0x96, 0xAA, + 0x23, 0x64, 0x15, 0x6F, 0x30, 0xB0, 0x64, 0x79, 0x66, 0x26, 0x99, 0x87, 0x0F, 0x32, 0xA2, 0x21, + 0x54, 0xE5, 0xF7, 0x6E, 0xAF, 0xA6, 0x51, 0x24, 0x07, 0xE3, 0x57, 0x4B, 0xA5, 0x16, 0x05, 0xF7, + 0xE6, 0x4E, 0xC6, 0x60, 0x85, 0x22, 0x5D, 0x7B, 0x00, 0xE7, 0x38, 0x1A, 0xDC, 0x90, 0x45, 0x8A, + 0x3B, 0x4A, 0x66, 0x6F, 0x49, 0x72, 0x43, 0xFA, 0xE0, 0x1C, 0x5F, 0x00, 0x12, 0xE2, 0x86, 0x21, + 0xFC, 0x64, 0xD7, 0x2A, 0x8E, 0x0E, 0xE2, 0x94, 0x76, 0x75, 0xC6, 0x72, 0x71, 0xCE, 0xB9, 0x8A, + 0xED, 0x31, 0xA8, 0x53, 0x83, 0x06, 0xB1, 0xDB, 0xCB, 0xB5, 0x5B, 0x24, 0xF5, 0xE3, 0xDE, 0x68, + 0x44, 0x02, 0x3A, 0x9C, 0x8E, 0x30, 0x49, 0x20, 0x5B, 0x09, 0xF1, 0x3E, 0x4D, 0xD6, 0x21, 0x5A, + 0x77, 0xD6, 0x68, 0x6B, 0xAE, 0xC5, 0xA9, 0xD9, 0xCE, 0xA3, 0x06, 0xFD, 0xF5, 0x60, 0xCA, 0xBF, + 0x18, 0x5F, 0x07, 0x23, 0x18, 0xC6, 0x45, 0xD3, 0x6B, 0x74, 0x2C, 0x00, 0x84, 0xC7, 0xB1, 0xC1, + 0xE8, 0xF5, 0x86, 0x54, 0xEC, 0x29, 0x68, 0x61, 0x2A, 0x0B, 0x0B, 0x3B, 0x2F, 0xD2, 0xE9, 0x07, + 0xE6, 0x01, 0x11, 0xFB, 0x76, 0x12, 0x0E, 0x0D, 0x35, 0x05, 0x03, 0x07, 0x3E, 0xAE, 0xDF, 0xA6, + 0x3C, 0xB7, 0x1E, 0x28, 0x0F, 0x5E, 0xCF, 0x81, 0x65, 0x5D, 0x80, 0xE1, 0x7D, 0xB0, 0xA4, 0x22, + 0x77, 0xB0, 0x74, 0x4B, 0x7D, 0x16, 0xE8, 0x82, 0x6B, 0xFF, 0x45, 0x6C, 0xF2, 0xF5, 0xCC, 0x7C, + 0x7C, 0x10, 0xC8, 0xF2, 0xCA, 0xCD, 0x79, 0x9B, 0x76, 0xF0, 0x3B, 0x1F, 0xFC, 0x6C, 0xE8, 0x3B, + 0x7E, 0xC7, 0x87, 0x33, 0x92, 0xF6, 0xE9, 0x30, 0x9B, 0xA2, 0xE6, 0xC3, 0xDF, 0xA0, 0x35, 0x8E, + 0x1B, 0x4E, 0xB0, 0x8F, 0x58, 0x92, 0x6D, 0x8C, 0xAE, 0x26, 0xFB, 0xD3, 0x10, 0xE6, 0x40, 0xFE, + 0x37, 0x2C, 0x45, 0x51, 0x4C, 0x0B, 0x4B, 0x52, 0x2F, 0x18, 0x30, 0xF5, 0xE9, 0xA0, 0x31, 0x2A, + 0xBC, 0x64, 0x85, 0xCE, 0x5B, 0x5F, 0x5C, 0xE5, 0x9B, 0x19, 0xD1, 0x3B, 0x74, 0xB4, 0x1A, 0x63, + 0x61, 0x6E, 0xDA, 0x5B, 0x76, 0xCB, 0x54, 0xCE, 0x0F, 0xCA, 0x63, 0x7A, 0xE6, 0x8B, 0xF3, 0x3D, + 0xF5, 0x1B, 0xA3, 0xE4, 0xDE, 0xCA, 0x69, 0x14, 0xB8, 0x93, 0x3E, 0x05, 0xC1, 0x05, 0xF3, 0xC0, + 0xB4, 0x3F, 0x72, 0x59, 0x90, 0x70, 0x43, 0x4C, 0x78, 0xC7, 0x0E, 0xAF, 0x33, 0xE1, 0xD0, 0xF2, + 0xF0, 0x02, 0xC2, 0x4C, 0x1C, 0x9D, 0xF9, 0xAE, 0x17, 0x9D, 0xD1, 0xE0, 0x1A, 0x16, 0x92, 0xD2, + 0x92, 0x79, 0x46, 0xAC, 0x87, 0xF1, 0x4D, 0xDC, 0x29, 0xB3, 0xE8, 0x73, 0xC1, 0xF5, 0x41, 0x04, + 0xEF, 0xF3, 0x3C, 0xF5, 0xFE, 0x70, 0xC8, 0x02, 0xED, 0x67, 0x43, 0x1D, 0x4E, 0x4E, 0xFD, 0xBB, + 0x19, 0x91, 0x1C, 0xFB, 0x81, 0xDD, 0x18, 0x85, 0xF0, 0x19, 0xCE, 0x6E, 0x50, 0x6D, 0x29, 0x9E, + 0x1A, 0x32, 0xF7, 0x62, 0x46, 0x86, 0xE6, 0x21, 0x1B, 0x81, 0xE5, 0xFC, 0xE8, 0xF4, 0xF0, 0xB7, + 0x19, 0x51, 0xED, 0x9D, 0x9F, 0xCD, 0x87, 0x27, 0x5C, 0x36, 0xBC, 0x4B, 0x82, 0x87, 0x1D, 0x76, + 0xC0, 0x3E, 0x33, 0xC2, 0x78, 0x01, 0xD3, 0x0C, 0x4F, 0xDE, 0x85, 0xE7, 0x5A, 0x08, 0x13, 0x8C, + 0x7C, 0x3A, 0x41, 0x2B, 0x88, 0x49, 0xEB, 0xE5, 0x1D, 0x0A, 0x5E, 0x12, 0x87, 0x1D, 0xEA, 0xA5, + 0xD1, 0x40, 0x58, 0x95, 0xB7, 0xE8, 0x6C, 0x68, 0x25, 0xDC, 0x72, 0x65, 0x4A, 0xA6, 0xCE, 0x24, + 0xF3, 0xCD, 0xB9, 0x19, 0x4C, 0x0E, 0xC5, 0xAD, 0xFD, 0x2C, 0x75, 0x58, 0xA9, 0xE3, 0xCA, 0x3A, + 0x5B, 0xB0, 0x9F, 0xD9, 0x86, 0x74, 0x53, 0xBC, 0x0F, 0xCA, 0x93, 0x0B, 0xCC, 0xBD, 0x07, 0x77, + 0x43, 0x4C, 0xBA, 0xC2, 0xB5, 0x09, 0xA3, 0x7B, 0x70, 0x2A, 0x50, 0x60, 0x23, 0xF6, 0x9E, 0x45, + 0xCB, 0xF3, 0x3D, 0xDA, 0x52, 0x1F, 0x6B, 0x60, 0xCB, 0xA0, 0x72, 0xC8, 0x2B, 0xF0, 0xEB, 0xBF, + 0x71, 0x50, 0xF0, 0xAA, 0x03, 0x8A, 0xF3, 0xF8, 0x7E, 0xFF, 0x17, 0xBC, 0x53, 0x8C, 0xBE, 0x82, + 0xB8, 0xFD, 0xCA, 0x12, 0x34, 0x4B, 0xB8, 0x45, 0xF2, 0xFA, 0xE4, 0x71, 0x0A, 0x9E, 0xD1, 0x9E, + 0x55, 0x45, 0x60, 0xE9, 0x64, 0x60, 0x75, 0xF5, 0x28, 0x75, 0xEB, 0x92, 0xBA, 0x83, 0x51, 0x28, + 0x5E, 0x8B, 0xC0, 0x04, 0xF2, 0x49, 0x0E, 0xE1, 0xBF, 0x4E, 0x69, 0x70, 0xCF, 0x9F, 0x47, 0xF0, + 0x03, 0x70, 0x0D, 0x17, 0x5B, 0x1D, 0xF9, 0x74, 0xB9, 0x4D, 0x3A, 0xCA, 0x51, 0x5A, 0xAB, 0x20, + 0x31, 0xB5, 0x44, 0x41, 0xEC, 0x73, 0xC2, 0xF7, 0xE5, 0x65, 0xD9, 0x7B, 0x91, 0x18, 0x5E, 0xDE, + 0x95, 0x01, 0xBE, 0x7C, 0xFF, 0xDA, 0x11, 0x59, 0xA6, 0xD9, 0x55, 0x03, 0xB5, 0x84, 0xFB, 0x95, + 0xAC, 0x30, 0xEE, 0xB1, 0x17, 0x99, 0x45, 0x49, 0xDB, 0xB0, 0x26, 0x59, 0x5D, 0x3D, 0xA7, 0x63, + 0xFF, 0x96, 0xB2, 0x68, 0x49, 0xDC, 0x92, 0xC4, 0xA0, 0x4A, 0xF6, 0x07, 0x2F, 0xAF, 0x2B, 0x94, + 0xF4, 0x98, 0xE0, 0x95, 0xE7, 0x47, 0x8B, 0x1D, 0x3D, 0x54, 0x73, 0x29, 0x96, 0x57, 0x20, 0xE7, + 0x06, 0x78, 0x5A, 0x61, 0xC5, 0x0B, 0x8E, 0xAC, 0xB8, 0x4C, 0x7C, 0xC8, 0xD1, 0xC8, 0x95, 0xF8, + 0x50, 0x82, 0x9B, 0x81, 0x08, 0xA6, 0x99, 0x5E, 0x68, 0x2F, 0xC0, 0x4F, 0x09, 0x15, 0xF0, 0x51, + 0x8F, 0x7E, 0x12, 0xAD, 0x5C, 0x8B, 0x87, 0x24, 0x08, 0xDA, 0xC4, 0x47, 0x5C, 0x58, 0x9D, 0x97, + 0xC2, 0x3D, 0xFB, 0x32, 0x96, 0x0C, 0x07, 0x1D, 0x2C, 0x14, 0xD0, 0x8C, 0x94, 0x33, 0xC5, 0xF9, + 0x2D, 0xD8, 0x9F, 0x37, 0xF3, 0xAE, 0x2D, 0x71, 0x3C, 0xF0, 0x24, 0x87, 0x7C, 0x95, 0x94, 0xF2, + 0x94, 0x79, 0x9A, 0x06, 0x0D, 0xC8, 0xA2, 0x52, 0xFE, 0x22, 0x0E, 0xF6, 0x95, 0x1E, 0xC5, 0x00, + 0x61, 0xA4, 0xCF, 0x66, 0x2C, 0xC6, 0x85, 0x6D, 0xD2, 0xB3, 0x2C, 0x6B, 0x29, 0x6B, 0xED, 0x58, + 0x64, 0x07, 0x58, 0xB9, 0x36, 0x61, 0x0B, 0x3B, 0x3C, 0x9A, 0x52, 0xAC, 0x1F, 0xB6, 0x91, 0x47, + 0x7F, 0x68, 0xCB, 0x25, 0x0C, 0xAF, 0x67, 0x05, 0xAF, 0x08, 0x8A, 0x20, 0x81, 0x46, 0x12, 0x09, + 0x85, 0xC1, 0x88, 0xDA, 0x41, 0x4C, 0xA2, 0x1A, 0xDE, 0x85, 0x2C, 0x8F, 0xFD, 0xE9, 0x60, 0x00, + 0xAB, 0x3B, 0xC6, 0xA5, 0x94, 0xA9, 0x3F, 0xC1, 0x23, 0xCA, 0x4D, 0x1C, 0x2E, 0xF0, 0x64, 0xFD, + 0x59, 0xA6, 0x52, 0x9C, 0x55, 0xD1, 0x19, 0x18, 0x83, 0x55, 0x64, 0xCC, 0x94, 0x90, 0x5F, 0xBC, + 0x9C, 0x4B, 0xDE, 0x2C, 0xBA, 0x16, 0x2B, 0x66, 0xD3, 0x4A, 0x30, 0xBB, 0x12, 0x8F, 0x77, 0x1C, + 0xF0, 0x60, 0x13, 0xE7, 0xDF, 0x4E, 0xC0, 0x8C, 0xDF, 0xA2, 0x08, 0xC3, 0x49, 0x38, 0xDB, 0x29, + 0x86, 0xC2, 0xDB, 0xB5, 0x4A, 0xE4, 0x8E, 0xF1, 0x8C, 0x80, 0x2F, 0xE8, 0xFF, 0xAE, 0xAC, 0xBE, + 0xB5, 0xFD, 0x82, 0xDA, 0x1C, 0x66, 0xC8, 0x95, 0xF1, 0x67, 0x68, 0x52, 0x31, 0x87, 0x86, 0x1B, + 0x31, 0xB9, 0x3D, 0x2D, 0xFA, 0x28, 0xD5, 0x71, 0x49, 0x7B, 0xE4, 0xFE, 0x6B, 0x09, 0x45, 0x16, + 0x4F, 0x3B, 0x24, 0x97, 0xDF, 0x74, 0xA5, 0x91, 0xFA, 0x5B, 0x73, 0x11, 0x0C, 0x9A, 0xC2, 0x9E, + 0x41, 0x42, 0xBC, 0xEC, 0xE6, 0x0D, 0xDF, 0xD6, 0x94, 0xF0, 0xA1, 0x8D, 0x97, 0xE3, 0x20, 0x3E, + 0xBD, 0xFD, 0x4D, 0x52, 0x46, 0x56, 0x2C, 0xE6, 0x04, 0x56, 0xCC, 0xE8, 0x5A, 0x30, 0x8C, 0x37, + 0x36, 0x80, 0xDF, 0x13, 0x70, 0xF2, 0x30, 0xF5, 0xC8, 0x15, 0x8D, 0xEE, 0xF0, 0xEC, 0xD8, 0x62, + 0x7B, 0x3F, 0x50, 0xD6, 0x6A, 0xA3, 0x1D, 0x1B, 0x8D, 0xEC, 0x49, 0x48, 0x31, 0x8B, 0x23, 0x4F, + 0xC3, 0xC2, 0x76, 0x3B, 0x54, 0xF5, 0x35, 0xD1, 0xC7, 0x75, 0xED, 0x63, 0xF2, 0x80, 0xF8, 0xCB, + 0xF9, 0xC0, 0x2C, 0x70, 0x55, 0xB8, 0xD8, 0xCC, 0x67, 0x62, 0x73, 0x16, 0x39, 0x20, 0xFD, 0xAA, + 0x92, 0x68, 0xC6, 0x83, 0x51, 0x0E, 0xA2, 0x62, 0x9F, 0xCF, 0x23, 0x60, 0xB8, 0x07, 0xFC, 0x6A, + 0xFC, 0x00, 0xD6, 0x21, 0xB2, 0x49, 0x16, 0x05, 0xA6, 0x21, 0x88, 0x2A, 0x1D, 0x0F, 0x24, 0xA8, + 0xC9, 0x21, 0x4D, 0x43, 0x15, 0xA6, 0x9F, 0x15, 0x56, 0xBA, 0x82, 0x1E, 0x61, 0x4D, 0x90, 0xE9, + 0xA4, 0x08, 0x80, 0x0F, 0xED, 0x2C, 0x01, 0xB9, 0xAD, 0xE0, 0x0C, 0xBA, 0xE8, 0xD2, 0xBF, 0x73, + 0xE9, 0xC8, 0x09, 0x17, 0xD3, 0x97, 0x57, 0xF6, 0x85, 0x10, 0x84, 0x17, 0x3D, 0x48, 0xF3, 0xC4, + 0xE8, 0xE2, 0x49, 0x04, 0x2B, 0xF6, 0xF3, 0x44, 0xBA, 0x21, 0x90, 0x78, 0xBC, 0x29, 0x88, 0xD4, + 0x63, 0xF7, 0x2D, 0x17, 0x3A, 0xED, 0x16, 0x04, 0xC5, 0x1B, 0xF9, 0x75, 0xE1, 0x50, 0xA5, 0x04, + 0x28, 0xFC, 0x5A, 0x0D, 0xFA, 0x6D, 0x9A, 0xF9, 0x0C, 0x40, 0x59, 0x1E, 0xB4, 0xFA, 0x54, 0x99, + 0x12, 0x09, 0xF0, 0xCA, 0x94, 0xCF, 0xCE, 0x24, 0x41, 0xC9, 0xBB, 0x32, 0xD5, 0xA0, 0xA5, 0x04, + 0x69, 0x0C, 0x01, 0xFE, 0x59, 0x09, 0xF2, 0x57, 0xF7, 0x9D, 0x9B, 0x02, 0xDE, 0xB9, 0xD7, 0x6E, + 0x35, 0xB8, 0x8B, 0xFD, 0xB3, 0x4F, 0x07, 0x12, 0xCF, 0xB0, 0xD2, 0xFC, 0xE4, 0x4C, 0xAA, 0xC1, + 0xB2, 0xDB, 0xCF, 0x29, 0x28, 0xF3, 0xE7, 0xAB, 0x41, 0x66, 0x97, 0x9A, 0xF7, 0x29, 0x1E, 0xC9, + 0xD7, 0xAB, 0x86, 0xAD, 0x7F, 0x1F, 0x46, 0x74, 0x9C, 0x22, 0x08, 0xF9, 0xDF, 0x95, 0x60, 0x0F, + 0xD5, 0xEC, 0x76, 0x00, 0x9D, 0xE4, 0xBB, 0xAB, 0x04, 0x7F, 0x7A, 0x21, 0x09, 0x0F, 0x53, 0xE0, + 0x55, 0x82, 0x7A, 0x27, 0x3D, 0x8F, 0x03, 0x70, 0xF2, 0x12, 0x29, 0x86, 0xE7, 0xCE, 0x82, 0x3E, + 0xBB, 0xF1, 0x51, 0x2C, 0x86, 0x24, 0xE1, 0x94, 0x33, 0x06, 0x94, 0xE7, 0xEA, 0x48, 0x6F, 0xBE, + 0x63, 0x6C, 0x41, 0x1B, 0x5F, 0x2D, 0x5B, 0xDF, 0x96, 0x8C, 0x29, 0xCB, 0x45, 0xBB, 0xBE, 0x0D, + 0x90, 0x76, 0x60, 0x0F, 0xF0, 0xFC, 0x4C, 0x36, 0xA7, 0xEA, 0xA8, 0x5F, 0x4A, 0x48, 0xB3, 0xC4, + 0xC5, 0x39, 0x74, 0xB9, 0xE1, 0x36, 0xEC, 0x9C, 0xB7, 0xF1, 0x7D, 0x29, 0xCB, 0xEA, 0xF6, 0xDA, + 0xEC, 0x81, 0xAC, 0xAC, 0x3D, 0xE7, 0xA5, 0xCC, 0xAA, 0x77, 0x2D, 0x06, 0x60, 0x32, 0x20, 0xB1, + 0x54, 0x91, 0xE4, 0xBE, 0x9C, 0xB9, 0x8D, 0xED, 0x52, 0xE4, 0xB2, 0xC3, 0xD3, 0xAD, 0xB5, 0xD8, + 0x74, 0xB2, 0x6D, 0xE5, 0x4E, 0x27, 0xDB, 0x56, 0x05, 0xB2, 0x19, 0xD4, 0xFB, 0xA7, 0x1F, 0x67, + 0xC2, 0x9C, 0xF8, 0xB0, 0x79, 0xDB, 0xCB, 0xC9, 0xD9, 0x58, 0xD6, 0xBB, 0x35, 0xF5, 0xB9, 0x97, + 0x02, 0xEF, 0xDB, 0x20, 0xA1, 0xE0, 0x83, 0x1F, 0x46, 0xA2, 0xF7, 0x37, 0x32, 0xBD, 0xBF, 0x91, + 0xD3, 0xFB, 0xD9, 0xD6, 0xE7, 0x48, 0x20, 0x43, 0x0E, 0x2D, 0x18, 0x27, 0xB7, 0x8D, 0xFF, 0x74, + 0x8A, 0xEC, 0x63, 0x3D, 0x4A, 0x86, 0x96, 0xB1, 0xD7, 0xB1, 0xD8, 0x9E, 0x37, 0x27, 0xB5, 0x66, + 0xE9, 0x74, 0xD6, 0xAC, 0xC6, 0x2D, 0xE3, 0xED, 0xF8, 0x14, 0xD2, 0x20, 0x5F, 0x9A, 0xD9, 0x02, + 0xAC, 0xCF, 0x8C, 0x60, 0xF4, 0xAD, 0x37, 0xF4, 0x3B, 0x03, 0x7F, 0x8C, 0x7F, 0xE1, 0x57, 0x32, + 0x46, 0xC6, 0x30, 0x6D, 0x2E, 0x18, 0x16, 0x17, 0x3C, 0xD6, 0xB1, 0xED, 0x8E, 0x30, 0x16, 0x31, + 0x00, 0x9F, 0xA8, 0x98, 0xB5, 0xF8, 0xBC, 0x8E, 0xF0, 0xF3, 0x32, 0x8C, 0xAC, 0xBD, 0x03, 0x5B, + 0x29, 0x8E, 0xFD, 0x30, 0x3A, 0x52, 0xE4, 0xDA, 0x0B, 0x09, 0xD8, 0x2C, 0xDA, 0x21, 0x9C, 0x9E, + 0x7D, 0x4F, 0xEE, 0x6C, 0x30, 0x19, 0x20, 0x07, 0xC7, 0x0D, 0x59, 0x38, 0xE8, 0xE9, 0xC5, 0xF9, + 0xD1, 0x19, 0x6E, 0xFB, 0xB0, 0x88, 0xC8, 0x91, 0xCF, 0xB9, 0x21, 0xF4, 0x3B, 0x78, 0x14, 0xB8, + 0x91, 0x16, 0xA7, 0x93, 0xEB, 0xC4, 0x14, 0xA5, 0x65, 0x0A, 0xFC, 0xC5, 0xF7, 0x81, 0x70, 0xA5, + 0xC9, 0x65, 0x54, 0x20, 0x19, 0x49, 0x00, 0xA2, 0x15, 0xD5, 0xE0, 0x85, 0xE2, 0xF4, 0xBA, 0x56, + 0xB7, 0x22, 0x9C, 0xA2, 0x05, 0xAD, 0xAB, 0x91, 0x13, 0x5C, 0xF6, 0x27, 0x76, 0xF0, 0xED, 0xDD, + 0xD4, 0xEB, 0xB6, 0x6A, 0xE3, 0x38, 0xFB, 0xB5, 0x1E, 0xBF, 0x71, 0x87, 0x47, 0x34, 0x8C, 0xFE, + 0x0F, 0xFE, 0xA8, 0xDF, 0x64, 0x44, 0x01, 0x64, 0x01, 0x89, 0x06, 0x37, 0x54, 0x47, 0xD6, 0x85, + 0xC8, 0x64, 0xF8, 0xFE, 0xFD, 0x5E, 0x4E, 0xA0, 0x09, 0xC0, 0x69, 0xDB, 0x7D, 0xE8, 0xDF, 0xC1, + 0x82, 0x23, 0x49, 0xDF, 0x9E, 0x6E, 0x74, 0xAE, 0xAE, 0x8E, 0xD9, 0xA2, 0x0F, 0x6B, 0xB8, 0x4E, + 0x48, 0xC2, 0xC8, 0x0E, 0x98, 0x0E, 0x30, 0xA5, 0xD2, 0xAE, 0x60, 0xBE, 0xF8, 0x41, 0xA9, 0x03, + 0x71, 0x69, 0x97, 0xB2, 0x52, 0xBC, 0xF1, 0xB8, 0xC0, 0xF3, 0xB2, 0xB8, 0xCE, 0xBF, 0xED, 0x6A, + 0xDC, 0x7F, 0x95, 0x17, 0xBD, 0xA6, 0xDD, 0x2C, 0x89, 0x44, 0xDE, 0x16, 0x96, 0x39, 0x12, 0x5D, + 0x02, 0xE4, 0x3B, 0x8D, 0x3B, 0xD9, 0x78, 0x4B, 0x7D, 0xED, 0x69, 0x8E, 0x23, 0x96, 0x93, 0x6C, + 0x55, 0xEF, 0x18, 0x16, 0x35, 0x3F, 0x5F, 0x69, 0x31, 0x94, 0xCF, 0x4A, 0x62, 0x6C, 0x8D, 0x58, + 0x24, 0x35, 0x93, 0xCE, 0xE3, 0xE2, 0xD2, 0xA0, 0xF5, 0xF5, 0xB2, 0xD5, 0x4B, 0xEC, 0x57, 0x96, + 0xA6, 0x7E, 0xD5, 0xB3, 0x54, 0x94, 0xE3, 0x46, 0x72, 0x1C, 0xD7, 0x12, 0x22, 0x5B, 0xEA, 0x97, + 0x29, 0xDE, 0x8C, 0xAD, 0x4D, 0x6F, 0x7D, 0xFC, 0x8F, 0x6A, 0x72, 0xB5, 0xF1, 0xF2, 0x54, 0x2D, + 0xAE, 0x3B, 0x56, 0xD4, 0x67, 0xB5, 0x78, 0x6E, 0xDC, 0xF8, 0xBE, 0x6B, 0xF9, 0x88, 0x29, 0x78, + 0x94, 0x6B, 0x06, 0xC9, 0x66, 0xAE, 0x29, 0xFF, 0x28, 0xE9, 0x2A, 0x3E, 0xAE, 0x04, 0x8B, 0xEB, + 0x18, 0x70, 0x37, 0xF1, 0x01, 0x0F, 0xF3, 0x32, 0xA6, 0xCB, 0x37, 0xA6, 0xB0, 0xBC, 0x70, 0x7F, + 0x6C, 0xAE, 0x0A, 0xA9, 0x5F, 0xD6, 0xFE, 0xA7, 0x94, 0x5A, 0xBC, 0xA3, 0x97, 0xA3, 0xD4, 0x2C, + 0x07, 0xB3, 0xB4, 0x2C, 0x8D, 0x57, 0x54, 0x86, 0x80, 0xA9, 0x8A, 0x0B, 0x2A, 0xB1, 0xC0, 0xF1, + 0xAF, 0x42, 0x14, 0x2E, 0x4B, 0x32, 0x4E, 0x07, 0xBE, 0xE7, 0xA0, 0xCB, 0xBE, 0x69, 0xB1, 0xE6, + 0x18, 0x1A, 0xB3, 0x69, 0x89, 0x17, 0x5E, 0xE4, 0x86, 0xBC, 0x35, 0xE6, 0xE1, 0x2F, 0x26, 0x76, + 0xE6, 0x87, 0x2E, 0xFE, 0x7F, 0x6F, 0x30, 0x98, 0xC2, 0x32, 0xE6, 0x9E, 0x2F, 0x74, 0x36, 0x50, + 0x90, 0x19, 0xA2, 0xDD, 0x0E, 0xA3, 0xBA, 0xD1, 0x29, 0xA0, 0x9A, 0x92, 0x55, 0x7C, 0x52, 0x16, + 0x6D, 0x75, 0xC8, 0x52, 0x82, 0xB5, 0xC9, 0x4A, 0xB7, 0xB7, 0x65, 0xF5, 0xAC, 0xCD, 0xCE, 0xC6, + 0xE6, 0x96, 0xCC, 0xA8, 0x19, 0xE2, 0x33, 0x42, 0xAC, 0xBF, 0xEC, 0x6E, 0x6E, 0x59, 0xEB, 0x9D, + 0x75, 0x6B, 0xAD, 0x1C, 0xE2, 0x77, 0x80, 0x58, 0xB7, 0xB6, 0x36, 0x37, 0x37, 0x37, 0x3A, 0xEB, + 0x5B, 0xEB, 0xC5, 0x00, 0xC7, 0x76, 0xC4, 0x92, 0x93, 0x23, 0x48, 0xC7, 0xDA, 0xB6, 0x7A, 0xDB, + 0xEB, 0x2F, 0xB7, 0x4B, 0x40, 0x7C, 0x6F, 0x28, 0x60, 0x56, 0xBA, 0x16, 0xC8, 0x6A, 0x6B, 0xC3, + 0xDA, 0x78, 0xB9, 0xD9, 0x2D, 0x86, 0xDA, 0x1B, 0x45, 0x6E, 0x34, 0x75, 0xD8, 0x16, 0xC9, 0xC6, + 0x26, 0xD0, 0xDA, 0xDA, 0x56, 0x96, 0x5F, 0xDA, 0x81, 0x8D, 0x8A, 0x21, 0x47, 0x3D, 0x72, 0x29, + 0x16, 0xF7, 0x30, 0xF4, 0xA0, 0xDC, 0x55, 0xB1, 0x12, 0x17, 0x87, 0xD9, 0x15, 0xE8, 0x73, 0x43, + 0xD1, 0x36, 0x17, 0x6F, 0x2D, 0x11, 0x97, 0x58, 0x10, 0x4D, 0x37, 0x5F, 0x5A, 0xEC, 0x5F, 0x9B, + 0x24, 0xBF, 0x24, 0xE3, 0x20, 0x2E, 0xC3, 0x41, 0x20, 0x7E, 0x2D, 0x1F, 0x7E, 0x65, 0x44, 0x3F, + 0xFF, 0x08, 0xA2, 0xBF, 0x3F, 0x06, 0xD1, 0x87, 0xA2, 0x58, 0xE5, 0xFA, 0xE6, 0xA0, 0x99, 0x49, + 0xA8, 0x65, 0x16, 0xB2, 0xF2, 0x4A, 0x83, 0x96, 0xC0, 0xD8, 0xFF, 0x42, 0x03, 0x4C, 0x81, 0x0F, + 0xDF, 0x56, 0xD9, 0x1D, 0xA1, 0x34, 0x0B, 0x44, 0x25, 0x41, 0x4B, 0xDA, 0xB8, 0xD2, 0xED, 0x5A, + 0x6B, 0xEB, 0x6D, 0xB2, 0xB5, 0xA5, 0x6C, 0x8D, 0xF2, 0xCF, 0x28, 0x64, 0x2C, 0xA8, 0x60, 0x54, + 0x73, 0xC8, 0xE9, 0x77, 0x09, 0x90, 0x20, 0x3E, 0x7F, 0xB0, 0xA1, 0xD0, 0xDA, 0x60, 0x0F, 0x0A, + 0x6C, 0x34, 0xD6, 0x1E, 0x73, 0xF4, 0x21, 0xD0, 0xEA, 0xF1, 0x19, 0xB7, 0xA7, 0x4D, 0xBC, 0xFC, + 0x3B, 0x7F, 0xF5, 0xC0, 0xEA, 0x54, 0x56, 0xA0, 0x87, 0xDC, 0xBD, 0xCA, 0x24, 0xA8, 0xB6, 0xC9, + 0x5E, 0x25, 0x07, 0x4E, 0xF7, 0x90, 0x2E, 0xAD, 0xCA, 0x7B, 0xD5, 0x32, 0x3C, 0x63, 0xDA, 0xAA, + 0xB2, 0x5B, 0x29, 0x13, 0xC4, 0x4D, 0xA7, 0x98, 0x60, 0xF1, 0x7E, 0x65, 0x75, 0x5A, 0xB9, 0xAD, + 0xC3, 0xED, 0x1E, 0x46, 0xCC, 0xD2, 0x5A, 0x67, 0xCD, 0xD8, 0xBA, 0x42, 0x8A, 0x67, 0xBF, 0x3E, + 0x15, 0xCD, 0x74, 0x2F, 0xED, 0x51, 0xFA, 0xB0, 0x90, 0xE2, 0xD3, 0xB5, 0x52, 0xD2, 0xD4, 0x6E, + 0x73, 0x8A, 0xDD, 0x46, 0x9A, 0x2A, 0x08, 0xEA, 0x9A, 0x6A, 0x15, 0x6B, 0x6A, 0xB7, 0x99, 0xA6, + 0x3E, 0x46, 0xEB, 0xCA, 0x34, 0xF5, 0x89, 0x68, 0x4A, 0x9A, 0xFA, 0xE4, 0x14, 0x9F, 0xAE, 0x95, + 0x92, 0xA6, 0xF6, 0x9A, 0x53, 0xEC, 0x35, 0xD2, 0xD4, 0x5E, 0x23, 0x4D, 0xED, 0x35, 0xD3, 0xD4, + 0xC7, 0x68, 0x5D, 0x99, 0xA6, 0x3E, 0x11, 0x4D, 0x49, 0x53, 0x9F, 0x9C, 0xE2, 0xD3, 0xB5, 0x52, + 0xD2, 0xD4, 0xB5, 0xE6, 0x14, 0xD7, 0x1A, 0x69, 0xEA, 0x5A, 0x23, 0x4D, 0x5D, 0x6B, 0xA6, 0xA9, + 0x8F, 0xD1, 0xBA, 0x32, 0x4D, 0x7D, 0x22, 0x9A, 0x92, 0xA6, 0x3E, 0x39, 0xC5, 0x39, 0xB5, 0xF2, + 0xB9, 0x1F, 0xB7, 0x1A, 0x5D, 0xE5, 0x5A, 0x07, 0xAE, 0x46, 0xDF, 0xB7, 0xD2, 0x91, 0xAB, 0xD1, + 0x91, 0xAD, 0x7B, 0xF8, 0x99, 0xE3, 0x9B, 0xB6, 0xAA, 0x42, 0x6A, 0x1E, 0x26, 0x3F, 0xF3, 0x75, + 0xEE, 0xBC, 0xE8, 0xCE, 0xEB, 0xD5, 0xC7, 0x11, 0x53, 0xFF, 0xF5, 0x7C, 0xE3, 0xEE, 0xDC, 0x5F, + 0xFF, 0x50, 0x8F, 0xFF, 0xC4, 0x03, 0x6C, 0x35, 0x10, 0x3B, 0xF3, 0x00, 0xEA, 0xCB, 0xBC, 0x01, + 0x39, 0xC9, 0xB1, 0x6A, 0x22, 0xE6, 0x86, 0x70, 0x75, 0x29, 0x6A, 0xAE, 0x4A, 0x13, 0x91, 0xF6, + 0x9A, 0x89, 0xB4, 0xD7, 0x58, 0xA4, 0xBD, 0x86, 0x22, 0xED, 0x35, 0x16, 0x69, 0xAF, 0xA1, 0x48, + 0xD7, 0x1A, 0x8A, 0x74, 0xAD, 0x99, 0x48, 0xD7, 0x1A, 0x8B, 0x74, 0xAD, 0xA1, 0x48, 0xD7, 0x1A, + 0x8B, 0x54, 0x87, 0x4C, 0x0E, 0x14, 0xE4, 0x1B, 0xB5, 0xA6, 0x83, 0x85, 0xB2, 0x7B, 0xB7, 0x05, + 0x5B, 0x21, 0x71, 0x06, 0x00, 0x76, 0xC1, 0x5E, 0x42, 0x70, 0xC0, 0x5E, 0x34, 0x12, 0x81, 0x77, + 0x17, 0xFE, 0x37, 0xEA, 0xC5, 0xC9, 0x27, 0x76, 0x5E, 0x68, 0x39, 0x4F, 0xA6, 0x34, 0x49, 0xA1, + 0xAE, 0xBF, 0x2A, 0x61, 0x0C, 0x04, 0x2C, 0xA6, 0xD2, 0x26, 0x6B, 0x9B, 0xFC, 0xBF, 0x64, 0xEA, + 0x5C, 0xDB, 0xCC, 0x8B, 0x0B, 0x3C, 0x33, 0x85, 0x28, 0x15, 0xEE, 0x91, 0x97, 0x52, 0x6F, 0xB5, + 0xF4, 0xFB, 0xB4, 0xD3, 0xC8, 0xFF, 0x0B, 0xBD, 0x3F, 0xA7, 0x1E, 0xBD, 0xB3, 0x47, 0x39, 0x31, + 0x2F, 0x52, 0x57, 0x05, 0x05, 0x5D, 0xA4, 0x5F, 0x62, 0xAE, 0x77, 0xFC, 0x43, 0x65, 0x68, 0xBC, + 0xF2, 0xF3, 0x96, 0x9F, 0xB9, 0x70, 0x6C, 0x6C, 0x93, 0x63, 0xD3, 0xCA, 0x6C, 0xD3, 0x76, 0xF9, + 0x21, 0x50, 0x1B, 0xBF, 0x6A, 0x5B, 0xB4, 0x67, 0xE6, 0x27, 0x90, 0xCB, 0x48, 0x1F, 0xB3, 0xDE, + 0x9E, 0x13, 0xC1, 0x44, 0x72, 0x18, 0x34, 0x5C, 0x16, 0x44, 0x8A, 0xA1, 0xC4, 0xA7, 0xD0, 0x68, + 0x3F, 0xF8, 0x76, 0x69, 0xF5, 0xFB, 0x47, 0x07, 0x95, 0x1D, 0x2D, 0x43, 0x68, 0xB2, 0x21, 0x94, + 0xD2, 0x48, 0xE7, 0xCC, 0x0E, 0x43, 0xF8, 0xCD, 0x79, 0x02, 0x5A, 0xDD, 0x27, 0x6A, 0x53, 0xF7, + 0x09, 0xDB, 0xD4, 0x7B, 0xA2, 0x36, 0xF5, 0x9E, 0xB0, 0x4D, 0x6B, 0x4F, 0xD4, 0xA6, 0xB5, 0x39, + 0xB5, 0x49, 0x0C, 0xB1, 0x8B, 0xFD, 0xB3, 0xD5, 0x4F, 0x07, 0x67, 0xF9, 0xF6, 0xE9, 0x62, 0x50, + 0x3B, 0xD6, 0x57, 0x18, 0x88, 0xE4, 0xBA, 0x78, 0x1A, 0x73, 0xBB, 0xB9, 0xB1, 0xB1, 0x96, 0x89, + 0xF2, 0x65, 0x1F, 0x65, 0x46, 0x95, 0x98, 0xFF, 0x92, 0xF5, 0x56, 0x42, 0x44, 0x8A, 0x23, 0xAE, + 0x1E, 0x45, 0x6E, 0xA2, 0xF4, 0x60, 0x16, 0x42, 0xDD, 0x43, 0x84, 0x54, 0x08, 0x1C, 0x72, 0x1E, + 0x42, 0x30, 0xB1, 0xF6, 0xC9, 0x69, 0xCA, 0xDA, 0xD4, 0x99, 0x3B, 0x6B, 0xAB, 0xAB, 0x1F, 0x3D, + 0x12, 0x5F, 0x59, 0x68, 0x13, 0xA8, 0x48, 0x78, 0xEF, 0xB0, 0x08, 0x0A, 0x4E, 0x8D, 0x3D, 0x9A, + 0x8D, 0xCB, 0x4E, 0xC0, 0xCF, 0x9B, 0xE0, 0xE0, 0xBB, 0xDC, 0xB8, 0xFE, 0x0C, 0x31, 0x92, 0x23, + 0x82, 0x59, 0x2C, 0x8E, 0x9A, 0x98, 0x02, 0x12, 0xDF, 0x23, 0xA8, 0xB9, 0x6D, 0xAC, 0x71, 0x2F, + 0x5E, 0xDC, 0xEE, 0x74, 0xE2, 0x95, 0x29, 0x0F, 0xB6, 0x17, 0x99, 0xBF, 0x4E, 0xA6, 0x11, 0xFD, + 0x9E, 0x55, 0x5C, 0xBC, 0x34, 0xA1, 0x75, 0x23, 0x7C, 0x6A, 0xCC, 0x5C, 0x91, 0x28, 0xE2, 0x1B, + 0xEB, 0xEC, 0x16, 0x7B, 0xEE, 0x98, 0x12, 0xC9, 0x3C, 0xEA, 0x75, 0xD7, 0x08, 0x80, 0x92, 0x03, + 0x76, 0xF0, 0xE2, 0xDE, 0x05, 0xF4, 0xAF, 0x53, 0xEA, 0xB1, 0x83, 0xF9, 0x6D, 0x6B, 0x0D, 0x7E, + 0xF4, 0x5E, 0x4A, 0x5D, 0x07, 0x9F, 0xD8, 0x16, 0x51, 0xEF, 0xA5, 0xCC, 0xAF, 0x7C, 0x25, 0xA6, + 0x78, 0x7A, 0x47, 0x72, 0x20, 0x13, 0xD7, 0x1E, 0xB1, 0xF8, 0x1D, 0x9B, 0x79, 0x8E, 0xE2, 0x52, + 0xF1, 0x25, 0x0E, 0xA2, 0x6E, 0x26, 0xC8, 0xA4, 0x6B, 0x0A, 0x2E, 0x31, 0x50, 0x4C, 0x43, 0xBD, + 0xA4, 0xD4, 0xA2, 0xC5, 0x77, 0x36, 0x32, 0xF7, 0xAB, 0xC3, 0x63, 0xF7, 0x9A, 0x8A, 0xDE, 0xC0, + 0x23, 0xCB, 0x9E, 0xCC, 0x08, 0x3F, 0xB1, 0xEC, 0x29, 0x7C, 0xE4, 0x5F, 0xE9, 0x59, 0x92, 0x72, + 0x2E, 0x84, 0xD0, 0x11, 0x2C, 0xE5, 0x52, 0x31, 0x3B, 0x73, 0x3C, 0x38, 0x7D, 0xCA, 0x03, 0x53, + 0xD1, 0x4C, 0x7E, 0x1F, 0xA9, 0x48, 0x3D, 0xE3, 0x34, 0x2A, 0xF5, 0x34, 0x74, 0x6C, 0x7F, 0x07, + 0xD0, 0x0B, 0x37, 0xBE, 0xCE, 0xD3, 0xB5, 0x36, 0xA0, 0x13, 0x32, 0x7E, 0x60, 0xB7, 0x0D, 0xDF, + 0xDB, 0x3D, 0x55, 0x4D, 0x94, 0x3B, 0x52, 0xC5, 0x9A, 0xC9, 0xC9, 0xC8, 0x1E, 0xE7, 0xAC, 0x84, + 0x2A, 0x2C, 0x12, 0x94, 0xB6, 0x6D, 0x5A, 0xE4, 0xCF, 0xA4, 0x97, 0x1F, 0x20, 0xA4, 0x71, 0xA8, + 0x56, 0x37, 0x9E, 0x53, 0x2B, 0xC9, 0x6B, 0xEA, 0x49, 0x3D, 0x05, 0x8D, 0x03, 0xED, 0x12, 0x37, + 0x3C, 0xEB, 0x81, 0xCF, 0x43, 0x14, 0x66, 0x82, 0x56, 0x61, 0xF3, 0x4C, 0x29, 0x75, 0xEA, 0x35, + 0xD3, 0x96, 0x50, 0x30, 0x7B, 0x1F, 0x67, 0x8D, 0x4C, 0x0F, 0xC7, 0xCD, 0xA7, 0xE3, 0xDB, 0x73, + 0x68, 0x72, 0x01, 0xF1, 0xD2, 0x76, 0x67, 0x52, 0x97, 0x56, 0x6A, 0xAE, 0x9A, 0x29, 0x95, 0xBB, + 0x7A, 0xBD, 0xED, 0xF5, 0xED, 0xCD, 0x97, 0x19, 0x7F, 0xAF, 0x0D, 0xDF, 0xDB, 0x50, 0x30, 0x87, + 0x76, 0x66, 0xA8, 0xB2, 0x78, 0x16, 0x7C, 0xDD, 0x4A, 0xEC, 0x1C, 0x9B, 0x9A, 0x5A, 0x3F, 0x2B, + 0x67, 0x25, 0x09, 0x94, 0x24, 0x0B, 0xE5, 0xB7, 0xAF, 0xAD, 0xF5, 0x2D, 0x45, 0xCD, 0xC5, 0x0C, + 0xB4, 0xDE, 0xDE, 0x9A, 0x8B, 0xAA, 0x97, 0xF3, 0x50, 0x24, 0xA0, 0xD5, 0xD5, 0xD8, 0x15, 0x4A, + 0xE4, 0x95, 0x7B, 0x4B, 0xC5, 0x2C, 0x8A, 0xA3, 0xB3, 0x3D, 0x7E, 0x0F, 0x6B, 0x31, 0xB9, 0xF6, + 0x79, 0x74, 0xD6, 0x92, 0xDA, 0xEB, 0x79, 0x5E, 0x47, 0xFA, 0x4F, 0x6E, 0xB2, 0x76, 0x73, 0x34, + 0xC7, 0xA6, 0x1A, 0x28, 0x1C, 0x9C, 0xF6, 0x1F, 0x9B, 0xC4, 0x7B, 0xB0, 0x00, 0x77, 0xF6, 0xFD, + 0x63, 0x93, 0xE9, 0x4F, 0xAF, 0xE0, 0xE7, 0x5C, 0xA9, 0xC4, 0x9B, 0x1F, 0xA2, 0xF2, 0x69, 0x34, + 0x11, 0xDE, 0xB3, 0x95, 0xF5, 0x9E, 0x2D, 0xA3, 0xF7, 0x6C, 0xA6, 0x93, 0xA8, 0xCC, 0xE9, 0xC5, + 0x59, 0x53, 0x6D, 0x49, 0xCE, 0x11, 0x81, 0xA7, 0xD1, 0xE8, 0xF0, 0xFB, 0xC4, 0xF7, 0xB8, 0xFB, + 0x0B, 0xCE, 0x61, 0x57, 0xB6, 0x1B, 0xCC, 0x33, 0xEC, 0x2A, 0x06, 0x23, 0xBD, 0x27, 0x5C, 0x76, + 0x48, 0x39, 0x01, 0x6E, 0x06, 0x2E, 0x4B, 0xB2, 0x0E, 0x2E, 0x09, 0x5E, 0x6D, 0x54, 0x7C, 0x91, + 0x35, 0xD6, 0x6A, 0xAB, 0x21, 0xEE, 0x73, 0x30, 0x3F, 0x07, 0x74, 0xC4, 0x54, 0xC3, 0x6A, 0xB3, + 0x64, 0xB6, 0xDA, 0x56, 0x12, 0x43, 0xDF, 0x35, 0x6E, 0x25, 0xD5, 0xA5, 0xE3, 0x86, 0x13, 0x91, + 0x2D, 0xFE, 0x31, 0x88, 0xA5, 0x27, 0x82, 0x93, 0x73, 0x7A, 0x4D, 0x03, 0x70, 0xD5, 0xE9, 0x91, + 0x23, 0x96, 0xA8, 0x99, 0x15, 0x2A, 0x5B, 0xA0, 0x86, 0x85, 0x04, 0x1E, 0xE2, 0x9C, 0x22, 0x39, + 0x4F, 0x82, 0x6B, 0xD9, 0x45, 0x30, 0xEF, 0x9B, 0xA8, 0x22, 0x25, 0x1C, 0x53, 0xF3, 0x2E, 0x48, + 0xF7, 0x7B, 0xE5, 0xAB, 0xDC, 0xBB, 0xA4, 0xAB, 0x64, 0x49, 0x4A, 0xB2, 0x04, 0x2D, 0xE0, 0x8B, + 0x12, 0x6F, 0x23, 0x6F, 0x01, 0xF8, 0x3C, 0x03, 0x4B, 0x09, 0xA6, 0x93, 0x19, 0x4C, 0x96, 0x37, + 0x45, 0xC2, 0xB0, 0x0C, 0x1F, 0xD8, 0x9F, 0x2D, 0xDD, 0x47, 0x8A, 0xB3, 0xEE, 0x24, 0x98, 0xA4, + 0x0A, 0x8F, 0xFA, 0x84, 0xF9, 0x83, 0x7A, 0x69, 0x44, 0x62, 0xF6, 0xCD, 0xDC, 0x5A, 0x1B, 0x3E, + 0xCB, 0xE6, 0xFE, 0x5D, 0xD9, 0xBB, 0xAE, 0x92, 0x32, 0xF7, 0xD8, 0xB7, 0x31, 0x1F, 0x55, 0x87, + 0x88, 0x56, 0xDF, 0xD9, 0x6E, 0x04, 0x4B, 0xEE, 0x05, 0x39, 0x4C, 0xDF, 0xAC, 0x83, 0xE6, 0x7B, + 0x21, 0xF1, 0xA5, 0x90, 0x6C, 0x43, 0x5F, 0xC5, 0xCC, 0x27, 0x27, 0x09, 0x2F, 0xE4, 0x27, 0x6C, + 0x79, 0xB6, 0x3C, 0xDD, 0xCD, 0x56, 0x3B, 0x47, 0x2A, 0x96, 0x53, 0x45, 0xC9, 0x9D, 0xD7, 0xB7, + 0x6F, 0xB1, 0x39, 0x9D, 0x8E, 0x12, 0x14, 0xAB, 0xE5, 0xD5, 0x6A, 0xA5, 0xB9, 0x81, 0xDB, 0x45, + 0x82, 0x6F, 0x2B, 0x48, 0x86, 0x34, 0x2F, 0x13, 0x49, 0x26, 0x27, 0x8A, 0x9C, 0x9C, 0x65, 0x96, + 0x9C, 0x24, 0xB3, 0xE7, 0x25, 0x99, 0x25, 0x37, 0xC9, 0x7C, 0xF2, 0x93, 0xCC, 0x9E, 0xA3, 0x64, + 0xB6, 0x3C, 0x25, 0xB3, 0xE4, 0x2A, 0x99, 0x35, 0x5F, 0xC9, 0x6C, 0x39, 0x4B, 0xE6, 0x9F, 0xB7, + 0xA4, 0x10, 0x63, 0xBA, 0x25, 0x92, 0x22, 0x72, 0xD3, 0x6F, 0x95, 0xF1, 0x94, 0xE4, 0x40, 0x29, + 0x47, 0x50, 0x31, 0x11, 0x4A, 0xF3, 0x64, 0x28, 0xB3, 0x26, 0x44, 0xC9, 0x66, 0x21, 0x45, 0x0B, + 0xC4, 0xE9, 0xFD, 0x03, 0x4C, 0xC0, 0xCF, 0x6B, 0x7E, 0x34, 0x4E, 0x5F, 0x8F, 0x35, 0x23, 0x08, + 0xFF, 0x3B, 0x93, 0x14, 0x4C, 0xCF, 0xE1, 0x22, 0x65, 0xF4, 0x5B, 0xE4, 0x76, 0x5F, 0x2A, 0xBF, + 0x7C, 0x7F, 0xD6, 0x57, 0xD7, 0xB8, 0x3C, 0x0D, 0x60, 0xC2, 0xCA, 0x9F, 0xFE, 0x44, 0x4C, 0x50, + 0xA0, 0x10, 0x23, 0x96, 0x3B, 0xBC, 0x2E, 0xE4, 0x5B, 0xEA, 0x1E, 0xF8, 0xD3, 0x26, 0x24, 0x8F, + 0x3F, 0x9E, 0xEE, 0xF5, 0x8D, 0xCC, 0x2E, 0x69, 0xCE, 0x82, 0x29, 0xF5, 0x47, 0xD9, 0xB4, 0x26, + 0xE9, 0x07, 0x9B, 0xBA, 0x14, 0x21, 0xCA, 0xAA, 0x72, 0xE3, 0x63, 0xDC, 0x19, 0xAC, 0x51, 0x88, + 0xC2, 0x9F, 0xAC, 0x23, 0xA9, 0x12, 0x2D, 0x2F, 0xEB, 0x8A, 0x61, 0x52, 0x81, 0x56, 0x96, 0xA2, + 0x31, 0xDD, 0xDB, 0x5B, 0x37, 0x3A, 0xB1, 0x27, 0x69, 0x22, 0xB7, 0xB1, 0xEB, 0xC1, 0x0F, 0xFB, + 0x7B, 0x9B, 0x5C, 0xB1, 0x12, 0x29, 0x17, 0x66, 0x3B, 0x31, 0x0A, 0x47, 0x07, 0xD2, 0x28, 0x8E, + 0x23, 0x24, 0x92, 0x27, 0x13, 0x38, 0x7B, 0x63, 0x3B, 0xFC, 0xC6, 0x0B, 0x38, 0x22, 0xA5, 0x90, + 0x29, 0x0E, 0x87, 0x7D, 0x8D, 0x24, 0x79, 0xE6, 0x09, 0xFE, 0xE1, 0x0D, 0x92, 0xE7, 0x1F, 0x16, + 0x19, 0x12, 0xE8, 0xBA, 0x2E, 0x79, 0xFD, 0x9A, 0xD3, 0x82, 0x8E, 0x81, 0x3E, 0xB2, 0xF4, 0xFE, + 0x29, 0x48, 0xA2, 0x69, 0x4E, 0xCC, 0x29, 0xC1, 0x4A, 0xAD, 0x2A, 0xEB, 0xD1, 0xA2, 0x5E, 0xC8, + 0x6C, 0x9D, 0xC4, 0xA9, 0x3E, 0x25, 0x83, 0x18, 0xDF, 0xE9, 0x86, 0xF6, 0x1B, 0xEE, 0x44, 0x85, + 0x04, 0x2C, 0x28, 0x71, 0x60, 0x4D, 0x39, 0x01, 0x7B, 0xCE, 0xF6, 0x50, 0xD4, 0xCE, 0x4A, 0xEE, + 0x56, 0x49, 0x59, 0x93, 0x5D, 0x8C, 0xB5, 0x50, 0x6F, 0x0B, 0xEE, 0xA4, 0x69, 0x92, 0x93, 0xA6, + 0x91, 0x5D, 0xE3, 0x56, 0x78, 0xFA, 0x22, 0x84, 0x43, 0x23, 0x30, 0xF5, 0xD4, 0x79, 0x07, 0xAB, + 0x6C, 0x81, 0x27, 0x7D, 0x02, 0x23, 0xC9, 0xC6, 0x7C, 0x1B, 0x3F, 0xB3, 0x81, 0xD7, 0x1A, 0xF9, + 0x4B, 0x52, 0xAE, 0x03, 0xD3, 0x9F, 0x7B, 0x7D, 0x9F, 0xBC, 0x57, 0xB5, 0x28, 0x2B, 0xC3, 0x52, + 0xD2, 0xDF, 0x2A, 0xD8, 0x2E, 0x51, 0xAF, 0x48, 0x86, 0x9D, 0xFD, 0x8F, 0x1F, 0xCF, 0x0F, 0x8E, + 0x4E, 0xF7, 0x2E, 0x0E, 0x2F, 0x8F, 0x4E, 0xCF, 0x3E, 0x5D, 0x5C, 0x5E, 0x7C, 0x3E, 0xC3, 0x5F, + 0x7F, 0xD9, 0x3B, 0x3E, 0x3A, 0xB8, 0xFC, 0x74, 0xFA, 0x97, 0xD3, 0x8F, 0xBF, 0x9E, 0xEA, 0xF7, + 0xD7, 0x93, 0x1E, 0x45, 0x26, 0x53, 0x8C, 0x78, 0x99, 0x1A, 0x5A, 0x41, 0xA6, 0xDE, 0x37, 0x0F, + 0xF3, 0x3C, 0xEF, 0x3C, 0x37, 0x5D, 0x61, 0x03, 0x60, 0xC0, 0x1F, 0xCA, 0xA1, 0x8E, 0xC4, 0xF8, + 0x6B, 0xB2, 0xD2, 0xDD, 0xB2, 0xB8, 0xFA, 0x9B, 0xCA, 0x61, 0x0A, 0xDA, 0xB2, 0x96, 0x0A, 0xC5, + 0x90, 0x1E, 0xBB, 0x6C, 0xF1, 0x55, 0xFB, 0x96, 0xF5, 0xEC, 0x04, 0x90, 0x3B, 0x54, 0xB8, 0xBA, + 0x48, 0x3A, 0x1D, 0x5F, 0x65, 0xDD, 0x31, 0xE8, 0x1F, 0x7F, 0x76, 0xAC, 0xBE, 0x02, 0x32, 0xB8, + 0x9F, 0x1A, 0xF8, 0x53, 0x03, 0xF3, 0x35, 0x30, 0xA3, 0x2F, 0x7F, 0xD8, 0x55, 0x0C, 0x5F, 0x61, + 0xFB, 0xB9, 0x0D, 0x0D, 0x79, 0x3E, 0x31, 0x96, 0x2A, 0xE8, 0x99, 0x34, 0xBF, 0xD0, 0xD8, 0xCB, + 0x7C, 0x4C, 0x02, 0x58, 0x6C, 0xE2, 0x0C, 0x94, 0x0E, 0xAA, 0x66, 0xA3, 0x25, 0x57, 0xEC, 0x4D, + 0xD9, 0x50, 0x3A, 0x41, 0x3C, 0x3B, 0x80, 0x13, 0x9A, 0x83, 0xE1, 0x11, 0x18, 0x9D, 0x60, 0x78, + 0x41, 0xB1, 0x1D, 0x8F, 0x46, 0x2C, 0x07, 0x6F, 0x68, 0x75, 0x84, 0x3D, 0x1A, 0x61, 0x6F, 0xF1, + 0xBC, 0x43, 0xC9, 0x34, 0xAB, 0x3D, 0xBC, 0x90, 0xA6, 0x9A, 0x55, 0x67, 0xD8, 0x74, 0x66, 0x14, + 0x23, 0x84, 0x91, 0x5A, 0x8C, 0xEB, 0xB4, 0x4D, 0x4C, 0xA4, 0x89, 0x62, 0x35, 0xCB, 0x56, 0x88, + 0x0C, 0x2A, 0x15, 0x61, 0xCB, 0x5E, 0xB9, 0xE6, 0x87, 0x8A, 0xA1, 0xB8, 0x04, 0xC1, 0xE5, 0x2B, + 0x9A, 0x9F, 0xF5, 0xFE, 0x32, 0x79, 0x7C, 0x53, 0xF7, 0xAF, 0xA1, 0xDB, 0x57, 0xD1, 0xB3, 0x13, + 0x08, 0xD0, 0xB3, 0xF8, 0x41, 0xAE, 0x1C, 0x7F, 0xD6, 0x36, 0x75, 0x8F, 0x76, 0x73, 0x93, 0xC0, + 0x18, 0x9E, 0x9E, 0x6B, 0xB2, 0x1A, 0x78, 0xA8, 0x4D, 0x9A, 0x67, 0x52, 0x29, 0x20, 0x2F, 0xC7, + 0x34, 0x54, 0x27, 0xDF, 0xC8, 0x2A, 0xE6, 0xA9, 0x8E, 0xD8, 0xDB, 0x9F, 0xB3, 0xEE, 0xA4, 0x2F, + 0x0B, 0xAB, 0x2A, 0x94, 0xC6, 0x52, 0xA3, 0x26, 0xCD, 0x4B, 0x71, 0x0C, 0xFD, 0x61, 0xBA, 0x78, + 0x3C, 0xA7, 0x8E, 0x50, 0xD2, 0x06, 0xCA, 0x24, 0x66, 0x00, 0xB5, 0x6A, 0xF5, 0x7F, 0x3C, 0xFD, + 0x97, 0xB5, 0xB9, 0xFB, 0xAC, 0xDB, 0xDC, 0x7D, 0x94, 0x36, 0xF7, 0x9E, 0x75, 0x9B, 0x7B, 0x8F, + 0xD2, 0xE6, 0xB5, 0x67, 0xDD, 0xE6, 0xB5, 0xBA, 0x6D, 0xD6, 0x51, 0x3E, 0xB6, 0xC3, 0x98, 0x67, + 0x1A, 0xD3, 0x63, 0x78, 0xE5, 0x49, 0x91, 0x7A, 0x36, 0x11, 0x7D, 0x4B, 0x87, 0xBF, 0xF4, 0xCE, + 0x2D, 0xA0, 0x78, 0xE0, 0xBD, 0x13, 0xB3, 0xCD, 0x8C, 0x26, 0xD6, 0x88, 0x6D, 0x23, 0x38, 0xAA, + 0xEB, 0xE9, 0xC6, 0x17, 0xDB, 0x46, 0xC1, 0xE2, 0x2F, 0xD6, 0x57, 0x31, 0xDF, 0xB2, 0x6F, 0x6E, + 0x78, 0x6A, 0x9F, 0x2E, 0xF2, 0xB3, 0x9C, 0xB8, 0x7C, 0x69, 0x89, 0x97, 0xC5, 0xD5, 0x5F, 0x13, + 0x4B, 0xFD, 0xF0, 0x06, 0x9F, 0x34, 0x58, 0x32, 0x21, 0xEF, 0x96, 0x20, 0xEF, 0xAA, 0xC8, 0xBB, + 0x3A, 0xF2, 0x6E, 0x11, 0xF2, 0x5E, 0x09, 0xF2, 0x9E, 0x8A, 0xBC, 0xA7, 0x23, 0xEF, 0x15, 0x21, + 0x5F, 0x2B, 0x41, 0xBE, 0xA6, 0x22, 0x5F, 0xD3, 0x91, 0xAF, 0x25, 0xC8, 0x9F, 0xDF, 0x96, 0x54, + 0x6D, 0x95, 0x95, 0x32, 0x2A, 0xDF, 0xF8, 0x21, 0xA8, 0xEA, 0x14, 0x7E, 0x85, 0x9F, 0xC1, 0xA8, + 0x44, 0x81, 0x45, 0x04, 0x14, 0x02, 0x09, 0xF5, 0xED, 0x44, 0xFE, 0xB1, 0x7F, 0xC7, 0xAE, 0x8D, + 0xD1, 0xC5, 0xA5, 0xF4, 0x71, 0x5F, 0xC0, 0xB5, 0x94, 0x73, 0xC7, 0x0A, 0x89, 0x19, 0xEF, 0x51, + 0xE9, 0x4E, 0x41, 0xD7, 0xE8, 0x12, 0xAC, 0x6F, 0x9B, 0x9E, 0x09, 0x46, 0xA4, 0x0D, 0x3B, 0xA2, + 0x7E, 0x67, 0xE4, 0xAF, 0xBA, 0x0A, 0x4C, 0x94, 0xD4, 0x39, 0x4C, 0x02, 0x55, 0x7A, 0x51, 0x54, + 0xCC, 0x6E, 0xD4, 0x2B, 0xF1, 0xDF, 0xAE, 0xD3, 0x6D, 0x13, 0xD7, 0xE9, 0x55, 0xE8, 0x3C, 0xBE, + 0x67, 0xD2, 0x35, 0x84, 0xA6, 0x89, 0xAD, 0x73, 0x40, 0x93, 0x2D, 0xCC, 0xA8, 0x7C, 0xB7, 0xB9, + 0xCE, 0x03, 0x81, 0x67, 0xBB, 0x87, 0xDB, 0x35, 0x9F, 0xB3, 0xA0, 0x4C, 0x4C, 0xC7, 0x5D, 0x4A, + 0xE8, 0x1C, 0x1A, 0x7F, 0xBE, 0x13, 0xA5, 0x3E, 0x93, 0x23, 0x3D, 0x2F, 0x1C, 0xEB, 0x7B, 0xD1, + 0x30, 0x65, 0x2F, 0xF6, 0x5E, 0xF8, 0xEF, 0x6C, 0x0C, 0x23, 0xB8, 0x3F, 0x10, 0xD7, 0xDB, 0xD5, + 0x25, 0xAA, 0x5A, 0x96, 0x7D, 0xD7, 0xB7, 0x15, 0x17, 0x91, 0xBD, 0x09, 0x4C, 0x23, 0xD4, 0x51, + 0xE2, 0x1A, 0x58, 0x06, 0x4A, 0x87, 0x5D, 0xFB, 0xE3, 0xD4, 0xF0, 0x50, 0xC8, 0xF8, 0xF0, 0x96, + 0x46, 0x89, 0xC5, 0x75, 0xB6, 0xBB, 0xED, 0x96, 0x99, 0x65, 0xC3, 0x51, 0x6C, 0x86, 0xFD, 0x4A, + 0x0F, 0x98, 0xA5, 0x2F, 0x89, 0xE5, 0xE5, 0xC3, 0x2C, 0x79, 0x7C, 0x6C, 0x97, 0x7C, 0x57, 0x62, + 0xCC, 0x4A, 0x5E, 0x53, 0x94, 0xDB, 0xF2, 0x37, 0x1A, 0xF8, 0x71, 0x56, 0x64, 0xE9, 0x95, 0x16, + 0x29, 0x0F, 0x34, 0xDB, 0xC6, 0x2F, 0x4D, 0xD3, 0x4D, 0x16, 0xF1, 0x4E, 0x85, 0x21, 0x9F, 0xF3, + 0xD2, 0x8B, 0x47, 0xC8, 0xA6, 0xDD, 0x30, 0x2F, 0x74, 0xAD, 0x9C, 0xD0, 0x43, 0x35, 0x9B, 0xAD, + 0xFE, 0x72, 0xFD, 0x83, 0x22, 0x29, 0x3D, 0xF5, 0xF2, 0x8B, 0xB9, 0xE7, 0x70, 0x6E, 0x92, 0x46, + 0xB5, 0x7A, 0x0A, 0xD5, 0xDC, 0xC6, 0x76, 0xAC, 0xB2, 0xF6, 0xA6, 0xC9, 0x97, 0xF9, 0xF9, 0xD8, + 0x5C, 0xD3, 0x38, 0x3F, 0xAF, 0x56, 0xF3, 0x27, 0x8B, 0xB0, 0x19, 0x86, 0x01, 0x21, 0x30, 0xF5, + 0xA3, 0x80, 0xDA, 0xE3, 0x54, 0x09, 0xAA, 0xC9, 0x83, 0x63, 0x95, 0x05, 0x22, 0xE1, 0xF9, 0xD1, + 0xD2, 0x28, 0x50, 0x80, 0x4C, 0xC2, 0xE4, 0x99, 0x94, 0x20, 0x8B, 0xED, 0x07, 0x37, 0xBD, 0xDB, + 0x29, 0x6A, 0xFC, 0x21, 0xBF, 0x3E, 0x36, 0x9F, 0xB6, 0x67, 0x90, 0xFD, 0xA0, 0xA6, 0xA7, 0x17, + 0xDD, 0xC5, 0x9B, 0xF5, 0x59, 0x37, 0x00, 0xE7, 0x0C, 0xB4, 0xED, 0xC5, 0xF3, 0x46, 0x95, 0xF7, + 0x03, 0x4C, 0xB3, 0xC6, 0x3C, 0x67, 0x0C, 0x35, 0x43, 0xFA, 0x73, 0x9E, 0x35, 0x92, 0x7C, 0xEE, + 0xF3, 0x98, 0x39, 0xB2, 0xC9, 0xE1, 0x9F, 0xE5, 0xEC, 0xA1, 0xA7, 0x0D, 0x9F, 0x9B, 0xED, 0x78, + 0x0E, 0x6D, 0xAF, 0x6C, 0x3A, 0x66, 0x6E, 0xB9, 0x8E, 0xEB, 0xF9, 0x1A, 0x0E, 0xE1, 0x38, 0xF3, + 0x54, 0xE8, 0x60, 0x17, 0x32, 0xEE, 0xB2, 0xEA, 0x8D, 0xEE, 0xFC, 0xA8, 0xB7, 0x5F, 0xD8, 0x83, + 0xEC, 0xAA, 0x51, 0x62, 0x13, 0x31, 0x7F, 0xFE, 0x26, 0xE9, 0xE1, 0x9D, 0x72, 0x80, 0x7E, 0x6D, + 0x80, 0x8B, 0xBA, 0x00, 0xBF, 0x48, 0x00, 0xEB, 0xE5, 0x00, 0xE7, 0x27, 0xFB, 0x59, 0x0A, 0x0F, + 0x0D, 0x5E, 0x28, 0x19, 0xB2, 0x87, 0xD6, 0x54, 0x6F, 0xF5, 0xFD, 0x99, 0x2A, 0x22, 0xAB, 0xB3, + 0xB1, 0x53, 0x06, 0xD0, 0xAF, 0x0D, 0x70, 0x51, 0x17, 0x40, 0x16, 0x51, 0x32, 0x2E, 0xF3, 0x01, + 0x54, 0x11, 0x25, 0x14, 0x1E, 0x66, 0x79, 0x94, 0x82, 0x5D, 0xA3, 0x63, 0x2E, 0x5D, 0x6C, 0xA8, + 0x18, 0x31, 0x4B, 0x22, 0xB4, 0x89, 0xC7, 0x9C, 0xE3, 0x90, 0x0E, 0x36, 0x2C, 0xAB, 0x0C, 0xAE, + 0x2B, 0xC1, 0xBD, 0x44, 0x38, 0x00, 0xEB, 0x9A, 0x9A, 0x25, 0xB9, 0x91, 0x85, 0xDA, 0x6B, 0xA8, + 0xDE, 0xAF, 0x59, 0xFD, 0xA2, 0x5E, 0x75, 0xB9, 0x53, 0x7A, 0x65, 0xD5, 0x33, 0x5A, 0x6B, 0xAA, + 0x9F, 0x75, 0x1F, 0xF1, 0xB7, 0xAE, 0xB5, 0xB6, 0x26, 0xC3, 0x5A, 0xA9, 0x61, 0x7E, 0xC8, 0x98, + 0x26, 0x71, 0xED, 0xF5, 0xA7, 0x61, 0x7A, 0x06, 0x86, 0xA9, 0x08, 0xE2, 0xFC, 0xB7, 0x93, 0xCB, + 0xF3, 0xBD, 0x5F, 0x7F, 0xAB, 0xCC, 0x13, 0x02, 0xF4, 0xDF, 0x9D, 0xBF, 0xFD, 0xED, 0xA7, 0xF1, + 0xAB, 0x6D, 0xFC, 0xF2, 0x20, 0xF4, 0x71, 0xD6, 0xDD, 0xAE, 0x48, 0x4A, 0x07, 0xEC, 0x59, 0x0D, + 0x01, 0xD7, 0x7B, 0x4D, 0x01, 0x37, 0x5B, 0xB9, 0xAE, 0xDA, 0x4F, 0x0B, 0xFF, 0xCF, 0x60, 0xE1, + 0x4D, 0xE0, 0x99, 0x35, 0xB6, 0xA4, 0xB7, 0xA6, 0x04, 0x6F, 0xD5, 0xC0, 0x99, 0xF6, 0x36, 0x07, + 0x67, 0x3A, 0x3C, 0x03, 0xF8, 0x66, 0x6E, 0x76, 0x3A, 0x83, 0xEB, 0x8D, 0x30, 0xC6, 0xC9, 0x4D, + 0x5D, 0xCF, 0x3F, 0xA3, 0x09, 0x8E, 0x2D, 0x69, 0x90, 0xED, 0xCB, 0xAE, 0x65, 0x6D, 0x54, 0xB5, + 0xF7, 0x32, 0xD4, 0xCB, 0xF5, 0x46, 0x50, 0x2F, 0x5B, 0x99, 0xD5, 0x7B, 0x39, 0xD4, 0x56, 0x23, + 0x5A, 0x5B, 0x1A, 0xAD, 0x8A, 0x60, 0xDB, 0x8D, 0x88, 0x6D, 0x37, 0x69, 0x58, 0xB7, 0xD7, 0x84, + 0x56, 0xB7, 0xD7, 0x84, 0x56, 0x6F, 0x4D, 0x99, 0x0E, 0xAA, 0xCA, 0x63, 0xDD, 0x7A, 0xD9, 0x53, + 0xAC, 0x6D, 0x45, 0x7A, 0x0C, 0xAE, 0xDB, 0xCA, 0xD9, 0xAA, 0x99, 0xC9, 0x2D, 0x48, 0x96, 0xE4, + 0x7C, 0xA8, 0xAA, 0xEA, 0x5B, 0x32, 0x5B, 0x49, 0x70, 0x39, 0xFE, 0x6B, 0x39, 0xA0, 0xAA, 0xF9, + 0xD5, 0xE1, 0xB6, 0x1A, 0xC2, 0x6D, 0x37, 0x83, 0xD3, 0x94, 0x6B, 0x6E, 0xB3, 0x71, 0xDE, 0xCE, + 0x50, 0xD3, 0x39, 0x43, 0xEF, 0xCA, 0x7A, 0x46, 0x5B, 0x27, 0xDD, 0x00, 0xFA, 0xA4, 0x7F, 0xB2, + 0x5E, 0xD7, 0xDA, 0x1F, 0xFB, 0x77, 0x68, 0x66, 0xEF, 0x5C, 0x27, 0xBA, 0xF9, 0x47, 0xB7, 0xF8, + 0x56, 0x23, 0x93, 0xDF, 0x7B, 0x42, 0x93, 0xDF, 0x7B, 0x4A, 0x93, 0xDF, 0x7B, 0x42, 0x93, 0xDF, + 0xFB, 0x69, 0xF2, 0x67, 0x36, 0xF9, 0xBD, 0xA7, 0x36, 0xF9, 0xBD, 0x86, 0x26, 0xBF, 0xD7, 0xD0, + 0xE4, 0xF7, 0x1A, 0x9A, 0xFC, 0xDE, 0x13, 0x99, 0x7C, 0x6B, 0xE3, 0x3F, 0x37, 0xAB, 0xC8, 0xB5, + 0xFA, 0x8C, 0x51, 0x05, 0x58, 0x58, 0x6D, 0xA5, 0xB1, 0xFF, 0x34, 0x33, 0xCD, 0x34, 0x64, 0xAF, + 0x40, 0xA6, 0x97, 0x68, 0xC2, 0xEC, 0xED, 0x12, 0xFE, 0x9C, 0x5F, 0x2A, 0x01, 0x8A, 0x7F, 0xEF, + 0x64, 0xEB, 0x7C, 0xD6, 0xEA, 0x7C, 0x36, 0xD4, 0xF9, 0x5D, 0xAB, 0xF3, 0xFB, 0x8E, 0xBC, 0x8B, + 0x07, 0xDC, 0xBC, 0xA7, 0xBE, 0x43, 0x23, 0x77, 0x50, 0xC8, 0x51, 0xF6, 0xBE, 0xCB, 0x50, 0x80, + 0x41, 0x49, 0xF9, 0x7D, 0x96, 0xA4, 0xB2, 0xEF, 0x69, 0x95, 0x3F, 0xEC, 0x1D, 0xEE, 0x9D, 0xED, + 0x1B, 0xAA, 0xEE, 0x8D, 0x22, 0xD1, 0xEB, 0x6A, 0x52, 0x98, 0xB1, 0x1D, 0x7C, 0x53, 0x32, 0xC2, + 0x74, 0x4B, 0xD2, 0xBE, 0x94, 0x80, 0xF7, 0x4C, 0xE0, 0xFC, 0xEA, 0xB7, 0xA0, 0x6F, 0x3B, 0xFF, + 0x31, 0x0D, 0x23, 0x60, 0x54, 0x0F, 0x05, 0x62, 0xA7, 0xB5, 0xA7, 0xF4, 0xEE, 0xD8, 0x4F, 0x6F, + 0x08, 0x65, 0x32, 0xA8, 0xA4, 0x75, 0x0C, 0x81, 0x51, 0xF4, 0xBB, 0x1B, 0x69, 0xE9, 0x10, 0x6E, + 0x5C, 0x87, 0x25, 0x74, 0x74, 0xBD, 0x33, 0x96, 0x86, 0x86, 0x37, 0x02, 0x63, 0xD7, 0x30, 0x17, + 0x1D, 0x78, 0x29, 0x47, 0xDE, 0x59, 0xE0, 0xE3, 0xED, 0xFD, 0x24, 0x49, 0x84, 0x4E, 0x13, 0xB1, + 0xEE, 0x79, 0x8E, 0x1C, 0x8D, 0x85, 0xD5, 0xB8, 0x8F, 0xC3, 0xD3, 0xB5, 0x81, 0xA0, 0xD3, 0x3F, + 0x16, 0x53, 0x36, 0x58, 0x26, 0x4D, 0xCB, 0x14, 0xBE, 0xB5, 0xEF, 0x8F, 0x27, 0x23, 0x1A, 0xA5, + 0x89, 0x93, 0x58, 0x8C, 0x5A, 0x8C, 0x41, 0xC6, 0x2D, 0xC8, 0x99, 0x1B, 0xC2, 0xBF, 0x66, 0x1B, + 0xA2, 0x35, 0x32, 0xA6, 0xD6, 0x8A, 0x6F, 0x76, 0xED, 0xDB, 0x23, 0x4C, 0x3C, 0x7B, 0x77, 0x43, + 0xF9, 0xE5, 0xAE, 0xC3, 0xFE, 0xD9, 0x5A, 0x8F, 0xDC, 0xD8, 0x21, 0xDE, 0x95, 0xBA, 0x76, 0x83, + 0x31, 0x14, 0x06, 0xA0, 0xDF, 0xEE, 0x24, 0x22, 0xFE, 0x35, 0x8F, 0x94, 0xC6, 0x45, 0x7F, 0x2A, + 0x1C, 0x72, 0x1D, 0xF8, 0x63, 0xB2, 0x77, 0xC6, 0x01, 0x86, 0x64, 0x02, 0x5C, 0x49, 0x91, 0x7A, + 0x1C, 0x0B, 0xE6, 0x52, 0x38, 0xE7, 0x68, 0xA4, 0x43, 0xFA, 0x03, 0xE0, 0x24, 0x18, 0xBB, 0x1E, + 0x05, 0xFA, 0xEE, 0xE0, 0x86, 0xA4, 0x1A, 0x80, 0x79, 0x51, 0x91, 0x1D, 0x3F, 0x70, 0x87, 0x30, + 0x76, 0x46, 0x8C, 0x70, 0xE2, 0x19, 0xC6, 0xD9, 0x19, 0x62, 0xA1, 0xFF, 0x61, 0x97, 0x78, 0xD3, + 0xD1, 0x68, 0x49, 0x8F, 0x2E, 0x8C, 0xA5, 0xA8, 0xD5, 0xAF, 0x92, 0xB4, 0x81, 0x7F, 0x7B, 0x45, + 0xF6, 0x46, 0x23, 0xD2, 0x87, 0xEF, 0x4E, 0x71, 0x2A, 0x3F, 0xDF, 0x0B, 0xFD, 0x11, 0xED, 0x8C, + 0x40, 0x63, 0x5B, 0x9F, 0xF8, 0x8D, 0x54, 0x02, 0xFF, 0x81, 0x9C, 0x40, 0x68, 0x59, 0x19, 0x98, + 0x93, 0x78, 0x5C, 0x27, 0x69, 0x2B, 0x47, 0xBE, 0xED, 0xFC, 0x6A, 0xBB, 0x91, 0x72, 0x11, 0x1D, + 0x33, 0x81, 0x88, 0xB7, 0x12, 0xC2, 0xE9, 0xD5, 0xD8, 0x8D, 0xE2, 0x34, 0x91, 0x98, 0x31, 0x04, + 0x06, 0x1B, 0x96, 0x87, 0x5F, 0xAC, 0xAF, 0x69, 0x5C, 0x3B, 0x4E, 0x59, 0x22, 0xB6, 0xDD, 0xA3, + 0x77, 0x04, 0x6F, 0x01, 0xCA, 0x49, 0x2D, 0xE2, 0xE2, 0x8E, 0x3D, 0x99, 0xF0, 0x41, 0x95, 0x45, + 0xDB, 0x66, 0x64, 0x97, 0x52, 0x9C, 0xF6, 0x7F, 0xD8, 0xDF, 0x05, 0xBE, 0xDF, 0x4E, 0x8E, 0x3F, + 0x44, 0x98, 0x72, 0xEC, 0xAF, 0x53, 0x1A, 0x46, 0x31, 0x56, 0xAC, 0xD0, 0xF1, 0x01, 0xE1, 0x62, + 0xEB, 0xEC, 0x63, 0xFF, 0x02, 0x73, 0x98, 0xAC, 0x4E, 0x59, 0x8B, 0x62, 0xC4, 0x2D, 0xB9, 0x26, + 0x1B, 0x5A, 0x31, 0x27, 0xF1, 0xC8, 0xE3, 0x66, 0x4C, 0x96, 0x86, 0x21, 0xD6, 0xF2, 0xF5, 0x55, + 0xF0, 0x86, 0x17, 0xBA, 0x78, 0x87, 0x6F, 0xA2, 0x24, 0x90, 0x6A, 0xED, 0x14, 0x88, 0xB6, 0x1F, + 0xD9, 0xD1, 0x34, 0xC4, 0xF0, 0x66, 0xD5, 0x20, 0x17, 0x13, 0x84, 0xEA, 0x45, 0x48, 0x33, 0x03, + 0x99, 0x0F, 0x3B, 0x73, 0xA5, 0x56, 0xEE, 0x60, 0x56, 0x08, 0xF8, 0xC1, 0x90, 0x46, 0x67, 0xB6, + 0x1B, 0xC0, 0x8C, 0x88, 0xC6, 0x55, 0x9D, 0x41, 0xAE, 0x22, 0xEF, 0x1D, 0xAB, 0xC1, 0xCB, 0x0C, + 0x22, 0x42, 0xED, 0x65, 0x79, 0x7D, 0x42, 0x86, 0xCB, 0x8F, 0x22, 0xEA, 0x75, 0xA4, 0x9C, 0x04, + 0x13, 0x4A, 0x83, 0x93, 0xBD, 0xFD, 0x50, 0x87, 0x3B, 0xF5, 0x3D, 0x9A, 0x17, 0x99, 0xCA, 0x28, + 0x1E, 0x86, 0x93, 0x53, 0xFF, 0xEE, 0x0C, 0xC0, 0x43, 0x83, 0xF9, 0x05, 0xC6, 0x98, 0x91, 0xD4, + 0x73, 0xC2, 0x0D, 0x63, 0x13, 0x25, 0x0A, 0x0C, 0x1C, 0x33, 0x38, 0x16, 0xAA, 0x23, 0x92, 0x77, + 0xE5, 0x45, 0xC8, 0xCA, 0x78, 0x52, 0x16, 0x92, 0x70, 0x05, 0xDB, 0x71, 0x0E, 0x6F, 0xE1, 0x17, + 0x0C, 0x53, 0xA6, 0x80, 0x7F, 0xB1, 0x75, 0xF0, 0xF1, 0x04, 0x6C, 0x71, 0x84, 0xDF, 0xA0, 0x23, + 0x28, 0xA6, 0xC7, 0x5B, 0xA4, 0x58, 0x65, 0x89, 0xEC, 0xBE, 0x01, 0x06, 0x13, 0x0D, 0x17, 0x02, + 0xAB, 0x10, 0xFB, 0x90, 0x4D, 0x32, 0xA6, 0x44, 0x3C, 0xB0, 0xBB, 0xF4, 0xE8, 0xD4, 0xB3, 0x5B, + 0x7B, 0xF0, 0x0B, 0xC7, 0x9C, 0x44, 0x3C, 0xB8, 0x18, 0x06, 0x61, 0x7F, 0x87, 0x5F, 0xD4, 0xD0, + 0x07, 0x5E, 0xED, 0x8B, 0xFB, 0xB5, 0xE3, 0x7B, 0x83, 0x91, 0x3B, 0xC0, 0x34, 0x1F, 0x89, 0x6C, + 0x17, 0x33, 0xAF, 0x5A, 0xAB, 0xE9, 0xF7, 0x72, 0xDE, 0xAA, 0xE5, 0x2E, 0x96, 0x1D, 0x4E, 0x03, + 0x16, 0x34, 0x8D, 0x8E, 0xF0, 0x87, 0xBF, 0x81, 0xF0, 0xB3, 0x72, 0xE2, 0xF8, 0x70, 0xE0, 0x1B, + 0x49, 0x1A, 0x10, 0xF5, 0xE9, 0x40, 0xDD, 0x36, 0x21, 0xAB, 0xB9, 0xF4, 0xA4, 0x60, 0xEC, 0x07, + 0x79, 0xB4, 0x1B, 0x51, 0xCE, 0x83, 0xB9, 0x94, 0x6A, 0x11, 0x6F, 0x52, 0x13, 0x0C, 0xCC, 0x61, + 0x5A, 0x31, 0xBC, 0x13, 0xCC, 0xE3, 0x45, 0x8E, 0xBC, 0x26, 0xAC, 0xC5, 0x29, 0x6C, 0x0D, 0xB8, + 0x4A, 0x9E, 0x2D, 0xE7, 0x76, 0x24, 0x14, 0xF5, 0xCD, 0x6F, 0x21, 0x73, 0x3B, 0xC2, 0x3C, 0xBF, + 0xDC, 0x47, 0x8B, 0x4D, 0x2D, 0x7A, 0x87, 0x10, 0xF3, 0x68, 0x4E, 0x8C, 0xA8, 0x52, 0x5B, 0x72, + 0xF8, 0x4C, 0x1B, 0x92, 0xDF, 0x58, 0x43, 0x5B, 0x8A, 0xDF, 0x84, 0x6F, 0xDC, 0xB2, 0x99, 0x9E, + 0x9A, 0xE7, 0xED, 0xC4, 0x75, 0x41, 0x51, 0x33, 0x87, 0xC9, 0x12, 0xA1, 0x79, 0x33, 0xDF, 0xB3, + 0x8C, 0x4E, 0xF3, 0x6E, 0xE5, 0x7B, 0x3D, 0x4F, 0x94, 0xA9, 0x91, 0xBC, 0x11, 0xF9, 0x8D, 0xE4, + 0x42, 0xC8, 0x36, 0x52, 0xA9, 0x94, 0xB7, 0xD3, 0x97, 0x59, 0x5F, 0x37, 0xD8, 0xD2, 0x53, 0x1E, + 0x52, 0x32, 0x66, 0xBA, 0x97, 0x8E, 0x44, 0xB7, 0x3A, 0x1B, 0x78, 0xBA, 0xB9, 0x07, 0xCE, 0x2D, + 0x4C, 0xC5, 0xE8, 0xB7, 0x1D, 0x77, 0x57, 0x8F, 0x7B, 0x0A, 0xB2, 0x07, 0xF5, 0x22, 0x52, 0xE9, + 0xF6, 0xC0, 0x6D, 0xF6, 0xB6, 0x6B, 0x65, 0x7E, 0xB6, 0x3B, 0x9B, 0x73, 0xE4, 0x27, 0xBB, 0x5F, + 0x54, 0x87, 0x99, 0x6E, 0x77, 0x73, 0xAE, 0xD2, 0xD1, 0x53, 0x0B, 0xD7, 0xE1, 0x65, 0x1D, 0xF7, + 0x2D, 0x1A, 0xB0, 0xD2, 0x8C, 0x9A, 0xB2, 0xB9, 0xA2, 0x62, 0x36, 0x8C, 0x51, 0xD3, 0x4B, 0xF0, + 0x8D, 0x87, 0x66, 0xCD, 0x67, 0xE5, 0xD3, 0x11, 0x67, 0xB8, 0x1C, 0x9C, 0x7B, 0x7D, 0xD9, 0x34, + 0xA2, 0x2B, 0xC0, 0xE7, 0xB5, 0x3A, 0x79, 0xBF, 0x67, 0x1E, 0xAD, 0xAE, 0xF2, 0x18, 0x90, 0xD6, + 0x6A, 0x0E, 0xD2, 0xBC, 0xD5, 0xC5, 0xF0, 0x86, 0x56, 0x6B, 0xD7, 0xC5, 0x66, 0x6F, 0x79, 0x16, + 0x61, 0x49, 0xEB, 0x0D, 0x17, 0xD3, 0x00, 0x48, 0xE4, 0x88, 0xD7, 0xA2, 0x60, 0x4B, 0x45, 0x51, + 0x8A, 0x4C, 0xDD, 0xB8, 0x33, 0x88, 0x05, 0x97, 0x71, 0x98, 0x13, 0x15, 0xB3, 0xFE, 0x7A, 0x74, + 0x34, 0x8B, 0x40, 0xB2, 0xA8, 0x92, 0xAC, 0x26, 0x96, 0x59, 0x07, 0x62, 0x88, 0xB7, 0xF6, 0xD4, + 0x39, 0x08, 0xFC, 0x09, 0x26, 0xD4, 0x37, 0x4F, 0xBD, 0xCA, 0x93, 0x72, 0xA5, 0xFA, 0x52, 0x81, + 0xA3, 0xAE, 0x79, 0x76, 0xAC, 0xC2, 0x91, 0x70, 0x16, 0x6A, 0x71, 0xD4, 0x94, 0x56, 0x8D, 0xD6, + 0x1B, 0xB4, 0x3D, 0xFF, 0x91, 0xC7, 0x19, 0xB5, 0xBE, 0xF4, 0xF5, 0x48, 0x73, 0x7F, 0x4F, 0xC0, + 0x4C, 0xF1, 0xE7, 0x83, 0x1B, 0x09, 0xAC, 0x1C, 0x5C, 0x91, 0xC1, 0x34, 0xA4, 0xC7, 0xFE, 0xC0, + 0x1E, 0xB9, 0x7F, 0xA3, 0xCE, 0x01, 0xB4, 0x33, 0x70, 0xAF, 0xA6, 0x3C, 0x8D, 0x64, 0xF3, 0xE6, + 0x17, 0xE0, 0x2C, 0x6C, 0xF9, 0xC8, 0x04, 0x74, 0x01, 0x2B, 0xC0, 0x63, 0x58, 0xD3, 0x8E, 0x4C, + 0xBD, 0x5F, 0x41, 0x1E, 0x4D, 0x91, 0x1A, 0x34, 0x45, 0x7F, 0x6B, 0x72, 0x46, 0xFD, 0xA8, 0xF1, + 0x74, 0x65, 0xE1, 0x88, 0x3A, 0xA0, 0x91, 0xED, 0x8E, 0xC2, 0x9A, 0x72, 0xA9, 0x83, 0xA8, 0x40, + 0x16, 0x1F, 0xEC, 0xC0, 0xC1, 0xAD, 0x20, 0x2E, 0x88, 0xE4, 0x0D, 0x9E, 0x39, 0x89, 0x26, 0x07, + 0x7B, 0x2D, 0x49, 0xC9, 0xB0, 0xB3, 0x89, 0xAA, 0x12, 0x26, 0x93, 0xAC, 0xD8, 0xC6, 0xD2, 0x1C, + 0xA4, 0x12, 0xE3, 0xA9, 0xD4, 0x7E, 0xB6, 0xE3, 0xD2, 0xAC, 0xC1, 0xC5, 0xA0, 0x26, 0x8F, 0x41, + 0xDA, 0xB2, 0x9B, 0x83, 0xBB, 0xA0, 0x62, 0xCB, 0xB1, 0x19, 0x86, 0xDD, 0xC2, 0xD9, 0x7C, 0x84, + 0x42, 0x64, 0xA5, 0x3E, 0x82, 0xF2, 0x34, 0xDD, 0x8C, 0x02, 0x28, 0x7F, 0xE0, 0x4E, 0x36, 0x9A, + 0x81, 0xDD, 0xAC, 0x97, 0x0B, 0x21, 0x8D, 0x6D, 0x6B, 0x3C, 0xBA, 0x91, 0xD5, 0x0F, 0x40, 0x54, + 0x1D, 0x3C, 0xF1, 0xC6, 0x5E, 0x3E, 0xAD, 0xF3, 0xA3, 0xD3, 0xC3, 0xDF, 0xE6, 0x21, 0x4F, 0x05, + 0x5B, 0x35, 0xF3, 0x91, 0x81, 0x6C, 0x68, 0x3C, 0xAA, 0xE3, 0x31, 0xC8, 0x41, 0x79, 0xD8, 0x6C, + 0x46, 0x29, 0x54, 0x7D, 0x24, 0x4D, 0x97, 0x41, 0x0A, 0x37, 0x8B, 0x04, 0x2A, 0x60, 0x31, 0xB5, + 0xDF, 0xF4, 0xF2, 0xD9, 0xAC, 0x72, 0xA8, 0xF9, 0x9A, 0x5A, 0x46, 0x1E, 0x00, 0x3F, 0xB6, 0x23, + 0x77, 0xA0, 0x22, 0x99, 0x49, 0x38, 0x35, 0x51, 0xE6, 0x48, 0x2A, 0x7E, 0x2B, 0x6D, 0x0E, 0x02, + 0xAA, 0xF0, 0xEC, 0x5A, 0xAE, 0x5C, 0x18, 0xEC, 0x5C, 0xC4, 0x51, 0x8C, 0x49, 0x91, 0x42, 0xE5, + 0x67, 0xD4, 0x1A, 0x0B, 0x67, 0x1E, 0x0F, 0xB5, 0x49, 0x7B, 0xDF, 0x85, 0xD8, 0x9A, 0x49, 0xAF, + 0x21, 0xCE, 0xEC, 0x96, 0xEC, 0xDE, 0x28, 0x72, 0xA3, 0xA9, 0xD3, 0x68, 0xB0, 0x29, 0x21, 0x10, + 0x19, 0xF4, 0xFA, 0xD3, 0x9F, 0x8F, 0x45, 0x20, 0xB3, 0xF9, 0x35, 0x67, 0x32, 0x6A, 0x40, 0xCA, + 0x1C, 0x91, 0x27, 0xF7, 0x8A, 0xEF, 0xF9, 0xBD, 0xE2, 0x7B, 0xF2, 0x9A, 0x78, 0xD3, 0xF1, 0x7E, + 0xE6, 0x05, 0x57, 0x28, 0xCA, 0xDE, 0x2E, 0x86, 0xE5, 0x4D, 0xE4, 0x7B, 0xE2, 0x72, 0x31, 0x7B, + 0xB2, 0x04, 0xB3, 0xD3, 0xBC, 0x65, 0x5F, 0xF1, 0x59, 0x89, 0x7B, 0x25, 0x40, 0x29, 0xAD, 0x6D, + 0x6C, 0x02, 0x1E, 0xD3, 0xE5, 0xB6, 0x20, 0xCE, 0x9C, 0x93, 0xE2, 0xDF, 0xC7, 0xFA, 0x8B, 0xF7, + 0xB2, 0x7A, 0x49, 0xAF, 0x43, 0x2C, 0xC9, 0xC1, 0x30, 0x39, 0xEE, 0x80, 0x94, 0x5A, 0xAA, 0x5E, + 0x74, 0x5A, 0xC9, 0x03, 0xAC, 0x15, 0x66, 0x78, 0xC1, 0xC3, 0x09, 0xC3, 0x3E, 0xCB, 0x14, 0x5F, + 0x82, 0xC8, 0x18, 0x6A, 0x31, 0x37, 0xF6, 0xE7, 0xC0, 0xB8, 0x89, 0x65, 0x25, 0x26, 0xAD, 0x28, + 0xFD, 0x51, 0x41, 0x62, 0xA6, 0x1C, 0x1D, 0xD6, 0x6E, 0xC8, 0x37, 0xD7, 0xFE, 0x34, 0x27, 0x6B, + 0x7E, 0x36, 0xA7, 0x7B, 0x96, 0xB6, 0xEF, 0xBB, 0x69, 0x97, 0xBE, 0xCA, 0xE0, 0xF9, 0xBE, 0x63, + 0xDA, 0x6E, 0x97, 0x07, 0x11, 0x66, 0x87, 0x16, 0x07, 0xF2, 0x2C, 0x33, 0xB3, 0xC6, 0x0A, 0x56, + 0x42, 0x2E, 0x4A, 0x76, 0xDF, 0xD5, 0xB4, 0x60, 0x86, 0x31, 0x86, 0xDF, 0xF8, 0xDF, 0x69, 0xDC, + 0x11, 0x39, 0xF2, 0x06, 0x01, 0x8B, 0x16, 0x99, 0x88, 0x7C, 0x59, 0x64, 0x85, 0x84, 0x77, 0xF6, + 0x84, 0xBF, 0xBE, 0xCD, 0x0E, 0xFA, 0x61, 0x4E, 0x4A, 0x79, 0x22, 0x21, 0xE3, 0x89, 0xA7, 0x0F, + 0xC1, 0x3A, 0x1E, 0xA6, 0xB6, 0xBE, 0x01, 0xBB, 0x4C, 0xC3, 0xF4, 0xC5, 0xCD, 0x94, 0x14, 0x79, + 0xA3, 0xEE, 0x50, 0xB2, 0x64, 0x06, 0xF6, 0x37, 0x7A, 0x42, 0x3F, 0x20, 0x4C, 0xB0, 0x63, 0x28, + 0x61, 0xC9, 0xFC, 0x76, 0xB2, 0xBD, 0x5B, 0x5B, 0x2F, 0xAA, 0xF4, 0xEE, 0x77, 0xD6, 0xBB, 0x59, + 0xD9, 0xC8, 0xFF, 0x64, 0x86, 0xD3, 0xCC, 0x5E, 0xE6, 0x13, 0x96, 0x6A, 0xF4, 0x64, 0x09, 0xAD, + 0x90, 0xEE, 0x52, 0x3E, 0x55, 0x26, 0x8C, 0x22, 0xA2, 0x0F, 0x73, 0x14, 0x14, 0x33, 0x99, 0x72, + 0x6B, 0x4D, 0x7C, 0x95, 0xB4, 0x6E, 0x05, 0x2F, 0x23, 0xC9, 0xAA, 0x55, 0x22, 0x2C, 0x89, 0x26, + 0x6B, 0xAB, 0x89, 0x24, 0x60, 0x3B, 0xF5, 0x23, 0xFA, 0x8A, 0xB4, 0x4A, 0x88, 0x2F, 0x03, 0xF1, + 0x16, 0xF1, 0xBD, 0xD1, 0x3D, 0xB9, 0xF3, 0x83, 0x6F, 0x61, 0xCC, 0x2C, 0xFC, 0xED, 0x5E, 0x9B, + 0xD0, 0x16, 0xE0, 0x63, 0x4F, 0xA4, 0xB8, 0x5E, 0xD4, 0x66, 0x6F, 0xD3, 0x87, 0x2C, 0xFB, 0x74, + 0x87, 0x7C, 0xC2, 0xB1, 0x62, 0x07, 0xF8, 0x48, 0x57, 0x84, 0x63, 0x85, 0x52, 0x62, 0x5F, 0xF9, + 0xB7, 0xB4, 0xAE, 0x98, 0x96, 0x63, 0x31, 0x1D, 0xD0, 0x42, 0x31, 0x3D, 0x28, 0x19, 0x4B, 0xC8, + 0xD1, 0x35, 0xB1, 0x47, 0x50, 0xDF, 0xB9, 0x67, 0xA3, 0x4F, 0x0C, 0xBC, 0x64, 0xF4, 0xB6, 0x99, + 0x24, 0x89, 0x1B, 0x91, 0x11, 0xC8, 0x52, 0x8C, 0x48, 0xCD, 0x92, 0xB3, 0x60, 0x9C, 0xBD, 0xF1, + 0x07, 0x0E, 0xFA, 0xB4, 0xE3, 0x0C, 0x6D, 0x01, 0x36, 0xC3, 0x22, 0x6E, 0x48, 0x3E, 0x48, 0x56, + 0x43, 0x41, 0x95, 0x30, 0x97, 0xD5, 0x7B, 0xFC, 0x77, 0x05, 0xED, 0xFF, 0xF6, 0x34, 0xC3, 0x01, + 0x9F, 0x62, 0x48, 0xB8, 0x79, 0xE4, 0xC1, 0x90, 0x73, 0x36, 0x5B, 0x9A, 0x68, 0xD0, 0xD8, 0x26, + 0xB4, 0x2A, 0x8C, 0xF0, 0x71, 0xAA, 0x07, 0xF9, 0xCA, 0x55, 0x37, 0x45, 0x21, 0xBA, 0x7D, 0xFB, + 0x87, 0xEF, 0x92, 0xA9, 0x5B, 0x7E, 0x46, 0x2D, 0xB9, 0x3A, 0xE3, 0xC1, 0xA4, 0x83, 0x71, 0x61, + 0x58, 0x33, 0x39, 0x45, 0x0E, 0xE8, 0x18, 0x06, 0xCB, 0x5B, 0xDB, 0xC1, 0xB5, 0x46, 0xB8, 0x98, + 0xAD, 0x13, 0x7B, 0xB4, 0xC6, 0x57, 0x5D, 0xA5, 0xDA, 0xE2, 0x4D, 0xD7, 0xCC, 0xF3, 0xE3, 0xEB, + 0xDB, 0xEC, 0x55, 0x57, 0x1B, 0xE6, 0x2D, 0xF5, 0x69, 0x57, 0x39, 0xB9, 0xF4, 0xCE, 0x8B, 0x9C, + 0x57, 0x6A, 0xA5, 0xE0, 0xF8, 0x36, 0x59, 0x79, 0x19, 0xBF, 0x4D, 0xFB, 0x32, 0xF3, 0x48, 0x6D, + 0x5C, 0x86, 0x24, 0xC5, 0xAF, 0xCD, 0x89, 0x7D, 0x7E, 0x4A, 0x62, 0xBF, 0xCF, 0x93, 0x58, 0xCE, + 0x43, 0x7A, 0xCA, 0x94, 0x1F, 0xBF, 0x44, 0x85, 0x3D, 0x47, 0xEC, 0xA1, 0x8D, 0x2F, 0x19, 0xF2, + 0xE7, 0x31, 0x5C, 0x49, 0x31, 0xF9, 0x73, 0x3B, 0x0E, 0xFD, 0xAE, 0xDE, 0x19, 0x62, 0x83, 0x78, + 0x47, 0x94, 0xBC, 0xC6, 0x00, 0x6C, 0x3F, 0x70, 0x42, 0xA6, 0x2E, 0x71, 0xF0, 0xE0, 0xF2, 0x32, + 0x2B, 0xD5, 0x47, 0x27, 0x22, 0x04, 0x43, 0x1D, 0x85, 0x4C, 0xEB, 0x12, 0xB0, 0x2F, 0xAC, 0xF2, + 0xD7, 0x38, 0xB9, 0x35, 0xD1, 0x73, 0xEA, 0xC6, 0x5E, 0xB5, 0xA2, 0x6B, 0xD2, 0xE9, 0x26, 0x43, + 0x89, 0x19, 0xAC, 0x0D, 0x43, 0x35, 0x4B, 0x06, 0x87, 0x67, 0x76, 0x18, 0x2C, 0x93, 0x05, 0x96, + 0x60, 0x36, 0xD5, 0xB6, 0xBC, 0x82, 0xCF, 0x79, 0x05, 0xBF, 0xEB, 0xC9, 0x8B, 0x6B, 0x1A, 0x47, + 0xFE, 0xEA, 0x0C, 0x13, 0xF7, 0xAE, 0x41, 0xAA, 0x4B, 0x2F, 0x72, 0xDA, 0xD5, 0x99, 0x4C, 0xC3, + 0x9B, 0xC5, 0x47, 0x6D, 0xD3, 0x92, 0x21, 0x71, 0x2A, 0x12, 0xC2, 0x35, 0xA6, 0x6E, 0x88, 0x1C, + 0x8A, 0x31, 0xC4, 0x89, 0x2D, 0x4A, 0x22, 0x57, 0x01, 0x91, 0x08, 0x10, 0xC7, 0x08, 0x67, 0xA8, + 0x29, 0xDD, 0x3D, 0x91, 0x7B, 0x34, 0xBD, 0x63, 0x89, 0x10, 0x6F, 0x58, 0x72, 0x6E, 0x75, 0xA6, + 0x34, 0xA9, 0x10, 0xD4, 0x35, 0x2B, 0x10, 0x02, 0xA0, 0x6C, 0xC4, 0x6A, 0x20, 0xD6, 0x15, 0x29, + 0xD0, 0x8B, 0x4F, 0x93, 0x2C, 0x0A, 0x7E, 0xB1, 0x75, 0xC0, 0xB8, 0x27, 0x78, 0xD8, 0xC8, 0x5A, + 0x83, 0xCB, 0x85, 0x04, 0x7C, 0x99, 0xB4, 0xFE, 0xA5, 0xB5, 0x94, 0xB7, 0x1D, 0x24, 0x77, 0x09, + 0x72, 0x32, 0xA0, 0xD8, 0x02, 0xB0, 0x89, 0x39, 0x6B, 0xC7, 0x62, 0x31, 0x62, 0x40, 0xB6, 0x62, + 0xD0, 0x7F, 0xA0, 0x0C, 0x73, 0xEF, 0x28, 0x71, 0x69, 0x76, 0xBF, 0xE6, 0x54, 0xFD, 0x9C, 0xA9, + 0xDA, 0xCB, 0xAB, 0xFA, 0x7B, 0xA6, 0xEA, 0x9A, 0x56, 0xD5, 0x3C, 0xFA, 0xA5, 0x0E, 0x35, 0xBD, + 0x83, 0x28, 0x73, 0x5D, 0x52, 0xE3, 0x73, 0x69, 0x8D, 0xDF, 0xF3, 0x6A, 0xA8, 0xAC, 0x29, 0x2F, + 0xFE, 0x25, 0x8F, 0x13, 0x49, 0x82, 0x06, 0x2F, 0x36, 0xB0, 0xC1, 0x2F, 0xE4, 0x0A, 0x40, 0x6C, + 0xCF, 0x21, 0x63, 0xDF, 0xF3, 0xC3, 0x89, 0x0D, 0xAB, 0x38, 0x16, 0x6B, 0xCE, 0xCC, 0xB0, 0xB6, + 0x52, 0x4F, 0x15, 0x45, 0x0A, 0x58, 0xCF, 0x55, 0x03, 0x91, 0x17, 0x3D, 0x9D, 0xFA, 0x51, 0x19, + 0xB2, 0x46, 0x25, 0x33, 0x33, 0xF0, 0xFD, 0x84, 0x3C, 0xB4, 0x69, 0xF3, 0xE5, 0x26, 0xE3, 0xBD, + 0x2F, 0x3D, 0x44, 0x5F, 0x7A, 0xB5, 0x08, 0x33, 0x5C, 0xFA, 0x01, 0x75, 0x5A, 0x45, 0x4F, 0x9A, + 0xB2, 0x9D, 0x90, 0x99, 0xC9, 0x8A, 0xD2, 0x57, 0xE4, 0xB7, 0xD5, 0xCF, 0xAB, 0xBF, 0xB7, 0xD4, + 0x3C, 0xF2, 0x86, 0xD6, 0xBF, 0x26, 0x1B, 0x4B, 0x99, 0xE3, 0xC3, 0x5C, 0x99, 0x86, 0xEE, 0xDF, + 0x28, 0x31, 0x99, 0x66, 0xC5, 0x3A, 0x26, 0x3E, 0x6E, 0x3A, 0x75, 0xD6, 0x9D, 0x2B, 0x71, 0x80, + 0xFA, 0x13, 0xD6, 0xF7, 0x52, 0xA0, 0x3F, 0x7A, 0xAB, 0xA0, 0x06, 0x22, 0xB1, 0xF8, 0x02, 0xAF, + 0x20, 0x0F, 0x53, 0xFE, 0x85, 0xED, 0x5D, 0x18, 0x27, 0xD8, 0x4C, 0xCD, 0x78, 0x0C, 0xB1, 0x62, + 0x75, 0xB4, 0xE5, 0x0A, 0x01, 0xB3, 0xAA, 0x73, 0x70, 0x75, 0x52, 0xF8, 0xE3, 0x62, 0xEB, 0x7F, + 0x99, 0x81, 0x04, 0x35, 0x80, 0xA5, 0xF6, 0xE0, 0x66, 0xD1, 0xBC, 0x0B, 0x29, 0xDB, 0xA4, 0x3F, + 0x2E, 0x46, 0x37, 0x6E, 0xC8, 0x37, 0x61, 0x16, 0x97, 0x0A, 0xAD, 0xBA, 0xA7, 0x5A, 0xF5, 0x4E, + 0x38, 0xBD, 0xE2, 0xAB, 0xC2, 0x45, 0xF0, 0x9F, 0xBA, 0x1B, 0xCA, 0x2B, 0xF3, 0x12, 0xD2, 0x04, + 0x14, 0x26, 0x3C, 0x9E, 0x58, 0x3E, 0xB6, 0x64, 0xC9, 0x14, 0x18, 0xDB, 0x2B, 0xED, 0xC3, 0xDA, + 0x57, 0x8E, 0x22, 0xDD, 0xEC, 0xD5, 0x9C, 0xF0, 0xF8, 0x66, 0x65, 0x35, 0x47, 0x3C, 0xAE, 0x5D, + 0xE6, 0x8C, 0xAB, 0xF5, 0xAA, 0x38, 0xE4, 0x31, 0xC4, 0xBC, 0x9C, 0xF2, 0xF4, 0x99, 0xB1, 0x42, + 0x77, 0x36, 0x39, 0x70, 0x00, 0x8F, 0xB6, 0xDB, 0xB5, 0xD6, 0xD6, 0xDB, 0x64, 0x6B, 0x4B, 0xA1, + 0xCE, 0x3F, 0x23, 0x0B, 0x58, 0x50, 0xDB, 0x69, 0xD6, 0xCF, 0x1D, 0x90, 0xCE, 0x06, 0xF6, 0xB5, + 0x42, 0x62, 0x83, 0x3D, 0x3C, 0xB8, 0xD1, 0x14, 0xBB, 0x76, 0xE8, 0x00, 0x34, 0x7A, 0x96, 0xD5, + 0xB1, 0xD8, 0x45, 0xC9, 0x8E, 0xE2, 0x99, 0xB3, 0x0F, 0x48, 0x8C, 0xFD, 0xF2, 0xEC, 0xBC, 0xF2, + 0x44, 0x6F, 0xEA, 0x7B, 0xE6, 0x31, 0x68, 0x5D, 0xEF, 0x3C, 0x51, 0xBC, 0x7A, 0x1E, 0xBA, 0x46, + 0x4E, 0xF2, 0xD2, 0xB5, 0x31, 0xA2, 0xF8, 0xA8, 0xE2, 0x8A, 0xB2, 0xB1, 0x48, 0xDC, 0x48, 0x36, + 0x95, 0xC5, 0x5A, 0xAA, 0x95, 0x69, 0xBA, 0x65, 0x2E, 0x55, 0x75, 0xE3, 0x11, 0x7C, 0x7E, 0xAD, + 0xCF, 0x8C, 0x7E, 0x7F, 0x52, 0x47, 0xF1, 0xFD, 0xFF, 0x01, 0x24, 0x65, 0x5A, 0x49, 0xC4, 0x6C, + 0xE7, 0xAF, 0x26, 0x32, 0x46, 0xB5, 0xCC, 0x19, 0xD6, 0x95, 0xB0, 0xB6, 0x43, 0x9C, 0xA8, 0xE3, + 0xF3, 0x59, 0x58, 0x24, 0xBD, 0x5B, 0x7D, 0x71, 0x51, 0x24, 0x59, 0xE9, 0xE4, 0x52, 0x5E, 0xA6, + 0xDD, 0xD8, 0xF4, 0x84, 0x46, 0x37, 0xBE, 0x93, 0x7B, 0xC7, 0x50, 0xB9, 0x60, 0x98, 0x5E, 0xAD, + 0x7F, 0x15, 0xDF, 0xA4, 0xCF, 0xBC, 0x60, 0x05, 0x28, 0x53, 0xF9, 0x4B, 0xF8, 0xB5, 0xB8, 0xE4, + 0xEC, 0x69, 0x6C, 0x7E, 0x0C, 0x98, 0xE9, 0x80, 0x3B, 0x37, 0xC8, 0x0B, 0x48, 0xC2, 0x37, 0xF1, + 0xAC, 0x53, 0x4E, 0x12, 0x82, 0x25, 0xB2, 0x42, 0x16, 0xA5, 0x2A, 0xD9, 0xE3, 0x6D, 0x51, 0x6D, + 0x99, 0x64, 0x6B, 0x99, 0x2F, 0x68, 0x2C, 0x91, 0x55, 0xD2, 0xE5, 0x17, 0xEB, 0x8B, 0xD8, 0x8E, + 0x67, 0x7E, 0xE0, 0xB2, 0x83, 0xF1, 0x2C, 0x50, 0xBA, 0xB8, 0x56, 0x74, 0x9F, 0xBB, 0x50, 0x4E, + 0xD9, 0xDC, 0x15, 0x05, 0x62, 0xD2, 0x84, 0x6A, 0x96, 0x93, 0xCE, 0x2A, 0x8A, 0xE0, 0x09, 0x25, + 0x95, 0xC9, 0x15, 0x61, 0x96, 0x93, 0xBE, 0x74, 0x7E, 0xB6, 0x16, 0x63, 0x3A, 0x3E, 0x13, 0x70, + 0x0C, 0x5E, 0x59, 0x41, 0xC4, 0x14, 0x93, 0x4A, 0x6F, 0x76, 0xC9, 0xA6, 0x69, 0xAA, 0x1E, 0xD9, + 0x51, 0x8C, 0x25, 0xAD, 0xBC, 0x42, 0xD6, 0x51, 0x90, 0x3D, 0xC3, 0xAB, 0x7A, 0xB9, 0x53, 0xB3, + 0x61, 0xF9, 0x5C, 0x92, 0x02, 0x24, 0xBB, 0xE4, 0x8F, 0xB9, 0x4E, 0x98, 0x7A, 0x93, 0xBD, 0x6F, + 0xA0, 0x2C, 0x8C, 0xC6, 0xB0, 0x1A, 0x8C, 0xD9, 0xEF, 0xEE, 0x48, 0x7F, 0xBE, 0x4E, 0x1A, 0x26, + 0x7D, 0xCD, 0x9E, 0x03, 0x94, 0x72, 0xB9, 0xBC, 0xAB, 0xF8, 0xEC, 0x29, 0x81, 0x65, 0xA2, 0x33, + 0x9E, 0xDD, 0xF3, 0x7F, 0xC8, 0x91, 0x44, 0x36, 0xC1, 0x89, 0x10, 0x05, 0x60, 0x8D, 0xD9, 0x7E, + 0x5E, 0x52, 0xD1, 0x39, 0x2E, 0x12, 0x4B, 0x7E, 0x23, 0x2A, 0x4B, 0xC8, 0x60, 0xD3, 0x38, 0x21, + 0x49, 0x43, 0xD7, 0x0C, 0x8A, 0x96, 0x63, 0x40, 0x4C, 0xE0, 0xBD, 0x7C, 0xF0, 0xDC, 0x4B, 0x72, + 0x19, 0x24, 0xDD, 0xAF, 0xDA, 0x2D, 0xCF, 0x26, 0xB9, 0x66, 0xE2, 0x64, 0x31, 0x35, 0xF0, 0xF4, + 0x0A, 0x72, 0xD6, 0x28, 0x78, 0x94, 0x98, 0xA2, 0xBC, 0xC7, 0xE2, 0x0C, 0x83, 0x7A, 0x27, 0xB7, + 0xAE, 0x3A, 0x4A, 0xCA, 0xEA, 0x25, 0x7A, 0x53, 0x52, 0x31, 0xED, 0xF0, 0xFC, 0x8A, 0x99, 0xCE, + 0x2D, 0xAD, 0xAA, 0x77, 0x64, 0xC5, 0x40, 0x18, 0x25, 0xCB, 0x49, 0xC8, 0xCD, 0x7B, 0x2C, 0x1A, + 0xC2, 0xAC, 0x31, 0x5F, 0x89, 0xE7, 0x84, 0xC5, 0x64, 0x77, 0xEA, 0x12, 0xE0, 0x46, 0xBB, 0x75, + 0xAA, 0xE7, 0x55, 0xB8, 0x63, 0x27, 0x59, 0xE5, 0xA2, 0x5D, 0x3B, 0x6D, 0x59, 0x50, 0x79, 0xE7, + 0xCE, 0xA4, 0x1F, 0xBA, 0xEE, 0x3C, 0xE2, 0x0E, 0x5E, 0x43, 0xF2, 0xC9, 0x4E, 0x1E, 0x68, 0xED, + 0x2A, 0x6A, 0xE4, 0x2A, 0x68, 0x9B, 0x79, 0x43, 0x4F, 0x17, 0x4C, 0xC5, 0x4D, 0x3D, 0x49, 0xEC, + 0xEA, 0xC6, 0x9E, 0xBE, 0x66, 0xAE, 0xB9, 0xB9, 0x57, 0x65, 0xC9, 0x3D, 0xCF, 0x0D, 0x3E, 0x6D, + 0xE1, 0x3C, 0xDB, 0x26, 0x9F, 0x24, 0x94, 0x7A, 0x1B, 0x7D, 0xC9, 0x68, 0xF9, 0xA1, 0x9B, 0x7D, + 0x8A, 0x7A, 0xC8, 0xFE, 0x15, 0x7A, 0x52, 0x2F, 0xF5, 0x19, 0x73, 0x6E, 0x5B, 0x82, 0x99, 0xF9, + 0x52, 0xAD, 0xB0, 0xAE, 0x43, 0x6C, 0xE8, 0x1F, 0x36, 0xCB, 0x50, 0xBC, 0x54, 0xB6, 0x1D, 0x4B, + 0x8D, 0xE1, 0xA3, 0x6C, 0x76, 0x4A, 0x3A, 0xF0, 0x90, 0x49, 0x16, 0xA6, 0xEC, 0x5A, 0x4A, 0xB9, + 0x8B, 0xB8, 0xCB, 0x0D, 0x3F, 0xA5, 0x9E, 0xED, 0xFC, 0x87, 0xEF, 0x7A, 0x8B, 0x0B, 0x0B, 0x49, + 0xE2, 0x26, 0xAD, 0x4A, 0xBB, 0xBC, 0xCA, 0xBF, 0xFE, 0x6B, 0xA6, 0x4E, 0x40, 0xA3, 0x69, 0xE0, + 0x31, 0x77, 0x5D, 0xE3, 0x6E, 0x48, 0x23, 0x4C, 0x17, 0xA5, 0x98, 0x62, 0x96, 0xA9, 0x0B, 0x4C, + 0x16, 0x68, 0x4F, 0x5C, 0x86, 0x16, 0x95, 0x4F, 0xEB, 0x9A, 0x59, 0x53, 0xEA, 0x88, 0x15, 0x96, + 0xB4, 0x69, 0x77, 0x74, 0xCD, 0x36, 0xE9, 0x22, 0xFB, 0x8A, 0xDC, 0xD9, 0x21, 0xC1, 0xB9, 0x9B, + 0x60, 0x8A, 0x29, 0xEA, 0xB4, 0x09, 0x1F, 0xD2, 0x58, 0x36, 0xA2, 0x3C, 0x17, 0x59, 0x38, 0x08, + 0xF0, 0x89, 0x23, 0x6D, 0xA1, 0x33, 0xA2, 0x27, 0xB6, 0x67, 0x0F, 0x69, 0x70, 0x81, 0x35, 0x33, + 0xC9, 0xA4, 0x18, 0xFC, 0x9B, 0xD7, 0x51, 0x40, 0xEC, 0x91, 0x3B, 0xF4, 0x76, 0x17, 0x46, 0xF4, + 0x3A, 0x5A, 0x80, 0x0F, 0x37, 0x6F, 0x70, 0xFF, 0xE0, 0xF5, 0x2A, 0xFC, 0x82, 0x7F, 0xF4, 0xC1, + 0x90, 0x89, 0x3F, 0x9C, 0x37, 0xAF, 0xF9, 0xA3, 0x94, 0xD1, 0xFD, 0x84, 0xEE, 0x72, 0x8F, 0xE3, + 0xCA, 0xFF, 0xBE, 0x40, 0x5C, 0x67, 0x77, 0x01, 0x09, 0xF2, 0xE5, 0xFC, 0xDE, 0x68, 0xB4, 0x40, + 0xD8, 0x9B, 0xA9, 0xF0, 0xD5, 0x0F, 0xC6, 0x2B, 0xAC, 0xE2, 0x0A, 0x07, 0x95, 0xF8, 0x62, 0x3B, + 0x93, 0x0B, 0x44, 0x04, 0x2E, 0x72, 0x0C, 0x31, 0xCB, 0xFE, 0x70, 0x88, 0xC9, 0x7D, 0x80, 0x9F, + 0x55, 0x24, 0xBB, 0x1A, 0x05, 0xC9, 0x0F, 0xC6, 0xB7, 0x64, 0xB6, 0x11, 0x8C, 0x35, 0xF1, 0x82, + 0xDB, 0xB0, 0x56, 0x4B, 0x12, 0xE5, 0xF7, 0xF1, 0xE8, 0x26, 0x8A, 0x26, 0x85, 0x09, 0xBC, 0xA4, + 0x7A, 0x22, 0x8F, 0xD7, 0xFB, 0x43, 0x9E, 0xC6, 0x0B, 0x67, 0x61, 0x96, 0x65, 0xAC, 0x95, 0xF5, + 0xCE, 0x62, 0x08, 0x96, 0x9D, 0x49, 0xB6, 0x13, 0x22, 0x6C, 0x6C, 0xE0, 0x8F, 0xD1, 0x8C, 0xC4, + 0xD5, 0x02, 0x1A, 0x4E, 0xC0, 0x9D, 0x60, 0x4C, 0x2E, 0x61, 0xC8, 0x8E, 0xC8, 0x59, 0x47, 0xF6, + 0xFB, 0xBF, 0xF0, 0x2C, 0x73, 0xAE, 0x17, 0xF9, 0x6C, 0xF2, 0x7F, 0x51, 0xBD, 0x23, 0xC1, 0xFD, + 0x56, 0x9A, 0x5F, 0x32, 0x99, 0xAA, 0x5A, 0x97, 0xFB, 0x5A, 0x14, 0x28, 0xB7, 0xC8, 0xDE, 0x9A, + 0xD1, 0x6F, 0xFF, 0x2A, 0xB2, 0x5D, 0x50, 0x43, 0xA9, 0xDC, 0xA8, 0xE3, 0xC6, 0x7A, 0xBA, 0x9E, + 0x4B, 0x69, 0x30, 0xB1, 0x82, 0xAE, 0xA2, 0x52, 0x1F, 0x8B, 0x5A, 0x52, 0x0F, 0x27, 0xCD, 0xC2, + 0x5C, 0x75, 0x27, 0xE9, 0x73, 0x58, 0x68, 0xFF, 0xBF, 0x7C, 0x35, 0x97, 0xB3, 0x0D, 0x79, 0x63, + 0x85, 0xF8, 0x82, 0x46, 0x2E, 0x86, 0xB8, 0x82, 0x82, 0x62, 0x8E, 0x5A, 0x16, 0xE7, 0x83, 0xFE, + 0x81, 0x8A, 0x96, 0xD7, 0x13, 0xCB, 0xBB, 0xB2, 0xF8, 0x15, 0x33, 0x45, 0xCE, 0xE9, 0x44, 0x60, + 0xB7, 0xBD, 0x7B, 0x2E, 0xA9, 0xB8, 0x32, 0x3B, 0x72, 0x08, 0x99, 0x6F, 0x7B, 0xCB, 0x85, 0xB6, + 0x42, 0xDC, 0x0E, 0xED, 0x30, 0xC3, 0x26, 0xBE, 0xA0, 0xEB, 0x83, 0x7F, 0xE2, 0x3B, 0x14, 0x84, + 0x3F, 0xC5, 0x41, 0x1C, 0x71, 0xEB, 0x3A, 0x2C, 0x8C, 0xF0, 0xCB, 0x74, 0x7B, 0xDE, 0xB3, 0x6A, + 0xA2, 0x75, 0x99, 0xFA, 0x5F, 0xBE, 0x7F, 0x4D, 0xD7, 0x73, 0x59, 0x25, 0xF9, 0x22, 0x7B, 0x3D, + 0x0F, 0xA5, 0x6D, 0x8E, 0x4D, 0x61, 0x9D, 0x46, 0x63, 0xCA, 0x57, 0xC2, 0xA3, 0xEC, 0x53, 0x04, + 0xB4, 0x42, 0xB3, 0x15, 0x65, 0x2D, 0x6A, 0x37, 0x9B, 0x90, 0xB2, 0xDA, 0x2B, 0x82, 0x34, 0x5B, + 0x38, 0x1A, 0xF3, 0xB2, 0xD2, 0x64, 0x09, 0x31, 0x81, 0xE5, 0x25, 0xB4, 0x2D, 0xC8, 0x86, 0x53, + 0xC0, 0x00, 0xD3, 0xF4, 0xC6, 0x1C, 0x68, 0x3B, 0x8D, 0x35, 0x22, 0x2D, 0xA5, 0x55, 0x9D, 0x6A, + 0xEA, 0xC8, 0x51, 0x18, 0x4E, 0x45, 0xD4, 0xFB, 0xD1, 0xC1, 0x2B, 0xB6, 0xA1, 0x6E, 0xE6, 0xA3, + 0x4E, 0x94, 0xBE, 0x4A, 0xE3, 0x2D, 0x7B, 0xBE, 0xBE, 0xD8, 0xA4, 0x62, 0x9D, 0xAA, 0x66, 0x95, + 0xD7, 0x2D, 0x31, 0xAD, 0x58, 0xA9, 0xB9, 0x79, 0x7D, 0x36, 0xD6, 0x91, 0x35, 0xE3, 0x59, 0x58, + 0xC8, 0x8C, 0x40, 0xEB, 0x5A, 0xC9, 0x9F, 0x16, 0xE3, 0x9F, 0xC2, 0x62, 0xB0, 0xD1, 0xE7, 0x3E, + 0x86, 0xD5, 0x90, 0xFC, 0xBE, 0x03, 0x98, 0x0E, 0xF1, 0x00, 0x22, 0xCD, 0xB4, 0x2A, 0x6E, 0xEC, + 0xA0, 0x33, 0x57, 0x3D, 0x71, 0x27, 0x62, 0x3C, 0x3A, 0x90, 0x0E, 0xD5, 0x38, 0x3B, 0x1E, 0x3B, + 0x51, 0xF9, 0x78, 0xCD, 0x90, 0xF5, 0xE3, 0xBB, 0x40, 0x8D, 0x91, 0x2A, 0x5B, 0x1C, 0x58, 0xCA, + 0x4F, 0x6C, 0xD2, 0xC8, 0x02, 0x1C, 0xA5, 0xEF, 0x78, 0xEA, 0x4E, 0x18, 0x1C, 0x7D, 0xCC, 0x6B, + 0x8D, 0x99, 0x65, 0xC1, 0xFA, 0x62, 0x89, 0x9A, 0x18, 0x3B, 0xA9, 0x2A, 0xD9, 0x4B, 0x19, 0xE7, + 0xAE, 0x99, 0xFB, 0x25, 0xB1, 0x72, 0x4B, 0xCF, 0x0B, 0xA7, 0xC1, 0x28, 0xC2, 0x64, 0x2F, 0xB8, + 0xE6, 0x6B, 0xAD, 0x22, 0x8A, 0x7F, 0x61, 0xEC, 0xB3, 0xBE, 0x92, 0xA5, 0xF9, 0x25, 0x45, 0x8F, + 0xAF, 0xAE, 0xE2, 0x31, 0xEA, 0x9F, 0x6C, 0xC6, 0xCD, 0xAE, 0x23, 0xBA, 0x41, 0x98, 0x47, 0x59, + 0x2B, 0x12, 0xF4, 0x42, 0xA6, 0xE0, 0x69, 0x43, 0xED, 0x4E, 0x7C, 0x32, 0xDB, 0xB9, 0x09, 0xE8, + 0x35, 0x3E, 0x5F, 0x1C, 0x57, 0x8B, 0xEF, 0x73, 0x26, 0xB4, 0x96, 0x97, 0x77, 0xF2, 0x7A, 0x3F, + 0x5E, 0x0B, 0x49, 0x87, 0x4E, 0xE9, 0xD8, 0xAF, 0xDB, 0x4F, 0x79, 0xF9, 0x5A, 0x71, 0xD3, 0x28, + 0xC5, 0xAA, 0x64, 0x6C, 0xF5, 0x32, 0xF9, 0x5A, 0xD3, 0x8A, 0x98, 0xB3, 0x35, 0x1D, 0x5F, 0xF1, + 0x72, 0x25, 0x59, 0x06, 0xA6, 0xD7, 0x14, 0x77, 0x4A, 0x34, 0x9C, 0xAA, 0x99, 0x84, 0xE7, 0xA5, + 0xDF, 0x2F, 0xF2, 0xCE, 0xCC, 0x33, 0xFD, 0x1E, 0x6F, 0xEA, 0x40, 0x7F, 0x33, 0xC6, 0x42, 0x71, + 0x78, 0x9E, 0x99, 0x82, 0x65, 0xCD, 0xD2, 0x77, 0xF0, 0x64, 0xBB, 0x6B, 0x40, 0x6E, 0x7C, 0xF3, + 0xB7, 0xBA, 0x66, 0x7E, 0xCF, 0x28, 0x24, 0x6B, 0x4B, 0x6B, 0xA7, 0xF2, 0x4C, 0x5B, 0x38, 0xD5, + 0x26, 0x8C, 0x94, 0x4F, 0xB0, 0x52, 0xDB, 0x57, 0x57, 0xCF, 0xE9, 0x35, 0xCC, 0xA8, 0x37, 0x3C, + 0x5D, 0x77, 0x12, 0x56, 0x54, 0xB8, 0xDC, 0x54, 0x76, 0x4F, 0x76, 0xB4, 0x2B, 0xA4, 0x3C, 0x73, + 0xB6, 0xA6, 0xF1, 0x52, 0x2E, 0x70, 0xFC, 0xB5, 0x3B, 0x97, 0xF4, 0xDF, 0x1C, 0x53, 0xC3, 0x8C, + 0xDF, 0x9C, 0x51, 0xC3, 0xD5, 0x68, 0x98, 0xEB, 0x87, 0x01, 0x66, 0xA3, 0x6F, 0x93, 0xF8, 0xD7, + 0x0F, 0x30, 0xBD, 0x8F, 0x68, 0xA0, 0x0A, 0x96, 0x61, 0xC9, 0x82, 0x33, 0x0B, 0xD3, 0x06, 0xEB, + 0xC2, 0x13, 0x67, 0xD7, 0x01, 0xE5, 0x87, 0x11, 0x6D, 0x7E, 0x28, 0x51, 0x07, 0xD0, 0xBE, 0xF2, + 0x83, 0x08, 0x00, 0xD9, 0xFF, 0xF3, 0x01, 0x73, 0x92, 0x9C, 0x8F, 0x8A, 0x13, 0x9C, 0x4B, 0xAF, + 0x44, 0x68, 0xF2, 0x88, 0xD3, 0x51, 0xA7, 0xDD, 0x3C, 0xA1, 0xC1, 0x80, 0xDF, 0x8F, 0xE5, 0x65, + 0x9D, 0x11, 0x4B, 0x5D, 0x4D, 0x56, 0x09, 0xFF, 0x33, 0xF2, 0x23, 0xDC, 0x07, 0xFC, 0x33, 0x1E, + 0xED, 0x4B, 0x59, 0xBD, 0x05, 0xDA, 0xB7, 0x76, 0x20, 0x1D, 0xD2, 0x9D, 0xD8, 0xD1, 0x4D, 0x27, + 0xF0, 0xA7, 0xC0, 0x8D, 0x40, 0xBB, 0x94, 0x82, 0x4C, 0xA5, 0xBC, 0xE8, 0x9A, 0x63, 0x6C, 0x80, + 0xC3, 0x41, 0xF7, 0xBF, 0x85, 0x6A, 0x52, 0x87, 0x67, 0x5B, 0x4F, 0xB6, 0x84, 0x05, 0xCB, 0x6F, + 0xF0, 0x65, 0x12, 0x2B, 0xF3, 0xC0, 0x52, 0x3E, 0x9D, 0xD6, 0x59, 0x9A, 0xC0, 0xBD, 0x4D, 0xEE, + 0xF0, 0x0A, 0x96, 0x37, 0xE4, 0x8A, 0x0E, 0xDE, 0x27, 0xD3, 0xEE, 0x7B, 0xE8, 0xA0, 0x71, 0xCB, + 0xF0, 0x68, 0xAE, 0xA6, 0x1D, 0xAA, 0x20, 0xCB, 0xE8, 0xF2, 0xCC, 0xEC, 0x24, 0x49, 0xCD, 0x5E, + 0x26, 0xC8, 0xF8, 0xE0, 0x68, 0x1E, 0x43, 0x9D, 0x7D, 0x4B, 0xCC, 0x39, 0x26, 0x56, 0xE7, 0x47, + 0x13, 0x6F, 0xEF, 0x8F, 0x9C, 0xBA, 0x5C, 0x4B, 0xF2, 0x90, 0x35, 0xBE, 0x91, 0x30, 0xDE, 0xD9, + 0xC0, 0xA4, 0xA3, 0x22, 0x95, 0x47, 0x43, 0x23, 0xA4, 0x7B, 0x88, 0x40, 0x60, 0x4D, 0xD1, 0x46, + 0x03, 0x91, 0x75, 0xF3, 0x2D, 0xCE, 0xA0, 0x8A, 0x6B, 0x93, 0xDE, 0xF3, 0xBF, 0x18, 0x54, 0xCA, + 0x06, 0xCA, 0x4F, 0xC8, 0x12, 0x8C, 0x6A, 0x2C, 0xAA, 0x71, 0x1B, 0x90, 0x1F, 0xE9, 0xE5, 0x40, + 0xC4, 0x6D, 0x4B, 0x8A, 0x31, 0xBB, 0xA2, 0xFC, 0x82, 0xCE, 0xF6, 0xFA, 0x96, 0x69, 0x26, 0x87, + 0xFA, 0x3C, 0x7D, 0x6A, 0x49, 0x93, 0xAA, 0xA4, 0x75, 0x4D, 0x9A, 0x64, 0x4A, 0xC8, 0x5A, 0xDC, + 0xA4, 0xBC, 0x14, 0xAE, 0x43, 0xB9, 0xB8, 0x62, 0x93, 0xA6, 0xCE, 0xA4, 0xA8, 0x31, 0x9F, 0x9C, + 0x1A, 0x8D, 0x99, 0x3A, 0x39, 0x79, 0x17, 0x0B, 0x9A, 0x93, 0x0B, 0x93, 0xE8, 0x9F, 0x63, 0x6E, + 0x50, 0xD7, 0xEA, 0x76, 0x2D, 0x53, 0x8B, 0x9C, 0x9B, 0xC1, 0xE4, 0x10, 0x96, 0x91, 0x81, 0x47, + 0xA3, 0x6C, 0xAB, 0x44, 0xC1, 0xC1, 0x87, 0xFD, 0xB3, 0xE2, 0x26, 0x49, 0x99, 0xD7, 0x8F, 0xCE, + 0x54, 0x16, 0x63, 0xEC, 0x85, 0xCD, 0x93, 0x32, 0xA2, 0x97, 0xC3, 0xCB, 0xFC, 0x43, 0x01, 0x5E, + 0xF6, 0x66, 0x71, 0xCE, 0xAA, 0x37, 0xAC, 0x05, 0xC2, 0xE3, 0xB7, 0x3B, 0xF7, 0xDA, 0x3D, 0xE5, + 0x00, 0x95, 0x1C, 0x48, 0xD7, 0xF9, 0xB7, 0x5D, 0x09, 0xE6, 0xEB, 0x02, 0xF9, 0xD3, 0x9F, 0x48, + 0x52, 0xF6, 0xC7, 0xDD, 0x7E, 0x5F, 0x77, 0x9E, 0x35, 0x7F, 0x4F, 0x26, 0x98, 0xE7, 0xEE, 0xA1, + 0xB0, 0xE5, 0x7A, 0xE8, 0xD1, 0xB1, 0x6E, 0x4B, 0x4E, 0x14, 0x89, 0xB5, 0xA4, 0x45, 0x20, 0x40, + 0xD3, 0x70, 0x6D, 0x20, 0x79, 0x5C, 0xF1, 0xA1, 0x14, 0x2B, 0xD3, 0xBC, 0x27, 0xD6, 0x6F, 0xA7, + 0xF4, 0x2E, 0x4E, 0x15, 0xA5, 0xF4, 0xB3, 0x9E, 0x37, 0xE5, 0x0F, 0x49, 0x62, 0x6A, 0x68, 0xEB, + 0xA2, 0x26, 0x5E, 0x16, 0x0A, 0xA0, 0xAB, 0xF2, 0x49, 0x38, 0xE4, 0x81, 0x15, 0x0B, 0xF1, 0x2B, + 0x1F, 0xFB, 0x82, 0x20, 0x94, 0xE0, 0x33, 0x32, 0xBF, 0xBA, 0xEF, 0x5C, 0x36, 0x31, 0xE0, 0xC5, + 0x6F, 0x3A, 0x9E, 0x44, 0xF7, 0xB2, 0xDA, 0x1A, 0x5C, 0x67, 0x91, 0x5B, 0x6F, 0x5F, 0xE3, 0x3B, + 0x2F, 0x00, 0x51, 0x30, 0x91, 0x4B, 0x1F, 0xF4, 0xC8, 0xC3, 0x6B, 0xCD, 0x30, 0x91, 0xC2, 0x1C, + 0x2A, 0x9A, 0xD4, 0xD2, 0xDE, 0x61, 0x42, 0xFD, 0x10, 0x8F, 0x6E, 0xE0, 0x8E, 0x8F, 0x74, 0x62, + 0x04, 0xCB, 0x51, 0x70, 0x5E, 0xC8, 0x60, 0x1A, 0x04, 0x38, 0xA5, 0xB3, 0xD6, 0x60, 0xCF, 0xB3, + 0xED, 0x99, 0xB3, 0x5F, 0xC3, 0x54, 0xE5, 0x46, 0xA1, 0x98, 0xB8, 0x9A, 0x69, 0x57, 0x81, 0x22, + 0x49, 0xA8, 0xF3, 0xF4, 0x48, 0x62, 0x7E, 0x79, 0x57, 0x06, 0x48, 0x97, 0x08, 0x6D, 0x5C, 0x3E, + 0x68, 0x25, 0x71, 0x40, 0x35, 0x14, 0x2A, 0x7D, 0xA0, 0x5A, 0xB6, 0xF3, 0x7D, 0xA9, 0x13, 0x74, + 0x3B, 0x90, 0xC3, 0x42, 0x06, 0xB4, 0x8D, 0xB5, 0x63, 0x32, 0x68, 0x00, 0x2A, 0x03, 0xB2, 0x9E, + 0x6A, 0xC7, 0x1D, 0xA2, 0xD5, 0xD6, 0xD5, 0x1B, 0xDF, 0x41, 0x89, 0xEF, 0xAD, 0xC8, 0x7B, 0x34, + 0x71, 0x39, 0xDB, 0x40, 0x00, 0x78, 0xB1, 0x27, 0x93, 0x20, 0x5B, 0x32, 0x3E, 0xB1, 0xA2, 0x94, + 0xA7, 0xB7, 0x39, 0x24, 0x7A, 0xC6, 0x47, 0xAF, 0x32, 0x4C, 0x99, 0x9E, 0xBE, 0x62, 0x95, 0x00, + 0xB9, 0x69, 0x6C, 0x2A, 0x4F, 0x37, 0xE5, 0x90, 0x5C, 0xCA, 0xEE, 0x39, 0xB4, 0xF6, 0x11, 0x8E, + 0xA9, 0xBA, 0x60, 0x0B, 0xD5, 0x29, 0x07, 0x3E, 0xB9, 0x2F, 0x52, 0x3A, 0x82, 0x04, 0xA3, 0xC9, + 0x63, 0x3F, 0x04, 0xA6, 0x98, 0x90, 0x85, 0x5F, 0xEC, 0x68, 0xE6, 0x38, 0xA1, 0xF1, 0x0B, 0xAF, + 0xB1, 0x78, 0xAD, 0xFE, 0xAD, 0xB4, 0x2F, 0x97, 0xA6, 0xF4, 0x00, 0xAA, 0x06, 0xCF, 0x76, 0xFC, + 0x4E, 0x3F, 0x5E, 0x1E, 0x9D, 0x5E, 0x1C, 0x9E, 0x9F, 0xC2, 0x1A, 0xB5, 0xB6, 0x39, 0x3A, 0xF5, + 0x71, 0x2B, 0x57, 0x99, 0x53, 0xA4, 0xEC, 0xD1, 0xEE, 0xED, 0x7B, 0x1A, 0x29, 0x46, 0x47, 0x9D, + 0x61, 0x4B, 0xED, 0x92, 0xB6, 0x9F, 0xA8, 0x9A, 0x37, 0x65, 0x4B, 0x33, 0xA7, 0x65, 0xFD, 0xC3, + 0xF3, 0x5F, 0x0E, 0xCF, 0x1B, 0xB4, 0x8B, 0xDB, 0x11, 0x02, 0xFD, 0xCD, 0x7D, 0x00, 0x96, 0xBB, + 0xC4, 0xBE, 0x05, 0x27, 0x96, 0x1F, 0x11, 0x3F, 0xB7, 0xB6, 0xEE, 0x7F, 0x3A, 0x3F, 0x3F, 0x3C, + 0x35, 0xF6, 0x60, 0x6E, 0x23, 0x93, 0x41, 0x0C, 0x53, 0xC9, 0x74, 0x82, 0xF6, 0x9C, 0xA7, 0x63, + 0x7C, 0xE2, 0xB6, 0xC9, 0xD3, 0x94, 0x4E, 0x47, 0xF7, 0xFA, 0x59, 0x34, 0x1F, 0x30, 0x7A, 0xDB, + 0x62, 0x77, 0x5D, 0x14, 0x39, 0xEC, 0x14, 0x22, 0x32, 0x73, 0xA3, 0xBE, 0xD2, 0x85, 0xD8, 0xCF, + 0xF2, 0x56, 0x68, 0xE6, 0x81, 0xA6, 0x02, 0x49, 0xC3, 0x4D, 0xA4, 0x8F, 0x37, 0x89, 0x4E, 0x3B, + 0xBA, 0xD2, 0x2D, 0xD6, 0xF3, 0x71, 0x27, 0x7E, 0x7C, 0xF7, 0x1B, 0x7D, 0x94, 0x4A, 0xE6, 0x2E, + 0xA3, 0xFC, 0xD9, 0x9E, 0x82, 0xF6, 0xBF, 0x17, 0x4F, 0x83, 0xE1, 0x56, 0xD4, 0xB5, 0xD4, 0xBC, + 0x9F, 0x6E, 0x8C, 0xEA, 0xC6, 0x18, 0x5C, 0x05, 0x55, 0x73, 0x1F, 0xDB, 0x51, 0x50, 0xA9, 0x19, + 0xDD, 0x04, 0x8D, 0x21, 0x93, 0x93, 0x30, 0xE4, 0x78, 0xE5, 0x11, 0x27, 0x85, 0xBD, 0xC5, 0xF7, + 0x46, 0xD0, 0x9E, 0x76, 0x5B, 0xB9, 0x6F, 0x3F, 0x1A, 0x59, 0x59, 0x32, 0x5A, 0xEA, 0x18, 0xDB, + 0xE1, 0xF9, 0xF9, 0x47, 0x75, 0x16, 0x7A, 0xA4, 0xA1, 0x55, 0x66, 0xF2, 0x99, 0x35, 0x88, 0xC5, + 0xA0, 0x6A, 0xBD, 0xF2, 0x14, 0xAB, 0xBA, 0x30, 0xF4, 0x23, 0x3B, 0xA6, 0x2E, 0x1E, 0x3A, 0x14, + 0x7B, 0x72, 0xF1, 0x5E, 0x91, 0xD1, 0xD1, 0xCA, 0x93, 0x53, 0xF5, 0xE1, 0xA9, 0x51, 0xE1, 0x9B, + 0x84, 0xFA, 0x83, 0x87, 0x55, 0x4D, 0xB8, 0x86, 0x4C, 0x3A, 0xBA, 0xD0, 0xC9, 0xEC, 0x66, 0x36, + 0x1A, 0xB5, 0x77, 0x55, 0x95, 0x64, 0x11, 0xEF, 0xDD, 0x5B, 0xEA, 0x11, 0x1B, 0x1F, 0xE8, 0x0D, + 0x16, 0x42, 0x91, 0xDF, 0x0C, 0x85, 0x79, 0x8F, 0x13, 0x15, 0x74, 0xB3, 0x17, 0xB9, 0xD7, 0x3C, + 0xCB, 0x18, 0x46, 0xF5, 0x31, 0x13, 0x21, 0x56, 0x98, 0xF8, 0x2D, 0x0D, 0xD8, 0x06, 0x4F, 0x8A, + 0x1C, 0x1C, 0x74, 0x9C, 0xF8, 0x1F, 0xDB, 0x4E, 0xB7, 0xA5, 0x20, 0xF5, 0x18, 0xD7, 0x11, 0xDA, + 0x0A, 0x7C, 0x53, 0x6B, 0x11, 0x69, 0x1E, 0x7A, 0x40, 0x4A, 0x5D, 0xAD, 0xC7, 0x18, 0x93, 0x8A, + 0xD0, 0x7C, 0xF5, 0x35, 0xAE, 0xB0, 0xB3, 0xFF, 0xF1, 0xE3, 0xF9, 0xC1, 0xD1, 0xE9, 0xDE, 0xC5, + 0x21, 0xB8, 0x7F, 0x67, 0x9F, 0x2E, 0x2E, 0x2F, 0x3E, 0x9F, 0xE1, 0xAF, 0xBF, 0xEC, 0x1D, 0x1F, + 0x1D, 0x5C, 0x7E, 0x3A, 0xFD, 0xCB, 0xE9, 0xC7, 0x5F, 0x4F, 0xD3, 0x2D, 0x7B, 0xC7, 0x0E, 0x6F, + 0xA4, 0xFB, 0xF0, 0x89, 0x79, 0xC4, 0x80, 0x7A, 0xC3, 0x77, 0x87, 0x0E, 0xDC, 0xB1, 0x3D, 0x32, + 0x94, 0x70, 0x53, 0xF5, 0xF1, 0xFA, 0x98, 0xB2, 0x57, 0x36, 0xF5, 0xB3, 0x4B, 0x30, 0x1E, 0xE0, + 0x6D, 0x45, 0xF8, 0x98, 0x5B, 0x22, 0x17, 0xFE, 0x66, 0x12, 0xDE, 0xB4, 0x46, 0xF5, 0x22, 0x77, + 0x37, 0x36, 0x9B, 0xAE, 0x86, 0x4C, 0xF0, 0x20, 0xE3, 0x69, 0x98, 0x58, 0xE5, 0x81, 0xED, 0x11, + 0x8A, 0x02, 0x61, 0x26, 0xD3, 0xF5, 0xA0, 0xF3, 0x5D, 0x87, 0xDD, 0x99, 0x0F, 0x45, 0x95, 0x3D, + 0xC2, 0xBF, 0xF1, 0x5A, 0xF8, 0x2C, 0x2D, 0xCB, 0x72, 0xC7, 0x4F, 0x39, 0xC3, 0x36, 0x59, 0x69, + 0x63, 0x34, 0x6D, 0x9B, 0x75, 0x53, 0x47, 0x3D, 0xC9, 0x93, 0x8C, 0x6F, 0x22, 0x78, 0xDD, 0xF4, + 0xAA, 0x97, 0x84, 0x43, 0xC7, 0x1D, 0xBA, 0x51, 0xDA, 0x4D, 0x78, 0x44, 0x9D, 0x77, 0x61, 0x14, + 0x01, 0x54, 0xB1, 0xB1, 0xBB, 0x02, 0x46, 0x79, 0x2D, 0x2F, 0x17, 0xBD, 0x2B, 0x23, 0x93, 0x43, + 0x2C, 0x0B, 0x2B, 0x0B, 0x4B, 0x69, 0x07, 0x2E, 0x2F, 0xB3, 0xB7, 0xAE, 0x46, 0x23, 0x32, 0xF4, + 0x7D, 0xA7, 0x02, 0x34, 0x01, 0xE8, 0xB4, 0x9F, 0x6B, 0x83, 0x77, 0x90, 0xB8, 0xD4, 0xAE, 0x22, + 0x04, 0xF1, 0xCE, 0x4B, 0x23, 0x65, 0x15, 0xA7, 0xDD, 0x6C, 0xC1, 0x06, 0x6A, 0x84, 0x71, 0x3C, + 0xA1, 0xA2, 0x02, 0x2C, 0x6D, 0x82, 0x7A, 0xB4, 0x06, 0xDE, 0x3D, 0x2A, 0xD1, 0xC4, 0x0F, 0x43, + 0x17, 0xA3, 0x7D, 0xB9, 0x56, 0xE0, 0x38, 0x8D, 0xF5, 0x25, 0x1D, 0x92, 0xE9, 0x87, 0x93, 0x93, + 0xCE, 0x98, 0xFF, 0x4B, 0xBF, 0x11, 0xD3, 0xC7, 0x15, 0xD3, 0xC7, 0x93, 0x93, 0x7E, 0xBF, 0x13, + 0xB2, 0x7F, 0x0A, 0x38, 0x31, 0x7C, 0x06, 0x04, 0x2B, 0xE9, 0xE7, 0x17, 0x46, 0x3D, 0x61, 0x77, + 0xD4, 0x66, 0x97, 0xDC, 0xFF, 0xC5, 0x10, 0x68, 0xCF, 0xEF, 0x90, 0x75, 0x18, 0x6D, 0xDB, 0xD6, + 0xDA, 0xDA, 0xFA, 0x4B, 0x96, 0x5D, 0x90, 0xC9, 0xAF, 0x93, 0xC6, 0x5F, 0xA7, 0x23, 0xFE, 0x0D, + 0xE9, 0xCD, 0x83, 0xF0, 0x47, 0x1C, 0x7F, 0x16, 0xCB, 0x78, 0x01, 0x23, 0xAD, 0x47, 0xEC, 0x11, + 0xE6, 0x5C, 0x74, 0x90, 0x11, 0x62, 0x6D, 0x90, 0xDE, 0x46, 0xA7, 0x67, 0xAD, 0x6F, 0x1B, 0x58, + 0x49, 0x6D, 0xD2, 0x1B, 0xB2, 0x36, 0x67, 0x4E, 0x7A, 0x8C, 0x99, 0xB5, 0x94, 0x99, 0x95, 0xAE, + 0xB5, 0xB1, 0xD2, 0xED, 0xAE, 0x58, 0x1B, 0x9D, 0xEE, 0x66, 0xCF, 0xC4, 0x8E, 0xD9, 0xB2, 0xBD, + 0xC1, 0x2B, 0x0C, 0x73, 0x62, 0xED, 0x25, 0xF2, 0x74, 0x4D, 0xEF, 0x68, 0xC0, 0xF9, 0xE9, 0x76, + 0x91, 0x9B, 0xAD, 0xAD, 0xED, 0xED, 0x1E, 0x59, 0x3C, 0xE0, 0x9A, 0x85, 0x55, 0xF8, 0x6F, 0x4B, + 0x09, 0x8F, 0xA9, 0x3B, 0xEB, 0xD1, 0xA1, 0x1D, 0x81, 0xDD, 0xEC, 0xBB, 0x43, 0x4F, 0xF5, 0x1E, + 0xD4, 0xB1, 0x6B, 0xA5, 0x86, 0x23, 0xB5, 0x52, 0x49, 0x31, 0xF7, 0xBB, 0xF0, 0xDA, 0xC0, 0x9E, + 0x64, 0xDC, 0xDA, 0x28, 0x3A, 0x8C, 0xEC, 0x67, 0xE7, 0x58, 0x78, 0xB7, 0x00, 0x8C, 0x17, 0x13, + 0x05, 0x37, 0x1F, 0xE9, 0x4D, 0x22, 0x95, 0x09, 0x35, 0x6C, 0x29, 0xE9, 0xD4, 0x95, 0x15, 0x44, + 0x84, 0x69, 0x42, 0xD3, 0x7E, 0x06, 0x6B, 0x8D, 0x73, 0x26, 0xDF, 0x72, 0x60, 0x6F, 0x78, 0x87, + 0x37, 0x94, 0x5B, 0x70, 0x9E, 0x5D, 0x94, 0x25, 0xDE, 0x10, 0x44, 0x63, 0x32, 0x24, 0x04, 0x3A, + 0xFA, 0x76, 0x9C, 0xAC, 0xC7, 0xDC, 0xD0, 0xE2, 0x9A, 0x4A, 0x9A, 0xE5, 0x92, 0x6F, 0x39, 0xFD, + 0xBA, 0xCB, 0xEE, 0xA6, 0xE0, 0x13, 0x92, 0xB9, 0xE5, 0x9B, 0x4B, 0xB0, 0x20, 0x5B, 0x5D, 0xCD, + 0x8E, 0x78, 0xF9, 0x99, 0xEF, 0xE6, 0x73, 0x35, 0xC7, 0x2B, 0xC5, 0x1C, 0xF0, 0x24, 0x23, 0xEC, + 0x48, 0x89, 0x5F, 0x52, 0x62, 0x07, 0xAD, 0x20, 0x5E, 0x6F, 0x10, 0xDF, 0xFD, 0x4E, 0x1D, 0x06, + 0xD6, 0x4D, 0xE0, 0x7C, 0x12, 0xA1, 0x37, 0x0A, 0x16, 0x61, 0x5F, 0x54, 0x14, 0x12, 0x6A, 0x7E, + 0xF3, 0xDB, 0x92, 0x71, 0xA4, 0x13, 0x5F, 0x5A, 0x6D, 0x65, 0x37, 0x31, 0x55, 0xFC, 0x44, 0x59, + 0xB9, 0x2D, 0xCE, 0x12, 0x15, 0xF3, 0xE4, 0xF8, 0xC5, 0x84, 0x12, 0x32, 0x27, 0x27, 0x72, 0xD4, + 0xDF, 0x3B, 0x17, 0xA6, 0x68, 0x98, 0x5E, 0x94, 0x29, 0x37, 0x9D, 0x98, 0xD9, 0xD5, 0xA9, 0x8F, + 0xD7, 0x8B, 0x6C, 0x02, 0x82, 0xDE, 0x58, 0xE9, 0xEA, 0x3B, 0xFF, 0xB3, 0x8A, 0xFE, 0xF2, 0xF4, + 0xE3, 0xE5, 0xC1, 0xE1, 0xFE, 0xD1, 0xC9, 0xDE, 0xB1, 0xD6, 0x0B, 0x21, 0x85, 0x09, 0xC8, 0xC1, + 0x66, 0x25, 0xFC, 0x68, 0xE2, 0x96, 0xD5, 0x81, 0x2F, 0xAE, 0x38, 0x84, 0x51, 0x64, 0xE9, 0x80, + 0x92, 0x05, 0x2D, 0x83, 0xC4, 0x72, 0xFC, 0x73, 0x22, 0x2F, 0x01, 0x00, 0x22, 0x93, 0x82, 0xEC, + 0x0C, 0xAE, 0x55, 0x4C, 0x6C, 0x39, 0x45, 0xB2, 0x4A, 0x36, 0x61, 0x0A, 0x60, 0x29, 0x03, 0x62, + 0x1A, 0xAB, 0x64, 0x6D, 0x13, 0x73, 0xE5, 0x2C, 0x69, 0x77, 0xDE, 0x95, 0x91, 0x6C, 0xF6, 0x6B, + 0x4C, 0x64, 0xFF, 0x8C, 0xDD, 0x21, 0xBF, 0x83, 0xF9, 0x60, 0x5A, 0x3D, 0x69, 0x43, 0x14, 0x47, + 0xA3, 0x3A, 0x40, 0xD9, 0xF8, 0xCC, 0x1D, 0x7E, 0x1B, 0x38, 0x3A, 0x73, 0x4B, 0xD7, 0x93, 0xB1, + 0xA9, 0xCC, 0xD0, 0xF3, 0x1B, 0x9A, 0x15, 0x07, 0x66, 0x3A, 0x22, 0x15, 0x0D, 0xC9, 0x19, 0x8E, + 0xFA, 0xD0, 0xA8, 0x3B, 0x00, 0x73, 0x87, 0x9F, 0x59, 0x4F, 0x15, 0xD1, 0xB0, 0x38, 0x66, 0x01, + 0x60, 0x40, 0x6C, 0x56, 0xD2, 0xBA, 0x4A, 0xF7, 0xC8, 0xFA, 0xA5, 0x2A, 0x98, 0xA2, 0x4D, 0x5D, + 0xAE, 0x0F, 0x2B, 0x8F, 0xA4, 0x10, 0x97, 0x27, 0x27, 0x97, 0x07, 0x7B, 0xFD, 0x0F, 0x9A, 0x5A, + 0x88, 0x68, 0xAA, 0xD4, 0x6C, 0x89, 0xBB, 0x72, 0x2B, 0xFA, 0x5D, 0xCA, 0x54, 0x07, 0x84, 0x29, + 0x47, 0x48, 0xCC, 0xCE, 0x94, 0x76, 0x58, 0x4E, 0xD7, 0xCA, 0xF5, 0xBB, 0x5F, 0x25, 0x63, 0x9A, + 0xE9, 0xDD, 0x67, 0xD6, 0x59, 0xF9, 0x7D, 0xD5, 0x4B, 0xFA, 0x2A, 0xF6, 0x8E, 0xE7, 0xDE, 0x57, + 0x60, 0xDE, 0x9F, 0x59, 0x77, 0x3D, 0x9B, 0xB9, 0x4F, 0x12, 0x4F, 0x95, 0x29, 0x50, 0x6E, 0x50, + 0x2F, 0x6D, 0x50, 0x76, 0x0E, 0xFC, 0x47, 0x9F, 0xA0, 0xB8, 0x52, 0xA6, 0x1B, 0x38, 0x73, 0xD5, + 0xC9, 0x9D, 0x12, 0x31, 0xA5, 0x06, 0xFC, 0x87, 0x09, 0x40, 0x58, 0x50, 0xF2, 0x78, 0x16, 0xB4, + 0xE2, 0x68, 0x24, 0x3F, 0x8D, 0x67, 0x51, 0x3F, 0xF5, 0x92, 0x7E, 0x22, 0x8F, 0xB3, 0x2E, 0xE1, + 0x06, 0xE2, 0x19, 0x75, 0xD6, 0x73, 0x33, 0x9D, 0xFF, 0xD3, 0xAC, 0xA6, 0xD8, 0xD0, 0x52, 0x4E, + 0x86, 0x0C, 0x08, 0xF8, 0xE1, 0x90, 0xA1, 0x20, 0xC9, 0x1D, 0xB6, 0xBD, 0xA4, 0x25, 0x0E, 0x30, + 0x74, 0x07, 0x3F, 0xF3, 0x49, 0x37, 0xE6, 0xA5, 0x1D, 0x76, 0xDC, 0xD4, 0x4D, 0x2F, 0xD8, 0xB7, + 0x89, 0x3F, 0x8D, 0xF0, 0x0F, 0x5B, 0xEC, 0xDA, 0x03, 0x50, 0xDF, 0x57, 0xF7, 0xE0, 0x71, 0x43, + 0xF9, 0x0A, 0xA4, 0x35, 0xA6, 0x64, 0x41, 0x1B, 0x32, 0x0B, 0x6D, 0x42, 0xA3, 0x81, 0x1C, 0x32, + 0xCC, 0xF8, 0x66, 0x7C, 0x48, 0x7C, 0xB5, 0x4D, 0x2A, 0x63, 0xDE, 0xAF, 0x17, 0x9B, 0x97, 0xD2, + 0x41, 0x23, 0xBF, 0x59, 0x61, 0xD0, 0xB8, 0xEA, 0x2A, 0xB7, 0x64, 0x1C, 0xD6, 0xA9, 0x86, 0xA5, + 0xDF, 0x96, 0x24, 0x31, 0xEB, 0xA7, 0xB5, 0x32, 0x1B, 0xE6, 0xB3, 0xAE, 0x19, 0xD9, 0xBC, 0x94, + 0xD6, 0x3C, 0xB0, 0x6E, 0x9B, 0x11, 0xDB, 0x5C, 0x91, 0xC5, 0xAE, 0xFB, 0x9C, 0x51, 0xF6, 0x3F, + 0x9F, 0xBC, 0xFD, 0x78, 0xCC, 0x90, 0xEA, 0x57, 0x56, 0x46, 0xBE, 0x37, 0x64, 0x59, 0x9A, 0x0E, + 0xE8, 0x30, 0xA0, 0xFA, 0xFE, 0x48, 0xA6, 0x27, 0xB4, 0xAE, 0x85, 0xC5, 0x99, 0x8E, 0xC0, 0x58, + 0x0F, 0x86, 0xEB, 0xA6, 0xB5, 0xA3, 0x65, 0xBE, 0x4C, 0x4A, 0x5F, 0x93, 0x5C, 0xCB, 0xC7, 0x07, + 0xBA, 0x64, 0xBA, 0x14, 0xBD, 0x1A, 0x64, 0x87, 0xED, 0xCB, 0xA5, 0x4C, 0x86, 0xCD, 0x19, 0xBB, + 0x37, 0x8F, 0xB5, 0x64, 0x08, 0x65, 0x44, 0xB8, 0x0C, 0xA3, 0x8A, 0x59, 0x97, 0xB8, 0xEA, 0x4E, + 0xF6, 0xF0, 0x62, 0x4E, 0x8A, 0xD2, 0x88, 0xB9, 0x95, 0xA7, 0xE0, 0x8E, 0xEB, 0x5C, 0x23, 0xFE, + 0xFE, 0xFB, 0xBF, 0x54, 0x06, 0xF1, 0xDB, 0x42, 0x4B, 0x65, 0xB3, 0x09, 0x5E, 0x62, 0x6A, 0xB7, + 0x66, 0x5B, 0xE6, 0xD2, 0xF4, 0xFE, 0x92, 0x3C, 0x80, 0x67, 0x57, 0xC1, 0x39, 0x23, 0x94, 0x56, + 0x55, 0x8F, 0x81, 0x57, 0xEF, 0xF9, 0x39, 0x89, 0x40, 0x72, 0x65, 0x1E, 0x83, 0xEB, 0xC7, 0xC5, + 0xAE, 0xAD, 0x60, 0x97, 0xFE, 0xD1, 0xCD, 0xB0, 0xC2, 0xF3, 0x89, 0x69, 0x6B, 0xBD, 0x0E, 0xCF, + 0x02, 0x41, 0x11, 0xCF, 0x95, 0x27, 0x80, 0xCD, 0xB9, 0x4F, 0x00, 0xF2, 0xE8, 0xAB, 0x39, 0x05, + 0x64, 0x44, 0xF4, 0x44, 0x53, 0x43, 0x66, 0x74, 0xD7, 0x9D, 0x1D, 0x4C, 0x8C, 0xAF, 0x3C, 0x11, + 0xE7, 0x33, 0xCF, 0x1C, 0x26, 0xE6, 0x17, 0xB2, 0x53, 0xCA, 0xBF, 0xB6, 0x5A, 0x8F, 0xD5, 0x86, + 0xA5, 0xC6, 0xD3, 0x93, 0x89, 0x77, 0xF2, 0xD8, 0x82, 0xCF, 0x35, 0xAF, 0x73, 0x52, 0x77, 0xE9, + 0x1A, 0xA6, 0xD1, 0x2C, 0xCC, 0x59, 0x81, 0x66, 0x6C, 0x49, 0x71, 0x3F, 0x3C, 0x71, 0x5B, 0xF4, + 0x89, 0x63, 0xFE, 0x23, 0xBA, 0xA8, 0x41, 0x99, 0x6B, 0x4A, 0x2A, 0x3D, 0x2D, 0xE6, 0x31, 0x0E, + 0x42, 0x1A, 0xE0, 0x93, 0x91, 0x02, 0x66, 0xF5, 0x5F, 0x9D, 0xD5, 0x4E, 0x84, 0x17, 0xAE, 0x07, + 0x4B, 0x3B, 0x44, 0x4D, 0x08, 0x11, 0x9F, 0xEB, 0xC3, 0x92, 0xB8, 0xCD, 0xF3, 0x4F, 0xC2, 0x0A, + 0xF6, 0x26, 0x90, 0x83, 0x24, 0x79, 0xF2, 0xCC, 0x37, 0xB8, 0x6A, 0x8E, 0x2F, 0x56, 0xAD, 0x48, + 0x41, 0x28, 0xF0, 0x59, 0x59, 0xA4, 0x63, 0x35, 0x25, 0xED, 0xA3, 0x48, 0xAD, 0xB9, 0x8C, 0x78, + 0x31, 0x1A, 0x54, 0x29, 0xE7, 0xC8, 0x97, 0x59, 0x92, 0x7C, 0x79, 0x1D, 0xEF, 0x29, 0x2B, 0x77, + 0x81, 0xDA, 0xC6, 0x17, 0x3B, 0x3D, 0x9E, 0xA9, 0x4F, 0xAC, 0xE1, 0xA5, 0xBB, 0xD0, 0xA2, 0x24, + 0x8D, 0x97, 0x2B, 0x58, 0x89, 0x87, 0x77, 0x6E, 0x34, 0xB8, 0x21, 0x45, 0x55, 0x58, 0x78, 0x02, + 0xBD, 0xB6, 0xA7, 0xA3, 0xE8, 0x95, 0x96, 0xEB, 0x9F, 0xF7, 0x44, 0xEB, 0x93, 0xF7, 0xCD, 0xF3, + 0xEF, 0x3C, 0x3D, 0x61, 0xAE, 0xF6, 0xAE, 0xC4, 0x00, 0x2F, 0x25, 0x2F, 0x56, 0x5E, 0xBF, 0xE7, + 0xD0, 0x92, 0xB7, 0x29, 0xE6, 0x4A, 0x10, 0x16, 0x57, 0xB9, 0x24, 0xD3, 0x7D, 0xCF, 0xB9, 0x92, + 0xBC, 0x2C, 0xA2, 0x49, 0x1E, 0x8F, 0x28, 0x9F, 0x8F, 0x73, 0x29, 0xAF, 0x3C, 0x12, 0x65, 0xE6, + 0xBD, 0x14, 0xC8, 0x38, 0xD9, 0x62, 0x9A, 0x7B, 0x7B, 0x8B, 0xE8, 0x2A, 0x9B, 0x5B, 0x8F, 0x40, + 0xB9, 0x5C, 0xD8, 0x2B, 0x8F, 0x43, 0x5E, 0x9F, 0x48, 0x0B, 0x25, 0xFF, 0x18, 0x0D, 0xAF, 0x42, + 0x9B, 0x4B, 0xFF, 0xB1, 0xC4, 0x5E, 0x89, 0x05, 0xDE, 0x03, 0x05, 0x2C, 0x3C, 0xBC, 0xC8, 0xB3, + 0x76, 0x0F, 0x2F, 0xFE, 0x3F, 0x53, 0xDF, 0xB5, 0xEA, 0x6E, 0x9B, 0x00 }; ///main_js //To convert AP-Config\index.html to index_html[], run the Python index_html_zipper.py script in the Tools folder: @@ -1020,1049 +1049,1090 @@ static const uint8_t main_js[] PROGMEM = { // python index_html_zipper.py static const uint8_t index_html[] PROGMEM = { - 0x1F, 0x8B, 0x08, 0x08, 0xD8, 0xB9, 0x5C, 0x67, 0x02, 0xFF, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E, - 0x68, 0x74, 0x6D, 0x6C, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0xED, 0x7D, 0xE9, 0x72, 0xDB, 0x48, - 0x93, 0xE0, 0xFF, 0x7E, 0x8A, 0xFA, 0x38, 0x3B, 0x63, 0x69, 0x46, 0xA4, 0x78, 0x48, 0xB2, 0xAC, - 0xB6, 0x15, 0xA1, 0xCB, 0xB6, 0xF6, 0x93, 0x6C, 0x8E, 0x28, 0x7F, 0x7D, 0x6C, 0xEC, 0x76, 0x40, - 0x40, 0x91, 0xC4, 0x67, 0x10, 0x40, 0xE3, 0x90, 0x2C, 0x4F, 0xCC, 0xC4, 0x3C, 0xC6, 0xEE, 0x83, - 0xEC, 0x0B, 0xEC, 0xA3, 0xCC, 0x93, 0x6C, 0x66, 0x56, 0x01, 0x04, 0x40, 0x9C, 0x24, 0x48, 0x51, - 0x32, 0xDD, 0xD1, 0x12, 0x05, 0xA2, 0xAE, 0xAC, 0xCC, 0xAC, 0xCC, 0xAC, 0x3C, 0xDE, 0xFE, 0xE5, - 0xFC, 0xF3, 0xD9, 0xED, 0x6F, 0xFD, 0x0B, 0x36, 0xF6, 0x26, 0xC6, 0xF1, 0x4F, 0x6F, 0xF1, 0x17, - 0x33, 0x14, 0x73, 0xF4, 0xAE, 0xC1, 0xCD, 0xC6, 0xF1, 0x4F, 0xF0, 0x84, 0x2B, 0xDA, 0xF1, 0x4F, - 0x0C, 0xFE, 0xBD, 0x9D, 0x70, 0x4F, 0x61, 0xEA, 0x58, 0x71, 0x5C, 0xEE, 0xBD, 0x6B, 0xF8, 0xDE, - 0xB0, 0x79, 0xD8, 0x60, 0xBB, 0xD1, 0x2F, 0xC7, 0x9E, 0x67, 0x37, 0xF9, 0x9F, 0xBE, 0x7E, 0xFF, - 0xAE, 0xF1, 0x6B, 0xF3, 0xCB, 0x49, 0xF3, 0xCC, 0x9A, 0xD8, 0x8A, 0xA7, 0xDF, 0x19, 0xBC, 0xC1, - 0x54, 0xCB, 0xF4, 0xB8, 0x09, 0x2D, 0x2F, 0x2F, 0xDE, 0x71, 0x6D, 0xC4, 0x77, 0xD4, 0xB1, 0x63, - 0x4D, 0xF8, 0xBB, 0xCE, 0xB4, 0x13, 0x4F, 0xF7, 0x0C, 0x7E, 0x3C, 0xB0, 0x15, 0xE7, 0xEB, 0x7B, - 0xDF, 0x64, 0x37, 0xB7, 0x7F, 0x65, 0x03, 0xEE, 0xF9, 0xF6, 0xDB, 0x5D, 0xF1, 0x4D, 0x64, 0x28, - 0x53, 0x81, 0xA6, 0x8D, 0x7B, 0x9D, 0x3F, 0xD8, 0x96, 0xE3, 0x35, 0xE8, 0x1B, 0xFC, 0x17, 0x8E, - 0xF2, 0xA0, 0x6B, 0xDE, 0xF8, 0x9D, 0xC6, 0xEF, 0x75, 0x95, 0x37, 0xE9, 0x8F, 0x1D, 0xA6, 0x9B, - 0xBA, 0xA7, 0x2B, 0x46, 0xD3, 0x55, 0x15, 0x03, 0x06, 0xDE, 0x61, 0x13, 0xE5, 0x9B, 0x3E, 0xF1, - 0x27, 0xD3, 0x07, 0xBE, 0xCB, 0x1D, 0xFA, 0x4B, 0x81, 0x39, 0xBF, 0x6B, 0xEF, 0x30, 0x77, 0xEC, - 0xE8, 0xE6, 0xD7, 0xA6, 0x67, 0x35, 0x87, 0xBA, 0xF7, 0xEE, 0x91, 0xBB, 0xD3, 0xD9, 0x1A, 0xF0, - 0x05, 0x73, 0xB8, 0xF1, 0xAE, 0xE1, 0x7A, 0x8F, 0x06, 0x77, 0xC7, 0x9C, 0x7B, 0x0D, 0x36, 0x76, - 0xF8, 0x10, 0x9E, 0x38, 0xEA, 0xEE, 0x9D, 0x65, 0x79, 0xAE, 0xE7, 0x28, 0x76, 0x6B, 0xA2, 0x9B, - 0x2D, 0xD5, 0x75, 0x1B, 0x25, 0x1B, 0xD2, 0xD3, 0x68, 0x03, 0x57, 0x75, 0x74, 0xDB, 0x63, 0xF0, - 0x9D, 0x78, 0xE1, 0xEF, 0x7F, 0xFA, 0xDC, 0x79, 0x6C, 0xF6, 0x5A, 0x07, 0xAD, 0x36, 0x75, 0xFE, - 0x77, 0x78, 0xF5, 0xED, 0xAE, 0x78, 0x2D, 0xA3, 0x4D, 0x7C, 0x36, 0x95, 0x1A, 0xDC, 0xF9, 0xA6, - 0x06, 0x13, 0x9A, 0x6D, 0x17, 0x6D, 0x78, 0x1C, 0x6E, 0xC1, 0x7F, 0xDB, 0xD2, 0x2C, 0xD5, 0x9F, - 0xC0, 0x2E, 0x6C, 0xB7, 0x2C, 0x73, 0xEB, 0x95, 0x6A, 0xE8, 0xEA, 0xD7, 0x57, 0x3B, 0xEC, 0x55, - 0xCB, 0xB3, 0x46, 0x23, 0x83, 0x37, 0xEF, 0x3C, 0x13, 0xFE, 0x1C, 0xFA, 0xA6, 0xEA, 0xE9, 0x96, - 0xC9, 0xB6, 0xF8, 0x36, 0xFB, 0xB7, 0xB0, 0xB5, 0xE8, 0x01, 0x96, 0xEF, 0x3B, 0x0E, 0x74, 0x71, - 0xAB, 0x38, 0x23, 0xEE, 0xB5, 0xD4, 0xB1, 0x6E, 0x68, 0xF0, 0xF7, 0xFF, 0x68, 0xFF, 0xCF, 0x6D, - 0xD9, 0xCD, 0x99, 0xA1, 0xB8, 0xEE, 0xD6, 0x2B, 0x1D, 0x76, 0xBC, 0xA9, 0x2A, 0x0E, 0xF7, 0x9A, - 0x9A, 0xF5, 0x60, 0xB2, 0xC8, 0xDF, 0xBE, 0xFD, 0x6A, 0xFB, 0xE7, 0xB0, 0xE3, 0x7F, 0xDF, 0x16, - 0xD3, 0x4D, 0xCE, 0x1E, 0x81, 0x3D, 0x9D, 0x7C, 0xCB, 0xF5, 0x00, 0x61, 0xD5, 0xE6, 0xC8, 0xB1, - 0x7C, 0x3B, 0x31, 0xAD, 0x31, 0xD7, 0x47, 0x63, 0xEF, 0x88, 0xB5, 0x7F, 0x8E, 0x3D, 0xB6, 0xEE, - 0xB9, 0x33, 0x34, 0xAC, 0x87, 0x23, 0x36, 0xD6, 0x35, 0x8D, 0x9B, 0xF1, 0x6F, 0x01, 0x82, 0xA6, - 0xAB, 0xE3, 0x42, 0x8F, 0x64, 0x07, 0xAC, 0xDD, 0xDA, 0x73, 0x19, 0x57, 0x5C, 0x1E, 0x7F, 0xF3, - 0xCE, 0x72, 0x34, 0xC0, 0xBE, 0x3B, 0xCB, 0xF3, 0xAC, 0xC9, 0x11, 0x73, 0x2D, 0x43, 0xD7, 0x58, - 0xC7, 0xFE, 0xC6, 0xFE, 0x41, 0x6D, 0xE3, 0x7F, 0x91, 0xA5, 0xFC, 0x34, 0x9D, 0xAF, 0xA1, 0xBB, - 0xDE, 0x7A, 0xCF, 0x16, 0xFF, 0xD9, 0x8A, 0xA6, 0xE9, 0xE6, 0xA8, 0xE9, 0x88, 0x39, 0xED, 0xB7, - 0xED, 0x6F, 0xE9, 0xCB, 0x11, 0xDD, 0x02, 0x51, 0xB8, 0xCC, 0xD3, 0x76, 0xD2, 0x9F, 0x8F, 0x13, - 0x2B, 0x15, 0xDF, 0x1D, 0x31, 0xD3, 0x32, 0x13, 0x93, 0x9C, 0x00, 0xF6, 0xE8, 0x66, 0xD3, 0xE0, - 0x43, 0x04, 0x44, 0xC6, 0x98, 0x77, 0x3E, 0x2C, 0xC1, 0x3C, 0x1A, 0x02, 0xD2, 0xBA, 0x89, 0x9E, - 0x2D, 0xDF, 0x03, 0x62, 0xE5, 0x31, 0x20, 0x46, 0x67, 0xAB, 0x9B, 0xF8, 0xF5, 0x85, 0xE3, 0x58, - 0x4E, 0xA2, 0xA5, 0xA6, 0xBB, 0xB6, 0xA1, 0x3C, 0x1E, 0x31, 0xF1, 0x4A, 0x7C, 0x5A, 0xAA, 0x65, - 0x58, 0x30, 0x5F, 0x87, 0x6B, 0xF1, 0xE7, 0x43, 0x60, 0x5E, 0x4D, 0x57, 0xFF, 0x0E, 0x03, 0xBA, - 0x13, 0xC5, 0x30, 0xB8, 0x93, 0x37, 0xEC, 0xC0, 0x57, 0x55, 0x84, 0x47, 0xF5, 0x81, 0x47, 0x0E, - 0x4F, 0x6E, 0x7C, 0xDE, 0xD0, 0xE1, 0xF7, 0x0F, 0x12, 0xA5, 0xEE, 0x2C, 0x43, 0xCB, 0xDA, 0xBE, - 0x6F, 0x4D, 0x6A, 0x9F, 0x98, 0x55, 0xF6, 0x46, 0xE0, 0x3F, 0x62, 0xCF, 0x47, 0xAC, 0xD7, 0xFE, - 0xC7, 0xEC, 0x5E, 0x45, 0x0F, 0xDD, 0x76, 0x5E, 0xC7, 0xDD, 0x1C, 0xB4, 0x0A, 0x7A, 0xD8, 0xCB, - 0xED, 0x61, 0x2F, 0xBB, 0x07, 0xC5, 0xF3, 0x80, 0xE9, 0x26, 0x1A, 0xDB, 0x56, 0x40, 0x2D, 0xCA, - 0x1D, 0x90, 0x80, 0xEF, 0x25, 0x00, 0xFE, 0xBD, 0xA9, 0x9B, 0x1A, 0xFF, 0x76, 0xC4, 0x3A, 0xED, - 0x76, 0x82, 0x24, 0x24, 0x29, 0x74, 0x66, 0xA0, 0x01, 0x87, 0x52, 0x53, 0x42, 0xE4, 0xA0, 0x9D, - 0xF2, 0x2D, 0x4D, 0xD7, 0xB3, 0x6C, 0x20, 0xA3, 0xF8, 0x64, 0x25, 0x7B, 0x13, 0x0C, 0xED, 0xED, - 0xAE, 0x38, 0xBA, 0x7F, 0x7A, 0x7B, 0x67, 0x69, 0x8F, 0x92, 0xC7, 0x6B, 0xFA, 0x3D, 0x53, 0x91, - 0x6F, 0xBE, 0x6B, 0xE0, 0x41, 0xA9, 0x00, 0x82, 0x38, 0x0D, 0xA6, 0x6B, 0xEF, 0x1A, 0x72, 0x79, - 0x97, 0xF0, 0xB8, 0x31, 0xE5, 0x86, 0xD4, 0x40, 0x31, 0xF4, 0x91, 0xF9, 0xAE, 0x41, 0xF3, 0x6D, - 0x04, 0xCD, 0xE5, 0xFB, 0x91, 0x77, 0xE9, 0x7D, 0x7D, 0x32, 0x4A, 0x76, 0xF7, 0x5E, 0x37, 0xF8, - 0x27, 0x38, 0xAD, 0x1B, 0xD3, 0xA3, 0xE5, 0x54, 0x7C, 0xDB, 0xFD, 0xE3, 0x6C, 0x4C, 0xAB, 0x19, - 0xB5, 0x6C, 0x73, 0xD4, 0x80, 0x81, 0xE0, 0xDC, 0x96, 0xDF, 0x31, 0x83, 0xDF, 0x73, 0xA3, 0x71, - 0x0C, 0x0C, 0xDA, 0x56, 0xCC, 0x68, 0x9F, 0x7D, 0xEE, 0xA8, 0x70, 0x30, 0x34, 0x62, 0x03, 0x13, - 0x72, 0x8B, 0x99, 0x11, 0xFA, 0xC1, 0x60, 0x08, 0x86, 0x77, 0x8D, 0x80, 0x1C, 0x24, 0x35, 0x34, - 0x8E, 0xFF, 0xE5, 0xE0, 0x35, 0xC0, 0x08, 0xFA, 0x8C, 0xAC, 0x72, 0x17, 0x96, 0x29, 0x21, 0x24, - 0x3E, 0xE6, 0x41, 0x2B, 0xD1, 0x31, 0x31, 0x9D, 0xE8, 0xAE, 0x10, 0x12, 0x4E, 0xF7, 0x50, 0x6C, - 0xA1, 0x00, 0xB2, 0xC3, 0x41, 0x74, 0xBA, 0x34, 0xFB, 0x8E, 0x85, 0x84, 0x1B, 0x85, 0xF3, 0xDD, - 0xF1, 0x0D, 0x7E, 0xE7, 0x01, 0x2C, 0xDE, 0xEE, 0xDE, 0x1D, 0xBF, 0xBD, 0x73, 0xE8, 0x7F, 0x94, - 0x80, 0x84, 0x08, 0xC3, 0x74, 0x17, 0xF8, 0x05, 0x9E, 0xC9, 0x08, 0x2E, 0xD6, 0x37, 0x90, 0x23, - 0xB3, 0x07, 0x45, 0xF7, 0x5A, 0xAD, 0xD6, 0xAA, 0xA6, 0x8E, 0xE2, 0x9C, 0xC1, 0x3D, 0x9E, 0x32, - 0x73, 0x76, 0x0E, 0x9D, 0x65, 0x4C, 0x7D, 0xAC, 0xB8, 0xC0, 0x9B, 0x1F, 0x18, 0xF5, 0xB1, 0x8A, - 0xC9, 0x0E, 0x75, 0x67, 0xF2, 0x00, 0x22, 0xC0, 0x17, 0xDB, 0xB0, 0x14, 0x2D, 0x7D, 0xD6, 0xC9, - 0xF9, 0xBE, 0x97, 0x6D, 0x98, 0x6F, 0x6B, 0x8A, 0xC7, 0x81, 0x55, 0x8A, 0x56, 0x2D, 0x16, 0xDF, - 0x04, 0xB1, 0x90, 0x60, 0x23, 0x4A, 0x2E, 0xE6, 0xF8, 0xA7, 0x54, 0x92, 0x42, 0x3C, 0xC6, 0xA5, - 0x86, 0xEF, 0x83, 0x68, 0x3A, 0x69, 0x76, 0xBA, 0x20, 0x63, 0x21, 0x21, 0x85, 0xF4, 0xE2, 0x78, - 0x5F, 0x9B, 0x2E, 0x0A, 0xC2, 0x11, 0x3A, 0x89, 0x49, 0xC9, 0xBF, 0xE8, 0xEF, 0x75, 0x21, 0x2A, - 0xA3, 0x78, 0x16, 0x99, 0x4D, 0x69, 0x30, 0x47, 0x00, 0xDB, 0xC9, 0x06, 0xEC, 0x04, 0x1A, 0xF5, - 0x95, 0x11, 0x2F, 0xB9, 0x20, 0xC7, 0x7A, 0x98, 0xD9, 0xC7, 0x3B, 0xC3, 0x52, 0xBF, 0xFE, 0x1C, - 0xED, 0xA0, 0xA0, 0x13, 0x71, 0x9C, 0x20, 0x7B, 0x4E, 0x30, 0x1B, 0xFC, 0x87, 0x8B, 0xBF, 0x00, - 0x89, 0xE6, 0xF1, 0x61, 0xCC, 0x61, 0xEF, 0x82, 0x4D, 0x3C, 0x8A, 0xB0, 0x0D, 0x00, 0x5E, 0xF0, - 0xF8, 0x6F, 0xDC, 0x71, 0x81, 0x59, 0x67, 0x33, 0x87, 0xFB, 0x76, 0xAB, 0x2D, 0xB9, 0x03, 0xE2, - 0xC4, 0xCC, 0x70, 0xD3, 0x5E, 0x47, 0xA6, 0xEB, 0x96, 0xEE, 0xF6, 0xC3, 0xA7, 0xC1, 0x20, 0x32, - 0xB7, 0xC8, 0x28, 0xAC, 0x60, 0x18, 0x81, 0x77, 0xA7, 0xB7, 0x97, 0xE7, 0xD9, 0xBD, 0x9F, 0x0B, - 0xDC, 0x3C, 0x35, 0x7C, 0xEE, 0x01, 0x5A, 0x8E, 0xD9, 0xE5, 0x39, 0x9C, 0xB1, 0xF0, 0xAF, 0xE4, - 0x18, 0xAA, 0x05, 0xF2, 0x93, 0x6E, 0x02, 0xD2, 0xBB, 0x57, 0x57, 0x1F, 0xB3, 0xC7, 0xB9, 0xBA, - 0x1A, 0x1F, 0xCD, 0x74, 0x93, 0x80, 0x0A, 0xB7, 0x34, 0x0E, 0xE2, 0xF3, 0x95, 0xE2, 0x65, 0xF7, - 0xB3, 0x07, 0xCB, 0x7F, 0xD3, 0xEE, 0xBE, 0xD9, 0x7B, 0xFD, 0x46, 0xCE, 0x70, 0xA7, 0x6C, 0xB7, - 0x79, 0x40, 0x6E, 0x76, 0xDA, 0xFB, 0xAD, 0xCE, 0xE1, 0x7E, 0x7B, 0xFF, 0xF5, 0x41, 0xA7, 0x62, - 0xC7, 0x27, 0x46, 0xCE, 0x7C, 0x3B, 0xFB, 0x07, 0x30, 0xE3, 0xC3, 0x60, 0xB2, 0x6C, 0xEB, 0xA4, - 0x7F, 0xB6, 0x3D, 0x0B, 0xCF, 0x52, 0x68, 0x13, 0x81, 0xF5, 0xC5, 0xD9, 0xC5, 0xFB, 0xEC, 0x41, - 0xF1, 0xDB, 0x22, 0x68, 0x73, 0x95, 0x0F, 0x7F, 0xCD, 0x03, 0x48, 0xF7, 0xB0, 0xDD, 0x6D, 0x1F, - 0xB4, 0xF6, 0x0F, 0x0E, 0x4B, 0xC2, 0x03, 0x7B, 0xFC, 0x2D, 0xA7, 0xC7, 0xBD, 0xD7, 0x9D, 0x83, - 0xC3, 0xF6, 0x5E, 0x6B, 0xAF, 0xDD, 0xAB, 0xD0, 0xE3, 0xEF, 0x79, 0xB8, 0x70, 0x78, 0x70, 0x70, - 0xB0, 0xDF, 0xDA, 0x3B, 0xDC, 0x4B, 0x9E, 0xCA, 0x45, 0x80, 0x8D, 0xF2, 0xB6, 0xD4, 0xBF, 0xC7, - 0x4E, 0xC0, 0x42, 0x26, 0x5E, 0xB3, 0x9D, 0x94, 0x6A, 0x66, 0xF9, 0x1E, 0x1D, 0x28, 0x33, 0x8C, - 0xE9, 0x2F, 0xCD, 0x26, 0x6B, 0x06, 0xFF, 0x18, 0x1C, 0xDD, 0x43, 0x90, 0x67, 0xD8, 0x99, 0x65, - 0x0E, 0xF5, 0x51, 0xE4, 0x8B, 0x66, 0xF3, 0x78, 0x96, 0xA1, 0xC9, 0xE1, 0x35, 0xD0, 0xCF, 0x40, - 0x39, 0x1A, 0x29, 0x76, 0xB3, 0x9B, 0xC2, 0xC3, 0xDE, 0x0A, 0x05, 0x24, 0x94, 0xAC, 0x3C, 0x93, - 0xC1, 0xFF, 0x4D, 0xDB, 0xD1, 0x61, 0x72, 0x8F, 0x6C, 0xAA, 0x34, 0x0B, 0x1E, 0x6C, 0x8B, 0x29, - 0x88, 0x19, 0x34, 0x98, 0xF7, 0x68, 0xC3, 0x32, 0x44, 0x17, 0x0D, 0x06, 0x47, 0x97, 0xD2, 0x14, - 0x2D, 0xE8, 0x3C, 0x31, 0x14, 0xDB, 0xE5, 0x8D, 0xD4, 0x3D, 0x12, 0xAF, 0x92, 0x7A, 0xFD, 0xAE, - 0xF1, 0x0F, 0xC1, 0xBB, 0xFD, 0x78, 0xEF, 0x8A, 0xA3, 0x2B, 0x4D, 0xFE, 0x0D, 0x36, 0x40, 0xE3, - 0x78, 0xB0, 0x2A, 0x06, 0x74, 0x27, 0x9E, 0xE2, 0x19, 0xE2, 0x58, 0x86, 0x3B, 0x1D, 0x27, 0xDE, - 0xF6, 0x38, 0x75, 0xD4, 0x38, 0x00, 0x7D, 0x47, 0x21, 0x13, 0xC0, 0x5B, 0x3D, 0xB6, 0x34, 0xD4, - 0xDE, 0xA7, 0xA7, 0x22, 0xE9, 0xF2, 0xA8, 0xD6, 0xB3, 0x3B, 0x9D, 0x25, 0xD4, 0x7D, 0x3C, 0xEE, - 0xF4, 0x34, 0x9C, 0x11, 0x00, 0x49, 0xC5, 0x98, 0xAC, 0x6D, 0x0A, 0x96, 0xC1, 0x26, 0x77, 0xB0, - 0x4F, 0x92, 0x5C, 0xCB, 0xAD, 0x8C, 0xB0, 0x24, 0xB5, 0x2F, 0x77, 0x0C, 0xF2, 0x42, 0x61, 0x87, - 0x33, 0xE8, 0x33, 0x33, 0x37, 0xC5, 0xD1, 0x18, 0xFE, 0x68, 0xA2, 0x34, 0x9F, 0x44, 0xD2, 0xB4, - 0x16, 0x43, 0xCB, 0x99, 0x48, 0xBB, 0x00, 0xA0, 0x7F, 0x37, 0x63, 0x3B, 0x22, 0x5B, 0x72, 0x94, - 0xF9, 0x82, 0xA0, 0x66, 0xD9, 0xAF, 0xE7, 0x49, 0x2C, 0xBB, 0x73, 0x9B, 0x40, 0xCB, 0x2A, 0x9F, - 0x90, 0xB9, 0x4D, 0x68, 0x07, 0x99, 0x5D, 0x90, 0x6D, 0x01, 0x6D, 0x78, 0x20, 0xB8, 0x70, 0x83, - 0xAB, 0x1E, 0x53, 0x98, 0xDC, 0x6D, 0x06, 0xB8, 0x05, 0x3A, 0x3D, 0x37, 0x19, 0xF4, 0x0F, 0x02, - 0x00, 0x93, 0x72, 0x30, 0x28, 0xFA, 0x16, 0x3C, 0x57, 0xBC, 0xE0, 0xC5, 0x16, 0x3B, 0xF1, 0x84, - 0x0C, 0xB9, 0x13, 0x95, 0xC9, 0x1E, 0x74, 0x10, 0x0F, 0x7C, 0x04, 0x35, 0x75, 0xCC, 0xB5, 0xE9, - 0xFB, 0x01, 0xB2, 0xA9, 0x63, 0xC5, 0x1C, 0x71, 0x97, 0xA1, 0x78, 0xE7, 0x2A, 0xF7, 0xF0, 0xCA, - 0x03, 0x8D, 0x07, 0xCA, 0xF2, 0x70, 0xC8, 0xD1, 0xCC, 0x14, 0x4E, 0x06, 0x24, 0xBC, 0xB0, 0x1F, - 0x50, 0xEA, 0xE9, 0xBD, 0x57, 0x03, 0x68, 0x13, 0xC7, 0xD8, 0x57, 0xF8, 0xA2, 0x0D, 0x93, 0x71, - 0xB9, 0xD6, 0xCA, 0x81, 0xED, 0x0C, 0xF8, 0x08, 0x7F, 0x75, 0x73, 0x68, 0x35, 0x55, 0xDD, 0x51, - 0x61, 0x40, 0x8F, 0x7F, 0xF3, 0x42, 0x5A, 0x9F, 0xB8, 0xB8, 0x53, 0x59, 0x9C, 0x30, 0xC1, 0x11, - 0xD3, 0x51, 0x20, 0xC1, 0x05, 0x4B, 0xA0, 0x07, 0x4A, 0x69, 0xC7, 0x05, 0x9B, 0x9F, 0xCE, 0xBF, - 0x63, 0xBA, 0x73, 0xC0, 0x3E, 0x73, 0x41, 0xA1, 0x9B, 0xB6, 0xEF, 0x49, 0x9E, 0xE5, 0x28, 0x9A, - 0x6E, 0x35, 0xA4, 0x25, 0x57, 0xC2, 0xFF, 0x46, 0x3C, 0xBB, 0x57, 0x40, 0x98, 0x79, 0xD7, 0x68, - 0x17, 0x75, 0x67, 0x28, 0x77, 0xDC, 0x88, 0xB2, 0x8E, 0x36, 0x69, 0x9A, 0xC7, 0x72, 0xE3, 0x41, - 0x0C, 0xA0, 0x37, 0x16, 0x02, 0xE5, 0x8B, 0x80, 0x64, 0xA7, 0x32, 0x24, 0x3B, 0x02, 0x92, 0x9D, - 0x6E, 0x6F, 0x6F, 0xFF, 0xE0, 0xF5, 0xE1, 0x9B, 0xF6, 0xF4, 0xD3, 0x06, 0xAA, 0x12, 0xAA, 0xDD, - 0xCA, 0x50, 0xED, 0x0A, 0xA8, 0x6E, 0x20, 0x28, 0x21, 0xD8, 0xAB, 0x0C, 0xC1, 0xDE, 0x06, 0x82, - 0x31, 0x08, 0xEE, 0x55, 0x86, 0xE0, 0xDE, 0x06, 0x82, 0x31, 0x08, 0xEE, 0x57, 0x86, 0xE0, 0xFE, - 0x06, 0x82, 0x31, 0x08, 0x1E, 0x54, 0x86, 0xE0, 0xC1, 0x06, 0x82, 0x31, 0x08, 0xBE, 0xAE, 0x0C, - 0xC1, 0xD7, 0xF5, 0x41, 0xB0, 0x46, 0x10, 0x8A, 0x69, 0xC2, 0xFB, 0xE1, 0x3C, 0x85, 0xE9, 0x3F, - 0xD0, 0x6D, 0xA3, 0x17, 0x39, 0xD2, 0xDC, 0xD9, 0xA3, 0x0F, 0x7B, 0xF4, 0x93, 0x06, 0xA2, 0x3E, - 0x42, 0x19, 0x2E, 0x17, 0x2C, 0xD8, 0xF9, 0x51, 0x31, 0x08, 0xE2, 0x8A, 0x19, 0x0E, 0x7A, 0x48, - 0xC3, 0xBD, 0xAE, 0xB2, 0x89, 0x28, 0xA8, 0x37, 0x62, 0x10, 0x91, 0x1A, 0x70, 0x4C, 0x2D, 0x17, - 0xBB, 0x92, 0xDF, 0xAB, 0x9D, 0x6C, 0x40, 0x17, 0x88, 0x61, 0xDF, 0x91, 0x4B, 0x45, 0xDC, 0x5F, - 0x3B, 0x6F, 0x6F, 0x33, 0x29, 0x60, 0x76, 0xA0, 0x33, 0x52, 0x82, 0xAE, 0x41, 0x65, 0x41, 0x03, - 0x6E, 0x7C, 0x30, 0x79, 0x95, 0x98, 0x33, 0x5C, 0xFD, 0xA8, 0x32, 0x6B, 0x84, 0x91, 0x77, 0xC9, - 0xFB, 0x25, 0xA8, 0x4B, 0x9A, 0x4C, 0xE2, 0xC6, 0x0F, 0xBA, 0x24, 0xF2, 0x4C, 0xBA, 0x8F, 0x90, - 0xD8, 0xD3, 0xC8, 0xB2, 0xAA, 0xA4, 0x62, 0x62, 0xBE, 0xFE, 0x4A, 0x17, 0xC2, 0xE6, 0x19, 0x7A, - 0x33, 0xCC, 0x8C, 0xB3, 0xB5, 0xDD, 0x90, 0xF7, 0x20, 0xF2, 0x41, 0xBA, 0x05, 0x62, 0x49, 0x9A, - 0x75, 0x44, 0xBB, 0x16, 0x93, 0x48, 0xAA, 0xC2, 0xA8, 0x4A, 0x0F, 0x15, 0xD5, 0xB3, 0x60, 0xE5, - 0x1A, 0x1F, 0x2A, 0xBE, 0xE1, 0xB9, 0x45, 0x6A, 0xEB, 0x52, 0x54, 0xD7, 0x22, 0x8E, 0x54, 0x1E, - 0xAB, 0x9D, 0x08, 0xF4, 0xAF, 0xDD, 0x51, 0x6D, 0x08, 0x9D, 0x66, 0x24, 0x4A, 0x79, 0x35, 0x61, - 0x17, 0x24, 0x0B, 0xFF, 0x0A, 0x8D, 0x82, 0x13, 0x0F, 0xF8, 0x66, 0xD4, 0x32, 0x58, 0xB3, 0x19, - 0x10, 0xD7, 0x33, 0x97, 0x0D, 0x30, 0xD2, 0x30, 0x1D, 0xEE, 0x11, 0x48, 0x25, 0xAC, 0x7F, 0x78, - 0xA5, 0xF2, 0xC4, 0xA6, 0xBF, 0xB8, 0x91, 0x2E, 0x77, 0x2D, 0xD9, 0xD6, 0x39, 0x66, 0x7B, 0xCD, - 0x4E, 0xAE, 0x89, 0x8E, 0xAE, 0xD2, 0xB8, 0xE2, 0xFA, 0x0E, 0xD1, 0xF8, 0x8D, 0xE2, 0xF1, 0x4B, - 0x3C, 0x69, 0x72, 0x28, 0xF2, 0x7A, 0xFA, 0x3A, 0xC3, 0xF7, 0x8F, 0x4A, 0x9D, 0x76, 0xF9, 0x2C, - 0x38, 0xE3, 0x6C, 0xEC, 0x12, 0x3F, 0xEC, 0x74, 0x91, 0xA4, 0x7B, 0x6C, 0xCA, 0xD0, 0xCB, 0xF0, - 0x8B, 0xC8, 0xF9, 0x9F, 0x58, 0xE0, 0xC7, 0xEF, 0xB1, 0x7B, 0xCE, 0xE8, 0x49, 0x7F, 0x69, 0x16, - 0x76, 0x8C, 0xFF, 0x3E, 0x7E, 0x3F, 0x2A, 0xF5, 0x5E, 0x8D, 0x4C, 0x35, 0xC1, 0x5C, 0x6F, 0xC7, - 0x9C, 0x99, 0xFE, 0xE4, 0x8E, 0x3B, 0xCC, 0x1A, 0x32, 0xF2, 0xF7, 0x00, 0xFC, 0x75, 0xD1, 0x50, - 0x68, 0x58, 0xAA, 0x40, 0xE6, 0xFF, 0xFA, 0xCF, 0xFF, 0x3D, 0xD4, 0xBF, 0x71, 0xF7, 0xBF, 0xFE, - 0xF3, 0xFF, 0x30, 0x1B, 0x5E, 0x74, 0x39, 0xA0, 0xAD, 0xD6, 0x62, 0x27, 0xE6, 0xA3, 0x37, 0xD6, - 0xCD, 0x11, 0x53, 0xEE, 0xAC, 0x7B, 0xCE, 0xF6, 0x3E, 0x7E, 0x07, 0x99, 0xF2, 0x11, 0xB0, 0x06, - 0x4D, 0x97, 0xD3, 0xBB, 0x3B, 0x78, 0x79, 0xC4, 0x5D, 0xEA, 0x09, 0xE8, 0x6D, 0x97, 0x7A, 0x1E, - 0xA1, 0x8B, 0x04, 0x3A, 0xF6, 0xA8, 0x0E, 0x87, 0xA5, 0xA9, 0x3A, 0x77, 0x5B, 0xEC, 0x93, 0x05, - 0x48, 0xC0, 0x70, 0x46, 0x11, 0x40, 0x33, 0x07, 0xAF, 0xAE, 0x75, 0x97, 0xBC, 0xBC, 0x1C, 0xF2, - 0xEF, 0x42, 0xD6, 0xDF, 0x81, 0xC1, 0xC8, 0x92, 0xA9, 0x9B, 0xEC, 0x14, 0xFD, 0x06, 0x26, 0x96, - 0xC6, 0x5B, 0xEC, 0x5C, 0x9C, 0x04, 0x47, 0x38, 0x99, 0x16, 0xBB, 0xD2, 0x27, 0x3A, 0x3A, 0xE7, - 0xB4, 0xDA, 0xED, 0x76, 0xA7, 0xDB, 0xA5, 0x76, 0x6D, 0xF8, 0xA2, 0xC4, 0xB6, 0x2F, 0xF5, 0xB8, - 0x28, 0x7B, 0x6C, 0x4C, 0xDF, 0x2B, 0x10, 0x07, 0x4B, 0x9C, 0x31, 0x19, 0x94, 0x21, 0x84, 0xD4, - 0xFD, 0x79, 0x08, 0x23, 0x2A, 0x49, 0x0A, 0x14, 0x4A, 0x95, 0x25, 0x23, 0x57, 0x05, 0xB3, 0xF4, - 0x53, 0x62, 0x18, 0x3B, 0xBD, 0xE9, 0xBC, 0x02, 0x66, 0x91, 0xE4, 0x17, 0x19, 0x37, 0x76, 0x5B, - 0x4F, 0x00, 0xC2, 0x5B, 0x37, 0x5C, 0x4F, 0x1B, 0x06, 0xE1, 0x93, 0x63, 0xCB, 0x79, 0xBB, 0x0B, - 0xBF, 0x68, 0xBC, 0xF5, 0x60, 0x49, 0x03, 0xAE, 0x36, 0x8E, 0x07, 0x44, 0x9F, 0x2E, 0xBB, 0xE3, - 0xDE, 0x03, 0x07, 0x12, 0x89, 0xBC, 0xE3, 0xAE, 0x19, 0xBF, 0xC9, 0x99, 0x69, 0x0B, 0x38, 0x01, - 0x90, 0xBD, 0xC0, 0x32, 0x1D, 0x7D, 0x24, 0x81, 0x0D, 0x98, 0xC0, 0x04, 0x80, 0xD8, 0xA1, 0xA9, - 0x97, 0xC6, 0x26, 0xF0, 0xDE, 0x05, 0x5E, 0x05, 0xFE, 0x33, 0xF4, 0x0D, 0xC1, 0x1F, 0x3C, 0xE5, - 0x2B, 0xB1, 0xA8, 0xD8, 0xCB, 0x1C, 0xFD, 0x2B, 0xD8, 0x90, 0x3F, 0x84, 0x33, 0x50, 0x54, 0xC7, - 0x72, 0xE1, 0x17, 0xF0, 0x26, 0x78, 0x1B, 0x5E, 0xBC, 0xE7, 0x8F, 0x6C, 0xAB, 0xBB, 0xF7, 0x2F, - 0x6C, 0x6C, 0xF9, 0x8E, 0xBB, 0x5D, 0x07, 0x77, 0x0A, 0xD9, 0x51, 0x07, 0xDF, 0x39, 0xEC, 0xBC, - 0x39, 0x08, 0x86, 0x07, 0x8E, 0x44, 0x20, 0x2F, 0x0D, 0xCD, 0x88, 0x73, 0xD8, 0x86, 0x29, 0xD5, - 0xCA, 0x94, 0x88, 0x82, 0xE6, 0xE4, 0x4A, 0xD0, 0xB6, 0x06, 0xB6, 0x34, 0xC7, 0xD7, 0x73, 0xAA, - 0xB2, 0xC2, 0x1F, 0xE7, 0xD1, 0x54, 0x26, 0xBA, 0x7A, 0x0D, 0x18, 0x6A, 0x9C, 0x3B, 0x96, 0x2D, - 0x04, 0xD1, 0x52, 0xF6, 0x90, 0x68, 0xD3, 0x46, 0xEA, 0x1E, 0x1E, 0xCC, 0xD8, 0x40, 0xCE, 0x45, - 0x1B, 0x46, 0x8D, 0x8E, 0x56, 0xAC, 0x4A, 0x9E, 0x68, 0x7F, 0xF7, 0x5D, 0x2F, 0xE0, 0x25, 0x1E, - 0x77, 0x4C, 0xC5, 0x60, 0x8A, 0x31, 0xB2, 0x1C, 0xDD, 0x1B, 0x4F, 0x90, 0x2A, 0x27, 0x8A, 0xA7, - 0x8E, 0xE9, 0x7B, 0x50, 0x0F, 0x84, 0xC2, 0xA9, 0xD8, 0x36, 0x28, 0xC8, 0x42, 0x14, 0xE2, 0xE6, - 0xBD, 0xEE, 0x58, 0x26, 0x8E, 0x2D, 0xF9, 0x93, 0xBC, 0xDA, 0x65, 0xFA, 0x04, 0x14, 0xD3, 0x7B, - 0x2E, 0xFA, 0x76, 0xB8, 0xCA, 0x75, 0xE0, 0x08, 0xAF, 0x5C, 0x31, 0x8C, 0x0D, 0x12, 0xBE, 0xE8, - 0x00, 0x38, 0x57, 0x94, 0xC9, 0xC9, 0x9B, 0x62, 0x1F, 0x6F, 0x5E, 0xAD, 0x7B, 0x5D, 0xC3, 0x1B, - 0x5D, 0x60, 0x16, 0x0E, 0xB0, 0x32, 0x55, 0xF5, 0x89, 0xB9, 0x04, 0xFE, 0xB8, 0xE8, 0xB9, 0x0D, - 0xB8, 0xDD, 0x42, 0xA7, 0x38, 0x1A, 0x31, 0x3A, 0x10, 0x4E, 0x1D, 0x60, 0xE5, 0x9B, 0xAE, 0xAF, - 0x7B, 0x18, 0xE8, 0xC1, 0x00, 0x46, 0x1E, 0xC2, 0x9D, 0x58, 0x0F, 0xED, 0x17, 0x35, 0x18, 0xC1, - 0xDB, 0x66, 0xD6, 0x92, 0x90, 0x91, 0x19, 0xFA, 0x57, 0x6E, 0xA0, 0x8B, 0x06, 0xDE, 0x4B, 0x83, - 0x28, 0x85, 0x2C, 0x0C, 0xB9, 0x22, 0x30, 0x47, 0x98, 0x7B, 0x38, 0x1E, 0xC8, 0x81, 0xD8, 0x3B, - 0x88, 0x6E, 0x82, 0xEB, 0x86, 0xB3, 0x14, 0xF3, 0x56, 0x1F, 0xD7, 0x57, 0x15, 0xAF, 0x6E, 0x5A, - 0x93, 0xB8, 0x5C, 0x78, 0x29, 0x2E, 0xDC, 0x00, 0x84, 0x4D, 0x34, 0x4E, 0x1B, 0x49, 0x42, 0x8B, - 0x33, 0x25, 0x4D, 0x92, 0x9D, 0xE0, 0x4A, 0x85, 0xEB, 0xA3, 0x61, 0x56, 0xC9, 0x27, 0xCA, 0x71, - 0x83, 0x89, 0x6E, 0x5E, 0x18, 0xFC, 0xBE, 0x2C, 0x23, 0xB8, 0x06, 0xC4, 0x1A, 0xFC, 0x8D, 0x61, - 0x13, 0x42, 0xC4, 0x55, 0xF3, 0x02, 0x18, 0x1F, 0x03, 0xA5, 0x18, 0x0F, 0x26, 0x80, 0x98, 0xAE, - 0x71, 0x0C, 0x24, 0x70, 0x89, 0x5C, 0x14, 0xA1, 0xD8, 0xBB, 0x40, 0x84, 0x86, 0xA1, 0x7B, 0x64, - 0x71, 0xBA, 0xE3, 0x28, 0x5C, 0x68, 0x82, 0x28, 0x40, 0x29, 0x6A, 0xFD, 0x80, 0x68, 0x5E, 0xF2, - 0x88, 0x95, 0xA7, 0xAB, 0x44, 0x8A, 0x52, 0xF6, 0x63, 0xF9, 0xF2, 0x12, 0x6C, 0xC7, 0xCB, 0x46, - 0xFC, 0xB3, 0x4F, 0x9F, 0xAB, 0xE0, 0xFD, 0xD9, 0xEE, 0xA7, 0xF6, 0x53, 0xE1, 0xFB, 0x14, 0x9F, - 0xD1, 0x35, 0x49, 0x31, 0x44, 0x58, 0x42, 0x2A, 0x72, 0x4F, 0xD5, 0xE9, 0x03, 0xED, 0x14, 0x44, - 0x5A, 0xA4, 0x8A, 0xDF, 0x2F, 0xCE, 0x9B, 0xEF, 0xDF, 0xF4, 0x89, 0xE9, 0x77, 0xDA, 0xE1, 0xE3, - 0x2F, 0xD7, 0x6F, 0x0E, 0xDB, 0x1B, 0x6A, 0x28, 0xA6, 0x06, 0xC4, 0x94, 0xB2, 0xC4, 0x00, 0xEF, - 0xAE, 0x07, 0x2D, 0xA0, 0x6D, 0x36, 0x30, 0x24, 0x9A, 0x2E, 0xE2, 0x0F, 0x71, 0xCC, 0xB3, 0x31, - 0x57, 0xBF, 0x9E, 0x5A, 0xDF, 0xB8, 0x5B, 0x92, 0x52, 0xC8, 0x1E, 0x1A, 0xED, 0x01, 0x1A, 0xC6, - 0xFF, 0x2E, 0x71, 0xF5, 0x55, 0xAB, 0xEB, 0xDE, 0x6D, 0x54, 0x88, 0x02, 0xF1, 0x47, 0x55, 0x6C, - 0x12, 0x9F, 0x40, 0xD6, 0x81, 0xA5, 0xCA, 0x88, 0x4D, 0x90, 0x87, 0xC4, 0x3B, 0x28, 0x76, 0x09, - 0xAA, 0x81, 0x23, 0xC2, 0xB1, 0x40, 0xB8, 0x02, 0xEA, 0xD0, 0x6D, 0x83, 0x4F, 0xA9, 0x2A, 0x54, - 0x25, 0xC3, 0xAF, 0x62, 0x20, 0x03, 0xB5, 0x76, 0x60, 0x4D, 0x78, 0x54, 0x02, 0x73, 0x99, 0xA6, - 0xAB, 0x1E, 0x8A, 0x7A, 0x28, 0xA0, 0x99, 0x1C, 0x28, 0x10, 0xDD, 0x04, 0x7D, 0x07, 0xA5, 0x45, - 0x98, 0x06, 0x77, 0x30, 0xB0, 0x21, 0xDE, 0x0D, 0xCD, 0xC7, 0xC6, 0x4F, 0x48, 0xA4, 0x42, 0x22, - 0x10, 0x6A, 0xAE, 0x10, 0xF0, 0xE2, 0x2F, 0x93, 0x3F, 0xA1, 0x30, 0xCB, 0x25, 0x85, 0x46, 0x58, - 0x0C, 0x4A, 0x9B, 0xDE, 0xD8, 0x72, 0x79, 0xB8, 0x34, 0x10, 0x57, 0xA1, 0x93, 0x40, 0xC8, 0x9C, - 0x88, 0xA5, 0xDE, 0x71, 0xFC, 0x9B, 0x18, 0x84, 0xE6, 0x3B, 0xF8, 0x39, 0x94, 0xF9, 0x54, 0xC5, - 0x50, 0xFD, 0x70, 0x7D, 0x21, 0xD3, 0x38, 0x81, 0x51, 0xB9, 0x89, 0xE0, 0x5C, 0x2F, 0x87, 0xC2, - 0x98, 0xD1, 0x3A, 0x06, 0xA9, 0x53, 0xAE, 0x9F, 0x5B, 0x7E, 0x82, 0x80, 0x11, 0xC9, 0xC5, 0xED, - 0x44, 0xF4, 0x86, 0xAD, 0xDC, 0xED, 0xF6, 0x4C, 0x47, 0xF2, 0x14, 0x10, 0xE4, 0x10, 0x1B, 0xFB, - 0x0F, 0x39, 0xF8, 0xB1, 0xF8, 0x5D, 0x4E, 0xBD, 0x16, 0xDC, 0x67, 0x76, 0x14, 0x7A, 0x1E, 0x5C, - 0xA1, 0xD0, 0x23, 0x98, 0x7B, 0x78, 0x35, 0x9F, 0x42, 0xCA, 0xE1, 0xE8, 0x73, 0xDF, 0x61, 0xA5, - 0x02, 0xF4, 0x83, 0x62, 0xE8, 0x06, 0xB7, 0x9E, 0x08, 0xA2, 0xC1, 0xE8, 0xC7, 0xF2, 0xC3, 0xAA, - 0x61, 0x1A, 0x8E, 0x5F, 0x33, 0x50, 0x0D, 0xCB, 0x84, 0xC9, 0x3D, 0x15, 0x50, 0xAF, 0x3E, 0x7F, - 0x3A, 0x19, 0x0C, 0x00, 0xA8, 0xE2, 0xC3, 0xCA, 0x81, 0x1A, 0x8C, 0x5F, 0x33, 0x50, 0xED, 0x27, - 0x03, 0x68, 0x1F, 0x81, 0xD9, 0x5F, 0x3D, 0x20, 0xFB, 0xB5, 0x03, 0xF1, 0x93, 0x72, 0xAF, 0xAB, - 0x4F, 0x04, 0xC6, 0xEB, 0x93, 0xBF, 0x5D, 0x9E, 0x35, 0x8E, 0x3F, 0xE1, 0xAF, 0x55, 0x83, 0xF2, - 0x93, 0x18, 0xBB, 0x5E, 0x60, 0xFE, 0xEB, 0xF7, 0x27, 0xA3, 0xF1, 0x7F, 0xFD, 0x1D, 0x09, 0x0C, - 0x7F, 0xAE, 0x1A, 0x92, 0x62, 0xE4, 0x7A, 0x01, 0x39, 0xB8, 0x53, 0x9E, 0x0A, 0x90, 0x83, 0xD3, - 0x13, 0x58, 0x0E, 0xFE, 0x5C, 0x35, 0x20, 0xC5, 0xC8, 0x0B, 0x01, 0xB2, 0xAA, 0x4E, 0x1C, 0x51, - 0x5E, 0x66, 0x05, 0xFD, 0xA7, 0x50, 0x64, 0x68, 0x26, 0xE2, 0x0C, 0xFE, 0xA8, 0xB8, 0xD2, 0x6E, - 0xDA, 0xA8, 0xB0, 0xEA, 0x29, 0x9E, 0xD4, 0x81, 0x1A, 0x42, 0x16, 0xFE, 0x10, 0x4E, 0xA8, 0x71, - 0x7C, 0x41, 0x4F, 0x98, 0x7C, 0xC4, 0x3E, 0x9E, 0x0C, 0x76, 0x2F, 0x0E, 0x0A, 0x55, 0xE8, 0x33, - 0x0B, 0xB4, 0x12, 0x4A, 0x34, 0xE3, 0x2E, 0x1F, 0xAB, 0x66, 0x27, 0xBD, 0x62, 0xBB, 0xC5, 0x17, - 0x50, 0x4C, 0x3E, 0x42, 0x0B, 0x76, 0x22, 0x2D, 0xCB, 0x6C, 0xC0, 0x1D, 0x0A, 0x93, 0x52, 0xA7, - 0x70, 0x08, 0xCC, 0xE7, 0x1A, 0x5D, 0xC4, 0x91, 0x96, 0x13, 0x00, 0x35, 0x46, 0x16, 0x11, 0xED, - 0xE4, 0xA2, 0x9C, 0x66, 0xB2, 0x8E, 0x3E, 0x63, 0x95, 0x34, 0xF8, 0x52, 0x38, 0x5C, 0x01, 0x7F, - 0x3F, 0x79, 0x8E, 0x6E, 0x9F, 0x19, 0x3A, 0x26, 0xAC, 0x08, 0x10, 0xF8, 0xD3, 0xED, 0xCD, 0x65, - 0x9F, 0x89, 0x87, 0xC5, 0x0A, 0xFC, 0xE2, 0xE8, 0x18, 0x9B, 0xC3, 0x8A, 0x2C, 0x05, 0x5F, 0x48, - 0x7B, 0xD6, 0x5D, 0xB9, 0x58, 0x95, 0x46, 0x47, 0x15, 0x1A, 0x2D, 0xF7, 0x98, 0x94, 0x21, 0x82, - 0x8F, 0x34, 0x92, 0x50, 0x9B, 0x15, 0xD0, 0x8E, 0x5D, 0x52, 0xA5, 0x5D, 0x81, 0xB6, 0x2D, 0xF6, - 0x9B, 0xE5, 0x3B, 0x41, 0xA8, 0xDF, 0xC4, 0x77, 0x3D, 0x34, 0xBC, 0x3D, 0xE8, 0xE8, 0x4A, 0x23, - 0xB2, 0x1E, 0x38, 0xE8, 0xD5, 0xCA, 0x14, 0x8F, 0xA1, 0xDF, 0x81, 0xA7, 0x4F, 0x78, 0x54, 0xA9, - 0x3E, 0xD7, 0xDD, 0xF5, 0xD3, 0xA8, 0x4B, 0x71, 0x62, 0x73, 0xBA, 0x69, 0x81, 0x1F, 0x5E, 0x6A, - 0x98, 0xEA, 0x32, 0x4E, 0xA5, 0x88, 0x0D, 0x2A, 0x3A, 0x0D, 0xD8, 0x19, 0xEE, 0x7C, 0xB4, 0x5C, - 0xAF, 0x84, 0x3B, 0xF7, 0xFE, 0x8C, 0x25, 0x57, 0x34, 0x2F, 0xE4, 0x1E, 0xD8, 0xFF, 0x51, 0x39, - 0x46, 0x9D, 0xE5, 0xD5, 0x7D, 0x50, 0xF5, 0xEA, 0xBB, 0xC0, 0xB3, 0x3B, 0x1D, 0x06, 0xA5, 0xAF, - 0xBD, 0x53, 0x9B, 0x2F, 0xFF, 0xE6, 0x7B, 0xC5, 0x78, 0xD1, 0xC7, 0x2C, 0x7C, 0x4B, 0xC4, 0x0B, - 0xEC, 0x7F, 0xD5, 0x78, 0x51, 0x68, 0xA1, 0x4E, 0x87, 0xC2, 0xFC, 0x98, 0x81, 0xCD, 0x5F, 0x1C, - 0x66, 0xC0, 0x51, 0xE0, 0x2C, 0x13, 0x33, 0xB0, 0xFF, 0x75, 0xE7, 0x18, 0x04, 0x83, 0xF9, 0xF1, - 0x02, 0x9B, 0xBF, 0x48, 0xBC, 0xE8, 0xFF, 0x52, 0x2C, 0xD9, 0xE6, 0x60, 0x4E, 0xD6, 0xAD, 0x61, - 0x05, 0xCC, 0x61, 0xFD, 0x5F, 0xE6, 0x46, 0x9E, 0xD7, 0x02, 0x71, 0x57, 0x81, 0x3C, 0x00, 0xA8, - 0xC5, 0xD0, 0xA7, 0xFF, 0xCB, 0x0B, 0x42, 0xA0, 0x6B, 0xCB, 0x37, 0xBD, 0xBE, 0xA5, 0x9B, 0xF3, - 0x1D, 0x39, 0xD4, 0xBC, 0xC4, 0x89, 0x03, 0xFD, 0xAF, 0x2F, 0x63, 0x89, 0xC0, 0x60, 0x1E, 0xCC, - 0x98, 0x36, 0x7F, 0x91, 0x78, 0xB1, 0x24, 0xC6, 0x52, 0x01, 0x73, 0x8A, 0xDF, 0x5A, 0x63, 0xC6, - 0x13, 0x03, 0xE4, 0x62, 0xE8, 0xB5, 0x5E, 0x8C, 0x27, 0xD4, 0xEF, 0x3B, 0xB5, 0xDB, 0x32, 0x23, - 0xAB, 0xBF, 0xC5, 0x2C, 0xBA, 0x13, 0xDD, 0xFB, 0xF0, 0xE1, 0xA4, 0x71, 0x1C, 0xFC, 0xC1, 0xE0, - 0x2F, 0xD4, 0x80, 0x05, 0x4F, 0x5E, 0xBE, 0x2D, 0x2A, 0x63, 0x3E, 0x85, 0x9B, 0xE9, 0x9B, 0xD4, - 0x21, 0xD7, 0x56, 0x6D, 0xBB, 0xA2, 0xAB, 0x7E, 0x95, 0xA0, 0x83, 0xB9, 0x2E, 0xFF, 0xF4, 0x75, - 0xF4, 0xEC, 0x84, 0x4F, 0x23, 0xDF, 0x50, 0x9C, 0xE9, 0x05, 0xBA, 0x4C, 0xD0, 0x48, 0x06, 0x03, - 0xBA, 0xA1, 0x27, 0x2B, 0xD6, 0x16, 0x82, 0x77, 0x22, 0xA2, 0x5B, 0xB7, 0x99, 0x2B, 0x53, 0x07, - 0xE1, 0xF7, 0xA2, 0x4B, 0xF8, 0x65, 0x32, 0xC5, 0xF7, 0xAC, 0x09, 0xE6, 0x77, 0x56, 0x0C, 0xE3, - 0x51, 0x46, 0x49, 0x4A, 0x47, 0x02, 0xC5, 0xE1, 0xAE, 0xC7, 0x94, 0x7B, 0x45, 0xA7, 0xDC, 0xDF, - 0x49, 0xFB, 0x44, 0x8A, 0x51, 0xE1, 0x25, 0xDA, 0xC2, 0x84, 0x9F, 0x36, 0x01, 0x11, 0x7D, 0xB4, - 0x4F, 0x45, 0x3C, 0x61, 0xCE, 0x58, 0x32, 0x4C, 0x31, 0x8C, 0x8B, 0x1D, 0x71, 0x4F, 0x86, 0x18, - 0x5F, 0xE9, 0xAE, 0xB7, 0xB5, 0x3D, 0x13, 0x80, 0x3B, 0xD1, 0xE8, 0x97, 0xCC, 0xAC, 0x9C, 0x19, - 0xD0, 0x98, 0x6F, 0x64, 0x2A, 0x11, 0xEC, 0x58, 0x18, 0xD8, 0x88, 0x41, 0xA3, 0xB9, 0xA3, 0xCC, - 0x19, 0xF7, 0x88, 0xFD, 0xE6, 0x6F, 0xA0, 0x04, 0x10, 0xC5, 0xF1, 0xB9, 0xD1, 0xE8, 0x47, 0x68, - 0xBA, 0x78, 0x00, 0xE4, 0x74, 0xAF, 0x8B, 0x22, 0x90, 0xCB, 0x93, 0x70, 0x12, 0x2F, 0x2E, 0x01, - 0x8B, 0x6F, 0xF1, 0x34, 0x29, 0x63, 0x0B, 0xFC, 0x74, 0x7D, 0x71, 0x42, 0x1E, 0x70, 0x37, 0x27, - 0xBF, 0xFC, 0x4A, 0x39, 0xB8, 0x90, 0xE2, 0xBC, 0x07, 0x8B, 0x4D, 0x2C, 0x20, 0x39, 0xD5, 0x9A, - 0x4C, 0x2C, 0x93, 0xBC, 0x86, 0x30, 0x95, 0x3F, 0x3A, 0xF4, 0xC0, 0xF6, 0xBA, 0xC2, 0xF9, 0x5B, - 0x00, 0xEA, 0xCE, 0xF7, 0xE2, 0xBE, 0x38, 0xAE, 0x6F, 0xE3, 0xBB, 0xAE, 0xF0, 0xFE, 0x06, 0x4A, - 0x37, 0xD9, 0xEB, 0x76, 0x24, 0x9B, 0x97, 0x6C, 0xE8, 0xB6, 0xD8, 0x85, 0xA2, 0x8E, 0xC3, 0x7E, - 0x44, 0xFC, 0x89, 0xE0, 0xA9, 0x72, 0x07, 0xD9, 0xC3, 0x58, 0x9F, 0xBE, 0x21, 0x52, 0x84, 0x69, - 0x92, 0xC0, 0xD9, 0x56, 0x7B, 0x9B, 0x26, 0x8E, 0x39, 0xDC, 0xAC, 0xA1, 0x87, 0xE1, 0x2A, 0x14, - 0xD1, 0x22, 0x7A, 0xA3, 0x8C, 0xBC, 0x72, 0xCA, 0x5B, 0x1D, 0xF6, 0x0E, 0x88, 0x60, 0xFA, 0x65, - 0xF8, 0x0D, 0xC6, 0x00, 0x76, 0xD0, 0x41, 0x70, 0x87, 0xED, 0xCB, 0x77, 0xC4, 0x77, 0x32, 0xAE, - 0x66, 0x9F, 0x51, 0xB8, 0xE0, 0x76, 0x84, 0xC1, 0x10, 0xC0, 0x80, 0x9D, 0xED, 0xB0, 0x0F, 0x03, - 0xFA, 0x71, 0x8B, 0x3F, 0xFE, 0xB6, 0x23, 0x80, 0x78, 0x7D, 0x26, 0x63, 0xF5, 0xDC, 0x23, 0xD6, - 0xC6, 0x03, 0xA5, 0xDB, 0x7E, 0x96, 0x19, 0xC8, 0x8A, 0x62, 0x6E, 0xF3, 0xE9, 0x28, 0x27, 0x39, - 0x5E, 0x4A, 0x6A, 0xC7, 0xFD, 0x94, 0xCC, 0x8E, 0xA9, 0x3D, 0xCE, 0x9B, 0x90, 0x20, 0xCA, 0x0B, - 0x67, 0x93, 0x12, 0x98, 0x13, 0xAE, 0xC8, 0xDD, 0x75, 0xB3, 0x32, 0x12, 0x94, 0x0B, 0x0E, 0x0B, - 0xF9, 0x2C, 0x85, 0xBF, 0xDF, 0x5A, 0x03, 0x8A, 0xBD, 0xD2, 0xCD, 0x51, 0xD0, 0xFD, 0x34, 0x11, - 0x01, 0x60, 0x46, 0xF8, 0x6D, 0x80, 0x5B, 0x6E, 0xC9, 0xB4, 0x04, 0x25, 0x03, 0x91, 0x96, 0x03, - 0x2D, 0x19, 0xF0, 0xBA, 0x1C, 0x80, 0x5D, 0xC5, 0x3B, 0x8F, 0x81, 0x4B, 0x7E, 0xB7, 0x04, 0x60, - 0xA5, 0x25, 0x85, 0x4D, 0x3F, 0x7B, 0x2D, 0x57, 0xD1, 0x55, 0x64, 0x00, 0x03, 0xCF, 0xE1, 0xCA, - 0x24, 0x88, 0x31, 0x72, 0xCB, 0x40, 0x34, 0xAB, 0x93, 0x4E, 0x89, 0x48, 0xA5, 0x2C, 0xED, 0xCA, - 0xA5, 0x1E, 0x2E, 0x31, 0x2E, 0x07, 0xA4, 0x4B, 0xEC, 0xF3, 0x0F, 0x90, 0x96, 0x89, 0x43, 0xC9, - 0xCE, 0x45, 0xD0, 0x0E, 0x7C, 0x79, 0x54, 0x2E, 0xCA, 0x6D, 0xCA, 0x94, 0xA2, 0x11, 0x20, 0xA9, - 0xE3, 0xD0, 0x6A, 0xD2, 0xBF, 0x99, 0x37, 0x22, 0x24, 0x36, 0x03, 0x8B, 0x9C, 0x42, 0x23, 0xD9, - 0x06, 0x3B, 0xED, 0x09, 0xEC, 0xBA, 0x78, 0x3C, 0x77, 0x3F, 0x9D, 0xC6, 0x71, 0xB7, 0x8E, 0x7E, - 0xBA, 0x98, 0x1E, 0xB7, 0x86, 0x7E, 0x7A, 0x8D, 0xE3, 0xFD, 0x3A, 0xFA, 0xD9, 0x43, 0xF8, 0xD4, - 0xD1, 0xD1, 0x3E, 0x02, 0xA8, 0x8E, 0x8E, 0x0E, 0x70, 0x65, 0x75, 0x74, 0xF4, 0x1A, 0x96, 0xB6, - 0x78, 0x2F, 0x87, 0xB0, 0xAE, 0xC5, 0x7B, 0x79, 0x03, 0x8B, 0xAA, 0x01, 0x09, 0x09, 0x9B, 0x6B, - 0xE8, 0xA7, 0x83, 0x09, 0xB0, 0x6B, 0xE8, 0x07, 0xB0, 0xB9, 0x57, 0xC7, 0x7C, 0x00, 0x9B, 0x0F, - 0xEA, 0xE8, 0x07, 0xB0, 0xB9, 0x3B, 0xD1, 0xCD, 0xC5, 0x3B, 0x02, 0x6C, 0xDE, 0xAF, 0xA5, 0xA3, - 0x03, 0xE2, 0x3F, 0x75, 0xF4, 0x84, 0xE8, 0x5C, 0xCF, 0x9C, 0x0E, 0x71, 0xD7, 0x6A, 0xE9, 0xE9, - 0x0D, 0xEE, 0x5B, 0xE5, 0x9E, 0x8A, 0x03, 0xF8, 0x96, 0x64, 0x93, 0x48, 0xDA, 0x26, 0xA6, 0xA6, - 0x82, 0xE0, 0xB8, 0xA3, 0x48, 0x9E, 0xE8, 0x41, 0xF8, 0xDC, 0x12, 0x65, 0x94, 0x92, 0x4C, 0x4A, - 0x4A, 0x38, 0xB9, 0xF2, 0x47, 0xB7, 0x5E, 0xF9, 0xA3, 0x13, 0x93, 0x3F, 0xBA, 0x4B, 0x93, 0x3F, - 0x3A, 0x99, 0xF2, 0x47, 0x67, 0x23, 0x7F, 0x6C, 0xE4, 0x8F, 0x8D, 0xFC, 0xB1, 0x91, 0x3F, 0x36, - 0xF2, 0xC7, 0x46, 0xFE, 0x58, 0x17, 0xF9, 0xA3, 0xFB, 0xFC, 0x12, 0x75, 0x15, 0x1B, 0x4E, 0x4A, - 0x1A, 0xA2, 0x22, 0xE6, 0x69, 0xBC, 0x76, 0x28, 0x32, 0xF5, 0xE1, 0xBF, 0xE5, 0x26, 0x97, 0x29, - 0xF1, 0x38, 0xB0, 0x93, 0xE6, 0xA5, 0x95, 0xA4, 0x74, 0x48, 0x4F, 0x99, 0x56, 0x52, 0xD4, 0xDD, - 0x73, 0x97, 0x55, 0x6D, 0xE6, 0x34, 0xD2, 0x75, 0xA5, 0xEB, 0x96, 0x48, 0xC3, 0x8C, 0x7D, 0x40, - 0x40, 0xA6, 0x94, 0x90, 0xCB, 0xA8, 0xB5, 0x82, 0xFF, 0x22, 0xC0, 0x4E, 0x64, 0xA6, 0x24, 0x08, - 0xAC, 0x53, 0x66, 0xCA, 0xDC, 0xE5, 0xCF, 0x59, 0x37, 0x86, 0x64, 0x5D, 0x32, 0x18, 0x5F, 0x9A, - 0x22, 0x39, 0x75, 0xA1, 0x57, 0x7A, 0x2C, 0xBB, 0x75, 0x00, 0xA8, 0x5B, 0x78, 0x38, 0x90, 0xFD, - 0x04, 0x39, 0xAF, 0x83, 0xE7, 0x71, 0xB9, 0x59, 0x36, 0x2C, 0xBC, 0x6E, 0x8E, 0xEA, 0x03, 0x33, - 0x23, 0x1C, 0x8B, 0x4F, 0xCD, 0x4B, 0x73, 0xC5, 0xC1, 0xF0, 0x97, 0x22, 0xAD, 0x9A, 0xED, 0x70, - 0x55, 0x07, 0xC4, 0x09, 0x13, 0x40, 0x5A, 0x43, 0xA6, 0x30, 0x9C, 0x26, 0x73, 0x65, 0x1A, 0x23, - 0xAA, 0x1A, 0xE8, 0xB1, 0xAF, 0x26, 0xD5, 0x27, 0xF6, 0x28, 0xEF, 0xE3, 0x1D, 0x67, 0xD6, 0x1D, - 0xD5, 0xE0, 0xD3, 0xD8, 0xDD, 0x23, 0x66, 0x8D, 0x74, 0x03, 0x63, 0x3D, 0x66, 0x8E, 0xC4, 0x9E, - 0x83, 0x1E, 0x5B, 0x94, 0x47, 0x8D, 0x7A, 0x84, 0x9E, 0xF0, 0xB6, 0x88, 0xB2, 0x4E, 0xE0, 0x4D, - 0x12, 0x4D, 0x5C, 0xA4, 0x45, 0x52, 0xBE, 0xE2, 0xED, 0x95, 0x6D, 0x3B, 0xD6, 0x37, 0xA0, 0x65, - 0x0F, 0xD3, 0x12, 0x1D, 0xB4, 0xC3, 0x8C, 0x6D, 0x0F, 0x96, 0xE3, 0x8D, 0x45, 0x4E, 0x22, 0x45, - 0x0B, 0x8A, 0xEB, 0x88, 0x09, 0xE0, 0x6C, 0xF1, 0xEA, 0x7B, 0x08, 0x33, 0x0B, 0x17, 0x81, 0xA3, - 0x69, 0xCC, 0x32, 0x93, 0x99, 0xE3, 0x82, 0x44, 0x73, 0x13, 0xEE, 0x8D, 0x2D, 0x8D, 0x29, 0xEA, - 0x58, 0xE7, 0x98, 0xC7, 0xE9, 0x3F, 0x7A, 0x6D, 0x75, 0x92, 0x92, 0x86, 0x09, 0x6F, 0xEE, 0xF0, - 0xE2, 0xFD, 0x1E, 0xB8, 0x4B, 0x8B, 0x5D, 0x9A, 0x2A, 0x8C, 0xEF, 0x06, 0xF9, 0x98, 0x82, 0x24, - 0x1B, 0x9F, 0xEF, 0xD0, 0xDB, 0x5F, 0x0C, 0x7C, 0xAB, 0x4F, 0x78, 0x90, 0xF9, 0xF2, 0x46, 0xF8, - 0x00, 0x68, 0x98, 0xFC, 0xD4, 0x64, 0x58, 0xE6, 0x2E, 0x12, 0xA1, 0xAF, 0x8B, 0xBE, 0x78, 0x98, - 0x43, 0x89, 0xC6, 0xA2, 0x8B, 0x44, 0x4C, 0xEF, 0x04, 0x53, 0xD0, 0x31, 0xED, 0xD4, 0x29, 0xC7, - 0xA2, 0xA1, 0xD3, 0x97, 0x74, 0x37, 0x98, 0xB4, 0x46, 0x61, 0x04, 0x94, 0xC2, 0xAE, 0x09, 0xAF, - 0x4C, 0xC2, 0x44, 0x9B, 0x32, 0x43, 0x13, 0xD5, 0x04, 0xC2, 0x24, 0xC2, 0x54, 0x70, 0x73, 0x9A, - 0xDC, 0xA3, 0x2D, 0xF2, 0x50, 0xED, 0xB7, 0xDA, 0x93, 0xE9, 0x45, 0xDC, 0x01, 0xDD, 0xC4, 0xBD, - 0x69, 0xB7, 0xDD, 0x1D, 0xD6, 0x69, 0xD1, 0x1F, 0xF4, 0xC2, 0xB3, 0x8B, 0x3B, 0x08, 0x38, 0x80, - 0x8C, 0xFF, 0x72, 0x1B, 0x25, 0xE2, 0x07, 0x83, 0x36, 0x73, 0x06, 0x2A, 0x2C, 0xE4, 0x09, 0x96, - 0xE4, 0x0F, 0xD6, 0x14, 0x97, 0x64, 0x66, 0xC7, 0x5C, 0x17, 0xC1, 0x4E, 0x37, 0x2D, 0xE5, 0x0C, - 0x62, 0x65, 0xB9, 0xDB, 0xA5, 0x08, 0xE6, 0x62, 0xEC, 0x09, 0xDB, 0x72, 0xB7, 0x8F, 0x4A, 0x5B, - 0x21, 0x8A, 0x92, 0xF9, 0x55, 0x30, 0x27, 0x54, 0xF3, 0x5B, 0x4F, 0x01, 0x52, 0xC9, 0x61, 0xEC, - 0x8C, 0xF6, 0x8B, 0xF8, 0x78, 0x55, 0x93, 0x05, 0x57, 0x8E, 0x46, 0x7D, 0xC9, 0xCB, 0x82, 0x88, - 0xBA, 0x72, 0xB2, 0x7B, 0x35, 0x9C, 0x0B, 0x38, 0x5D, 0xA9, 0xAE, 0x89, 0x1B, 0xF6, 0xCE, 0x41, - 0xEC, 0x07, 0x3E, 0x84, 0xE7, 0x7B, 0xC8, 0x19, 0x4B, 0x35, 0xDF, 0x9A, 0x2C, 0x8A, 0xA2, 0x84, - 0x9C, 0x12, 0x4F, 0xEF, 0x0A, 0xA3, 0x3D, 0x17, 0x46, 0xD1, 0x99, 0x0D, 0x98, 0x17, 0x57, 0x93, - 0x1D, 0xAD, 0x0E, 0x69, 0x97, 0xE5, 0xAA, 0x45, 0xC2, 0xC0, 0xA2, 0xC2, 0xDA, 0x7B, 0xEC, 0x64, - 0x56, 0x52, 0x0B, 0xAD, 0x89, 0x29, 0x32, 0x5B, 0x35, 0x59, 0x4D, 0x0C, 0x70, 0x4C, 0xBF, 0x62, - 0x27, 0x9E, 0x28, 0xD5, 0x7D, 0xCC, 0xB6, 0xCE, 0xC6, 0x16, 0xA6, 0xD4, 0xC1, 0xA2, 0xA7, 0xCC, - 0x2A, 0x76, 0xDC, 0xFF, 0x20, 0xCB, 0xB6, 0x6E, 0x07, 0xB5, 0x41, 0x9F, 0x44, 0xF2, 0x8B, 0x4A, - 0x7C, 0x5E, 0x44, 0x42, 0x0B, 0xE5, 0x3C, 0x14, 0x7C, 0x40, 0xCE, 0xA3, 0x72, 0xC6, 0x42, 0x66, - 0xE3, 0x20, 0x71, 0x80, 0x34, 0x22, 0xD7, 0x19, 0xAE, 0x83, 0x45, 0xCA, 0xC2, 0xA2, 0x94, 0x24, - 0x42, 0x27, 0x31, 0x17, 0x65, 0xDC, 0xED, 0x49, 0x08, 0x3E, 0x93, 0x09, 0xD7, 0x74, 0x21, 0xE1, - 0xDD, 0xF1, 0x91, 0x1E, 0xE4, 0xBA, 0xA4, 0xB0, 0xC9, 0x9B, 0xDB, 0xB3, 0xEB, 0x59, 0xD7, 0xC5, - 0x13, 0x29, 0x35, 0x86, 0x62, 0x99, 0xEE, 0x0A, 0x99, 0x2F, 0x14, 0x41, 0x49, 0x14, 0xEA, 0xF7, - 0xFB, 0x6C, 0xCB, 0x16, 0x95, 0xC5, 0x5D, 0x0E, 0x02, 0xAA, 0xEF, 0x30, 0xCF, 0xF7, 0x2C, 0x50, - 0xC1, 0x8C, 0x6D, 0xAA, 0x86, 0x88, 0x6F, 0x25, 0x44, 0x23, 0xA6, 0x8C, 0xA0, 0x0B, 0x74, 0x9D, - 0x84, 0x79, 0x92, 0xFB, 0x95, 0x1A, 0x4A, 0xBE, 0x11, 0xB1, 0x29, 0x28, 0x57, 0xFD, 0xCA, 0x9D, - 0xC2, 0x0D, 0xF3, 0xFD, 0x5A, 0xBE, 0xA1, 0x71, 0x67, 0x07, 0xF4, 0x2E, 0xC3, 0x02, 0xDC, 0xB2, - 0x9E, 0x9F, 0xC4, 0x44, 0xA0, 0xAD, 0x21, 0x46, 0xB3, 0x4C, 0xE0, 0x7C, 0x06, 0x25, 0xD3, 0x14, - 0x84, 0x4E, 0x18, 0xE0, 0x11, 0xD2, 0x9D, 0xA8, 0x30, 0x2C, 0xAB, 0x0E, 0xC9, 0xAD, 0x8F, 0xD1, - 0x76, 0xBB, 0xB4, 0x93, 0xFC, 0x94, 0xF6, 0xE7, 0xF0, 0x9B, 0x26, 0x66, 0x90, 0x37, 0x47, 0xAA, - 0x76, 0xCC, 0xA6, 0x5F, 0x84, 0x91, 0xFA, 0xB9, 0xD7, 0x53, 0xA5, 0x9C, 0xBF, 0x83, 0x1A, 0xC4, - 0x05, 0x1B, 0xB4, 0x44, 0xA9, 0x22, 0xD2, 0x78, 0x0A, 0xBA, 0xBD, 0x50, 0x1A, 0x10, 0x81, 0x22, - 0x87, 0x33, 0x22, 0x41, 0xC9, 0xEB, 0xBB, 0x2C, 0x27, 0x2A, 0xDF, 0x25, 0xD0, 0x46, 0x60, 0x5A, - 0xAA, 0x14, 0x4E, 0x79, 0xC3, 0x64, 0xE8, 0x58, 0x35, 0x3B, 0x12, 0x3A, 0x55, 0x5D, 0x61, 0x8C, - 0x37, 0x6D, 0xEB, 0x7B, 0xF4, 0xD3, 0xFE, 0xFD, 0xE2, 0xBC, 0x9C, 0x37, 0xD5, 0xF2, 0x4D, 0xA9, - 0x7D, 0x5F, 0xE6, 0x34, 0x96, 0xF9, 0xEA, 0xC4, 0x2C, 0x23, 0x1C, 0x18, 0x2D, 0xAC, 0x16, 0xBD, - 0x21, 0xB8, 0xE6, 0xAF, 0xBB, 0xBF, 0xED, 0xFE, 0x8E, 0x50, 0xE2, 0x6E, 0xEB, 0xE5, 0xDA, 0x58, - 0x57, 0x43, 0x01, 0xB1, 0x7C, 0xFE, 0xBD, 0x39, 0xEE, 0x83, 0x69, 0x4B, 0x2E, 0x44, 0xE1, 0xF3, - 0x2C, 0xAA, 0x8A, 0x92, 0xD1, 0xAF, 0x15, 0x64, 0xDD, 0x0A, 0xD7, 0xDC, 0x69, 0x5A, 0xDB, 0xE1, - 0x52, 0x04, 0xE1, 0xC8, 0x82, 0x2B, 0xC9, 0xBD, 0xD3, 0x76, 0x2F, 0x42, 0x37, 0x5B, 0x0A, 0x0E, - 0xFD, 0x56, 0x0E, 0x87, 0x7E, 0x7B, 0x31, 0x38, 0xF4, 0xDB, 0x9C, 0x38, 0xF4, 0xDB, 0x06, 0x87, - 0xB2, 0x70, 0xE8, 0xF7, 0x72, 0x38, 0xF4, 0xFB, 0x8B, 0xC1, 0xA1, 0xDF, 0xE7, 0xC4, 0xA1, 0xDF, - 0x37, 0x38, 0x94, 0xC0, 0x21, 0x13, 0xE4, 0x27, 0x94, 0xCE, 0x85, 0x9C, 0x5E, 0x06, 0x8B, 0xCA, - 0xD5, 0xBB, 0x7C, 0x52, 0x44, 0x2A, 0x0A, 0x32, 0x8D, 0xAE, 0xB9, 0x12, 0x22, 0x45, 0x5B, 0xBE, - 0x1C, 0x73, 0xE3, 0xF1, 0x59, 0x10, 0xFE, 0xF4, 0x05, 0x6F, 0x5C, 0x22, 0x52, 0xF4, 0x93, 0xD7, - 0x1E, 0xA2, 0xFA, 0x16, 0x68, 0x24, 0x18, 0xC6, 0xA4, 0x63, 0x69, 0xD5, 0xA0, 0xDC, 0xC6, 0x20, - 0x27, 0xFF, 0xE9, 0xC3, 0xBE, 0xC0, 0xF4, 0x55, 0xCB, 0x7E, 0xDC, 0xB5, 0x31, 0xD4, 0x52, 0x1A, - 0x40, 0x66, 0x24, 0xEA, 0xE7, 0x2D, 0x47, 0x97, 0xAF, 0xDF, 0xB3, 0x5C, 0xEE, 0x53, 0x45, 0x7D, - 0x4A, 0x92, 0x0D, 0x85, 0xEC, 0x65, 0x54, 0x38, 0x06, 0xE6, 0x22, 0xDF, 0x3C, 0x12, 0x3B, 0x25, - 0x41, 0x53, 0xCE, 0x45, 0x54, 0x8C, 0x19, 0x75, 0xA8, 0x1C, 0x08, 0x13, 0x50, 0x04, 0xA1, 0x05, - 0x9B, 0xC3, 0x39, 0x65, 0x7D, 0xE7, 0xEA, 0xDF, 0xC9, 0x3D, 0xAF, 0x34, 0x6E, 0x84, 0x01, 0x48, - 0xDF, 0x9A, 0x0F, 0xBA, 0xE6, 0x8D, 0x8F, 0x7A, 0xFB, 0xED, 0x72, 0xE1, 0x47, 0x59, 0x6E, 0x42, - 0xA8, 0xB9, 0x0A, 0xF3, 0xD4, 0x09, 0x6B, 0x76, 0xBA, 0x87, 0xED, 0x6E, 0xFB, 0xA0, 0xB5, 0x7F, - 0x70, 0xC8, 0x9A, 0x7B, 0xAF, 0x3B, 0x07, 0x87, 0xED, 0xBD, 0xD6, 0x5E, 0xBB, 0xC7, 0xF6, 0xDA, - 0x87, 0x07, 0x07, 0x07, 0xFB, 0xAD, 0xBD, 0xC3, 0xBD, 0x3A, 0xDC, 0x2C, 0x83, 0x21, 0x4F, 0x57, - 0x34, 0x64, 0x8F, 0x86, 0x3C, 0x5B, 0xD1, 0x68, 0x7B, 0x8D, 0xE3, 0x13, 0x3C, 0xB8, 0x6E, 0xC7, - 0x8A, 0x77, 0xE9, 0xDE, 0x5A, 0xD6, 0x95, 0x65, 0x8E, 0x6E, 0xAD, 0x53, 0x7E, 0x2E, 0x30, 0x10, - 0xF8, 0x48, 0xE6, 0x44, 0x2A, 0xD5, 0xF8, 0x9A, 0x7B, 0xD2, 0xE5, 0xBD, 0xBC, 0xD6, 0x4D, 0x06, - 0x99, 0xB9, 0x5F, 0xAA, 0xD1, 0x84, 0xA4, 0x68, 0x5A, 0x5C, 0x32, 0xA9, 0xD7, 0x6E, 0x24, 0xBB, - 0x47, 0x63, 0xD1, 0x89, 0xA6, 0x55, 0xB4, 0x0E, 0xE5, 0x04, 0x0F, 0x2A, 0x4B, 0x9D, 0x75, 0xD0, - 0x7F, 0x60, 0xE3, 0xAA, 0x6B, 0xDE, 0x1A, 0x20, 0xA0, 0xC7, 0x97, 0x39, 0xF3, 0xE9, 0x08, 0x38, - 0xF7, 0x73, 0xFA, 0x6B, 0x5D, 0x6C, 0x72, 0x80, 0x00, 0x20, 0x4B, 0xEC, 0x88, 0x42, 0xAA, 0x8A, - 0x46, 0xB9, 0x1D, 0x76, 0xC8, 0x04, 0x17, 0x56, 0xB2, 0x76, 0xB8, 0x0A, 0xA7, 0x45, 0x44, 0x92, - 0xA0, 0xB7, 0xC5, 0xA2, 0x98, 0xEE, 0xB5, 0x7E, 0x3C, 0xEF, 0xC7, 0x2A, 0xC9, 0x4F, 0xEA, 0xBF, - 0x5C, 0xF8, 0x80, 0x85, 0x0A, 0x72, 0xEE, 0x16, 0x3A, 0x4F, 0x7F, 0xB7, 0xF0, 0x81, 0xAA, 0x19, - 0xC8, 0x3B, 0xB6, 0x7A, 0xAF, 0x14, 0x46, 0xB2, 0xD7, 0x27, 0x73, 0x79, 0x79, 0xB2, 0x5B, 0x85, - 0x0F, 0xE1, 0xCA, 0xA7, 0x37, 0x0B, 0xA5, 0x29, 0xAF, 0x0C, 0x67, 0x8B, 0xDD, 0x2C, 0xA4, 0x8C, - 0x16, 0xDE, 0x2E, 0x5C, 0x5D, 0x8D, 0x4B, 0x0F, 0xBC, 0xF6, 0x97, 0x10, 0xB0, 0x98, 0xE4, 0xBD, - 0x03, 0x3E, 0xDA, 0xDC, 0x3A, 0xD4, 0xE1, 0xCD, 0x43, 0xF0, 0xBC, 0x52, 0xBC, 0xE2, 0x94, 0x25, - 0x99, 0xEE, 0x3B, 0x7B, 0x89, 0xAC, 0x72, 0x7B, 0x33, 0xA4, 0x05, 0xFD, 0xEB, 0x9E, 0xAF, 0xF1, - 0x1A, 0xFC, 0xBF, 0x0E, 0x96, 0x60, 0x78, 0x89, 0xC1, 0xA0, 0xBA, 0x05, 0x4F, 0xB6, 0x7C, 0x69, - 0x7E, 0x5E, 0x62, 0x6D, 0xA8, 0x8F, 0x2C, 0x15, 0x35, 0x60, 0x80, 0xF5, 0xC7, 0x8D, 0x00, 0x0A, - 0x73, 0x20, 0x87, 0x6C, 0xFA, 0x62, 0x2C, 0xBC, 0x42, 0x22, 0xF7, 0x48, 0xF2, 0x7C, 0x8F, 0x95, - 0x5B, 0xBD, 0x54, 0xFB, 0x6C, 0x35, 0x9B, 0x6F, 0x56, 0xFA, 0x38, 0x90, 0xC5, 0xC5, 0x48, 0x4C, - 0x0C, 0x75, 0x54, 0x31, 0x8C, 0x35, 0xB0, 0xF4, 0xC4, 0x27, 0x9C, 0x6F, 0xEB, 0x79, 0xAE, 0xF7, - 0xC7, 0x95, 0x1B, 0xA7, 0x12, 0xFC, 0x89, 0x21, 0x58, 0x75, 0xA3, 0x98, 0x98, 0x67, 0x73, 0x40, - 0x7E, 0x3C, 0xB9, 0x28, 0x7D, 0x1A, 0x5F, 0x2B, 0xCE, 0xD7, 0x5D, 0x18, 0x0D, 0x7D, 0x2E, 0xE7, - 0x3B, 0xC2, 0xEB, 0x91, 0x3A, 0x22, 0x92, 0xC7, 0x47, 0x8E, 0x4D, 0xD9, 0xC9, 0x9D, 0x75, 0xCF, - 0xD9, 0x85, 0x61, 0xE8, 0xB6, 0x6B, 0xE9, 0x5A, 0x58, 0x24, 0x1E, 0x26, 0x1C, 0xD4, 0x92, 0x97, - 0x02, 0x4A, 0x28, 0x79, 0xA1, 0xBE, 0xA5, 0x48, 0xC8, 0x45, 0xDF, 0xC7, 0xE7, 0x13, 0xCB, 0xF4, - 0xA9, 0x28, 0xB2, 0x0C, 0x19, 0xC0, 0x2D, 0x32, 0xB5, 0x2A, 0x82, 0xCB, 0xD2, 0x85, 0x97, 0x2A, - 0x02, 0x4C, 0x69, 0x93, 0x6F, 0x21, 0xBB, 0xDE, 0xAB, 0x22, 0xBB, 0x55, 0xBF, 0x90, 0x0B, 0x31, - 0xB9, 0xEC, 0x9A, 0xCA, 0xDD, 0x08, 0x95, 0x7C, 0xCD, 0x9E, 0x9D, 0x48, 0x0D, 0xC9, 0x3E, 0x97, - 0xC8, 0x49, 0x08, 0xBF, 0xD2, 0x79, 0x22, 0x93, 0x59, 0xAC, 0x0C, 0x3E, 0xF4, 0x8E, 0xF6, 0x2A, - 0x58, 0x91, 0xD3, 0xF4, 0x66, 0xA1, 0x17, 0x23, 0x71, 0x08, 0xE7, 0xDC, 0x88, 0x33, 0x6D, 0xC4, - 0xFE, 0x85, 0xE5, 0xD5, 0x81, 0x9B, 0xA0, 0x42, 0x53, 0x85, 0x8F, 0x51, 0x20, 0x98, 0x62, 0xA8, - 0xD0, 0x14, 0xF9, 0x0B, 0xA6, 0xBB, 0x16, 0x15, 0x25, 0x39, 0x83, 0x47, 0xC4, 0x73, 0xE6, 0x38, - 0x41, 0x96, 0xA1, 0xE5, 0x5C, 0x0E, 0x83, 0x92, 0x96, 0x3B, 0xE1, 0xD4, 0x44, 0xDD, 0x50, 0x39, - 0x61, 0x8A, 0x7D, 0xFA, 0x67, 0xD7, 0xBF, 0xF3, 0x1C, 0x85, 0x6A, 0x72, 0xFE, 0xB3, 0xA8, 0x26, - 0x2F, 0x3D, 0x66, 0x4F, 0xE0, 0xB7, 0x69, 0x2A, 0x4C, 0xF2, 0x2C, 0x0C, 0xBB, 0x39, 0xB9, 0xE9, - 0x4F, 0x33, 0x7F, 0x86, 0xDC, 0x28, 0xCC, 0xB6, 0x07, 0xDD, 0x51, 0x39, 0x1B, 0xAC, 0x45, 0x1D, - 0xB8, 0xCD, 0x4E, 0xF9, 0xD9, 0x18, 0x7A, 0x78, 0x64, 0x0F, 0x63, 0x6E, 0xCA, 0x98, 0x26, 0x51, - 0x05, 0x94, 0x98, 0x18, 0xF9, 0xB6, 0x2A, 0xF2, 0xEE, 0xCA, 0xB6, 0x80, 0xDF, 0x8C, 0x63, 0xA3, - 0xEE, 0x50, 0x7C, 0x91, 0x6F, 0x8A, 0x17, 0xA8, 0x49, 0x58, 0x0C, 0xBB, 0xC5, 0x3E, 0x59, 0x1E, - 0x3F, 0xA2, 0x38, 0xAD, 0x70, 0x9D, 0xB4, 0xE5, 0x14, 0x6F, 0x64, 0x3C, 0x28, 0x8F, 0xAE, 0x34, - 0x60, 0xC9, 0x7A, 0xA5, 0x63, 0x8C, 0x34, 0xCC, 0xB8, 0xF8, 0x63, 0x54, 0x93, 0xFE, 0x19, 0x2A, - 0x7D, 0x65, 0x52, 0x97, 0x96, 0x8B, 0xE7, 0x7D, 0xB6, 0x04, 0xDE, 0xCD, 0x20, 0x70, 0x56, 0x2A, - 0xA7, 0x6E, 0x3A, 0xA1, 0x1B, 0xD0, 0xC5, 0x49, 0xFF, 0x2C, 0x49, 0xE7, 0xF0, 0x68, 0x4D, 0xC9, - 0x1C, 0x66, 0x96, 0x42, 0xE5, 0x8A, 0xA6, 0x95, 0x25, 0x70, 0x49, 0x21, 0x65, 0xC9, 0x7B, 0x43, - 0x7C, 0x55, 0x88, 0x6F, 0x85, 0x8A, 0xB5, 0x44, 0xDC, 0x62, 0x29, 0x7B, 0x3F, 0x4D, 0xCA, 0x46, - 0x3C, 0x2A, 0x2D, 0x38, 0x2F, 0x0F, 0xBB, 0xF3, 0x05, 0xE6, 0x00, 0x83, 0xFB, 0x63, 0x8A, 0x1E, - 0x27, 0xC4, 0x8E, 0x0B, 0xD0, 0xAE, 0x3F, 0x09, 0x5E, 0x56, 0xE4, 0xCB, 0xE2, 0x64, 0xD9, 0x61, - 0x36, 0x35, 0x52, 0xA9, 0x91, 0xC8, 0xEE, 0x4A, 0xE7, 0x8A, 0xF8, 0x9A, 0xF0, 0x1A, 0xDD, 0x2C, - 0x1C, 0xC6, 0x35, 0x9D, 0xD0, 0x72, 0x87, 0xB9, 0x63, 0x3C, 0x7D, 0x30, 0xF3, 0x03, 0x22, 0x19, - 0xEA, 0x79, 0x14, 0x80, 0x02, 0x98, 0xFC, 0x32, 0x5D, 0x2A, 0x72, 0x65, 0xEB, 0xFD, 0xE5, 0x39, - 0xBA, 0x05, 0x98, 0x1B, 0xA4, 0x02, 0xAE, 0x6E, 0x13, 0x11, 0x3D, 0xAC, 0xCC, 0x22, 0x42, 0xD7, - 0x1A, 0xAB, 0x21, 0xED, 0xC0, 0xA3, 0x24, 0xB0, 0xCD, 0x37, 0xE6, 0x31, 0x89, 0x91, 0x27, 0xDB, - 0x13, 0x53, 0xF6, 0x07, 0x38, 0x3E, 0x44, 0xA8, 0x15, 0x05, 0x8B, 0x83, 0xF4, 0x17, 0x2C, 0x8D, - 0xDD, 0xF1, 0x21, 0x66, 0x96, 0x16, 0x67, 0x16, 0xC6, 0x72, 0xC9, 0x73, 0x23, 0x4C, 0x56, 0x4D, - 0xEE, 0x4F, 0x51, 0x17, 0x27, 0x74, 0x95, 0xFA, 0x01, 0x69, 0xF0, 0x60, 0x89, 0x3E, 0x82, 0x21, - 0x82, 0xCD, 0xE5, 0x27, 0x18, 0xB4, 0xDE, 0xF8, 0x0A, 0x2E, 0x9D, 0x90, 0xEA, 0xF7, 0x15, 0xBC, - 0x52, 0xBC, 0x5D, 0xB4, 0x2A, 0xA3, 0xF1, 0x6C, 0xE3, 0x32, 0xF8, 0x44, 0x2E, 0x83, 0x01, 0x05, - 0x95, 0x74, 0x1B, 0x2C, 0xBD, 0x3D, 0xD1, 0xCD, 0xAD, 0xDD, 0xCB, 0x70, 0x7A, 0x2A, 0xA5, 0x7B, - 0x1A, 0x4E, 0xBF, 0x5F, 0x33, 0x6F, 0xC3, 0xBD, 0x76, 0xAB, 0xFD, 0xA6, 0xDD, 0x7D, 0xB3, 0xF7, - 0xFA, 0x0D, 0x6B, 0x76, 0xDA, 0xFB, 0xAD, 0xCE, 0xE1, 0x7E, 0x7B, 0xFF, 0xF5, 0x41, 0x87, 0x75, - 0xF6, 0x0F, 0xE0, 0xBB, 0xC3, 0x37, 0xF5, 0xBA, 0x1A, 0xAE, 0x60, 0xBC, 0xC0, 0xCF, 0x70, 0x05, - 0x43, 0x95, 0x70, 0x32, 0xCC, 0x9C, 0x45, 0x25, 0x1B, 0xF5, 0x7C, 0x33, 0xDE, 0x78, 0x18, 0x66, - 0x78, 0x18, 0xCE, 0x0A, 0x91, 0xB5, 0x7B, 0x19, 0x06, 0x43, 0x2C, 0xC1, 0xD3, 0x70, 0xD9, 0xB3, - 0x8F, 0x8E, 0xB1, 0x1C, 0x8F, 0xC3, 0x65, 0xAF, 0x20, 0x3E, 0xCA, 0xB3, 0xF7, 0x3C, 0x8C, 0x1E, - 0x5D, 0x3F, 0x8C, 0x03, 0xE2, 0xF3, 0xAC, 0xBA, 0x8E, 0x85, 0xEF, 0xB1, 0xEA, 0x6A, 0xAC, 0xEA, - 0xBA, 0x78, 0xB8, 0xB2, 0xAA, 0xEB, 0xC1, 0x1C, 0x9E, 0xA6, 0xEA, 0xBA, 0x4B, 0xA3, 0xA3, 0xE0, - 0xED, 0xDB, 0x84, 0xDC, 0x41, 0x96, 0x92, 0x57, 0xEE, 0x4C, 0xF9, 0x75, 0xCA, 0x99, 0x36, 0x53, - 0x7C, 0xFD, 0x33, 0xA5, 0x2B, 0x11, 0xD5, 0xD7, 0x85, 0x34, 0xEF, 0xE1, 0xE5, 0x89, 0xA2, 0x62, - 0xF2, 0x0F, 0xA9, 0x10, 0xC7, 0xFB, 0xF1, 0x45, 0x4E, 0x10, 0x33, 0x56, 0xF8, 0xFD, 0xE5, 0x54, - 0x64, 0x17, 0x1B, 0x3A, 0x6F, 0x32, 0x89, 0xB7, 0x25, 0x2A, 0xC5, 0xA4, 0xE6, 0xE7, 0x2C, 0x59, - 0x34, 0x2D, 0xCE, 0xE5, 0x0B, 0x39, 0x40, 0xA9, 0xC2, 0x69, 0x33, 0xCB, 0x6E, 0x67, 0xA4, 0xEA, - 0x2C, 0x1C, 0x2E, 0x91, 0xCA, 0x33, 0xA5, 0xE3, 0x62, 0xA6, 0x15, 0xA5, 0x62, 0xD6, 0xC1, 0xFC, - 0x9C, 0x35, 0x54, 0x4A, 0x13, 0x28, 0x50, 0x58, 0x2D, 0xAD, 0x82, 0xBB, 0x75, 0x3C, 0x6B, 0x67, - 0xE5, 0x95, 0xD6, 0x5F, 0xDC, 0xAA, 0x76, 0x3F, 0x9A, 0xE8, 0x9A, 0xC2, 0xAA, 0xF6, 0x7F, 0xB4, - 0xAB, 0xBB, 0x02, 0xD7, 0x5F, 0x10, 0x3C, 0xF8, 0x87, 0x33, 0x3A, 0x5A, 0xD4, 0x8B, 0xE3, 0xB0, - 0x92, 0x95, 0xAB, 0xB2, 0xA5, 0x2B, 0x03, 0x8A, 0x15, 0x46, 0xB3, 0xF3, 0x3A, 0x5A, 0xD4, 0xF6, - 0x55, 0xDD, 0x67, 0xE4, 0x89, 0xB1, 0xB0, 0x6F, 0x39, 0x6B, 0x86, 0x85, 0x38, 0xA3, 0xA7, 0xC6, - 0xC2, 0xC2, 0x3B, 0x8F, 0x2C, 0x38, 0x2E, 0x8E, 0x87, 0xA2, 0xA3, 0x1F, 0x0E, 0x0F, 0xB1, 0xE4, - 0xFA, 0x7A, 0xE1, 0x21, 0xCE, 0xE8, 0xB9, 0x71, 0x43, 0x09, 0xC5, 0xC5, 0xB1, 0x50, 0x74, 0xF4, - 0x43, 0x62, 0x61, 0xFF, 0x97, 0x9A, 0xF0, 0x30, 0xAB, 0x26, 0xFA, 0x1C, 0x78, 0x58, 0xBA, 0xF6, - 0x79, 0x0E, 0x2A, 0xBE, 0xAE, 0x74, 0x05, 0x5C, 0x0B, 0x2A, 0x12, 0x28, 0xEB, 0x41, 0x46, 0xEC, - 0xEA, 0x07, 0x42, 0xC7, 0x69, 0x39, 0xF8, 0x25, 0x33, 0x45, 0x1A, 0xA8, 0xC2, 0xD9, 0x0C, 0x33, - 0x7A, 0x3E, 0x4C, 0x31, 0x06, 0xC5, 0x45, 0xF0, 0x30, 0xDA, 0xD1, 0x0F, 0x89, 0x85, 0x4B, 0x67, - 0x8A, 0x73, 0xE0, 0xE1, 0xB3, 0x62, 0x8A, 0x09, 0x50, 0xD6, 0x83, 0x8C, 0x4F, 0xC3, 0x14, 0x17, - 0x78, 0xA5, 0x8C, 0x49, 0xE0, 0xD9, 0xDB, 0x7B, 0x3A, 0xCB, 0xB2, 0xF7, 0x74, 0xAA, 0xDA, 0x7B, - 0xBA, 0xCF, 0xD5, 0xDE, 0xD3, 0x79, 0xA9, 0xF6, 0x9E, 0xCE, 0xC6, 0xDE, 0x53, 0x83, 0xBD, 0xA7, - 0x53, 0x97, 0xBD, 0xA7, 0xF3, 0x63, 0xDA, 0x7B, 0x3A, 0x1B, 0x7B, 0x4F, 0x2D, 0xF6, 0x9E, 0x4E, - 0x5D, 0xF6, 0x9E, 0xCE, 0x8F, 0x69, 0xEF, 0xE9, 0x6C, 0xEC, 0x3D, 0x35, 0xD8, 0x7B, 0x3A, 0x75, - 0xD9, 0x7B, 0x3A, 0x3F, 0xAA, 0xBD, 0xA7, 0xB3, 0xB1, 0xF7, 0xD4, 0x65, 0xEF, 0xE9, 0xD4, 0x67, - 0xEF, 0xE9, 0xFC, 0x98, 0xF6, 0x9E, 0xCE, 0xC6, 0xDE, 0x53, 0x83, 0xBD, 0xA7, 0x53, 0x97, 0xBD, - 0xA7, 0xF3, 0xA3, 0xDA, 0x7B, 0x3A, 0x1B, 0x7B, 0x4F, 0x5D, 0xF6, 0x9E, 0x4E, 0x7D, 0xF6, 0x9E, - 0xCE, 0xC6, 0xDE, 0xB3, 0x6E, 0xF6, 0x9E, 0xEE, 0xB2, 0xEC, 0x3D, 0xDD, 0xAA, 0xF6, 0x9E, 0xDE, - 0x73, 0xB5, 0xF7, 0x74, 0x5F, 0xAA, 0xBD, 0xA7, 0xBB, 0xB1, 0xF7, 0xD4, 0x60, 0xEF, 0xE9, 0xD6, - 0x65, 0xEF, 0xE9, 0xFE, 0x98, 0xF6, 0x9E, 0xEE, 0xC6, 0xDE, 0x53, 0x8B, 0xBD, 0xA7, 0x5B, 0x97, - 0xBD, 0xA7, 0xFB, 0x63, 0xDA, 0x7B, 0xBA, 0x1B, 0x7B, 0x4F, 0x0D, 0xF6, 0x9E, 0x6E, 0x5D, 0xF6, - 0x9E, 0xEE, 0x8F, 0x6A, 0xEF, 0xE9, 0x6E, 0xEC, 0x3D, 0x75, 0xD9, 0x7B, 0xBA, 0xF5, 0xD9, 0x7B, - 0xBA, 0x3F, 0xA6, 0xBD, 0xA7, 0xBB, 0xB1, 0xF7, 0xD4, 0x60, 0xEF, 0xE9, 0xD6, 0x65, 0xEF, 0xE9, - 0xFE, 0xA8, 0xF6, 0x9E, 0xEE, 0xC6, 0xDE, 0x53, 0x97, 0xBD, 0xA7, 0x5B, 0x9F, 0xBD, 0xA7, 0xBB, - 0xB1, 0xF7, 0xAC, 0x9B, 0xBD, 0xA7, 0xB7, 0x2C, 0x7B, 0x4F, 0xAF, 0xAA, 0xBD, 0x67, 0xEF, 0xB9, - 0xDA, 0x7B, 0x7A, 0x2F, 0xD5, 0xDE, 0xD3, 0xDB, 0xD8, 0x7B, 0x6A, 0xB0, 0xF7, 0xF4, 0xEA, 0xB2, - 0xF7, 0xF4, 0x7E, 0x4C, 0x7B, 0x4F, 0x6F, 0x63, 0xEF, 0xA9, 0xC5, 0xDE, 0xD3, 0xAB, 0xCB, 0xDE, - 0xD3, 0xFB, 0x31, 0xED, 0x3D, 0xBD, 0x8D, 0xBD, 0xA7, 0x06, 0x7B, 0x4F, 0xAF, 0x2E, 0x7B, 0x4F, - 0xEF, 0x47, 0xB5, 0xF7, 0xF4, 0x36, 0xF6, 0x9E, 0xBA, 0xEC, 0x3D, 0xBD, 0xFA, 0xEC, 0x3D, 0xBD, - 0x1F, 0xD3, 0xDE, 0xD3, 0xDB, 0xD8, 0x7B, 0x6A, 0xB0, 0xF7, 0xF4, 0xEA, 0xB2, 0xF7, 0xF4, 0x7E, - 0x54, 0x7B, 0x4F, 0x6F, 0x63, 0xEF, 0xA9, 0xCB, 0xDE, 0xD3, 0xAB, 0xCF, 0xDE, 0xD3, 0x7B, 0x31, - 0xF6, 0x9E, 0xA2, 0x1C, 0x41, 0x39, 0xBD, 0x4A, 0x1B, 0x50, 0x98, 0x13, 0x6D, 0xC4, 0xBD, 0x6B, - 0xEE, 0xBA, 0xCA, 0x88, 0x5F, 0xE9, 0xAE, 0x87, 0x75, 0x2B, 0xA9, 0x42, 0xC0, 0x5C, 0x26, 0xA2, - 0xFC, 0xB4, 0x50, 0xB1, 0xA4, 0x6F, 0xA5, 0xCC, 0x43, 0xC1, 0x73, 0x4C, 0xB1, 0x2F, 0x8C, 0x1B, - 0xD7, 0xEE, 0x08, 0xA7, 0x98, 0x3F, 0x52, 0xAA, 0x05, 0x29, 0x69, 0x21, 0xCA, 0xEE, 0x3B, 0x7F, - 0xD7, 0x6E, 0x6E, 0xCF, 0xAE, 0xD9, 0x0D, 0x25, 0x87, 0x7D, 0xAB, 0x13, 0xBA, 0x61, 0x12, 0x2B, - 0x68, 0x7B, 0x86, 0x16, 0xA1, 0x46, 0x1D, 0x26, 0xA3, 0x12, 0xE6, 0xA2, 0xD2, 0x99, 0xBA, 0x68, - 0x86, 0x8E, 0xA7, 0x4E, 0x70, 0xCA, 0x97, 0xE6, 0xD0, 0x2A, 0xAE, 0x2D, 0x27, 0xF3, 0x77, 0xD1, - 0x42, 0x31, 0x6D, 0xBC, 0xA3, 0x98, 0xEE, 0x44, 0xF7, 0x22, 0x25, 0x0F, 0x70, 0xC5, 0x4C, 0xC1, - 0x1C, 0xD5, 0x9A, 0x48, 0xA5, 0x85, 0xA9, 0x76, 0x3B, 0x1F, 0xBF, 0x53, 0x2E, 0xF8, 0x89, 0xC0, - 0x26, 0x97, 0x75, 0xDA, 0xED, 0xFD, 0x1D, 0xF8, 0xF9, 0x7A, 0x0F, 0x7F, 0x1E, 0xD2, 0xCF, 0x37, - 0xF8, 0xB3, 0xD3, 0xDD, 0x13, 0x09, 0xE6, 0xDB, 0xAD, 0xA0, 0x51, 0xA7, 0xDB, 0x6B, 0xCB, 0x4C, - 0xF5, 0x32, 0x4D, 0xAF, 0x61, 0x3D, 0x50, 0x55, 0x06, 0xFC, 0x96, 0xEA, 0x5C, 0xB8, 0xA2, 0x2A, - 0x0A, 0x3C, 0x87, 0xF1, 0x4D, 0x8D, 0x92, 0x9E, 0x62, 0xD2, 0x3B, 0x4F, 0xD1, 0x0D, 0xCB, 0x91, - 0xD5, 0x14, 0xE4, 0x5C, 0xA1, 0xF7, 0xC7, 0x5D, 0xC5, 0x30, 0xC4, 0x6E, 0x05, 0x33, 0x6A, 0xB1, - 0x2B, 0x1D, 0xBE, 0x74, 0x8F, 0x58, 0x1B, 0x5F, 0xEE, 0xB6, 0xA3, 0xD5, 0x1A, 0x44, 0x0D, 0x16, - 0x82, 0x1D, 0x8C, 0xE7, 0x51, 0xBD, 0x06, 0x0B, 0x38, 0x88, 0xA3, 0x6B, 0x1A, 0x37, 0xF1, 0x7D, - 0x9C, 0x2B, 0x95, 0x6B, 0xD1, 0x4D, 0x86, 0x88, 0xC2, 0x26, 0x96, 0xC6, 0x9F, 0x57, 0xEA, 0xB0, - 0x54, 0x0B, 0xE2, 0x3C, 0x94, 0x50, 0xBF, 0x11, 0x91, 0x7A, 0x8C, 0x37, 0xBD, 0xB3, 0x80, 0x0A, - 0x26, 0x41, 0xEB, 0x42, 0x8E, 0x9B, 0x9A, 0x5C, 0x32, 0x3D, 0x91, 0x64, 0xA4, 0x30, 0x8A, 0xC3, - 0x5D, 0xEE, 0xDD, 0x5A, 0x88, 0x28, 0x32, 0x2F, 0x1C, 0x15, 0x75, 0xBD, 0xC1, 0xC7, 0xB0, 0xEB, - 0xA5, 0xCE, 0x84, 0xA0, 0x61, 0xB9, 0x3C, 0x92, 0x65, 0xEB, 0xCE, 0x3C, 0x2D, 0x30, 0xAE, 0xAC, - 0x87, 0xD3, 0x80, 0xCC, 0x42, 0x80, 0x94, 0x82, 0x06, 0x66, 0xA6, 0x04, 0x22, 0x0D, 0x5B, 0x03, - 0xD5, 0x99, 0x5F, 0x2B, 0x81, 0xA6, 0x18, 0x36, 0x88, 0xB7, 0x93, 0xF8, 0xB1, 0xD5, 0x58, 0x76, - 0xFA, 0xC8, 0x12, 0x8F, 0xD3, 0x06, 0x79, 0xFB, 0x97, 0x66, 0x93, 0x35, 0x83, 0x7F, 0x81, 0x38, - 0xC6, 0x9D, 0x21, 0x26, 0x8E, 0x16, 0xF4, 0x16, 0xF9, 0xBA, 0xD9, 0x4C, 0xF4, 0x18, 0x21, 0x34, - 0x0D, 0x64, 0x55, 0x5D, 0x63, 0x23, 0xC5, 0x4E, 0xAD, 0xE7, 0x9C, 0x71, 0xBD, 0x93, 0x79, 0x9F, - 0x83, 0x30, 0xB4, 0xED, 0x20, 0x7D, 0x60, 0x99, 0xE3, 0xF9, 0xA7, 0xEC, 0x8B, 0x9E, 0xE4, 0x91, - 0xDD, 0xEF, 0x07, 0x1D, 0x57, 0x3A, 0x8D, 0xC3, 0x66, 0xA9, 0x43, 0xC5, 0xB3, 0x7E, 0x67, 0x6C, - 0xB8, 0x90, 0xF6, 0x62, 0xF0, 0xF5, 0x1D, 0x51, 0x9A, 0x44, 0x1E, 0xD8, 0x76, 0xE4, 0x8D, 0xC5, - 0x4F, 0xED, 0x74, 0xCC, 0x4E, 0x43, 0x8E, 0x42, 0x0E, 0x1C, 0xAE, 0x3E, 0x65, 0x94, 0x6C, 0x86, - 0x3B, 0x97, 0x1C, 0x78, 0x4E, 0x69, 0x34, 0xD9, 0xE5, 0xF9, 0x51, 0x20, 0x35, 0x8F, 0xA1, 0xC3, - 0x07, 0x58, 0xEC, 0xE5, 0x79, 0x4E, 0x7A, 0xF5, 0xDD, 0x13, 0x14, 0x94, 0x73, 0xF3, 0xA3, 0x9F, - 0x63, 0x89, 0x23, 0x90, 0xBA, 0x75, 0x83, 0x7D, 0xE5, 0xF0, 0x11, 0xB6, 0x5E, 0x77, 0x78, 0x38, - 0x8C, 0x06, 0x5F, 0xDF, 0xF0, 0x89, 0xA2, 0x9B, 0xBA, 0x39, 0xCA, 0x19, 0xC9, 0x62, 0x7F, 0x85, - 0xD6, 0x99, 0x62, 0xF9, 0x93, 0xA6, 0x74, 0x8D, 0xA2, 0xD8, 0x59, 0x98, 0x73, 0xD4, 0x0D, 0xD3, - 0xBB, 0x46, 0xBF, 0xCF, 0x65, 0x4A, 0x91, 0xC6, 0x6C, 0x15, 0x79, 0x60, 0x33, 0x27, 0xBE, 0xC2, - 0x9C, 0xB0, 0x57, 0x4D, 0x3C, 0x19, 0xD8, 0x2E, 0xBB, 0xEC, 0xC7, 0x69, 0x55, 0x8D, 0x00, 0x83, - 0xE4, 0x2C, 0xE5, 0x1E, 0x44, 0x3B, 0xAA, 0x80, 0x35, 0x9B, 0xB2, 0xF5, 0xD9, 0x65, 0x6C, 0x1D, - 0x71, 0x6B, 0xE4, 0x28, 0xF6, 0x58, 0x57, 0x6F, 0xF8, 0x08, 0xD6, 0x78, 0xEE, 0x58, 0xB6, 0xE0, - 0x2A, 0x45, 0x18, 0x49, 0x88, 0x97, 0x6C, 0xDE, 0x38, 0xFE, 0x10, 0x3E, 0x61, 0xE2, 0x51, 0xB1, - 0x82, 0x1F, 0x2F, 0x57, 0x30, 0xD3, 0x65, 0xEA, 0x3C, 0xE3, 0x2A, 0xBB, 0x26, 0x67, 0x1D, 0x64, - 0x97, 0xCD, 0xDD, 0x83, 0x78, 0x52, 0xFC, 0x76, 0xE3, 0xF8, 0xCB, 0xA0, 0x5C, 0xA2, 0xFA, 0xD9, - 0xCA, 0x04, 0x17, 0x5F, 0xE6, 0x6A, 0x09, 0x53, 0x3C, 0x99, 0xAF, 0x25, 0x70, 0x8A, 0xBF, 0xDE, - 0xCC, 0xD5, 0x72, 0xAF, 0x71, 0xFC, 0xDF, 0x15, 0xC0, 0x95, 0xE2, 0xC6, 0xC5, 0x79, 0xF8, 0xEB, - 0xA5, 0xBE, 0x81, 0xD8, 0xFD, 0x47, 0xCB, 0x77, 0xD8, 0x74, 0x9B, 0x99, 0x43, 0xFB, 0x2C, 0xB5, - 0x2F, 0x50, 0xE8, 0x80, 0x01, 0x8B, 0x74, 0xCA, 0x92, 0x52, 0x87, 0x0E, 0xFF, 0xD3, 0xE7, 0xA6, - 0xFA, 0x48, 0x3A, 0x9B, 0xC0, 0x0A, 0xC5, 0x88, 0xF0, 0x2E, 0x90, 0x2A, 0x6C, 0x5D, 0x8D, 0x10, - 0xE9, 0x97, 0xC1, 0x3A, 0x91, 0xA7, 0x90, 0x8D, 0x9C, 0x05, 0x68, 0xD7, 0xB6, 0x07, 0xDC, 0xC3, - 0x64, 0xD4, 0x6E, 0xE6, 0xF9, 0xBC, 0xD8, 0xD9, 0x53, 0xFA, 0xFC, 0x51, 0x7C, 0xCF, 0x82, 0x73, - 0xF1, 0x86, 0x9B, 0xFC, 0x41, 0x31, 0x00, 0xBB, 0xE1, 0x6F, 0x3C, 0x28, 0x99, 0x7C, 0x52, 0xB2, - 0xD0, 0xCB, 0x22, 0x27, 0x49, 0x72, 0x0A, 0xE5, 0x37, 0x7A, 0xB1, 0x5C, 0xF9, 0x12, 0x89, 0x7F, - 0xC1, 0x93, 0x81, 0x04, 0x0B, 0xE4, 0x45, 0x0C, 0xD6, 0x0A, 0xBA, 0x46, 0xF7, 0x90, 0xA1, 0x5C, - 0x81, 0xAA, 0x07, 0x89, 0x1B, 0x8F, 0x3B, 0x0C, 0xA7, 0x89, 0x15, 0xE9, 0xB0, 0x66, 0x25, 0x60, - 0xAE, 0xE7, 0xF1, 0x89, 0x4D, 0x95, 0xB3, 0x00, 0xDB, 0x4C, 0x24, 0x03, 0xF8, 0xF8, 0xEA, 0xA3, - 0x35, 0xE1, 0xAF, 0x98, 0xC9, 0xBD, 0x07, 0xCB, 0xF9, 0xBA, 0x03, 0x7A, 0x10, 0x88, 0x44, 0x0A, - 0x76, 0xB5, 0x83, 0x5F, 0x3B, 0xB8, 0x44, 0x1A, 0x2B, 0x82, 0xDB, 0xE2, 0x88, 0xD7, 0x4A, 0xD7, - 0xFF, 0xAC, 0x39, 0xB1, 0x7E, 0x31, 0x21, 0x94, 0x77, 0x24, 0xAA, 0x64, 0xFE, 0x8E, 0x1E, 0x46, - 0x51, 0x01, 0x5A, 0x08, 0x92, 0x7D, 0xC7, 0x1A, 0xEA, 0x06, 0xBF, 0xB5, 0xBE, 0xF2, 0x12, 0xBE, - 0x61, 0x85, 0x77, 0x2F, 0xB3, 0x25, 0xDA, 0xA4, 0xBC, 0x2A, 0xC7, 0x61, 0x34, 0xD0, 0x51, 0xB5, - 0x2D, 0x58, 0xBC, 0x54, 0xC3, 0xB4, 0xC4, 0x28, 0xF1, 0x50, 0xCB, 0x19, 0x29, 0xA6, 0xFE, 0x5D, - 0xE8, 0x16, 0x63, 0xC5, 0xC5, 0xA4, 0xF5, 0x96, 0x63, 0x5B, 0x64, 0x3E, 0x02, 0x6E, 0x6A, 0x8E, - 0x06, 0x9E, 0xC3, 0x95, 0x09, 0xF1, 0xCD, 0x98, 0xA8, 0xA3, 0xA8, 0x2A, 0x1A, 0xA6, 0x77, 0x44, - 0x82, 0x7C, 0x9D, 0xE4, 0x1D, 0x87, 0x63, 0xAF, 0x0C, 0x09, 0x93, 0x7A, 0x57, 0x7D, 0x17, 0x30, - 0x58, 0xE6, 0xBB, 0x67, 0xB6, 0x5C, 0xB8, 0x87, 0x0B, 0x8F, 0xA2, 0x23, 0x60, 0xF5, 0x23, 0x6B, - 0x32, 0x91, 0x6D, 0x9F, 0xB3, 0x81, 0xAD, 0x38, 0x5F, 0xDF, 0xFB, 0xA6, 0x80, 0x50, 0x63, 0x8E, - 0x8A, 0x16, 0x75, 0x96, 0x80, 0x28, 0x87, 0xD1, 0x65, 0x38, 0x56, 0xD6, 0xCD, 0xD8, 0xEB, 0x32, - 0x84, 0x58, 0xE1, 0x1A, 0xA2, 0x00, 0xB5, 0x4B, 0xD7, 0x21, 0xCF, 0xEF, 0xA7, 0x86, 0xC2, 0xE4, - 0xB5, 0x31, 0x80, 0x7A, 0x0F, 0x28, 0x92, 0x15, 0x5C, 0x0F, 0x0E, 0x88, 0x2F, 0x36, 0x10, 0x1B, - 0x47, 0xC3, 0x11, 0x3D, 0xA1, 0x53, 0x4A, 0x3C, 0x5B, 0xC1, 0x21, 0x35, 0x3B, 0x8D, 0xA7, 0x38, - 0xA6, 0x64, 0x9D, 0x65, 0x51, 0xC2, 0x25, 0xC6, 0x00, 0xE8, 0x00, 0x7B, 0xD0, 0x0D, 0x03, 0x4D, - 0xDD, 0x72, 0xB2, 0x5C, 0x23, 0x46, 0xE1, 0xD3, 0x84, 0x35, 0xB4, 0xAF, 0x63, 0x33, 0x13, 0xF0, - 0x95, 0x59, 0x36, 0xD6, 0x1C, 0xF6, 0x4D, 0xDD, 0x7B, 0xAC, 0xAE, 0x0B, 0xAD, 0xF7, 0x59, 0x84, - 0xBB, 0xE5, 0xBB, 0xFC, 0xC4, 0x75, 0x75, 0xD7, 0xFB, 0x64, 0x3D, 0x9C, 0x05, 0x1B, 0x5A, 0xB1, - 0x32, 0x77, 0x69, 0x34, 0x2E, 0x8D, 0xCA, 0xD1, 0x59, 0x81, 0xA4, 0x15, 0x7C, 0xAC, 0x50, 0xA7, - 0x72, 0x11, 0x0C, 0x8E, 0x8F, 0xFE, 0x44, 0x27, 0x1D, 0x61, 0x71, 0x58, 0x4E, 0x7B, 0x0A, 0x82, - 0xEB, 0x0F, 0x27, 0xA2, 0xC2, 0xCA, 0x2C, 0x0A, 0x83, 0xDC, 0xA4, 0x4F, 0xE0, 0xB4, 0xBA, 0x97, - 0x95, 0xB1, 0x15, 0xF5, 0x4F, 0x5F, 0x77, 0x75, 0x3A, 0x23, 0x6D, 0xC0, 0x7E, 0xAC, 0x13, 0x6C, - 0xAA, 0x7C, 0x3E, 0x34, 0x5E, 0x83, 0xC3, 0x6A, 0x71, 0xE6, 0x2B, 0x77, 0x17, 0x8B, 0xDF, 0x19, - 0xFA, 0x77, 0xAE, 0xC1, 0xFA, 0x3D, 0x47, 0xBF, 0xF3, 0xA9, 0x36, 0xE0, 0x3A, 0x61, 0x7F, 0xEA, - 0x0C, 0x45, 0xD5, 0x3E, 0x7A, 0xCC, 0xA2, 0xCF, 0x57, 0x47, 0x16, 0x19, 0xD3, 0x5A, 0x0B, 0x12, - 0x31, 0x42, 0xD8, 0x44, 0xAD, 0x59, 0x78, 0x87, 0x49, 0x32, 0x9D, 0x21, 0x0B, 0x1E, 0x86, 0x74, - 0x43, 0xC5, 0x48, 0x75, 0x13, 0x28, 0x47, 0x09, 0xAB, 0x78, 0xA3, 0x3C, 0x02, 0xF2, 0x80, 0xE9, - 0x81, 0x3A, 0x17, 0xE9, 0xE5, 0x79, 0x53, 0x4C, 0xB9, 0x2B, 0x1D, 0x23, 0x6D, 0x6B, 0x6F, 0x41, - 0x6A, 0xBA, 0xE2, 0xF7, 0xDC, 0x28, 0x61, 0x32, 0x4B, 0xD3, 0x56, 0xF2, 0x3B, 0xCD, 0xC4, 0x67, - 0x7C, 0x83, 0xD1, 0x2B, 0xE5, 0x3D, 0x67, 0xE2, 0x06, 0xB6, 0x82, 0x81, 0x4B, 0xAC, 0x78, 0x7E, - 0xE3, 0x5B, 0xB6, 0x11, 0xAE, 0xD3, 0x6E, 0xB7, 0xD9, 0x37, 0xBC, 0x9D, 0x6F, 0x7F, 0x9D, 0x90, - 0xBE, 0xE0, 0xF2, 0x8A, 0xF5, 0x23, 0x67, 0xCC, 0x73, 0xFB, 0xD4, 0xE5, 0x7E, 0x5D, 0x3D, 0xC2, - 0xE2, 0xBA, 0xFB, 0xD8, 0x23, 0xFC, 0xAC, 0xA7, 0xC7, 0x5E, 0x72, 0xDD, 0x1F, 0x81, 0xD0, 0x81, - 0xA0, 0x4C, 0x38, 0x99, 0x1E, 0x17, 0xEA, 0x79, 0x2F, 0xBE, 0xFA, 0xDA, 0xFA, 0xDD, 0x8F, 0xC3, - 0x60, 0xBE, 0x7E, 0x2B, 0x54, 0xFA, 0x5C, 0x0A, 0x73, 0x94, 0x16, 0x47, 0xE4, 0x6A, 0x53, 0xCE, - 0xA8, 0x45, 0xA9, 0xCC, 0x43, 0x2A, 0x33, 0x10, 0xD7, 0x5B, 0xB1, 0x35, 0xA2, 0x52, 0x8C, 0xB5, - 0x0E, 0x27, 0x30, 0xB4, 0xC6, 0xB5, 0x96, 0x44, 0x02, 0x36, 0x51, 0x1E, 0x91, 0x6F, 0x6A, 0xDC, - 0x86, 0x6F, 0x51, 0x4E, 0x8E, 0xB0, 0xC5, 0x2C, 0x78, 0x3D, 0x07, 0x36, 0x99, 0x6B, 0xA4, 0xAC, - 0x26, 0x7D, 0xE0, 0xC7, 0x9F, 0x56, 0x71, 0x91, 0xED, 0x78, 0xEE, 0xD3, 0x5F, 0x60, 0xE3, 0x2C, - 0x96, 0x75, 0x87, 0x1D, 0xED, 0xBB, 0xDA, 0x35, 0x76, 0xA4, 0x65, 0xD6, 0x2D, 0xF5, 0x14, 0x7A, - 0x33, 0xD7, 0xD3, 0xD8, 0x78, 0xAD, 0xEE, 0xA5, 0x73, 0x97, 0x33, 0xDF, 0xD5, 0x74, 0x8A, 0x11, - 0x12, 0x36, 0x38, 0xBF, 0x62, 0x21, 0x22, 0xE0, 0x7B, 0x2B, 0xA8, 0x01, 0x29, 0xFC, 0xC1, 0x80, - 0x64, 0x81, 0x32, 0xB9, 0x83, 0x57, 0x12, 0xE7, 0x27, 0xB7, 0x27, 0x0C, 0xC1, 0xB7, 0xC3, 0xDC, - 0x31, 0xA8, 0x29, 0x43, 0x1F, 0xA4, 0x2C, 0xC9, 0x56, 0xD1, 0x9D, 0x27, 0x89, 0x9B, 0xA9, 0x32, - 0x48, 0xD0, 0x1B, 0x2E, 0xFA, 0xB3, 0x5D, 0x74, 0x3F, 0x1A, 0x6B, 0x4A, 0x7E, 0x6A, 0xD8, 0xEE, - 0x6C, 0xAC, 0x98, 0x66, 0x44, 0x60, 0x09, 0x16, 0x5B, 0xF2, 0xE0, 0x8E, 0x4A, 0x2F, 0x61, 0x9F, - 0xA7, 0x8A, 0x0F, 0x02, 0xDF, 0x0D, 0xFE, 0x49, 0xD8, 0xC3, 0xF0, 0x01, 0x79, 0x22, 0x1E, 0xB1, - 0x39, 0x05, 0x94, 0x78, 0xDF, 0xF1, 0x25, 0x88, 0x47, 0x69, 0xE2, 0xC7, 0x7C, 0x92, 0xC7, 0xDE, - 0x61, 0x1B, 0x84, 0x0F, 0xFC, 0xB9, 0xD0, 0xB1, 0xF8, 0xE6, 0x00, 0xBB, 0xC1, 0x9F, 0x8B, 0xC9, - 0x2C, 0x6F, 0xBA, 0xD8, 0x0F, 0xFD, 0x5A, 0x4C, 0xB0, 0x38, 0xDC, 0xC3, 0x8E, 0xE8, 0xD7, 0x62, - 0xE7, 0xFD, 0x6B, 0x5A, 0x19, 0xFD, 0x5A, 0x6C, 0x69, 0x9D, 0x7D, 0xB1, 0x36, 0xFA, 0xBD, 0x98, - 0x1C, 0xD6, 0x6B, 0xD3, 0xEA, 0xC4, 0xEF, 0xC5, 0xC4, 0xA4, 0x83, 0xB6, 0x40, 0x00, 0xFA, 0xBD, - 0x18, 0x0A, 0x74, 0x3B, 0x02, 0x09, 0xE8, 0xF7, 0xF3, 0x11, 0x86, 0x6E, 0xC9, 0x73, 0x16, 0x68, - 0x96, 0xEE, 0x07, 0x50, 0x27, 0x44, 0xB9, 0xE8, 0xE6, 0xE4, 0xFC, 0xF2, 0x33, 0x31, 0xAD, 0xC0, - 0xF3, 0x55, 0x05, 0xE6, 0x31, 0x12, 0x96, 0x94, 0x89, 0xE2, 0xA9, 0xC0, 0xE2, 0x50, 0x4D, 0x74, - 0xB0, 0x4E, 0xEE, 0xD8, 0x42, 0xBE, 0x07, 0x7A, 0x22, 0x91, 0x69, 0x44, 0xF4, 0x21, 0xB4, 0xB9, - 0xB3, 0xDD, 0x97, 0x26, 0xEA, 0x94, 0xE3, 0xBB, 0xB8, 0x3D, 0x35, 0xB3, 0xDD, 0x44, 0x97, 0x8D, - 0xE3, 0x6B, 0xFF, 0x1B, 0x93, 0x7F, 0xCC, 0xCD, 0x6F, 0x93, 0x9D, 0xA6, 0x4D, 0xBE, 0x4E, 0x9E, - 0x0B, 0x44, 0xF2, 0xE9, 0xFA, 0xE2, 0x64, 0x51, 0xED, 0xAE, 0xDF, 0x1F, 0xEC, 0x5E, 0xDC, 0xA3, - 0x63, 0xF4, 0x2D, 0x60, 0xFD, 0x08, 0x4B, 0x56, 0x2F, 0xA4, 0xDC, 0x09, 0xEF, 0x4D, 0xFF, 0x9B, - 0x5C, 0x31, 0x6C, 0xCD, 0x65, 0xF7, 0x6C, 0x51, 0xF5, 0xEE, 0xE4, 0xFC, 0x6C, 0xF7, 0xFC, 0xE4, - 0xEC, 0x79, 0xB1, 0x83, 0x50, 0x60, 0x09, 0x69, 0x5F, 0x0A, 0x84, 0xD2, 0xF1, 0x5D, 0xD3, 0x87, - 0x43, 0xEE, 0x20, 0xE4, 0x15, 0xDB, 0x36, 0x74, 0x61, 0x3C, 0x72, 0x5B, 0x0C, 0x77, 0x15, 0x35, - 0x24, 0x32, 0x1F, 0xE1, 0x8B, 0xC0, 0x1F, 0x74, 0x10, 0x80, 0x6E, 0x7F, 0xDD, 0xBD, 0xF9, 0x15, - 0xF9, 0x43, 0x68, 0x68, 0x42, 0xBA, 0x76, 0x26, 0xF4, 0xB9, 0xC5, 0x70, 0x23, 0xE5, 0x16, 0xC6, - 0x5A, 0xDB, 0x3E, 0x08, 0xB3, 0x68, 0x9D, 0x85, 0x7E, 0x60, 0x06, 0x1A, 0xB3, 0x7C, 0x0F, 0x4D, - 0x74, 0x78, 0x1D, 0xB1, 0x6F, 0xBA, 0x78, 0x5D, 0xE9, 0x13, 0xD3, 0xF2, 0x82, 0xC6, 0x68, 0xA9, - 0x83, 0x79, 0xC0, 0xBE, 0x85, 0x1D, 0xE1, 0xFD, 0xA7, 0xC9, 0x14, 0x4D, 0x23, 0x5B, 0x2F, 0x4C, - 0x66, 0x70, 0x7E, 0xB2, 0x3B, 0x38, 0xBB, 0x12, 0x86, 0x62, 0x55, 0xEC, 0x75, 0x8B, 0xFD, 0x32, - 0xE6, 0xDC, 0xD8, 0x3D, 0xD7, 0x1D, 0x76, 0xAB, 0xAB, 0x5F, 0x63, 0xD3, 0xA0, 0x2B, 0x7B, 0xCB, - 0xD3, 0xEF, 0x39, 0xD6, 0xB7, 0xE7, 0xCE, 0x50, 0x51, 0x75, 0x73, 0xD4, 0x62, 0x72, 0x6B, 0x63, - 0xEF, 0xCA, 0xDB, 0xD1, 0xA1, 0xEE, 0x4C, 0xD0, 0x69, 0x31, 0x01, 0x9F, 0x90, 0x2F, 0x22, 0xA0, - 0x5E, 0x9C, 0x95, 0x2C, 0xE0, 0x18, 0x28, 0xA2, 0xD5, 0xC9, 0xEB, 0x84, 0x84, 0x79, 0x8E, 0xDB, - 0x55, 0x93, 0x80, 0x19, 0xEB, 0x79, 0x66, 0xEE, 0x1B, 0xF1, 0x72, 0x23, 0x5E, 0x6E, 0xC4, 0xCB, - 0xDA, 0xBC, 0x52, 0x4C, 0x71, 0x2C, 0x60, 0x54, 0xD2, 0x8E, 0x0C, 0xD3, 0x4A, 0x0A, 0x9B, 0xB3, - 0xE7, 0x4D, 0xA6, 0xAC, 0x19, 0x61, 0xA8, 0x11, 0x7E, 0x2A, 0xE0, 0xFB, 0x4C, 0x04, 0xCD, 0x4A, - 0x4C, 0x35, 0x54, 0xFB, 0xF1, 0x2C, 0x0C, 0x8C, 0x3E, 0x09, 0xAB, 0x48, 0x59, 0xC3, 0xFB, 0x62, - 0xD7, 0x73, 0xA5, 0xAF, 0xE8, 0xC4, 0xAD, 0xD3, 0x45, 0x74, 0xE2, 0xA1, 0x0B, 0x7A, 0xF0, 0xB4, - 0x7C, 0x98, 0x30, 0x36, 0xAF, 0x16, 0x22, 0xBC, 0xB8, 0x3F, 0x7A, 0x62, 0xEE, 0xD5, 0x51, 0x6A, - 0x71, 0xEA, 0x89, 0x50, 0xD0, 0x07, 0x6E, 0x72, 0x22, 0x17, 0x45, 0x40, 0x03, 0xA3, 0x74, 0xD8, - 0x40, 0x4A, 0x44, 0x20, 0x9E, 0xC0, 0x7B, 0x13, 0x19, 0xFC, 0x38, 0xF1, 0x81, 0x56, 0x86, 0x94, - 0x71, 0x61, 0x47, 0xD8, 0xA1, 0x00, 0x7F, 0x30, 0x7A, 0x50, 0xCA, 0x4A, 0xEA, 0x63, 0x8B, 0xA1, - 0xE3, 0x8B, 0xEE, 0x70, 0x57, 0xDC, 0x83, 0x3B, 0x5C, 0xE5, 0x76, 0x82, 0x9A, 0xA4, 0x2F, 0xA1, - 0xE8, 0xA0, 0xD3, 0x6E, 0xEF, 0xB4, 0xDB, 0x6D, 0xDF, 0x95, 0xE2, 0x98, 0xC1, 0xCD, 0x11, 0xD9, - 0xB7, 0x34, 0xF6, 0x26, 0xFC, 0x4A, 0xCC, 0x80, 0x7B, 0x0F, 0x9C, 0x9B, 0xE2, 0x3D, 0xB7, 0x55, - 0x25, 0x40, 0x7A, 0x59, 0xE4, 0x58, 0x49, 0xF7, 0xAB, 0x12, 0xA9, 0x9F, 0x41, 0x9A, 0xE7, 0x1C, - 0xC3, 0x3E, 0xDD, 0xB9, 0x29, 0x74, 0x21, 0x57, 0xC8, 0x2C, 0x39, 0x2A, 0x36, 0xCF, 0x5B, 0xD8, - 0xA9, 0x53, 0xB1, 0x51, 0x02, 0xBB, 0x4B, 0x77, 0x39, 0xA7, 0x87, 0xE4, 0xED, 0x2C, 0x6A, 0xB0, - 0xAD, 0xFF, 0xF7, 0x7F, 0xDD, 0xED, 0xA3, 0xF2, 0x8B, 0xA9, 0x42, 0xFE, 0x0B, 0xBB, 0xE5, 0x65, - 0xBA, 0xE8, 0xE5, 0x65, 0x94, 0xAA, 0xD4, 0x2B, 0xFE, 0x9B, 0x41, 0xA0, 0x99, 0x8D, 0xA9, 0x38, - 0x53, 0xBB, 0xB8, 0xCB, 0x3A, 0xF2, 0x0C, 0x94, 0xB8, 0xEF, 0x99, 0x87, 0xA8, 0x96, 0x8F, 0xF8, - 0x57, 0xC4, 0xB7, 0x96, 0x8E, 0xEE, 0xFD, 0x08, 0x93, 0xAC, 0x34, 0x96, 0xA0, 0x89, 0x67, 0x80, - 0xEA, 0xB3, 0x78, 0x26, 0x41, 0xBB, 0x38, 0xC2, 0x8A, 0x8E, 0x9E, 0x03, 0x9A, 0xD2, 0x85, 0x18, - 0xD1, 0x94, 0x36, 0xE2, 0xF3, 0x2A, 0xBB, 0x85, 0x38, 0xDB, 0xB7, 0x0C, 0xC5, 0xC1, 0x9B, 0xE2, - 0xD5, 0x60, 0x6D, 0x30, 0xDC, 0xD1, 0x1C, 0x1C, 0x37, 0xA6, 0x5F, 0xA7, 0x2F, 0x62, 0x76, 0xBF, - 0x17, 0x5A, 0xDF, 0x3C, 0xDA, 0x79, 0xB6, 0x41, 0xF2, 0x46, 0x77, 0x75, 0x73, 0xC4, 0x70, 0x3B, - 0xAB, 0xA9, 0x69, 0xD9, 0xF6, 0xC9, 0xF7, 0x8A, 0x61, 0xCC, 0xDF, 0x67, 0x79, 0xBD, 0x6D, 0x89, - 0x12, 0x68, 0xC2, 0x2E, 0x28, 0x24, 0x40, 0x57, 0x1F, 0xA1, 0x1D, 0x2D, 0xF0, 0x15, 0x1B, 0xA3, - 0x4B, 0x83, 0x08, 0x8C, 0x54, 0xC9, 0x48, 0xB6, 0xE5, 0x08, 0x58, 0x72, 0x58, 0xF7, 0x36, 0x4A, - 0xA8, 0x18, 0x0A, 0x13, 0x7B, 0x61, 0x28, 0x21, 0x43, 0x6F, 0x44, 0x04, 0xCF, 0xC8, 0x26, 0x54, - 0xDD, 0xD0, 0x65, 0x4A, 0x90, 0x55, 0xA4, 0xC8, 0x0A, 0x8C, 0x67, 0xEE, 0xD4, 0x50, 0xAB, 0x50, - 0xDB, 0x3E, 0xCA, 0xB0, 0x6B, 0x32, 0xB0, 0x5F, 0x59, 0xA3, 0x11, 0x86, 0x45, 0x4B, 0x2D, 0xAE, - 0x34, 0xD0, 0x2A, 0x6B, 0x7B, 0xC2, 0x9C, 0x2F, 0x87, 0x5B, 0x89, 0xD6, 0x57, 0x7A, 0x6A, 0xB3, - 0xDA, 0x61, 0x3A, 0x88, 0x9E, 0x58, 0x59, 0x84, 0x79, 0x4C, 0x1D, 0x0F, 0x02, 0x71, 0x9B, 0xE3, - 0x0C, 0xDD, 0x6D, 0xA1, 0xD0, 0xF5, 0xDA, 0xA1, 0xE9, 0x5C, 0x7D, 0x64, 0x37, 0xD7, 0x83, 0x16, - 0x46, 0xE9, 0x0C, 0x7D, 0x43, 0x9A, 0x63, 0x30, 0x9E, 0x90, 0x19, 0xFA, 0x57, 0xD0, 0x19, 0x7D, - 0xBC, 0xD4, 0xF7, 0x1C, 0x5D, 0x31, 0x47, 0xBE, 0x21, 0x6D, 0x2E, 0x03, 0x2E, 0x22, 0x7A, 0x50, - 0xB8, 0x04, 0x7A, 0xFA, 0x8A, 0xB6, 0x79, 0x62, 0x7C, 0xD2, 0x4D, 0xF4, 0xF7, 0x8B, 0xF3, 0xE6, - 0xFB, 0x37, 0x7D, 0x76, 0x69, 0x7A, 0x7C, 0x24, 0x3D, 0x48, 0xAE, 0x15, 0xD3, 0x57, 0x44, 0xFF, - 0x13, 0xCB, 0xE1, 0x71, 0xEB, 0x7F, 0xB1, 0x3E, 0x5A, 0xD1, 0xAF, 0xF4, 0xF9, 0x6B, 0x94, 0x51, - 0x8C, 0xAA, 0x4D, 0xA5, 0xA4, 0x01, 0xB0, 0xE3, 0x65, 0x89, 0x2C, 0x34, 0xEB, 0x95, 0x89, 0x2C, - 0x82, 0x55, 0xD4, 0x2E, 0xB2, 0xC4, 0x17, 0x31, 0xBB, 0x2B, 0x6B, 0x24, 0xB2, 0x60, 0x36, 0x1B, - 0xCF, 0x22, 0x97, 0xC2, 0xBA, 0x44, 0x16, 0x72, 0x4F, 0x14, 0x89, 0x72, 0x5E, 0x86, 0xC4, 0x12, - 0xB2, 0x42, 0xC2, 0xFC, 0x50, 0x66, 0x31, 0x80, 0xB6, 0xB8, 0xC6, 0xD0, 0xD1, 0x73, 0x1C, 0xE4, - 0x12, 0xD3, 0x13, 0x5C, 0x27, 0x02, 0xDE, 0x8D, 0x30, 0x52, 0xDD, 0x70, 0xED, 0x83, 0x6E, 0x14, - 0x1C, 0x97, 0x18, 0x93, 0x4F, 0x1E, 0x62, 0x6B, 0x68, 0xA4, 0x8E, 0xCC, 0x2D, 0x61, 0xA3, 0x66, - 0xC2, 0xA9, 0x2D, 0x92, 0x90, 0xE4, 0x09, 0xEC, 0xD0, 0x55, 0x40, 0x57, 0x8D, 0xA4, 0x32, 0xC6, - 0x29, 0x97, 0x8C, 0x2F, 0x85, 0xDC, 0x24, 0xF4, 0x42, 0x8A, 0x23, 0x5F, 0xA0, 0x68, 0xA4, 0xC8, - 0x11, 0xA5, 0xBD, 0xDB, 0xFD, 0x72, 0xFA, 0xEB, 0xEE, 0xA0, 0x7F, 0x72, 0x73, 0xFB, 0x09, 0xE9, - 0x2F, 0x10, 0x17, 0xBE, 0xC0, 0x83, 0x6E, 0x84, 0xFA, 0xDE, 0x2B, 0xD5, 0x6C, 0x60, 0xEB, 0x74, - 0xE0, 0x17, 0xBD, 0x56, 0x49, 0x20, 0x08, 0x37, 0x46, 0xC0, 0x6C, 0x60, 0xF9, 0x8E, 0x9A, 0x72, - 0x7C, 0x57, 0x26, 0x8F, 0xE0, 0xE8, 0x4E, 0xED, 0x1F, 0x68, 0x21, 0xD8, 0x46, 0xB9, 0x55, 0xE2, - 0x79, 0xC5, 0x63, 0x76, 0xE6, 0x88, 0x4D, 0x1D, 0xEC, 0x16, 0x08, 0xA1, 0x91, 0xBB, 0xDC, 0xC6, - 0xA2, 0x07, 0xE8, 0xEC, 0x49, 0x27, 0x92, 0x88, 0xCC, 0x71, 0xC8, 0xCD, 0x1C, 0xC3, 0x97, 0xFD, - 0x8A, 0xF7, 0xBA, 0x95, 0x8E, 0xC9, 0xE5, 0x69, 0x0A, 0x14, 0x21, 0x26, 0x77, 0x37, 0x1A, 0x15, - 0x86, 0x6E, 0x2B, 0x18, 0x28, 0xA9, 0x6B, 0xE2, 0x84, 0x24, 0xCA, 0x94, 0x29, 0x01, 0xA2, 0x69, - 0x58, 0x5C, 0xDA, 0x9A, 0x16, 0x79, 0x3E, 0x7F, 0xBA, 0xF8, 0xDC, 0x3C, 0x7F, 0x73, 0x76, 0xC4, - 0xE4, 0x86, 0x0B, 0xE8, 0x46, 0xC8, 0xF9, 0xB2, 0xDF, 0x7A, 0x81, 0xC4, 0xBC, 0x68, 0xD8, 0x25, - 0x3A, 0x8E, 0x9F, 0x18, 0x0F, 0x98, 0x22, 0x84, 0x1C, 0xC3, 0x25, 0x0B, 0x25, 0x45, 0x48, 0xFA, - 0x3A, 0x61, 0xAE, 0x4F, 0xF6, 0x65, 0x70, 0x5A, 0xEC, 0x24, 0xBE, 0xBC, 0xE0, 0x79, 0x71, 0x48, - 0x7C, 0x30, 0x5D, 0xF7, 0xD6, 0xFA, 0xE2, 0xDE, 0x0D, 0xC8, 0xA9, 0x2B, 0x3C, 0x2E, 0x93, 0xB3, - 0x2D, 0x04, 0x2C, 0xAC, 0x66, 0xF9, 0x91, 0xF6, 0x19, 0x73, 0x5E, 0x71, 0xB8, 0xFD, 0x67, 0x01, - 0x16, 0x02, 0x91, 0xF4, 0x85, 0x23, 0xB7, 0xB3, 0x2D, 0x9D, 0xEF, 0x08, 0xB7, 0x08, 0xCA, 0x5D, - 0x04, 0xC7, 0xE2, 0xB6, 0xD8, 0x69, 0x24, 0x2C, 0xDC, 0x6D, 0x72, 0x86, 0x88, 0x04, 0x60, 0xBA, - 0x9E, 0xE2, 0xF9, 0x6E, 0x24, 0x65, 0x6C, 0xE5, 0x83, 0xF2, 0x29, 0x43, 0xED, 0x2B, 0x7C, 0x95, - 0x1D, 0xF4, 0x93, 0x45, 0x55, 0x89, 0xF8, 0x9F, 0x5F, 0xF4, 0xF7, 0x7A, 0x7E, 0xF8, 0x4F, 0xC9, - 0xD0, 0x9F, 0xAA, 0x61, 0x3F, 0x73, 0x45, 0xF9, 0xA4, 0x47, 0xF8, 0xE0, 0x1A, 0xE6, 0x0A, 0xF0, - 0x89, 0x34, 0x9C, 0x05, 0x6D, 0x04, 0x32, 0x89, 0xD0, 0x9E, 0x07, 0x7D, 0xA8, 0x2F, 0x16, 0xD9, - 0x33, 0x1B, 0xD5, 0x93, 0xDC, 0xB5, 0xC2, 0x68, 0x9E, 0xCC, 0xB9, 0x57, 0x0B, 0xE4, 0xC9, 0x0F, - 0xE2, 0x49, 0xC3, 0xC4, 0x4F, 0x22, 0xCF, 0x92, 0x9B, 0x7E, 0x21, 0x5D, 0x03, 0x53, 0x08, 0x05, - 0x64, 0x10, 0xA9, 0x98, 0xEA, 0x70, 0x0D, 0x1A, 0x01, 0x2B, 0x10, 0x01, 0xD8, 0x30, 0x37, 0xD0, - 0x32, 0xF7, 0xC4, 0xEE, 0xC8, 0x94, 0x4F, 0x40, 0xDF, 0x27, 0xA0, 0xA8, 0x06, 0x7F, 0x85, 0x6A, - 0xAB, 0xE7, 0xE8, 0x32, 0x1B, 0x07, 0xF9, 0x51, 0x61, 0xFA, 0x12, 0xF9, 0xCE, 0x34, 0x29, 0x61, - 0x2C, 0x86, 0xBB, 0x55, 0x36, 0x85, 0xE2, 0xA2, 0x0C, 0x21, 0xEB, 0xBB, 0xAC, 0x23, 0xB0, 0xF2, - 0x5D, 0x6F, 0x54, 0x6A, 0x45, 0x74, 0x95, 0x9B, 0xF6, 0x47, 0x7B, 0x30, 0xC0, 0xCC, 0xA0, 0x91, - 0x1B, 0xD0, 0xDE, 0x8C, 0xA9, 0x08, 0x5F, 0x61, 0x9D, 0xFC, 0xBB, 0xD5, 0xB9, 0xEE, 0x51, 0xAB, - 0x64, 0xF0, 0x49, 0x99, 0xB4, 0x3C, 0xB0, 0x3C, 0xD8, 0xC7, 0xBC, 0x41, 0xEC, 0xF4, 0xF6, 0xF3, - 0x5C, 0x93, 0xE6, 0x33, 0xDD, 0x25, 0xEE, 0x52, 0x1F, 0xFA, 0x80, 0x4F, 0x5A, 0xC1, 0x4E, 0xF5, - 0x7F, 0xC9, 0x04, 0xC3, 0x5A, 0xED, 0x5F, 0xB8, 0x9C, 0x6A, 0xDB, 0x16, 0x34, 0x7B, 0x4E, 0x5B, - 0xD7, 0x29, 0x4B, 0x60, 0xDD, 0x35, 0xDA, 0x20, 0x31, 0xE9, 0x4A, 0x9B, 0xD3, 0x79, 0x6E, 0x34, - 0xD5, 0x59, 0x9C, 0xA6, 0xD6, 0x6A, 0xCB, 0xE6, 0xA3, 0xA9, 0xCE, 0x73, 0xA4, 0xA9, 0x6E, 0x59, - 0x9A, 0xEA, 0xAD, 0xD1, 0x06, 0x75, 0xAB, 0xD3, 0x54, 0xF7, 0xB9, 0xD1, 0x54, 0x77, 0x71, 0x9A, - 0x5A, 0xAB, 0x2D, 0x9B, 0x8F, 0xA6, 0xBA, 0xCF, 0x91, 0xA6, 0x7A, 0x65, 0x69, 0x6A, 0x6F, 0x8D, - 0x36, 0xA8, 0x57, 0x9D, 0xA6, 0x7A, 0xCF, 0x8D, 0xA6, 0x7A, 0x8B, 0xD3, 0xD4, 0x5A, 0x6D, 0xD9, - 0x7C, 0x34, 0xD5, 0x5B, 0x35, 0x4D, 0x85, 0x7A, 0x3D, 0xA9, 0xD4, 0x68, 0x63, 0xAF, 0x72, 0x69, - 0x90, 0xDC, 0x4C, 0xD1, 0xCB, 0xE7, 0x7B, 0xEE, 0x9C, 0xF4, 0x1B, 0xC7, 0x81, 0x11, 0x81, 0xB3, - 0x6B, 0x4B, 0x2B, 0xBA, 0x14, 0x88, 0x5F, 0x02, 0xC4, 0xA7, 0xD4, 0x48, 0x4C, 0x53, 0x0E, 0x50, - 0xD5, 0xDC, 0x3F, 0x6B, 0xDE, 0x3F, 0x29, 0x61, 0x94, 0x9F, 0x35, 0xE5, 0xA3, 0x0E, 0x9E, 0xDF, - 0x2E, 0xDF, 0x78, 0x5F, 0x9F, 0xAD, 0xE0, 0xD2, 0x64, 0x27, 0xFD, 0x48, 0xCC, 0x54, 0x98, 0x5A, - 0x04, 0x75, 0x7B, 0xC7, 0x52, 0x34, 0x55, 0x01, 0xE5, 0x5F, 0xC6, 0xB6, 0xAA, 0x2A, 0x77, 0x5D, - 0x46, 0xA9, 0x5D, 0x19, 0xE6, 0x95, 0xE6, 0x68, 0x52, 0xFC, 0x6B, 0x53, 0x80, 0xB4, 0xC5, 0xA0, - 0x2F, 0x32, 0x2E, 0xA4, 0xF7, 0x16, 0xC9, 0x3B, 0x4D, 0xB9, 0x90, 0xC4, 0xBB, 0x68, 0x63, 0x88, - 0xC4, 0xFD, 0x92, 0x61, 0x81, 0x6E, 0xCB, 0x39, 0x93, 0x35, 0x31, 0x60, 0x90, 0xCB, 0x3E, 0xC6, - 0xD5, 0x3A, 0x30, 0xF8, 0x5A, 0xDA, 0x19, 0xAA, 0xD9, 0x0D, 0x6F, 0xCF, 0xFA, 0x6C, 0x97, 0x7D, - 0x39, 0xEF, 0x3F, 0x77, 0xE3, 0x21, 0x2C, 0x04, 0x56, 0x31, 0x97, 0xF9, 0x30, 0xD6, 0x74, 0x16, - 0xC8, 0x49, 0x10, 0x25, 0xAC, 0x88, 0x9E, 0x6A, 0x7F, 0xD1, 0xEC, 0x27, 0xB7, 0x23, 0xE6, 0x2C, - 0x62, 0x41, 0x4B, 0x62, 0xE1, 0xAD, 0x4A, 0x85, 0xDB, 0x94, 0x5B, 0xD5, 0x3E, 0x33, 0x74, 0xE0, - 0x08, 0x8D, 0x63, 0x04, 0xAB, 0xF8, 0x9C, 0xCF, 0x47, 0x17, 0xBF, 0x0E, 0x99, 0x0E, 0x9A, 0x49, - 0xAD, 0x61, 0xCD, 0x2D, 0x2F, 0x78, 0xF7, 0xD4, 0xFA, 0xC6, 0xA9, 0xF6, 0xD8, 0xB2, 0x59, 0xDE, - 0x70, 0x9A, 0xAF, 0x32, 0x83, 0x41, 0x85, 0xAC, 0xC9, 0xF6, 0xDD, 0xB1, 0xB8, 0x3F, 0xC1, 0xEA, - 0x7A, 0xC0, 0x94, 0x46, 0xFA, 0x3D, 0x37, 0x09, 0x41, 0x5D, 0x2A, 0xB0, 0x39, 0xBD, 0x4D, 0x11, - 0xDF, 0xE0, 0x7D, 0xCA, 0xBA, 0x30, 0xA9, 0xCC, 0xE3, 0x3A, 0x04, 0x79, 0x6E, 0x8A, 0xAE, 0xB9, - 0xC2, 0x64, 0xA2, 0xE7, 0x79, 0x38, 0x0C, 0x06, 0xBC, 0x37, 0xE6, 0x0A, 0x17, 0x80, 0x86, 0x47, - 0x35, 0x5D, 0xA5, 0x85, 0xDE, 0x5A, 0xB0, 0x77, 0x74, 0xEB, 0x05, 0x3B, 0xEA, 0xBB, 0xD1, 0x44, - 0xBC, 0xDD, 0x37, 0x7B, 0x87, 0xEB, 0x7B, 0xC1, 0x55, 0x54, 0xDB, 0x66, 0xDE, 0xF8, 0x9C, 0x2A, - 0xF2, 0x69, 0x7C, 0x47, 0x0B, 0xFA, 0xB5, 0x67, 0x9B, 0xCC, 0x1B, 0x71, 0x53, 0xFD, 0x7A, 0xAF, - 0x5E, 0x04, 0xFE, 0x68, 0xB9, 0xF3, 0x21, 0x30, 0x36, 0xC4, 0xBE, 0x72, 0x21, 0x35, 0xE5, 0xCB, - 0x47, 0x2B, 0xBE, 0x36, 0x46, 0xCF, 0x45, 0x14, 0x97, 0x29, 0xBC, 0x57, 0x48, 0x58, 0x81, 0x7B, - 0xF5, 0x18, 0x67, 0xFE, 0x30, 0xD6, 0xD5, 0xB1, 0xF0, 0xC2, 0x86, 0x39, 0xAA, 0x34, 0xC7, 0x78, - 0xE2, 0x6B, 0x64, 0x90, 0x14, 0x3F, 0xDF, 0xDA, 0x50, 0x4E, 0x29, 0xCA, 0x11, 0xA8, 0x54, 0xEE, - 0x46, 0x26, 0x8D, 0x8C, 0xB0, 0xFD, 0xEA, 0xC8, 0x28, 0xEF, 0x24, 0xA9, 0xA4, 0xB3, 0xDB, 0x69, - 0xD2, 0x41, 0xD5, 0x75, 0x94, 0x36, 0x21, 0xD4, 0x2C, 0x40, 0x89, 0x5A, 0xDA, 0x42, 0x80, 0x12, - 0x9F, 0x57, 0x20, 0x40, 0xC9, 0x41, 0x4B, 0x09, 0x50, 0xE2, 0xDD, 0xA7, 0x16, 0xA0, 0x40, 0x23, - 0xB4, 0x1E, 0x98, 0x6E, 0xDE, 0x59, 0x3E, 0x88, 0x4E, 0xC4, 0x31, 0x84, 0x48, 0x25, 0xDC, 0xBD, - 0x62, 0xE2, 0x54, 0x8A, 0xD8, 0x24, 0x43, 0xB7, 0x02, 0x36, 0x83, 0xA5, 0x90, 0x45, 0xF3, 0xF5, - 0xB9, 0x5E, 0xCE, 0x93, 0xA8, 0xC4, 0x1E, 0x2C, 0x5D, 0xA2, 0x12, 0xC3, 0x6C, 0x24, 0xAA, 0x97, - 0x73, 0x2E, 0x44, 0x76, 0xB4, 0xEC, 0x51, 0x30, 0x6D, 0xF2, 0xFC, 0x8F, 0x02, 0xB1, 0x96, 0x67, - 0x71, 0x14, 0x7C, 0xD1, 0xC2, 0xA3, 0x00, 0x8D, 0x13, 0xAB, 0x39, 0x0A, 0xA6, 0x83, 0x4E, 0x59, - 0xBE, 0xAF, 0xD9, 0x4F, 0xCD, 0xEC, 0xA7, 0xC6, 0x41, 0xE2, 0xE7, 0x2E, 0x74, 0xC9, 0x4D, 0x95, - 0xBB, 0xD2, 0x91, 0xF4, 0xBC, 0xBF, 0xEE, 0x2C, 0xDB, 0xD7, 0xCA, 0x55, 0x35, 0x5C, 0x98, 0x69, - 0xFB, 0xDA, 0x3A, 0x32, 0x6D, 0x44, 0xE0, 0x74, 0xA6, 0xDD, 0x69, 0x77, 0x3A, 0xED, 0x0D, 0xD7, - 0xCE, 0xE3, 0xDA, 0xF1, 0x2D, 0x2D, 0xC5, 0xB5, 0x63, 0x4D, 0x9E, 0x39, 0xD7, 0x0E, 0x59, 0x52, - 0x1D, 0x5C, 0xBB, 0x9A, 0xF5, 0x3C, 0x08, 0x95, 0x7A, 0xD6, 0x96, 0x73, 0x5A, 0xC4, 0x5C, 0x86, - 0xF3, 0x68, 0xCB, 0x59, 0xF0, 0x46, 0x81, 0x93, 0xB0, 0x99, 0x53, 0x90, 0xD4, 0x93, 0x9B, 0xCC, - 0xB3, 0xA7, 0xBF, 0x46, 0x16, 0xF3, 0x0B, 0xD7, 0xA6, 0x9A, 0x63, 0x17, 0x83, 0x7E, 0xB3, 0xB0, - 0xDE, 0x59, 0x1D, 0xC1, 0x77, 0x72, 0xBC, 0x25, 0x1F, 0xE4, 0x68, 0xEB, 0xA1, 0xB4, 0xB2, 0x18, - 0x6C, 0x05, 0x6B, 0xEB, 0x75, 0x83, 0x24, 0x88, 0x61, 0xFA, 0x5A, 0x71, 0xB5, 0x07, 0x68, 0xDD, - 0xA4, 0x0F, 0x3B, 0x68, 0x16, 0x0A, 0x1F, 0x4D, 0xE0, 0x70, 0xD0, 0xC5, 0xDD, 0x9F, 0x88, 0xB8, - 0x43, 0xA7, 0x62, 0x8C, 0x28, 0x10, 0x51, 0xAD, 0x13, 0xDD, 0x75, 0x29, 0xAE, 0xF5, 0xA3, 0xF5, - 0xC0, 0xEF, 0x31, 0x2D, 0xDC, 0x29, 0xAC, 0xD4, 0xB3, 0x2C, 0x6F, 0xCC, 0x06, 0xFD, 0x3E, 0x8E, - 0x65, 0x5A, 0xDE, 0x74, 0x38, 0x51, 0x15, 0xC5, 0x45, 0xE3, 0x13, 0x26, 0x75, 0xFB, 0x19, 0x1F, - 0x47, 0x9A, 0x9C, 0x5E, 0x5D, 0x44, 0x8E, 0xA4, 0xCF, 0xC3, 0xE1, 0x5A, 0x3B, 0x18, 0x87, 0x14, - 0x36, 0x4F, 0x10, 0x7D, 0x3A, 0x32, 0xE7, 0xAC, 0x57, 0x90, 0xF9, 0xF5, 0xC9, 0xD9, 0x51, 0xC0, - 0x96, 0x69, 0x6C, 0x78, 0xD0, 0x60, 0xAE, 0xF7, 0x88, 0x9B, 0x2D, 0xEF, 0x4E, 0x8F, 0x04, 0x4B, - 0xFE, 0xB9, 0x71, 0x7C, 0x72, 0x72, 0x74, 0x7A, 0x7A, 0x74, 0x76, 0x76, 0x74, 0x7E, 0x7E, 0x74, - 0x71, 0x71, 0xF4, 0xFE, 0x7D, 0x55, 0x1F, 0x80, 0xB9, 0x66, 0xDA, 0x57, 0x74, 0x4C, 0xE5, 0x4C, - 0x13, 0x76, 0x8F, 0x2A, 0x86, 0x15, 0x95, 0x3B, 0x57, 0x6D, 0xCE, 0x1D, 0x58, 0xB9, 0x9B, 0xBD, - 0xF4, 0x4F, 0x96, 0xC9, 0x17, 0x3C, 0x55, 0x6B, 0x14, 0x0C, 0xB1, 0x91, 0x9C, 0xA9, 0x10, 0xFF, - 0x80, 0xC9, 0x01, 0x57, 0x9D, 0x1C, 0xED, 0xDB, 0xDF, 0x7E, 0x2E, 0x5A, 0xB2, 0x3C, 0xB2, 0xE2, - 0x47, 0x11, 0x42, 0x01, 0x4E, 0x28, 0x84, 0xB5, 0x00, 0x74, 0x23, 0xEB, 0x4C, 0x4B, 0x95, 0x39, - 0x8B, 0x8D, 0xC5, 0xA1, 0xDA, 0x61, 0x87, 0x43, 0x80, 0xE2, 0x81, 0xF7, 0xF3, 0xA4, 0x17, 0x00, - 0x5F, 0x72, 0x7C, 0x0E, 0x52, 0x2A, 0x7C, 0x2B, 0xB7, 0x3A, 0xBD, 0xF6, 0xCA, 0x0A, 0xEC, 0xD9, - 0x91, 0xF2, 0xD6, 0x0F, 0x96, 0xD4, 0x57, 0xDC, 0x1D, 0xC6, 0xBF, 0xE9, 0x1E, 0xFA, 0x39, 0x48, - 0xB9, 0x41, 0x38, 0x28, 0x90, 0x41, 0x8A, 0x4C, 0xDE, 0x2E, 0xF7, 0x60, 0xE3, 0x24, 0x70, 0x7D, - 0xD3, 0xD3, 0x0D, 0xF6, 0x0A, 0x57, 0xF3, 0x0A, 0x8D, 0x50, 0x2E, 0xC7, 0x82, 0xC3, 0x5F, 0x4C, - 0xDD, 0x93, 0xF1, 0x11, 0xEE, 0x83, 0x62, 0x23, 0xF5, 0x05, 0x16, 0x73, 0xEE, 0x4A, 0x2F, 0x06, - 0x80, 0x2B, 0xC3, 0x8A, 0x4D, 0xBE, 0x49, 0x89, 0x63, 0xCD, 0x11, 0x72, 0x39, 0xAA, 0x5F, 0x6A, - 0x03, 0x4F, 0x74, 0x00, 0x8E, 0xAD, 0xE7, 0x1B, 0x3F, 0x55, 0x0F, 0x43, 0x58, 0x49, 0x00, 0xE1, - 0x7B, 0x0B, 0xE5, 0x2D, 0x49, 0x0B, 0x41, 0xF4, 0xA0, 0x78, 0x88, 0xE1, 0x2E, 0x85, 0x3B, 0x10, - 0xE0, 0xF0, 0x6A, 0x42, 0x07, 0x63, 0xB3, 0x05, 0xEC, 0x93, 0x45, 0x73, 0x9F, 0x80, 0x74, 0x80, - 0x18, 0x28, 0x79, 0x85, 0xA2, 0xAA, 0xBA, 0x26, 0xEA, 0x2F, 0x6A, 0xDC, 0xE0, 0x41, 0xD2, 0x1D, - 0x5B, 0x30, 0x73, 0x3A, 0x6B, 0x44, 0x64, 0x6E, 0xB0, 0x2E, 0x36, 0xF1, 0xF1, 0x9A, 0x4B, 0x77, - 0x5C, 0x4F, 0x24, 0x55, 0xA6, 0x15, 0xC0, 0xC7, 0x21, 0x26, 0xE1, 0xA1, 0x10, 0x22, 0x41, 0x5B, - 0x40, 0x4E, 0x44, 0x71, 0xF9, 0x16, 0xDD, 0x75, 0xA5, 0x84, 0xF9, 0x8F, 0xAA, 0x3C, 0xBE, 0x1D, - 0xDF, 0xFF, 0x74, 0xCE, 0x5D, 0x81, 0x47, 0x0F, 0xA9, 0x3B, 0x71, 0xF0, 0xCE, 0xF2, 0xEA, 0xE3, - 0x29, 0x19, 0x54, 0x63, 0xD5, 0x76, 0xDA, 0x74, 0xAF, 0xDD, 0x51, 0x42, 0xF9, 0x1B, 0xF8, 0xE4, - 0x23, 0xB6, 0xB0, 0x1A, 0x5B, 0x7F, 0xB8, 0x66, 0x24, 0xBB, 0x06, 0xEB, 0x3B, 0xBA, 0x85, 0xB9, - 0x75, 0x9E, 0xBB, 0x1E, 0x19, 0x59, 0x53, 0xB0, 0xA4, 0x5C, 0xAD, 0x72, 0xA6, 0xDB, 0x0C, 0x2D, - 0x33, 0xBB, 0xDF, 0xD9, 0x4D, 0x8B, 0xC2, 0x35, 0x55, 0xF3, 0x8C, 0x84, 0xFC, 0x2F, 0x49, 0xFF, - 0xAC, 0xA0, 0x80, 0x96, 0x5D, 0x59, 0x2D, 0xA1, 0xA0, 0xE5, 0x5C, 0x98, 0x23, 0xF0, 0x11, 0x49, - 0x28, 0xDC, 0x2B, 0x7D, 0xC8, 0x51, 0x27, 0xCA, 0xA6, 0xFA, 0x19, 0x1B, 0xE2, 0x5E, 0x28, 0xCF, - 0x1D, 0xD0, 0x87, 0x83, 0x19, 0x1B, 0x62, 0x74, 0x9F, 0xC4, 0x38, 0x2C, 0x18, 0xE7, 0xE8, 0xA7, - 0x25, 0x1F, 0x33, 0xF2, 0x88, 0x39, 0x89, 0xA4, 0x7F, 0x90, 0x59, 0x1D, 0x40, 0x32, 0xB3, 0x29, - 0x27, 0x9B, 0x3E, 0x64, 0xA6, 0x25, 0x5C, 0x0B, 0x44, 0xE5, 0x4B, 0xAE, 0xDF, 0xCB, 0x52, 0x27, - 0x94, 0x10, 0x62, 0xA2, 0x98, 0x8F, 0xB2, 0x3A, 0x8B, 0xDB, 0x9A, 0x12, 0x70, 0xE0, 0x86, 0x20, - 0x6E, 0x11, 0xA5, 0x8F, 0x16, 0x89, 0x5C, 0x98, 0x2D, 0x12, 0xE3, 0x56, 0x83, 0xEC, 0x11, 0x57, - 0xFA, 0x44, 0xC7, 0x42, 0x51, 0xF8, 0x52, 0x07, 0xC4, 0xDF, 0xA0, 0xAF, 0x2A, 0xA1, 0xFA, 0x75, - 0x9C, 0x41, 0xF9, 0xBA, 0x67, 0x39, 0xE7, 0xF3, 0x57, 0x72, 0xAF, 0x85, 0xA5, 0x78, 0xEF, 0x55, - 0x39, 0xE7, 0xF3, 0x57, 0x22, 0xD9, 0xEE, 0xAB, 0xA0, 0x93, 0xA8, 0x59, 0xF3, 0x55, 0x92, 0x54, - 0x93, 0xA8, 0x58, 0x5D, 0x57, 0xB4, 0x0B, 0xBA, 0x5C, 0xDA, 0x8D, 0x4F, 0x61, 0x1C, 0xF6, 0x2C, - 0x29, 0x4C, 0x8F, 0x84, 0x65, 0xC7, 0x66, 0x63, 0x0D, 0xF9, 0x91, 0xF0, 0x6F, 0xB6, 0x03, 0x24, - 0x06, 0xD9, 0x8A, 0x2B, 0xEA, 0x38, 0x96, 0x1C, 0x25, 0x40, 0x5B, 0x3A, 0xD5, 0xD1, 0x23, 0x5A, - 0x09, 0x28, 0x06, 0x0B, 0xCC, 0x9B, 0xAA, 0xC3, 0x31, 0x0D, 0x3F, 0xEA, 0x24, 0x41, 0x37, 0xC1, - 0xBB, 0xE4, 0xBC, 0x23, 0x91, 0x9F, 0xCA, 0x5A, 0x7C, 0xC5, 0xF7, 0x64, 0x0D, 0xDA, 0x07, 0x78, - 0xDA, 0xA2, 0x3A, 0x5E, 0x23, 0x2E, 0x32, 0xB0, 0xB8, 0x0A, 0xD2, 0x19, 0x5D, 0xBD, 0xBF, 0x1A, - 0xC0, 0xE7, 0x38, 0x13, 0x7F, 0x55, 0x52, 0x64, 0x5B, 0x9B, 0x18, 0xEF, 0xE8, 0x41, 0x2D, 0x39, - 0xE1, 0xE4, 0x5B, 0x13, 0x2B, 0x2B, 0x09, 0x8C, 0x48, 0x22, 0x65, 0xB0, 0xEF, 0x57, 0x7A, 0xA6, - 0x27, 0x4E, 0xBA, 0xF4, 0x96, 0x21, 0xA8, 0x85, 0xFD, 0x63, 0xC7, 0xA7, 0xF4, 0x6E, 0xBB, 0x71, - 0xDC, 0xCE, 0x97, 0xB2, 0x16, 0x1D, 0xA1, 0xD3, 0x38, 0xEE, 0x2C, 0x77, 0x04, 0xAC, 0x48, 0xBD, - 0xDC, 0x11, 0x40, 0xFF, 0xEB, 0x2D, 0x77, 0x84, 0xBD, 0xC6, 0xF1, 0xDE, 0x72, 0x47, 0xD8, 0x6F, - 0x1C, 0xEF, 0x2F, 0x77, 0x84, 0x83, 0xC6, 0xF1, 0xC1, 0x72, 0x47, 0x78, 0xDD, 0x38, 0x7E, 0x5D, - 0x34, 0x82, 0x53, 0x65, 0xE8, 0x0C, 0x82, 0x93, 0xB6, 0xDD, 0x39, 0x54, 0x9E, 0x50, 0xD5, 0x01, - 0xB6, 0xC4, 0xBD, 0x5B, 0xEB, 0x2C, 0xBB, 0x73, 0xBC, 0x1F, 0xBF, 0xC1, 0xB7, 0x90, 0x13, 0x06, - 0x0F, 0xB3, 0x57, 0xB7, 0xB0, 0x92, 0x71, 0x69, 0xBA, 0x9E, 0xE3, 0xE3, 0x79, 0xF0, 0x24, 0xAA, - 0x05, 0x82, 0x5A, 0x0F, 0xA7, 0xB0, 0x50, 0x55, 0xE8, 0x74, 0x85, 0xE3, 0x72, 0xA6, 0xF3, 0x4A, - 0xB7, 0x57, 0x33, 0xCD, 0x67, 0xB7, 0x60, 0x06, 0x82, 0x09, 0x6D, 0x22, 0xB2, 0xBC, 0xA7, 0xBE, - 0xCC, 0x2A, 0x58, 0xCD, 0x1C, 0x2A, 0x04, 0x79, 0xF7, 0xE8, 0x46, 0x45, 0xB7, 0xFC, 0x52, 0x26, - 0xB4, 0x2A, 0x0E, 0x8F, 0x34, 0x83, 0x89, 0xCD, 0x4D, 0x97, 0x40, 0x1F, 0x9A, 0xD0, 0xF0, 0x8B, - 0x5C, 0x61, 0x39, 0xDA, 0xAA, 0xF8, 0xA2, 0xBE, 0x06, 0x2F, 0xC9, 0x99, 0x99, 0xAE, 0x4A, 0x93, - 0x21, 0x4F, 0x47, 0xDC, 0x2A, 0x34, 0xF8, 0x86, 0xE3, 0x93, 0x61, 0x19, 0x59, 0x0D, 0x68, 0x24, - 0xE8, 0x0F, 0xE3, 0x70, 0x6D, 0xB6, 0xDA, 0xD1, 0x9A, 0x69, 0x1C, 0x55, 0x8D, 0xBB, 0xB5, 0x20, - 0x9A, 0xC8, 0xE2, 0x76, 0xAB, 0xDB, 0x27, 0x06, 0x40, 0xD4, 0xD7, 0x40, 0xBF, 0x90, 0x19, 0xCC, - 0xE0, 0x19, 0x0B, 0x1E, 0x2E, 0x17, 0x85, 0x52, 0xE6, 0xB0, 0x22, 0xEC, 0x91, 0x4B, 0xB5, 0x2D, - 0xDC, 0x4F, 0x58, 0xAF, 0x22, 0x27, 0x10, 0xCD, 0xC3, 0xA6, 0xA0, 0x3B, 0x95, 0xA9, 0x84, 0xDF, - 0xA5, 0x64, 0x29, 0x5F, 0x47, 0x44, 0xAA, 0x7C, 0x6B, 0x2A, 0xD7, 0xD9, 0x1F, 0x83, 0x3A, 0x73, - 0x46, 0x34, 0xF3, 0xC7, 0x64, 0xB2, 0x5C, 0x7F, 0xAC, 0xD4, 0x21, 0x1B, 0x09, 0x9F, 0xA1, 0xBD, - 0x0C, 0x33, 0xCA, 0x89, 0xDC, 0x17, 0x6A, 0x9D, 0xCF, 0x0C, 0xA9, 0x6B, 0xB6, 0x35, 0x99, 0x6C, - 0xAF, 0x3A, 0xAC, 0xE3, 0xA4, 0x4F, 0xC5, 0x62, 0x65, 0x10, 0xAD, 0xA7, 0x98, 0xA0, 0x36, 0x0E, - 0x1D, 0x6B, 0x22, 0xAB, 0x22, 0xBA, 0x3C, 0x08, 0xF2, 0x08, 0xB0, 0x4C, 0x1A, 0x4E, 0x82, 0x3F, - 0x6D, 0x5C, 0x1C, 0x53, 0x69, 0x01, 0x2D, 0x76, 0x8B, 0xF6, 0x17, 0xAA, 0x3D, 0xEB, 0x2B, 0x86, - 0xF1, 0x88, 0x0A, 0x27, 0x7C, 0xA1, 0x05, 0x61, 0xBA, 0xAE, 0xAE, 0xCD, 0xF4, 0x87, 0xF7, 0x61, - 0xE8, 0xBF, 0xAD, 0x18, 0x2A, 0xE6, 0xF3, 0x87, 0x97, 0x35, 0xDF, 0xA1, 0x0B, 0x31, 0xF9, 0x02, - 0x7C, 0xA3, 0xDF, 0x39, 0x32, 0x31, 0xFF, 0xC9, 0xC4, 0xF2, 0x85, 0xC3, 0xB7, 0xA2, 0x69, 0xA2, - 0x18, 0xE3, 0xC7, 0x93, 0x8B, 0xE0, 0xFA, 0x00, 0xE6, 0xEF, 0xD0, 0x65, 0xDA, 0x50, 0xFF, 0x86, - 0x97, 0x0A, 0x0A, 0x3A, 0xAB, 0x01, 0xD7, 0x9F, 0x58, 0x18, 0xBA, 0x7C, 0xE6, 0x1E, 0xB1, 0x5B, - 0xCB, 0x51, 0xC7, 0x5B, 0x9D, 0xCE, 0x01, 0x80, 0xFA, 0x25, 0xF9, 0xAD, 0x49, 0x1C, 0xAC, 0xE2, - 0xB7, 0x56, 0x58, 0x4F, 0x29, 0x1D, 0xF9, 0x4B, 0x19, 0xFF, 0x53, 0x9B, 0xAE, 0xDC, 0x93, 0xAD, - 0x90, 0x95, 0x7C, 0xE4, 0x48, 0x2B, 0x7F, 0xAC, 0x86, 0x89, 0x04, 0x83, 0x55, 0x65, 0x1F, 0xA2, - 0x1D, 0xDB, 0x52, 0x5A, 0x5F, 0x5B, 0x4A, 0x2B, 0x17, 0xFA, 0x7D, 0x3C, 0x26, 0x44, 0xC1, 0xAA, - 0x6D, 0xE0, 0x26, 0xDB, 0xAB, 0x8F, 0x11, 0x13, 0xE4, 0x4F, 0x06, 0x24, 0x51, 0x93, 0x51, 0x52, - 0x3B, 0x50, 0x89, 0x3B, 0x11, 0xC7, 0x18, 0x12, 0x3C, 0x5A, 0x68, 0x75, 0x53, 0x35, 0x7C, 0xA4, - 0x61, 0xCC, 0xCE, 0x6D, 0xBA, 0xB2, 0x5A, 0xB7, 0x15, 0x2D, 0x8B, 0x7D, 0x72, 0xD3, 0xC7, 0x24, - 0xB4, 0xBE, 0x41, 0x81, 0xFF, 0x41, 0x83, 0xA8, 0xFF, 0x69, 0xEB, 0x70, 0xB2, 0x21, 0xE3, 0x52, - 0x64, 0x1C, 0xA2, 0x5F, 0x15, 0x02, 0x0E, 0x1A, 0x3D, 0x2D, 0xE9, 0x66, 0x28, 0xD6, 0x59, 0x9A, - 0xF5, 0xE0, 0x11, 0x44, 0xA3, 0xC9, 0x73, 0xBF, 0xB0, 0x13, 0xAB, 0x98, 0x4B, 0x77, 0x8E, 0x35, - 0x9D, 0x05, 0x71, 0x0C, 0x3E, 0x09, 0x9D, 0xD9, 0x15, 0x4D, 0x9F, 0xFC, 0xF2, 0x2D, 0x67, 0x09, - 0x73, 0x2A, 0xCB, 0xBE, 0x6D, 0x58, 0x8A, 0xF6, 0x89, 0x3F, 0xBC, 0xD7, 0x9D, 0x09, 0x96, 0x48, - 0x3A, 0xD7, 0xEF, 0xCB, 0x54, 0x9A, 0x29, 0xA5, 0xA4, 0xCC, 0x74, 0xDE, 0x38, 0x96, 0x50, 0x0E, - 0x1E, 0x14, 0x64, 0x0B, 0x22, 0x06, 0x45, 0xEE, 0x79, 0xDE, 0xD7, 0xA0, 0xC9, 0xDF, 0xB8, 0x83, - 0x5C, 0xF1, 0x8B, 0x3D, 0x72, 0x14, 0x50, 0x39, 0x32, 0x9D, 0xD6, 0xEE, 0xDB, 0xAD, 0xF6, 0x3C, - 0x76, 0xF0, 0xEC, 0xE3, 0x2E, 0x87, 0xA0, 0xF3, 0xDC, 0x13, 0xCE, 0x10, 0x2E, 0x51, 0x20, 0xCC, - 0xE5, 0xA2, 0x10, 0xDA, 0xEC, 0xD4, 0x44, 0x77, 0x68, 0xA1, 0xA3, 0x21, 0xE8, 0xAA, 0x0F, 0xBE, - 0x08, 0x81, 0x3B, 0xC5, 0xB8, 0x15, 0xA9, 0x65, 0x62, 0x1A, 0x03, 0x5B, 0x71, 0xBE, 0xBE, 0xF7, - 0xE9, 0x32, 0xD1, 0xE5, 0x9C, 0x2E, 0x26, 0x61, 0x56, 0x43, 0x39, 0x2B, 0x92, 0x52, 0xC3, 0x14, - 0xB8, 0xE1, 0xF5, 0xA4, 0xF0, 0x34, 0x5B, 0x2B, 0x8D, 0x2C, 0xC2, 0xF8, 0x83, 0xC9, 0x07, 0x7B, - 0x89, 0x7E, 0x1B, 0x82, 0xC7, 0x3F, 0xAD, 0xA3, 0xD6, 0xCD, 0xD9, 0x94, 0xB4, 0x84, 0x49, 0xE5, - 0x94, 0x7B, 0x4A, 0x04, 0x03, 0x56, 0xE3, 0x81, 0x15, 0x9D, 0xC6, 0x8A, 0x85, 0x2B, 0x5A, 0x6F, - 0x88, 0x5B, 0xC8, 0xF5, 0x15, 0xDD, 0x74, 0xE5, 0x2D, 0x36, 0x5E, 0xD6, 0xB1, 0x21, 0x57, 0x3C, - 0x1F, 0x6F, 0xC8, 0x81, 0x1A, 0xD8, 0x44, 0x79, 0x64, 0xD2, 0x8B, 0x1A, 0xD4, 0x22, 0x98, 0x3A, - 0x0A, 0x57, 0x1E, 0x5E, 0x97, 0x83, 0x3E, 0xC4, 0xF1, 0x18, 0x21, 0x94, 0x1C, 0xEA, 0x1C, 0xA4, - 0x2B, 0x4C, 0x07, 0xB6, 0xEE, 0x6E, 0x56, 0x4F, 0x8B, 0x7F, 0x27, 0xBE, 0x67, 0x05, 0x5B, 0xFF, - 0xC5, 0x86, 0xCD, 0x9C, 0xD6, 0x8F, 0xC7, 0xAF, 0xB0, 0x70, 0x9C, 0x1A, 0x62, 0x63, 0x71, 0xB9, - 0x01, 0xEA, 0x61, 0x65, 0x7E, 0x83, 0x69, 0x93, 0x5F, 0x31, 0xF6, 0x66, 0xC5, 0x06, 0xDA, 0xDC, - 0xD1, 0x2D, 0x4D, 0x57, 0xC9, 0x5C, 0xA0, 0x86, 0xDC, 0x3D, 0xCA, 0x47, 0x5B, 0x0C, 0x1A, 0x2B, - 0xF4, 0xC8, 0x76, 0x2C, 0xCD, 0x17, 0x4E, 0x21, 0xF7, 0xE2, 0x74, 0x4C, 0xE7, 0xB6, 0x3B, 0x78, - 0x8D, 0x1D, 0x38, 0x7C, 0xA0, 0xA9, 0x4C, 0x64, 0x20, 0x53, 0x82, 0x9D, 0xC2, 0xD1, 0x9E, 0x37, - 0xBE, 0xC7, 0x37, 0x97, 0x16, 0x15, 0xDF, 0xE1, 0x45, 0x6A, 0xF4, 0x2D, 0x54, 0xF5, 0x3A, 0xA6, - 0xFA, 0x46, 0x10, 0x8F, 0x4E, 0x94, 0x6B, 0xDD, 0xF4, 0x01, 0xF1, 0xCB, 0x15, 0x86, 0xC9, 0x71, - 0x5D, 0x12, 0xE1, 0x8F, 0xAF, 0x67, 0x94, 0x66, 0xD9, 0x7F, 0x58, 0xDC, 0x9D, 0x30, 0xCA, 0x3D, - 0x7A, 0xE2, 0xB2, 0x36, 0x88, 0xBF, 0x21, 0x93, 0x08, 0x11, 0xD6, 0xA7, 0x7D, 0x42, 0xBC, 0x0D, - 0x29, 0x63, 0x26, 0x11, 0x5E, 0x2A, 0x41, 0x30, 0x65, 0xE8, 0xF1, 0xA8, 0xB3, 0xD3, 0x44, 0xAE, - 0x7A, 0x8C, 0x4E, 0x19, 0x9C, 0xB6, 0x39, 0xA6, 0x25, 0xEF, 0x01, 0xE0, 0xB6, 0x3A, 0xB0, 0x9E, - 0xC7, 0xED, 0xE7, 0x57, 0xF1, 0xA6, 0x64, 0x6D, 0xA7, 0xB2, 0x4A, 0x31, 0x45, 0x36, 0xB8, 0x13, - 0xE0, 0x00, 0x52, 0x3D, 0xCE, 0x42, 0xD1, 0x12, 0x43, 0xDA, 0xB9, 0x3D, 0x2C, 0x52, 0x33, 0x7C, - 0x81, 0x2A, 0x3E, 0x79, 0x3A, 0x37, 0x5E, 0xC7, 0x67, 0x1B, 0xB9, 0x14, 0x43, 0x1F, 0x99, 0xC0, - 0x29, 0xC8, 0x60, 0x27, 0xA0, 0x03, 0x8F, 0x3F, 0x70, 0x2F, 0x26, 0xD3, 0x27, 0xD4, 0x10, 0xD3, - 0x32, 0x73, 0xAF, 0x43, 0xF2, 0x14, 0x86, 0x64, 0xD7, 0x25, 0x62, 0x51, 0x4A, 0xEA, 0x0E, 0xA3, - 0x58, 0xCF, 0xA8, 0x39, 0x08, 0xAE, 0x88, 0xA2, 0xFA, 0x7D, 0xAF, 0xD5, 0xD9, 0x6B, 0x75, 0xF6, - 0xCB, 0x68, 0x0D, 0x02, 0x16, 0xC7, 0x6F, 0xE1, 0xE0, 0x19, 0x51, 0xFC, 0x47, 0x54, 0x46, 0x16, - 0x5D, 0xF6, 0xE5, 0x57, 0xA7, 0x8A, 0x13, 0x1E, 0xBE, 0x9D, 0x6E, 0x03, 0x28, 0xF3, 0x1B, 0x7C, - 0x68, 0xB7, 0x8B, 0xB9, 0x85, 0x84, 0xE8, 0x83, 0xAE, 0x79, 0xE3, 0xA3, 0x5E, 0xBB, 0x4D, 0xD1, - 0x3D, 0x80, 0x22, 0xB2, 0xE3, 0x52, 0x06, 0x9B, 0xF4, 0x39, 0xE5, 0x78, 0x5D, 0xF7, 0x5E, 0xFF, - 0x63, 0x81, 0xE9, 0x46, 0xAE, 0xBD, 0x2E, 0xC3, 0x6B, 0x19, 0xF5, 0x3A, 0xA9, 0x28, 0x96, 0x4C, - 0x95, 0x3B, 0xD4, 0x0D, 0xC0, 0x1E, 0x91, 0xCC, 0xF5, 0x0E, 0x98, 0x14, 0xFD, 0x49, 0xE6, 0x0C, - 0xFF, 0x6E, 0xA2, 0x7B, 0x01, 0x1A, 0xBC, 0xA7, 0xE7, 0x96, 0xA9, 0x92, 0x4F, 0x5B, 0x14, 0x66, - 0xA8, 0xBD, 0xFF, 0xA2, 0xE8, 0x1E, 0xFA, 0xBF, 0xEF, 0x66, 0x0F, 0x2A, 0x5E, 0x64, 0xA7, 0x97, - 0x9F, 0xE6, 0xF0, 0xC9, 0xAC, 0xCD, 0x2F, 0x30, 0xC0, 0x64, 0x38, 0x2B, 0x7C, 0x53, 0xF7, 0x5E, - 0xB9, 0xD3, 0x83, 0xE1, 0xEE, 0x91, 0x09, 0x53, 0x04, 0x5D, 0xA0, 0x30, 0x80, 0x05, 0xD2, 0x0F, - 0xC2, 0x63, 0x7A, 0xA9, 0x13, 0x66, 0xCA, 0x61, 0xF6, 0x18, 0x68, 0x17, 0x03, 0x37, 0xFB, 0x67, - 0xAB, 0x76, 0xD8, 0xCB, 0x76, 0x0C, 0x4A, 0xA2, 0x33, 0xAE, 0xA6, 0x6A, 0xF0, 0x40, 0x16, 0x76, - 0xA6, 0xB2, 0xBF, 0x50, 0x9C, 0x82, 0xFE, 0xBD, 0x81, 0x87, 0xA2, 0x53, 0x85, 0x42, 0xC4, 0x51, - 0x69, 0x27, 0xEC, 0x20, 0xB4, 0xFE, 0x5C, 0xC2, 0xFE, 0x60, 0x81, 0x2D, 0x7A, 0x5A, 0x29, 0x27, - 0xF1, 0xB4, 0xAF, 0xF8, 0xDC, 0x16, 0xCD, 0x43, 0xDC, 0x6E, 0x1C, 0xDF, 0x58, 0x94, 0x8A, 0xA4, - 0x62, 0x2A, 0xE2, 0x4E, 0xE3, 0xF8, 0x54, 0x71, 0x79, 0xE5, 0x76, 0x00, 0xB8, 0x4F, 0xB7, 0xFD, - 0xF5, 0xC8, 0x60, 0xDC, 0xF7, 0x65, 0x28, 0xB1, 0xD8, 0x1D, 0xDD, 0x84, 0xD3, 0xC0, 0xB1, 0x28, - 0x02, 0x59, 0x5C, 0x75, 0x3A, 0x0C, 0xE6, 0x2A, 0x5C, 0x33, 0x82, 0x78, 0xBC, 0x3E, 0xC5, 0xE3, - 0x7D, 0x36, 0xF1, 0x4B, 0xF2, 0x0F, 0x6B, 0xAD, 0x7D, 0x78, 0xB1, 0x61, 0x8D, 0x6E, 0xAD, 0xC1, - 0xF9, 0x99, 0x92, 0x99, 0x10, 0x7C, 0xE9, 0x8E, 0x41, 0x61, 0xE9, 0x79, 0xAC, 0xFD, 0x0E, 0x50, - 0x1E, 0x9C, 0x33, 0x9C, 0xCE, 0x2A, 0x3C, 0x7D, 0x8A, 0xAB, 0xDE, 0xD7, 0x6B, 0x0B, 0x24, 0xE5, - 0x74, 0xA2, 0xAB, 0x0E, 0x80, 0x9C, 0x6C, 0xD1, 0x8C, 0x8C, 0x7C, 0x1E, 0xF1, 0xD8, 0x1D, 0xCC, - 0x74, 0x16, 0x96, 0xC8, 0x4B, 0xD4, 0x7A, 0x9E, 0x75, 0xF8, 0x79, 0x56, 0xFE, 0x3E, 0x33, 0x20, - 0x2F, 0x52, 0x39, 0xCB, 0x5B, 0x70, 0xCA, 0xEA, 0x9B, 0x51, 0xEE, 0x0B, 0xF2, 0x16, 0xCC, 0xE3, - 0x16, 0x23, 0x10, 0xE6, 0xD2, 0x1C, 0x95, 0x6F, 0x4C, 0xAE, 0xA3, 0x50, 0x60, 0xC3, 0x41, 0xD8, - 0x16, 0x68, 0x5D, 0xDB, 0x47, 0xD5, 0x4C, 0x08, 0x8B, 0x6B, 0x95, 0x81, 0x5F, 0x10, 0xFA, 0x69, - 0x20, 0x2B, 0x83, 0x55, 0x23, 0x36, 0x51, 0x62, 0x04, 0x32, 0x81, 0xA8, 0x63, 0x9D, 0xDF, 0x23, - 0xE2, 0x19, 0x62, 0x31, 0x52, 0x91, 0xE4, 0xE4, 0x0B, 0x31, 0xF5, 0xCF, 0xE0, 0x43, 0x9F, 0xE0, - 0xC6, 0x0C, 0x0C, 0x76, 0xC1, 0xF7, 0x0C, 0x0B, 0x7E, 0x80, 0xC0, 0x37, 0xD9, 0xA1, 0xBC, 0x4C, - 0x26, 0xCE, 0x69, 0x87, 0x8A, 0x3C, 0x82, 0xD2, 0xEA, 0xFA, 0xCE, 0x3D, 0x7F, 0x74, 0x91, 0x94, - 0x15, 0xE8, 0xCC, 0x41, 0xDB, 0x63, 0xF4, 0x8A, 0x17, 0x46, 0x4F, 0x2A, 0x9C, 0x52, 0x29, 0x0D, - 0xC3, 0x69, 0x3A, 0xD8, 0xB8, 0x7B, 0x78, 0x38, 0xFD, 0x66, 0x9E, 0xF2, 0xD1, 0x75, 0xEA, 0xA1, - 0xE5, 0x8C, 0x35, 0xE5, 0x4D, 0x74, 0x95, 0x75, 0xCF, 0x08, 0xCA, 0x96, 0x12, 0xF2, 0xA7, 0xEF, - 0x2F, 0x78, 0x21, 0xBB, 0x0A, 0x4A, 0x14, 0x7E, 0x08, 0x8B, 0xD0, 0x62, 0xE1, 0x06, 0xA2, 0x40, - 0x2F, 0xFD, 0x1D, 0x24, 0x39, 0x2E, 0x95, 0xDA, 0x80, 0x70, 0xE2, 0x28, 0x1F, 0x27, 0x38, 0x61, - 0xA2, 0x44, 0x62, 0x0C, 0xD8, 0x3C, 0x86, 0xDE, 0x60, 0x4A, 0xCA, 0x34, 0xBA, 0xA3, 0xEF, 0x90, - 0xEE, 0x44, 0xB2, 0x14, 0x83, 0x0E, 0x05, 0x99, 0x12, 0x4D, 0x11, 0xC4, 0x28, 0x88, 0xEE, 0x85, - 0xD3, 0xD5, 0x12, 0x09, 0x4B, 0x62, 0x60, 0x05, 0xD2, 0x12, 0x2D, 0x96, 0x4A, 0x5C, 0x52, 0x40, - 0x13, 0xBA, 0xA8, 0x1C, 0xA2, 0x04, 0x79, 0x45, 0xE8, 0xB2, 0x84, 0x01, 0x77, 0x46, 0x33, 0x49, - 0x0C, 0x18, 0x21, 0xED, 0x98, 0x71, 0x85, 0xC4, 0x35, 0x52, 0x17, 0x51, 0x0B, 0x39, 0x12, 0x28, - 0x52, 0xDA, 0x60, 0x18, 0x59, 0xDA, 0x27, 0x65, 0x92, 0x73, 0x63, 0x3D, 0x78, 0x7F, 0xF1, 0x07, - 0x8C, 0xD4, 0xF2, 0xEF, 0xBE, 0xD5, 0x6A, 0x07, 0x5C, 0xB4, 0xF6, 0xF5, 0xDC, 0xCC, 0x6F, 0x91, - 0x4C, 0x2D, 0xF9, 0x56, 0x32, 0x72, 0x69, 0xFC, 0xC4, 0x1F, 0x00, 0x5C, 0x35, 0xD8, 0xC7, 0x66, - 0xEC, 0x64, 0x91, 0xEE, 0xD1, 0x48, 0x36, 0xC0, 0x3F, 0xE9, 0x6A, 0x1D, 0x1E, 0x94, 0x0B, 0xF7, - 0x5F, 0x9E, 0x8C, 0x73, 0x66, 0x58, 0xAE, 0xAC, 0xEB, 0xAE, 0xFA, 0x8E, 0x83, 0x71, 0x1E, 0xC8, - 0x5C, 0xD1, 0x7F, 0x8C, 0xA6, 0xED, 0x4A, 0x8E, 0x6B, 0x99, 0xFC, 0x99, 0xB0, 0xBC, 0x7A, 0xB1, - 0x33, 0xD4, 0xD9, 0x3A, 0x09, 0x22, 0xAE, 0xED, 0xCE, 0xF3, 0xA6, 0x1F, 0x2A, 0x53, 0xC1, 0x5D, - 0xE7, 0x4D, 0x3F, 0x90, 0x92, 0x57, 0x76, 0x79, 0x39, 0x9D, 0xC5, 0xD3, 0xA5, 0x3C, 0x89, 0x5E, - 0x5F, 0x22, 0x42, 0x06, 0xCE, 0x99, 0x37, 0x7C, 0xC8, 0x1D, 0x4C, 0x66, 0xCA, 0xFA, 0x96, 0x4B, - 0x6E, 0x8B, 0xC2, 0xD0, 0x46, 0xC9, 0xCE, 0x3A, 0xED, 0xF6, 0xFE, 0x2E, 0xFC, 0x38, 0x08, 0x05, - 0x82, 0xD0, 0x69, 0x59, 0x84, 0xD8, 0x8E, 0x5E, 0xCC, 0x1D, 0x64, 0xB8, 0x47, 0x6B, 0x71, 0xED, - 0x38, 0x9D, 0xCE, 0x25, 0x9A, 0xB0, 0x01, 0xA9, 0xE6, 0x12, 0x43, 0xA1, 0x9B, 0x52, 0x5C, 0x45, - 0x8E, 0xC5, 0x82, 0xC1, 0xD8, 0x96, 0xBB, 0xBD, 0x06, 0x57, 0x8E, 0x48, 0xAB, 0x81, 0x12, 0x98, - 0xBC, 0x64, 0xCC, 0xC1, 0xDF, 0xB8, 0x8D, 0x02, 0xED, 0x60, 0xBA, 0x5C, 0x97, 0x2C, 0xF8, 0x90, - 0x4C, 0xB1, 0xB0, 0xB9, 0x52, 0x4C, 0x48, 0x9F, 0x29, 0xE8, 0x57, 0xFA, 0x32, 0x71, 0xB6, 0xED, - 0xF2, 0xAF, 0x11, 0x6B, 0xB5, 0xFE, 0x5C, 0x5B, 0xAE, 0xA2, 0xAB, 0x35, 0xD8, 0x80, 0xE4, 0xF1, - 0x06, 0xCD, 0x84, 0x9F, 0xED, 0x72, 0xCE, 0x38, 0x39, 0xF9, 0x9B, 0xCB, 0x4F, 0x17, 0xBF, 0x86, - 0xA7, 0x1C, 0xFD, 0xB5, 0xEA, 0x73, 0x2E, 0x36, 0x93, 0xF5, 0x38, 0xE9, 0x04, 0x1C, 0x62, 0x45, - 0x5C, 0x24, 0x57, 0x10, 0x26, 0xDD, 0x07, 0xDD, 0x1B, 0xCB, 0xEC, 0x5E, 0x20, 0xAA, 0x99, 0x21, - 0xA3, 0x20, 0x19, 0x8D, 0xD4, 0x08, 0x4D, 0xFA, 0x17, 0xBF, 0x94, 0x83, 0x2E, 0xBA, 0x49, 0x0B, - 0x1F, 0x75, 0xE4, 0xEF, 0x0B, 0xD4, 0xFC, 0x0D, 0x35, 0xA6, 0x73, 0x09, 0xA9, 0xD4, 0xCB, 0xA7, - 0x32, 0xCE, 0x6C, 0xC9, 0x43, 0x90, 0xE6, 0x18, 0xED, 0x79, 0xAE, 0x7C, 0xE2, 0x02, 0x05, 0x62, - 0x7B, 0x79, 0x54, 0x81, 0x83, 0xC6, 0xEE, 0xB4, 0x52, 0x66, 0x84, 0x20, 0xC8, 0x9E, 0x68, 0xD9, - 0x3B, 0xAE, 0xFC, 0xFB, 0xAE, 0x0E, 0xFB, 0x68, 0xF9, 0x25, 0x2E, 0xBC, 0xF2, 0x2F, 0xBF, 0x0E, - 0xA8, 0x17, 0x77, 0xA1, 0x6E, 0x30, 0x35, 0xC6, 0x5E, 0x0D, 0xFD, 0x00, 0x2A, 0x74, 0xF6, 0x99, - 0x74, 0x33, 0x29, 0xDF, 0x53, 0xFE, 0x35, 0xDB, 0xF2, 0x15, 0xBA, 0x6C, 0x76, 0x12, 0xCF, 0xDD, - 0x14, 0xB8, 0x33, 0xED, 0xB2, 0x31, 0x82, 0x2A, 0x88, 0xEE, 0x53, 0xA4, 0x13, 0x94, 0x41, 0x76, - 0x37, 0xCB, 0xE6, 0x66, 0xEC, 0x0A, 0x25, 0x80, 0xEC, 0x73, 0xD2, 0x05, 0x2B, 0xF0, 0x88, 0xCF, - 0x77, 0x58, 0x45, 0x90, 0xE8, 0x23, 0x90, 0x0C, 0xEA, 0x65, 0x15, 0x30, 0x40, 0x8E, 0xB4, 0x5C, - 0x96, 0x53, 0x58, 0xD3, 0x69, 0x86, 0xC7, 0xC1, 0x62, 0x0C, 0x23, 0x36, 0xAF, 0x90, 0x5F, 0xA4, - 0xCD, 0xB6, 0x26, 0x76, 0xE1, 0x2E, 0xCA, 0x2A, 0xBA, 0x0B, 0x73, 0x89, 0xFD, 0xC5, 0xF9, 0x43, - 0x7B, 0xB1, 0x2E, 0xF6, 0x90, 0xC5, 0x2C, 0xD6, 0xC5, 0x7E, 0xE3, 0xB8, 0xB7, 0xE0, 0x2C, 0x30, - 0x87, 0x4D, 0xFB, 0x45, 0x70, 0x38, 0xCC, 0x73, 0xFE, 0x98, 0x96, 0x9F, 0x2E, 0xE4, 0x5F, 0x00, - 0xAB, 0x17, 0x64, 0xC6, 0xAA, 0xC9, 0x69, 0x4C, 0x78, 0x0A, 0x06, 0xA9, 0xDE, 0x29, 0x95, 0x68, - 0x7A, 0xD9, 0xF0, 0x02, 0xCE, 0x17, 0xE5, 0x76, 0xB3, 0xDD, 0x35, 0x8E, 0xA7, 0xD9, 0xE4, 0xFB, - 0x8E, 0xE5, 0x59, 0xC0, 0xDB, 0x2A, 0x79, 0xEB, 0xA4, 0x74, 0x99, 0x31, 0xF3, 0x1A, 0xFC, 0x77, - 0x06, 0xFD, 0xFE, 0x5C, 0xDE, 0x3B, 0x57, 0x17, 0xF3, 0x38, 0xEF, 0x60, 0x42, 0xFE, 0x7F, 0x62, - 0xF3, 0x34, 0x86, 0xBD, 0xF8, 0x3C, 0x1C, 0xAE, 0x87, 0xE7, 0xCF, 0x59, 0x98, 0x6B, 0x1B, 0x8B, - 0x0A, 0xA0, 0x45, 0xE4, 0x0C, 0x7B, 0xD5, 0xD5, 0x68, 0xE9, 0x01, 0xEE, 0xA0, 0xAF, 0x16, 0x16, - 0x5D, 0x41, 0x2C, 0x20, 0x41, 0x63, 0x0B, 0x96, 0xBF, 0x1D, 0xAD, 0x4F, 0x70, 0x65, 0x3D, 0x34, - 0x2F, 0x4C, 0xEE, 0x8C, 0x1E, 0xD9, 0x16, 0x00, 0x65, 0x9B, 0x0A, 0x1F, 0x78, 0xBE, 0x63, 0x46, - 0xDE, 0xB1, 0x86, 0xC3, 0x08, 0x5D, 0x87, 0x00, 0x5C, 0x7F, 0xC7, 0xA1, 0x3B, 0xCE, 0x6D, 0xAA, - 0x4B, 0x27, 0xA2, 0x87, 0x97, 0x55, 0x35, 0xE3, 0x94, 0x86, 0x89, 0xC4, 0xCF, 0x68, 0x3A, 0xFE, - 0x16, 0x8F, 0x97, 0x5D, 0x42, 0x23, 0x18, 0x7C, 0xD9, 0xC9, 0x1B, 0x65, 0x81, 0x43, 0x59, 0x44, - 0xE3, 0xCE, 0xFF, 0xFE, 0x1D, 0x5D, 0xF6, 0x2D, 0x4A, 0x3F, 0xCA, 0x14, 0x9F, 0xAA, 0x62, 0x70, - 0xAE, 0xDD, 0x29, 0x98, 0x87, 0x51, 0x18, 0x84, 0x7D, 0x17, 0x73, 0x68, 0x54, 0xF0, 0x09, 0x5A, - 0x0F, 0xAC, 0x99, 0x70, 0xC5, 0xF5, 0x1D, 0x82, 0xCE, 0x40, 0x55, 0x8C, 0xC5, 0x98, 0x73, 0xB2, - 0xB3, 0xC6, 0xF1, 0xF5, 0xF4, 0x09, 0xA3, 0x47, 0x95, 0x38, 0xF3, 0x4C, 0x7F, 0xA9, 0x53, 0xAE, - 0x81, 0x2B, 0x5F, 0x73, 0xD8, 0x6A, 0x77, 0x1E, 0xC6, 0xFC, 0x9E, 0x73, 0x6F, 0xF7, 0x12, 0xED, - 0x3D, 0xEE, 0x7A, 0x30, 0xCB, 0x73, 0x71, 0x5F, 0x2B, 0xAB, 0x1E, 0x60, 0x15, 0x17, 0x9F, 0x4A, - 0x1A, 0xE8, 0x26, 0x9B, 0xD0, 0x2A, 0x91, 0xE7, 0x01, 0xF2, 0x7A, 0xD2, 0xB1, 0x41, 0xB3, 0x40, - 0x59, 0xC3, 0x58, 0x43, 0x65, 0x38, 0x44, 0xC8, 0x8B, 0xAA, 0x9D, 0x94, 0xB9, 0x08, 0xD8, 0xA3, - 0x69, 0x3C, 0x4E, 0xE9, 0x40, 0x06, 0xB7, 0x50, 0xAF, 0xA2, 0x16, 0x82, 0xBC, 0x1C, 0x8E, 0x4A, - 0x42, 0x02, 0x94, 0xCF, 0x21, 0x85, 0x68, 0xED, 0xAC, 0x11, 0x83, 0xB9, 0xC8, 0xCB, 0x34, 0x25, - 0xBA, 0xF0, 0x5C, 0xC0, 0x0E, 0xBE, 0xC6, 0xDB, 0xCF, 0x65, 0xF3, 0xC9, 0xC8, 0x4C, 0x9E, 0xA8, - 0x6C, 0x60, 0x2C, 0x34, 0xD0, 0x11, 0xAB, 0x9E, 0xBA, 0xD2, 0x8C, 0xB9, 0x61, 0xA3, 0x2F, 0x0D, - 0xA5, 0xA3, 0xD5, 0xB8, 0x6D, 0x58, 0x8F, 0xC2, 0xE9, 0x5D, 0x76, 0xA1, 0x63, 0x1E, 0x5C, 0x20, - 0x71, 0xCB, 0x43, 0x19, 0x5C, 0x95, 0xF9, 0x83, 0x66, 0x32, 0x63, 0xAD, 0x3D, 0x77, 0x4D, 0x04, - 0xF9, 0xD1, 0x96, 0xD4, 0x52, 0x3A, 0xA8, 0x6A, 0x1A, 0x1B, 0x87, 0xDF, 0x81, 0x74, 0x13, 0x44, - 0x47, 0xD5, 0x10, 0x9C, 0x97, 0xAB, 0x65, 0xC8, 0xFD, 0x76, 0x8F, 0x56, 0x6F, 0xFE, 0x9E, 0x86, - 0xEA, 0xC9, 0x49, 0xE4, 0x86, 0xE8, 0x51, 0x66, 0xD1, 0x52, 0x11, 0x79, 0xCF, 0x37, 0x51, 0xCD, - 0x9C, 0xF7, 0x60, 0x71, 0x8C, 0x29, 0x4C, 0x3D, 0x10, 0x7B, 0x7D, 0x9E, 0x5B, 0xAF, 0x02, 0xFD, - 0x72, 0xB5, 0x9C, 0xFC, 0xBD, 0xA2, 0x7A, 0x96, 0x33, 0xCD, 0x5D, 0x1B, 0x16, 0x97, 0x11, 0xCF, - 0x23, 0x29, 0x66, 0x97, 0xCB, 0xC7, 0x93, 0xF3, 0x28, 0xBA, 0x59, 0xAA, 0x8F, 0xAF, 0x27, 0x57, - 0x2A, 0xE8, 0x85, 0x3B, 0x18, 0x69, 0x81, 0x34, 0x82, 0xE2, 0x2E, 0xD6, 0x53, 0xA2, 0xBA, 0xAA, - 0x24, 0x0B, 0x08, 0x5A, 0xF2, 0xA2, 0xD5, 0xE7, 0x64, 0x7A, 0x7F, 0x12, 0x9D, 0x5D, 0x50, 0xCC, - 0xBF, 0x86, 0x2D, 0x5A, 0xEC, 0x04, 0x3A, 0x11, 0x4E, 0x93, 0x26, 0xDE, 0x46, 0x61, 0x00, 0xD4, - 0x44, 0xD1, 0x29, 0x2F, 0x02, 0x39, 0x5D, 0xA6, 0x96, 0xA9, 0x99, 0x8E, 0xB1, 0xA4, 0x92, 0x34, - 0x4F, 0x5E, 0xB1, 0xB6, 0x6C, 0xC9, 0x99, 0x6C, 0xA7, 0xB3, 0x61, 0x12, 0x67, 0x16, 0xCD, 0xBB, - 0x9C, 0x40, 0xC2, 0x58, 0x99, 0x99, 0x30, 0xE9, 0xF2, 0x2C, 0x65, 0xE4, 0xA6, 0x96, 0xB6, 0xD3, - 0xA6, 0x5A, 0x43, 0xA4, 0x58, 0xB5, 0x34, 0xCE, 0x17, 0x80, 0x1F, 0x8E, 0xC9, 0x9F, 0x2E, 0x89, - 0x33, 0x97, 0x13, 0x58, 0x42, 0x0A, 0xE7, 0x8B, 0x44, 0xD7, 0x95, 0x92, 0x50, 0x25, 0x1A, 0xCF, - 0x0C, 0x59, 0x2E, 0x8C, 0x38, 0x01, 0xDE, 0x44, 0xB6, 0xAA, 0x70, 0xED, 0x4F, 0x9D, 0xDF, 0x39, - 0xB1, 0xD8, 0x75, 0xAC, 0x57, 0x2A, 0x67, 0x78, 0xFE, 0xF1, 0xAC, 0x0F, 0x67, 0x51, 0x00, 0x56, - 0x1F, 0x3D, 0x2C, 0xF1, 0xD9, 0x12, 0x8F, 0xA1, 0xE8, 0xC8, 0x53, 0xCE, 0xA0, 0x8D, 0x55, 0x3B, - 0x98, 0x06, 0xF0, 0x83, 0xEC, 0xFA, 0x35, 0xAB, 0x39, 0xAC, 0xA2, 0x4A, 0x48, 0x08, 0x1D, 0x3A, - 0xB0, 0xB0, 0xD8, 0x28, 0xCE, 0xBD, 0x15, 0xE8, 0x0C, 0xB2, 0x0C, 0x36, 0x53, 0x64, 0x9A, 0xD1, - 0xCB, 0x3E, 0x3B, 0x11, 0xE5, 0xFD, 0x76, 0xD8, 0xF9, 0xA7, 0xC1, 0x0E, 0xFB, 0xA0, 0x78, 0xFC, - 0x41, 0xEA, 0xCF, 0x03, 0xFF, 0x0E, 0x7B, 0xBA, 0x56, 0xDC, 0xAF, 0xCF, 0xC8, 0xB4, 0x93, 0x1F, - 0x28, 0x4B, 0xCB, 0xBE, 0xEC, 0xC7, 0x4B, 0xA2, 0x07, 0x40, 0x5B, 0x96, 0xFA, 0x11, 0xA0, 0xD1, - 0x65, 0x3F, 0x96, 0x40, 0x73, 0x7F, 0x46, 0xCB, 0x78, 0x9F, 0xD8, 0x94, 0x27, 0xD0, 0x20, 0x10, - 0x5D, 0x28, 0x0A, 0x50, 0x9E, 0x71, 0x69, 0x28, 0x45, 0xBA, 0x42, 0x12, 0x81, 0x5E, 0x52, 0x2E, - 0xCB, 0xFD, 0x1A, 0x8B, 0xA8, 0x47, 0x36, 0xBF, 0x54, 0x34, 0xC7, 0xF4, 0xFD, 0xD5, 0x65, 0xAE, - 0xAC, 0x15, 0xCB, 0x81, 0x8F, 0x14, 0xA0, 0x39, 0xBC, 0xB1, 0xCE, 0x98, 0x8D, 0x0B, 0xD8, 0x20, - 0x73, 0x2E, 0x32, 0x17, 0x83, 0x28, 0x81, 0xCD, 0xD0, 0xE0, 0x99, 0xA2, 0xB3, 0x3C, 0x11, 0x0B, - 0x50, 0x5A, 0xBE, 0xB5, 0xCE, 0x68, 0x1D, 0x2C, 0x64, 0x83, 0xDA, 0xB9, 0xA8, 0x5D, 0x0E, 0x4C, - 0x09, 0xF4, 0x96, 0x8D, 0x9E, 0x29, 0x8A, 0x0B, 0x41, 0xAF, 0x00, 0xC3, 0x23, 0xD2, 0xE0, 0x3A, - 0x63, 0xB9, 0x2B, 0xA6, 0x39, 0x81, 0x69, 0x6E, 0x30, 0x3D, 0x1F, 0xD3, 0xE5, 0xB6, 0x57, 0x42, - 0x74, 0xD1, 0x66, 0x0D, 0xD2, 0xE1, 0x67, 0x8B, 0xF9, 0xF3, 0x56, 0x28, 0x0D, 0x96, 0xF8, 0xC9, - 0xB3, 0xD1, 0x8F, 0xA2, 0x80, 0x1C, 0x42, 0xF4, 0xC3, 0xAC, 0x2B, 0xF8, 0x7E, 0x1D, 0xE5, 0x45, - 0x03, 0x84, 0x9F, 0xE9, 0x3C, 0xD4, 0xBF, 0x90, 0x06, 0x3A, 0xDD, 0xDE, 0x73, 0x2C, 0xE7, 0x59, - 0x06, 0x95, 0xAB, 0xA5, 0x87, 0x4F, 0xEE, 0x58, 0xA1, 0x99, 0x3E, 0xD1, 0x60, 0x09, 0x86, 0xFA, - 0xF9, 0xAD, 0x71, 0xB8, 0xD5, 0x4F, 0x65, 0x88, 0x33, 0x3D, 0x7B, 0x09, 0x36, 0x38, 0x58, 0xD1, - 0x5C, 0xE6, 0xB7, 0x69, 0xBB, 0x39, 0x2D, 0x6F, 0x53, 0x50, 0x26, 0x8C, 0x6E, 0xB8, 0xCE, 0xA7, - 0xB6, 0xB7, 0x4D, 0x57, 0xF7, 0x64, 0xB5, 0x98, 0x4D, 0x44, 0x7F, 0xC3, 0xB8, 0xF8, 0x66, 0x03, - 0xFC, 0xCC, 0x22, 0x4E, 0x87, 0xAF, 0xB2, 0xE0, 0xDD, 0x55, 0x15, 0x51, 0x16, 0x57, 0xE5, 0xA6, - 0x46, 0x6E, 0x6F, 0x6E, 0x90, 0x2E, 0x46, 0x9F, 0xF8, 0x93, 0x69, 0xA4, 0x4E, 0x90, 0x28, 0xD5, - 0x15, 0x66, 0x72, 0x74, 0x4E, 0x0A, 0xB2, 0x15, 0xB5, 0xD8, 0x45, 0x6B, 0xD4, 0x92, 0x17, 0x9E, - 0x82, 0x71, 0x92, 0x61, 0x0F, 0xA3, 0x00, 0x0F, 0xF0, 0x1A, 0xC6, 0x9D, 0x5E, 0xD5, 0x60, 0x57, - 0x98, 0x86, 0xE2, 0x7F, 0x1D, 0xB0, 0x77, 0xEC, 0x60, 0xCF, 0xDD, 0xF0, 0x56, 0x49, 0x2A, 0x31, - 0x1C, 0x29, 0xE4, 0xAD, 0x89, 0x06, 0xF5, 0xF3, 0xD6, 0xDA, 0x49, 0xC0, 0xE1, 0xAA, 0xEE, 0x46, - 0x63, 0x76, 0x32, 0xF0, 0x3F, 0x78, 0xEF, 0x09, 0x71, 0xDF, 0x0E, 0xE6, 0x10, 0x14, 0xA5, 0x91, - 0x69, 0xE0, 0x54, 0xC3, 0x52, 0xBF, 0xE6, 0x21, 0x7B, 0x93, 0x6A, 0x80, 0x7B, 0xC9, 0x5E, 0x08, - 0xDF, 0xF1, 0xBB, 0x77, 0xEC, 0x3F, 0x3A, 0xFE, 0x06, 0xE5, 0x43, 0x0C, 0x0E, 0x71, 0xA2, 0x14, - 0xBE, 0x07, 0x6F, 0xAF, 0x3F, 0xB2, 0xDF, 0x58, 0x96, 0x77, 0xCE, 0x8D, 0x42, 0x3B, 0x06, 0xBE, - 0xC7, 0xE8, 0xC5, 0x27, 0xC4, 0x76, 0xCF, 0xA2, 0xDB, 0x6E, 0xCB, 0x37, 0xB5, 0xA6, 0xE7, 0xE8, - 0x36, 0xA0, 0x35, 0xFA, 0xF4, 0x49, 0x57, 0x53, 0x27, 0x8C, 0xFB, 0x26, 0xEC, 0x47, 0x6F, 0x29, - 0x3F, 0xEA, 0x82, 0xD7, 0xDE, 0x60, 0xB3, 0xC4, 0xCF, 0xE9, 0xA6, 0x97, 0xC1, 0xE6, 0xF0, 0xED, - 0x67, 0x82, 0xCD, 0x20, 0x03, 0x8A, 0x3C, 0xFA, 0xA5, 0x50, 0x3A, 0x7C, 0x7B, 0x0D, 0x24, 0x18, - 0x8E, 0xC0, 0x04, 0x3C, 0x36, 0x40, 0x38, 0xBD, 0xE7, 0x65, 0xF1, 0xBA, 0xD3, 0x6E, 0x6F, 0x50, - 0x3B, 0x86, 0xAC, 0x53, 0x0C, 0x28, 0x8D, 0xDF, 0x61, 0x93, 0x67, 0x80, 0xE4, 0x01, 0x3E, 0x5C, - 0x6A, 0x45, 0x18, 0x1E, 0x62, 0xCE, 0xE5, 0xF9, 0x4A, 0xD1, 0x5B, 0xC5, 0x6C, 0xB3, 0xE4, 0x9D, - 0xA4, 0x0F, 0x75, 0x89, 0xE4, 0x09, 0x34, 0x8E, 0x20, 0xF0, 0x87, 0xFE, 0x60, 0x9A, 0xF9, 0x0D, - 0xA3, 0x30, 0xC7, 0x8A, 0xA3, 0xA8, 0x05, 0xFE, 0xD2, 0x2F, 0x07, 0xAD, 0x0B, 0xAC, 0x72, 0x89, - 0x1D, 0x2F, 0x85, 0xD1, 0xD3, 0xF7, 0xD7, 0xCA, 0x94, 0x41, 0xB9, 0x0D, 0xAF, 0x15, 0x13, 0x34, - 0x31, 0xA7, 0x1E, 0x63, 0x86, 0x65, 0xAA, 0x61, 0x72, 0x7C, 0xEC, 0xFD, 0x4A, 0x77, 0x29, 0xE1, - 0xB9, 0xB8, 0xD2, 0x37, 0xB8, 0x1C, 0xAC, 0x31, 0x5F, 0xB5, 0xBB, 0x0C, 0x7B, 0x46, 0xD2, 0xEC, - 0x91, 0x61, 0xE6, 0x78, 0x1F, 0x1D, 0x3F, 0xD5, 0xD0, 0x31, 0xD3, 0x7F, 0x86, 0xE1, 0x23, 0xD6, - 0x53, 0x39, 0x53, 0x47, 0x0C, 0xD4, 0xD2, 0xCA, 0x11, 0x01, 0xC8, 0x93, 0x5B, 0x3B, 0xA2, 0x4B, - 0x5A, 0xD8, 0xDE, 0x11, 0x2F, 0xF2, 0x90, 0x8E, 0xD4, 0x83, 0x73, 0x36, 0xD0, 0xBF, 0x63, 0xA0, - 0x8D, 0x20, 0x13, 0x57, 0xC3, 0x3F, 0xB3, 0xF3, 0xF9, 0xB5, 0xD9, 0xF5, 0x29, 0x52, 0x08, 0xDB, - 0x65, 0xEF, 0x1D, 0x1E, 0x6D, 0x87, 0x7F, 0x0E, 0x6C, 0x60, 0x8D, 0xD9, 0xFC, 0xB0, 0xA8, 0xD3, - 0xF2, 0xE6, 0x6C, 0x2A, 0x73, 0x95, 0xDC, 0xBD, 0x5B, 0x7C, 0x98, 0x75, 0x58, 0x78, 0x8E, 0x84, - 0xC7, 0x2B, 0x83, 0x0F, 0xBD, 0x57, 0x39, 0xFC, 0xC2, 0x1B, 0x1F, 0x63, 0x4E, 0xC3, 0xB7, 0xBB, - 0xF0, 0x21, 0xF7, 0x2D, 0x84, 0x54, 0xD1, 0x5B, 0xDA, 0x71, 0x8C, 0xA9, 0x4D, 0x1D, 0xA7, 0x82, - 0xB9, 0x0F, 0x28, 0xCE, 0xE7, 0xC4, 0x48, 0x0B, 0xC2, 0x93, 0x4E, 0x57, 0x85, 0xD7, 0x32, 0xA1, - 0xA7, 0x55, 0x14, 0x18, 0x44, 0x8E, 0x98, 0xE9, 0x0F, 0xA6, 0x98, 0xE5, 0x53, 0xB5, 0xEB, 0x39, - 0x8B, 0x83, 0x4B, 0xA3, 0x24, 0x8F, 0x17, 0xDF, 0xC8, 0x61, 0xF6, 0x8F, 0xC0, 0x55, 0xE8, 0x8F, - 0x76, 0xCB, 0xFB, 0xE6, 0x65, 0x8F, 0x1D, 0xB4, 0xDD, 0xA7, 0xBD, 0x2F, 0x7A, 0x2B, 0x03, 0x88, - 0x22, 0xF0, 0x0B, 0x57, 0x7D, 0x79, 0x2E, 0x40, 0x9A, 0x3D, 0x95, 0x62, 0x30, 0x66, 0xC0, 0x9F, - 0x45, 0x19, 0x44, 0x8E, 0x93, 0xAD, 0x84, 0xE8, 0x0A, 0x61, 0xDD, 0xED, 0x76, 0xDA, 0xBD, 0xCE, - 0x1F, 0xED, 0x6E, 0xBB, 0xD3, 0x3E, 0x10, 0x29, 0x36, 0x8B, 0x00, 0x09, 0x4D, 0xD8, 0x5F, 0x97, - 0x05, 0xF0, 0x99, 0xF9, 0xBC, 0x68, 0x98, 0x77, 0xDB, 0x6F, 0xCA, 0xC1, 0x7C, 0xAF, 0x7D, 0xB8, - 0x0A, 0x98, 0xCB, 0xF9, 0xAC, 0x25, 0xCC, 0xE1, 0x29, 0x32, 0xE7, 0xE3, 0x0C, 0x4E, 0x5E, 0xB8, - 0x49, 0xE2, 0x0C, 0x2B, 0xD8, 0xA6, 0xFC, 0xAB, 0x9C, 0xC6, 0x54, 0x2A, 0x8A, 0x2C, 0x14, 0x34, - 0x78, 0xEE, 0x11, 0x9B, 0x14, 0x9F, 0x8A, 0x33, 0xA1, 0x96, 0xD8, 0xC7, 0x3A, 0xDE, 0x98, 0x67, - 0x2D, 0x20, 0x96, 0x60, 0x0D, 0x15, 0x5A, 0x8D, 0xFC, 0xBC, 0xC8, 0x7A, 0x0A, 0xB6, 0x32, 0xFD, - 0x92, 0x39, 0xDF, 0x65, 0xB9, 0x5C, 0xD5, 0x9F, 0xCC, 0x8A, 0x3F, 0xF8, 0xB9, 0x33, 0x3D, 0x3B, - 0x3B, 0xD1, 0x12, 0x3F, 0xA2, 0x1A, 0x0E, 0x0A, 0x50, 0x39, 0xA5, 0x7D, 0x44, 0x91, 0x99, 0x9F, - 0x4A, 0x2B, 0x31, 0xE9, 0x55, 0xA1, 0xEC, 0x94, 0x3A, 0x50, 0xED, 0x48, 0x19, 0xA8, 0xC2, 0x32, - 0x4F, 0xD9, 0x15, 0x97, 0xA4, 0x38, 0x25, 0x16, 0x83, 0x65, 0x60, 0x7C, 0x37, 0xAD, 0x28, 0x6B, - 0xC5, 0xC8, 0x05, 0x53, 0x4B, 0xE8, 0x15, 0xD3, 0x37, 0xC7, 0x4E, 0xBA, 0x7C, 0x3A, 0xAB, 0x77, - 0xA7, 0x15, 0x0E, 0x4B, 0xE8, 0x76, 0x98, 0x0F, 0xC1, 0x05, 0x01, 0x19, 0x3E, 0x77, 0x48, 0xED, - 0xEE, 0x74, 0x83, 0x47, 0xED, 0xAC, 0x88, 0xC5, 0x9C, 0x84, 0xC8, 0xCA, 0x3D, 0x3F, 0xF5, 0xCC, - 0x19, 0x2D, 0x45, 0x24, 0x54, 0x89, 0x93, 0x01, 0xBE, 0x2B, 0xEE, 0x09, 0x29, 0xB9, 0x31, 0xFC, - 0x95, 0xBA, 0xFF, 0x89, 0x6B, 0xCE, 0x19, 0x45, 0x19, 0xBB, 0x41, 0xA5, 0xB8, 0x13, 0x2A, 0xC5, - 0xD9, 0x04, 0x14, 0x48, 0xBE, 0x62, 0x96, 0x55, 0x35, 0xCA, 0x78, 0xEB, 0x20, 0x74, 0xA5, 0x6C, - 0x40, 0x4B, 0x5A, 0x30, 0x4B, 0x89, 0xED, 0x11, 0x5B, 0x52, 0x69, 0x13, 0xF8, 0x37, 0xDD, 0x4B, - 0xDB, 0x04, 0xCB, 0xF7, 0x70, 0x8A, 0xA9, 0x9B, 0x81, 0x6D, 0xA6, 0x9B, 0x71, 0x01, 0x7F, 0xA1, - 0x83, 0x7C, 0xEA, 0x86, 0x50, 0x48, 0xD0, 0xEC, 0x3E, 0x60, 0x34, 0xEF, 0xBD, 0xB4, 0x55, 0x84, - 0x23, 0x54, 0xDC, 0x18, 0x39, 0xF3, 0xB4, 0x8D, 0x29, 0x88, 0x14, 0xB2, 0x8F, 0xFF, 0xC9, 0xBC, - 0x73, 0xED, 0x9F, 0x8B, 0xC0, 0x1E, 0xF9, 0x33, 0xFA, 0xD1, 0x55, 0x1D, 0xDD, 0xF6, 0x98, 0xEB, - 0xA8, 0xB0, 0xBF, 0x8E, 0xBA, 0x8B, 0x91, 0x6A, 0xAD, 0xBF, 0xD3, 0x68, 0xE2, 0xAB, 0xAC, 0x26, - 0xD3, 0x9E, 0x61, 0xC5, 0x2E, 0x46, 0x4A, 0x59, 0x58, 0x30, 0xC0, 0x65, 0xEF, 0x98, 0x66, 0xA9, - 0x3E, 0xDA, 0x9C, 0x5A, 0x7F, 0xFA, 0xDC, 0x79, 0x14, 0x4A, 0x83, 0xE5, 0x80, 0xDA, 0xB0, 0xF5, - 0xAA, 0xE5, 0x79, 0xAF, 0xB6, 0xC3, 0x96, 0x41, 0x9B, 0x16, 0x1C, 0xEF, 0x17, 0x8A, 0x3A, 0xDE, - 0xF2, 0xD8, 0xBB, 0x63, 0xF6, 0x6F, 0xB1, 0x65, 0x60, 0x72, 0x35, 0x0C, 0xC0, 0x74, 0x3D, 0x47, - 0xB1, 0x5B, 0xB7, 0xA2, 0xC5, 0x96, 0x37, 0xED, 0xE4, 0xDF, 0xB7, 0xE5, 0x0C, 0x83, 0x79, 0x01, - 0xB0, 0x41, 0xD3, 0x04, 0xBE, 0xF1, 0x76, 0x77, 0xEC, 0x4D, 0x8C, 0xE3, 0xFF, 0x0F, 0x60, 0xEA, - 0x12, 0x48, 0x97, 0x42, 0x00 + 0x1F, 0x8B, 0x08, 0x08, 0x30, 0x53, 0xB7, 0x67, 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, + 0x01, 0x49, 0x12, 0xCF, 0x20, 0x80, 0x8B, 0x41, 0xB2, 0x6E, 0x9D, 0xAA, 0x53, 0xCB, 0xE8, 0x5E, + 0x48, 0x6F, 0xA0, 0x97, 0x52, 0x2B, 0xE9, 0x88, 0xC8, 0x04, 0x08, 0x90, 0x18, 0x49, 0x90, 0x22, + 0x65, 0xF9, 0xBD, 0x2B, 0x89, 0x20, 0x72, 0x8A, 0x8C, 0x88, 0x8C, 0x88, 0x8C, 0xE1, 0xC3, 0x3F, + 0x9C, 0x7D, 0x39, 0xBD, 0xFB, 0xB5, 0x77, 0xCE, 0x46, 0xFE, 0xD8, 0x3C, 0xFA, 0xE9, 0x03, 0xFE, + 0x62, 0xA6, 0x6A, 0x0D, 0x3F, 0x36, 0xB8, 0xD5, 0x38, 0xFA, 0x09, 0x9E, 0x70, 0x55, 0x3F, 0xFA, + 0x89, 0xC1, 0xBF, 0x0F, 0x63, 0xEE, 0xAB, 0x4C, 0x1B, 0xA9, 0xAE, 0xC7, 0xFD, 0x8F, 0x8D, 0xC0, + 0x1F, 0x34, 0x0F, 0x1A, 0x6C, 0x27, 0xFE, 0xE5, 0xC8, 0xF7, 0x9D, 0x26, 0xFF, 0x23, 0x30, 0x1E, + 0x3E, 0x36, 0x7E, 0x69, 0x7E, 0x3D, 0x6E, 0x9E, 0xDA, 0x63, 0x47, 0xF5, 0x8D, 0x7B, 0x93, 0x37, + 0x98, 0x66, 0x5B, 0x3E, 0xB7, 0xA0, 0xE5, 0xE5, 0xF9, 0x47, 0xAE, 0x0F, 0xF9, 0xB6, 0x36, 0x72, + 0xED, 0x31, 0xFF, 0xD8, 0x9E, 0x74, 0xE2, 0x1B, 0xBE, 0xC9, 0x8F, 0xFA, 0x8E, 0xEA, 0x7E, 0xBB, + 0x08, 0x2C, 0x76, 0x7B, 0xF7, 0x57, 0xD6, 0xE7, 0x7E, 0xE0, 0x7C, 0xD8, 0x11, 0xDF, 0xC4, 0x86, + 0xB2, 0x54, 0x68, 0xDA, 0x78, 0x30, 0xF8, 0xA3, 0x63, 0xBB, 0x7E, 0x83, 0xBE, 0xC1, 0x7F, 0xD1, + 0x28, 0x8F, 0x86, 0xEE, 0x8F, 0x3E, 0xEA, 0xFC, 0xC1, 0xD0, 0x78, 0x93, 0x3E, 0x6C, 0x33, 0xC3, + 0x32, 0x7C, 0x43, 0x35, 0x9B, 0x9E, 0xA6, 0x9A, 0x30, 0xF0, 0x36, 0x1B, 0xAB, 0xDF, 0x8D, 0x71, + 0x30, 0x9E, 0x3C, 0x08, 0x3C, 0xEE, 0xD2, 0x27, 0x15, 0xE6, 0xFC, 0x51, 0xD9, 0x66, 0xDE, 0xC8, + 0x35, 0xAC, 0x6F, 0x4D, 0xDF, 0x6E, 0x0E, 0x0C, 0xFF, 0xE3, 0x13, 0xF7, 0x26, 0xB3, 0x35, 0xE1, + 0x0B, 0xE6, 0x72, 0xF3, 0x63, 0xC3, 0xF3, 0x9F, 0x4C, 0xEE, 0x8D, 0x38, 0xF7, 0x1B, 0x6C, 0xE4, + 0xF2, 0x01, 0x3C, 0x71, 0xB5, 0x9D, 0x7B, 0xDB, 0xF6, 0x3D, 0xDF, 0x55, 0x9D, 0xD6, 0xD8, 0xB0, + 0x5A, 0x9A, 0xE7, 0x35, 0x4A, 0x36, 0xA4, 0xA7, 0xF1, 0x06, 0x9E, 0xE6, 0x1A, 0x8E, 0xCF, 0xE0, + 0x3B, 0xF1, 0xC2, 0xDF, 0xFF, 0x08, 0xB8, 0xFB, 0xD4, 0xEC, 0xB6, 0xF6, 0x5B, 0x0A, 0x75, 0xFE, + 0x77, 0x78, 0xF5, 0xC3, 0x8E, 0x78, 0x2D, 0xA3, 0x4D, 0x72, 0x36, 0x95, 0x1A, 0xDC, 0x07, 0x96, + 0x0E, 0x13, 0x9A, 0x6D, 0x17, 0x6F, 0x78, 0x14, 0x6D, 0xC1, 0x7F, 0xD9, 0xD2, 0x6D, 0x2D, 0x18, + 0xC3, 0x2E, 0xBC, 0x6D, 0xD9, 0xD6, 0xD6, 0x1B, 0xCD, 0x34, 0xB4, 0x6F, 0x6F, 0xB6, 0xD9, 0x9B, + 0x96, 0x6F, 0x0F, 0x87, 0x26, 0x6F, 0xDE, 0xFB, 0x16, 0x7C, 0x1C, 0x04, 0x96, 0xE6, 0x1B, 0xB6, + 0xC5, 0xB6, 0xF8, 0x5B, 0xF6, 0xAF, 0x51, 0x6B, 0xD1, 0x03, 0x2C, 0x3F, 0x70, 0x5D, 0xE8, 0xE2, + 0x4E, 0x75, 0x87, 0xDC, 0x6F, 0x69, 0x23, 0xC3, 0xD4, 0xE1, 0xF3, 0xFF, 0x50, 0xFE, 0xE7, 0x5B, + 0xD9, 0xCD, 0xA9, 0xA9, 0x7A, 0xDE, 0xD6, 0x1B, 0x03, 0x76, 0xBC, 0xA9, 0xA9, 0x2E, 0xF7, 0x9B, + 0xBA, 0xFD, 0x68, 0xB1, 0xD8, 0xE7, 0xC0, 0x79, 0xF3, 0xF6, 0x2F, 0x51, 0xC7, 0xFF, 0xF6, 0x56, + 0x4C, 0x77, 0x7A, 0xF6, 0x08, 0xEC, 0xC9, 0xE4, 0x5B, 0x9E, 0x0F, 0x08, 0xAB, 0x35, 0x87, 0xAE, + 0x1D, 0x38, 0x53, 0xD3, 0x1A, 0x71, 0x63, 0x38, 0xF2, 0x0F, 0x99, 0xF2, 0x97, 0xC4, 0x63, 0xFB, + 0x81, 0xBB, 0x03, 0xD3, 0x7E, 0x3C, 0x64, 0x23, 0x43, 0xD7, 0xB9, 0x95, 0xFC, 0x16, 0x20, 0x68, + 0x79, 0x06, 0x2E, 0xF4, 0x50, 0x76, 0xC0, 0x94, 0xD6, 0xAE, 0xC7, 0xB8, 0xEA, 0xF1, 0xE4, 0x9B, + 0xF7, 0xB6, 0xAB, 0x03, 0xF6, 0xDD, 0xDB, 0xBE, 0x6F, 0x8F, 0x0F, 0x99, 0x67, 0x9B, 0x86, 0xCE, + 0xDA, 0xCE, 0x77, 0xF6, 0x8F, 0x9A, 0x82, 0xFF, 0x8B, 0x2D, 0xE5, 0xA7, 0xC9, 0x7C, 0x4D, 0xC3, + 0xF3, 0xD7, 0x7B, 0xB6, 0xF8, 0xCF, 0x51, 0x75, 0xDD, 0xB0, 0x86, 0x4D, 0x57, 0xCC, 0x69, 0x4F, + 0x71, 0xBE, 0xA7, 0x2F, 0x47, 0x74, 0x0B, 0x44, 0xE1, 0x31, 0x5F, 0xDF, 0x4E, 0x7F, 0x3E, 0x9A, + 0x5A, 0xA9, 0xF8, 0xEE, 0x90, 0x59, 0xB6, 0x35, 0x35, 0xC9, 0x31, 0x60, 0x8F, 0x61, 0x35, 0x4D, + 0x3E, 0x40, 0x40, 0x64, 0x8C, 0x79, 0x1F, 0xC0, 0x12, 0xAC, 0xC3, 0x01, 0x20, 0xAD, 0x37, 0xD5, + 0xB3, 0x1D, 0xF8, 0x40, 0xAC, 0x3C, 0x01, 0xC4, 0xF8, 0x6C, 0x0D, 0x0B, 0xBF, 0x3E, 0x77, 0x5D, + 0xDB, 0x9D, 0x6A, 0xA9, 0x1B, 0x9E, 0x63, 0xAA, 0x4F, 0x87, 0x4C, 0xBC, 0x92, 0x9C, 0x96, 0x66, + 0x9B, 0x36, 0xCC, 0xD7, 0xE5, 0x7A, 0xF2, 0xF9, 0x00, 0x98, 0x57, 0xD3, 0x33, 0xFE, 0x84, 0x01, + 0xBD, 0xB1, 0x6A, 0x9A, 0xDC, 0xCD, 0x1B, 0xB6, 0x1F, 0x68, 0x1A, 0xC2, 0xA3, 0xFA, 0xC0, 0x43, + 0x97, 0x4F, 0x6F, 0x7C, 0xDE, 0xD0, 0xD1, 0xF7, 0x8F, 0x12, 0xA5, 0xEE, 0x6D, 0x53, 0xCF, 0xDA, + 0xBE, 0xEF, 0x4D, 0x6A, 0x3F, 0x35, 0xAB, 0xEC, 0x8D, 0xC0, 0x7F, 0xC4, 0x9E, 0x0F, 0x59, 0x57, + 0xF9, 0xAF, 0xD9, 0xBD, 0x8A, 0x1E, 0x3A, 0x4A, 0x5E, 0xC7, 0x9D, 0x1C, 0xB4, 0x0A, 0x7B, 0xD8, + 0xCD, 0xED, 0x61, 0x37, 0xBB, 0x07, 0xD5, 0xF7, 0x81, 0xE9, 0x4E, 0x35, 0x76, 0xEC, 0x90, 0x5A, + 0xD4, 0x7B, 0x20, 0x81, 0xC0, 0x9F, 0x02, 0xF8, 0x9F, 0x4D, 0xC3, 0xD2, 0xF9, 0xF7, 0x43, 0xD6, + 0x56, 0x94, 0x29, 0x92, 0x90, 0xA4, 0xD0, 0x9E, 0x81, 0x06, 0x1C, 0x4A, 0x4D, 0x09, 0x91, 0x7D, + 0x25, 0xE5, 0x5B, 0x9A, 0xAE, 0x6F, 0x3B, 0x40, 0x46, 0xC9, 0xC9, 0x4A, 0xF6, 0x26, 0x18, 0xDA, + 0x87, 0x1D, 0x71, 0x74, 0xFF, 0xF4, 0xE1, 0xDE, 0xD6, 0x9F, 0x24, 0x8F, 0xD7, 0x8D, 0x07, 0xA6, + 0x21, 0xDF, 0xFC, 0xD8, 0xC0, 0x83, 0x52, 0x05, 0x04, 0x71, 0x1B, 0xCC, 0xD0, 0x3F, 0x36, 0xE4, + 0xF2, 0x2E, 0xE1, 0x71, 0x63, 0xC2, 0x0D, 0xA9, 0x81, 0x6A, 0x1A, 0x43, 0xEB, 0x63, 0x83, 0xE6, + 0xDB, 0x08, 0x9B, 0xCB, 0xF7, 0x63, 0xEF, 0xD2, 0xFB, 0xC6, 0x78, 0x38, 0xDD, 0xDD, 0x85, 0x61, + 0xF2, 0x1B, 0x38, 0xAD, 0x1B, 0x93, 0xA3, 0xE5, 0x44, 0x7C, 0x7B, 0x02, 0x92, 0xC6, 0xB7, 0x96, + 0x63, 0x0D, 0x1B, 0x30, 0x06, 0x1C, 0xD9, 0xF2, 0x31, 0x33, 0xF9, 0x03, 0x37, 0x1B, 0x47, 0xC0, + 0x9B, 0x1D, 0xD5, 0x8A, 0x77, 0xD7, 0xE3, 0xAE, 0x06, 0x67, 0x42, 0x23, 0x31, 0x26, 0xE1, 0xB5, + 0x98, 0x14, 0x61, 0x1E, 0x8C, 0x83, 0x10, 0xF8, 0xD8, 0x08, 0x29, 0x41, 0x12, 0x02, 0x1D, 0x5C, + 0xD0, 0x61, 0x6C, 0x75, 0x3B, 0xB0, 0x3C, 0x09, 0x19, 0xF1, 0x67, 0x1E, 0x94, 0xA6, 0x7A, 0x25, + 0x66, 0x13, 0xDF, 0x0D, 0x42, 0xBE, 0xC9, 0xDE, 0x89, 0xAD, 0x13, 0xC0, 0x75, 0x39, 0x88, 0x4C, + 0x97, 0x56, 0xCF, 0xB5, 0x91, 0x60, 0xE3, 0xF0, 0xBD, 0x3F, 0xBA, 0xC5, 0xEF, 0x7C, 0xE0, 0x8D, + 0x1F, 0x76, 0xEE, 0x8F, 0x3E, 0xDC, 0xBB, 0xF4, 0x1F, 0x4A, 0x3E, 0x42, 0x74, 0x61, 0x86, 0x07, + 0x7C, 0x02, 0xCF, 0x62, 0x78, 0xA5, 0xC5, 0x7A, 0x26, 0x72, 0x62, 0xF6, 0xA8, 0x1A, 0x7E, 0xAB, + 0xD5, 0x5A, 0xD5, 0xD4, 0x51, 0x8C, 0x33, 0xB9, 0xCF, 0x53, 0x66, 0xCE, 0xCE, 0xA0, 0xB3, 0x8C, + 0xA9, 0x8F, 0x54, 0x0F, 0x78, 0xF2, 0x23, 0xA3, 0x3E, 0x56, 0x31, 0xD9, 0x81, 0xE1, 0x8E, 0x1F, + 0xE1, 0xE8, 0xFF, 0xEA, 0x98, 0xB6, 0xAA, 0xA7, 0xCF, 0x7A, 0x7A, 0xBE, 0x17, 0xB2, 0x0D, 0x0B, + 0x1C, 0x5D, 0xF5, 0x39, 0xB0, 0x48, 0xD1, 0xAA, 0xC5, 0x92, 0x9B, 0x20, 0x16, 0x12, 0x6E, 0x44, + 0xC9, 0xC5, 0x1C, 0xFD, 0x94, 0x4A, 0x4A, 0x88, 0xC4, 0xB8, 0xD4, 0xE8, 0x7D, 0x10, 0x49, 0xC7, + 0xCD, 0x76, 0x07, 0x50, 0x14, 0x09, 0x28, 0xA2, 0x13, 0xD7, 0xFF, 0xD6, 0xF4, 0x50, 0x00, 0x8E, + 0x11, 0x49, 0x42, 0x3A, 0xFE, 0xD9, 0xB8, 0x30, 0x84, 0x88, 0x8C, 0xD8, 0x1D, 0x9B, 0x4D, 0x69, + 0x30, 0xC7, 0x00, 0xDB, 0xCE, 0x06, 0xEC, 0x18, 0x1A, 0xF5, 0xD4, 0x21, 0x2F, 0xB9, 0x20, 0xD7, + 0x7E, 0x9C, 0xD9, 0xC7, 0x7B, 0xD3, 0xD6, 0xBE, 0xFD, 0x25, 0xDE, 0x41, 0x41, 0x27, 0xE2, 0x18, + 0x41, 0xB6, 0x3C, 0xC5, 0x64, 0xF0, 0x1F, 0x2E, 0xFE, 0x1C, 0x24, 0x99, 0xA7, 0xC7, 0x11, 0x87, + 0xBD, 0x0B, 0x37, 0xF1, 0x30, 0xC6, 0x33, 0x00, 0x78, 0xE1, 0xE3, 0xBF, 0x71, 0xD7, 0x03, 0x26, + 0x9D, 0xCD, 0x19, 0x1E, 0x94, 0x96, 0x22, 0xB9, 0x03, 0xE2, 0xC4, 0xCC, 0x70, 0x93, 0x5E, 0x87, + 0x96, 0xE7, 0x95, 0xEE, 0xF6, 0xD3, 0x4D, 0xBF, 0x1F, 0x9B, 0x5B, 0x6C, 0x14, 0x56, 0x30, 0x8C, + 0xC0, 0xBB, 0x93, 0xBB, 0xCB, 0xB3, 0xEC, 0xDE, 0xCF, 0x04, 0x6E, 0x9E, 0x98, 0x01, 0xF7, 0x01, + 0x2D, 0x47, 0xEC, 0xF2, 0x0C, 0xCE, 0x56, 0xF8, 0x57, 0x72, 0x0C, 0xCD, 0x06, 0xB9, 0xC9, 0xB0, + 0x00, 0xE9, 0xBD, 0xAB, 0xAB, 0xCF, 0xD9, 0xE3, 0x5C, 0x5D, 0x8D, 0x0E, 0x67, 0xBA, 0x99, 0x82, + 0x0A, 0xB7, 0x75, 0x0E, 0x62, 0xF3, 0x95, 0xEA, 0x67, 0xF7, 0xB3, 0x0B, 0xCB, 0x7F, 0xAF, 0x74, + 0xDE, 0xEF, 0xBE, 0x7B, 0x2F, 0x67, 0xB8, 0x5D, 0xB6, 0xDB, 0x3C, 0x20, 0x37, 0xDB, 0xCA, 0x5E, + 0xAB, 0x7D, 0xB0, 0xA7, 0xEC, 0xBD, 0xDB, 0x6F, 0x57, 0xEC, 0xF8, 0xD8, 0xCC, 0x99, 0x6F, 0x7B, + 0x6F, 0x1F, 0x66, 0x7C, 0x10, 0x4E, 0x96, 0x6D, 0x1D, 0xF7, 0x4E, 0xDF, 0xCE, 0xC2, 0xB3, 0x14, + 0xDA, 0xC4, 0x60, 0x7D, 0x7E, 0x7A, 0x7E, 0x91, 0x3D, 0x28, 0x7E, 0x5B, 0x04, 0x6D, 0xAE, 0xF1, + 0xC1, 0x2F, 0x79, 0x00, 0xE9, 0x1C, 0x28, 0x1D, 0x65, 0xBF, 0xB5, 0xB7, 0x7F, 0x50, 0x12, 0x1E, + 0xD8, 0xE3, 0xAF, 0x39, 0x3D, 0xEE, 0xBE, 0x6B, 0xEF, 0x1F, 0x28, 0xBB, 0xAD, 0x5D, 0xA5, 0x5B, + 0xA1, 0xC7, 0xDF, 0xF2, 0x70, 0xE1, 0x60, 0x7F, 0x7F, 0x7F, 0xAF, 0xB5, 0x7B, 0xB0, 0x3B, 0x7D, + 0x2A, 0x17, 0x01, 0x36, 0xCE, 0xDB, 0x52, 0x3F, 0x8F, 0xDC, 0x90, 0x85, 0x8C, 0xFD, 0xA6, 0x32, + 0x2D, 0xCD, 0xCC, 0xF2, 0x3D, 0x3A, 0x50, 0x66, 0x18, 0xD3, 0x3F, 0x34, 0x9B, 0xAC, 0x19, 0xFE, + 0x63, 0x70, 0x74, 0x0F, 0x40, 0x8E, 0x61, 0xA7, 0xB6, 0x35, 0x30, 0x86, 0xB1, 0x2F, 0x9A, 0xCD, + 0xA3, 0x59, 0x86, 0x26, 0x87, 0xD7, 0x41, 0x2F, 0x03, 0xA5, 0x68, 0xA8, 0x3A, 0xCD, 0x4E, 0x0A, + 0x0F, 0xFB, 0x20, 0x14, 0x8F, 0x48, 0xA2, 0xF2, 0x2D, 0x06, 0xFF, 0x35, 0x1D, 0xD7, 0x80, 0xC9, + 0x3D, 0xB1, 0x89, 0xB2, 0x2C, 0x78, 0xB0, 0x23, 0xA6, 0x20, 0x66, 0xD0, 0x60, 0xFE, 0x93, 0x03, + 0xCB, 0x10, 0x5D, 0x34, 0x18, 0x1C, 0x5D, 0x6A, 0x53, 0xB4, 0xA0, 0xF3, 0xC4, 0x54, 0x1D, 0x8F, + 0x37, 0x52, 0xF7, 0x48, 0xBC, 0x4A, 0x6A, 0xF5, 0xC7, 0xC6, 0x3F, 0x86, 0xEF, 0xF6, 0x92, 0xBD, + 0xAB, 0xAE, 0xA1, 0x36, 0xF9, 0x77, 0xD8, 0x00, 0x9D, 0xE3, 0xC1, 0xAA, 0x9A, 0xD0, 0x9D, 0x78, + 0x8A, 0x67, 0x88, 0x6B, 0x9B, 0xDE, 0x64, 0x9C, 0x64, 0xDB, 0xA3, 0xD4, 0x51, 0x93, 0x00, 0x0C, + 0x5C, 0x95, 0x54, 0xFF, 0x0F, 0x46, 0x62, 0x69, 0xA8, 0xB5, 0x4F, 0x4E, 0x45, 0xD2, 0xE1, 0x51, + 0x9D, 0x67, 0xF7, 0x06, 0x9B, 0x52, 0xF3, 0xF1, 0xB8, 0x33, 0xD2, 0x70, 0x46, 0x00, 0x24, 0x15, + 0x63, 0xB2, 0xB6, 0x29, 0x5C, 0x06, 0x1B, 0xDF, 0xC3, 0x3E, 0x49, 0x72, 0x2D, 0xB7, 0x32, 0xC2, + 0x92, 0xD4, 0xBE, 0xBC, 0x11, 0xC8, 0x0B, 0x85, 0x1D, 0xCE, 0xA0, 0xCF, 0xCC, 0xDC, 0x54, 0x57, + 0x67, 0xF8, 0xA3, 0x89, 0x52, 0xFC, 0x34, 0x92, 0xA6, 0xB5, 0x18, 0xD8, 0xEE, 0x58, 0xDA, 0x03, + 0x00, 0xFD, 0x3B, 0x19, 0xDB, 0x11, 0xDB, 0x92, 0xC3, 0xCC, 0x17, 0x04, 0x35, 0xCB, 0x7E, 0x7D, + 0x5F, 0x62, 0xD9, 0xBD, 0xD7, 0x04, 0x5A, 0xD6, 0xF8, 0x98, 0xCC, 0x6C, 0x42, 0x2B, 0xC8, 0xEC, + 0x82, 0x6C, 0x0A, 0x68, 0xBB, 0x03, 0xC1, 0x85, 0x9B, 0x5C, 0xF3, 0x99, 0xCA, 0xE4, 0x6E, 0x33, + 0xC0, 0x2D, 0xD0, 0xE5, 0xB9, 0xC5, 0xA0, 0x7F, 0x10, 0x00, 0x98, 0x94, 0x83, 0x41, 0xC1, 0xB7, + 0xE1, 0xB9, 0xEA, 0x87, 0x2F, 0xB6, 0xD8, 0xB1, 0x2F, 0x64, 0xC8, 0xED, 0xB8, 0x4C, 0xF6, 0x68, + 0x80, 0x78, 0x10, 0x20, 0xA8, 0xA9, 0x63, 0xAE, 0x4F, 0xDE, 0x0F, 0x91, 0x4D, 0x1B, 0xA9, 0xD6, + 0x90, 0x7B, 0x0C, 0xC5, 0x3B, 0x4F, 0x7D, 0x80, 0x57, 0x1E, 0x69, 0x3C, 0x50, 0x92, 0x07, 0x03, + 0x8E, 0xE6, 0xA5, 0x68, 0x32, 0x20, 0xE1, 0x45, 0xFD, 0x80, 0x32, 0x4F, 0xEF, 0xBD, 0xE9, 0x43, + 0x9B, 0x24, 0xC6, 0xBE, 0xC1, 0x17, 0x1D, 0x98, 0x8C, 0xC7, 0xF5, 0x56, 0x0E, 0x6C, 0x67, 0xC0, + 0x47, 0xF8, 0x6B, 0x58, 0x03, 0xBB, 0xA9, 0x19, 0xAE, 0x06, 0x03, 0xFA, 0xFC, 0xBB, 0x1F, 0xD1, + 0xFA, 0xD8, 0xC3, 0x9D, 0xCA, 0xE2, 0x84, 0x53, 0x1C, 0x31, 0x1D, 0x05, 0xA6, 0xB8, 0x60, 0x09, + 0xF4, 0x40, 0x29, 0xED, 0xA8, 0x60, 0xF3, 0xD3, 0xF9, 0x77, 0x42, 0x67, 0x0E, 0xD9, 0x67, 0x2E, + 0x28, 0x0C, 0xCB, 0x09, 0x7C, 0xC9, 0xB3, 0x5C, 0x55, 0x37, 0xEC, 0x86, 0xB4, 0xE0, 0x4A, 0xF8, + 0xDF, 0x8A, 0x67, 0x0F, 0x2A, 0x08, 0x33, 0x1F, 0x1B, 0x4A, 0x51, 0x77, 0xA6, 0x7A, 0xCF, 0xCD, + 0x38, 0xEB, 0x50, 0x48, 0xC3, 0x3C, 0x92, 0x1B, 0x0F, 0x62, 0x00, 0xBD, 0xB1, 0x10, 0x28, 0x5F, + 0x04, 0x24, 0xDB, 0x95, 0x21, 0xD9, 0x16, 0x90, 0x6C, 0x77, 0xBA, 0xBB, 0x7B, 0xFB, 0xEF, 0x0E, + 0xDE, 0x2B, 0x93, 0xBF, 0x5E, 0xA1, 0x2A, 0xA1, 0xDA, 0xA9, 0x0C, 0xD5, 0x8E, 0x80, 0xEA, 0x2B, + 0x04, 0x25, 0x04, 0xBB, 0x95, 0x21, 0xD8, 0x7D, 0x85, 0x60, 0x02, 0x82, 0xBB, 0x95, 0x21, 0xB8, + 0xFB, 0x0A, 0xC1, 0x04, 0x04, 0xF7, 0x2A, 0x43, 0x70, 0xEF, 0x15, 0x82, 0x09, 0x08, 0xEE, 0x57, + 0x86, 0xE0, 0xFE, 0x2B, 0x04, 0x13, 0x10, 0x7C, 0x57, 0x19, 0x82, 0xEF, 0xEA, 0x83, 0x60, 0x8D, + 0x20, 0x14, 0xD3, 0x84, 0xF7, 0xA3, 0x79, 0x0A, 0x93, 0x7F, 0xA8, 0xDB, 0xC6, 0x2F, 0x70, 0xA4, + 0xB9, 0xB3, 0x4B, 0x7F, 0xEC, 0xD2, 0x4F, 0x1A, 0x88, 0xFA, 0x88, 0x64, 0xB8, 0x5C, 0xB0, 0x60, + 0xE7, 0x87, 0xC5, 0x20, 0x48, 0x2A, 0x66, 0x38, 0xE8, 0x01, 0x0D, 0xF7, 0xAE, 0xCA, 0x26, 0xA2, + 0xA0, 0xDE, 0x48, 0x40, 0x44, 0x6A, 0xC0, 0x09, 0xB5, 0x5C, 0xEC, 0x4A, 0x7E, 0xAF, 0xCE, 0x74, + 0x03, 0xBA, 0x38, 0x8C, 0xFA, 0x8E, 0x5D, 0x26, 0xE2, 0xFE, 0x3A, 0x79, 0x7B, 0x9B, 0x49, 0x01, + 0xB3, 0x03, 0x9D, 0x92, 0x12, 0x74, 0x0D, 0x2A, 0x0B, 0x1A, 0x70, 0x93, 0x83, 0xC9, 0x2B, 0xC4, + 0x9C, 0xE1, 0xEA, 0x47, 0x95, 0x59, 0x23, 0x8C, 0xBC, 0x43, 0xDE, 0x2B, 0x41, 0x5D, 0xD2, 0x64, + 0x92, 0x34, 0x7E, 0xD0, 0x0D, 0x91, 0x6F, 0xD1, 0x7D, 0x84, 0xC4, 0x9E, 0x46, 0x96, 0x55, 0x25, + 0x15, 0x13, 0xF3, 0xF5, 0x57, 0xBA, 0x08, 0xB6, 0x4E, 0xD1, 0x8B, 0x61, 0x66, 0x9C, 0xAD, 0xB7, + 0x0D, 0x79, 0x0F, 0x22, 0x1F, 0xA4, 0x5B, 0x20, 0x96, 0xA4, 0x59, 0xC7, 0xB4, 0x6B, 0x31, 0x89, + 0x69, 0x55, 0x18, 0x55, 0xE9, 0x81, 0xAA, 0xF9, 0x36, 0xAC, 0x5C, 0xE7, 0x03, 0x35, 0x30, 0x7D, + 0xAF, 0x48, 0x6D, 0x5D, 0x8A, 0xEA, 0x5A, 0xC4, 0x91, 0xCA, 0x63, 0xB5, 0x1B, 0x83, 0xFE, 0xB5, + 0x37, 0xAC, 0x0D, 0xA1, 0xD3, 0x8C, 0x44, 0x29, 0xAF, 0x4E, 0xD9, 0x05, 0xC9, 0xC2, 0xBF, 0x42, + 0xA3, 0xE0, 0xD8, 0x07, 0xBE, 0x19, 0xB7, 0x0C, 0xD6, 0x6C, 0x06, 0xC4, 0xF5, 0xCC, 0x65, 0x03, + 0x8C, 0x35, 0x4C, 0x87, 0x7B, 0x0C, 0x52, 0x53, 0xD6, 0x3F, 0xBC, 0x52, 0x79, 0x66, 0xD3, 0x5F, + 0xD2, 0x48, 0x97, 0xBB, 0x96, 0x6C, 0xEB, 0x1C, 0x73, 0xFC, 0x66, 0x3B, 0xD7, 0x44, 0x47, 0x57, + 0x69, 0x5C, 0xF5, 0x02, 0x97, 0x68, 0xFC, 0x56, 0xF5, 0xF9, 0x25, 0x9E, 0x34, 0x39, 0x14, 0x79, + 0x3D, 0x79, 0x9D, 0xE1, 0xFB, 0x87, 0xA5, 0x4E, 0xBB, 0x7C, 0x16, 0x9C, 0x71, 0x36, 0x76, 0x88, + 0x1F, 0xB6, 0x3B, 0x48, 0xD2, 0x5D, 0x36, 0x61, 0xE8, 0x65, 0xF8, 0x45, 0xEC, 0xFC, 0x9F, 0x5A, + 0xE0, 0xE7, 0x3F, 0x13, 0xF7, 0x9C, 0xF1, 0x93, 0xFE, 0xD2, 0x2A, 0xEC, 0x18, 0xFF, 0x7D, 0xFE, + 0xF3, 0xB0, 0xD4, 0x7B, 0x35, 0x32, 0xD5, 0x29, 0xE6, 0x7A, 0x37, 0xE2, 0xCC, 0x0A, 0xC6, 0xF7, + 0xDC, 0x65, 0xF6, 0x80, 0x91, 0x9F, 0x07, 0xE0, 0xAF, 0x87, 0x86, 0x42, 0xD3, 0xD6, 0x04, 0x32, + 0xFF, 0xE7, 0x7F, 0xFC, 0xEF, 0x81, 0xF1, 0x9D, 0x7B, 0xFF, 0xF9, 0x1F, 0xFF, 0x87, 0x39, 0xF0, + 0xA2, 0xC7, 0x01, 0x6D, 0xF5, 0x16, 0x3B, 0xB6, 0x9E, 0xFC, 0x91, 0x61, 0x0D, 0x99, 0x7A, 0x6F, + 0x3F, 0x70, 0xB6, 0xFB, 0xF9, 0x4F, 0x90, 0x29, 0x9F, 0x00, 0x6B, 0xD0, 0x74, 0x39, 0xB9, 0xBB, + 0x83, 0x97, 0x87, 0xDC, 0xA3, 0x9E, 0x80, 0xDE, 0x76, 0xA8, 0xE7, 0xE1, 0x10, 0xDB, 0x81, 0x44, + 0xAA, 0xB9, 0x1C, 0x96, 0xA6, 0x19, 0xDC, 0x6B, 0xB1, 0x1B, 0x1B, 0x90, 0x80, 0xE1, 0x8C, 0x62, + 0x80, 0x66, 0x2E, 0x5E, 0x5D, 0x1B, 0x1E, 0x79, 0x77, 0xB9, 0xE4, 0xD7, 0x85, 0xAC, 0xBF, 0x0D, + 0x83, 0x91, 0x25, 0xD3, 0xB0, 0xD8, 0x09, 0xFA, 0x0D, 0x8C, 0x6D, 0x9D, 0xB7, 0xD8, 0x99, 0x38, + 0x09, 0x0E, 0x71, 0x32, 0x2D, 0x76, 0x65, 0x8C, 0x0D, 0xDF, 0x3B, 0x64, 0x4A, 0x4B, 0x51, 0x94, + 0x76, 0xA7, 0x43, 0x0D, 0x15, 0xF8, 0xA6, 0xC4, 0xBE, 0x2F, 0xF5, 0xBC, 0x28, 0x7B, 0x6E, 0x4C, + 0xDE, 0x2B, 0x90, 0x07, 0x4B, 0x1C, 0x32, 0x19, 0xA4, 0x21, 0xA4, 0xD4, 0xBD, 0x79, 0x28, 0x23, + 0x2E, 0x4A, 0x0A, 0x1C, 0x4A, 0x15, 0x26, 0x63, 0x77, 0x05, 0xB3, 0x04, 0x54, 0x62, 0x18, 0x27, + 0xBD, 0xE9, 0xBC, 0x12, 0x66, 0x91, 0xE8, 0x17, 0x1B, 0x37, 0x71, 0x5D, 0x4F, 0x00, 0xC2, 0x6B, + 0x37, 0x5C, 0x8F, 0x02, 0x83, 0xF0, 0xF1, 0x91, 0xED, 0x7E, 0xD8, 0x81, 0x5F, 0x34, 0xDE, 0x7A, + 0xF0, 0xA4, 0x3E, 0xD7, 0x1A, 0x47, 0x7D, 0x22, 0x50, 0x8F, 0xDD, 0x73, 0xFF, 0x91, 0x03, 0x8D, + 0xC4, 0xDE, 0xF1, 0xD6, 0x8C, 0xE1, 0xE4, 0xCC, 0xB4, 0x05, 0xAC, 0x00, 0xE8, 0x5E, 0x60, 0x99, + 0x81, 0xCE, 0x91, 0xC0, 0x07, 0x2C, 0xE0, 0x02, 0x40, 0xED, 0xD0, 0xD4, 0x4F, 0xE3, 0x13, 0x78, + 0xF1, 0x02, 0xAF, 0x02, 0x03, 0x1A, 0x04, 0xA6, 0x60, 0x10, 0xBE, 0xFA, 0x8D, 0x78, 0x54, 0xE2, + 0x65, 0x8E, 0x0E, 0x16, 0x6C, 0xC0, 0x1F, 0xA3, 0x19, 0xA8, 0x9A, 0x6B, 0x7B, 0xF0, 0x0B, 0x98, + 0x13, 0xBC, 0x0D, 0x2F, 0x3E, 0xF0, 0x27, 0xB6, 0xD5, 0xD9, 0xFD, 0x6F, 0x6C, 0x64, 0x07, 0xAE, + 0xF7, 0xB6, 0x0E, 0xF6, 0x34, 0xE1, 0x47, 0x6D, 0x7C, 0xE9, 0xA0, 0xFD, 0x7E, 0x3F, 0x1C, 0x1F, + 0x58, 0x12, 0xC1, 0xBC, 0x34, 0x38, 0x63, 0xBE, 0x61, 0xAF, 0x5C, 0xA9, 0x56, 0xAE, 0x44, 0x24, + 0x34, 0x27, 0x5B, 0x82, 0xB6, 0x35, 0xF0, 0xA5, 0x39, 0xBE, 0x9E, 0x53, 0x99, 0x15, 0x1E, 0x39, + 0x4F, 0x96, 0x3A, 0x36, 0xB4, 0x6B, 0x40, 0x51, 0xF3, 0xCC, 0xB5, 0x1D, 0x21, 0x8A, 0x96, 0xB2, + 0x88, 0xC4, 0x9B, 0x36, 0x52, 0xF7, 0x70, 0x7F, 0xC6, 0x0A, 0x72, 0x26, 0xDA, 0x30, 0x6A, 0x74, + 0xB8, 0x62, 0x65, 0xF2, 0x58, 0xFF, 0x7B, 0xE0, 0xF9, 0x21, 0x33, 0xF1, 0xB9, 0x6B, 0xA9, 0x26, + 0x53, 0xCD, 0xA1, 0xED, 0x1A, 0xFE, 0x68, 0x8C, 0x54, 0x39, 0x56, 0x7D, 0x6D, 0x44, 0xDF, 0x83, + 0x82, 0x20, 0x54, 0x4E, 0xD5, 0x71, 0x40, 0x45, 0x16, 0xC2, 0x10, 0xB7, 0x1E, 0x0C, 0xD7, 0xB6, + 0x70, 0x6C, 0xC9, 0xA0, 0xE4, 0xE5, 0x2E, 0x33, 0xC6, 0xA0, 0x9A, 0x3E, 0x70, 0xD1, 0xB7, 0xCB, + 0x35, 0x6E, 0x00, 0x4B, 0x78, 0xE3, 0x89, 0x61, 0x1C, 0x90, 0xF1, 0x45, 0x07, 0xC0, 0xBA, 0xE2, + 0x5C, 0x4E, 0xDE, 0x15, 0x07, 0x78, 0xF7, 0x6A, 0x3F, 0x18, 0x3A, 0xDE, 0xE9, 0x02, 0xB7, 0x70, + 0x81, 0x97, 0x69, 0x5A, 0x40, 0xDC, 0x25, 0xF4, 0xC4, 0x45, 0x9F, 0x6D, 0xC0, 0xED, 0x16, 0xBA, + 0xC5, 0xD1, 0x88, 0xF1, 0x81, 0x70, 0xEA, 0x00, 0xAB, 0xC0, 0xF2, 0x02, 0xC3, 0xC7, 0x10, 0x0F, + 0x06, 0x30, 0xF2, 0x11, 0xEE, 0xC4, 0x7B, 0x68, 0xBF, 0xA8, 0xC1, 0x10, 0xDE, 0xB6, 0xB2, 0x96, + 0x84, 0x9C, 0xCC, 0x34, 0xBE, 0x71, 0x13, 0x9D, 0x34, 0xF0, 0x66, 0x1A, 0x84, 0x29, 0xE4, 0x61, + 0xC8, 0x16, 0x81, 0x3B, 0xC2, 0xDC, 0xA3, 0xF1, 0x40, 0x12, 0xC4, 0xDE, 0x41, 0x78, 0x13, 0x6C, + 0x37, 0x9A, 0xA5, 0x98, 0xB7, 0xF6, 0xB4, 0xBE, 0xCA, 0x78, 0x75, 0xE3, 0x9A, 0xC4, 0xE5, 0xC2, + 0x6B, 0x71, 0xE1, 0x08, 0x20, 0xAC, 0xA2, 0x49, 0xDA, 0x98, 0x26, 0xB4, 0x24, 0x53, 0xD2, 0x25, + 0xD9, 0x09, 0xAE, 0x54, 0xB8, 0x3E, 0x1A, 0x66, 0xE5, 0x7C, 0x62, 0x6C, 0x58, 0xE7, 0x26, 0x7F, + 0xC8, 0xD5, 0x85, 0xE7, 0x36, 0x9E, 0xCD, 0xC8, 0x35, 0x62, 0xB0, 0xB2, 0x4C, 0xE5, 0x1A, 0x90, + 0xB4, 0xFF, 0x37, 0x86, 0x4D, 0x08, 0xA9, 0x0F, 0x7F, 0x5A, 0xB1, 0x88, 0x23, 0xF9, 0x0B, 0xCC, + 0x03, 0xC3, 0xAE, 0x18, 0x0F, 0x27, 0x82, 0xD4, 0xA3, 0x73, 0x0C, 0x4B, 0xF0, 0x88, 0x04, 0x55, + 0x61, 0x2E, 0xF0, 0x80, 0xB0, 0x4D, 0xD3, 0xF0, 0xC9, 0x8E, 0x75, 0xCF, 0x51, 0x62, 0xD1, 0x05, + 0xA1, 0x81, 0xAA, 0x55, 0x56, 0x3F, 0x59, 0x9A, 0x6E, 0x52, 0x8E, 0xCC, 0xCA, 0x9C, 0xFE, 0xF3, + 0x92, 0x52, 0x95, 0xA3, 0x3C, 0x81, 0x9D, 0x15, 0x8E, 0x6E, 0xD1, 0x60, 0xF3, 0xCE, 0x6B, 0x98, + 0xF8, 0xE9, 0xCD, 0x97, 0x95, 0x91, 0x21, 0x8C, 0x55, 0x85, 0x0A, 0x4F, 0x77, 0x6E, 0x94, 0xE7, + 0xA6, 0xBE, 0x09, 0x75, 0xA1, 0xFB, 0x95, 0x6A, 0x8A, 0xB8, 0x8B, 0x54, 0x52, 0x9B, 0x98, 0x0C, + 0xF6, 0xF5, 0x13, 0x90, 0xDA, 0x91, 0x46, 0x7F, 0x3B, 0x3F, 0x6B, 0x5E, 0xBC, 0xEF, 0xD1, 0xB1, + 0xD6, 0x56, 0xA2, 0xC7, 0x5F, 0xAF, 0xDF, 0x1F, 0x28, 0xAF, 0xB4, 0x39, 0x1F, 0x6D, 0x22, 0x16, + 0x55, 0x21, 0x4D, 0x78, 0x7F, 0x63, 0x28, 0x13, 0x4D, 0xD7, 0xA1, 0x9D, 0xD5, 0xF2, 0x10, 0xF5, + 0x88, 0xF5, 0x9F, 0x8E, 0xB8, 0xF6, 0xED, 0xC4, 0xFE, 0xCE, 0xBD, 0x92, 0xD2, 0x34, 0x99, 0x8B, + 0xE3, 0x3D, 0x40, 0xC3, 0xE4, 0xE7, 0x12, 0x37, 0x83, 0xB5, 0x7A, 0x36, 0xDE, 0xC5, 0x25, 0x4C, + 0x90, 0x0D, 0x35, 0xD5, 0x21, 0xD9, 0x12, 0x04, 0x41, 0x58, 0xAA, 0x0C, 0x64, 0x05, 0x61, 0x51, + 0xBC, 0x83, 0x32, 0xA9, 0x20, 0x38, 0x38, 0xEB, 0x5C, 0x1B, 0x24, 0x4F, 0x20, 0x2C, 0xC3, 0x31, + 0xF9, 0x84, 0x20, 0x23, 0x45, 0x3B, 0xFA, 0x2A, 0x01, 0x32, 0x50, 0xFA, 0xFB, 0xF6, 0x98, 0xC7, + 0xC5, 0x53, 0x8F, 0xE9, 0x86, 0xE6, 0xA3, 0x1C, 0x8C, 0xD2, 0xAB, 0xC5, 0x81, 0x78, 0xD1, 0x8B, + 0x32, 0x70, 0x51, 0x94, 0x86, 0x69, 0x70, 0x17, 0xE3, 0x3E, 0x92, 0xDD, 0xD0, 0x7C, 0x1C, 0xFC, + 0x0B, 0xE9, 0x5B, 0x88, 0x4B, 0xC2, 0x08, 0x20, 0xA4, 0xDF, 0xE4, 0xCB, 0xE4, 0x6E, 0x29, 0xAC, + 0x96, 0xD3, 0x12, 0x35, 0x2C, 0x06, 0x45, 0x71, 0x7F, 0x64, 0x7B, 0x3C, 0x5A, 0x1A, 0xC8, 0xF2, + 0xD0, 0x49, 0x28, 0x81, 0x8F, 0xC5, 0x52, 0xEF, 0x39, 0x7E, 0x26, 0xDE, 0xA2, 0x07, 0x2E, 0xFE, + 0x1D, 0x09, 0xC4, 0x9A, 0x6A, 0x6A, 0x41, 0xB4, 0xBE, 0x88, 0xDF, 0x1C, 0xC3, 0xA8, 0xDC, 0x42, + 0x70, 0xAE, 0x97, 0xBF, 0x65, 0xC2, 0xA6, 0x9F, 0x80, 0xD4, 0x09, 0x37, 0xCE, 0xEC, 0x60, 0x8A, + 0xCE, 0x11, 0xC9, 0xC5, 0xE5, 0x4D, 0xFC, 0x02, 0xB2, 0xDC, 0x11, 0x33, 0xD3, 0x91, 0x3C, 0x48, + 0x04, 0x39, 0x24, 0xC6, 0xFE, 0x5D, 0x0E, 0x7E, 0x24, 0x7E, 0x97, 0xE3, 0x70, 0x82, 0x49, 0xCD, + 0x8E, 0x42, 0xCF, 0xC3, 0x1B, 0x26, 0x7A, 0x04, 0x73, 0x8F, 0x3C, 0x17, 0x52, 0x48, 0x39, 0x1A, + 0x7D, 0xEE, 0x2B, 0xBE, 0x54, 0x80, 0x7E, 0x52, 0x4D, 0xC3, 0xE4, 0xF6, 0x33, 0x41, 0x34, 0x1C, + 0xFD, 0x48, 0xFE, 0xB1, 0x6A, 0x98, 0x46, 0xE3, 0xD7, 0x0C, 0x54, 0xD3, 0xB6, 0x60, 0x72, 0xCF, + 0x05, 0xD4, 0xAB, 0x2F, 0x37, 0xC7, 0xFD, 0x3E, 0x00, 0x55, 0xFC, 0xB1, 0x72, 0xA0, 0x86, 0xE3, + 0xD7, 0x0C, 0x54, 0xE7, 0xD9, 0x00, 0xDA, 0x43, 0x60, 0xF6, 0x56, 0x0F, 0xC8, 0x5E, 0xED, 0x40, + 0xBC, 0x51, 0x1F, 0x0C, 0xED, 0x99, 0xC0, 0x08, 0x63, 0x5F, 0x9E, 0x36, 0x8E, 0x6E, 0x8E, 0xFF, + 0x76, 0x79, 0xBA, 0x6A, 0x50, 0xCA, 0xB1, 0xEB, 0x05, 0xE6, 0xBF, 0xFC, 0xF9, 0x6C, 0x34, 0xFE, + 0x2F, 0xBF, 0x21, 0x81, 0xE1, 0xCF, 0x55, 0x43, 0x52, 0x8C, 0x5C, 0x2F, 0x20, 0xFB, 0xF7, 0xEA, + 0x73, 0x01, 0xB2, 0x7F, 0x72, 0x0C, 0xCB, 0xC1, 0x9F, 0xAB, 0x06, 0xA4, 0x18, 0x79, 0x21, 0x40, + 0xCE, 0xA5, 0x5A, 0x3B, 0x13, 0xBF, 0x90, 0x84, 0xDC, 0xBB, 0x04, 0x7F, 0xB9, 0x42, 0x45, 0x86, + 0x66, 0x22, 0xCE, 0xE0, 0xCF, 0xAA, 0x27, 0x8D, 0xCA, 0x55, 0x0C, 0x0A, 0x13, 0x3C, 0xA9, 0x03, + 0x35, 0x84, 0x2C, 0xFC, 0x29, 0x9A, 0x50, 0xE3, 0xE8, 0x9C, 0x9E, 0x30, 0xF9, 0x88, 0x7D, 0x3E, + 0xEE, 0xEF, 0x9C, 0xEF, 0x17, 0xEA, 0x90, 0xA7, 0x36, 0x68, 0x25, 0x94, 0x7F, 0xC7, 0x5B, 0x3E, + 0x56, 0xCD, 0x4E, 0x7A, 0xC5, 0x17, 0x1A, 0x5F, 0x41, 0x31, 0xF9, 0x0C, 0x2D, 0xD8, 0xB1, 0x34, + 0xBB, 0xB3, 0x3E, 0x77, 0x29, 0x8A, 0x4C, 0x9B, 0xC0, 0x21, 0xBC, 0x5B, 0xD0, 0xE9, 0x9A, 0x92, + 0xB4, 0x9C, 0x10, 0xA8, 0x09, 0xB2, 0x88, 0x69, 0x27, 0xE7, 0xE5, 0x34, 0x93, 0x75, 0x74, 0xA9, + 0xAB, 0xA4, 0xC1, 0x97, 0xC2, 0xE1, 0x0A, 0xF8, 0x7B, 0xE3, 0xBB, 0x86, 0x73, 0x6A, 0x1A, 0x98, + 0xCC, 0x23, 0x44, 0xE0, 0x9B, 0xBB, 0xDB, 0xCB, 0x1E, 0x13, 0x0F, 0x8B, 0x15, 0xF8, 0xC5, 0xD1, + 0x31, 0x31, 0x87, 0x15, 0x59, 0x0A, 0xBE, 0x92, 0xF6, 0x6C, 0x78, 0x72, 0xB1, 0x1A, 0x8D, 0x8E, + 0x2A, 0x34, 0x5E, 0x6B, 0x60, 0xCE, 0x8A, 0x18, 0x3E, 0xD2, 0x48, 0x42, 0x6D, 0x56, 0x41, 0x3B, + 0xF6, 0x48, 0x95, 0xF6, 0x04, 0xDA, 0xB6, 0xD8, 0xAF, 0x76, 0xE0, 0x86, 0x91, 0x90, 0xE3, 0xC0, + 0xF3, 0xD1, 0x66, 0xF7, 0x68, 0xA0, 0xA7, 0x91, 0x48, 0x0A, 0xE1, 0xA2, 0xD3, 0x2F, 0x53, 0x7D, + 0x86, 0x5E, 0x19, 0xBE, 0x31, 0xE6, 0x71, 0xA5, 0xFA, 0xCC, 0xF0, 0xD6, 0x4F, 0xA3, 0x2E, 0xC5, + 0x89, 0xAD, 0xC9, 0xA6, 0x85, 0x6E, 0x8A, 0xA9, 0x51, 0xBC, 0x4B, 0x36, 0xF8, 0xC6, 0xA7, 0x01, + 0x3B, 0xC3, 0xDD, 0xCF, 0xB6, 0xE7, 0x97, 0xF0, 0x76, 0xDF, 0x9B, 0x31, 0x06, 0x8B, 0xE6, 0x85, + 0xDC, 0x03, 0xFB, 0x3F, 0x9C, 0xD7, 0x60, 0x79, 0x30, 0x9F, 0xC1, 0xB2, 0xC0, 0xF1, 0x3D, 0x1D, + 0x06, 0xA5, 0xAD, 0x97, 0xA9, 0xCD, 0x97, 0x6F, 0xCC, 0x5C, 0x31, 0x5E, 0xF4, 0x30, 0x39, 0xE1, + 0x12, 0xF1, 0x02, 0xFB, 0x5F, 0x35, 0x5E, 0x14, 0x1A, 0xB2, 0xD3, 0xA1, 0x30, 0x3F, 0x66, 0x60, + 0xF3, 0x17, 0x87, 0x19, 0x70, 0x14, 0xB8, 0xCB, 0xC4, 0x0C, 0xEC, 0x7F, 0xDD, 0x39, 0x06, 0xC1, + 0x60, 0x7E, 0xBC, 0xC0, 0xE6, 0x2F, 0x12, 0x2F, 0x7A, 0x3F, 0x17, 0x4B, 0xB6, 0x39, 0x98, 0x93, + 0x75, 0xF1, 0x58, 0x01, 0x73, 0x58, 0xEF, 0xE7, 0xB9, 0x91, 0xE7, 0x9D, 0x40, 0xDC, 0x55, 0x20, + 0x0F, 0x00, 0x6A, 0x31, 0xF4, 0xE9, 0xFD, 0xFC, 0x82, 0x10, 0xE8, 0xDA, 0x0E, 0x2C, 0xBF, 0x67, + 0x1B, 0xD6, 0x7C, 0x47, 0x0E, 0x35, 0x2F, 0x71, 0xE2, 0x40, 0xFF, 0xEB, 0xCB, 0x58, 0x62, 0x30, + 0x98, 0x07, 0x33, 0x26, 0xCD, 0x5F, 0x24, 0x5E, 0x2C, 0x89, 0xB1, 0x54, 0xC0, 0x9C, 0xE2, 0xB7, + 0xD6, 0x98, 0xF1, 0x24, 0x00, 0xB9, 0x18, 0x7A, 0xAD, 0x17, 0xE3, 0x89, 0xF4, 0xFB, 0x76, 0xED, + 0xB6, 0xCC, 0xD8, 0xEA, 0xEF, 0x30, 0xB9, 0xF0, 0xD8, 0xF0, 0x3F, 0x7D, 0x3A, 0x6E, 0x1C, 0x85, + 0x1F, 0x18, 0x7C, 0x42, 0x0D, 0x58, 0xF0, 0xE4, 0xE5, 0xDB, 0xA2, 0x32, 0xE6, 0x53, 0xB8, 0x99, + 0x81, 0x45, 0x1D, 0x72, 0x7D, 0xD5, 0xB6, 0x2B, 0xBA, 0xEA, 0xD7, 0x08, 0x3A, 0x98, 0x0A, 0xF4, + 0x8F, 0xC0, 0x40, 0xB7, 0x57, 0xF8, 0x6B, 0x18, 0x98, 0xAA, 0x3B, 0xB9, 0x40, 0x97, 0xF9, 0x2B, + 0xC9, 0x60, 0x40, 0x37, 0xF4, 0x64, 0xC5, 0xDA, 0x42, 0xF0, 0x8E, 0x45, 0xF0, 0xEF, 0x5B, 0xE6, + 0xC9, 0xCC, 0x4A, 0xF8, 0xBD, 0xE8, 0x12, 0x7E, 0x59, 0x4C, 0x0D, 0x7C, 0x7B, 0x8C, 0x69, 0xAF, + 0x55, 0xD3, 0x7C, 0x92, 0x41, 0xA4, 0xD2, 0x91, 0x40, 0x75, 0xB9, 0xE7, 0x33, 0xF5, 0x41, 0x35, + 0x28, 0x25, 0xFA, 0xB4, 0x7D, 0x22, 0xC5, 0xA8, 0xF0, 0x12, 0x6D, 0x61, 0xC2, 0x89, 0x9D, 0x80, + 0x88, 0x0E, 0xEC, 0x27, 0x22, 0xDC, 0x32, 0x67, 0x2C, 0x19, 0xC5, 0x19, 0x85, 0x0D, 0x0F, 0xB9, + 0x2F, 0x23, 0xB0, 0xAF, 0x0C, 0xCF, 0xDF, 0x7A, 0x3B, 0x13, 0x9F, 0x3C, 0xD6, 0xE9, 0x97, 0x4C, + 0x38, 0x9D, 0x19, 0xEF, 0x99, 0x6F, 0x64, 0x2A, 0x11, 0x0B, 0x5A, 0x18, 0xF7, 0x89, 0x31, 0xB5, + 0xB9, 0xA3, 0xCC, 0x19, 0x16, 0x8A, 0xFD, 0xE6, 0x6F, 0xA0, 0x04, 0x10, 0x85, 0x39, 0x7A, 0xF1, + 0xE0, 0x50, 0x68, 0xBA, 0x78, 0x7C, 0xE8, 0x64, 0xAF, 0x8B, 0x02, 0xB4, 0xCB, 0x93, 0xF0, 0x34, + 0x5E, 0x5C, 0x02, 0x16, 0xDF, 0xE1, 0x69, 0x52, 0xC6, 0x16, 0x78, 0x73, 0x7D, 0x7E, 0x4C, 0xCE, + 0x73, 0xB7, 0xC7, 0x3F, 0xFF, 0x42, 0x29, 0xCA, 0x90, 0xE2, 0xFC, 0x47, 0x9B, 0x8D, 0x6D, 0x20, + 0x39, 0xCD, 0x1E, 0x8F, 0x6D, 0x8B, 0xBC, 0x86, 0xB0, 0xC2, 0x01, 0x3A, 0xF4, 0xC0, 0xF6, 0x7A, + 0xC2, 0x33, 0x5E, 0x00, 0xEA, 0x3E, 0xF0, 0x93, 0xBE, 0x38, 0x5E, 0xE0, 0xE0, 0xBB, 0x9E, 0x70, + 0x8D, 0x07, 0x4A, 0xB7, 0xD8, 0x3B, 0x25, 0x96, 0xEC, 0x4C, 0x36, 0xF4, 0x5A, 0xEC, 0x5C, 0xD5, + 0x46, 0x51, 0x3F, 0x22, 0x3A, 0x47, 0xF0, 0x54, 0xB9, 0x83, 0xEC, 0x71, 0x64, 0x4C, 0xDE, 0x10, + 0x19, 0xD4, 0x74, 0x49, 0xE0, 0x6C, 0x4B, 0x79, 0x4B, 0x13, 0xC7, 0x14, 0x77, 0xF6, 0xC0, 0xC7, + 0x60, 0x1E, 0x8A, 0xF7, 0x11, 0xBD, 0x51, 0xC2, 0x62, 0x39, 0xE5, 0xAD, 0x36, 0xFB, 0x08, 0x44, + 0x30, 0xF9, 0x32, 0xFA, 0x06, 0x43, 0x24, 0xDB, 0xE8, 0x5B, 0xB8, 0xCD, 0xF6, 0xE4, 0x3B, 0xE2, + 0x3B, 0x19, 0x75, 0xB4, 0xC7, 0x28, 0x9A, 0xF2, 0x6D, 0x8C, 0xC1, 0x10, 0xC0, 0x80, 0x9D, 0x6D, + 0xB3, 0x4F, 0x7D, 0xFA, 0x71, 0x87, 0x3F, 0xFE, 0xB6, 0x2D, 0x80, 0x78, 0x7D, 0x1A, 0x0B, 0x65, + 0xC4, 0x03, 0xA5, 0xA3, 0x6C, 0x64, 0x82, 0xB6, 0xA2, 0x90, 0xE4, 0x7C, 0x3A, 0xCA, 0xC9, 0x1D, + 0x98, 0x92, 0xF9, 0x72, 0x2F, 0x25, 0xF1, 0x65, 0x6A, 0x8F, 0xF3, 0xE6, 0x6B, 0x88, 0xF3, 0xC2, + 0xD9, 0x9C, 0x0D, 0xD6, 0x98, 0xAB, 0x72, 0x77, 0xBD, 0xAC, 0x84, 0x0D, 0xE5, 0x3C, 0x5B, 0x23, + 0x3E, 0x4B, 0xD9, 0x01, 0xEE, 0xEC, 0x3E, 0x45, 0xA6, 0x19, 0xD6, 0x30, 0xEC, 0x7E, 0x92, 0xA7, + 0x01, 0x30, 0x23, 0xFA, 0x36, 0xC4, 0x2D, 0xAF, 0x64, 0xD6, 0x86, 0x92, 0x51, 0x5A, 0xCB, 0x81, + 0x96, 0x8C, 0x07, 0x5E, 0x0E, 0xC0, 0xAE, 0x92, 0x9D, 0x27, 0xC0, 0x25, 0xBF, 0x5B, 0x02, 0xB0, + 0xD2, 0x72, 0xE6, 0xA6, 0x9F, 0xBD, 0xB6, 0xA7, 0x1A, 0x1A, 0x32, 0x80, 0xBE, 0xEF, 0x72, 0x75, + 0x1C, 0x06, 0x60, 0x79, 0x65, 0x20, 0x9A, 0xD5, 0x49, 0xBB, 0x44, 0x18, 0x57, 0x96, 0x76, 0xE5, + 0x51, 0x0F, 0x97, 0x18, 0xB4, 0x04, 0xD2, 0x25, 0xF6, 0xF9, 0x3B, 0x48, 0xCB, 0xC4, 0xA1, 0x64, + 0xE7, 0x22, 0xA2, 0x09, 0xBE, 0x3C, 0x2C, 0xE7, 0x68, 0x3C, 0x61, 0x4A, 0xF1, 0xF0, 0x98, 0xD4, + 0x71, 0x68, 0x35, 0xA9, 0xDF, 0x94, 0x0E, 0x8C, 0x9C, 0x37, 0xAC, 0x26, 0x31, 0x53, 0x9B, 0x9C, + 0x47, 0x63, 0x49, 0x1B, 0xDB, 0xCA, 0x18, 0xB0, 0x43, 0x3C, 0x9E, 0xBB, 0x9F, 0x76, 0xE3, 0xA8, + 0x53, 0x47, 0x3F, 0x1D, 0xCC, 0x32, 0x5C, 0x43, 0x3F, 0xDD, 0xC6, 0xD1, 0x5E, 0x1D, 0xFD, 0xEC, + 0x22, 0x7C, 0xEA, 0xE8, 0x68, 0x0F, 0x01, 0x54, 0x47, 0x47, 0xFB, 0xB8, 0xB2, 0x3A, 0x3A, 0x7A, + 0x07, 0x4B, 0x5B, 0xBC, 0x97, 0x03, 0x58, 0xD7, 0xE2, 0xBD, 0xBC, 0x87, 0x45, 0xD5, 0x80, 0x84, + 0x84, 0xCD, 0x35, 0xF4, 0xD3, 0xC6, 0x3C, 0xE2, 0x35, 0xF4, 0x03, 0xD8, 0xDC, 0xAD, 0x63, 0x3E, + 0x80, 0xCD, 0xFB, 0x75, 0xF4, 0x03, 0xD8, 0xDC, 0x19, 0x1B, 0xD6, 0xE2, 0x1D, 0x01, 0x36, 0xEF, + 0xD5, 0xD2, 0xD1, 0x3E, 0xF1, 0x9F, 0x3A, 0x7A, 0x42, 0x74, 0xAE, 0x67, 0x4E, 0x07, 0xB8, 0x6B, + 0xB5, 0xF4, 0xF4, 0x1E, 0xF7, 0xAD, 0x72, 0x4F, 0xC5, 0x51, 0x90, 0x4B, 0xB2, 0x5D, 0x4C, 0xDB, + 0x30, 0x26, 0x26, 0x85, 0xF0, 0x58, 0xA4, 0x60, 0xA1, 0xF8, 0x81, 0xB9, 0x69, 0xE9, 0x46, 0x4A, + 0x49, 0x30, 0x25, 0x25, 0xA1, 0x5C, 0x39, 0xA5, 0x53, 0xAF, 0x9C, 0xD2, 0x4E, 0xC8, 0x29, 0x9D, + 0xA5, 0xC9, 0x29, 0xED, 0x4C, 0x39, 0xA5, 0xFD, 0x2A, 0xA7, 0xBC, 0xCA, 0x29, 0xAF, 0x72, 0xCA, + 0xAB, 0x9C, 0xF2, 0x2A, 0xA7, 0xBC, 0xCA, 0x29, 0x9B, 0x26, 0xA7, 0x74, 0x36, 0x2F, 0x2D, 0x5A, + 0xB1, 0x21, 0xA6, 0xA4, 0x61, 0x2B, 0x66, 0xEE, 0xC6, 0x6B, 0x0C, 0x34, 0x1D, 0x2E, 0x37, 0xBE, + 0xB8, 0xC4, 0xE3, 0xD0, 0xAE, 0x9A, 0x97, 0xA5, 0x93, 0x92, 0x4B, 0x3D, 0x67, 0x96, 0x4E, 0x51, + 0xC3, 0xD0, 0x5B, 0x56, 0xF1, 0x9E, 0x93, 0x58, 0xD7, 0x95, 0xAE, 0x67, 0x62, 0x0D, 0x33, 0xF6, + 0x01, 0x01, 0x99, 0x52, 0x91, 0x2F, 0xA3, 0x74, 0x0D, 0xFE, 0x8B, 0x01, 0x7B, 0x2A, 0xD1, 0x27, + 0x41, 0x60, 0x9D, 0x12, 0x7D, 0xE6, 0x2E, 0x7F, 0xCE, 0x32, 0x3C, 0x24, 0xF3, 0x92, 0x81, 0xF9, + 0xD2, 0x12, 0xB9, 0xBE, 0x0B, 0xBD, 0xD8, 0x13, 0xC9, 0xC2, 0x43, 0x40, 0xDD, 0xC1, 0xC3, 0xBE, + 0xEC, 0x27, 0x4C, 0x21, 0x1E, 0x3E, 0x4F, 0x7A, 0x2B, 0xC8, 0x86, 0x85, 0xD7, 0xD3, 0x71, 0xBD, + 0x60, 0x66, 0x84, 0x23, 0xF1, 0x57, 0xF3, 0xD2, 0x5A, 0x71, 0xF0, 0xFC, 0xA5, 0x48, 0x52, 0xE7, + 0xB8, 0x5C, 0x33, 0x00, 0x71, 0xA2, 0x7C, 0x9A, 0xF6, 0x80, 0xA9, 0x0C, 0xA7, 0xC9, 0x3C, 0x99, + 0x13, 0x8A, 0x8A, 0x30, 0xFA, 0xEC, 0x9B, 0x45, 0x65, 0x9E, 0x7D, 0x4A, 0xA3, 0x79, 0xCF, 0x99, + 0x7D, 0x4F, 0x25, 0x0D, 0x75, 0x76, 0xFF, 0x84, 0x49, 0x38, 0xBD, 0xD0, 0xB8, 0x8F, 0x89, 0x38, + 0xB1, 0xE7, 0xB0, 0xC7, 0x16, 0x65, 0xA5, 0xA3, 0x1E, 0xA1, 0x27, 0xBC, 0x5D, 0xA2, 0x04, 0x17, + 0x78, 0xF3, 0x44, 0x13, 0x17, 0x39, 0xA6, 0xD4, 0x6F, 0x78, 0xDB, 0xE5, 0x38, 0xAE, 0xFD, 0x1D, + 0x68, 0xD9, 0xC7, 0x1C, 0x4F, 0xFB, 0x4A, 0x94, 0xFF, 0xEE, 0xD1, 0x76, 0xFD, 0x91, 0x48, 0xF0, + 0xA4, 0xEA, 0x61, 0xAD, 0x22, 0x31, 0x01, 0x9C, 0x2D, 0x5E, 0x95, 0x0F, 0x60, 0x66, 0xD1, 0x22, + 0x70, 0x34, 0x9D, 0xD9, 0xD6, 0x74, 0x1E, 0xBE, 0x30, 0x6D, 0xDF, 0x98, 0xFB, 0x23, 0x5B, 0x67, + 0xAA, 0x36, 0x32, 0x38, 0x26, 0xC5, 0xFA, 0xF7, 0xAE, 0xA2, 0x8D, 0x53, 0x72, 0x5A, 0xE1, 0x4D, + 0x1F, 0x5E, 0xD4, 0x3F, 0x00, 0x77, 0x69, 0xB1, 0x4B, 0x4B, 0x83, 0xF1, 0xBD, 0x30, 0xB9, 0x55, + 0x98, 0xCF, 0xE3, 0xCB, 0x3D, 0x46, 0x07, 0x88, 0x81, 0xEF, 0x8C, 0x31, 0x0F, 0x13, 0x89, 0xDE, + 0x0A, 0x9F, 0x01, 0x1D, 0x73, 0xC9, 0x5A, 0x0C, 0xAB, 0x06, 0xC6, 0x22, 0xFA, 0x0D, 0xD1, 0x17, + 0x8F, 0x12, 0x52, 0xD1, 0x58, 0x74, 0xF1, 0x88, 0xB9, 0xB2, 0x60, 0x0A, 0x06, 0xE6, 0xF0, 0x3A, + 0xE1, 0x58, 0x80, 0x75, 0xF2, 0x92, 0xE1, 0x85, 0x93, 0xD6, 0x29, 0xEC, 0x80, 0x12, 0x02, 0x36, + 0xE1, 0x95, 0x71, 0x94, 0xB7, 0x54, 0xA6, 0xBB, 0xA2, 0x12, 0x4B, 0x98, 0x93, 0x99, 0xEA, 0x97, + 0x4E, 0xF2, 0x88, 0x28, 0x22, 0xA9, 0xD7, 0x5E, 0x4B, 0x19, 0x4F, 0x2E, 0xEE, 0xF6, 0xE9, 0xE6, + 0x6E, 0x5F, 0x51, 0xBC, 0x6D, 0xD6, 0x6E, 0xD1, 0x07, 0x7A, 0x61, 0xE3, 0xE2, 0x14, 0x42, 0x0E, + 0x20, 0xE3, 0xC5, 0xBC, 0x46, 0x89, 0x78, 0xC3, 0xB0, 0xCD, 0x9C, 0x81, 0x0D, 0x0B, 0x79, 0x8E, + 0x4D, 0xF3, 0x07, 0x7B, 0x82, 0x4B, 0x32, 0x4F, 0x66, 0xAE, 0x4B, 0x61, 0xBB, 0x93, 0x96, 0xE5, + 0x06, 0xB1, 0xB2, 0xDC, 0x6D, 0x54, 0x0C, 0x73, 0x31, 0x56, 0x85, 0x6D, 0x79, 0x6F, 0x0F, 0x4B, + 0x5B, 0x23, 0x8A, 0x32, 0x23, 0x56, 0x30, 0x17, 0x54, 0xF3, 0x73, 0x4F, 0x01, 0x52, 0xC9, 0x61, + 0x9C, 0x8C, 0xF6, 0x8B, 0xF8, 0x84, 0x55, 0x93, 0xF5, 0x56, 0x8E, 0x46, 0x3D, 0xC9, 0xCB, 0xC2, + 0x08, 0xBC, 0x72, 0xB2, 0x79, 0x35, 0x9C, 0x0B, 0x39, 0x5D, 0xA9, 0xAE, 0x89, 0x1B, 0x76, 0xCF, + 0x40, 0xAC, 0x07, 0x3E, 0x84, 0xE7, 0x7B, 0xC4, 0x19, 0x4B, 0x35, 0xDF, 0x1A, 0x2F, 0x8A, 0xA2, + 0x84, 0x9C, 0x12, 0x4F, 0xEF, 0x0B, 0xA3, 0x43, 0x17, 0x46, 0xD1, 0x99, 0x0D, 0x98, 0x17, 0x57, + 0xA7, 0x3B, 0x5A, 0x1D, 0xD2, 0x2E, 0xCB, 0xB5, 0x8B, 0x84, 0x81, 0x45, 0x85, 0xB5, 0x0B, 0xEC, + 0x64, 0x56, 0x52, 0x8B, 0xAC, 0x85, 0x29, 0x32, 0x5B, 0x35, 0x59, 0x4D, 0x0C, 0x70, 0x44, 0xBF, + 0x12, 0x27, 0x9E, 0x28, 0x7B, 0x7E, 0xC4, 0xB6, 0x4E, 0x47, 0x36, 0xA6, 0xE0, 0xC1, 0x1A, 0xB2, + 0xCC, 0x2E, 0x76, 0xF4, 0xFF, 0x24, 0xAB, 0xE0, 0xBE, 0x0D, 0x4B, 0xAD, 0x3E, 0x8B, 0xE4, 0x17, + 0x97, 0xF8, 0xFC, 0x98, 0x84, 0x16, 0xC9, 0x79, 0x28, 0xF8, 0x80, 0x9C, 0x47, 0xD5, 0xA1, 0x85, + 0xCC, 0xC6, 0x41, 0xE2, 0x00, 0x69, 0x44, 0xAE, 0x33, 0x5A, 0x07, 0x8B, 0x55, 0xD9, 0x45, 0x29, + 0x49, 0x84, 0x5A, 0x62, 0x62, 0xCF, 0xA4, 0x9B, 0x94, 0x10, 0x7C, 0xC6, 0x63, 0xAE, 0x1B, 0x42, + 0xC2, 0xBB, 0xE7, 0x43, 0x23, 0x4C, 0x1C, 0x4A, 0x61, 0x96, 0xB7, 0x77, 0xA7, 0xD7, 0xB3, 0xAE, + 0x8E, 0xC7, 0x52, 0x6A, 0x8C, 0xC4, 0x32, 0xC3, 0x13, 0x32, 0x5F, 0x24, 0x82, 0x92, 0x28, 0xD4, + 0xEB, 0xF5, 0xD8, 0x96, 0x23, 0x0A, 0xB5, 0x7B, 0x1C, 0x04, 0xD4, 0xC0, 0x65, 0x7E, 0xE0, 0xDB, + 0xA0, 0x82, 0x99, 0x6F, 0xA9, 0xB8, 0x24, 0xBE, 0x35, 0x25, 0x1A, 0x31, 0x75, 0x08, 0x5D, 0xA0, + 0xAB, 0x25, 0xCC, 0x93, 0xDC, 0xB5, 0xB4, 0x48, 0xF2, 0x8D, 0x89, 0x4D, 0x61, 0xF5, 0xEF, 0x37, + 0xDE, 0x04, 0x6E, 0x98, 0x3D, 0xD9, 0x0E, 0x4C, 0x9D, 0xBB, 0xDB, 0xA0, 0x77, 0x99, 0x36, 0xE0, + 0x96, 0xBD, 0x79, 0x12, 0x13, 0x81, 0xB6, 0x86, 0x98, 0xCE, 0x32, 0x81, 0xF6, 0x19, 0x94, 0x4C, + 0x53, 0x10, 0x3A, 0x61, 0x88, 0x47, 0x48, 0x77, 0xA2, 0x60, 0xB3, 0x2C, 0xE2, 0x24, 0xB7, 0x3E, + 0x41, 0xDB, 0x4A, 0x69, 0xA7, 0xFA, 0x09, 0xED, 0xCF, 0xE1, 0x67, 0x4D, 0xCC, 0x20, 0x6F, 0x8E, + 0x54, 0x3C, 0x9A, 0x4D, 0xBE, 0x88, 0x22, 0xFB, 0x73, 0xAF, 0xA9, 0x4A, 0x39, 0x8B, 0x87, 0x25, + 0x9D, 0x0B, 0x36, 0x68, 0x89, 0x52, 0x45, 0xAC, 0xF1, 0x04, 0x74, 0xBB, 0x91, 0x34, 0x20, 0x02, + 0x4B, 0x0E, 0x66, 0x44, 0x82, 0x92, 0xD7, 0x78, 0x59, 0x4E, 0x57, 0x81, 0x47, 0xA0, 0x8D, 0xC1, + 0xB4, 0x54, 0x65, 0xA1, 0xF2, 0x86, 0xC7, 0xC8, 0x11, 0x6B, 0x76, 0x24, 0x74, 0xC2, 0xBA, 0xC2, + 0x98, 0x70, 0xDA, 0xD6, 0x0B, 0xF4, 0xEB, 0x46, 0x37, 0xC0, 0x72, 0xEE, 0x57, 0xCB, 0xB7, 0x95, + 0xF6, 0x02, 0x99, 0x21, 0x5A, 0x26, 0xB8, 0x13, 0xD3, 0x8C, 0xB1, 0x60, 0x34, 0xA1, 0xDA, 0xF4, + 0x86, 0x60, 0x9B, 0xBF, 0xEC, 0xFC, 0xBA, 0xF3, 0x1B, 0x82, 0x89, 0x7B, 0xAD, 0x97, 0x6B, 0x44, + 0x5D, 0x0D, 0x09, 0x24, 0xCA, 0x23, 0x74, 0xE7, 0xB8, 0x18, 0xA6, 0x2D, 0x39, 0x17, 0x85, 0xE4, + 0xB3, 0xC8, 0x2A, 0x4E, 0x47, 0xBF, 0x54, 0x10, 0x76, 0x2B, 0xDC, 0x77, 0xA7, 0xA9, 0x6D, 0x07, + 0x4B, 0x91, 0x84, 0x63, 0x0B, 0xAE, 0x24, 0xF8, 0x4E, 0xDA, 0xBD, 0x08, 0xE5, 0x6C, 0x29, 0x38, + 0xF4, 0x6B, 0x39, 0x1C, 0xFA, 0xF5, 0xC5, 0xE0, 0xD0, 0xAF, 0x73, 0xE2, 0xD0, 0xAF, 0xAF, 0x38, + 0x94, 0x85, 0x43, 0xBF, 0x95, 0xC3, 0xA1, 0xDF, 0x5E, 0x0C, 0x0E, 0xFD, 0x36, 0x27, 0x0E, 0xFD, + 0xF6, 0x8A, 0x43, 0x53, 0x38, 0x64, 0x81, 0x00, 0x85, 0xE2, 0xB9, 0x10, 0xD4, 0xCB, 0x60, 0x51, + 0xB9, 0xFA, 0xA1, 0xCF, 0x8A, 0x48, 0x45, 0x51, 0xA9, 0xF1, 0x35, 0x57, 0x42, 0xA4, 0x78, 0xCB, + 0x97, 0x63, 0x6F, 0x3C, 0x3A, 0x0D, 0xE3, 0xA5, 0xBE, 0xE2, 0x95, 0x4B, 0x4C, 0x8C, 0x7E, 0xF6, + 0x52, 0x4E, 0x54, 0x2D, 0x04, 0xAD, 0x04, 0x83, 0x84, 0x74, 0x2C, 0xCD, 0x1A, 0x94, 0x0C, 0x19, + 0xE4, 0xE4, 0x3F, 0x02, 0xD8, 0x17, 0x98, 0xBE, 0x66, 0x3B, 0x4F, 0x3B, 0x0E, 0xC6, 0x66, 0x4A, + 0x0B, 0xC8, 0x8C, 0x44, 0xBD, 0xD9, 0x72, 0x74, 0xF9, 0x6A, 0x48, 0xCB, 0xE5, 0x3E, 0x55, 0xD4, + 0xA7, 0x69, 0xB2, 0xA1, 0x18, 0xBF, 0x8C, 0x8A, 0xD1, 0xC0, 0x5C, 0xE4, 0x9B, 0x87, 0x62, 0xA7, + 0x24, 0x68, 0xCA, 0xF9, 0x8A, 0x8A, 0x31, 0xE3, 0x9E, 0x95, 0x7D, 0x61, 0x03, 0x8A, 0x21, 0xB4, + 0x60, 0x73, 0x38, 0xA7, 0xAC, 0xEF, 0x3C, 0xE3, 0x4F, 0xF2, 0xBF, 0x2B, 0x8D, 0x1B, 0x51, 0xC4, + 0xD2, 0xF7, 0xE6, 0xA3, 0xA1, 0xFB, 0xA3, 0xC3, 0xEE, 0x9E, 0x52, 0x2E, 0x5E, 0x29, 0xCB, 0x0F, + 0x08, 0x55, 0x57, 0x61, 0x9F, 0x3A, 0x66, 0xCD, 0x76, 0xE7, 0x40, 0xE9, 0x28, 0xFB, 0xAD, 0xBD, + 0xFD, 0x03, 0xD6, 0xDC, 0x7D, 0xD7, 0xDE, 0x3F, 0x50, 0x76, 0x5B, 0xBB, 0x4A, 0x97, 0xED, 0x2A, + 0x07, 0xFB, 0xFB, 0xFB, 0x7B, 0xAD, 0xDD, 0x83, 0xDD, 0x3A, 0xFC, 0x28, 0xC3, 0x21, 0x4F, 0x56, + 0x34, 0x64, 0x97, 0x86, 0x3C, 0x5D, 0xD1, 0x68, 0xBB, 0x8D, 0xA3, 0x63, 0x3C, 0xB8, 0xEE, 0x46, + 0xAA, 0x7F, 0xE9, 0xDD, 0xD9, 0xF6, 0x95, 0x6D, 0x0D, 0xEF, 0xEC, 0x13, 0x7E, 0x26, 0x30, 0x10, + 0xF8, 0x48, 0xE6, 0x44, 0x2A, 0x55, 0x4C, 0x9B, 0x7B, 0xD2, 0xE5, 0xDD, 0xB8, 0xD6, 0x4D, 0x06, + 0x99, 0xB9, 0x60, 0xAA, 0xD1, 0x86, 0xA4, 0xEA, 0x7A, 0x52, 0x32, 0xA9, 0xD7, 0x70, 0x24, 0xBB, + 0x47, 0x6B, 0xD1, 0xB1, 0xAE, 0x57, 0xB4, 0x0E, 0xE5, 0x44, 0x1B, 0xAA, 0x4B, 0x9D, 0x75, 0xD8, + 0x7F, 0x68, 0xE4, 0xAA, 0x6B, 0xDE, 0x3A, 0x20, 0xA0, 0xCF, 0x97, 0x39, 0xF3, 0xC9, 0x08, 0x38, + 0xF7, 0x33, 0xFA, 0xB4, 0x2E, 0x36, 0x39, 0x40, 0x00, 0x90, 0x25, 0xB6, 0x45, 0x61, 0x5A, 0x55, + 0xA7, 0x64, 0x10, 0xDB, 0x64, 0x82, 0x8B, 0x2A, 0x83, 0xBB, 0x5C, 0x83, 0xD3, 0x22, 0x26, 0x49, + 0xD0, 0xDB, 0x62, 0x51, 0xCC, 0xF0, 0x5B, 0x3F, 0x9E, 0x7B, 0x63, 0x95, 0x6C, 0x29, 0xF5, 0xDF, + 0x2E, 0x7C, 0xC2, 0xCA, 0x06, 0x39, 0x97, 0x0B, 0xED, 0xE7, 0xBF, 0x5C, 0xF8, 0x44, 0xE5, 0x0F, + 0xE4, 0x25, 0x5B, 0xBD, 0x77, 0x0A, 0x43, 0xD9, 0xEB, 0xB3, 0xF9, 0xBC, 0x3C, 0xDB, 0xB5, 0xC2, + 0xA7, 0x68, 0xE5, 0x93, 0xAB, 0x85, 0xAA, 0x61, 0x31, 0xB9, 0x9C, 0x2D, 0x71, 0xB5, 0x90, 0x32, + 0x5A, 0x74, 0xBD, 0x70, 0x75, 0x35, 0x2A, 0x3D, 0xF0, 0xFA, 0xDF, 0x42, 0xC0, 0x6A, 0xA6, 0x2F, + 0x1E, 0xF0, 0xD1, 0xEB, 0xB5, 0x43, 0x1D, 0xFE, 0x3C, 0x04, 0xCF, 0x2B, 0xD5, 0x2F, 0x4E, 0x72, + 0x92, 0xE9, 0xC0, 0xB3, 0x3B, 0x95, 0x87, 0x6E, 0x77, 0x86, 0xB6, 0xA0, 0x7F, 0xC3, 0x0F, 0x74, + 0x5E, 0x83, 0x07, 0xD8, 0xFE, 0x12, 0x2C, 0x2F, 0x09, 0x18, 0x54, 0x37, 0xE1, 0xC9, 0x96, 0x2F, + 0xCD, 0xD3, 0x4B, 0xAC, 0x0D, 0x15, 0x92, 0xA5, 0xA2, 0x06, 0x0C, 0xB0, 0xFE, 0xB8, 0x11, 0x42, + 0x61, 0x0E, 0xE4, 0x90, 0x4D, 0x5F, 0x8C, 0x89, 0x57, 0x88, 0xE4, 0x3E, 0x89, 0x9E, 0x17, 0x58, + 0x08, 0xD7, 0x4F, 0x35, 0xD0, 0x56, 0x33, 0xFA, 0x66, 0x25, 0x9C, 0x03, 0x61, 0x5C, 0x8C, 0xC4, + 0xC4, 0x50, 0x87, 0x15, 0x03, 0x5A, 0x43, 0x53, 0x4F, 0x72, 0xC2, 0xF9, 0xC6, 0x9E, 0x4D, 0xBD, + 0x40, 0xAE, 0xDC, 0x38, 0x95, 0xE0, 0x8F, 0x4D, 0xC1, 0xAA, 0x1B, 0xC5, 0xC4, 0x3C, 0x9B, 0x35, + 0xF2, 0xF3, 0xF1, 0x79, 0xE9, 0xD3, 0xF8, 0x5A, 0x75, 0xBF, 0xED, 0xC0, 0x68, 0xE8, 0x75, 0x39, + 0xDF, 0x11, 0x5E, 0x8F, 0xD4, 0x11, 0x93, 0x3C, 0x3E, 0x73, 0x6C, 0xCA, 0x8E, 0xEF, 0xED, 0x07, + 0xCE, 0xCE, 0x4D, 0xD3, 0x70, 0x3C, 0xDB, 0xD0, 0x43, 0x2F, 0x36, 0x00, 0xC2, 0x37, 0xE9, 0xE4, + 0x6F, 0x48, 0x01, 0x25, 0x12, 0xBD, 0x50, 0xE1, 0x52, 0x25, 0xE4, 0xE2, 0xEF, 0xE3, 0xF3, 0xB1, + 0x6D, 0x05, 0x54, 0x63, 0x5A, 0x06, 0x0D, 0xE0, 0x16, 0x59, 0x7A, 0x15, 0xC1, 0x65, 0xE9, 0xC2, + 0x4B, 0x15, 0x01, 0xA6, 0xB4, 0xCD, 0xB7, 0x90, 0x5D, 0xEF, 0x56, 0x91, 0xDD, 0xAA, 0xDF, 0xC8, + 0x45, 0x98, 0x5C, 0x76, 0x4D, 0xE5, 0xAE, 0x84, 0x4A, 0xBE, 0xE6, 0xCC, 0x4E, 0xA4, 0x86, 0xF4, + 0xA0, 0x4B, 0xE4, 0x24, 0x84, 0x5F, 0xE9, 0x3C, 0x91, 0xC9, 0xBC, 0x57, 0x26, 0x1F, 0xF8, 0x87, + 0xBB, 0x15, 0xCC, 0xC8, 0x69, 0x8A, 0xB3, 0x50, 0x8C, 0x91, 0x38, 0x84, 0x7B, 0x6E, 0xCC, 0x9D, + 0x36, 0x66, 0x00, 0xC3, 0x6A, 0xF5, 0xC0, 0x4D, 0x50, 0xA3, 0xA9, 0xC2, 0xC7, 0x28, 0x14, 0x4C, + 0x35, 0x35, 0x68, 0x8A, 0xFC, 0x05, 0x13, 0x64, 0x8B, 0x1A, 0x94, 0x9C, 0xC1, 0x23, 0xE2, 0x39, + 0x73, 0x9C, 0x20, 0xCB, 0xD0, 0x72, 0x2E, 0x07, 0x61, 0x11, 0xCC, 0xED, 0x68, 0x6A, 0xA2, 0xD2, + 0xA8, 0x9C, 0x30, 0x45, 0x3F, 0xFD, 0xB3, 0x17, 0xDC, 0xFB, 0xAE, 0x4A, 0x55, 0x3C, 0xFF, 0x99, + 0xB8, 0x47, 0xE8, 0x33, 0x7B, 0x0C, 0xBF, 0x2D, 0x4B, 0x65, 0x92, 0x67, 0x61, 0xE0, 0xCD, 0xF1, + 0x6D, 0x6F, 0x92, 0x2B, 0x34, 0xE2, 0x46, 0x51, 0x7E, 0x3E, 0xE8, 0x8E, 0x0A, 0xE0, 0x60, 0x19, + 0xEE, 0xD0, 0x71, 0x76, 0xC2, 0xCF, 0x46, 0xD0, 0xC3, 0x13, 0x7B, 0x1C, 0x71, 0x4B, 0x46, 0x35, + 0x89, 0xBA, 0xA1, 0xC4, 0xC4, 0xC8, 0xBB, 0x55, 0x95, 0x97, 0x57, 0x8E, 0x0D, 0xFC, 0x66, 0x94, + 0x18, 0x75, 0x9B, 0x22, 0x8C, 0x02, 0x4B, 0xBC, 0x40, 0x4D, 0xA2, 0x3A, 0xE0, 0x2D, 0x76, 0x63, + 0xFB, 0xFC, 0x90, 0x22, 0xB5, 0xA2, 0x75, 0xD2, 0x96, 0x53, 0xC4, 0x91, 0xF9, 0xA8, 0x3E, 0x79, + 0xD2, 0x82, 0x25, 0x2B, 0x9C, 0x8E, 0x30, 0xD6, 0x30, 0xE3, 0xE6, 0x8F, 0xF9, 0x08, 0xB2, 0x0D, + 0x54, 0xFA, 0xCA, 0x24, 0x3B, 0x2D, 0x17, 0xB1, 0xBB, 0xB1, 0x04, 0xDE, 0xC9, 0x20, 0x70, 0x56, + 0x2A, 0x0B, 0x6F, 0x3A, 0xA1, 0x9B, 0xD0, 0xC5, 0x71, 0xEF, 0x74, 0x9A, 0xCE, 0xE1, 0xD1, 0x9A, + 0x92, 0x39, 0xCC, 0x2C, 0x85, 0xCA, 0x55, 0x5D, 0x2F, 0x4B, 0xE0, 0x92, 0x42, 0xCA, 0x92, 0xF7, + 0x2B, 0xF1, 0x55, 0x21, 0xBE, 0x15, 0x2A, 0xD6, 0x12, 0x71, 0x8B, 0xA5, 0xEC, 0xBD, 0x34, 0x29, + 0x1B, 0xF1, 0xA8, 0xB4, 0xE0, 0xBC, 0x3C, 0xEC, 0xCE, 0x17, 0x98, 0x43, 0x0C, 0xEE, 0x8D, 0x28, + 0x7E, 0x9C, 0x10, 0x3B, 0x29, 0x40, 0x7B, 0xC1, 0x38, 0x7C, 0x59, 0x95, 0x2F, 0x8B, 0x93, 0x65, + 0x9B, 0x39, 0xD4, 0x48, 0xA3, 0x46, 0x22, 0x1F, 0x2C, 0x9D, 0x2B, 0xE2, 0x6B, 0xC2, 0x6B, 0xF4, + 0xB3, 0x70, 0x19, 0xD7, 0x0D, 0x42, 0xCB, 0x6D, 0xE6, 0x8D, 0xF0, 0xF4, 0xC1, 0xDC, 0x0E, 0x88, + 0x64, 0xA8, 0xE7, 0x51, 0x08, 0x0A, 0x60, 0xF2, 0xCB, 0xF4, 0xA9, 0xC8, 0x95, 0xAD, 0xF7, 0x96, + 0xE7, 0xE9, 0x16, 0x62, 0x6E, 0x98, 0x3C, 0xB8, 0xBA, 0x4D, 0x44, 0xF4, 0xB0, 0x32, 0x8B, 0x08, + 0xDD, 0x6B, 0xAC, 0x86, 0xB4, 0x43, 0x97, 0x92, 0xD0, 0x38, 0xDF, 0x98, 0xC7, 0x24, 0x46, 0xAE, + 0x6C, 0xCF, 0x4C, 0xD9, 0x9F, 0xE0, 0xF8, 0x10, 0xC1, 0x56, 0x14, 0x2E, 0x0E, 0xD2, 0x5F, 0xB8, + 0x34, 0x76, 0xCF, 0x07, 0x98, 0x8B, 0x5A, 0x9C, 0x59, 0x18, 0xCD, 0x25, 0xCF, 0x8D, 0x28, 0xBD, + 0x35, 0xF9, 0x3F, 0xC5, 0x7D, 0x9C, 0xD0, 0x57, 0xEA, 0x07, 0xA4, 0xC1, 0xFD, 0x25, 0x3A, 0x09, + 0x46, 0x08, 0x36, 0x97, 0xA3, 0x60, 0xD8, 0xFA, 0xD5, 0x59, 0x70, 0xE9, 0x84, 0x54, 0xBF, 0xB3, + 0xE0, 0x95, 0xEA, 0xEF, 0xA0, 0x55, 0x19, 0x8D, 0x67, 0xAF, 0x3E, 0x83, 0xCF, 0xE4, 0x33, 0x18, + 0x52, 0x50, 0x49, 0xBF, 0xC1, 0xD2, 0xDB, 0x13, 0xDF, 0xDC, 0xDA, 0xDD, 0x0C, 0x27, 0xA7, 0x52, + 0xBA, 0xAB, 0xE1, 0xE4, 0xFB, 0x35, 0x73, 0x37, 0xDC, 0x55, 0x5A, 0xCA, 0x7B, 0xA5, 0xF3, 0x7E, + 0xF7, 0xDD, 0x7B, 0xD6, 0x6C, 0x2B, 0x7B, 0xAD, 0xF6, 0xC1, 0x9E, 0xB2, 0xF7, 0x6E, 0xBF, 0xCD, + 0xDA, 0x7B, 0xFB, 0xF0, 0xDD, 0xC1, 0xFB, 0x7A, 0x7D, 0x0D, 0x57, 0x30, 0x5E, 0xE8, 0x68, 0xB8, + 0x82, 0xA1, 0x4A, 0x78, 0x19, 0x66, 0xCE, 0xA2, 0x92, 0x8D, 0x7A, 0xBE, 0x19, 0xBF, 0xBA, 0x18, + 0x66, 0xB8, 0x18, 0xCE, 0x0A, 0x91, 0xB5, 0xBB, 0x19, 0x86, 0x43, 0x2C, 0xC1, 0xD5, 0x70, 0xD9, + 0xB3, 0x8F, 0x8F, 0xB1, 0x1C, 0x97, 0xC3, 0x65, 0xAF, 0x20, 0x39, 0xCA, 0xC6, 0xBB, 0x1E, 0xC6, + 0x8F, 0xAE, 0x1F, 0xC6, 0x03, 0x71, 0x33, 0xEB, 0xB4, 0xF7, 0xB9, 0xFB, 0x80, 0x75, 0x5A, 0x13, + 0x75, 0xDA, 0xC5, 0xC3, 0x95, 0xD5, 0x69, 0x0F, 0xE7, 0xF0, 0x3C, 0x75, 0xDA, 0x3D, 0x1A, 0x1D, + 0x05, 0xEF, 0xC0, 0x21, 0xE4, 0x0E, 0xF3, 0x94, 0xBC, 0xF1, 0x66, 0x0A, 0xB6, 0x53, 0xD6, 0xB4, + 0x99, 0x72, 0xED, 0x5F, 0x28, 0x61, 0x89, 0xA8, 0xD7, 0x2E, 0xA4, 0x79, 0x1F, 0x2F, 0x4F, 0x54, + 0x0D, 0xD3, 0x7F, 0x48, 0x85, 0x38, 0xD9, 0x4F, 0x20, 0xB2, 0x82, 0x58, 0x89, 0x52, 0xF1, 0x2F, + 0xA7, 0x86, 0xBB, 0xD8, 0xD0, 0x79, 0xD3, 0x49, 0x7C, 0x28, 0x51, 0x5B, 0x26, 0x35, 0x43, 0x67, + 0xC9, 0x32, 0x6B, 0x49, 0x2E, 0x5F, 0xC8, 0x01, 0x4A, 0x95, 0x5A, 0x9B, 0x59, 0xB6, 0x92, 0x91, + 0xAC, 0xB3, 0x70, 0xB8, 0xA9, 0x64, 0x9E, 0x29, 0x1D, 0x17, 0x33, 0xAD, 0x38, 0x15, 0xB3, 0x36, + 0x66, 0xE8, 0xAC, 0xA1, 0xB6, 0x9A, 0x40, 0x81, 0xC2, 0xFA, 0x6A, 0x15, 0xFC, 0xAD, 0x93, 0x79, + 0x3B, 0x2B, 0xAF, 0xB4, 0xFE, 0x72, 0x58, 0xB5, 0xFB, 0xD1, 0xC4, 0xD7, 0x44, 0x15, 0x19, 0x3F, + 0xDB, 0x9E, 0x3F, 0x47, 0x29, 0x9F, 0x25, 0x94, 0x10, 0x0F, 0xFF, 0xE1, 0x8C, 0x0E, 0x17, 0xF5, + 0xE2, 0x38, 0xA8, 0x64, 0xE5, 0xAA, 0x6C, 0xE9, 0xCA, 0x80, 0x62, 0x85, 0xD1, 0x9C, 0xBC, 0x8E, + 0x16, 0xB5, 0x7D, 0x55, 0xF7, 0x19, 0x79, 0x66, 0x2C, 0xEC, 0xD9, 0xEE, 0x9A, 0x61, 0x21, 0xCE, + 0xE8, 0xB9, 0xB1, 0xB0, 0xF0, 0xCE, 0x23, 0x0B, 0x8E, 0x8B, 0xE3, 0xA1, 0xE8, 0xE8, 0x87, 0xC3, + 0x43, 0x2C, 0xD2, 0xBE, 0x5E, 0x78, 0x88, 0x33, 0xDA, 0x34, 0x6E, 0x28, 0xA1, 0xB8, 0x38, 0x16, + 0x8A, 0x8E, 0x7E, 0x48, 0x2C, 0xEC, 0xFD, 0x5C, 0x13, 0x1E, 0x66, 0x55, 0x51, 0x9F, 0x03, 0x0F, + 0x4B, 0x57, 0x4B, 0xCF, 0x41, 0xC5, 0x77, 0x95, 0xAE, 0x80, 0x6B, 0x41, 0x45, 0x02, 0x65, 0x3D, + 0xC8, 0x88, 0x5D, 0xFD, 0x40, 0xE8, 0x38, 0x29, 0x20, 0xBF, 0x64, 0xA6, 0x48, 0x03, 0x55, 0x38, + 0x9B, 0x61, 0x46, 0x9B, 0xC3, 0x14, 0x13, 0x50, 0x5C, 0x04, 0x0F, 0xE3, 0x1D, 0xFD, 0x90, 0x58, + 0xB8, 0x74, 0xA6, 0x38, 0x07, 0x1E, 0x6E, 0x14, 0x53, 0x9C, 0x02, 0x65, 0x3D, 0xC8, 0xF8, 0x3C, + 0x4C, 0x71, 0x81, 0x57, 0xCA, 0x98, 0x04, 0x36, 0xDE, 0xDE, 0xD3, 0x5E, 0x96, 0xBD, 0xA7, 0x5D, + 0xD5, 0xDE, 0xD3, 0xD9, 0x54, 0x7B, 0x4F, 0xFB, 0xA5, 0xDA, 0x7B, 0xDA, 0xAF, 0xF6, 0x9E, 0x1A, + 0xEC, 0x3D, 0xED, 0xBA, 0xEC, 0x3D, 0xED, 0x1F, 0xD3, 0xDE, 0xD3, 0x7E, 0xB5, 0xF7, 0xD4, 0x62, + 0xEF, 0x69, 0xD7, 0x65, 0xEF, 0x69, 0xFF, 0x98, 0xF6, 0x9E, 0xF6, 0xAB, 0xBD, 0xA7, 0x06, 0x7B, + 0x4F, 0xBB, 0x2E, 0x7B, 0x4F, 0xFB, 0x47, 0xB5, 0xF7, 0xB4, 0x5F, 0xED, 0x3D, 0x75, 0xD9, 0x7B, + 0xDA, 0xF5, 0xD9, 0x7B, 0xDA, 0x3F, 0xA6, 0xBD, 0xA7, 0xFD, 0x6A, 0xEF, 0xA9, 0xC1, 0xDE, 0xD3, + 0xAE, 0xCB, 0xDE, 0xD3, 0xFE, 0x51, 0xED, 0x3D, 0xED, 0x57, 0x7B, 0x4F, 0x5D, 0xF6, 0x9E, 0x76, + 0x7D, 0xF6, 0x9E, 0xF6, 0xAB, 0xBD, 0x67, 0xDD, 0xEC, 0x3D, 0x9D, 0x65, 0xD9, 0x7B, 0x3A, 0x55, + 0xED, 0x3D, 0xDD, 0x4D, 0xB5, 0xF7, 0x74, 0x5E, 0xAA, 0xBD, 0xA7, 0xF3, 0x6A, 0xEF, 0xA9, 0xC1, + 0xDE, 0xD3, 0xA9, 0xCB, 0xDE, 0xD3, 0xF9, 0x31, 0xED, 0x3D, 0x9D, 0x57, 0x7B, 0x4F, 0x2D, 0xF6, + 0x9E, 0x4E, 0x5D, 0xF6, 0x9E, 0xCE, 0x8F, 0x69, 0xEF, 0xE9, 0xBC, 0xDA, 0x7B, 0x6A, 0xB0, 0xF7, + 0x74, 0xEA, 0xB2, 0xF7, 0x74, 0x7E, 0x54, 0x7B, 0x4F, 0xE7, 0xD5, 0xDE, 0x53, 0x97, 0xBD, 0xA7, + 0x53, 0x9F, 0xBD, 0xA7, 0xF3, 0x63, 0xDA, 0x7B, 0x3A, 0xAF, 0xF6, 0x9E, 0x1A, 0xEC, 0x3D, 0x9D, + 0xBA, 0xEC, 0x3D, 0x9D, 0x1F, 0xD5, 0xDE, 0xD3, 0x79, 0xB5, 0xF7, 0xD4, 0x65, 0xEF, 0xE9, 0xD4, + 0x67, 0xEF, 0xE9, 0xBC, 0xDA, 0x7B, 0xD6, 0xCD, 0xDE, 0xD3, 0x5D, 0x96, 0xBD, 0xA7, 0x5B, 0xD5, + 0xDE, 0xB3, 0xBB, 0xA9, 0xF6, 0x9E, 0xEE, 0x4B, 0xB5, 0xF7, 0x74, 0x5F, 0xED, 0x3D, 0x35, 0xD8, + 0x7B, 0xBA, 0x75, 0xD9, 0x7B, 0xBA, 0x3F, 0xA6, 0xBD, 0xA7, 0xFB, 0x6A, 0xEF, 0xA9, 0xC5, 0xDE, + 0xD3, 0xAD, 0xCB, 0xDE, 0xD3, 0xFD, 0x31, 0xED, 0x3D, 0xDD, 0x57, 0x7B, 0x4F, 0x0D, 0xF6, 0x9E, + 0x6E, 0x5D, 0xF6, 0x9E, 0xEE, 0x8F, 0x6A, 0xEF, 0xE9, 0xBE, 0xDA, 0x7B, 0xEA, 0xB2, 0xF7, 0x74, + 0xEB, 0xB3, 0xF7, 0x74, 0x7F, 0x4C, 0x7B, 0x4F, 0xF7, 0xD5, 0xDE, 0x53, 0x83, 0xBD, 0xA7, 0x5B, + 0x97, 0xBD, 0xA7, 0xFB, 0xA3, 0xDA, 0x7B, 0xBA, 0xAF, 0xF6, 0x9E, 0xBA, 0xEC, 0x3D, 0xDD, 0xFA, + 0xEC, 0x3D, 0xDD, 0x17, 0x63, 0xEF, 0x29, 0xCA, 0x11, 0x94, 0xD3, 0xAB, 0xB4, 0x01, 0x45, 0x39, + 0xD1, 0x86, 0xDC, 0xBF, 0xE6, 0x9E, 0xA7, 0x0E, 0xF9, 0x95, 0xE1, 0xF9, 0x58, 0xB8, 0x92, 0x2A, + 0x04, 0xCC, 0x65, 0x22, 0xCA, 0x4F, 0x0B, 0x95, 0x48, 0xFA, 0x56, 0xCA, 0x3C, 0x14, 0x3E, 0xC7, + 0x14, 0xFB, 0xC2, 0xB8, 0x71, 0xED, 0x0D, 0x71, 0x8A, 0xF9, 0x23, 0xA5, 0x5A, 0x90, 0xA6, 0x2D, + 0x44, 0xD9, 0x7D, 0xE7, 0xEF, 0xDA, 0xED, 0xDD, 0xE9, 0x35, 0xBB, 0xA5, 0xE4, 0xB0, 0x1F, 0x0C, + 0x42, 0x37, 0x4C, 0x62, 0x05, 0x6D, 0x4F, 0xD1, 0x22, 0xD4, 0xA8, 0xC3, 0x64, 0x54, 0xC2, 0x5C, + 0x54, 0x3A, 0x53, 0x17, 0xCD, 0xD0, 0xF5, 0xB5, 0x31, 0x4E, 0xF9, 0xD2, 0x1A, 0xD8, 0xC5, 0xB5, + 0xE5, 0x64, 0xFE, 0x2E, 0x5A, 0x28, 0xA6, 0x8D, 0x77, 0x55, 0xCB, 0x1B, 0x1B, 0x7E, 0xAC, 0xE4, + 0x01, 0xAE, 0x98, 0xA9, 0x98, 0xA3, 0x5A, 0x17, 0xA9, 0xB4, 0x30, 0xD5, 0x6E, 0xFB, 0xF3, 0x9F, + 0x94, 0x0B, 0x7E, 0x2C, 0xB0, 0xC9, 0x63, 0x6D, 0x45, 0xD9, 0xDB, 0x86, 0x9F, 0xEF, 0x76, 0xF1, + 0xE7, 0x01, 0xFD, 0x7C, 0x8F, 0x3F, 0xDB, 0x9D, 0x5D, 0x91, 0x60, 0x5E, 0x69, 0x85, 0x8D, 0xDA, + 0x9D, 0xAE, 0x22, 0x33, 0xD5, 0xCB, 0x34, 0xBD, 0xA6, 0xFD, 0x48, 0x55, 0x19, 0xF0, 0x5B, 0xAA, + 0x73, 0xE1, 0x89, 0xAA, 0x28, 0xF0, 0x1C, 0xC6, 0xB7, 0x74, 0x4A, 0x7A, 0x8A, 0x49, 0xEF, 0x7C, + 0xD5, 0x30, 0x6D, 0x57, 0x56, 0x53, 0x90, 0x73, 0x85, 0xDE, 0x9F, 0x76, 0x54, 0xD3, 0x14, 0xBB, + 0x15, 0xCE, 0xA8, 0xC5, 0xAE, 0x0C, 0xF8, 0xD2, 0x3B, 0x64, 0x0A, 0xBE, 0xDC, 0x51, 0xE2, 0xD5, + 0x1A, 0x44, 0x0D, 0x16, 0x82, 0x1D, 0x8C, 0xE7, 0x53, 0xBD, 0x06, 0x1B, 0x38, 0x88, 0x6B, 0xE8, + 0x3A, 0xB7, 0xF0, 0x7D, 0x9C, 0x2B, 0x95, 0x6B, 0x31, 0x2C, 0x86, 0x88, 0xC2, 0xC6, 0xB6, 0xCE, + 0x37, 0x2B, 0x75, 0x58, 0xAA, 0x05, 0x71, 0x1E, 0x4A, 0xA8, 0xDF, 0x88, 0x48, 0x3D, 0x26, 0x9B, + 0xDE, 0xDB, 0x40, 0x05, 0xE3, 0xB0, 0x75, 0x21, 0xC7, 0x4D, 0x4D, 0x2E, 0x99, 0x9E, 0x48, 0x32, + 0x56, 0x18, 0xC5, 0xE5, 0x1E, 0xF7, 0xEF, 0x6C, 0x44, 0x14, 0x99, 0x17, 0x8E, 0xAA, 0xBA, 0xDE, + 0xE2, 0x63, 0xD8, 0xF5, 0x52, 0x67, 0x42, 0xD8, 0xB0, 0x5C, 0x1E, 0xC9, 0xB2, 0x75, 0x67, 0x9E, + 0x17, 0x18, 0x57, 0xF6, 0xE3, 0x49, 0x48, 0x66, 0x11, 0x40, 0x4A, 0x41, 0x03, 0x33, 0x53, 0x02, + 0x91, 0x46, 0xAD, 0x81, 0xEA, 0xAC, 0x6F, 0x95, 0x40, 0x53, 0x0C, 0x1B, 0xC4, 0xDB, 0x71, 0xF2, + 0xD8, 0x6A, 0x2C, 0x3B, 0x7D, 0x64, 0x89, 0xC7, 0x69, 0x83, 0x7C, 0xF8, 0x87, 0x66, 0x93, 0x35, + 0xC3, 0x7F, 0xA1, 0x38, 0xC6, 0xDD, 0x01, 0x26, 0x8E, 0x16, 0xF4, 0x16, 0xFB, 0xBA, 0xD9, 0x9C, + 0xEA, 0x31, 0x46, 0x68, 0x3A, 0xC8, 0xAA, 0x86, 0xCE, 0x86, 0xAA, 0x93, 0x5A, 0xD0, 0x39, 0xE3, + 0x7A, 0x27, 0xF3, 0x3E, 0x07, 0x61, 0xE8, 0x38, 0x61, 0xFA, 0xC0, 0x32, 0xC7, 0xF3, 0x4F, 0xD9, + 0x17, 0x3D, 0xD3, 0x47, 0x76, 0xAF, 0x17, 0x76, 0x5C, 0xE9, 0x34, 0x8E, 0x9A, 0xA5, 0x0E, 0x95, + 0xCC, 0xFA, 0x9D, 0xB1, 0xE1, 0x42, 0xDA, 0x4B, 0xC0, 0x37, 0x70, 0x45, 0x69, 0x12, 0x79, 0x60, + 0x3B, 0xB1, 0x37, 0x16, 0x3F, 0xB5, 0xD3, 0x31, 0x3B, 0x0D, 0x39, 0x0A, 0x39, 0x70, 0xB4, 0xFA, + 0x94, 0x51, 0xB2, 0x19, 0xEE, 0x5C, 0x72, 0xE0, 0x19, 0xA5, 0xD1, 0x64, 0x97, 0x67, 0x87, 0xA1, + 0xD4, 0x3C, 0x82, 0x0E, 0x1F, 0x61, 0xB1, 0x97, 0x67, 0x39, 0xE9, 0xD5, 0x77, 0x8E, 0x51, 0x50, + 0xCE, 0xCD, 0x8F, 0x7E, 0x86, 0x25, 0x8E, 0x40, 0xEA, 0x36, 0x4C, 0xF6, 0x8D, 0xC3, 0x9F, 0xB0, + 0xF5, 0x86, 0xCB, 0xA3, 0x61, 0x74, 0xF8, 0xFA, 0x96, 0x8F, 0x55, 0xC3, 0x32, 0xAC, 0x61, 0xCE, + 0x48, 0x36, 0xFB, 0x2B, 0xB4, 0xCE, 0x14, 0xCB, 0x9F, 0x35, 0xA5, 0x6B, 0x1C, 0xC5, 0x4E, 0xA3, + 0x9C, 0xA3, 0x5E, 0x94, 0xDE, 0x35, 0xFE, 0x7D, 0x2E, 0x53, 0x8A, 0x35, 0x66, 0xAB, 0xC8, 0x03, + 0x9B, 0x39, 0xF1, 0x15, 0xE6, 0x84, 0xBD, 0x6A, 0xE2, 0xC9, 0xC0, 0x76, 0xD8, 0x65, 0x2F, 0x49, + 0xAB, 0x5A, 0x0C, 0x18, 0x24, 0x67, 0xA9, 0x0F, 0x20, 0xDA, 0x51, 0x05, 0xAC, 0xD9, 0x94, 0xAD, + 0x1B, 0x97, 0xB1, 0x75, 0xC8, 0xED, 0xA1, 0xAB, 0x3A, 0x23, 0x43, 0xBB, 0xE5, 0x43, 0x58, 0xE3, + 0x99, 0x6B, 0x3B, 0x82, 0xAB, 0x14, 0x61, 0x24, 0x21, 0xDE, 0x74, 0xF3, 0xC6, 0xD1, 0xA7, 0xE8, + 0x09, 0x13, 0x8F, 0x8A, 0x15, 0xFC, 0x64, 0xB9, 0x82, 0x99, 0x2E, 0x53, 0xE7, 0x99, 0x54, 0xD9, + 0x75, 0x39, 0xEB, 0x30, 0xBB, 0x6C, 0xEE, 0x1E, 0x24, 0x93, 0xE2, 0x2B, 0x8D, 0xA3, 0xAF, 0xFD, + 0x72, 0x89, 0xEA, 0x67, 0x2B, 0x13, 0x9C, 0x7F, 0x9D, 0xAB, 0x25, 0x4C, 0xF1, 0x78, 0xBE, 0x96, + 0xC0, 0x29, 0xFE, 0x7A, 0x3B, 0x57, 0xCB, 0xDD, 0xC6, 0xD1, 0x7F, 0x57, 0x01, 0x57, 0x8A, 0x1B, + 0x17, 0xE7, 0xE1, 0xAF, 0x97, 0xFA, 0xFA, 0x62, 0xF7, 0x9F, 0xEC, 0xC0, 0x65, 0x93, 0x6D, 0x66, + 0x2E, 0xED, 0xB3, 0xD4, 0xBE, 0x40, 0xA1, 0x03, 0x06, 0x2C, 0xD2, 0x29, 0x4B, 0x4A, 0x1D, 0xB8, + 0xFC, 0x8F, 0x80, 0x5B, 0xDA, 0x13, 0xE9, 0x6C, 0x02, 0x2B, 0x54, 0x33, 0xC6, 0xBB, 0x40, 0xAA, + 0x70, 0x0C, 0x2D, 0x46, 0xA4, 0x5F, 0xFB, 0xEB, 0x44, 0x9E, 0x42, 0x36, 0x72, 0x17, 0xA0, 0x5D, + 0xC7, 0xE9, 0x73, 0x1F, 0x93, 0x51, 0x7B, 0x99, 0xE7, 0xF3, 0x62, 0x67, 0x4F, 0xE9, 0xF3, 0x47, + 0x0D, 0x7C, 0x1B, 0xCE, 0xC5, 0x5B, 0x6E, 0xF1, 0x47, 0xD5, 0x04, 0xEC, 0x86, 0xCF, 0x78, 0x50, + 0x32, 0xF9, 0xA4, 0x64, 0xA1, 0x97, 0x45, 0x4E, 0x92, 0xE9, 0x29, 0x94, 0xDF, 0xE8, 0xC5, 0x72, + 0xE5, 0x4B, 0x24, 0xFE, 0x19, 0x4F, 0x06, 0x12, 0x2C, 0x90, 0x17, 0x31, 0x58, 0x2B, 0xE8, 0x1A, + 0x9D, 0x03, 0x86, 0x72, 0x05, 0xAA, 0x1E, 0x24, 0x6E, 0x3C, 0x6D, 0x33, 0x9C, 0x26, 0x56, 0xA4, + 0xC3, 0x9A, 0x95, 0x80, 0xB9, 0xBE, 0xCF, 0xC7, 0x0E, 0x55, 0xCE, 0x02, 0x6C, 0xB3, 0x90, 0x0C, + 0xE0, 0xCF, 0x37, 0x9F, 0xED, 0x31, 0x7F, 0xC3, 0x2C, 0xEE, 0x3F, 0xDA, 0xEE, 0xB7, 0x6D, 0xD0, + 0x83, 0x40, 0x24, 0x52, 0xB1, 0xAB, 0x6D, 0xFC, 0xDA, 0xC5, 0x25, 0xD2, 0x58, 0x31, 0xDC, 0x16, + 0x47, 0xBC, 0x5E, 0xBA, 0xFE, 0x67, 0xCD, 0x89, 0xF5, 0x8B, 0x09, 0xA1, 0xBC, 0x23, 0x51, 0x25, + 0xF3, 0x77, 0xFC, 0x30, 0x8A, 0x0B, 0xD0, 0x42, 0x90, 0xEC, 0xB9, 0xF6, 0xC0, 0x30, 0xF9, 0x9D, + 0xFD, 0x8D, 0x97, 0xF0, 0x0D, 0x2B, 0xBC, 0x7B, 0x99, 0x2D, 0xD1, 0x26, 0xE5, 0x55, 0x39, 0x0E, + 0xA3, 0x81, 0x0E, 0xAB, 0x6D, 0xC1, 0xE2, 0xA5, 0x1A, 0x26, 0x25, 0x46, 0x89, 0x87, 0xDA, 0xEE, + 0x50, 0xB5, 0x8C, 0x3F, 0x85, 0x6E, 0x31, 0x52, 0x3D, 0x4C, 0x5A, 0x6F, 0xBB, 0x8E, 0x4D, 0xE6, + 0x23, 0xE0, 0xA6, 0xD6, 0xB0, 0xEF, 0xBB, 0x5C, 0x1D, 0x13, 0xDF, 0x4C, 0x88, 0x3A, 0xAA, 0xA6, + 0xA1, 0x61, 0x7A, 0x5B, 0x24, 0xC8, 0x37, 0x48, 0xDE, 0x71, 0x39, 0xF6, 0xCA, 0x90, 0x30, 0xA9, + 0x77, 0x2D, 0xF0, 0x00, 0x83, 0x65, 0xBE, 0x7B, 0xE6, 0xC8, 0x85, 0xFB, 0xB8, 0xF0, 0x38, 0x3A, + 0x02, 0x56, 0x3F, 0xB1, 0x26, 0x13, 0xD9, 0xF6, 0x39, 0xEB, 0x3B, 0xAA, 0xFB, 0xED, 0x22, 0xB0, + 0x04, 0x84, 0x1A, 0x73, 0x54, 0xB4, 0xA8, 0xB3, 0x04, 0x44, 0x39, 0x8C, 0x2E, 0xC3, 0xB1, 0xB2, + 0x6E, 0xC6, 0xDE, 0x95, 0x21, 0xC4, 0x0A, 0xD7, 0x10, 0x05, 0xA8, 0x5D, 0xBA, 0x0E, 0x79, 0x7E, + 0x3F, 0x35, 0x14, 0x26, 0xAF, 0x8D, 0x01, 0xD4, 0x7B, 0x40, 0x91, 0xAC, 0xE0, 0xF9, 0x70, 0x40, + 0x7C, 0x75, 0x80, 0xD8, 0x38, 0x1A, 0x8E, 0xE8, 0x09, 0x9D, 0x52, 0xE2, 0xD9, 0x0A, 0x0E, 0xA9, + 0xD9, 0x69, 0x3C, 0xC7, 0x31, 0x25, 0xEB, 0x2C, 0x8B, 0x12, 0x2E, 0x09, 0x06, 0x40, 0x07, 0xD8, + 0xA3, 0x61, 0x9A, 0x68, 0xEA, 0x96, 0x93, 0xE5, 0x3A, 0x31, 0x8A, 0x80, 0x26, 0xAC, 0xA3, 0x7D, + 0x1D, 0x9B, 0x59, 0x80, 0xAF, 0xCC, 0x76, 0xB0, 0xE6, 0x70, 0x60, 0x19, 0xFE, 0x53, 0x75, 0x5D, + 0x68, 0xBD, 0xCF, 0x22, 0xDC, 0xAD, 0xC0, 0xE3, 0xC7, 0x9E, 0x67, 0x78, 0xFE, 0x8D, 0xFD, 0x78, + 0x1A, 0x6E, 0x68, 0xC5, 0xCA, 0xDC, 0xA5, 0xD1, 0xB8, 0x34, 0x2A, 0xC7, 0x67, 0x05, 0x92, 0x56, + 0xF8, 0x67, 0x85, 0x3A, 0x95, 0x8B, 0x60, 0x70, 0x72, 0xF4, 0x67, 0x3A, 0xE9, 0x08, 0x8B, 0xA3, + 0x72, 0xDA, 0x13, 0x10, 0x5C, 0x7F, 0x3A, 0x16, 0x15, 0x56, 0x66, 0x51, 0x18, 0xE4, 0x26, 0x63, + 0x0C, 0xA7, 0xD5, 0x83, 0xAC, 0x8C, 0xAD, 0x6A, 0x7F, 0x04, 0x86, 0x67, 0xD0, 0x19, 0xE9, 0x00, + 0xF6, 0x63, 0x9D, 0x60, 0x4B, 0xE3, 0xF3, 0xA1, 0xF1, 0x1A, 0x1C, 0x56, 0x8B, 0x33, 0x5F, 0xB9, + 0xBB, 0x58, 0xFC, 0xCE, 0x34, 0xFE, 0xE4, 0x3A, 0xAC, 0xDF, 0x77, 0x8D, 0xFB, 0x80, 0x6A, 0x03, + 0xAE, 0x13, 0xF6, 0xA7, 0xCE, 0x50, 0x54, 0xED, 0xA3, 0xC7, 0x2C, 0xFE, 0xBC, 0xC6, 0xF2, 0xAE, + 0x0B, 0x92, 0x4D, 0xC6, 0xB4, 0xD7, 0x82, 0x84, 0xCC, 0x08, 0x76, 0x71, 0x6B, 0x17, 0xDE, 0x71, + 0x92, 0xCC, 0x67, 0xCA, 0x82, 0x88, 0x11, 0x5D, 0x51, 0xB1, 0x52, 0xC3, 0x02, 0xCA, 0x52, 0xA3, + 0x2A, 0xDF, 0x28, 0xAF, 0x80, 0xBC, 0x60, 0xF9, 0xA0, 0xEE, 0xC5, 0x7A, 0xD9, 0x6C, 0x8A, 0x2A, + 0x77, 0xE5, 0x63, 0xA6, 0x6D, 0xED, 0x1D, 0x48, 0x55, 0x57, 0xFC, 0x81, 0x9B, 0x25, 0x4C, 0x6A, + 0x69, 0xDA, 0x4C, 0x7E, 0xA7, 0x59, 0xF8, 0xCE, 0xF0, 0x0D, 0x46, 0xAF, 0x94, 0xF7, 0xAC, 0x49, + 0x1A, 0xE0, 0x0A, 0x06, 0x2E, 0xB1, 0xE2, 0x72, 0x98, 0x38, 0xAF, 0x01, 0x2F, 0xDB, 0x90, 0xD7, + 0x56, 0x14, 0x85, 0x7D, 0xC7, 0x1B, 0x7E, 0xE5, 0xDB, 0x98, 0x74, 0x0E, 0x8F, 0x57, 0xAC, 0x41, + 0x39, 0x63, 0xE2, 0xDB, 0xA3, 0x2E, 0xF7, 0xEA, 0xEA, 0x11, 0x16, 0xD7, 0xD9, 0xC3, 0x1E, 0xE1, + 0x67, 0x3D, 0x3D, 0x76, 0xA7, 0xD7, 0xFD, 0x19, 0x98, 0x01, 0x10, 0x9D, 0x05, 0xA7, 0xDB, 0xD3, + 0x42, 0x3D, 0xEF, 0x26, 0x57, 0x5F, 0x5B, 0xBF, 0x7B, 0x49, 0x18, 0xCC, 0xD7, 0x6F, 0x85, 0x6A, + 0xA1, 0x4B, 0x61, 0xA0, 0xD2, 0x6A, 0x89, 0x9C, 0x6F, 0xC2, 0x3D, 0xF5, 0x38, 0x25, 0xFA, 0x48, + 0x89, 0x26, 0xD2, 0x43, 0x2B, 0xB1, 0x46, 0x54, 0xAC, 0xB1, 0x5E, 0xE2, 0x18, 0x86, 0xD6, 0xB9, + 0xDE, 0x92, 0x48, 0xC0, 0xC6, 0xEA, 0x13, 0xF2, 0x56, 0x9D, 0x3B, 0xF0, 0x2D, 0xCA, 0xDA, 0x31, + 0xD6, 0x99, 0x05, 0xAF, 0x4D, 0x60, 0xA5, 0xB9, 0x86, 0xCE, 0x8A, 0x12, 0xCC, 0x2A, 0x2E, 0xC2, + 0x5D, 0xDF, 0x7B, 0xFE, 0x0B, 0x70, 0x9C, 0xC5, 0xB2, 0xEE, 0xC0, 0xE3, 0x7D, 0x57, 0xBB, 0x06, + 0x8F, 0xB5, 0xCC, 0xBA, 0xE5, 0x9E, 0x40, 0x6F, 0xE6, 0x7A, 0x1B, 0x1B, 0xAF, 0xD5, 0xBD, 0x76, + 0xEE, 0x72, 0xE6, 0xBB, 0xDA, 0x4E, 0x31, 0x62, 0xC2, 0x06, 0xE7, 0x57, 0x3C, 0x44, 0x04, 0xBC, + 0xB0, 0xC3, 0x1A, 0x92, 0xC2, 0x9F, 0x0C, 0xC8, 0x15, 0xA8, 0x92, 0xBB, 0x78, 0xA5, 0x71, 0x76, + 0x7C, 0x77, 0xCC, 0x10, 0x7C, 0xDB, 0xCC, 0x1B, 0x81, 0x9A, 0x33, 0x08, 0x40, 0x0A, 0x93, 0x2C, + 0x15, 0xDD, 0x81, 0xA6, 0x71, 0x33, 0x55, 0x46, 0x09, 0x7B, 0xC3, 0x45, 0x7F, 0x71, 0x8A, 0xEE, + 0x57, 0x13, 0x4D, 0xC9, 0xCF, 0x0D, 0xDB, 0x9D, 0x8E, 0x54, 0xCB, 0x8A, 0x09, 0x34, 0xE1, 0x62, + 0x4B, 0x1E, 0xDA, 0x71, 0xE9, 0x26, 0xEA, 0xF3, 0x44, 0x0D, 0x40, 0x20, 0xBC, 0xC5, 0x8F, 0x84, + 0x3D, 0x0C, 0x1F, 0x90, 0x27, 0xE3, 0x21, 0x9B, 0x53, 0x80, 0x49, 0xF6, 0x9D, 0x5C, 0x82, 0x78, + 0x94, 0x26, 0x7A, 0xCC, 0x27, 0x75, 0xEC, 0x1E, 0x28, 0x20, 0x78, 0xE0, 0xCF, 0x85, 0x8E, 0xC4, + 0xF7, 0xFB, 0xD8, 0x0D, 0xFE, 0x5C, 0x4C, 0x5E, 0x79, 0xDF, 0xC1, 0x7E, 0xE8, 0xD7, 0x62, 0x42, + 0xC5, 0xC1, 0x2E, 0x76, 0x44, 0xBF, 0x16, 0x3B, 0xEB, 0xDF, 0xD1, 0xCA, 0xE8, 0xD7, 0x62, 0x4B, + 0x6B, 0xEF, 0x89, 0xB5, 0xD1, 0xEF, 0xC5, 0x64, 0xB0, 0xAE, 0x42, 0xAB, 0x13, 0xBF, 0x17, 0x13, + 0x91, 0xF6, 0x15, 0x81, 0x00, 0xF4, 0x7B, 0x31, 0x14, 0xE8, 0xB4, 0x05, 0x12, 0xD0, 0xEF, 0xCD, + 0x11, 0x84, 0xEE, 0xC8, 0xF3, 0x16, 0x68, 0x96, 0xEE, 0x17, 0x50, 0x67, 0x44, 0x99, 0xE8, 0xF6, + 0xF8, 0xEC, 0xF2, 0x0B, 0x31, 0xAD, 0xD0, 0x73, 0x56, 0x03, 0xE6, 0x31, 0x14, 0x96, 0x98, 0xB1, + 0xEA, 0x6B, 0xC0, 0xE2, 0x50, 0x8D, 0x74, 0xB1, 0xCE, 0xEE, 0xC8, 0x46, 0xBE, 0x07, 0x7A, 0x24, + 0x91, 0x69, 0x4C, 0xEC, 0x21, 0xB4, 0xB9, 0x77, 0xBC, 0x97, 0x26, 0xE6, 0x94, 0xE3, 0xBB, 0xB8, + 0x3D, 0x35, 0xB3, 0xDD, 0xA9, 0x2E, 0x1B, 0x47, 0xD7, 0xC1, 0x77, 0x26, 0x3F, 0xCC, 0xCD, 0x6F, + 0xA7, 0x3B, 0x4D, 0x9B, 0x7C, 0x9D, 0x3C, 0x17, 0x88, 0xE4, 0xE6, 0xFA, 0xFC, 0x78, 0x51, 0xCD, + 0xAE, 0xD7, 0xEB, 0xEF, 0x9C, 0x3F, 0xA0, 0x63, 0xF5, 0x1D, 0x60, 0xFD, 0x10, 0x4B, 0x5E, 0x2F, + 0xA4, 0xD8, 0x09, 0xEF, 0xCF, 0xE0, 0xBB, 0x5C, 0x31, 0x6C, 0xCD, 0x65, 0xE7, 0x74, 0x51, 0xD5, + 0xEE, 0xF8, 0xEC, 0x74, 0xE7, 0xEC, 0xF8, 0x74, 0xB3, 0xD8, 0x41, 0x24, 0xB0, 0x44, 0xB4, 0x2F, + 0x05, 0x42, 0xE9, 0x38, 0xAF, 0x1B, 0x83, 0x01, 0x77, 0x11, 0xF2, 0xAA, 0xE3, 0x98, 0x86, 0x30, + 0x2E, 0x79, 0x2D, 0x86, 0xBB, 0x8A, 0xDA, 0x11, 0x99, 0x97, 0xF0, 0x45, 0xE0, 0x0F, 0x06, 0x08, + 0x40, 0x77, 0xBF, 0xEC, 0xDC, 0xFE, 0x82, 0xFC, 0x21, 0x32, 0x44, 0x21, 0x5D, 0xBB, 0x63, 0xFA, + 0xBB, 0xC5, 0x70, 0x23, 0xE5, 0x16, 0x26, 0x5A, 0x3B, 0x01, 0x08, 0xB3, 0x68, 0xDD, 0x85, 0x7E, + 0x60, 0x06, 0x3A, 0xB3, 0x03, 0x1F, 0x4D, 0x78, 0x78, 0x9D, 0xB1, 0x67, 0x79, 0x78, 0xDD, 0x19, + 0x10, 0xD3, 0xF2, 0xC3, 0xC6, 0x68, 0xC9, 0x83, 0x79, 0xC0, 0xBE, 0x45, 0x1D, 0xE1, 0xFD, 0xA9, + 0xC5, 0x54, 0x5D, 0x27, 0x5B, 0x31, 0x4C, 0xA6, 0x7F, 0x76, 0xBC, 0xD3, 0x3F, 0xBD, 0x12, 0x86, + 0x66, 0x4D, 0xEC, 0x75, 0x8B, 0xFD, 0x3C, 0xE2, 0xDC, 0xDC, 0x39, 0x33, 0x5C, 0x76, 0x67, 0x68, + 0xDF, 0x12, 0xD3, 0xA0, 0x2B, 0x7F, 0xDB, 0x37, 0x1E, 0x38, 0x0C, 0x00, 0x42, 0xD8, 0x40, 0xD5, + 0x0C, 0x6B, 0xD8, 0x62, 0x72, 0x6B, 0x13, 0xEF, 0xCA, 0xDB, 0xD5, 0x81, 0xE1, 0x8E, 0xD1, 0xE9, + 0x71, 0x0A, 0x3E, 0x11, 0x5F, 0x44, 0x40, 0xBD, 0x38, 0x2B, 0x5A, 0xC8, 0x31, 0x50, 0x44, 0xAB, + 0x93, 0xD7, 0x09, 0x09, 0xF3, 0x0C, 0xB7, 0xAB, 0x26, 0x01, 0x33, 0xD1, 0xF3, 0xCC, 0xDC, 0x5F, + 0xC5, 0xCB, 0x57, 0xF1, 0xF2, 0x55, 0xBC, 0xAC, 0xCD, 0xAB, 0xC5, 0x12, 0xC7, 0x02, 0x46, 0x35, + 0x6D, 0xCB, 0x30, 0xAF, 0x69, 0x61, 0x73, 0xF6, 0xBC, 0xC9, 0x94, 0x35, 0x63, 0x0C, 0x35, 0xC6, + 0x4F, 0x05, 0x7C, 0x37, 0x44, 0xD0, 0xAC, 0xC4, 0x54, 0x23, 0xB5, 0x1F, 0xCF, 0xC2, 0xD0, 0xE8, + 0x33, 0x65, 0x15, 0x29, 0x6B, 0x74, 0x5F, 0xEC, 0x7A, 0xAF, 0xF4, 0x15, 0x9F, 0xB8, 0x95, 0x3A, + 0x8F, 0x4F, 0x3C, 0x72, 0x61, 0x0F, 0x9F, 0x96, 0x0F, 0x33, 0xC6, 0xE6, 0xD5, 0x42, 0x8C, 0x17, + 0xF7, 0x67, 0x9F, 0x9A, 0x7B, 0x75, 0x94, 0x5A, 0x9C, 0x7A, 0x62, 0x14, 0xF4, 0x89, 0x5B, 0x9C, + 0xC8, 0x45, 0x15, 0xD0, 0xC0, 0x28, 0x1F, 0xD6, 0x97, 0x12, 0x11, 0x88, 0x27, 0xF0, 0xDE, 0x58, + 0x06, 0x4F, 0x8E, 0x03, 0xA0, 0x95, 0x01, 0x65, 0x6C, 0xD8, 0x16, 0x76, 0x28, 0xC0, 0x1F, 0x8C, + 0x3E, 0x94, 0xB2, 0x92, 0xF6, 0xD4, 0x62, 0xE8, 0x38, 0x63, 0xB8, 0xDC, 0x13, 0xF7, 0xE8, 0x2E, + 0xD7, 0xB8, 0x33, 0x45, 0x4D, 0xD2, 0x17, 0x51, 0x74, 0xD0, 0x56, 0x94, 0x6D, 0x45, 0x51, 0x02, + 0x4F, 0x8A, 0x63, 0x26, 0xB7, 0x86, 0x64, 0xDF, 0xD2, 0xD9, 0xFB, 0xE8, 0x2B, 0x31, 0x03, 0xEE, + 0x3F, 0x72, 0x6E, 0x89, 0xF7, 0xBC, 0x56, 0x95, 0x00, 0xEB, 0x65, 0x91, 0x63, 0x25, 0xDD, 0xAF, + 0x4A, 0xA4, 0x7F, 0x06, 0x69, 0x9E, 0x71, 0x0C, 0x1B, 0xF5, 0xE6, 0xA6, 0xD0, 0x85, 0x5C, 0x29, + 0xB3, 0xE4, 0xA8, 0xC4, 0x3C, 0xEF, 0x60, 0xA7, 0x4E, 0xC4, 0x46, 0x09, 0xEC, 0x2E, 0xDD, 0xE5, + 0x9C, 0x1E, 0x96, 0x77, 0xB3, 0xA8, 0xC1, 0xB6, 0xFE, 0xDF, 0xFF, 0xF5, 0xDE, 0x1E, 0x96, 0x5F, + 0x4C, 0x15, 0xF2, 0x5F, 0xD8, 0xAD, 0x2F, 0xD3, 0xC5, 0x2F, 0x2F, 0x23, 0x55, 0xA5, 0x5E, 0xF1, + 0xDF, 0x0C, 0x02, 0xCD, 0x6C, 0x4C, 0xC5, 0x99, 0x3A, 0xC5, 0x5D, 0xD6, 0x91, 0xA7, 0xA0, 0xC4, + 0x5D, 0xCF, 0x3C, 0x44, 0xB5, 0x7C, 0xC4, 0xBF, 0x22, 0xBE, 0xB5, 0x74, 0x74, 0xEF, 0xC5, 0x98, + 0x64, 0xA5, 0xB1, 0x04, 0x4D, 0x6C, 0x00, 0xAA, 0xCF, 0xE2, 0x99, 0x04, 0xED, 0xE2, 0x08, 0x2B, + 0x3A, 0xDA, 0x04, 0x34, 0xA5, 0x0B, 0x31, 0xA2, 0x29, 0x7D, 0xC8, 0xE7, 0x55, 0x76, 0x0B, 0x71, + 0xB6, 0x67, 0x9B, 0xAA, 0x8B, 0xB7, 0xC4, 0xAB, 0xC1, 0xDA, 0x70, 0xB8, 0xC3, 0x39, 0x38, 0x6E, + 0x42, 0xBF, 0x4E, 0x5F, 0xC4, 0xEC, 0x7E, 0x2F, 0xB4, 0xBE, 0x79, 0xB4, 0xF3, 0x6C, 0x83, 0xE4, + 0xAD, 0xE1, 0x19, 0xD6, 0x90, 0xE1, 0x76, 0x56, 0x53, 0xD3, 0xB2, 0xED, 0x93, 0x17, 0xAA, 0x69, + 0xCE, 0xDF, 0x67, 0x79, 0xBD, 0x6D, 0x89, 0x12, 0xE8, 0x94, 0x5D, 0x50, 0x48, 0x80, 0x9E, 0x31, + 0x44, 0x3B, 0x5A, 0xE8, 0x4B, 0x36, 0x42, 0x77, 0x06, 0x11, 0x58, 0xA9, 0x91, 0x91, 0x6C, 0xCB, + 0x15, 0xB0, 0xE4, 0xB0, 0xEE, 0xB7, 0x28, 0xA1, 0x62, 0x28, 0x4D, 0xE2, 0x85, 0x81, 0x84, 0x0C, + 0xBD, 0x11, 0x13, 0x3C, 0x63, 0x9B, 0x50, 0x75, 0x43, 0x97, 0x29, 0x41, 0x56, 0x91, 0x22, 0x2B, + 0x30, 0x9E, 0xB9, 0x53, 0x4B, 0xAD, 0x42, 0x6D, 0xFB, 0x2C, 0xC3, 0xB6, 0xC9, 0xC0, 0x7E, 0x65, + 0x0F, 0x87, 0x18, 0x56, 0x2D, 0xB5, 0xB8, 0xD2, 0x40, 0xAB, 0xAC, 0xED, 0x09, 0x73, 0xBE, 0x1C, + 0x6E, 0x25, 0x5A, 0x5F, 0xE9, 0xA9, 0xCD, 0x6A, 0x87, 0xE9, 0x20, 0x7A, 0x66, 0x65, 0x11, 0xE6, + 0x31, 0x71, 0x3C, 0x08, 0xC5, 0x6D, 0x8E, 0x33, 0xF4, 0xDE, 0x0A, 0x85, 0xAE, 0xAB, 0x44, 0xA6, + 0x73, 0xED, 0x89, 0xDD, 0x5E, 0xF7, 0x5B, 0x18, 0xE5, 0x33, 0x08, 0x4C, 0x69, 0x8E, 0xC1, 0x78, + 0x44, 0x66, 0x1A, 0xDF, 0x40, 0x67, 0x0C, 0xF0, 0x52, 0xDF, 0x77, 0x0D, 0xD5, 0x1A, 0x06, 0xA6, + 0xB4, 0xB9, 0xF4, 0xB9, 0x88, 0x08, 0x42, 0xE1, 0x12, 0xE8, 0xE9, 0x1B, 0xDA, 0xE6, 0x89, 0xF1, + 0x49, 0x37, 0xD2, 0xDF, 0xCE, 0xCF, 0x9A, 0x17, 0xEF, 0x7B, 0xEC, 0xD2, 0xF2, 0xF9, 0x50, 0x7A, + 0x90, 0x5C, 0xAB, 0x56, 0xA0, 0x8A, 0xFE, 0xC7, 0xB6, 0xCB, 0x93, 0xD6, 0xFF, 0x62, 0x7D, 0xB4, + 0xA2, 0xDF, 0xE9, 0xE6, 0x6B, 0x94, 0x71, 0x8C, 0xAA, 0x4D, 0xA5, 0xA4, 0x01, 0xB0, 0xE3, 0x65, + 0x89, 0x2C, 0x34, 0xEB, 0x95, 0x89, 0x2C, 0x82, 0x55, 0xD4, 0x2E, 0xB2, 0x24, 0x17, 0x31, 0xBB, + 0x2B, 0x6B, 0x24, 0xB2, 0x60, 0x36, 0x1C, 0xDF, 0x26, 0x77, 0xC2, 0xBA, 0x44, 0x16, 0x72, 0x4D, + 0x14, 0x89, 0x76, 0x5E, 0x86, 0xC4, 0x12, 0xB1, 0x42, 0xC2, 0xFC, 0x48, 0x66, 0x31, 0x81, 0xB6, + 0xB8, 0xCE, 0xD0, 0xC9, 0x73, 0x14, 0xE6, 0x22, 0x33, 0xA6, 0xB8, 0x4E, 0x0C, 0xBC, 0xAF, 0xC2, + 0x48, 0x75, 0xC3, 0x75, 0x00, 0xBA, 0x51, 0x78, 0x5C, 0x62, 0x4C, 0x3F, 0x79, 0x88, 0xAD, 0xA1, + 0x91, 0x3A, 0x36, 0xB7, 0x29, 0x1B, 0x35, 0xA3, 0xC7, 0xA5, 0x37, 0x25, 0x96, 0xF8, 0xE4, 0x19, + 0xEC, 0xD5, 0x55, 0x40, 0x5C, 0x8D, 0xF4, 0x32, 0xC6, 0x29, 0x97, 0xF4, 0x2F, 0x85, 0x2C, 0x25, + 0x94, 0x23, 0xCA, 0x24, 0x9F, 0xA1, 0x78, 0xC4, 0xC9, 0x21, 0xA5, 0xD7, 0xDB, 0xF9, 0x7A, 0xF2, + 0xCB, 0x4E, 0xBF, 0x77, 0x7C, 0x7B, 0x77, 0x83, 0x74, 0x1A, 0x8A, 0x15, 0x5F, 0xE1, 0x41, 0x27, + 0x46, 0xA5, 0x17, 0x6A, 0x35, 0x5B, 0xD9, 0xC6, 0x0A, 0x06, 0x11, 0xE0, 0x05, 0x4C, 0xFA, 0x76, + 0xE0, 0x6A, 0x29, 0xC7, 0x78, 0x65, 0x32, 0x09, 0x8F, 0xF0, 0xD4, 0xFE, 0x81, 0x26, 0xC2, 0x6D, + 0x92, 0x5B, 0x21, 0x9E, 0x57, 0x3C, 0x6E, 0x67, 0x8E, 0xDA, 0xD4, 0xC1, 0xEE, 0x00, 0xD1, 0x1B, + 0xB9, 0xCB, 0xAD, 0x9C, 0xFE, 0x76, 0xDE, 0x03, 0x77, 0xF6, 0x64, 0x14, 0x49, 0x4B, 0xE6, 0x38, + 0x14, 0x67, 0x8E, 0xED, 0xCB, 0x5E, 0xC5, 0x7B, 0xE0, 0x4A, 0xC7, 0xEA, 0xF2, 0x34, 0x0B, 0x8A, + 0x38, 0x93, 0x58, 0x10, 0x8F, 0x32, 0x43, 0x37, 0x17, 0x0C, 0xCC, 0x34, 0x74, 0x71, 0xA2, 0x12, + 0x85, 0xCA, 0x14, 0x04, 0xF1, 0xB4, 0x2F, 0x1E, 0x6D, 0x61, 0x8B, 0x3C, 0xA5, 0x6F, 0xCE, 0xBF, + 0x34, 0xCF, 0xDE, 0x9F, 0x1E, 0x32, 0x89, 0x18, 0x02, 0xBA, 0x31, 0xB2, 0xBE, 0xEC, 0x6D, 0xEA, + 0xFD, 0xD1, 0x02, 0xAF, 0x14, 0x86, 0x79, 0xA2, 0xA3, 0xF9, 0xB1, 0xF9, 0x88, 0x29, 0x49, 0xC8, + 0x91, 0x5C, 0xB2, 0x52, 0x52, 0x9C, 0xA4, 0x6F, 0x14, 0xE6, 0x16, 0x65, 0x5F, 0xFB, 0x27, 0xC5, + 0x4E, 0xE5, 0xCB, 0x0B, 0xD6, 0x17, 0x87, 0xC5, 0x27, 0xCB, 0xF3, 0xEE, 0xEC, 0xAF, 0xDE, 0x7D, + 0x9F, 0x9C, 0xC0, 0xA2, 0xE3, 0x75, 0x7A, 0xB6, 0x85, 0x80, 0x85, 0xD5, 0x2C, 0x3F, 0xB2, 0x3F, + 0x63, 0xCE, 0x2B, 0x0E, 0xEF, 0xFF, 0x22, 0xC0, 0x42, 0x20, 0x92, 0xBE, 0x73, 0xE4, 0xA6, 0xB6, + 0x65, 0xF0, 0x6D, 0xE1, 0x46, 0x41, 0xB9, 0x92, 0xE0, 0x78, 0x7C, 0x2B, 0x76, 0x1A, 0x09, 0x0B, + 0x77, 0x9B, 0x9C, 0x27, 0x62, 0x01, 0x9D, 0x9E, 0xAF, 0xFA, 0x81, 0x17, 0x4B, 0x51, 0x5B, 0xF9, + 0xC0, 0x7C, 0xCE, 0xD0, 0xFE, 0x0A, 0x5F, 0x65, 0x07, 0x09, 0x65, 0x51, 0xD5, 0x54, 0xBC, 0xD0, + 0xCF, 0xC6, 0x85, 0x91, 0x1F, 0x2E, 0x54, 0x32, 0x54, 0xA8, 0x6A, 0x98, 0xD0, 0x5C, 0x51, 0x41, + 0xE9, 0x11, 0x41, 0xB8, 0x86, 0xB9, 0x02, 0x82, 0x62, 0x0D, 0x67, 0x41, 0x1B, 0x83, 0xCC, 0x54, + 0x28, 0xD0, 0xA3, 0x31, 0x30, 0x16, 0x8B, 0x04, 0x9A, 0x8D, 0x02, 0x9A, 0xDE, 0xB5, 0xC2, 0xE8, + 0x9F, 0xCC, 0xB9, 0x57, 0x0B, 0xFC, 0xC9, 0x0F, 0xFA, 0x49, 0xC3, 0xC4, 0x1B, 0x91, 0xD7, 0xC9, + 0x4B, 0xBF, 0xC0, 0xAE, 0x81, 0x29, 0x44, 0x82, 0x32, 0x88, 0x5E, 0x4C, 0x73, 0xB9, 0x0E, 0x8D, + 0x80, 0x15, 0x88, 0x80, 0x6E, 0x98, 0x1B, 0x68, 0xA5, 0xBB, 0x62, 0x77, 0x64, 0x8A, 0x29, 0xA0, + 0xEF, 0x63, 0x50, 0x6C, 0xC3, 0x4F, 0x91, 0x9A, 0xEB, 0xBB, 0x86, 0xCC, 0xFE, 0x41, 0x7E, 0x57, + 0x98, 0x2E, 0x45, 0xBE, 0x33, 0x49, 0x82, 0x98, 0x88, 0x09, 0x6F, 0x95, 0x4D, 0xD9, 0xB8, 0x28, + 0x43, 0xC8, 0xFA, 0x2E, 0xEB, 0x08, 0xAC, 0x7C, 0x37, 0x1C, 0x97, 0x6E, 0x11, 0x5D, 0xE5, 0xA6, + 0xFD, 0xAE, 0xF4, 0xFB, 0x98, 0x89, 0x34, 0x76, 0x63, 0xDA, 0x9D, 0x31, 0x2D, 0xE1, 0x2B, 0xAC, + 0x9D, 0x7F, 0x17, 0x3B, 0xD7, 0xBD, 0x6B, 0x95, 0x8C, 0x41, 0x29, 0x93, 0x96, 0x07, 0x96, 0x0F, + 0xFB, 0x98, 0x37, 0x88, 0x93, 0xDE, 0x7E, 0x9E, 0x6B, 0xD5, 0x7C, 0xA6, 0xBB, 0xC4, 0x5D, 0xEA, + 0x41, 0x1F, 0xF0, 0x97, 0x5E, 0xB0, 0x53, 0xBD, 0x9F, 0x33, 0xC1, 0xB0, 0x56, 0xFB, 0x17, 0x2D, + 0xA7, 0xDA, 0xB6, 0x85, 0xCD, 0x36, 0x69, 0xEB, 0xDA, 0x65, 0x09, 0xAC, 0xB3, 0x46, 0x1B, 0x24, + 0x26, 0x5D, 0x69, 0x73, 0xDA, 0x9B, 0x46, 0x53, 0xED, 0xC5, 0x69, 0x6A, 0xAD, 0xB6, 0x6C, 0x3E, + 0x9A, 0x6A, 0x6F, 0x22, 0x4D, 0x75, 0xCA, 0xD2, 0x54, 0x77, 0x8D, 0x36, 0xA8, 0x53, 0x9D, 0xA6, + 0x3A, 0x9B, 0x46, 0x53, 0x9D, 0xC5, 0x69, 0x6A, 0xAD, 0xB6, 0x6C, 0x3E, 0x9A, 0xEA, 0x6C, 0x22, + 0x4D, 0x75, 0xCB, 0xD2, 0xD4, 0xEE, 0x1A, 0x6D, 0x50, 0xB7, 0x3A, 0x4D, 0x75, 0x37, 0x8D, 0xA6, + 0xBA, 0x8B, 0xD3, 0xD4, 0x5A, 0x6D, 0xD9, 0x7C, 0x34, 0xD5, 0x5D, 0x35, 0x4D, 0x45, 0x7A, 0x3D, + 0xA9, 0xD4, 0x68, 0x8B, 0xAF, 0x72, 0xB9, 0x30, 0xBD, 0x99, 0xA2, 0x97, 0x2F, 0x0F, 0xDC, 0x3D, + 0xEE, 0x35, 0x8E, 0x42, 0x23, 0x02, 0x67, 0xD7, 0xB6, 0x5E, 0x74, 0x79, 0x90, 0xBC, 0x2C, 0x48, + 0x4E, 0xA9, 0x31, 0x35, 0x4D, 0x39, 0x40, 0x55, 0x73, 0xFF, 0xAC, 0x79, 0xFF, 0xB8, 0x84, 0x51, + 0x7E, 0xD6, 0x94, 0x8F, 0x3A, 0x78, 0x7E, 0xBB, 0x7C, 0xE3, 0x7D, 0x7D, 0xB6, 0x82, 0x4B, 0x8B, + 0x1D, 0xF7, 0x62, 0x31, 0x56, 0x51, 0x2A, 0x12, 0xD4, 0xED, 0x5D, 0x5B, 0xD5, 0x35, 0x15, 0x94, + 0x7F, 0x19, 0x0B, 0xAB, 0x69, 0xDC, 0xF3, 0x18, 0xA5, 0x92, 0x65, 0x98, 0xC7, 0x9A, 0xA3, 0x49, + 0xF1, 0xAF, 0x4D, 0x01, 0xD2, 0x16, 0x83, 0xBE, 0xC8, 0xB8, 0x90, 0xDE, 0x5B, 0x2C, 0xCF, 0x35, + 0xE5, 0x4D, 0x12, 0xEF, 0xA2, 0x8D, 0x21, 0x16, 0x27, 0x4C, 0x86, 0x05, 0xBA, 0x5D, 0xE7, 0x4C, + 0xD6, 0xE0, 0x80, 0x41, 0x2E, 0x7B, 0x18, 0x87, 0xEB, 0xC2, 0xE0, 0x6B, 0x69, 0x67, 0xA8, 0x66, + 0x37, 0xBC, 0x3B, 0xED, 0xB1, 0x1D, 0xF6, 0xF5, 0xAC, 0xB7, 0xE9, 0xC6, 0x43, 0x58, 0x08, 0xAC, + 0x62, 0x2E, 0xF3, 0x61, 0xA2, 0xE9, 0x2C, 0x90, 0xA7, 0x41, 0x34, 0x65, 0x45, 0xF4, 0x35, 0xE7, + 0xAB, 0xEE, 0x3C, 0xBB, 0x1D, 0x31, 0x67, 0x11, 0x0B, 0x5A, 0x12, 0x0B, 0x6F, 0x55, 0x2A, 0xDC, + 0xA6, 0xDC, 0x69, 0xCE, 0xA9, 0x69, 0x00, 0x47, 0x68, 0x1C, 0x21, 0x58, 0xC5, 0xDF, 0xF9, 0x7C, + 0x74, 0xF1, 0xEB, 0x90, 0xC9, 0xA0, 0x99, 0xD4, 0x1A, 0xD5, 0xF8, 0xF2, 0xC3, 0x77, 0x4F, 0xEC, + 0xEF, 0x9C, 0x6A, 0x9D, 0x2D, 0x9B, 0xE5, 0x0D, 0x26, 0xF9, 0x2F, 0x33, 0x18, 0x54, 0xC4, 0x9A, + 0x9C, 0xC0, 0x1B, 0x89, 0xFB, 0x13, 0xAC, 0xE6, 0x07, 0x4C, 0x69, 0x68, 0x3C, 0x70, 0x8B, 0x10, + 0xD4, 0xA3, 0x82, 0x9E, 0x93, 0xDB, 0x14, 0xF1, 0x0D, 0xDE, 0xA7, 0xAC, 0x0B, 0x93, 0xCA, 0x3C, + 0xAE, 0x23, 0x90, 0xE7, 0xA6, 0xF4, 0x9A, 0x2B, 0xAC, 0x26, 0x7E, 0x9E, 0x47, 0xC3, 0x60, 0x80, + 0x7C, 0x63, 0xAE, 0xF0, 0x02, 0x68, 0x78, 0x58, 0xD3, 0x55, 0x5A, 0xE4, 0xDD, 0x05, 0x7B, 0x47, + 0xB7, 0x5E, 0xB0, 0xA3, 0x81, 0x17, 0x4F, 0xFC, 0xDB, 0x79, 0xBF, 0x7B, 0xB0, 0xBE, 0x17, 0x5C, + 0x45, 0xB5, 0x74, 0xE6, 0x8D, 0xE7, 0xA9, 0x22, 0x9F, 0x26, 0x77, 0xB4, 0xA0, 0x5F, 0x67, 0xB6, + 0xC9, 0xBC, 0x11, 0x3A, 0xD5, 0xAF, 0xF7, 0xEA, 0x45, 0xE0, 0xCF, 0xB6, 0x37, 0x1F, 0x02, 0x63, + 0x43, 0xEC, 0x2B, 0x17, 0x52, 0x13, 0xBE, 0x7C, 0xB8, 0xE2, 0x6B, 0x63, 0xF4, 0x74, 0x44, 0x71, + 0x99, 0xC2, 0x81, 0x85, 0x84, 0x15, 0xBA, 0x63, 0x8F, 0x70, 0xE6, 0x8F, 0x23, 0x43, 0x1B, 0x09, + 0xAF, 0x6D, 0x98, 0xA3, 0x46, 0x73, 0x4C, 0x26, 0xDA, 0x46, 0x06, 0x49, 0xF1, 0xF6, 0xAD, 0x57, + 0xCA, 0x29, 0x45, 0x39, 0x02, 0x95, 0xCA, 0xDD, 0xC8, 0xA4, 0x91, 0x11, 0xB6, 0x5F, 0x1D, 0x19, + 0xE5, 0x9D, 0x24, 0x95, 0x74, 0x76, 0x27, 0x4D, 0x3A, 0xA8, 0xBA, 0x8E, 0xD2, 0x26, 0x84, 0x9A, + 0x05, 0x28, 0x51, 0xBB, 0x5B, 0x08, 0x50, 0xE2, 0xEF, 0x15, 0x08, 0x50, 0x72, 0xD0, 0x52, 0x02, + 0x94, 0x78, 0xF7, 0xB9, 0x05, 0x28, 0xD0, 0x08, 0xED, 0x47, 0x66, 0x58, 0xF7, 0x76, 0x00, 0xA2, + 0x13, 0x71, 0x0C, 0x21, 0x52, 0x09, 0x77, 0xAF, 0x84, 0x38, 0x95, 0x22, 0x36, 0xC9, 0x50, 0xAF, + 0x90, 0xCD, 0x60, 0xE9, 0x65, 0xD1, 0x7C, 0x7D, 0xAE, 0x97, 0xF3, 0x24, 0x2A, 0xB1, 0x07, 0x4B, + 0x97, 0xA8, 0xC4, 0x30, 0xAF, 0x12, 0xD5, 0xCB, 0x39, 0x17, 0x62, 0x3B, 0x5A, 0xF6, 0x28, 0x98, + 0x34, 0x59, 0xDE, 0x51, 0x50, 0x67, 0x29, 0xD3, 0x38, 0x40, 0x72, 0xF9, 0xDE, 0x8D, 0xEF, 0x1A, + 0xA0, 0xD9, 0x63, 0x1E, 0x92, 0x46, 0x65, 0x0E, 0x37, 0xE3, 0x9C, 0x3C, 0xD3, 0xE5, 0xD1, 0xCD, + 0xDD, 0xED, 0x25, 0x08, 0x5B, 0xF4, 0xA9, 0x44, 0x79, 0xCA, 0x3A, 0xAB, 0x1D, 0x66, 0x71, 0x4D, + 0x3E, 0xC6, 0x10, 0x39, 0x8E, 0x06, 0x36, 0x31, 0x3B, 0x4D, 0xA6, 0x61, 0x21, 0x76, 0x8A, 0x61, + 0xAD, 0x21, 0x47, 0x95, 0x93, 0x27, 0xF6, 0xE8, 0xC5, 0x4B, 0xC7, 0x21, 0x6B, 0x1D, 0x72, 0x5F, + 0x54, 0x97, 0xAF, 0xA8, 0x90, 0x2E, 0x85, 0xFE, 0x4A, 0x54, 0x27, 0x5D, 0x95, 0xA4, 0x21, 0x10, + 0x67, 0x23, 0x24, 0x8D, 0xAF, 0x7A, 0x24, 0x69, 0xA0, 0xED, 0x6B, 0x35, 0x92, 0xC6, 0x64, 0xD0, + 0x09, 0xBD, 0x05, 0xBA, 0xF3, 0xDC, 0xB2, 0xC4, 0xC4, 0xF6, 0x4C, 0xE2, 0x82, 0x07, 0x5D, 0x72, + 0x4B, 0xE3, 0x9E, 0xF4, 0x53, 0x3E, 0xEB, 0xAD, 0xBB, 0x44, 0x10, 0xE8, 0xE5, 0x8A, 0x74, 0x2E, + 0x2C, 0x13, 0x04, 0xFA, 0x3A, 0xCA, 0x04, 0x88, 0xC0, 0xE9, 0x32, 0x41, 0x5B, 0x69, 0xB7, 0x95, + 0x57, 0xA1, 0x20, 0x4F, 0x28, 0x48, 0x6E, 0x69, 0x29, 0xA1, 0x20, 0xD1, 0x64, 0xC3, 0xF5, 0xC3, + 0x88, 0x25, 0xD5, 0xC1, 0xB5, 0xAB, 0x5D, 0xCE, 0x88, 0x74, 0xF4, 0x1B, 0x7E, 0x31, 0x43, 0x8B, + 0x98, 0xEB, 0x5E, 0x26, 0xDE, 0x72, 0x16, 0xBC, 0x71, 0xE0, 0x4C, 0x5D, 0xC9, 0x50, 0x2C, 0xDE, + 0xB3, 0xDF, 0xC8, 0x64, 0x4F, 0x7F, 0x8D, 0x2E, 0x64, 0xCE, 0x3D, 0x87, 0x4A, 0xE8, 0x9D, 0xF7, + 0x7B, 0xCD, 0x9B, 0x2F, 0x3F, 0x2F, 0xFB, 0x7C, 0x0F, 0x87, 0x5B, 0xF2, 0x39, 0x8E, 0x96, 0x44, + 0x4A, 0x72, 0x8C, 0x21, 0x7F, 0xB0, 0xB4, 0x6E, 0x27, 0x4C, 0xC9, 0x19, 0x25, 0x53, 0x16, 0x17, + 0xC7, 0x80, 0xD5, 0x4D, 0xFA, 0x63, 0x1B, 0x8D, 0x8E, 0xD1, 0x23, 0x10, 0x82, 0x7D, 0x43, 0xDC, + 0x2C, 0x8B, 0xB8, 0x4E, 0x74, 0x59, 0x27, 0x79, 0x96, 0x62, 0xAC, 0xC7, 0x86, 0xE7, 0x25, 0xA3, + 0xAC, 0xBF, 0x0C, 0x06, 0x6B, 0xED, 0x73, 0x1E, 0x51, 0xC5, 0x3C, 0x79, 0x18, 0xD2, 0x11, 0x30, + 0x67, 0xBD, 0x82, 0x34, 0xAF, 0x8F, 0x4F, 0x0F, 0x43, 0x56, 0x4A, 0x63, 0xC3, 0x83, 0x06, 0xF3, + 0xFC, 0x27, 0xDC, 0x21, 0x79, 0x9D, 0x7E, 0x28, 0xD8, 0xE8, 0x5F, 0x1A, 0x47, 0xC7, 0xC7, 0x87, + 0x27, 0x27, 0x87, 0xA7, 0xA7, 0x87, 0x67, 0x67, 0x87, 0xE7, 0xE7, 0x87, 0x17, 0x17, 0x55, 0xDD, + 0x42, 0xE6, 0x9A, 0x69, 0x4F, 0x35, 0x30, 0x1B, 0x38, 0x4D, 0xD8, 0x3B, 0xAC, 0x18, 0x69, 0x56, + 0xEE, 0x2C, 0x74, 0x38, 0x77, 0x61, 0xE5, 0x5E, 0xF6, 0xD2, 0x6F, 0x6C, 0x8B, 0xAF, 0x44, 0x3D, + 0x2E, 0x25, 0xCC, 0x61, 0x23, 0x39, 0x53, 0x21, 0xB2, 0x01, 0x63, 0x02, 0x4E, 0x38, 0x3E, 0xDC, + 0x73, 0xBE, 0xFF, 0xA5, 0x68, 0xC9, 0xF2, 0x98, 0x49, 0x1E, 0x1F, 0x08, 0x05, 0x38, 0x55, 0x10, + 0xD6, 0x02, 0xD0, 0x8D, 0xAC, 0x73, 0x28, 0x55, 0x4E, 0x2C, 0xBE, 0x3F, 0x88, 0x54, 0x05, 0x27, + 0x1A, 0x02, 0x94, 0x05, 0x74, 0xD9, 0x20, 0x59, 0x1E, 0x98, 0x89, 0x1B, 0x70, 0x90, 0x2C, 0xE1, + 0x5B, 0xB9, 0xD5, 0xE9, 0xE5, 0x7B, 0x56, 0x70, 0xC5, 0x11, 0xAB, 0xB0, 0xFE, 0x68, 0x4B, 0x1D, + 0xC3, 0xDB, 0x66, 0xFC, 0xBB, 0xE1, 0xA3, 0xEB, 0x8B, 0x3C, 0xEB, 0x85, 0xCF, 0x0A, 0xD9, 0x28, + 0xE9, 0x16, 0xC4, 0xE3, 0x3E, 0x6C, 0x9C, 0x04, 0x6E, 0x60, 0xF9, 0x86, 0xC9, 0xDE, 0xE0, 0x6A, + 0xDE, 0xA0, 0x5D, 0xD2, 0xE3, 0x58, 0xF3, 0xFA, 0xAB, 0x65, 0xF8, 0x32, 0x64, 0xC6, 0x7B, 0x54, + 0x1D, 0xA4, 0xBE, 0xF0, 0x12, 0x85, 0x7B, 0xD2, 0xB1, 0x05, 0xE0, 0xCA, 0xB0, 0xE0, 0x57, 0x60, + 0x51, 0xEE, 0x61, 0x50, 0xE6, 0x55, 0x5F, 0x94, 0xD0, 0x75, 0xEC, 0x47, 0xBC, 0x3D, 0xB6, 0x5A, + 0x9B, 0x1B, 0x52, 0x57, 0x0F, 0x43, 0x58, 0x49, 0x4C, 0xE9, 0x85, 0x8D, 0x32, 0x92, 0xA4, 0x85, + 0x30, 0xA0, 0x54, 0x3C, 0xC4, 0x08, 0xA8, 0xC2, 0x1D, 0x08, 0x71, 0x78, 0x35, 0xD1, 0xA4, 0x89, + 0xD9, 0x02, 0xF6, 0xC9, 0xBA, 0xCD, 0xCF, 0x40, 0x3A, 0x40, 0x0C, 0x94, 0xFF, 0x44, 0xD5, 0x34, + 0x43, 0x17, 0x25, 0x3E, 0x75, 0x6E, 0xF2, 0x30, 0x6F, 0x93, 0x23, 0x98, 0x39, 0x9D, 0x35, 0x22, + 0x58, 0x3B, 0x5C, 0x17, 0x1B, 0x07, 0x78, 0xF3, 0x69, 0xB8, 0xF0, 0xF3, 0x9E, 0x87, 0x95, 0xA7, + 0xE1, 0xCF, 0x01, 0xE6, 0x71, 0xA2, 0xA8, 0x32, 0x41, 0x5B, 0x40, 0x4E, 0x44, 0x71, 0xF9, 0x46, + 0xFE, 0x75, 0xA5, 0x84, 0xF9, 0x8F, 0xAA, 0x3C, 0xBE, 0x9D, 0xDC, 0xFF, 0x74, 0xCE, 0x5D, 0x81, + 0x47, 0x0F, 0xA8, 0x3B, 0x71, 0xF0, 0xCE, 0xF2, 0xEA, 0xA3, 0x09, 0x19, 0x54, 0x63, 0xD5, 0x4E, + 0xDA, 0x74, 0xAF, 0xBD, 0xE1, 0x94, 0xC2, 0xD6, 0x0F, 0xC8, 0x6D, 0x70, 0x59, 0xAA, 0x67, 0xB6, + 0xF4, 0x65, 0xDA, 0xAE, 0x9A, 0x9B, 0xF8, 0x7C, 0x5A, 0x3C, 0xB0, 0x40, 0x2A, 0xA8, 0x24, 0x90, + 0x15, 0xDA, 0xBD, 0xCB, 0x33, 0xA8, 0x2B, 0x98, 0x2C, 0x66, 0x66, 0xBA, 0x55, 0x8B, 0x4D, 0x10, + 0x8B, 0x33, 0x18, 0x31, 0xDA, 0x8A, 0x0C, 0xDF, 0x09, 0xD5, 0x00, 0x57, 0x28, 0xA5, 0xD5, 0xE7, + 0xD4, 0x0F, 0x9E, 0xCB, 0xEA, 0x9D, 0x8D, 0x5C, 0x21, 0xC6, 0xCE, 0x9B, 0xB6, 0x6D, 0x2E, 0x2E, + 0x74, 0x6A, 0xDB, 0xAE, 0x6E, 0x58, 0x42, 0x95, 0xBF, 0xA0, 0xA2, 0xE9, 0x96, 0xF6, 0xC4, 0xB6, + 0xAE, 0x3F, 0xFF, 0xF9, 0xF6, 0x39, 0x7C, 0x51, 0x28, 0x17, 0xFD, 0x20, 0x9A, 0x07, 0xA1, 0x86, + 0xB8, 0xE7, 0x40, 0x43, 0x05, 0x1B, 0xD9, 0x8E, 0x83, 0x32, 0x54, 0x98, 0x15, 0x5C, 0x16, 0xE1, + 0xF1, 0x5A, 0x92, 0x71, 0xC1, 0xB9, 0x42, 0xE2, 0x95, 0x68, 0x35, 0xE2, 0xAA, 0xCB, 0xB8, 0xAA, + 0x8D, 0x98, 0x0D, 0xE7, 0x8D, 0x2B, 0x4E, 0x24, 0xE8, 0x52, 0x24, 0x15, 0x41, 0xAF, 0x17, 0x2D, + 0xBE, 0xFC, 0x68, 0xD8, 0x18, 0x12, 0xBD, 0x6F, 0x2B, 0x00, 0x8A, 0x16, 0xBB, 0x32, 0xC6, 0x20, + 0xEB, 0xC1, 0x67, 0x05, 0x8D, 0x36, 0xEC, 0x7D, 0xE7, 0x1D, 0x3E, 0x7E, 0x01, 0xE7, 0x95, 0x94, + 0xF8, 0x85, 0x4D, 0x78, 0x0F, 0xE7, 0xD0, 0x65, 0x13, 0xC5, 0xA5, 0x8A, 0x49, 0x33, 0x27, 0xF3, + 0xB3, 0xE4, 0xB6, 0x13, 0x9E, 0x3C, 0x81, 0x7A, 0x84, 0x73, 0xE5, 0x34, 0xBB, 0xCC, 0xE6, 0x0B, + 0x5A, 0x3C, 0xEB, 0xA5, 0x2A, 0x51, 0x62, 0xC1, 0x0B, 0x85, 0x1D, 0x9D, 0xEB, 0x06, 0x95, 0xF5, + 0x65, 0xA6, 0xE1, 0xF9, 0xDC, 0x42, 0x0C, 0x46, 0x75, 0x63, 0xD5, 0x04, 0x76, 0x3C, 0xF0, 0xE9, + 0x66, 0x10, 0x24, 0x2E, 0xB1, 0x59, 0x94, 0xEF, 0x43, 0xCC, 0x75, 0x1B, 0x38, 0x34, 0xE8, 0x29, + 0x38, 0x35, 0x99, 0x3E, 0x24, 0xCA, 0x07, 0x03, 0xAF, 0x1B, 0x43, 0xCB, 0x46, 0x39, 0xCF, 0xB3, + 0x05, 0x85, 0x11, 0x13, 0x97, 0xEF, 0x21, 0x17, 0x1F, 0xA0, 0xDF, 0x47, 0x48, 0x94, 0x52, 0xC3, + 0xC2, 0xFC, 0x9C, 0x58, 0xA3, 0x34, 0x16, 0x06, 0x00, 0x94, 0xE3, 0xF2, 0xA6, 0x38, 0x81, 0xA6, + 0x87, 0x89, 0x11, 0x5D, 0x57, 0xF1, 0x26, 0x14, 0xD7, 0x56, 0xB0, 0xD9, 0xBE, 0xA2, 0x78, 0xAF, + 0xE4, 0x36, 0x1F, 0xB9, 0x89, 0x8C, 0x34, 0x98, 0x55, 0xD5, 0x55, 0x35, 0x51, 0xAD, 0x7D, 0xCC, + 0xED, 0xC0, 0xFF, 0xDD, 0x2B, 0x4F, 0x73, 0xD9, 0x7D, 0x2C, 0x83, 0xF0, 0x2A, 0x08, 0x7C, 0xD5, + 0xAC, 0xFB, 0xB1, 0x4C, 0x7B, 0xAC, 0xE7, 0x1A, 0x36, 0xE6, 0xE3, 0xDC, 0x74, 0x63, 0x7F, 0x6C, + 0x4D, 0xE1, 0x92, 0x72, 0x4D, 0xFF, 0x33, 0xDD, 0x66, 0x5C, 0x05, 0x64, 0xF7, 0x3B, 0xBB, 0x33, + 0x71, 0xB8, 0xA6, 0x5E, 0x0F, 0xC4, 0xD2, 0x7E, 0x2D, 0xE9, 0x92, 0xA0, 0xC2, 0x2D, 0x41, 0xD9, + 0x95, 0xD5, 0x92, 0x0E, 0xA6, 0x5C, 0x18, 0x63, 0x0C, 0x3E, 0x22, 0x61, 0x9D, 0x77, 0x65, 0x0C, + 0x38, 0x16, 0xC7, 0xC9, 0xE6, 0xEE, 0x33, 0x17, 0xBD, 0xBB, 0x91, 0x01, 0x6F, 0x9F, 0xFE, 0xD8, + 0x9F, 0xB9, 0xE8, 0x8D, 0xEF, 0x93, 0x18, 0x87, 0x85, 0xE3, 0x1C, 0xAE, 0x48, 0x19, 0x38, 0x8E, + 0xA5, 0x80, 0x93, 0x99, 0xDD, 0x18, 0xA0, 0x28, 0xE5, 0x71, 0x36, 0x06, 0xCC, 0xB2, 0x85, 0x7B, + 0x31, 0x1C, 0x3A, 0x98, 0xD0, 0xD9, 0x78, 0x90, 0xE5, 0x11, 0xE9, 0xD4, 0x1A, 0xAB, 0xD6, 0x53, + 0x78, 0x60, 0xB5, 0x26, 0x04, 0x1C, 0xBA, 0x22, 0x0B, 0x7F, 0x17, 0x19, 0xA7, 0x41, 0x36, 0x36, + 0xCC, 0x30, 0x8F, 0xB9, 0x6B, 0xC2, 0x0C, 0x72, 0xE1, 0xA1, 0xB2, 0x87, 0x6F, 0xB5, 0x3B, 0x4A, + 0xD4, 0xD9, 0x7A, 0x69, 0x0A, 0xE5, 0x22, 0x50, 0xDF, 0x24, 0x0E, 0x93, 0xDD, 0x37, 0xE5, 0xDC, + 0xAF, 0xDE, 0x88, 0x83, 0xE3, 0x4D, 0xD8, 0x49, 0xFC, 0xE0, 0x78, 0x33, 0x4D, 0xAB, 0xD3, 0xB8, + 0x58, 0xFD, 0x76, 0xC0, 0x29, 0xE8, 0x72, 0x69, 0x7E, 0x39, 0x85, 0xC9, 0x98, 0x66, 0x69, 0x61, + 0x72, 0x26, 0x2C, 0x3B, 0x41, 0xD3, 0xB1, 0x87, 0xA5, 0x10, 0x08, 0x4F, 0x9D, 0x10, 0x8B, 0x41, + 0x16, 0x23, 0x5D, 0x25, 0x9E, 0x21, 0x31, 0xC4, 0x5B, 0xB2, 0xE3, 0x60, 0x58, 0xA4, 0x1A, 0x92, + 0x0C, 0xE0, 0x2F, 0x08, 0x6C, 0x2E, 0x47, 0x7D, 0x09, 0xAD, 0xD0, 0x61, 0x37, 0xE1, 0xBB, 0xE4, + 0xC1, 0x2F, 0xB1, 0x9F, 0x6A, 0xE1, 0x7D, 0xC3, 0xF7, 0xE8, 0x31, 0xC8, 0x6A, 0xF0, 0xB4, 0x45, + 0xC5, 0x7F, 0x87, 0x5C, 0xA4, 0x61, 0xF4, 0x54, 0x24, 0x34, 0xF2, 0xBF, 0x7D, 0xD3, 0x87, 0xBF, + 0x93, 0x5C, 0xFC, 0x4D, 0x49, 0x23, 0xDD, 0xDA, 0x24, 0x7A, 0x8A, 0x9F, 0xD4, 0x92, 0x15, 0x8E, + 0xBF, 0x37, 0xB1, 0x1C, 0xAB, 0xC0, 0x88, 0x69, 0xA4, 0x0C, 0xF7, 0xFD, 0xCA, 0xC8, 0x74, 0xC7, + 0x4F, 0xB7, 0xD7, 0x65, 0x98, 0xE6, 0xA2, 0xFE, 0xB1, 0xE3, 0x13, 0x7A, 0x57, 0x69, 0x1C, 0x29, + 0xF9, 0x76, 0xB5, 0x45, 0x47, 0x68, 0x37, 0x8E, 0xDA, 0xCB, 0x1D, 0x01, 0x76, 0xAC, 0xB3, 0xDC, + 0x11, 0xBA, 0x8D, 0xA3, 0xEE, 0x72, 0x47, 0xD8, 0x6D, 0x1C, 0xED, 0x2E, 0x77, 0x84, 0xBD, 0xC6, + 0xD1, 0xDE, 0x72, 0x47, 0xD8, 0x6F, 0x1C, 0xED, 0x2F, 0x77, 0x84, 0x77, 0x8D, 0xA3, 0x77, 0x45, + 0x23, 0xB8, 0x55, 0x86, 0xCE, 0x20, 0x38, 0xA9, 0xF3, 0xCD, 0x61, 0xE4, 0x8E, 0x8C, 0xDB, 0x2E, + 0x6A, 0x99, 0x77, 0xF6, 0x69, 0x76, 0xE7, 0xE8, 0xC5, 0x28, 0x75, 0x51, 0x3B, 0xD4, 0x32, 0x73, + 0x6C, 0xDC, 0x0B, 0x27, 0x86, 0xBC, 0xB4, 0x3C, 0xDF, 0x0D, 0xF0, 0x3C, 0x78, 0x16, 0xDD, 0x02, + 0x41, 0x6D, 0x44, 0x53, 0x08, 0x75, 0x81, 0x1A, 0x35, 0x8E, 0xCB, 0x99, 0xCE, 0x2B, 0xF9, 0x18, + 0xCD, 0x34, 0x9F, 0xDD, 0x82, 0x19, 0x08, 0x4E, 0xA9, 0x13, 0xB1, 0xE5, 0x3D, 0xB7, 0xCB, 0x51, + 0xC1, 0x6A, 0xE6, 0xD0, 0x21, 0xC8, 0xC5, 0xDF, 0x30, 0x2B, 0xC6, 0xE6, 0xD6, 0x7D, 0x1F, 0x71, + 0x47, 0x33, 0x18, 0x3B, 0xDC, 0xF2, 0x08, 0xF4, 0xD1, 0xA5, 0x29, 0x7E, 0x51, 0x60, 0x53, 0x9E, + 0xB4, 0x5A, 0xC5, 0x5D, 0xC6, 0xEC, 0x4C, 0x57, 0xA5, 0xCA, 0x50, 0xB8, 0x13, 0x6E, 0x15, 0x5E, + 0xF1, 0x47, 0xE3, 0x93, 0x6D, 0x0F, 0x59, 0x0D, 0xA8, 0x24, 0xE8, 0xB5, 0xEC, 0x72, 0x7D, 0xB6, + 0x44, 0x6A, 0x6B, 0xA3, 0xEE, 0x26, 0x96, 0x83, 0x68, 0x22, 0x95, 0xF3, 0x9D, 0xE1, 0x1C, 0x9B, + 0x00, 0xD1, 0x40, 0x07, 0xFD, 0x42, 0xA6, 0x31, 0x86, 0x67, 0x2C, 0x7C, 0xB8, 0x5C, 0x14, 0x4A, + 0x99, 0xC3, 0x8A, 0xB0, 0x47, 0x2E, 0xD5, 0xB1, 0x71, 0x3F, 0x61, 0xBD, 0xAA, 0x9C, 0x40, 0x3C, + 0x19, 0xB3, 0x8A, 0x4E, 0xEF, 0x96, 0x1A, 0x7D, 0x97, 0x52, 0xDA, 0x68, 0x83, 0x42, 0x3B, 0xB2, + 0xD9, 0x9D, 0x5C, 0x67, 0x6F, 0x04, 0xEA, 0xCC, 0x29, 0xD1, 0xCC, 0xEF, 0xE3, 0xF1, 0x72, 0xBD, + 0xE6, 0x53, 0x87, 0x6C, 0xA4, 0xDA, 0x69, 0x67, 0xED, 0x28, 0xC7, 0x72, 0x5F, 0xA8, 0x75, 0x3E, + 0x33, 0xA4, 0xAE, 0xD9, 0xD6, 0x78, 0xBC, 0xF2, 0xFB, 0xB4, 0xE3, 0xDE, 0x29, 0xEA, 0x6D, 0x32, + 0x93, 0x8E, 0xAF, 0x5A, 0x1A, 0x5E, 0xAD, 0xD9, 0x63, 0x59, 0x4A, 0x1D, 0x14, 0x47, 0x19, 0xE9, + 0x1D, 0x62, 0x99, 0xB4, 0x9C, 0x84, 0x1F, 0x1D, 0x5C, 0x1C, 0xD3, 0x68, 0x01, 0x2D, 0x76, 0x87, + 0x06, 0x18, 0xF8, 0x7F, 0xE0, 0x05, 0xAA, 0x69, 0x3E, 0xA1, 0xC2, 0x69, 0xF9, 0x61, 0x25, 0x1C, + 0x2C, 0xED, 0xA7, 0xCF, 0xF4, 0x87, 0x1E, 0x50, 0x18, 0xC4, 0xA9, 0x9A, 0x1A, 0x45, 0x38, 0xE9, + 0x4C, 0x0F, 0x5C, 0x72, 0x81, 0x92, 0x2F, 0xC0, 0x37, 0xC6, 0xBD, 0x2B, 0xAB, 0x79, 0x1D, 0x8F, + 0xED, 0x40, 0x44, 0x7D, 0xAA, 0xBA, 0x2E, 0x2E, 0xF0, 0x3E, 0x1F, 0x9F, 0x87, 0x77, 0x28, 0x30, + 0x7F, 0x97, 0xDC, 0xA7, 0x06, 0xC6, 0x77, 0x74, 0x23, 0x51, 0x31, 0xA4, 0x00, 0xB8, 0xFE, 0xD8, + 0xC6, 0xFC, 0x45, 0xA7, 0xDE, 0x21, 0xBB, 0xB3, 0x5D, 0x6D, 0xB4, 0xD5, 0x6E, 0xBF, 0x03, 0x50, + 0xB3, 0xF3, 0xBF, 0xFD, 0x75, 0x6B, 0xB7, 0x83, 0x7F, 0x5D, 0x00, 0xF4, 0x7C, 0x60, 0xC9, 0x9E, + 0x6A, 0x68, 0x5B, 0xFB, 0xEF, 0x27, 0x8F, 0x44, 0xE5, 0x00, 0xF6, 0xD0, 0x49, 0x3C, 0x85, 0x8F, + 0xEF, 0x14, 0xF8, 0xF8, 0x92, 0xE2, 0x13, 0x24, 0x16, 0xD7, 0x74, 0xBB, 0xD0, 0xC8, 0xA6, 0xD8, + 0x72, 0xF7, 0x09, 0xA9, 0x4D, 0x57, 0x1E, 0xB1, 0x50, 0xC8, 0x8C, 0x3E, 0x73, 0xA4, 0xB6, 0xDF, + 0x57, 0xC3, 0x86, 0xC2, 0xC1, 0xAA, 0x32, 0x20, 0xD1, 0x8E, 0x6D, 0xA9, 0xAD, 0x6F, 0x2D, 0xB5, + 0x95, 0x0B, 0xFD, 0x1E, 0x1E, 0x34, 0xA2, 0x4E, 0xEE, 0x5B, 0xE0, 0x47, 0xCF, 0x70, 0xBD, 0x2F, + 0x18, 0x08, 0x99, 0xA0, 0x44, 0x29, 0x78, 0xC9, 0x2F, 0x80, 0x4A, 0xBC, 0xB1, 0x38, 0x08, 0x91, + 0x22, 0xD1, 0xC8, 0x6B, 0x58, 0x9A, 0x19, 0x20, 0x17, 0xC0, 0x62, 0x3F, 0x16, 0x7A, 0x76, 0x78, + 0x2D, 0x76, 0x63, 0x23, 0x6B, 0xA0, 0x6A, 0x5B, 0xAA, 0xC9, 0x8E, 0x6F, 0x7B, 0x58, 0xCB, 0x22, + 0x30, 0x29, 0x7F, 0x58, 0xD8, 0x20, 0x1E, 0x67, 0xD4, 0x3A, 0x18, 0xBF, 0x92, 0x71, 0x29, 0x32, + 0x8E, 0xD0, 0xAF, 0x0A, 0x01, 0x87, 0x8D, 0x9E, 0x97, 0x74, 0x33, 0x54, 0xF3, 0x2C, 0xDD, 0xBC, + 0xFF, 0x04, 0xC2, 0xD5, 0x78, 0xD3, 0xEF, 0xFC, 0xC4, 0x2A, 0xE6, 0xD2, 0xBE, 0x13, 0x4D, 0x67, + 0x41, 0x9C, 0x80, 0xCF, 0x94, 0xD6, 0xED, 0x89, 0xA6, 0xCF, 0x7E, 0x7F, 0x97, 0xB3, 0x84, 0x39, + 0xD5, 0xED, 0xC0, 0x31, 0x6D, 0x55, 0xBF, 0xE1, 0x8F, 0x17, 0x86, 0x3B, 0xC6, 0xCA, 0xAC, 0x67, + 0xC6, 0x43, 0x99, 0x02, 0x97, 0xA5, 0xD4, 0x9C, 0x99, 0xCE, 0x1B, 0x47, 0x12, 0xCA, 0xE1, 0x83, + 0x82, 0xA4, 0xA3, 0xC4, 0xA0, 0x28, 0xA4, 0xC3, 0xFF, 0x16, 0x36, 0xF9, 0x1B, 0x77, 0x91, 0x2B, + 0x7E, 0x75, 0x86, 0xAE, 0x0A, 0x4A, 0x4B, 0x66, 0xA0, 0xC3, 0x83, 0xD2, 0x52, 0xE6, 0xB1, 0xA4, + 0x67, 0x1F, 0x77, 0x39, 0x04, 0x9D, 0xE7, 0xD2, 0x7A, 0x8A, 0x70, 0x89, 0x03, 0x61, 0x2E, 0xB7, + 0xD6, 0xC8, 0xEA, 0xA7, 0x4D, 0x75, 0x87, 0x36, 0x3E, 0x1A, 0x82, 0x6E, 0x0B, 0xE1, 0x8B, 0x08, + 0xB8, 0x13, 0x8C, 0x5B, 0x91, 0x62, 0x27, 0xA6, 0xD1, 0x77, 0x54, 0xF7, 0xDB, 0x45, 0x40, 0xF7, + 0x91, 0x1E, 0xE7, 0x74, 0xB7, 0x09, 0xB3, 0x1A, 0xC8, 0x59, 0x91, 0x9C, 0x1B, 0x55, 0xD2, 0x88, + 0x6E, 0x38, 0x85, 0xA3, 0xCC, 0x5A, 0xE9, 0x74, 0x31, 0xC6, 0x1F, 0x4E, 0x3E, 0xDC, 0x4B, 0xF4, + 0xF5, 0x15, 0x3C, 0xFE, 0x79, 0x9D, 0xFB, 0x6F, 0x4F, 0x27, 0xA4, 0x25, 0x8C, 0x32, 0x27, 0xDC, + 0x57, 0x63, 0x18, 0xB0, 0x1A, 0xAF, 0xFD, 0xF8, 0x34, 0x56, 0x2C, 0x5C, 0xD1, 0x7A, 0x23, 0xDC, + 0x42, 0xAE, 0xAF, 0x1A, 0x96, 0x27, 0x2F, 0xC2, 0xF1, 0xBA, 0x8F, 0x0D, 0xB8, 0xEA, 0x07, 0x78, + 0xC9, 0x0E, 0xD4, 0xC0, 0xC6, 0xEA, 0x13, 0xB3, 0x6C, 0x72, 0xCC, 0x07, 0xC5, 0x0A, 0xA6, 0x8E, + 0xC2, 0x95, 0x8F, 0x37, 0xEE, 0xA0, 0x51, 0x71, 0x3C, 0x46, 0x08, 0x25, 0x07, 0x06, 0x07, 0xE9, + 0x0A, 0xB3, 0x0A, 0xAF, 0xBB, 0xEF, 0xD5, 0xF3, 0xE2, 0xDF, 0x71, 0xE0, 0xDB, 0xE1, 0xD6, 0x7F, + 0x75, 0x60, 0x33, 0x79, 0x64, 0x2D, 0xC5, 0xAF, 0xB0, 0x5E, 0xB5, 0x16, 0x61, 0x63, 0x71, 0xD5, + 0x32, 0xEA, 0x61, 0x65, 0xB1, 0x26, 0x69, 0x93, 0x5F, 0x31, 0xF6, 0x66, 0xE5, 0x80, 0x70, 0xB8, + 0x6B, 0xD8, 0xE8, 0x3B, 0x89, 0x06, 0x07, 0x2D, 0xE2, 0xEE, 0x71, 0x3E, 0xDA, 0x62, 0xD0, 0x58, + 0xA5, 0x47, 0x8E, 0x6B, 0xEB, 0x81, 0xF0, 0x2B, 0x79, 0x10, 0xA7, 0x63, 0x3A, 0xB7, 0xDD, 0xC6, + 0x8B, 0xF0, 0xD0, 0x67, 0x04, 0x8D, 0x6D, 0x22, 0x91, 0xB1, 0x1A, 0xEE, 0x14, 0x8E, 0xB6, 0xD9, + 0xF8, 0x9E, 0xDC, 0x5C, 0x5A, 0x54, 0x72, 0x87, 0x17, 0x29, 0x0D, 0x3E, 0x97, 0xCA, 0x9B, 0xAA, + 0xFA, 0xC6, 0x10, 0x8F, 0x4E, 0x94, 0x6B, 0xC3, 0x0A, 0x00, 0xF1, 0xCB, 0xD5, 0x97, 0xCC, 0xF1, + 0x7E, 0x12, 0x69, 0x2E, 0xDE, 0xCD, 0x28, 0xCD, 0xB2, 0xFF, 0x98, 0xF7, 0x38, 0x0C, 0xEA, 0x1D, + 0x3E, 0x73, 0x75, 0x4C, 0xC4, 0xDF, 0x88, 0x49, 0x44, 0x08, 0x1B, 0xD0, 0x3E, 0x21, 0xDE, 0x46, + 0x94, 0x31, 0x93, 0x4F, 0x3B, 0x95, 0x20, 0x98, 0x3A, 0xF1, 0xF2, 0x25, 0x7F, 0xA9, 0xB1, 0x5C, + 0xF5, 0x08, 0xDD, 0x3A, 0x38, 0x6D, 0x73, 0x42, 0x4B, 0xDE, 0x05, 0xC0, 0x6D, 0xB5, 0x61, 0x3D, + 0x4F, 0x6F, 0x37, 0xAF, 0x70, 0x66, 0xC9, 0x52, 0xB2, 0xA5, 0x3D, 0x67, 0x11, 0x9D, 0xBC, 0x31, + 0x70, 0x00, 0xA9, 0x1E, 0x67, 0xA1, 0x68, 0x89, 0x21, 0x9D, 0xDC, 0x1E, 0xE6, 0x55, 0x9A, 0xCB, + 0xF8, 0x1B, 0x57, 0xF7, 0xAC, 0x8D, 0x2E, 0xF4, 0xB3, 0x8D, 0x5C, 0xAA, 0x69, 0x0C, 0x2D, 0xE0, + 0x14, 0x64, 0xB0, 0x13, 0xD0, 0x81, 0xC7, 0x9F, 0xB8, 0x9F, 0x90, 0xE9, 0xCB, 0x07, 0x54, 0x15, + 0x2A, 0x0C, 0xD3, 0x5D, 0x97, 0x88, 0x5F, 0x2E, 0xA9, 0x3B, 0x0C, 0x13, 0x3D, 0xA3, 0xE6, 0x20, + 0xB8, 0x22, 0x8A, 0xEA, 0x0F, 0xDD, 0x56, 0x7B, 0xB7, 0xD5, 0xDE, 0x2B, 0xA3, 0x35, 0x08, 0x58, + 0x1C, 0x7D, 0x80, 0x83, 0x67, 0x48, 0x31, 0xC3, 0x71, 0x19, 0x59, 0x74, 0xD9, 0x93, 0x5F, 0x9D, + 0xA8, 0x6E, 0x74, 0xF8, 0xB6, 0x3B, 0x0D, 0xA0, 0xCC, 0xEF, 0xF0, 0x87, 0xA2, 0x14, 0x73, 0x0B, + 0x09, 0xD1, 0x47, 0x43, 0xF7, 0x47, 0x87, 0x5D, 0x45, 0xA1, 0x88, 0x70, 0x40, 0x11, 0xD9, 0x71, + 0x29, 0x83, 0x4D, 0xFA, 0x9C, 0x72, 0x22, 0xF5, 0xBA, 0xEF, 0xFE, 0x6B, 0x81, 0xE9, 0x46, 0xAE, + 0xBD, 0x2E, 0xC3, 0x6B, 0x19, 0xF5, 0x7A, 0x5A, 0x51, 0x2C, 0x99, 0x6E, 0x6E, 0x60, 0x98, 0x80, + 0x3D, 0xA2, 0x26, 0xC4, 0x3D, 0x30, 0x29, 0xFA, 0x48, 0xE6, 0x8C, 0xE0, 0x7E, 0x6C, 0xF8, 0x21, + 0x1A, 0x5C, 0xD0, 0x73, 0xDB, 0xD2, 0xC8, 0x2B, 0x2E, 0x0E, 0x33, 0xD4, 0xDE, 0x7F, 0x56, 0x0D, + 0x1F, 0x63, 0x26, 0x77, 0xB2, 0x07, 0x15, 0x2F, 0xB2, 0x93, 0xCB, 0x9B, 0x39, 0xBC, 0x3A, 0x6B, + 0xF3, 0x2C, 0x0C, 0x31, 0x19, 0xCE, 0x8A, 0xC0, 0x32, 0xFC, 0x37, 0xDE, 0xE4, 0x60, 0xB8, 0x7F, + 0x62, 0xC2, 0x14, 0x41, 0x57, 0x30, 0x0C, 0x60, 0x81, 0xF4, 0x83, 0xF0, 0x98, 0x5C, 0x0B, 0x45, + 0x09, 0x37, 0x99, 0x33, 0x02, 0xDA, 0xC5, 0x08, 0xBC, 0xDE, 0xE9, 0xAA, 0x5D, 0xFE, 0xB2, 0x5D, + 0x8B, 0xA6, 0xD1, 0x19, 0x57, 0x53, 0x35, 0xE0, 0x34, 0x0B, 0x3B, 0x53, 0xD9, 0xDF, 0x24, 0x32, + 0x4F, 0xF5, 0xFC, 0xBE, 0x8F, 0xA2, 0xD3, 0x4C, 0x6D, 0x93, 0x42, 0xF3, 0x10, 0x49, 0x3B, 0x51, + 0x07, 0x91, 0xF5, 0xE7, 0x12, 0xF6, 0x07, 0x23, 0x60, 0xE8, 0x69, 0xA5, 0xD2, 0x26, 0x93, 0xBE, + 0x92, 0x73, 0x5B, 0xB4, 0x9C, 0x89, 0xD2, 0x38, 0xBA, 0xB5, 0x29, 0xE5, 0x5C, 0xC5, 0x8A, 0x26, + 0xED, 0xC6, 0xD1, 0x89, 0xEA, 0xF1, 0xCA, 0xED, 0x3A, 0x98, 0x90, 0xB1, 0x7A, 0x01, 0x95, 0xAE, + 0x18, 0x0E, 0xB3, 0x38, 0xAE, 0x47, 0x11, 0x95, 0x5E, 0x20, 0x5C, 0x6B, 0x85, 0x85, 0x14, 0x83, + 0x5D, 0xC5, 0x55, 0xAA, 0x18, 0x1D, 0xA8, 0x89, 0x5C, 0x42, 0xC2, 0xCC, 0x0F, 0x3D, 0xCA, 0xFC, + 0xF0, 0xC5, 0x42, 0xEA, 0x22, 0xBF, 0xB4, 0xD6, 0xDA, 0x27, 0xB2, 0x31, 0xED, 0xE1, 0x9D, 0xDD, + 0x3F, 0x3B, 0x55, 0x33, 0xAB, 0x11, 0xAD, 0x20, 0x40, 0x7A, 0x08, 0x87, 0xFC, 0x10, 0x63, 0xA4, + 0x87, 0x78, 0x52, 0xF7, 0xCF, 0x18, 0x4E, 0x67, 0x35, 0xD1, 0xD2, 0x72, 0xE8, 0xD5, 0x65, 0x0A, + 0x55, 0x41, 0x6E, 0xD7, 0x5C, 0x00, 0x39, 0x59, 0xB0, 0x19, 0x99, 0x06, 0x7D, 0xC2, 0x25, 0xCA, + 0x0B, 0x1A, 0xD5, 0xE7, 0x8E, 0x14, 0x59, 0xD8, 0xA1, 0x61, 0x9A, 0xA3, 0xD1, 0xC6, 0xC5, 0x40, + 0x27, 0x40, 0x5E, 0xA4, 0xA8, 0x96, 0xB7, 0xFB, 0x94, 0xD5, 0x52, 0xE3, 0x3C, 0x1B, 0xA4, 0x34, + 0x98, 0x07, 0x86, 0xBA, 0x35, 0xE6, 0xD2, 0x37, 0xD5, 0xEF, 0x4C, 0xAE, 0xA3, 0x50, 0xCC, 0xC3, + 0x41, 0xD8, 0x16, 0xE8, 0x6A, 0x6F, 0x0F, 0xAB, 0x19, 0x1E, 0x16, 0xD7, 0x45, 0x43, 0x7F, 0x24, + 0xF4, 0x0F, 0x41, 0xA6, 0x05, 0xAB, 0x46, 0x6C, 0x62, 0x18, 0x9E, 0x41, 0x86, 0x13, 0x6D, 0x64, + 0xF0, 0x07, 0x44, 0x3C, 0x53, 0x2C, 0x46, 0xAA, 0x9F, 0x9C, 0x7C, 0x30, 0x26, 0x7E, 0x21, 0x7C, + 0x10, 0x10, 0xDC, 0x98, 0x89, 0x51, 0x36, 0xF8, 0x9E, 0x69, 0xC3, 0x0F, 0x10, 0x13, 0xC7, 0xDB, + 0x14, 0xE6, 0x69, 0xE1, 0x9C, 0xB6, 0xA9, 0xC2, 0x3C, 0xA8, 0xBA, 0x5E, 0xE0, 0x3E, 0xF0, 0x27, + 0x4A, 0x63, 0xAB, 0x42, 0x67, 0x2E, 0x5A, 0x2C, 0xE3, 0x17, 0xC3, 0x30, 0xFA, 0xB4, 0x9A, 0x2A, + 0x55, 0xD9, 0x58, 0x70, 0x28, 0xB6, 0xEE, 0x1C, 0x1C, 0x4C, 0xBE, 0x2A, 0xA9, 0xC2, 0x2E, 0x4D, + 0x7D, 0x2D, 0x67, 0xE3, 0x29, 0x6F, 0xD9, 0xAB, 0xAC, 0xB2, 0xC6, 0x70, 0xB6, 0x94, 0x6E, 0x30, + 0x79, 0x7F, 0x65, 0x21, 0xD4, 0xF3, 0x93, 0xA2, 0x70, 0x5F, 0x58, 0x84, 0x18, 0x0B, 0x37, 0x10, + 0xF5, 0x00, 0xE9, 0x26, 0x21, 0xE9, 0x71, 0xA9, 0xE4, 0x06, 0x94, 0x93, 0xC4, 0xF9, 0x24, 0xC5, + 0x09, 0xCB, 0x26, 0x52, 0x63, 0xC8, 0xE7, 0x31, 0xE6, 0x07, 0x13, 0xE2, 0xA7, 0x11, 0x1E, 0x7D, + 0x47, 0xD1, 0xE5, 0x94, 0x2C, 0xC3, 0xA4, 0x53, 0x41, 0x66, 0xCC, 0x55, 0x05, 0x35, 0x0A, 0xAA, + 0x7B, 0xE9, 0x84, 0xB5, 0x44, 0xCA, 0x92, 0x28, 0x58, 0x81, 0xB6, 0x44, 0x8B, 0xA5, 0x52, 0x97, + 0x14, 0xD1, 0x84, 0x0E, 0x2B, 0x87, 0x28, 0x41, 0x5F, 0x31, 0xC2, 0x2C, 0x61, 0xF8, 0x9D, 0xD1, + 0x68, 0xA6, 0x06, 0x8C, 0xD1, 0x76, 0xC2, 0x28, 0x43, 0x02, 0x1B, 0xA9, 0x99, 0xA8, 0xBD, 0x1C, + 0x0A, 0x14, 0x29, 0x6D, 0x68, 0x8C, 0x2D, 0xED, 0x46, 0x1D, 0xE7, 0xDC, 0x74, 0xF7, 0x2F, 0xCE, + 0x7F, 0x87, 0x91, 0x5A, 0xC1, 0xFD, 0xF7, 0x5A, 0xED, 0x87, 0x0B, 0x58, 0xDB, 0x16, 0xE3, 0x7E, + 0x8B, 0x64, 0x05, 0xCC, 0xB7, 0xAE, 0x91, 0x33, 0xE5, 0x0D, 0x7F, 0x04, 0x70, 0xD5, 0x60, 0x57, + 0x9B, 0xB1, 0xAF, 0xC5, 0xBA, 0x47, 0xE3, 0x5A, 0x1F, 0x3F, 0xD2, 0x95, 0x3C, 0x3C, 0x28, 0x97, + 0x5A, 0x6A, 0x79, 0x52, 0xCE, 0xA9, 0x69, 0x63, 0x72, 0x3E, 0xB2, 0x75, 0x04, 0xAE, 0x8B, 0x11, + 0x26, 0xC8, 0x5D, 0xD1, 0xEF, 0x8C, 0xA6, 0xED, 0x49, 0x96, 0x6B, 0x5B, 0x7C, 0x43, 0x58, 0x5E, + 0xBD, 0xD8, 0x19, 0x69, 0x6D, 0xED, 0x29, 0x22, 0xAE, 0xED, 0xAE, 0xF4, 0xB6, 0x17, 0xA9, 0x53, + 0xE1, 0x1D, 0xE9, 0x6D, 0x2F, 0x94, 0x93, 0x57, 0x76, 0xE9, 0x39, 0x99, 0xC5, 0xF3, 0xA5, 0xD7, + 0x8B, 0x5F, 0x7B, 0x22, 0x42, 0x86, 0x4E, 0x9D, 0xB7, 0x7C, 0xC0, 0x5D, 0x4C, 0x76, 0xCF, 0x7A, + 0xB6, 0x67, 0xC8, 0xDC, 0x44, 0xF6, 0x58, 0x64, 0xBB, 0x6A, 0x2B, 0xCA, 0xDE, 0x0E, 0xFC, 0xD8, + 0x8F, 0x24, 0x82, 0xC8, 0x5D, 0x5A, 0x04, 0xF7, 0x0E, 0x5F, 0xCC, 0xDD, 0x65, 0xB4, 0x47, 0x6B, + 0x71, 0x5D, 0x39, 0x99, 0x0E, 0xA5, 0x41, 0x01, 0xA4, 0x9A, 0x4B, 0x0E, 0x85, 0x6E, 0x4A, 0x71, + 0x15, 0x39, 0x16, 0x0B, 0x07, 0x63, 0x5B, 0xDE, 0xDB, 0x35, 0xB8, 0xAA, 0x44, 0x5A, 0x0D, 0xD5, + 0xC0, 0xE9, 0xCB, 0xC9, 0x1C, 0xFC, 0x4D, 0x5A, 0x29, 0xD0, 0x12, 0x66, 0xC8, 0x75, 0xC9, 0x7A, + 0x73, 0xD3, 0xD9, 0x1D, 0x5E, 0xAF, 0x22, 0xA7, 0xA4, 0xCF, 0x14, 0xF4, 0x2B, 0x7D, 0x09, 0x39, + 0xDB, 0x76, 0xF9, 0xD7, 0x8F, 0xB5, 0xDA, 0x7F, 0xAE, 0x29, 0x7C, 0xA3, 0x06, 0x2B, 0x90, 0x3C, + 0xDE, 0xA0, 0x99, 0xF0, 0xCF, 0x5D, 0xCE, 0x19, 0x27, 0x27, 0x7F, 0x7B, 0x79, 0x73, 0xFE, 0x4B, + 0x74, 0xCA, 0xD1, 0xA7, 0x55, 0x9F, 0x73, 0x89, 0x99, 0xAC, 0xC7, 0x49, 0x27, 0xE0, 0x90, 0xA8, + 0x21, 0x29, 0xB9, 0x82, 0x30, 0xEA, 0x3E, 0x1A, 0xFE, 0x48, 0x66, 0x92, 0x05, 0x51, 0xCD, 0x8A, + 0x18, 0x05, 0xC9, 0x68, 0xA4, 0x46, 0xE8, 0xD2, 0x2F, 0xF9, 0xA5, 0x1C, 0x74, 0xF1, 0x4D, 0x5A, + 0xF8, 0xA8, 0x23, 0x3F, 0x61, 0xA0, 0xE6, 0xEF, 0xA8, 0x31, 0x9D, 0x49, 0x48, 0xA5, 0x5E, 0x5A, + 0x95, 0x71, 0x82, 0x9B, 0x3E, 0x04, 0x69, 0x8E, 0xF1, 0x9E, 0xE7, 0xAA, 0x37, 0x43, 0xBD, 0x94, + 0x62, 0xBF, 0x89, 0xFD, 0x3E, 0xAC, 0xC0, 0x65, 0x13, 0xF7, 0x65, 0x29, 0xB3, 0x46, 0x30, 0x65, + 0x2F, 0xA6, 0xEC, 0xFD, 0x59, 0xFE, 0x5D, 0x5A, 0x9B, 0x7D, 0xB6, 0x83, 0x12, 0x97, 0x69, 0xF9, + 0x17, 0x6B, 0xFB, 0xD4, 0x8B, 0xB7, 0x50, 0x37, 0x98, 0xB8, 0x63, 0xB7, 0x86, 0x7E, 0x00, 0x5D, + 0xDA, 0x7B, 0x4C, 0xBA, 0xB0, 0x94, 0xEF, 0x29, 0xFF, 0x1A, 0x6E, 0xF9, 0x4A, 0x5F, 0x36, 0xCB, + 0x49, 0xA6, 0x96, 0x0A, 0x5D, 0xA5, 0x76, 0xD8, 0x08, 0x41, 0x15, 0xC6, 0x1E, 0xAA, 0xD2, 0xC1, + 0xCA, 0x24, 0xE3, 0x9C, 0xED, 0x70, 0x2B, 0x71, 0xD1, 0x12, 0x42, 0x76, 0x93, 0xF4, 0xC5, 0x0A, + 0x7C, 0xE4, 0xCB, 0x3D, 0x16, 0x3A, 0x27, 0xFA, 0x08, 0xA5, 0x87, 0x7A, 0xD9, 0x09, 0x0C, 0x90, + 0x23, 0x51, 0xD7, 0xC9, 0x4D, 0xEC, 0xC9, 0x52, 0xA2, 0x63, 0x65, 0x31, 0xA6, 0x92, 0x98, 0x7B, + 0xC4, 0x53, 0xD2, 0x56, 0x54, 0x13, 0x4B, 0xF1, 0x16, 0x65, 0x27, 0x9D, 0x85, 0x39, 0xC9, 0xDE, + 0xE2, 0x3C, 0x44, 0x59, 0xAC, 0x8B, 0x5D, 0x64, 0x43, 0x8B, 0x75, 0xB1, 0xD7, 0x38, 0xEA, 0x2E, + 0x38, 0x0B, 0xCC, 0xC2, 0xA3, 0xBC, 0x08, 0x2E, 0xC8, 0x1F, 0x38, 0x30, 0x9B, 0x94, 0x14, 0x7B, + 0x89, 0x94, 0xAC, 0x2F, 0xC7, 0x1C, 0x56, 0x93, 0xD3, 0x9A, 0xF0, 0x54, 0x04, 0x5C, 0xF0, 0x6D, + 0xDB, 0x1F, 0x51, 0x06, 0xEA, 0x3B, 0x10, 0xC9, 0x2B, 0x73, 0xC7, 0x38, 0x47, 0x9C, 0xED, 0xAE, + 0x71, 0x74, 0x12, 0x3E, 0x63, 0x3D, 0xD7, 0xF6, 0x6D, 0xE0, 0x7F, 0x95, 0xBC, 0x85, 0x52, 0xBA, + 0xCC, 0x98, 0x79, 0x0D, 0xFE, 0x43, 0xFD, 0x5E, 0x6F, 0x2E, 0xEF, 0xA1, 0xAB, 0xF3, 0x79, 0x9C, + 0x87, 0x60, 0x34, 0xF6, 0x4F, 0x6C, 0x9E, 0xC6, 0xB0, 0x17, 0x5F, 0x06, 0x83, 0xF5, 0xF0, 0x1E, + 0x3A, 0x8D, 0xEA, 0xC3, 0x60, 0x32, 0x7C, 0xB4, 0xAC, 0x9C, 0x62, 0xAF, 0x86, 0xC6, 0x26, 0x3B, + 0x2F, 0x12, 0xF0, 0x32, 0x2C, 0xEE, 0x87, 0x58, 0x40, 0xC2, 0xC8, 0x16, 0x2C, 0xFF, 0xED, 0x76, + 0xEC, 0xA5, 0x2B, 0xFB, 0xB1, 0x79, 0x6E, 0x71, 0x77, 0xF8, 0xC4, 0xB6, 0x00, 0x28, 0x6F, 0x29, + 0x83, 0xBE, 0x1F, 0xB8, 0x56, 0xEC, 0x1D, 0x7B, 0x30, 0x88, 0xD1, 0x75, 0x04, 0xC0, 0xF5, 0x77, + 0x41, 0xBA, 0xE7, 0xDC, 0xA1, 0xF2, 0xDA, 0x22, 0x7A, 0x79, 0x59, 0xD5, 0xD9, 0x4E, 0x68, 0x98, + 0x58, 0xFC, 0x8E, 0x6E, 0xE0, 0x6F, 0xF1, 0x78, 0xD9, 0xB5, 0xDA, 0xC2, 0xC1, 0x97, 0x9D, 0x7E, + 0x52, 0xD6, 0x69, 0x97, 0x25, 0x19, 0xEE, 0x83, 0x3F, 0xFF, 0xC4, 0x90, 0x01, 0x9B, 0x32, 0xA8, + 0x32, 0x35, 0xA0, 0xF2, 0x0A, 0x9C, 0xEB, 0xF7, 0x2A, 0x66, 0x92, 0x14, 0x86, 0xE5, 0xC0, 0xC3, + 0x2C, 0x20, 0x15, 0xBC, 0x8B, 0xD6, 0x03, 0x6B, 0xC6, 0x5C, 0xF5, 0x02, 0x97, 0xA0, 0xD3, 0xD7, + 0x54, 0x73, 0x31, 0xE6, 0x3C, 0xDD, 0x59, 0xE3, 0xE8, 0x7A, 0xF2, 0x84, 0xD1, 0xA3, 0x4A, 0x9C, + 0x79, 0xA6, 0xBF, 0xD4, 0x29, 0xD7, 0xC0, 0x95, 0xAF, 0x39, 0x6C, 0xB5, 0x37, 0x0F, 0x63, 0xBE, + 0xE0, 0xDC, 0xDF, 0xB9, 0x44, 0xBB, 0x91, 0xB7, 0x1E, 0xCC, 0xF2, 0x4C, 0xDC, 0xFB, 0xCA, 0x4A, + 0x5D, 0x58, 0x0E, 0x24, 0xA0, 0x32, 0x5C, 0x86, 0xC5, 0xC6, 0xB4, 0x4A, 0xE4, 0x79, 0x80, 0xBC, + 0xBE, 0xF4, 0x90, 0xD0, 0x6D, 0x50, 0xE8, 0x30, 0xD6, 0x51, 0x1D, 0x0C, 0x10, 0xF2, 0x54, 0x4D, + 0x58, 0x64, 0x7D, 0x02, 0xF6, 0x68, 0x99, 0x4F, 0x13, 0x3A, 0x90, 0xC1, 0x35, 0xD4, 0xAB, 0xA8, + 0xDF, 0x25, 0x2F, 0x99, 0xE3, 0x92, 0x90, 0x00, 0xE5, 0x26, 0x24, 0x41, 0xAD, 0x9D, 0x35, 0x62, + 0x30, 0x19, 0xF9, 0xAB, 0xA6, 0x44, 0x37, 0x9E, 0x09, 0xD8, 0xC1, 0xD7, 0x78, 0x8B, 0xBA, 0x6C, + 0x3E, 0x19, 0x9B, 0xC9, 0x33, 0x95, 0xA7, 0x4E, 0x84, 0x26, 0xBA, 0x62, 0xD5, 0x13, 0x9F, 0x9C, + 0x11, 0x37, 0x1D, 0x74, 0xCA, 0xA1, 0x84, 0xBA, 0x3A, 0x77, 0x4C, 0xFB, 0x49, 0x38, 0xDD, 0xCB, + 0x2E, 0x0C, 0xCC, 0xE4, 0x0B, 0x24, 0x6E, 0xFB, 0x28, 0x83, 0x6B, 0x32, 0x03, 0xD2, 0x4C, 0x6E, + 0xAF, 0xB5, 0xE7, 0xAE, 0x53, 0x41, 0x86, 0xB4, 0x25, 0xB5, 0x94, 0xBB, 0xAC, 0x9A, 0x46, 0xC7, + 0xE5, 0xF7, 0x20, 0xDD, 0x84, 0xD1, 0x59, 0x35, 0x04, 0x07, 0xE6, 0x6A, 0x19, 0x72, 0xBF, 0xBD, + 0xC3, 0xD5, 0x9B, 0xD1, 0x27, 0xA1, 0x82, 0x72, 0x12, 0xB9, 0x21, 0x82, 0x94, 0x1B, 0xB5, 0x54, + 0x44, 0xE0, 0xE6, 0x26, 0xCA, 0x99, 0xF3, 0x3E, 0x2D, 0x89, 0x31, 0x85, 0xA9, 0x0F, 0x12, 0xAF, + 0xCF, 0x73, 0x7B, 0x36, 0xAF, 0x7E, 0xE9, 0x8D, 0x02, 0x1F, 0xCF, 0xFB, 0x1B, 0xFB, 0x74, 0x84, + 0x69, 0x60, 0x64, 0xE1, 0x0D, 0x39, 0x93, 0x53, 0xC9, 0x25, 0x05, 0xD1, 0xAD, 0xDE, 0x35, 0x3F, + 0x7F, 0x76, 0xA0, 0xA0, 0xC9, 0xEF, 0x45, 0x6A, 0x7F, 0x1F, 0x0B, 0x36, 0x21, 0x39, 0x0E, 0x0F, + 0x97, 0xEB, 0xB6, 0x5F, 0x0E, 0x68, 0xAB, 0xF4, 0xE7, 0x4F, 0x3D, 0x44, 0x48, 0x35, 0x03, 0x85, + 0x4C, 0x16, 0x3E, 0xC0, 0x5C, 0x57, 0x24, 0x88, 0x10, 0x94, 0x80, 0x64, 0x65, 0xD6, 0x75, 0x18, + 0xAD, 0xF0, 0x68, 0x41, 0xF1, 0x07, 0xDD, 0x46, 0x2D, 0xB4, 0xE4, 0x80, 0x04, 0x04, 0x5F, 0x93, + 0xE5, 0x1A, 0xC5, 0x76, 0x4A, 0xF7, 0x20, 0x6A, 0x00, 0x92, 0x34, 0x8F, 0xA3, 0xC6, 0x38, 0x45, + 0x52, 0x23, 0x2C, 0x71, 0xF6, 0x2C, 0x85, 0x19, 0x2C, 0x23, 0xAD, 0x64, 0x3E, 0x1A, 0x3C, 0xCB, + 0x41, 0x55, 0x40, 0x30, 0x8B, 0x9D, 0x5C, 0x78, 0x5B, 0x91, 0xBB, 0x6B, 0xE1, 0xE8, 0xCF, 0x70, + 0x70, 0x79, 0x11, 0x27, 0xC8, 0x39, 0xAF, 0x22, 0x82, 0xC8, 0x3A, 0xB2, 0xB2, 0x09, 0x25, 0x0C, + 0x8F, 0x99, 0xF8, 0x30, 0x2B, 0x6C, 0x2B, 0x2C, 0x71, 0xF9, 0x96, 0xEA, 0x7D, 0x28, 0xDB, 0xCA, + 0x81, 0xD2, 0xFA, 0xE1, 0x4E, 0xBA, 0x22, 0x1E, 0x5D, 0x74, 0xF4, 0xE5, 0xB7, 0x5F, 0xC2, 0x59, + 0xB8, 0x5A, 0xAD, 0xE6, 0x42, 0xD5, 0x7C, 0xDB, 0x9D, 0x64, 0xA2, 0x8F, 0x8A, 0x03, 0x8B, 0xE7, + 0xB1, 0x84, 0xF1, 0xCB, 0xD5, 0x69, 0xA6, 0xE7, 0x51, 0xE4, 0xAD, 0x51, 0x9F, 0x8E, 0x33, 0xBD, + 0x52, 0x41, 0x8B, 0xDC, 0xC5, 0xF4, 0xAF, 0x48, 0x7C, 0x74, 0x86, 0x80, 0x20, 0x89, 0xA1, 0x05, + 0x42, 0x2F, 0x76, 0xA3, 0xCA, 0x6E, 0x91, 0xF2, 0x2C, 0xAB, 0xF5, 0x90, 0x19, 0xC9, 0xF3, 0x6D, + 0xD8, 0x9E, 0xB0, 0x45, 0x8B, 0x1D, 0x43, 0x27, 0x22, 0x12, 0xC1, 0x42, 0x0F, 0x0F, 0x0C, 0x46, + 0x1E, 0xAB, 0x06, 0xE5, 0x28, 0xA2, 0x48, 0x86, 0xD4, 0x32, 0xC3, 0x93, 0x31, 0x96, 0x54, 0x52, + 0x78, 0x95, 0x2A, 0x54, 0x39, 0x8C, 0x6E, 0x54, 0xAD, 0xA8, 0x30, 0x98, 0xC6, 0x99, 0x45, 0xAB, + 0x28, 0x4C, 0x21, 0x61, 0xA2, 0x4C, 0x70, 0x54, 0x42, 0x61, 0x96, 0x32, 0x72, 0x0B, 0x45, 0x38, + 0x69, 0x53, 0xAD, 0x21, 0x6A, 0xBB, 0x5A, 0x51, 0x86, 0x73, 0x2C, 0x01, 0x6A, 0xF1, 0xE7, 0x2B, + 0xC9, 0xC0, 0xE5, 0x04, 0x96, 0x50, 0x90, 0xE1, 0x7C, 0xAA, 0xEB, 0x4A, 0x09, 0x21, 0xA7, 0x1A, + 0xCF, 0x0C, 0x59, 0x2E, 0xA5, 0xC7, 0x14, 0x78, 0xA7, 0x32, 0x47, 0x46, 0x6B, 0x7F, 0xEE, 0x6A, + 0x0D, 0x53, 0x8B, 0xAD, 0xB5, 0xDE, 0x5B, 0x4D, 0x87, 0x92, 0x9C, 0xE1, 0xD9, 0xE7, 0xD3, 0x1E, + 0x9C, 0x45, 0x21, 0x58, 0x03, 0x8C, 0x5A, 0xC0, 0x67, 0x4B, 0x3C, 0x86, 0xE2, 0x23, 0x4F, 0x38, + 0x83, 0x3E, 0xD2, 0x9C, 0x70, 0x1A, 0xC0, 0x0F, 0xB2, 0xCB, 0xD1, 0xAD, 0xE6, 0xB0, 0x8A, 0xEB, + 0x52, 0x11, 0x74, 0xE8, 0xC0, 0xC2, 0xCA, 0xBE, 0x38, 0xF7, 0x56, 0xA8, 0xC3, 0x20, 0xAF, 0xC2, + 0x87, 0xAA, 0x4C, 0x1A, 0x7E, 0xD9, 0x63, 0xC7, 0xBA, 0x8E, 0x07, 0xC3, 0x36, 0x3B, 0xBB, 0xE9, + 0x6F, 0xB3, 0x4F, 0xAA, 0xCF, 0x1F, 0xA5, 0x2D, 0xB9, 0x1F, 0xDC, 0x63, 0x4F, 0xD7, 0xAA, 0xF7, + 0x6D, 0x83, 0xAE, 0x39, 0xF2, 0x93, 0x56, 0xD0, 0xB2, 0x2F, 0x7B, 0x7D, 0x79, 0x10, 0x0B, 0xA4, + 0x0F, 0x81, 0xB6, 0x2C, 0x0D, 0x27, 0x44, 0xA3, 0xCB, 0x5E, 0x22, 0x99, 0xF5, 0xDE, 0x8C, 0xDE, + 0x72, 0x31, 0xB5, 0x29, 0xCF, 0xA0, 0x94, 0x20, 0xBA, 0x90, 0xF2, 0x20, 0xCF, 0xB8, 0x34, 0x94, + 0x22, 0x25, 0x64, 0x1A, 0x81, 0x5E, 0x52, 0x5E, 0xE9, 0xBD, 0x2A, 0x19, 0xA5, 0x71, 0xDE, 0x39, + 0xF9, 0xA4, 0x63, 0x9B, 0x5F, 0x2A, 0x42, 0x72, 0xF2, 0xFE, 0xEA, 0xB2, 0x48, 0xD7, 0x8A, 0xE5, + 0xC0, 0x47, 0x0A, 0xD0, 0x1C, 0xDE, 0x58, 0x67, 0xCC, 0xC6, 0x05, 0xBC, 0x22, 0x73, 0x2E, 0x32, + 0x17, 0x83, 0x68, 0x0A, 0x9B, 0xA1, 0xC1, 0x86, 0xA2, 0xB3, 0x3C, 0x11, 0x0B, 0x50, 0x5A, 0xBE, + 0xB5, 0xCE, 0x68, 0x1D, 0x2E, 0xE4, 0x15, 0xB5, 0x73, 0x51, 0xBB, 0x1C, 0x98, 0xA6, 0xD0, 0x5B, + 0x36, 0xDA, 0x50, 0x14, 0x17, 0x82, 0x5E, 0x01, 0x86, 0xC7, 0xA4, 0xC1, 0x75, 0xC6, 0x72, 0x4F, + 0x4C, 0x73, 0x0C, 0xD3, 0x7C, 0xC5, 0xF4, 0x7C, 0x4C, 0x97, 0xDB, 0x5E, 0x09, 0xD1, 0x45, 0x9B, + 0x35, 0x28, 0x4D, 0x93, 0x2D, 0xE6, 0xCF, 0x5B, 0x70, 0x3C, 0x5C, 0xE2, 0x8D, 0xEF, 0xA0, 0x4F, + 0x61, 0x01, 0x39, 0x44, 0xE8, 0x77, 0x73, 0xD7, 0x23, 0x1F, 0xC4, 0x3A, 0xAA, 0x85, 0x87, 0x08, + 0x3F, 0xD3, 0x79, 0xA4, 0x7F, 0x21, 0x0D, 0xB4, 0x3B, 0xDD, 0x4D, 0x2C, 0xCE, 0x5D, 0x06, 0x95, + 0xAB, 0x95, 0x6A, 0x99, 0xDE, 0xB1, 0x42, 0xBB, 0xFD, 0x54, 0x83, 0x25, 0x5F, 0x5A, 0x57, 0xB3, + 0xC6, 0xE1, 0x56, 0x3F, 0x97, 0x21, 0xCE, 0xF2, 0x9D, 0x25, 0xD8, 0xE0, 0x60, 0x45, 0x73, 0x99, + 0xDF, 0x26, 0xED, 0xE6, 0xB4, 0xBC, 0x4D, 0x40, 0x29, 0x8D, 0x6E, 0xD2, 0xE6, 0x86, 0xCB, 0x7C, + 0x6E, 0x73, 0xDB, 0x64, 0x71, 0xB5, 0x5A, 0xDA, 0xAA, 0x30, 0x3A, 0x0B, 0xB1, 0xDF, 0x34, 0xCF, + 0xBF, 0x3B, 0x00, 0x3E, 0xAB, 0x88, 0xD1, 0xE1, 0xAB, 0x2C, 0x7C, 0xF7, 0x70, 0x45, 0xEE, 0x01, + 0xE2, 0x6A, 0xDF, 0xD2, 0xC9, 0x03, 0xDC, 0x0B, 0x73, 0xB0, 0x19, 0xE3, 0x60, 0x3C, 0x09, 0x7E, + 0x0D, 0x73, 0x96, 0x7B, 0xC2, 0x4A, 0x8E, 0x7E, 0xBA, 0x61, 0x0A, 0xC0, 0x16, 0x3B, 0x6F, 0x0D, + 0x5B, 0xF2, 0x2E, 0x55, 0xF0, 0x4D, 0xB2, 0xEB, 0x61, 0x60, 0xFD, 0x3E, 0xDE, 0xC2, 0x78, 0x93, + 0x9B, 0x1A, 0xEC, 0x0A, 0x33, 0x3B, 0xFD, 0xAF, 0x7D, 0xF6, 0x91, 0xED, 0xEF, 0x7A, 0xAF, 0xAC, + 0x55, 0x92, 0x4A, 0x02, 0x47, 0x0A, 0x59, 0xEB, 0x54, 0x83, 0xFA, 0x59, 0x6B, 0xED, 0x24, 0xE0, + 0x72, 0xCD, 0xF0, 0xE2, 0x21, 0xAE, 0x19, 0xF8, 0x1F, 0xBE, 0xF7, 0x8C, 0xB8, 0xEF, 0x84, 0x73, + 0x08, 0xEB, 0xC3, 0xC9, 0xAC, 0xAA, 0x9A, 0x69, 0x6B, 0xDF, 0xF2, 0x90, 0xBD, 0xD9, 0x51, 0x26, + 0xE8, 0x3E, 0xE9, 0x85, 0xF0, 0x1D, 0xBF, 0xFB, 0xC8, 0xFE, 0xBD, 0x1D, 0xBC, 0xA2, 0x7C, 0x84, + 0xC1, 0x11, 0x4E, 0x94, 0xC2, 0xF7, 0xF0, 0xED, 0xF5, 0x47, 0xF6, 0x5B, 0xDB, 0xF6, 0xCF, 0xB8, + 0x59, 0x68, 0xC6, 0xC0, 0xF7, 0x18, 0xBD, 0xF8, 0x8C, 0xD8, 0xEE, 0xDB, 0x74, 0xD9, 0x6D, 0x07, + 0x96, 0xDE, 0xF4, 0x5D, 0xC3, 0x01, 0xB4, 0x46, 0xF7, 0x76, 0x19, 0x75, 0xE1, 0x46, 0xA9, 0x54, + 0x08, 0xFB, 0xD1, 0xCD, 0x2B, 0x88, 0x7B, 0xA3, 0x2B, 0xAF, 0xD8, 0x2C, 0xF1, 0x73, 0xB2, 0xE9, + 0x65, 0xB0, 0x39, 0x7A, 0x7B, 0x43, 0xB0, 0x19, 0x44, 0x40, 0x51, 0xD2, 0xA6, 0x14, 0x4A, 0x47, + 0x6F, 0xAF, 0x81, 0x04, 0xC3, 0x11, 0x98, 0x80, 0xC7, 0x26, 0xC8, 0xA6, 0x0F, 0xBC, 0x2C, 0x5E, + 0xB7, 0x15, 0xE5, 0x15, 0xB5, 0x13, 0xC8, 0x3A, 0xC1, 0x80, 0xD2, 0xF8, 0x1D, 0x35, 0xD9, 0x00, + 0x24, 0x0F, 0xF1, 0xE1, 0x52, 0x2F, 0xC2, 0xF0, 0x08, 0x73, 0x2E, 0xCF, 0x56, 0x8A, 0xDE, 0x1A, + 0xA6, 0x70, 0x27, 0xE7, 0x24, 0x63, 0x60, 0x48, 0x24, 0x9F, 0x42, 0xE3, 0x18, 0x02, 0x7F, 0xEA, + 0xF5, 0x63, 0xD9, 0x54, 0x31, 0x6B, 0xC1, 0x48, 0x75, 0x55, 0xAD, 0x20, 0x76, 0xE8, 0xE5, 0xE0, + 0x75, 0x81, 0x55, 0x6E, 0x6A, 0xCB, 0x4B, 0xA1, 0xF4, 0xE4, 0xFD, 0xB5, 0x32, 0x65, 0x50, 0xC2, + 0xE0, 0x6B, 0xD5, 0x52, 0xD1, 0x11, 0xB5, 0x16, 0x63, 0x86, 0x6D, 0x69, 0x51, 0xA1, 0x1A, 0xEC, + 0xFD, 0xCA, 0xF0, 0xA8, 0xF8, 0x88, 0xB8, 0xD2, 0x37, 0xB9, 0x1C, 0xAC, 0x31, 0x5F, 0xE5, 0xD9, + 0x0C, 0x7B, 0xC6, 0xB4, 0xD9, 0x23, 0xC3, 0xCC, 0x71, 0x11, 0x1F, 0x3F, 0xD5, 0xD0, 0x31, 0xD3, + 0x7F, 0x86, 0xE1, 0x23, 0xD1, 0x53, 0x39, 0x53, 0x47, 0x02, 0xD4, 0xD2, 0xCC, 0x11, 0x03, 0xC8, + 0xB3, 0x9B, 0x3B, 0xE2, 0x4B, 0x5A, 0xD8, 0xE0, 0x91, 0x2C, 0xB8, 0x94, 0x8E, 0xD4, 0xFD, 0x33, + 0xD6, 0x37, 0xFE, 0xC4, 0xA0, 0x53, 0xE9, 0xAA, 0xAB, 0xE3, 0xC7, 0xEC, 0x1C, 0xB9, 0x0A, 0xBB, + 0x3E, 0x41, 0x0A, 0x61, 0x3B, 0xEC, 0xC2, 0xE5, 0xF1, 0x76, 0xF8, 0xB1, 0xEF, 0x00, 0x6F, 0xCC, + 0x66, 0x88, 0x45, 0x9D, 0x96, 0x37, 0x67, 0x53, 0xC9, 0xC9, 0xE9, 0xDD, 0xBB, 0xC3, 0x87, 0x59, + 0xA7, 0x85, 0xEF, 0x4A, 0x78, 0xBC, 0x31, 0xF9, 0xC0, 0x7F, 0x93, 0xC3, 0x2F, 0xFC, 0xD1, 0x11, + 0xE6, 0x09, 0xFE, 0xB0, 0x03, 0x7F, 0xE4, 0xBE, 0x85, 0x90, 0x2A, 0x7A, 0x4B, 0x3F, 0x4A, 0x30, + 0xB5, 0x89, 0xE3, 0x54, 0x38, 0xF7, 0x3E, 0xC5, 0xBC, 0x1E, 0x9B, 0x69, 0x01, 0xE9, 0xD2, 0xE9, + 0xAA, 0xF0, 0x5A, 0x26, 0xF2, 0xB4, 0x8A, 0x03, 0x83, 0xC8, 0x11, 0xB3, 0xE7, 0xC2, 0x14, 0xB3, + 0x7C, 0xAA, 0x76, 0x7C, 0x77, 0x71, 0x70, 0xE9, 0x94, 0x38, 0xF9, 0xFC, 0x3B, 0x39, 0xCC, 0xFE, + 0x1E, 0xBA, 0x0A, 0xFD, 0xAE, 0xB4, 0xFC, 0xEF, 0x7E, 0xF6, 0xD8, 0x61, 0xDB, 0x3D, 0xDA, 0xFB, + 0xA2, 0xB7, 0x32, 0x80, 0x28, 0x82, 0xA0, 0x71, 0xD5, 0x97, 0x67, 0x02, 0xA4, 0xD9, 0x53, 0x29, + 0x06, 0x63, 0x06, 0xFC, 0x59, 0x9C, 0x41, 0xE4, 0x38, 0xD9, 0x4A, 0x88, 0xAE, 0x10, 0xD6, 0x9D, + 0x4E, 0x5B, 0xE9, 0xB6, 0x7F, 0x57, 0x3A, 0x4A, 0x5B, 0xD9, 0x17, 0x69, 0xAB, 0x8B, 0x00, 0x09, + 0x4D, 0xD8, 0x5F, 0x97, 0x05, 0xF0, 0x99, 0xF9, 0xBC, 0x68, 0x98, 0x77, 0x94, 0xF7, 0xE5, 0x60, + 0xBE, 0xAB, 0x1C, 0xAC, 0x02, 0xE6, 0x72, 0x3E, 0x6B, 0x09, 0x73, 0x78, 0x8A, 0xCC, 0xF9, 0x28, + 0x83, 0x93, 0x17, 0x6E, 0x92, 0x38, 0xC3, 0x0A, 0xB6, 0x29, 0xFF, 0x2A, 0xA7, 0x31, 0x91, 0x8A, + 0x62, 0x0B, 0x05, 0x15, 0x9E, 0xFB, 0xC4, 0x26, 0xC5, 0x5F, 0xC5, 0xD9, 0xC5, 0x4B, 0xEC, 0x63, + 0x1D, 0x6F, 0xCC, 0xB3, 0x16, 0x10, 0x4B, 0xB0, 0x9E, 0x19, 0xAD, 0x46, 0xFE, 0xBD, 0xC8, 0x7A, + 0x0A, 0xB6, 0x32, 0xFD, 0x92, 0x39, 0xDF, 0x65, 0xB9, 0x5C, 0x05, 0xBE, 0xCC, 0xEA, 0x7B, 0xF8, + 0x77, 0x7B, 0x72, 0x76, 0xB6, 0xE3, 0xE5, 0xF6, 0x44, 0x65, 0x3A, 0x14, 0xA0, 0x72, 0xCA, 0xEC, + 0x89, 0x82, 0x6F, 0x3F, 0x95, 0x56, 0x62, 0xD2, 0x2B, 0x34, 0x3A, 0x29, 0x35, 0x19, 0x95, 0x58, + 0x49, 0xC6, 0xC2, 0x92, 0x8B, 0xD9, 0xD5, 0x0F, 0xA5, 0x38, 0x25, 0x16, 0x83, 0x25, 0xD9, 0x02, + 0x2F, 0xAD, 0x40, 0x7A, 0xC5, 0xC8, 0x05, 0x4B, 0x9F, 0xD2, 0x2B, 0x26, 0x6F, 0x8E, 0xDC, 0x74, + 0xF9, 0x74, 0x56, 0xF1, 0x4E, 0x2B, 0xE2, 0x39, 0xA5, 0xDB, 0x61, 0xE4, 0x9D, 0x07, 0x02, 0x32, + 0xFC, 0xDD, 0x26, 0xBD, 0xBB, 0xDD, 0x09, 0x1F, 0x29, 0x59, 0x41, 0x91, 0x39, 0x45, 0x06, 0xD4, + 0x07, 0x7E, 0xE2, 0x5B, 0x33, 0x5A, 0x8A, 0x48, 0x2E, 0x96, 0x24, 0x03, 0x7C, 0x57, 0x5C, 0x14, + 0x52, 0xC1, 0x00, 0xF8, 0x94, 0xBA, 0xFF, 0x53, 0xB1, 0x05, 0x33, 0x8A, 0x32, 0x76, 0x83, 0x4A, + 0x71, 0x3B, 0x52, 0x8A, 0xB3, 0x09, 0x28, 0x94, 0x7C, 0xC5, 0x2C, 0xAB, 0x6A, 0x94, 0xC9, 0xD6, + 0x61, 0xE8, 0x4A, 0xD9, 0x80, 0x96, 0xB4, 0x60, 0x96, 0x12, 0xDB, 0x23, 0xB6, 0xA4, 0xD2, 0x26, + 0xF0, 0xEF, 0x86, 0x9F, 0xB6, 0x09, 0x76, 0xE0, 0xE3, 0x14, 0x53, 0x37, 0x03, 0xDB, 0x4C, 0x36, + 0xE3, 0x1C, 0x3E, 0xA1, 0x83, 0x7C, 0xEA, 0x86, 0x50, 0x48, 0xD0, 0xEC, 0x3E, 0x60, 0x66, 0x8B, + 0x07, 0x69, 0xAB, 0x88, 0x46, 0xA8, 0xB8, 0x31, 0x72, 0xE6, 0x69, 0x1B, 0x53, 0x10, 0x29, 0xE4, + 0x1C, 0xFD, 0x93, 0x75, 0xEF, 0x39, 0x7F, 0x29, 0x02, 0x7B, 0xEC, 0x63, 0xFC, 0x4F, 0x4F, 0x73, + 0x0D, 0xC7, 0x67, 0x9E, 0xAB, 0xC1, 0xFE, 0xBA, 0xDA, 0x0E, 0x46, 0xAA, 0xB5, 0xFE, 0x4E, 0xA3, + 0x89, 0xAF, 0xB2, 0x9A, 0x4C, 0x7A, 0x86, 0x15, 0x7B, 0x18, 0x29, 0x65, 0x63, 0x15, 0x1E, 0x8F, + 0x7D, 0x64, 0xBA, 0xAD, 0x05, 0x68, 0x74, 0x6A, 0xFD, 0x11, 0x70, 0xF7, 0x49, 0x28, 0x0D, 0xB6, + 0x0B, 0x6A, 0xC3, 0xD6, 0x9B, 0x96, 0xEF, 0xBF, 0x79, 0x1B, 0xB5, 0x0C, 0xDB, 0xB4, 0xE0, 0x78, + 0x3F, 0x57, 0xB5, 0xD1, 0x96, 0xCF, 0x3E, 0x1E, 0xB1, 0x7F, 0x4D, 0x2C, 0x03, 0x93, 0x91, 0x62, + 0x32, 0x02, 0xCF, 0x77, 0x55, 0xA7, 0x75, 0x27, 0x5A, 0x6C, 0xF9, 0x93, 0x4E, 0xFE, 0xED, 0xAD, + 0x9C, 0x61, 0x38, 0x2F, 0x00, 0x36, 0x68, 0x9A, 0xC0, 0x37, 0x3E, 0xEC, 0x8C, 0xFC, 0xB1, 0x79, + 0xF4, 0xFF, 0x01, 0x4A, 0x32, 0x29, 0x6B, 0x23, 0x58, 0x00 }; ///index_html //Content of bootstrap.bundle.min.jss with gzip compression diff --git a/Firmware/RTK_Everywhere/icons.h b/Firmware/RTK_Everywhere/icons.h index 6bca2d264..8a77e22d2 100644 --- a/Firmware/RTK_Everywhere/icons.h +++ b/Firmware/RTK_Everywhere/icons.h @@ -1797,7 +1797,7 @@ correctionIconAttribute correctionIconAttributes[CORR_NUM] = { { 4, 0, BT_Symbol_Width, BT_Symbol_Height, BT_Symbol }, { 0, 0, 15, 14, Corr_USB_Icon }, { 0, 0, 15, 14, Corr_TCP_Icon }, - { 1, 0, SIV_Antenna_LBand_Width, SIV_Antenna_LBand_Height, SIV_Antenna_LBand }, + { 1, 1, SIV_Antenna_LBand_Width, SIV_Antenna_LBand_Height, SIV_Antenna_LBand }, { 0, 0, 15, 14, Corr_IP_Icon }, }; @@ -1936,11 +1936,17 @@ const uint8_t SIVIconXPos64x48 = 2; // This aligns the SIV icon neatly under the const uint8_t SIVIconYPos64x48 = 35; const uint8_t SIVIconXPos128x64 = 74; // Just because we can, move SIV to the right on 128x64 const uint8_t SIVIconYPos128x64 = 26; +const uint8_t BaseSIVIconXPos128x64 = 74; // Move SIV info below the 'Xmitting RTCM' text on 128x64 +const uint8_t BaseSIVIconYPos128x64 = (26 + 15); // Assume font size 1; const int SIVTextStartXPosOffset[DISPLAY_MAX_NONE] = { -2, -2 }; // This is a bodge to allow the paintBaseTempSurveyStarted text to be printed in the correct place const iconProperties SIVIconProperties = {{{ &SIV_Antenna, SIV_Antenna_Width, SIV_Antenna_Height, SIVIconXPos64x48, SIVIconYPos64x48 }, { &SIV_Antenna, SIV_Antenna_Width, SIV_Antenna_Height, SIVIconXPos128x64, SIVIconYPos128x64 }}}; + +const iconProperties BaseSIVIconProperties = {{{ &SIV_Antenna, SIV_Antenna_Width, SIV_Antenna_Height, SIVIconXPos64x48, SIVIconYPos64x48 }, + { &SIV_Antenna, SIV_Antenna_Width, SIV_Antenna_Height, BaseSIVIconXPos128x64, BaseSIVIconYPos128x64 }}}; + const iconProperties LBandIconProperties = {{{ &SIV_Antenna_LBand, SIV_Antenna_LBand_Width, SIV_Antenna_LBand_Height, SIVIconXPos64x48, SIVIconYPos64x48 }, { &SIV_Antenna_LBand, SIV_Antenna_LBand_Width, SIV_Antenna_LBand_Height, SIVIconXPos128x64, SIVIconYPos128x64 }}}; const iconProperties ShortIconProperties = {{{ &Antenna_Short, Antenna_Short_Width, Antenna_Short_Height, SIVIconXPos64x48, SIVIconYPos64x48 }, diff --git a/Firmware/RTK_Everywhere/menuBase.ino b/Firmware/RTK_Everywhere/menuBase.ino index cab608c01..6407a9048 100644 --- a/Firmware/RTK_Everywhere/menuBase.ino +++ b/Firmware/RTK_Everywhere/menuBase.ino @@ -247,7 +247,7 @@ void menuBase() else if (settings.fixedBase == false && incoming == 2 && (!present.gnss_mosaicX5)) { // Arbitrary 10 minute limit - getNewSetting("Enter the number of seconds for survey-in obseration time", 60, 60 * 10, + getNewSetting("Enter the number of seconds for survey-in observation time", 60, 60 * 10, &settings.observationSeconds); } else if (settings.fixedBase == false && incoming == 3 && diff --git a/Firmware/RTK_Everywhere/menuCommands.ino b/Firmware/RTK_Everywhere/menuCommands.ino index f209ecac7..01bc362c6 100644 --- a/Firmware/RTK_Everywhere/menuCommands.ino +++ b/Firmware/RTK_Everywhere/menuCommands.ino @@ -661,18 +661,14 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting SystemState *ptr = (SystemState *)var; knownSetting = true; - // 0 = Rover, 1 = Base, 2 = NTP - if (settingValue == 2) - { - if (productVariant == RTK_EVK) - settings.lastState = STATE_NTPSERVER_NOT_STARTED; - else - knownSetting = false; - } + // 0 = Rover, 1 = Base, 2 = NTP, 3 = Base Caster + settings.lastState = STATE_ROVER_NOT_STARTED; // Default if (settingValue == 1) settings.lastState = STATE_BASE_NOT_STARTED; - else - settings.lastState = STATE_ROVER_NOT_STARTED; // Default + else if (settingValue == 2 && productVariant == RTK_EVK) + settings.lastState = STATE_NTPSERVER_NOT_STARTED; + else if (settingValue == 3) + settings.lastState = STATE_BASE_CASTER_NOT_STARTED; } break; case tPulseEdg: { @@ -1080,6 +1076,19 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting } } break; + case tLgMRPqtm: { + for (int x = 0; x < qualifier; x++) + { + if ((suffix[0] == lgMessagesPQTM[x].msgTextName[0]) && + (strcmp(suffix, lgMessagesPQTM[x].msgTextName) == 0)) + { + settings.lg290pMessageRatesPQTM[x] = settingValue; + knownSetting = true; + break; + } + } + } + break; case tLgConst: { for (int x = 0; x < qualifier; x++) { @@ -1134,7 +1143,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting // This is one of the first settings to be received. If seen, remove the station files. removeFile(stationCoordinateECEFFileName); removeFile(stationCoordinateGeodeticFileName); - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Station coordinate files removed"); knownSetting = true; } @@ -1146,7 +1155,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting replaceCharacter((char *)settingValueStr, ' ', ','); // Replace all ' ' with ',' before recording to file recordLineToSD(stationCoordinateECEFFileName, settingValueStr); recordLineToLFS(stationCoordinateECEFFileName, settingValueStr); - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("%s recorded\r\n", settingValueStr); knownSetting = true; } @@ -1155,7 +1164,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting replaceCharacter((char *)settingValueStr, ' ', ','); // Replace all ' ' with ',' before recording to file recordLineToSD(stationCoordinateGeodeticFileName, settingValueStr); recordLineToLFS(stationCoordinateGeodeticFileName, settingValueStr); - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("%s recorded\r\n", settingValueStr); knownSetting = true; } @@ -1207,39 +1216,20 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting else if (strcmp(settingName, "exitAndReset") == 0) { // Confirm receipt - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Sending reset confirmation"); sendStringToWebsocket((char *)"confirmReset,1,"); delay(500); // Allow for delivery - if (configureViaEthernet) - systemPrintln("Reset after Configure-Via-Ethernet"); - else - systemPrintln("Reset after AP Config"); - - if (configureViaEthernet) - { - ethernetWebServerStopESP32W5500(); - - // We need to exit configure-via-ethernet mode. - // But if the settings have not been saved then lastState will still be STATE_CONFIG_VIA_ETH_STARTED. - // If that is true, then force exit to Base mode. I think it is the best we can do. - //(If the settings have been saved, then the code will restart in NTP, Base or Rover mode as desired.) - if (settings.lastState == STATE_CONFIG_VIA_ETH_STARTED) - { - systemPrintln("Settings were not saved. Resetting into Base mode."); - settings.lastState = STATE_BASE_NOT_STARTED; - recordSystemSettings(); - } - } + systemPrintln("Reset after AP Config"); ESP.restart(); } else if (strcmp(settingName, "setProfile") == 0) { // Change to new profile - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("Changing to profile number %d\r\n", settingValue); changeProfileNumber(settingValue); @@ -1251,7 +1241,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting createSettingsString(settingsCSV); - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) { systemPrintf("Sending profile %d\r\n", settingValue); systemPrintf("Profile contents: %s\r\n", settingsCSV); @@ -1274,7 +1264,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting createSettingsString(settingsCSV); - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) { systemPrintf("Sending reset profile %d\r\n", settingValue); systemPrintf("Profile contents: %s\r\n", settingsCSV); @@ -1308,48 +1298,66 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting } else if (strcmp(settingName, "checkNewFirmware") == 0) { - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Checking for new OTA Pull firmware"); sendStringToWebsocket((char *)"checkingNewFirmware,1,"); // Tell the config page we received their request - // Indicate to the OTA state machine that we need to do a version check - otaRequestFirmwareVersionCheck = true; + // We don't use the OTA state machine here because we need to respond to + // 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(); + } // Get firmware version from server - // otaCheckVersion will call wifiConnect if needed - // if (otaCheckVersion(reportedVersion, sizeof(reportedVersion))) - // { - // // We got a version number, now determine if it's newer or not - // char currentVersion[21]; - // getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); - // if (isReportedVersionNewer(reportedVersion, currentVersion) == true) - // { - // if (settings.debugWebConfig == true) - // systemPrintln("New version detected"); - // snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,%s,", reportedVersion); - // } - // else - // { - // if (settings.debugWebConfig == true) - // systemPrintln("No new firmware available"); - // snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,CURRENT,"); - // } - // } - // else - // { - // // Failed to get version number - // if (settings.debugWebConfig == true) - // systemPrintln("Sending error to AP config page"); - // snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,ERROR,"); - // } - - // sendStringToWebsocket(newVersionCSV); + char newVersionCSV[40]; + if (networkHasInternet() == false) + { + // No internet. Report error. + if (settings.debugWebServer == true) + systemPrintln("No internet available. Sending error to Web config page."); + snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,NO_INTERNET,"); + } + else + { + char otaReportedVersion[50]; + if (otaCheckVersion(otaReportedVersion, sizeof(otaReportedVersion))) + { + // 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) + { + if (settings.debugWebServer == true) + systemPrintln("New version detected"); + snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,%s,", otaReportedVersion); + } + else + { + if (settings.debugWebServer == true) + systemPrintln("No new firmware available"); + snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,CURRENT,"); + } + } + else + { + // Failed to get version number + if (settings.debugWebServer == true) + systemPrintln("Sending error to Web config page"); + snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,NO_SERVER,"); + } + } + + sendStringToWebsocket(newVersionCSV); + knownSetting = true; } else if (strcmp(settingName, "getNewFirmware") == 0) { - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintln("Getting new OTA Pull firmware"); sendStringToWebsocket((char *)"gettingNewFirmware,1,"); @@ -1359,8 +1367,6 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting // Notify the network layer we need access, and let OTA state machine take over otaRequestFirmwareUpdate = true; - otaForcedUpdate(); // otaForcedUpdate will call wifiConnect if needed. Also does previouslyConnected check - // We get here if WiFi failed to connect sendStringToWebsocket((char *)"gettingNewFirmware,ERROR,"); knownSetting = true; @@ -1379,7 +1385,8 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting const char *table[] = { "baseTypeSurveyIn", "enableFactoryDefaults", "enableFirmwareUpdate", "enableForgetRadios", "fileSelectAll", "fixedBaseCoordinateTypeGeo", "fixedHAEAPC", "measurementRateSec", - "nicknameECEF", "nicknameGeodetic", "saveToArduino", + "nicknameECEF", "nicknameGeodetic", "saveToArduino", "enableAutoReset", + "shutdownNoChargeTimeoutMinutesCheckbox", }; const int tableEntries = sizeof(table) / sizeof(table[0]); @@ -1874,6 +1881,17 @@ void createSettingsString(char *newSettings) } } break; + case tLgMRPqtm: { + // Record LG290P PQTM rates + for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) + { + char tempString[50]; // lg290pMessageRatesPQTM_EPE=1 Not a float + snprintf(tempString, sizeof(tempString), "%s%s,%d,", rtkSettingsEntries[i].name, + lgMessagesPQTM[x].msgTextName, settings.lg290pMessageRatesPQTM[x]); + stringRecord(newSettings, tempString); + } + } + break; case tLgConst: { // Record LG290P Constellations // lg290pConstellations are uint8_t, but here we have to convert to bool (true / false) so the web page @@ -1909,20 +1927,24 @@ void createSettingsString(char *newSettings) stringRecord(newSettings, "measurementRateHz", 1.0 / gnss->getRateS(), 2); // 2 = decimals to print - // System state at power on. Convert various system states to either Rover or Base or NTP. - int lastState; // 0 = Rover, 1 = Base, 2 = NTP + // System state at power on. Convert various system states to either Rover, Base, NTP, or BaseCast. + int lastState; // 0 = Rover, 1 = Base, 2 = NTP, 3 = BaseCast if (present.ethernet_ws5500 == true) { lastState = 1; // Default Base - if (settings.lastState >= STATE_ROVER_NOT_STARTED && settings.lastState <= STATE_ROVER_RTK_FIX) + if (settings.baseCasterOverride) + lastState = 3; + else if (settings.lastState >= STATE_ROVER_NOT_STARTED && settings.lastState <= STATE_ROVER_RTK_FIX) lastState = 0; - if (settings.lastState >= STATE_NTPSERVER_NOT_STARTED && settings.lastState <= STATE_NTPSERVER_SYNC) + else if (settings.lastState >= STATE_NTPSERVER_NOT_STARTED && settings.lastState <= STATE_NTPSERVER_SYNC) lastState = 2; } else { lastState = 0; // Default Rover - if (settings.lastState >= STATE_BASE_NOT_STARTED && settings.lastState <= STATE_BASE_FIXED_TRANSMITTING) + if (settings.baseCasterOverride) + lastState = 3; + else if (settings.lastState >= STATE_BASE_NOT_STARTED && settings.lastState <= STATE_BASE_FIXED_TRANSMITTING) lastState = 1; } stringRecord(newSettings, "lastState", lastState); @@ -2056,7 +2078,7 @@ void createSettingsString(char *newSettings) { trim(stationInfo); // Remove trailing whitespace - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("ECEF SD station %d - found: %s\r\n", index, stationInfo); replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side @@ -2068,7 +2090,7 @@ void createSettingsString(char *newSettings) { trim(stationInfo); // Remove trailing whitespace - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("ECEF LFS station %d - found: %s\r\n", index, stationInfo); replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side @@ -2094,7 +2116,7 @@ void createSettingsString(char *newSettings) { trim(stationInfo); // Remove trailing whitespace - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("Geo SD station %d - found: %s\r\n", index, stationInfo); replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side @@ -2106,7 +2128,7 @@ void createSettingsString(char *newSettings) { trim(stationInfo); // Remove trailing whitespace - if (settings.debugWebConfig == true) + if (settings.debugWebServer == true) systemPrintf("Geo LFS station %d - found: %s\r\n", index, stationInfo); replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side @@ -2344,12 +2366,6 @@ SettingValueResponse getSettingValue(bool inCommands, const char *settingName, c knownSetting = true; } break; - case tSysState: { - SystemState *ptr = (SystemState *)var; - writeToString(settingValueStr, (int)*ptr); - knownSetting = true; - } - break; case tPulseEdg: { pulseEdgeType_e *ptr = (pulseEdgeType_e *)var; writeToString(settingValueStr, (int)*ptr); @@ -2740,6 +2756,19 @@ SettingValueResponse getSettingValue(bool inCommands, const char *settingName, c } } break; + case tLgMRPqtm: { + for (int x = 0; x < qualifier; x++) + { + if ((suffix[0] == lgMessagesPQTM[x].msgTextName[0]) && + (strcmp(suffix, lgMessagesPQTM[x].msgTextName) == 0)) + { + writeToString(settingValueStr, settings.lg290pMessageRatesPQTM[x]); + knownSetting = true; + break; + } + } + } + break; case tLgConst: { for (int x = 0; x < qualifier; x++) { @@ -2839,7 +2868,7 @@ SettingValueResponse getSettingValue(bool inCommands, const char *settingName, c if (knownSetting == false) { - if (settings.debugWebConfig) + if (settings.debugWebServer) systemPrintf("getSettingValue() Unknown setting: %s\r\n", settingName); } @@ -3286,6 +3315,18 @@ void commandList(bool inCommands, int i) } } break; + case tLgMRPqtm: { + // Record LG290P PQTM rates + for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) + { + snprintf(settingName, sizeof(settingName), "%s%s", rtkSettingsEntries[i].name, + lgMessagesPQTM[x].msgTextName); + + getSettingValue(inCommands, settingName, settingValue); + commandSendExecuteListResponse(settingName, "uint8_t", settingValue); + } + } + break; case tLgConst: { // Record LG290P Constellations for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) diff --git a/Firmware/RTK_Everywhere/menuCorrectionsPriorities.ino b/Firmware/RTK_Everywhere/menuCorrectionsPriorities.ino index 7a3205f83..858447079 100644 --- a/Firmware/RTK_Everywhere/menuCorrectionsPriorities.ino +++ b/Firmware/RTK_Everywhere/menuCorrectionsPriorities.ino @@ -299,11 +299,11 @@ void menuCorrectionsPriorities() &settings.correctionsSourcesLifetime_s); // Check for priority decrease - else if ((incoming >= 'a') && (incoming < ('a' + correctionsSource::CORR_NUM))) + else if ((incoming >= 'a') && (incoming < ('a' + CORR_NUM))) correctionPriorityDecrease(settings.correctionsSourcesPriority[incoming - 'a']); // Check for priority increase - else if ((incoming >= 'A') && (incoming < ('A' + correctionsSource::CORR_NUM))) + else if ((incoming >= 'A') && (incoming < ('A' + CORR_NUM))) correctionPriorityIncrease(settings.correctionsSourcesPriority[incoming - 'A']); else if (incoming == 'x') diff --git a/Firmware/RTK_Everywhere/menuFirmware.ino b/Firmware/RTK_Everywhere/menuFirmware.ino index aa39a92a6..25af250c6 100644 --- a/Firmware/RTK_Everywhere/menuFirmware.ino +++ b/Firmware/RTK_Everywhere/menuFirmware.ino @@ -10,6 +10,19 @@ menuFirmware.ino #ifdef COMPILE_OTA_AUTO +// Automatic over-the-air (OTA) firmware update support +enum OtaState +{ + OTA_STATE_OFF = 0, + OTA_STATE_WAIT_FOR_NETWORK, + OTA_STATE_GET_FIRMWARE_VERSION, + OTA_STATE_CHECK_FIRMWARE_VERSION, + OTA_STATE_UPDATE_FIRMWARE, + + // Add new states here + OTA_STATE_MAX +}; + static const char *const otaStateNames[] = {"OTA_STATE_OFF", "OTA_STATE_WAIT_FOR_NETWORK", "OTA_STATE_GET_FIRMWARE_VERSION", "OTA_STATE_CHECK_FIRMWARE_VERSION", "OTA_STATE_UPDATE_FIRMWARE"}; @@ -20,8 +33,7 @@ static const int otaStateEntries = sizeof(otaStateNames) / sizeof(otaStateNames[ //---------------------------------------- static uint32_t otaLastUpdateCheck; -static NetPriority_t otaPriority = NETWORK_OFFLINE; -static OtaState otaState; +static uint8_t otaState; #endif // COMPILE_OTA_AUTO @@ -249,7 +261,7 @@ void updateFromSD(const char *firmwareFileName) } // Turn off any tasks so that we are not disrupted - espnowStop(); + ESPNOW_STOP(); WIFI_STOP(); bluetoothStop(); @@ -304,7 +316,7 @@ void updateFromSD(const char *firmwareFileName) // Bulk write from the SD file to flash while (firmwareFile.available()) { - bluetoothLedBlink(); // Toggle LED to indcate activity + bluetoothLedBlink(); // Toggle LED to indicate activity int bytesToWrite = pageSize; // Max number of bytes to read if (firmwareFile.available() < bytesToWrite) @@ -433,7 +445,7 @@ bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength) bool gotVersion = false; #ifdef COMPILE_NETWORK - if (networkIsOnline() == false) + if (networkHasInternet() == false) { systemPrintln("Error: Network not available!"); return (false); @@ -461,6 +473,9 @@ bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength) // Call getVersion after original inquiry String otaVersion = ota.GetVersion(); otaVersion.toCharArray(versionAvailable, versionAvailableLength); + + if (settings.debugFirmwareUpdate) + systemPrintf("Reported version available: %s\r\n", versionAvailable); } else if (response == ESP32OTAPull::HTTP_FAILED) { @@ -513,47 +528,6 @@ void otaUpdateFirmware() #endif // COMPILE_NETWORK } -// Start WiFi outside of the network layer and perform the over-the-air update -void otaForcedUpdate() -{ -#ifdef COMPILE_NETWORK - bool wasInAPmode = false; - - if (!networkIsOnline()) - { - if (wifiNetworkCount() == 0) - systemPrintln("Error: Please enter at least one SSID before getting keys"); - else - systemPrintln("Error: Network not available!"); - } - else - { - // Determine if WiFi is running - bool wifiRunning = WiFi.STA.started() || WiFi.STA.linkUp() || WiFi.STA.connected(); - - if ((networkIsOnline) || (wifiConnect(settings.wifiConnectTimeoutMs, true, &wasInAPmode) == - true)) // Use WIFI_AP_STA if already in WIFI_AP mode - otaUpdateFirmware(); - - // Update failed. - // If we were in WIFI_AP mode, return to WIFI_AP mode - if (wasInAPmode) - wifiSetApMode(); - - if (systemState != STATE_WIFI_CONFIG) - { - // WIFI_STOP() turns off the entire radio including the webserver. We need to turn off Station mode - // only. For now, unit exits AP mode via reset so if we are in AP config mode, leave WiFi Station - // running. - - // If WiFi was originally off, turn it off again - if (wifiRunning == false) - WIFI_STOP(); - } - } -#endif // COMPILE_NETWORK -} - // Called while the OTA Pull update is happening void otaPullCallback(int bytesWritten, int totalLength) { @@ -741,7 +715,7 @@ int mapMonthName(char *mmm) #ifdef COMPILE_OTA_AUTO // Get the OTA state name -const char *otaGetStateName(OtaState state, char *string) +const char *otaGetStateName(uint8_t state, char *string) { if (state < OTA_STATE_MAX) return otaStateNames[state]; @@ -750,7 +724,7 @@ const char *otaGetStateName(OtaState state, char *string) } // Set the next OTA state -void otaSetState(OtaState newState) +void otaSetState(uint8_t newState) { char string1[40]; char string2[40]; @@ -809,8 +783,10 @@ void otaUpdateStop() if (otaState != OTA_STATE_OFF) { // Stop network - systemPrintln("Firmware update releasing network request"); - online.otaFirmwareUpdate = false; + if (settings.debugFirmwareUpdate) + systemPrintln("Firmware update releasing network request"); + + online.otaClient = false; otaRequestFirmwareUpdate = false; // Let the network know we no longer need it @@ -864,7 +840,9 @@ void otaUpdate() otaUpdateStop(); // Wait until the network is connected to the media - else if (networkIsConnected(&otaPriority)) + // else if (networkHasInternet()) + else if (networkInterfaceHasInternet( + NETWORK_WIFI)) // TODO Remove once OTA works over other network interfaces { if (settings.debugFirmwareUpdate) systemPrintln("Firmware update connected to network"); @@ -877,39 +855,45 @@ void otaUpdate() // Get firmware version from server case OTA_STATE_GET_FIRMWARE_VERSION: // Determine if the network has failed - if (!networkIsConnected(&otaPriority)) + // if (networkHasInternet() == false) + if (networkInterfaceHasInternet(NETWORK_WIFI) == + false) // TODO Remove once OTA works over other network interfaces otaUpdateStop(); if (settings.debugFirmwareUpdate) - systemPrintln("Firmware update checking SparkFun released firmware version"); + systemPrintln("Checking for latest firmware version"); - // Only update to production firmware, disable release candidates - enableRCFirmware = 0; + // If we are using auto updates, only update to production firmware, disable release candidates + if (settings.enableAutoFirmwareUpdate) + enableRCFirmware = 0; // Get firmware version from server otaReportedVersion[0] = 0; if (otaCheckVersion(otaReportedVersion, sizeof(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; - } + online.otaClient = true; // Create a string of the unit's current firmware version char currentVersion[21]; getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware); - // If we are doing a scheduled automatic update or a manually requested update, continue through the - // state machine - // 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) || (currentVersion[0] == 'd') || (FIRMWARE_VERSION_MAJOR == 99)) { - // Allow update if locally compiled developer version 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; + } + + // If we are doing a scheduled automatic update or a manually requested update, continue through the + // state machine otaSetState(OTA_STATE_UPDATE_FIRMWARE); } else @@ -929,7 +913,7 @@ void otaUpdate() // Update the firmware case OTA_STATE_UPDATE_FIRMWARE: // Determine if the network has failed - if (!networkIsConnected(&otaPriority)) + if (networkHasInternet() == false) otaUpdateStop(); else { @@ -950,4 +934,10 @@ 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 4b52b5927..cb000000b 100644 --- a/Firmware/RTK_Everywhere/menuMain.ino +++ b/Firmware/RTK_Everywhere/menuMain.ino @@ -128,220 +128,172 @@ void menuMain() return; } - if (configureViaEthernet) + while (1) { - while (1) - { - systemPrintln(); - char versionString[21]; - getFirmwareVersion(versionString, sizeof(versionString), true); - RTKBrandAttribute *brandAttributes = getBrandAttributeFromBrand(present.brand); - systemPrintf("%s RTK %s %s\r\n", brandAttributes->name, platformPrefix, versionString); - - systemPrintln("\r\n** Configure Via Ethernet Mode **\r\n"); - - systemPrintln("Menu: Main"); - - systemPrintln("r) Restart Base"); - - systemPrintln("x) Exit"); - - byte incoming = getUserInputCharacterNumber(); - - if (incoming == 'r') - { - displayConfigViaEthStarting(1000); - - ethernetWebServerStopESP32W5500(); + systemPrintln(); + char versionString[21]; + getFirmwareVersion(versionString, sizeof(versionString), true); + RTKBrandAttribute *brandAttributes = getBrandAttributeFromBrand(present.brand); + systemPrintf("%s RTK %s %s\r\n", brandAttributes->name, platformPrefix, versionString); - settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS on this profile - settings.lastState = STATE_BASE_NOT_STARTED; // Record the _next_ state for POR - recordSystemSettings(); +#ifdef COMPILE_BT - ESP.restart(); - } - else if (incoming == 'x') - break; - else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY) - break; - else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); + if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP_AND_BLE) + { + systemPrint("** Bluetooth SPP and BLE broadcasting as: "); + systemPrint(deviceName); } - } - - else - { - while (1) + else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP) { - systemPrintln(); - char versionString[21]; - getFirmwareVersion(versionString, sizeof(versionString), true); - RTKBrandAttribute *brandAttributes = getBrandAttributeFromBrand(present.brand); - systemPrintf("%s RTK %s %s\r\n", brandAttributes->name, platformPrefix, versionString); - -#ifdef COMPILE_BT - - if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP_AND_BLE) - { - systemPrint("** Bluetooth SPP and BLE broadcasting as: "); - systemPrint(deviceName); - } - else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP) - { - systemPrint("** Bluetooth SPP broadcasting as: "); - systemPrint(deviceName); - } - else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE) - { - systemPrint("** Bluetooth Low-Energy broadcasting as: "); - systemPrint(deviceName); - } - else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_OFF) - { - systemPrint("** Bluetooth Turned Off"); - } - systemPrintln(" **"); + systemPrint("** Bluetooth SPP broadcasting as: "); + systemPrint(deviceName); + } + else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE) + { + systemPrint("** Bluetooth Low-Energy broadcasting as: "); + systemPrint(deviceName); + } + else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_OFF) + { + systemPrint("** Bluetooth Turned Off"); + } + systemPrintln(" **"); #else // COMPILE_BT - systemPrintln("** Bluetooth Not Compiled **"); + systemPrintln("** Bluetooth Not Compiled **"); #endif // COMPILE_BT - systemPrintln("Menu: Main"); + systemPrintln("Menu: Main"); - systemPrintln("1) Configure GNSS Receiver"); + systemPrintln("1) Configure GNSS Receiver"); - systemPrintln("2) Configure GNSS Messages"); + systemPrintln("2) Configure GNSS Messages"); - systemPrintln("3) Configure Base"); + systemPrintln("3) Configure Base"); - systemPrintln("4) Configure Ports"); + systemPrintln("4) Configure Ports"); - if (productVariant != RTK_TORCH) // Torch does not have logging - systemPrintln("5) Configure Logging"); + if (productVariant != RTK_TORCH) // Torch does not have logging + systemPrintln("5) Configure Logging"); #ifdef COMPILE_WIFI - systemPrintln("6) Configure WiFi"); + systemPrintln("6) Configure WiFi"); #else // COMPILE_WIFI - systemPrintln("6) **WiFi Not Compiled**"); + systemPrintln("6) **WiFi Not Compiled**"); #endif // COMPILE_WIFI #ifdef COMPILE_NETWORK - systemPrintln("7) Configure TCP/UDP"); + systemPrintln("7) Configure TCP/UDP"); #else // COMPILE_NETWORK - systemPrintln("7) **TCP/UDP Not Compiled**"); + systemPrintln("7) **TCP/UDP Not Compiled**"); #endif // COMPILE_NETWORK #ifdef COMPILE_ETHERNET - if (present.ethernet_ws5500 == true) - systemPrintln("e) Configure Ethernet"); + if (present.ethernet_ws5500 == true) + systemPrintln("e) Configure Ethernet"); #endif // COMPILE_ETHERNET - systemPrintln("f) Firmware Update"); + systemPrintln("f) Firmware Update"); - systemPrintln("i) Configure Corrections Priorities"); + systemPrintln("i) Configure Corrections Priorities"); #ifdef COMPILE_ETHERNET - if (present.ethernet_ws5500 == true) - systemPrintln("n) Configure NTP"); + if (present.ethernet_ws5500 == true) + systemPrintln("n) Configure NTP"); #endif // COMPILE_ETHERNET - systemPrintln("p) Configure PointPerfect"); + systemPrintln("p) Configure PointPerfect"); - systemPrintln("r) Configure Radios"); + systemPrintln("r) Configure Radios"); - systemPrintln("s) Configure System"); + systemPrintln("s) Configure System"); - if (present.imu_im19) - systemPrintln("t) Configure Instrument Setup"); + if (present.imu_im19) + systemPrintln("t) Configure Instrument Setup"); - systemPrintln("u) Configure User Profiles"); + systemPrintln("u) Configure User Profiles"); - if (btPrintEcho) - systemPrintln("b) Exit Bluetooth Echo mode"); + if (btPrintEcho) + systemPrintln("b) Exit Bluetooth Echo mode"); - systemPrintln("+) Enter Command line mode"); + systemPrintln("+) Enter Command line mode"); - systemPrintln("x) Exit"); + systemPrintln("x) Exit"); - byte incoming = getUserInputCharacterNumber(); + byte incoming = getUserInputCharacterNumber(); - if (incoming == 1) - menuGNSS(); - else if (incoming == 2) - gnss->menuMessages(); - else if (incoming == 3) - menuBase(); - else if (incoming == 4) - menuPorts(); + if (incoming == 1) + menuGNSS(); + else if (incoming == 2) + gnss->menuMessages(); + else if (incoming == 3) + menuBase(); + else if (incoming == 4) + menuPorts(); #ifdef COMPILE_MOSAICX5 - else if (incoming == 5 && productVariant == RTK_FACET_MOSAIC) - menuLogMosaic(); -#endif // COMPILE_MOSAICX5 - else if (incoming == 5 && productVariant != RTK_TORCH) // Torch does not have logging - menuLog(); - else if (incoming == 6) - menuWiFi(); - else if (incoming == 7) - menuTcpUdp(); - else if (incoming == 'e' && (present.ethernet_ws5500 == true)) - menuEthernet(); - else if (incoming == 'f') - menuFirmware(); - else if (incoming == 'i') - menuCorrectionsPriorities(); - else if (incoming == 'n' && (present.ethernet_ws5500 == true)) - menuNTP(); - else if (incoming == 'u') - menuUserProfiles(); - else if (incoming == 'p') - menuPointPerfect(); - else if (incoming == 'r') - menuRadio(); - else if (incoming == 's') - menuSystem(); - else if ((incoming == 't') && present.imu_im19) - menuInstrument(); - else if (incoming == 'b' && btPrintEcho == true) - { - printEndpoint = PRINT_ENDPOINT_SERIAL; - systemPrintln("BT device has exited echo mode"); - btPrintEcho = false; - break; // Exit config menu - } - else if (incoming == '+') - menuCommands(); - else if (incoming == 'x') - break; - else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY) - break; - else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); + else if (incoming == 5 && productVariant == RTK_FACET_MOSAIC) + menuLogMosaic(); +#endif // COMPILE_MOSAICX5 + else if (incoming == 5 && productVariant != RTK_TORCH) // Torch does not have logging + menuLog(); + else if (incoming == 6) + menuWiFi(); + else if (incoming == 7) + menuTcpUdp(); + else if (incoming == 'e' && (present.ethernet_ws5500 == true)) + menuEthernet(); + else if (incoming == 'f') + menuFirmware(); + else if (incoming == 'i') + menuCorrectionsPriorities(); + else if (incoming == 'n' && (present.ethernet_ws5500 == true)) + menuNTP(); + else if (incoming == 'u') + menuUserProfiles(); + else if (incoming == 'p') + menuPointPerfect(); + else if (incoming == 'r') + menuRadio(); + else if (incoming == 's') + menuSystem(); + else if ((incoming == 't') && present.imu_im19) + menuInstrument(); + else if (incoming == 'b' && btPrintEcho == true) + { + printEndpoint = PRINT_ENDPOINT_SERIAL; + systemPrintln("BT device has exited echo mode"); + btPrintEcho = false; + break; // Exit config menu } + else if (incoming == '+') + menuCommands(); + else if (incoming == 'x') + break; + else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY) + break; + else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); } - if (!configureViaEthernet) + // Reboot as base only if currently operating as a base station + if (restartBase == true && inBaseMode() == true) { + restartBase = false; + settings.gnssConfiguredBase = false; // Reapply configuration + requestChangeState(STATE_BASE_NOT_STARTED); // Restart base upon exit for latest changes to take effect + } - // Reboot as base only if currently operating as a base station - if (restartBase == true && inBaseMode() == true) - { - restartBase = false; - requestChangeState(STATE_BASE_NOT_STARTED); // Restart base upon exit for latest changes to take effect - } - - if (restartRover == true && inRoverMode() == true) - { - restartRover = false; - requestChangeState(STATE_ROVER_NOT_STARTED); // Restart rover upon exit for latest changes to take effect - } + if (restartRover == true && inRoverMode() == true) + { + restartRover = false; + settings.gnssConfiguredRover = false; // Reapply configuration + requestChangeState(STATE_ROVER_NOT_STARTED); // Restart rover upon exit for latest changes to take effect + } - gnss->saveConfiguration(); + gnss->saveConfiguration(); - recordSystemSettings(); // Once all menus have exited, record the new settings to LittleFS and config file - } + recordSystemSettings(); // Once all menus have exited, record the new settings to LittleFS and config file if (settings.debugGnss == true) { @@ -349,14 +301,6 @@ void menuMain() gnss->debuggingEnable(); } - // Restart WiFi if anything changes - if (restartWiFi == true) - { - restartWiFi = false; - - wifiRestart(); - } - clearBuffer(); // Empty buffer of any newline chars btPrintEchoExit = false; // We are out of the menu system inMainMenu = false; @@ -373,13 +317,13 @@ void menuMain() } // Change system wide settings based on current user profile -// Ways to change the ZED settings: -// Menus - we apply ZED changes at the exit of each sub menu -// Settings file - we detect differences between NVM and settings txt file and updateGNSSSettings = true -// Profile - Before profile is changed, set updateGNSSSettings = true -// AP - once new settings are parsed, set updateGNSSSettings = true +// Ways to change the GNSS settings: +// Menus - we apply changes at the exit of each sub menu +// Settings file - we detect differences between NVM and settings txt file +// Profile - +// AP - // Setup button - -// Factory reset - updatesZEDSettings = true by default +// Factory reset - void menuUserProfiles() { uint8_t originalProfileNumber = profileNumber; @@ -514,8 +458,9 @@ void menuUserProfiles() // Change the active profile number, without unit reset void changeProfileNumber(byte newProfileNumber) { - settings.updateGNSSSettings = true; // When this profile is loaded next, force system to update GNSS settings. - recordSystemSettings(); // Before switching, we need to record the current settings to LittleFS and SD + settings.gnssConfiguredBase = false; // On the next boot, reapply all settings + settings.gnssConfiguredRover = false; + recordSystemSettings(); // Before switching, we need to record the current settings to LittleFS and SD recordProfileNumber(newProfileNumber); profileNumber = newProfileNumber; @@ -536,7 +481,7 @@ void changeProfileNumber(byte newProfileNumber) // Erase all settings. Upon restart, unit will use defaults void factoryReset(bool alreadyHasSemaphore) { - displaySytemReset(); // Display friendly message on OLED + displaySystemReset(); // Display friendly message on OLED tasksStopGnssUart(); @@ -554,6 +499,8 @@ void factoryReset(bool alreadyHasSemaphore) sd->remove(stationCoordinateGeodeticFileName); xSemaphoreGive(sdCardSemaphore); + + systemPrintln("Settings files deleted..."); } // End sdCardSemaphore else { @@ -563,9 +510,13 @@ void factoryReset(bool alreadyHasSemaphore) // An error occurs when a settings file is on the microSD card and it is not // deleted, as such the settings on the microSD card will be loaded when the // RTK reboots, resulting in failure to achieve the factory reset condition - log_d("sdCardSemaphore failed to yield, held by %s, menuMain.ino line %d\r\n", semaphoreHolder, __LINE__); + systemPrintf("sdCardSemaphore failed to yield, held by %s, menuMain.ino line %d\r\n", semaphoreHolder, __LINE__); } } + else + { + systemPrintln("microSD not online. Unable to delete settings files..."); + } tiltSensorFactoryReset(); @@ -573,8 +524,13 @@ void factoryReset(bool alreadyHasSemaphore) LittleFS.format(); if (online.gnss == true) + { + systemPrintln("Resetting the GNSS to factory defaults. This could take a few seconds..."); gnss->factoryReset(); - + } + else + systemPrintln("GNSS not online. Unable to factoryReset..."); + systemPrintln("Settings erased successfully. Rebooting. Goodbye!"); delay(2000); ESP.restart(); @@ -663,9 +619,9 @@ void menuRadio() // Start ESP-NOW so that getChannel runs correctly if (settings.enableEspNow == true) - espnowStart(); + ESPNOW_START(); else - espnowStop(); + ESPNOW_STOP(); } else if (settings.enableEspNow == true && incoming == 2) { @@ -677,7 +633,7 @@ void menuRadio() byte bContinue = getUserInputCharacterNumber(); if (bContinue == 'y') { - if (espnowState > ESPNOW_OFF) + if (espnowGetState() > ESPNOW_OFF) { for (int x = 0; x < settings.espnowPeerCount; x++) espnowRemovePeer(settings.espnowPeers[x]); @@ -688,7 +644,7 @@ void menuRadio() } else if (settings.enableEspNow == true && incoming == 4) { - if (wifiIsRunning() == false) + if (WIFI_IS_RUNNING() == false) { if (getNewSetting("Enter the WiFi channel to use for ESP-NOW communication", 1, 14, &settings.wifiChannel) == INPUT_RESPONSE_VALID) @@ -696,13 +652,13 @@ void menuRadio() } else { - systemPrintln("ESP-NOW channel can't be modified while WiFi is connected."); + systemPrintln("ESP-NOW channel can't be modified while WiFi is active."); } } else if (settings.enableEspNow == true && incoming == 5 && settings.debugEspNow == true) { - if (espnowState == ESPNOW_OFF) - espnowStart(); + if (espnowGetState() == ESPNOW_OFF) + ESPNOW_START(); uint8_t peer1[] = {0xB8, 0xD6, 0x1A, 0x0D, 0x8F, 0x9C}; // Random MAC #ifdef COMPILE_ESPNOW @@ -725,8 +681,8 @@ void menuRadio() } else if (settings.enableEspNow == true && incoming == 6 && settings.debugEspNow == true) { - if (espnowState == ESPNOW_OFF) - espnowStart(); + if (espnowGetState() == ESPNOW_OFF) + ESPNOW_START(); 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 " @@ -738,8 +694,8 @@ void menuRadio() } else if (settings.enableEspNow == true && incoming == 7 && settings.debugEspNow == true) { - if (espnowState == ESPNOW_OFF) - espnowStart(); + if (espnowGetState() == ESPNOW_OFF) + ESPNOW_START(); 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 " @@ -775,7 +731,7 @@ void menuRadio() printUnknown(incoming); } - espnowStart(); + ESPNOW_START(); // 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 30734acf3..af6e24c3a 100644 --- a/Firmware/RTK_Everywhere/menuMessages.ino +++ b/Firmware/RTK_Everywhere/menuMessages.ino @@ -281,8 +281,9 @@ bool beginLogging(const char *customFileName) return(false); } - fileSize = 0; + logFileSize = 0; lastLogSize = 0; // Reset counter - used for displaying active logging icon + lastFileReport = millis(); // Fake last file report to avoid an immediate timeout bufferOverruns = 0; // Reset counter @@ -705,6 +706,16 @@ void checkGNSSArrayDefaults() for (int x = 0; x < MAX_LG290P_RTCM_MSG; x++) settings.lg290pMessageRatesRTCMBase[x] = lgMessagesRTCM[x].msgDefaultRate; } + + if (settings.lg290pMessageRatesPQTM[0] == 254) + { + defaultsApplied = true; + + // Reset rates to defaults + for (int x = 0; x < MAX_LG290P_PQTM_MSG; x++) + settings.lg290pMessageRatesPQTM[x] = lgMessagesPQTM[x].msgDefaultRate; + } + } #endif // COMPILE_LG290P @@ -744,187 +755,3 @@ void setLoggingType() { loggingType = (LoggingType)gnss->getLoggingType(); } - -// During the logging test, we have to modify the messages and rate of the device -void setLogTestFrequencyMessages(int rate, int messages) -{ -#ifdef COMPILE_ZED - // Set measurement frequency - gnss->setRate(1.0 / (double)rate); // Convert Hz to seconds. This will set settings.measurementRateMs, - // settings.navigationRate, and GSV message - - // Set messages - setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages - GNSS_ZED * zed = (GNSS_ZED *)gnss; - if (messages == 5) - { - zed->setMessageRateByName("NMEA_GGA", 1); - zed->setMessageRateByName("NMEA_GSA", 1); - zed->setMessageRateByName("NMEA_GST", 1); - zed->setMessageRateByName("NMEA_GSV", rate); // One report per second - zed->setMessageRateByName("NMEA_RMC", 1); - - log_d("Messages: Surveying Defaults (NMEAx5)"); - } - else if (messages == 7) - { - zed->setMessageRateByName("NMEA_GGA", 1); - zed->setMessageRateByName("NMEA_GSA", 1); - zed->setMessageRateByName("NMEA_GST", 1); - zed->setMessageRateByName("NMEA_GSV", rate); // One report per second - zed->setMessageRateByName("NMEA_RMC", 1); - zed->setMessageRateByName("RXM_RAWX", 1); - zed->setMessageRateByName("RXM_SFRBX", 1); - - log_d("Messages: PPP NMEAx5+RXMx2"); - } - else - log_d("Unknown message amount"); - - // Apply these message rates to both UART1 / SPI and USB - gnss->setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set - gnss->setMessagesUsb(MAX_SET_MESSAGES_RETRIES); -#endif // COMPILE_ZED -} - -// The log test allows us to record a series of different system configurations into -// one file. At the same time, we log the output of the ZED via the USB connection. -// Once complete, the SD log is compared against the USB log to verify both are identical. -// Be sure to set maxLogLength_minutes before running test. maxLogLength_minutes will -// set the length of each test. -void updateLogTest() -{ - // Log is complete, run next text - int rate = 4; - int messages = 5; - int semaphoreWait = 10; - - // Advance to next state - // Note: logTestState is LOGTEST_END by default. - // The increment causes the default switch case to be executed, resetting logTestState to LOGTEST_END. - // The test is started via the debug menu, setting logTestState to LOGTEST_START. - logTestState++; - - switch (logTestState) - { - default: - logTestState = LOGTEST_END; - settings.runLogTest = false; - break; - - case (LOGTEST_4HZ_5MSG_10MS): - // During the first test, create the log file - reuseLastLog = false; - char fileName[100]; - snprintf(fileName, sizeof(fileName), "/%s_LogTest_%02d%02d%02d_%02d%02d%02d.ubx", // SdFat library - platformFilePrefix, rtc.getYear() - 2000, rtc.getMonth() + 1, - rtc.getDay(), // ESP32Time returns month:0-11 - rtc.getHour(true), rtc.getMinute(), rtc.getSecond() // ESP32Time getHour(true) returns hour:0-23 - ); - endSD(false, true); // End previous log - - beginLogging(fileName); - - rate = 4; - messages = 5; - semaphoreWait = 10; - break; - case (LOGTEST_4HZ_7MSG_10MS): - rate = 4; - messages = 7; - semaphoreWait = 10; - break; - case (LOGTEST_10HZ_5MSG_10MS): - rate = 10; - messages = 5; - semaphoreWait = 10; - break; - case (LOGTEST_10HZ_7MSG_10MS): - rate = 10; - messages = 7; - semaphoreWait = 10; - break; - - case (LOGTEST_4HZ_5MSG_0MS): - rate = 4; - messages = 5; - semaphoreWait = 0; - break; - case (LOGTEST_4HZ_7MSG_0MS): - rate = 4; - messages = 7; - semaphoreWait = 0; - break; - case (LOGTEST_10HZ_5MSG_0MS): - rate = 10; - messages = 5; - semaphoreWait = 0; - break; - case (LOGTEST_10HZ_7MSG_0MS): - rate = 10; - messages = 7; - semaphoreWait = 0; - break; - - case (LOGTEST_4HZ_5MSG_50MS): - rate = 4; - messages = 5; - semaphoreWait = 50; - break; - case (LOGTEST_4HZ_7MSG_50MS): - rate = 4; - messages = 7; - semaphoreWait = 50; - break; - case (LOGTEST_10HZ_5MSG_50MS): - rate = 10; - messages = 5; - semaphoreWait = 50; - break; - case (LOGTEST_10HZ_7MSG_50MS): - rate = 10; - messages = 7; - semaphoreWait = 50; - break; - - case (LOGTEST_END): - // Reduce rate - rate = 4; - messages = 5; - semaphoreWait = 10; - setLogTestFrequencyMessages(rate, messages); // Set messages and rate for both UART1 / SPI and USB ports - log_d("Log Test Complete"); - break; - } - - if (settings.runLogTest == true) - { - setLogTestFrequencyMessages(rate, messages); // Set messages and rate for both UART1 / SPI and USB ports - - loggingSemaphoreWait_ms = semaphoreWait / portTICK_PERIOD_MS; // Update variable - - startCurrentLogTime_minutes = millis() / 1000L / 60; // Mark now as start of logging - - char logMessage[100]; - snprintf(logMessage, sizeof(logMessage), "Start log test: %dHz, %dMsg, %dMS", rate, messages, semaphoreWait); - - char nmeaMessage[100]; // Max NMEA sentence length is 82 - createNMEASentence(CUSTOM_NMEA_TYPE_LOGTEST_STATUS, nmeaMessage, sizeof(nmeaMessage), - logMessage); // textID, buffer, sizeOfBuffer, text - - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_LOGTEST); - - logFile->println(nmeaMessage); - - xSemaphoreGive(sdCardSemaphore); - } - else - { - log_w("sdCardSemaphore failed to yield, menuMessages.ino line %d", __LINE__); - } - - systemPrintf("%s\r\n", logMessage); - } -} diff --git a/Firmware/RTK_Everywhere/menuPP.ino b/Firmware/RTK_Everywhere/menuPP.ino index 4d6f4b6c8..23fd38add 100644 --- a/Firmware/RTK_Everywhere/menuPP.ino +++ b/Firmware/RTK_Everywhere/menuPP.ino @@ -21,7 +21,6 @@ static const uint8_t ppIpToken[16] = {POINTPERFECT_IP_TOKEN}; // Toke static const uint8_t ppLbandIpToken[16] = {POINTPERFECT_LBAND_IP_TOKEN}; // Token in HEX form #ifdef COMPILE_NETWORK -static NetPriority_t pointperfectPriority = NETWORK_OFFLINE; MqttClient *menuppMqttClient; #endif // COMPILE_NETWORK @@ -29,6 +28,17 @@ MqttClient *menuppMqttClient; // L-Band Routines - compiled out //---------------------------------------- +bool productVariantSupportsAssistNow() +{ + if (productVariant == RTK_FACET_MOSAIC) + return false; + if (productVariant == RTK_TORCH) + return false; + if (productVariant == RTK_POSTCARD) + return false; + return true; +} + void menuPointPerfectKeys() { while (1) @@ -129,7 +139,7 @@ void menuPointPerfectKeys() settings.pointPerfectNextKeyDuration = settings.pointPerfectCurrentKeyDuration; if (settings.debugCorrections == true) - pointperfectPrintKeyInformation(); + pointperfectPrintKeyInformation("Menu PP"); } else if (incoming == 4) { @@ -719,175 +729,212 @@ long gpsToMjd(long GpsCycle, long GpsWeek, long GpsSeconds) // Global L-Band Routines //---------------------------------------- -// Begin any L-Band hardware +// Update any L-Band hardware // Check if NEO-D9S is connected. Configure if available. // If GNSS is mosaic-X5, configure LBandBeam1 -void beginLBand() +void updateLBand() { - // Skip if going into configure-via-ethernet mode - if (configureViaEthernet) - { - if (settings.debugCorrections == true) - systemPrintln("configureViaEthernet: skipping beginLBand"); - return; - } #ifdef COMPILE_L_BAND if (present.lband_neo) { - if (i2cLBand.begin(*i2c_0, 0x43) == - false) // Connect to the u-blox NEO-D9S using Wire port. The D9S default I2C address is 0x43 (not 0x42) + if (!online.lband_neo && settings.enablePointPerfectCorrections) { - if (settings.debugCorrections == true) + static bool lband_neo_can_not_begin = false; + + if (lband_neo_can_not_begin) + return; + + // NEO-D9S is present but is not yet online. Try to begin the hardware + if (i2cLBand.begin(*i2c_0, 0x43) == + false) // Connect to the u-blox NEO-D9S using Wire port. The D9S default I2C address is 0x43 (not 0x42) + { systemPrintln("L-Band not detected"); - return; - } + lband_neo_can_not_begin = true; + return; + } - // Check the firmware version of the NEO-D9S. Based on Example21_ModuleInfo. - if (i2cLBand.getModuleInfo(1100) == true) // Try to get the module info - { - // Reconstruct the firmware version - snprintf(neoFirmwareVersion, sizeof(neoFirmwareVersion), "%s %d.%02d", i2cLBand.getFirmwareType(), - i2cLBand.getFirmwareVersionHigh(), i2cLBand.getFirmwareVersionLow()); + // Check the firmware version of the NEO-D9S. Based on Example21_ModuleInfo. + if (i2cLBand.getModuleInfo(1100) == true) // Try to get the module info + { + // Reconstruct the firmware version + snprintf(neoFirmwareVersion, sizeof(neoFirmwareVersion), "%s %d.%02d", i2cLBand.getFirmwareType(), + i2cLBand.getFirmwareVersionHigh(), i2cLBand.getFirmwareVersionLow()); - printNEOInfo(); // Print module firmware version - } + printNEOInfo(); // Print module firmware version + } + else + { + systemPrintln("L-Band not detected"); + lband_neo_can_not_begin = true; + return; + } - gnss->update(); + // Update the GNSS position. Use the position to set the frequency if available + gnss->update(); - uint32_t LBandFreq; - uint8_t fixType = gnss->getFixType(); - double latitude = gnss->getLatitude(); - double longitude = gnss->getLongitude(); - // If we have a fix, check which frequency to use - if (fixType >= 2 && fixType <= 5) // 2D, 3D, 3D+DR, or Time - { - int r = 0; // Step through each geographic region - for (; r < numRegionalAreas; r++) + uint32_t LBandFreq; + uint8_t fixType = gnss->getFixType(); + double latitude = gnss->getLatitude(); + double longitude = gnss->getLongitude(); + + // If we have a fix, check which frequency to use + if (fixType >= 2 && fixType <= 5) // 2D, 3D, 3D+DR, or Time { - if ((longitude >= Regional_Information_Table[r].area.lonWest) && - (longitude <= Regional_Information_Table[r].area.lonEast) && - (latitude >= Regional_Information_Table[r].area.latSouth) && - (latitude <= Regional_Information_Table[r].area.latNorth)) + int r = 0; // Step through each geographic region + for (; r < numRegionalAreas; r++) + { + if ((longitude >= Regional_Information_Table[r].area.lonWest) && + (longitude <= Regional_Information_Table[r].area.lonEast) && + (latitude >= Regional_Information_Table[r].area.latSouth) && + (latitude <= Regional_Information_Table[r].area.latNorth)) + { + LBandFreq = Regional_Information_Table[r].frequency; + if (settings.debugCorrections == true) + systemPrintf("Setting L-Band frequency to %s (%dHz)\r\n", Regional_Information_Table[r].name, + LBandFreq); + break; + } + } + if (r == numRegionalAreas) // Geographic region not found { - LBandFreq = Regional_Information_Table[r].frequency; + LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency; if (settings.debugCorrections == true) - systemPrintf("Setting L-Band frequency to %s (%dHz)\r\n", Regional_Information_Table[r].name, - LBandFreq); - break; + systemPrintf("Error: Unknown L-Band geographic region. Using %s (%dHz)\r\n", + Regional_Information_Table[settings.geographicRegion].name, LBandFreq); } } - if (r == numRegionalAreas) // Geographic region not found + else { LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency; if (settings.debugCorrections == true) - systemPrintf("Error: Unknown L-Band geographic region. Using %s (%dHz)\r\n", - Regional_Information_Table[settings.geographicRegion].name, LBandFreq); + systemPrintf("No fix available for L-Band geographic region determination. Using %s (%dHz)\r\n", + Regional_Information_Table[settings.geographicRegion].name, LBandFreq); } - } - else - { - LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency; - if (settings.debugCorrections == true) - systemPrintf("No fix available for L-Band geographic region determination. Using %s (%dHz)\r\n", - Regional_Information_Table[settings.geographicRegion].name, LBandFreq); - } - - bool response = true; - response &= i2cLBand.newCfgValset(); - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_CENTER_FREQUENCY, LBandFreq); // Default 1539812500 Hz - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_SEARCH_WINDOW, 2200); // Default 2200 Hz - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_USE_SERVICE_ID, 0); // Default 1 - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_SERVICE_ID, 21845); // Default 50821 - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_DATA_RATE, 2400); // Default 2400 bps - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_USE_DESCRAMBLER, 1); // Default 1 - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_DESCRAMBLER_INIT, 26969); // Default 23560 - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_USE_PRESCRAMBLING, 0); // Default 0 - response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_UNIQUE_WORD, 16238547128276412563ull); - response &= - i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, 0); // Diasable UBX-RXM-PMP on UART1. Not used. - - response &= i2cLBand.sendCfgValset(); - - GNSS_ZED *zed = (GNSS_ZED *)gnss; - response &= zed->lBandCommunicationEnable(); - if (response == false) - systemPrintln("L-Band failed to configure"); - - i2cLBand.softwareResetGNSSOnly(); // Do a restart + bool response = true; + response &= i2cLBand.newCfgValset(); + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_CENTER_FREQUENCY, LBandFreq); // Default 1539812500 Hz + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_SEARCH_WINDOW, 2200); // Default 2200 Hz + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_USE_SERVICE_ID, 0); // Default 1 + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_SERVICE_ID, 21845); // Default 50821 + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_DATA_RATE, 2400); // Default 2400 bps + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_USE_DESCRAMBLER, 1); // Default 1 + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_DESCRAMBLER_INIT, 26969); // Default 23560 + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_USE_PRESCRAMBLING, 0); // Default 0 + response &= i2cLBand.addCfgValset(UBLOX_CFG_PMP_UNIQUE_WORD, 16238547128276412563ull); + response &= + i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, 0); // Disable UBX-RXM-PMP on UART1. Not used. + + response &= i2cLBand.sendCfgValset(); + + GNSS_ZED *zed = (GNSS_ZED *)gnss; + response &= zed->lBandCommunicationEnable(); + + if (response == false) + { + systemPrintln("L-Band failed to configure"); + lband_neo_can_not_begin = true; + return; + } - if (settings.debugCorrections == true) - systemPrintln("L-Band online"); + i2cLBand.softwareResetGNSSOnly(); // Do a restart - gnss->applyPointPerfectKeys(); // Apply keys now, if we have them. This sets online.lbandCorrections + if (settings.debugCorrections == true) + systemPrintln("L-Band online"); - online.lband_neo = true; + online.lband_neo = true; + } + else if (online.lband_neo && settings.enablePointPerfectCorrections) + { + // L-Band is online. Apply the keys if they have changed + // This may be redundant as PROVISIONING_KEYS_REMAINING also applies the keys + static char previousKey[33] = ""; + if (strncmp(previousKey, settings.pointPerfectCurrentKey, 33) != 0) + { + strncpy(previousKey, settings.pointPerfectCurrentKey, 33); + gnss->applyPointPerfectKeys(); // Apply keys now. This sets online.lbandCorrections + if (settings.debugCorrections == true) + systemPrintln("ZED-F9P PointPerfect keys applied"); + } + } } #endif // COMPILE_L_BAND #ifdef COMPILE_MOSAICX5 - if (present.gnss_mosaicX5 && settings.enablePointPerfectCorrections) + if (present.gnss_mosaicX5) { - uint32_t LBandFreq; - uint8_t fixType = gnss->getFixType(); - double latitude = gnss->getLatitude(); - double longitude = gnss->getLongitude(); - // If we have a fix, check which frequency to use - if (fixType >= 1) // Stand-Alone PVT or better + if (!online.lband_gnss && settings.enablePointPerfectCorrections) { - int r = 0; // Step through each geographic region - for (; r < numRegionalAreas; r++) + static bool lband_gnss_can_not_begin = false; + + if (lband_gnss_can_not_begin) + return; + + uint32_t LBandFreq; + uint8_t fixType = gnss->getFixType(); + double latitude = gnss->getLatitude(); + double longitude = gnss->getLongitude(); + // If we have a fix, check which frequency to use + if (fixType >= 1) // Stand-Alone PVT or better { - if ((longitude >= Regional_Information_Table[r].area.lonWest) && - (longitude <= Regional_Information_Table[r].area.lonEast) && - (latitude >= Regional_Information_Table[r].area.latSouth) && - (latitude <= Regional_Information_Table[r].area.latNorth)) + int r = 0; // Step through each geographic region + for (; r < numRegionalAreas; r++) + { + if ((longitude >= Regional_Information_Table[r].area.lonWest) && + (longitude <= Regional_Information_Table[r].area.lonEast) && + (latitude >= Regional_Information_Table[r].area.latSouth) && + (latitude <= Regional_Information_Table[r].area.latNorth)) + { + LBandFreq = Regional_Information_Table[r].frequency; + if (settings.debugCorrections == true) + systemPrintf("Setting L-Band frequency to %s (%dHz)\r\n", Regional_Information_Table[r].name, + LBandFreq); + break; + } + } + if (r == numRegionalAreas) // Geographic region not found { - LBandFreq = Regional_Information_Table[r].frequency; + LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency; if (settings.debugCorrections == true) - systemPrintf("Setting L-Band frequency to %s (%dHz)\r\n", Regional_Information_Table[r].name, - LBandFreq); - break; + systemPrintf("Error: Unknown L-Band geographic region. Using %s (%dHz)\r\n", + Regional_Information_Table[settings.geographicRegion].name, LBandFreq); } } - if (r == numRegionalAreas) // Geographic region not found + else { LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency; if (settings.debugCorrections == true) - systemPrintf("Error: Unknown L-Band geographic region. Using %s (%dHz)\r\n", - Regional_Information_Table[settings.geographicRegion].name, LBandFreq); + systemPrintf("No fix available for L-Band geographic region determination. Using %s (%dHz)\r\n", + Regional_Information_Table[settings.geographicRegion].name, LBandFreq); } - } - else - { - LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency; - if (settings.debugCorrections == true) - systemPrintf("No fix available for L-Band geographic region determination. Using %s (%dHz)\r\n", - Regional_Information_Table[settings.geographicRegion].name, LBandFreq); - } - bool result = true; + bool result = true; + + GNSS_MOSAIC *mosaic = (GNSS_MOSAIC *)gnss; - // If no SPARTN data is received, the L-Band may need a 'kick'. Turn L-Band off and back on again! - GNSS_MOSAIC *mosaic = (GNSS_MOSAIC *)gnss; - result &= mosaic->sendWithResponse("slsm,off\n\r", "LBandSelectMode"); // Turn L-Band off + result &= mosaic->configureGNSSCOM(true); // Ensure LBandBeam1 is enabled on COM1 - // US SPARTN 1.8 service is on 1556290000 Hz - // EU SPARTN 1.8 service is on 1545260000 Hz - result &= - mosaic->sendWithResponse(String("slbb,User1," + String(LBandFreq) + ",baud2400,PPerfect,EU,Enabled\n\r"), - "LBandBeams"); // Set Freq, baud rate - result &= - mosaic->sendWithResponse("slcs,5555,6959\n\r", "LBandCustomServiceID"); // 21845 = 0x5555; 26969 = 0x6959 - result &= mosaic->sendWithResponse("slsm,manual,Inmarsat,User1,\n\r", - "LBandSelectMode"); // Set L-Band demodulator to manual + result &= mosaic->configureLBand(true, LBandFreq); // Start L-Band - if (result == false) - systemPrintln("mosaic-X5 L-Band failed to configure"); - else if (settings.debugCorrections == true) - systemPrintln("mosaic-X5 L-Band online"); + result &= mosaic->saveConfiguration(); // Save the updated configuration. Probably redundant? - online.lband_gnss = result; + if (result == false) + { + systemPrintln("mosaic-X5 L-Band failed to configure"); + lband_gnss_can_not_begin = true; + } + else + { + if (settings.debugCorrections == true) + systemPrintln("mosaic-X5 L-Band online"); + online.lband_gnss = true; + } + } + //else if (online.lband_gnss && settings.enablePointPerfectCorrections) + { + // If no SPARTN data is received, the L-Band may need a 'kick'. Turn L-Band off and back on again! + // But gnss->update will do this. No need to do it here + } } #endif // /COMPILE_MOSAICX5 } @@ -994,7 +1041,7 @@ void menuPointPerfect() systemPrint(localizedDistributionTileLevelNames[settings.localizedDistributionTileLevel]); systemPrintln(")"); } - if (productVariant != RTK_FACET_MOSAIC) + if (productVariantSupportsAssistNow()) { systemPrint("a) Use AssistNow: "); if (settings.useAssistNow == true) @@ -1045,7 +1092,7 @@ void menuPointPerfect() if (settings.localizedDistributionTileLevel >= LOCALIZED_DISTRIBUTION_TILE_LEVELS) settings.localizedDistributionTileLevel = 0; } - else if (incoming == 'a' && pointPerfectIsEnabled() && (productVariant != RTK_FACET_MOSAIC)) + else if (incoming == 'a' && pointPerfectIsEnabled() && productVariantSupportsAssistNow()) { settings.useAssistNow ^= 1; } @@ -1096,19 +1143,11 @@ bool pointPerfectIsEnabled() } // Process any new L-Band from I2C -void updateLBand() +void updateLBandCorrections() { static unsigned long lbandLastReport; static unsigned long lbandTimeFloatStarted; // Monitors the ZED during L-Band reception if a fix takes too long - // Skip if in configure-via-ethernet mode - if (configureViaEthernet) - { - // if (settings.debugCorrections == true) - // systemPrintln("configureViaEthernet: skipping updateLBand"); - return; - } - #ifdef COMPILE_L_BAND if (online.lbandCorrections == true) { @@ -1232,16 +1271,16 @@ void provisioningSetState(uint8_t newState) unsigned long provisioningStartTime_millis; const unsigned long provisioningTimeout_ms = 120000; -void updateProvisioning() +// Return true if we are in states that require network access +bool provisioningNeedsNetwork() { - // Skip if in configure-via-ethernet mode - if (configureViaEthernet) - { - // if (settings.debugCorrections == true) - // systemPrintln("configureViaEthernet: skipping updateProvisioning"); - return; - } + if (provisioningState >= PROVISIONING_WAIT_FOR_NETWORK && provisioningState <= PROVISIONING_STARTED) + return true; + return false; +} +void provisioningUpdate() +{ DMW_st(provisioningSetState, provisioningState); switch (provisioningState) @@ -1253,17 +1292,15 @@ void updateProvisioning() } break; case PROVISIONING_WAIT_RTC: { - if ((online.rtc) - // If RTC is not online after provisioningTimeout_ms, try to provision anyway - || (millis() > (provisioningStartTime_millis + provisioningTimeout_ms)) || (settings.requestKeyUpdate)) + // 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: { @@ -1298,7 +1335,10 @@ void updateProvisioning() if (settings.debugPpCertificate) systemPrintf("Days until keys expire: %d\r\n", daysRemaining); - if (daysRemaining > 28) + // 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 @@ -1326,14 +1366,14 @@ void updateProvisioning() // Wait for connection to the network case PROVISIONING_WAIT_FOR_NETWORK: { - // Determine if the key update request has been canceled while waiting - if (settings.requestKeyUpdate == false) + // Stop waiting if PointPerfect has been disabled + if (settings.enablePointPerfectCorrections == false) { provisioningSetState(PROVISIONING_NOT_STARTED); } // Wait until the network is available #ifdef COMPILE_NETWORK - else if (networkIsConnected(&pointperfectPriority)) + else if (networkHasInternet()) { if (settings.debugPpCertificate) systemPrintln("PointPerfect key update connected to network"); diff --git a/Firmware/RTK_Everywhere/menuPorts.ino b/Firmware/RTK_Everywhere/menuPorts.ino index 68a93a088..fc415bbcb 100644 --- a/Firmware/RTK_Everywhere/menuPorts.ino +++ b/Firmware/RTK_Everywhere/menuPorts.ino @@ -306,13 +306,18 @@ void menuPortsMultiplexed() restartBase = true; } #endif // COMPILE_MOSAICX5 + + gnss->beginExternalEvent(); // Update with new settings + gnss->beginPPS(); } -// Configure the behavior of the PPS and INT pins on the ZED-F9P -// Most often used for logging events (inputs) and when external triggers (outputs) occur +// Configure the behavior of the PPS and INT pins. +// Most often used for logging events (inputs) and when external triggers (outputs) occur. +// menuPortHardwareTriggers is only called by menuPortsMultiplexed. +// Call gnss->beginExternalEvent() and gnss->beginPPS() in menuPortsMultiplexed, not here +// since menuPortsMultiplexed has control of the multiplexer void menuPortHardwareTriggers() { - bool updateSettings = false; while (1) { systemPrintln(); @@ -364,7 +369,6 @@ void menuPortHardwareTriggers() if (incoming == 1) { settings.enableExternalPulse ^= 1; - updateSettings = true; } else if (incoming == 2 && settings.enableExternalPulse == true) { @@ -382,7 +386,6 @@ void menuPortHardwareTriggers() if (interval >= 1 && interval <= MAX_MOSAIC_PPS_INTERVALS) { settings.externalPulseTimeBetweenPulse_us = mosaicPPSIntervals[interval - 1].interval_us; - updateSettings = true; } } else @@ -402,8 +405,6 @@ void menuPortHardwareTriggers() (settings.externalPulseLength_us / 1000)) // pulseTime must be longer than pulseLength settings.externalPulseLength_us = settings.externalPulseTimeBetweenPulse_us / 2; // Force pulse length to be 1/2 time between pulses - - updateSettings = true; } } } @@ -421,7 +422,6 @@ void menuPortHardwareTriggers() else { settings.externalPulseLength_us = pulseLength * 1000; - updateSettings = true; } } } @@ -431,17 +431,14 @@ void menuPortHardwareTriggers() settings.externalPulsePolarity = PULSE_FALLING_EDGE; else settings.externalPulsePolarity = PULSE_RISING_EDGE; - updateSettings = true; } else if (incoming == 5) { settings.enableExternalHardwareEventLogging ^= 1; - updateSettings = true; } else if ((incoming == 6) && (settings.enableExternalHardwareEventLogging == true) && present.gnss_mosaicX5) { settings.externalEventPolarity ^= 1; - updateSettings = true; } else if (incoming == 'x') break; @@ -454,11 +451,4 @@ void menuPortHardwareTriggers() } clearBuffer(); // Empty buffer of any newline chars - - if (updateSettings) - { - settings.updateGNSSSettings = true; // Force update - gnss->beginExternalEvent(); // Update with new settings - gnss->beginPPS(); - } } diff --git a/Firmware/RTK_Everywhere/menuSystem.ino b/Firmware/RTK_Everywhere/menuSystem.ino index 4bec7b477..870bb0337 100644 --- a/Firmware/RTK_Everywhere/menuSystem.ino +++ b/Firmware/RTK_Everywhere/menuSystem.ino @@ -91,7 +91,7 @@ void menuSystem() else systemPrintln("Offline - "); - if (online.lbandCorrections == true) + if (online.ppl == true) systemPrint("Keys Good"); else systemPrint("No Keys"); @@ -158,10 +158,11 @@ void menuSystem() // Support mode switching systemPrintln("B) Switch to Base mode"); + systemPrintln("C) Switch to Base Caster mode"); if (present.ethernet_ws5500 == true) systemPrintln("N) Switch to NTP Server mode"); systemPrintln("R) Switch to Rover mode"); - systemPrintln("W) Switch to WiFi Config mode"); + systemPrintln("W) Switch to Web Config mode"); if (present.fastPowerOff == true) systemPrintln("S) Shut down"); @@ -199,10 +200,11 @@ void menuSystem() if (present.fuelgauge_max17048 || present.fuelgauge_bq40z50 || present.charger_mp2762a) { - if (settings.shutdownNoChargeTimeout_s == 0) + if (settings.shutdownNoChargeTimeoutMinutes == 0) systemPrintln("c) Shutdown if not charging: Disabled"); else - systemPrintf("c) Shutdown if not charging after: %d seconds\r\n", settings.shutdownNoChargeTimeout_s); + systemPrintf("c) Shutdown if not charging after: %d minutes\r\n", + settings.shutdownNoChargeTimeoutMinutes); } systemPrintln("d) Debug software"); @@ -298,8 +300,8 @@ void menuSystem() else if ((incoming == 'c') && (present.fuelgauge_max17048 || present.fuelgauge_bq40z50 || present.charger_mp2762a)) { - getNewSetting("Enter time in seconds to shutdown unit if not charging (0 to disable)", 0, 60 * 60 * 24 * 7, - &settings.shutdownNoChargeTimeout_s); // Arbitrary 7 day limit + getNewSetting("Enter time in minutes to shutdown unit if not charging (0 to disable)", 0, 60 * 24 * 7, + &settings.shutdownNoChargeTimeoutMinutes); // Arbitrary 7 day limit } else if (incoming == 'd') menuDebugSoftware(); @@ -392,8 +394,14 @@ void menuSystem() else if (incoming == 'B') { forceSystemStateUpdate = true; // Immediately go to this new state + baseCasterDisableOverride(); // Leave Caster mode changeState(STATE_BASE_NOT_STARTED); } + else if (incoming == 'C') + { + forceSystemStateUpdate = true; // Immediately go to this new state + changeState(STATE_BASE_CASTER_NOT_STARTED); + } else if ((incoming == 'N') && (present.ethernet_ws5500 == true)) { forceSystemStateUpdate = true; // Immediately go to this new state @@ -407,7 +415,7 @@ void menuSystem() else if (incoming == 'W') { forceSystemStateUpdate = true; // Immediately go to this new state - changeState(STATE_WIFI_CONFIG_NOT_STARTED); + changeState(STATE_WEB_CONFIG_NOT_STARTED); } else if (incoming == 'S' && present.fastPowerOff == true) { @@ -464,9 +472,6 @@ void menuDebugHardware() systemPrint("5) Print log file status: "); systemPrintf("%s\r\n", settings.enablePrintLogFileStatus ? "Enabled" : "Disabled"); - systemPrint("6) Run Logging Test: "); - systemPrintf("%s\r\n", settings.runLogTest ? "Enabled" : "Disabled"); - systemPrint("7) Print SD and UART buffer sizes: "); systemPrintf("%s\r\n", settings.enablePrintSDBuffers ? "Enabled" : "Disabled"); @@ -485,6 +490,8 @@ void menuDebugHardware() if (present.gnss_um980) systemPrintln("13) UM980 direct connect"); + else if (present.gnss_lg290p) + systemPrintln("13) LG290P reset for firmware update"); systemPrint("14) PSRAM ("); if (ESP.getPsramSize() == 0) @@ -532,15 +539,7 @@ void menuDebugHardware() settings.enablePrintLogFileMessages ^= 1; else if (incoming == 5) settings.enablePrintLogFileStatus ^= 1; - else if (incoming == 6) - { - settings.runLogTest ^= 1; - - logTestState = LOGTEST_START; // Start test - // Mark the current log file as complete to force the test start - startCurrentLogTime_minutes = systemTime_minutes - settings.maxLogLength_minutes; - } else if (incoming == 7) settings.enablePrintSDBuffers ^= 1; else if (incoming == 9) @@ -564,16 +563,28 @@ void menuDebugHardware() { settings.enableImuCompensationDebug ^= 1; } - else if (incoming == 13 && present.gnss_um980) + else if (incoming == 13) { - // Create a file in LittleFS - if (createUm980Passthrough() == true) + if (present.gnss_um980) { - systemPrintln(); - systemPrintln("UM980 passthrough mode has been recorded to LittleFS. Device will now reset."); - systemFlush(); // Complete prints + // Create a file in LittleFS + if (createUm980Passthrough() == true) + { + systemPrintln(); + systemPrintln("UM980 passthrough mode has been recorded to LittleFS. Device will now reset."); + systemFlush(); // Complete prints - ESP.restart(); + ESP.restart(); + } + } + else if (present.gnss_lg290p) + { + systemPrintln(); + systemPrintln("QGNSS must be connected to CH342 Port B at 460800bps. Begin firmware update from QGNSS (hit the play button) then reset the LG290P."); + lg290pReset(); + delay(100); + lg290pBoot(); + systemPrintln("LG290P reset complete."); } } else if (incoming == 14) @@ -653,8 +664,8 @@ void menuDebugNetwork() systemPrintf("%s\r\n", settings.debugWifiState ? "Enabled" : "Disabled"); // WiFi Config - systemPrint("4) Debug Web Config: "); - systemPrintf("%s\r\n", settings.debugWebConfig ? "Enabled" : "Disabled"); + systemPrint("4) Debug Web Server: "); + systemPrintf("%s\r\n", settings.debugWebServer ? "Enabled" : "Disabled"); // Network systemPrint("10) Debug network layer: "); @@ -716,7 +727,7 @@ void menuDebugNetwork() else if (incoming == 3) settings.debugWifiState ^= 1; else if (incoming == 4) - settings.debugWebConfig ^= 1; + settings.debugWebServer ^= 1; else if (incoming == 10) settings.debugNetworkLayer ^= 1; else if (incoming == 11) @@ -1146,11 +1157,14 @@ void menuPeriodicPrint() systemPrint("7) WiFi state: "); systemPrintf("%s\r\n", PERIODIC_SETTING(PD_WIFI_STATE) ? "Enabled" : "Disabled"); - systemPrint("8) ZED RX data: "); - systemPrintf("%s\r\n", PERIODIC_SETTING(PD_ZED_DATA_RX) ? "Enabled" : "Disabled"); + systemPrint("8) GNSS RX data: "); + systemPrintf("%s\r\n", PERIODIC_SETTING(PD_GNSS_DATA_RX) ? "Enabled" : "Disabled"); - systemPrint("9) ZED TX data: "); - systemPrintf("%s\r\n", PERIODIC_SETTING(PD_ZED_DATA_TX) ? "Enabled" : "Disabled"); + systemPrint("9) GNSS TX data: "); + systemPrintf("%s\r\n", PERIODIC_SETTING(PD_GNSS_DATA_TX) ? "Enabled" : "Disabled"); + + systemPrint("10) GNSS RX byte count: "); + systemPrintf("%s\r\n", PERIODIC_SETTING(PD_GNSS_DATA_RX_BYTE_COUNT) ? "Enabled" : "Disabled"); systemPrintln("----- Software -----"); @@ -1270,9 +1284,11 @@ void menuPeriodicPrint() else if (incoming == 7) PERIODIC_TOGGLE(PD_WIFI_STATE); else if (incoming == 8) - PERIODIC_TOGGLE(PD_ZED_DATA_RX); + PERIODIC_TOGGLE(PD_GNSS_DATA_RX); else if (incoming == 9) - PERIODIC_TOGGLE(PD_ZED_DATA_TX); + PERIODIC_TOGGLE(PD_GNSS_DATA_TX); + else if (incoming == 10) + PERIODIC_TOGGLE(PD_GNSS_DATA_RX_BYTE_COUNT); else if (incoming == 20) { @@ -1536,9 +1552,9 @@ void printFileList() ); char fileSizeChar[20]; - String fileSize; - stringHumanReadableSize(fileSize, tempFile.fileSize()); - fileSize.toCharArray(fileSizeChar, sizeof(fileSizeChar)); + String fileSizeStr; + stringHumanReadableSize(fileSizeStr, tempFile.fileSize()); + fileSizeStr.toCharArray(fileSizeChar, sizeof(fileSizeChar)); char fileName[50]; // Handle long file names tempFile.getName(fileName, sizeof(fileName)); diff --git a/Firmware/RTK_Everywhere/settings.h b/Firmware/RTK_Everywhere/settings.h index efd1f3307..ed6ace872 100644 --- a/Firmware/RTK_Everywhere/settings.h +++ b/Firmware/RTK_Everywhere/settings.h @@ -19,15 +19,18 @@ typedef enum 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_WIFI_CONFIG_NOT_STARTED, - STATE_WIFI_CONFIG, + STATE_WEB_CONFIG_NOT_STARTED, + STATE_WEB_CONFIG_WAIT_FOR_NETWORK, + STATE_WEB_CONFIG, STATE_TEST, STATE_TESTING, STATE_PROFILE, @@ -37,10 +40,6 @@ typedef enum STATE_NTPSERVER_NOT_STARTED, STATE_NTPSERVER_NO_SYNC, STATE_NTPSERVER_SYNC, - STATE_CONFIG_VIA_ETH_NOT_STARTED, - STATE_CONFIG_VIA_ETH_STARTED, - STATE_CONFIG_VIA_ETH, - STATE_CONFIG_VIA_ETH_RESTART_BASE, STATE_SHUTDOWN, STATE_NOT_SET, // Must be last on list } SystemState; @@ -52,11 +51,10 @@ 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_ETHERNET_CONFIG 0x0004 -#define RTK_MODE_NTP 0x0008 -#define RTK_MODE_ROVER 0x0010 -#define RTK_MODE_TESTING 0x0020 -#define RTK_MODE_WIFI_CONFIG 0x0040 +#define RTK_MODE_NTP 0x0004 +#define RTK_MODE_ROVER 0x0008 +#define RTK_MODE_TESTING 0x0010 +#define RTK_MODE_WEB_CONFIG 0x0020 typedef uint8_t RtkMode_t; @@ -151,7 +149,7 @@ typedef enum typedef uint8_t CORRECTION_ID_T; // Type holding a correction ID or priority typedef uint8_t CORRECTION_MASK_T; // Type holding a bitmask of correction IDs -const char * const correctionsSourceNames[correctionsSource::CORR_NUM] = +const char * const correctionsSourceNames[CORR_NUM] = { // These must match correctionsSource above "External Radio", @@ -174,7 +172,6 @@ typedef struct uint8_t newProfile; // Only valid when newState == STATE_PROFILE } setupButton; - const SystemState platformPreviousStateTable[] = { STATE_ROVER_NOT_STARTED, // EVK @@ -238,7 +235,6 @@ typedef enum CUSTOM_NMEA_TYPE_SYSTEM_VERSION, CUSTOM_NMEA_TYPE_ZED_VERSION, CUSTOM_NMEA_TYPE_STATUS, - CUSTOM_NMEA_TYPE_LOGTEST_STATUS, CUSTOM_NMEA_TYPE_DEVICE_BT_ID, CUSTOM_NMEA_TYPE_PARSER_STATS, CUSTOM_NMEA_TYPE_CURRENT_DATE, @@ -301,8 +297,8 @@ enum PeriodDisplayValues PD_CELLULAR_STATE, // 28 PD_WIFI_STATE, // 29 - PD_ZED_DATA_RX, // 30 - PD_ZED_DATA_TX, // 31 + PD_GNSS_DATA_RX, // 30 + PD_GNSS_DATA_TX, // 31 PD_MQTT_CLIENT_DATA, // 32 PD_MQTT_CLIENT_STATE, // 33 @@ -314,6 +310,8 @@ enum PeriodDisplayValues PD_PROVISIONING_STATE, // 36 PD_CORRECTION_SOURCE, // 37 + + PD_GNSS_DATA_RX_BYTE_COUNT, // 38 // Add new values before this line }; @@ -359,13 +357,13 @@ typedef struct _NTRIP_SERVER_DATA typedef enum { - ESPNOW_OFF, + ESPNOW_OFF = 0, ESPNOW_BROADCASTING, ESPNOW_PAIRING, ESPNOW_MAC_RECEIVED, ESPNOW_PAIRED, + ESPNOW_MAX } ESPNOWState; -volatile ESPNOWState espnowState = ESPNOW_OFF; const uint8_t ESPNOW_MAX_PEERS = 5; // Maximum of 5 rovers @@ -377,27 +375,6 @@ typedef enum BLUETOOTH_RADIO_OFF, } BluetoothRadioType_e; -// Don't make this a typedef enum as logTestState -// can be incremented beyond LOGTEST_END -enum LogTestState -{ - LOGTEST_START = 0, - LOGTEST_4HZ_5MSG_10MS, - LOGTEST_4HZ_7MSG_10MS, - LOGTEST_10HZ_5MSG_10MS, - LOGTEST_10HZ_7MSG_10MS, - LOGTEST_4HZ_5MSG_0MS, - LOGTEST_4HZ_7MSG_0MS, - LOGTEST_10HZ_5MSG_0MS, - LOGTEST_10HZ_7MSG_0MS, - LOGTEST_4HZ_5MSG_50MS, - LOGTEST_4HZ_7MSG_50MS, - LOGTEST_10HZ_5MSG_50MS, - LOGTEST_10HZ_7MSG_50MS, - LOGTEST_END, -}; -uint8_t logTestState = LOGTEST_END; - typedef struct WiFiNetwork { char ssid[50]; @@ -467,7 +444,6 @@ typedef enum FUNCTION_CREATEFILE, FUNCTION_ENDLOGGING, FUNCTION_FINDLOG, - FUNCTION_LOGTEST, FUNCTION_FILELIST, FUNCTION_FILEMANAGER_OPEN1, FUNCTION_FILEMANAGER_OPEN2, @@ -510,7 +486,6 @@ typedef enum SETTING_KNOWN_STRING, } SettingValueResponse; - #define INCHES_IN_A_METER 39.37007874 #define FEET_IN_A_METER 3.280839895 @@ -540,23 +515,6 @@ const measurementScaleEntry measurementScaleTable[] = { }; const int measurementScaleEntries = sizeof(measurementScaleTable) / sizeof(measurementScaleTable[0]); -#ifdef COMPILE_OTA_AUTO - -// Automatic over-the-air (OTA) firmware update support -enum OtaState -{ - OTA_STATE_OFF = 0, - OTA_STATE_WAIT_FOR_NETWORK, - OTA_STATE_GET_FIRMWARE_VERSION, - OTA_STATE_CHECK_FIRMWARE_VERSION, - OTA_STATE_UPDATE_FIRMWARE, - - // Add new states here - OTA_STATE_MAX -}; - -#endif // COMPILE_OTA_AUTO - // Regional Support // Some regions have both L-Band and IP. More just have IP. // Do we want the user to be able to specify which region they are in? @@ -594,6 +552,33 @@ 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_NTRIP_SERVER, + NETCONSUMER_TCP_CLIENT, + NETCONSUMER_TCP_SERVER, + NETCONSUMER_UDP_SERVER, + NETCONSUMER_PPL_KEY_UPDATE, + NETCONSUMER_PPL_MQTT_CLIENT, + NETCONSUMER_OTA_CLIENT, + NETCONSUMER_WEB_CONFIG, +}; + // 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 @@ -623,7 +608,7 @@ struct Settings // Battery bool enablePrintBatteryMessages = true; - uint32_t shutdownNoChargeTimeout_s = 0; // If > 0, shut down unit after timeout if not charging + uint32_t shutdownNoChargeTimeoutMinutes = 0; // If > 0, shut down unit after timeout if not charging // Beeper bool enableBeeper = true; // Some platforms have an audible notification @@ -635,7 +620,7 @@ struct Settings // Corrections int correctionsSourcesLifetime_s = 30; // Expire a corrections source if no data is seen for this many seconds - CORRECTION_ID_T correctionsSourcesPriority[correctionsSource::CORR_NUM] = { (CORRECTION_ID_T)-1 }; // -1 indicates array is uninitialized, indexed by correction source ID + CORRECTION_ID_T correctionsSourcesPriority[CORR_NUM] = { (CORRECTION_ID_T)-1 }; // -1 indicates array is uninitialized, indexed by correction source ID bool debugCorrections = false; uint8_t enableExtCorrRadio = 254; // Will be initialized to true or false depending on model uint8_t extCorrRadioSPARTNSource = 0; // This selects IP (0) vs. L-Band (1) for _SPARTN_ corrections on Radio Ext (UART2) @@ -669,7 +654,14 @@ struct Settings uint16_t measurementRateMs = 250; // Elapsed ms between GNSS measurements. 25ms to 65535ms. Default 4Hz. uint16_t navigationRate = 1; // Ratio between number of measurements and navigation solutions. Default 1 for 4Hz (with measurementRate). - bool updateGNSSSettings = true; // When in doubt, update the GNSS with current 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 + bool gnssConfiguredOnce = false; + bool gnssConfiguredBase = false; + bool gnssConfiguredRover = false; // GNSS UART uint16_t serialGNSSRxFullThreshold = 50; // RX FIFO full interrupt. Max of ~128. See pinUART2Task(). @@ -689,7 +681,6 @@ struct Settings bool enablePrintLogFileStatus = true; int maxLogLength_minutes = 60 * 24; // Default to 24 hours int maxLogTime_minutes = 60 * 24; // Default to 24 hours - bool runLogTest = false; // When set to true, device will create a series of test logs // MQTT bool debugMqttClientData = false; // Debug the MQTT SPARTAN data flow @@ -793,7 +784,7 @@ struct Settings uint8_t gnssUartInterruptsCore = 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino uint8_t handleGnssDataTaskCore = 1; // Core where task should run, 0=core, 1=Arduino - uint8_t handleGnssDataTaskPriority = 1; // Read from the cicular buffer and dole out to end points (SD, TCP, BT). + uint8_t handleGnssDataTaskPriority = 1; // Read from the circular buffer and dole out to end points (SD, TCP, BT). uint8_t i2cInterruptsCore = 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino uint8_t measurementScale = MEASUREMENT_UNITS_METERS; bool printBootTimes = false; // Print times and deltas during boot @@ -976,7 +967,7 @@ struct Settings uint16_t httpPort = 80; // WiFi - bool debugWebConfig = false; + bool debugWebServer = false; bool debugWifiState = false; bool enableCaptivePortal = true; uint8_t wifiChannel = 1; //Valid channels are 1 to 14 @@ -1013,9 +1004,12 @@ 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. #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. // Add new settings to appropriate group above or create new group // Then also add to the same group in rtkSettingsEntries below @@ -1079,6 +1073,7 @@ typedef enum { tLgMRNmea, tLgMRRvRT, tLgMRBaRT, + tLgMRPqtm, tLgConst, // Add new settings types above <----------------> // (Maintain the enum of existing settings types!) @@ -1160,11 +1155,11 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 1, 1, 0, 1, 1, 1, 1, 1, 1, _double, 9, & settings.fixedLong, "fixedLong", }, { 1, 1, 0, 1, 1, 1, 1, 1, 1, _int, 0, & settings.observationSeconds, "observationSeconds", }, { 1, 1, 0, 1, 1, 1, 1, 1, 1, _float, 2, & settings.observationPositionAccuracy, "observationPositionAccuracy", }, - { 1, 1, 0, 1, 1, 0, 1, 1, 1, _float, 1, & settings.surveyInStartingAccuracy, "surveyInStartingAccuracy", }, + { 0, 1, 0, 1, 1, 0, 1, 1, 1, _float, 1, & settings.surveyInStartingAccuracy, "surveyInStartingAccuracy", }, // Battery { 0, 0, 0, 0, 1, 1, 1, 1, 1, _bool, 0, & settings.enablePrintBatteryMessages, "enablePrintBatteryMessages", }, - { 1, 1, 0, 0, 1, 1, 1, 1, 1, _uint32_t, 0, & settings.shutdownNoChargeTimeout_s, "shutdownNoChargeTimeout", }, + { 1, 1, 0, 0, 1, 1, 1, 1, 1, _uint32_t, 0, & settings.shutdownNoChargeTimeoutMinutes, "shutdownNoChargeTimeoutMinutes", }, // F // a @@ -1191,7 +1186,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = // Corrections { 1, 1, 0, 1, 1, 1, 1, 1, 1, _int, 0, & settings.correctionsSourcesLifetime_s, "correctionsSourcesLifetime", }, - { 1, 1, 1, 1, 1, 1, 1, 1, 1, tCorrSPri, correctionsSource::CORR_NUM, & settings.correctionsSourcesPriority, "correctionsPriority_", }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, tCorrSPri, CORR_NUM, & settings.correctionsSourcesPriority, "correctionsPriority_", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugCorrections, "debugCorrections", }, { 1, 1, 0, 1, 1, 1, 0, 1, 1, _bool, 0, & settings.enableExtCorrRadio, "enableExtCorrRadio", }, { 1, 1, 0, 1, 1, 0, 0, 1, 0, _uint8_t, 0, & settings.extCorrRadioSPARTNSource, "extCorrRadioSPARTNSource", }, @@ -1260,7 +1255,9 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enablePrintPosition, "enablePrintPosition", }, { 0, 1, 0, 1, 1, 1, 1, 1, 1, _uint16_t, 0, & settings.measurementRateMs, "measurementRateMs", }, { 0, 1, 0, 1, 1, 1, 1, 1, 1, _uint16_t, 0, & settings.navigationRate, "navigationRate", }, - { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.updateGNSSSettings, "updateGNSSSettings", }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.gnssConfiguredOnce, "gnssConfiguredOnce", }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.gnssConfiguredBase, "gnssConfiguredBase", }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.gnssConfiguredRover, "gnssConfiguredRover", }, // Hardware { 1, 1, 0, 1, 1, 1, 0, 1, 0, _bool, 0, & settings.enableExternalHardwareEventLogging, "enableExternalHardwareEventLogging", }, @@ -1291,18 +1288,17 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 0, 0, 1, 1, 1, 0, 1, 1, _bool, 0, & settings.enablePrintLogFileStatus, "enablePrintLogFileStatus", }, { 1, 1, 0, 1, 1, 1, 0, 1, 1, _int, 0, & settings.maxLogLength_minutes, "maxLogLength", }, { 1, 1, 0, 1, 1, 1, 0, 1, 1, _int, 0, & settings.maxLogTime_minutes, "maxLogTime"}, - { 0, 0, 0, 1, 1, 0, 0, 1, 1, _bool, 0, & settings.runLogTest, "runLogTest", }, // Not stored in NVM // Mosaic #ifdef COMPILE_MOSAICX5 { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicConst, MAX_MOSAIC_CONSTELLATIONS, & settings.mosaicConstellations, "constellation_", }, - { 0, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMSNmea, MAX_MOSAIC_NMEA_MSG, & settings.mosaicMessageStreamNMEA, "messageStreamNMEA_", }, - { 0, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicSINmea, MOSAIC_NUM_NMEA_STREAMS, & settings.mosaicStreamIntervalsNMEA, "streamIntervalNMEA_", }, - { 0, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMIRvRT, MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS, & settings.mosaicMessageIntervalsRTCMv3Rover, "messageIntervalRTCMRover_", }, - { 0, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMIBaRT, MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS, & settings.mosaicMessageIntervalsRTCMv3Base, "messageIntervalRTCMBase_", }, - { 0, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMERvRT, MAX_MOSAIC_RTCM_V3_MSG, & settings.mosaicMessageEnabledRTCMv3Rover, "messageEnabledRTCMRover_", }, - { 0, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMEBaRT, MAX_MOSAIC_RTCM_V3_MSG, & settings.mosaicMessageEnabledRTCMv3Base, "messageEnabledRTCMBase_", }, + { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMSNmea, MAX_MOSAIC_NMEA_MSG, & settings.mosaicMessageStreamNMEA, "messageStreamNMEA_", }, + { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicSINmea, MOSAIC_NUM_NMEA_STREAMS, & settings.mosaicStreamIntervalsNMEA, "streamIntervalNMEA_", }, + { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMIRvRT, MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS, & settings.mosaicMessageIntervalsRTCMv3Rover, "messageIntervalRTCMRover_", }, + { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMIBaRT, MAX_MOSAIC_RTCM_V3_INTERVAL_GROUPS, & settings.mosaicMessageIntervalsRTCMv3Base, "messageIntervalRTCMBase_", }, + { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMERvRT, MAX_MOSAIC_RTCM_V3_MSG, & settings.mosaicMessageEnabledRTCMv3Rover, "messageEnabledRTCMRover_", }, + { 1, 1, 1, 0, 0, 1, 0, 0, 0, tMosaicMEBaRT, MAX_MOSAIC_RTCM_V3_MSG, & settings.mosaicMessageEnabledRTCMv3Base, "messageEnabledRTCMBase_", }, { 1, 1, 0, 0, 0, 1, 0, 0, 0, _bool, 0, & settings.enableLoggingRINEX, "enableLoggingRINEX", }, { 1, 1, 0, 0, 0, 1, 0, 0, 0, _uint8_t, 0, & settings.RINEXFileDuration, "RINEXFileDuration", }, { 1, 1, 0, 0, 0, 1, 0, 0, 0, _uint8_t, 0, & settings.RINEXObsInterval, "RINEXObsInterval", }, @@ -1315,7 +1311,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = // Multicast DNS { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.mdnsEnable, "mdnsEnable", }, - { 1, 1, 0, 1, 1, 1, 1, 1, 1, tCharArry, sizeof(settings.mdnsHostName), & settings.mdnsHostName, "mdnsHostName", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, tCharArry, sizeof(settings.mdnsHostName), & settings.mdnsHostName, "mdnsHostName", }, // Network layer { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugNetworkLayer, "debugNetworkLayer", }, @@ -1338,7 +1334,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = // NTP (Ethernet Only) { 0, 0, 0, 1, 0, 0, 0, 0, 0, _bool, 0, & settings.debugNtp, "debugNtp", }, - { 1, 1, 0, 1, 0, 0, 0, 0, 0, _bool, 0, & settings.enableNTPFile, "enableNTPFile", }, + { 0, 1, 0, 1, 0, 0, 0, 0, 0, _bool, 0, & settings.enableNTPFile, "enableNTPFile", }, { 1, 1, 0, 1, 0, 0, 0, 0, 0, _uint16_t, 0, & settings.ethernetNtpPort, "ethernetNtpPort", }, { 1, 1, 0, 1, 0, 0, 0, 0, 0, _uint8_t, 0, & settings.ntpPollExponent, "ntpPollExponent", }, { 1, 1, 0, 1, 0, 0, 0, 0, 0, _int8_t, 0, & settings.ntpPrecision, "ntpPrecision", }, @@ -1497,7 +1493,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = // g s x k 2 c h d d Type Qual Variable Name // Setup Button - { 1, 1, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.disableSetupButton, "disableSetupButton", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.disableSetupButton, "disableSetupButton", }, // State { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enablePrintDuplicateStates, "enablePrintDuplicateStates", }, @@ -1516,9 +1512,9 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 1, 1, 0, 1, 1, 1, 1, 1, 1, _uint16_t, 0, & settings.tcpServerPort, "tcpServerPort", }, // Time Zone - { 1, 1, 0, 1, 1, 1, 1, 1, 1, _int8_t, 0, & settings.timeZoneHours, "timeZoneHours", }, - { 1, 1, 0, 1, 1, 1, 1, 1, 1, _int8_t, 0, & settings.timeZoneMinutes, "timeZoneMinutes", }, - { 1, 1, 0, 1, 1, 1, 1, 1, 1, _int8_t, 0, & settings.timeZoneSeconds, "timeZoneSeconds", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _int8_t, 0, & settings.timeZoneHours, "timeZoneHours", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _int8_t, 0, & settings.timeZoneMinutes, "timeZoneMinutes", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _int8_t, 0, & settings.timeZoneSeconds, "timeZoneSeconds", }, // F // a @@ -1578,7 +1574,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint16_t, 0, & settings.httpPort, "httpPort", }, // WiFi - { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugWebConfig, "debugWebConfig", }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugWebServer, "debugWebServer", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugWifiState, "debugWifiState", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enableCaptivePortal, "enableCaptivePortal", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.wifiChannel, "wifiChannel", }, @@ -1621,9 +1617,12 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 1, 1, 0, 0, 0, 0, 0, 1, tLgMRNmea, MAX_LG290P_NMEA_MSG, & settings.lg290pMessageRatesNMEA, "messageRateNMEA_", }, { 0, 1, 1, 0, 0, 0, 0, 0, 1, tLgMRBaRT, MAX_LG290P_RTCM_MSG, & settings.lg290pMessageRatesRTCMBase, "messageRateRTCMBase_", }, { 0, 1, 1, 0, 0, 0, 0, 0, 1, tLgMRRvRT, MAX_LG290P_RTCM_MSG, & settings.lg290pMessageRatesRTCMRover, "messageRateRTCMRover_", }, + { 0, 1, 1, 0, 0, 0, 0, 0, 1, tLgMRPqtm, MAX_LG290P_PQTM_MSG, & settings.lg290pMessageRatesPQTM, "messageRatePQTM_", }, #endif // COMPILE_LG290P { 0, 0, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.debugSettings, "debugSettings", }, + { 1, 1, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.enableNtripCaster, "enableNtripCaster", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.baseCasterOverride, "baseCasterOverride", }, // Add new settings to appropriate group above or create new group // Then also add to the same group in settings above @@ -1760,34 +1759,35 @@ struct struct_present // Monitor which devices on the device are on or offline. struct struct_online { - bool microSD = false; + bool batteryCharger_mp2762a = false; + bool batteryFuelGauge = false; + bool bluetooth = false; + bool button = false; bool display = false; + bool ethernetNTPServer = false; // EthernetUDP + bool fs = false; bool gnss = false; + bool gpioExpander = false; + bool httpClient = false; + bool i2c = false; + bool lband_gnss = false; + bool lband_neo = false; + bool lbandCorrections = false; bool logging = false; - bool serialOutput = false; - bool fs = false; - bool rtc = false; - bool batteryFuelGauge = false; + bool loraRadio = false; + bool microSD = false; + bool mqttClient = false; bool ntripClient = false; bool ntripServer[NTRIP_SERVER_MAX] = {false, false, false, false}; - bool lband_neo = false; - bool lband_gnss = false; - bool lbandCorrections = false; - bool i2c = false; + bool otaClient = false; + bool ppl = false; + bool psram = false; + bool rtc = false; + bool serialOutput = false; bool tcpClient = false; bool tcpServer = false; bool udpServer = false; - bool ethernetNTPServer = false; // EthernetUDP - bool otaFirmwareUpdate = false; - bool bluetooth = false; - bool mqttClient = false; - bool psram = false; - bool ppl = false; - bool batteryCharger_mp2762a = false; - bool httpClient = false; - bool loraRadio = false; - bool button = false; - bool gpioExpander = false; + bool webServer = false; } online; typedef uint8_t NetIndex_t; // Index into the networkInterfaceTable @@ -1799,15 +1799,9 @@ typedef int8_t NetPriority_t; // Index into networkPriorityTable enum NetworkTypes { NETWORK_NONE = -1, // The values below must start at zero and be sequential - #ifdef COMPILE_ETHERNET - NETWORK_ETHERNET, - #endif // COMPILE_ETHERNET - #ifdef COMPILE_WIFI - NETWORK_WIFI = 1, - #endif // COMPILE_WIFI - #ifdef COMPILE_CELLULAR - NETWORK_CELLULAR, - #endif // COMPILE_CELLULAR + NETWORK_ETHERNET = 0, + NETWORK_WIFI = 1, + NETWORK_CELLULAR = 2, // Add new networks here NETWORK_MAX }; @@ -1821,7 +1815,7 @@ enum NetworkTypes typedef void (* NETWORK_POLL_ROUTINE)(NetIndex_t index, uintptr_t parameter, bool debug); // Sequence entry specifying a poll routine call for a network interface -typedef struct _NETWORK_POLL_SEQUENCE +typedef const struct _NETWORK_POLL_SEQUENCE { NETWORK_POLL_ROUTINE routine; // Address of poll routine, nullptr at end of table uintptr_t parameter; // Parameter passed to poll routine @@ -1848,21 +1842,28 @@ extern NETWORK_POLL_SEQUENCE laraOffSequence[]; extern NETWORK_POLL_SEQUENCE laraOnSequence[]; // List of networks -// Multiple networks may running in parallel with highest priority being -// set to the default network. 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 +// 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 #ifdef COMPILE_ETHERNET {Ð, "Ethernet", &present.ethernet_ws5500, PD_ETHERNET_STATE, nullptr, nullptr, nullptr}, + #else + {nullptr, "Ethernet-NotCompiled", nullptr, PD_ETHERNET_STATE, nullptr, nullptr, nullptr}, #endif // COMPILE_ETHERNET + #ifdef COMPILE_WIFI {&WiFi.STA, "WiFi", nullptr, PD_WIFI_STATE, nullptr, wifiStartSequence, wifiStopSequence}, + #else + {nullptr, "WiFi-NotCompiled", nullptr, PD_WIFI_STATE, nullptr, nullptr, nullptr}, #endif // COMPILE_WIFI + #ifdef COMPILE_CELLULAR {&PPP, "Cellular", &present.cellular_lara, PD_CELLULAR_STATE, laraBootSequence, laraOnSequence, laraOffSequence}, + #else + {nullptr, "Cellular-NotCompiled", nullptr, PD_CELLULAR_STATE, nullptr, nullptr, nullptr}, #endif // COMPILE_CELLULAR }; const int networkInterfaceTableEntries = sizeof(networkInterfaceTable) / sizeof(networkInterfaceTable[0]); @@ -1923,5 +1924,333 @@ o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU rqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- )====="; + +#ifdef COMPILE_WIFI + +//**************************************** +// 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 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 + 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 + IPAddress _apGatewayAddress;// IP address of the gateway to the larger network (internet?) + 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 + 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 + IPAddress _staIpAddress; // IP address of the station + uint8_t _staIpType; // 4 or 6 when IP address is assigned + volatile uint8_t _staMacAddress[6]; // MAC address of the station + 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 + bool _usingDefaultChannel; // Using default WiFi channel + bool _verbose; // True causes more debug output to be displayed + + // Display components begin started or stopped + // Inputs: + // text: Text describing the component list + // components: A bit mask of the components + void displayComponents(const char * text, WIFI_ACTION_t components); + + // Start the WiFi event handler + void eventHandlerStart(); + + // Stop the WiFi event handler + void eventHandlerStop(); + + // Set the WiFi mode + // Inputs: + // setMode: Modes to set + // xorMode: Modes to toggle + // + // Math: result = (mode | setMode) ^ xorMode + // + // setMode + // 0 1 + // xorMode 0 No change Set bit + // 1 Toggle bit Clear bit + // + // Outputs: + // Returns true if successful and false upon failure + bool setWiFiMode(uint8_t setMode, uint8_t xorMode); + + // Set the WiFi radio protocols + // Inputs: + // interface: Interface on which to set the protocols + // enableWiFiProtocols: When true, enable the WiFi protocols + // enableLongRangeProtocol: When true, enable the long range protocol + // Outputs: + // Returns true if successful and false upon failure + bool setWiFiProtocols(wifi_interface_t interface, + bool enableWiFiProtocols, + bool enableLongRangeProtocol); + + // Handle the soft AP events + // 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 softApEventHandler(arduino_event_id_t event, arduino_event_info_t info); + + // Set the soft AP host name + // Inputs: + // hostName: Zero terminated host name character string + // Outputs: + // Returns true if successful and false upon failure + bool softApSetHostName(const char * hostName); + + // Set the soft AP configuration + // Inputs: + // ipAddress: IP address of the server, nullptr or empty string causes + // default 192.168.4.1 to be used + // subnetMask: Subnet mask for local network segment, nullptr or empty + // string causes default 0.0.0.0 to be used, unless ipAddress + // is not specified, in which case 255.255.255.0 is used + // gatewayAddress: Gateway to internet IP address, nullptr or empty string + // causes default 0.0.0.0 to be used (no access to internet) + // dnsAddress: Domain name server (name to IP address translation) IP address, + // nullptr or empty string causes 0.0.0.0 to be used (only + // mDNS name translation, if started) + // dhcpStartAddress: Start of DHCP IP address assignments for the local + // network segment, nullptr or empty string causes default + // 0.0.0.0 to be used (disable DHCP server) unless ipAddress + // was not specified in which case 192.168.4.2 + // Outputs: + // Returns true if successful and false upon failure + bool softApSetIpAddress(const char * ipAddress, + const char * subnetMask, + const char * gatewayAddress, + const char * dnsAddress, + const char * dhcpFirstAddress); + + // Set the soft AP SSID and password + // Outputs: + // Returns true if successful and false upon failure + bool softApSetSsidPassword(const char * ssid, const char * password); + + // Connect to an access point + // Outputs: + // Return true if the connection was successful and false upon failure. + bool stationConnectAP(); + + // Disconnect the station from an AP + // Outputs: + // Returns true if successful and false upon failure + bool stationDisconnect(); + + // Handle the WiFi station events + // 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 stationEventHandler(arduino_event_id_t event, arduino_event_info_t info); + + // Set the station's host name + // Inputs: + // hostName: Zero terminated host name character string + // Outputs: + // Returns true if successful and false upon failure + bool stationHostName(const char * hostName); + + // Start the WiFi scan + // Inputs: + // channel: Channel number for the scan, zero (0) scan all channels + // Outputs: + // Returns the number of access points + int16_t stationScanForAPs(WIFI_CHANNEL_t channel); + + // Select the AP and channel to use for WiFi station + // Inputs: + // apCount: Number to APs detected by the WiFi scan + // list: Determine if the APs should be listed + // Outputs: + // 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 + // https://github.com/espressif/arduino-esp32 + // in libraries/Network/src/NetworkEvents.h + // info: Additional data about the event + void wifiEvent(arduino_event_id_t event, arduino_event_info_t info); + + public: + + // Constructor + // Inputs: + // verbose: Set to true to display additional WiFi debug data + RTK_WIFI(bool verbose = false); + + // Attempts a connection to all provided SSIDs + // Inputs: + // timeout: Number of milliseconds to wait for the connection + // startAP: Set true to start AP mode, false does not change soft AP + // status + // Outputs: + // Returns true if successful and false upon timeout, no matching + // SSID or other failure + bool connect(unsigned long timeout, + bool startAP); + + // Enable or disable the WiFi modes + // Inputs: + // enableESPNow: Enable ESP-NOW mode + // enableSoftAP: Enable soft AP mode + // enableStataion: Enable station mode + // Outputs: + // Returns true if the modes were successfully configured + bool enable(bool enableESPNow, bool enableSoftAP, bool enableStation); + + // 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 + void espNowSetChannel(WIFI_CHANNEL_t channel); + + // 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 eventHandler(arduino_event_id_t event, arduino_event_info_t info); + + // Get the current WiFi channel + // Outputs: + // 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 + // subnetMask: Subnet mask for the soft AP network + // firstDhcpAddress: First IP address to use in the DHCP range + // dnsAddress: IP address to use for DNS lookup (translate name to IP address) + // gatewayAddress: IP address of the gateway to a larger network (internet?) + // Outputs: + // Returns true if the soft AP was successfully configured. + bool softApConfiguration(IPAddress ipAddress, + IPAddress subnetMask, + IPAddress firstDhcpAddress, + IPAddress dnsAddress, + IPAddress gateway); + + // Display the soft AP configuration + // Inputs: + // display: Address of a Print object + void softApConfigurationDisplay(Print * display); + + // 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 + // soft AP if settings.wifiConfigOverAP is true + // Outputs: + // Returns true if the soft AP was started successfully and false + // otherwise + bool startAp(bool forceAP); + + // 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 station status + // Outputs: + // Returns true if the WiFi station is being started or is online + bool stationRunning(); + + // Test the WiFi modes + // Inputs: + // testDurationMsec: Milliseconds to run each test + void test(uint32_t testDurationMsec); + + // Enable or disable verbose debug output + // Inputs: + // enable: Set true to enable verbose debug output + // Outputs: + // Return the previous enable value + bool verbose(bool enable); + + // Verify the WiFi tables + void verifyTables(); +}; + +#endif // COMPILE_WIFI #endif // COMPILE_NETWORK #endif // __SETTINGS_H__ diff --git a/Firmware/RTK_Everywhere/src/BluetoothSerial/BluetoothSerial.cpp b/Firmware/RTK_Everywhere/src/BluetoothSerial/BluetoothSerial.cpp index aed24de27..e941a8ca7 100644 --- a/Firmware/RTK_Everywhere/src/BluetoothSerial/BluetoothSerial.cpp +++ b/Firmware/RTK_Everywhere/src/BluetoothSerial/BluetoothSerial.cpp @@ -789,10 +789,12 @@ static bool waitForConnect(int timeout) { if ((rc & SPP_CONNECTED) != 0) { return true; } else if ((rc & SPP_CLOSED) != 0) { - log_d("connection closed!"); + if (timeout > 0) + log_d("connection closed!"); return false; } - log_d("timeout"); + if (timeout > 0) + log_d("timeout"); return false; } @@ -881,7 +883,7 @@ size_t BluetoothSerial::write(const uint8_t *buffer, size_t size) { void BluetoothSerial::flush() { if (_spp_tx_queue != NULL) { while (uxQueueMessagesWaiting(_spp_tx_queue) > 0) { - delay(100); + delay(2); // https://github.com/espressif/arduino-esp32/pull/9905 } } } diff --git a/Firmware/RTK_Everywhere/support.ino b/Firmware/RTK_Everywhere/support.ino index e5cecb3dc..bb6429aa8 100644 --- a/Firmware/RTK_Everywhere/support.ino +++ b/Firmware/RTK_Everywhere/support.ino @@ -721,8 +721,9 @@ void verifyTables() provisioningVerifyTables(); mosaicVerifyTables(); correctionVerifyTables(); + webServerVerifyTables(); - if (correctionsSource::CORR_NUM >= (int)('x' - 'a')) + if (CORR_NUM >= (int)('x' - 'a')) reportFatalError("Too many correction sources"); } diff --git a/Firmware/Test Sketches/Hookup_Display/Display.ino b/Firmware/Test Sketches/Hookup_Display/Display.ino index 4b0344604..4fb46fdc2 100644 --- a/Firmware/Test Sketches/Hookup_Display/Display.ino +++ b/Firmware/Test Sketches/Hookup_Display/Display.ino @@ -1566,7 +1566,7 @@ void displayWiFiConfig() } //When user does a factory reset, let us know -void displaySytemReset() +void displaySystemReset() { displayMessage("Factory Reset", 0); }