diff --git a/.github/workflows/compile-rtk-firmware.yml b/.github/workflows/compile-rtk-firmware.yml
new file mode 100644
index 000000000..91b0a89e3
--- /dev/null
+++ b/.github/workflows/compile-rtk-firmware.yml
@@ -0,0 +1,187 @@
+name: Build RTK Firmware
+on:
+ workflow_dispatch:
+ branches:
+
+env:
+ FILENAME_PREFIX: RTK_Surveyor_Firmware
+ FIRMWARE_VERSION_MAJOR: 4
+ FIRMWARE_VERSION_MINOR: 2
+ POINTPERFECT_TOKEN: ${{ secrets.POINTPERFECT_TOKEN }}
+
+jobs:
+ build:
+
+ name: Build
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@master
+
+ - name: Get current date
+ id: date
+ run: echo "date=$(date +'%b_%d_%Y')" >> $GITHUB_OUTPUT
+
+ - name: Get current date
+ id: dateNoScores
+ run: echo "dateNoScores=$(date +'%b %d %Y')" >> $GITHUB_OUTPUT
+
+ - name: Extract branch name
+ run: echo "BRANCH=${{github.ref_name}}" >> $GITHUB_ENV
+
+ #File_Name_v3_1.bin
+ #File_Name_RC-Jan_26_2023.bin
+ - name: Create file ending and compiler flags based on branch
+ run: |
+ if [[ $BRANCH == 'main' ]]; then
+ echo "FILE_ENDING_UNDERSCORE=_v${{ env.FIRMWARE_VERSION_MAJOR }}_${{ env.FIRMWARE_VERSION_MINOR }}" >> "$GITHUB_ENV"
+ echo "FILE_ENDING_NOUNDERSCORE=_v${{ env.FIRMWARE_VERSION_MAJOR }}.${{ env.FIRMWARE_VERSION_MINOR }}" >> "$GITHUB_ENV"
+ echo "JSON_ENDING=" >> "$GITHUB_ENV"
+ echo "JSON_FILE_NAME=RTK-Firmware.json" >> "$GITHUB_ENV"
+ echo "ENABLE_DEVELOPER=false" >> "$GITHUB_ENV"
+ echo "DEBUG_LEVEL=none" >> "$GITHUB_ENV"
+ else
+ echo "FILE_ENDING_UNDERSCORE=_RC-${{ steps.date.outputs.date }}" >> "$GITHUB_ENV"
+ echo "FILE_ENDING_NOUNDERSCORE=_RC-${{ steps.dateNoScores.outputs.dateNoScores }}" >> "$GITHUB_ENV"
+ echo "JSON_ENDING=-${{ steps.dateNoScores.outputs.dateNoScores }}" >> "$GITHUB_ENV"
+ echo "JSON_FILE_NAME=RTK-RC-Firmware.json" >> "$GITHUB_ENV"
+ echo "ENABLE_DEVELOPER=true" >> "$GITHUB_ENV"
+ echo "DEBUG_LEVEL=debug" >> "$GITHUB_ENV"
+ fi
+
+ - name: Setup Arduino CLI
+ uses: arduino/setup-arduino-cli@v1
+
+ - name: Start config file
+ run: arduino-cli config init --additional-urls "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json"
+
+ - name: Update index
+ run: arduino-cli core update-index
+
+ #We limit the ESP32 core to v2.0.2
+ - name: Install platform
+ run: arduino-cli core install esp32:esp32@2.0.2
+
+ - name: Get Known Libraries
+ run: arduino-cli lib install
+ ArduinoJson@6.19.4
+ ESP32Time@2.0.0
+ ESP32_BleSerial@1.0.5
+ "ESP32-OTA-Pull"@1.0.0
+ Ethernet@2.0.2
+ JC_Button@2.1.2
+ PubSubClient@2.8.0
+ "SdFat"@2.1.1
+ "SparkFun LIS2DH12 Arduino Library"@1.0.3
+ "SparkFun MAX1704x Fuel Gauge Arduino Library"@1.0.4
+ "SparkFun u-blox GNSS v3"@3.0.14
+ SparkFun_WebServer_ESP32_W5500@1.5.5
+ SSLClientESP32@2.0.0
+
+ - name: Enable external libs
+ run: arduino-cli config set library.enable_unsafe_install true
+
+ - name: Get Libraries
+ run: arduino-cli lib install --git-url
+ https://github.com/sparkfun/SparkFun_Qwiic_OLED_Arduino_Library.git
+ https://github.com/me-no-dev/ESPAsyncWebServer.git
+ https://github.com/me-no-dev/AsyncTCP.git
+
+ #Incorporate ESP-Now patch into core: https://github.com/espressif/arduino-esp32/pull/7044/files
+ #- name: Patch ESP32 Core
+ # run: |
+ # cd Firmware/RTK_Surveyor/Patch/
+ # cp WiFiGeneric.cpp /home/runner/.arduino15/packages/esp32/hardware/esp32/2.0.2/libraries/WiFi/src/WiFiGeneric.cpp
+
+ #Patch Server.h to avoid https://github.com/arduino-libraries/Ethernet/issues/88#issuecomment-455498941
+ #Note: this patch can be removed if/when we upgrade to ESP32 core >= v2.0.6
+ - name: Patch ESP32 Server.h for Ethernet
+ run: |
+ cd Firmware/RTK_Surveyor/Patch/
+ cp Server.h /home/runner/.arduino15/packages/esp32/hardware/esp32/2.0.2/cores/esp32/Server.h
+
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+
+ # Configure Python - now we have Python installed, we need to provide everything needed by esptool otherwise the compile fails
+ - name: Configure Python
+ run: |
+ pip3 install pyserial
+
+ - name: Update index_html
+ run: |
+ cd Firmware/Tools
+ python index_html_zipper.py ../RTK_Surveyor/AP-Config/index.html ../RTK_Surveyor/form.h
+
+ - name: Update main_js
+ run: |
+ cd Firmware/Tools
+ python main_js_zipper.py ../RTK_Surveyor/AP-Config/src/main.js ../RTK_Surveyor/form.h
+
+ - name: Commit and push form.h
+ uses: actions-js/push@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ directory: ./Firmware/RTK_Surveyor
+ branch: ${{ env.BRANCH }}
+ message: 'Update form.h via Python'
+
+ - name: Copy custom app3M_fat9M_16MB.csv
+ run:
+ cp Firmware/app3M_fat9M_16MB.csv /home/runner/.arduino15/packages/esp32/hardware/esp32/2.0.2/tools/partitions/app3M_fat9M_16MB.csv
+
+ - name: Compile Sketch
+ run: arduino-cli compile --fqbn "esp32:esp32:esp32":DebugLevel=${{ env.DEBUG_LEVEL }} ./Firmware/RTK_Surveyor/RTK_Surveyor.ino
+ --build-property build.partitions=app3M_fat9M_16MB
+ --build-property upload.maximum_size=3145728
+ --build-property "compiler.cpp.extra_flags=\"-DPOINTPERFECT_TOKEN=$POINTPERFECT_TOKEN\" \"-DFIRMWARE_VERSION_MAJOR=$FIRMWARE_VERSION_MAJOR\" \"-DFIRMWARE_VERSION_MINOR=$FIRMWARE_VERSION_MINOR\" \"-DENABLE_DEVELOPER=${{ env.ENABLE_DEVELOPER }}\""
+ --export-binaries
+
+ - name: Rename binary
+ run: |
+ cd Firmware/RTK_Surveyor/build/esp32.esp32.esp32/
+ mv RTK_Surveyor.ino.bin ${{ env.FILENAME_PREFIX }}${{ env.FILE_ENDING_UNDERSCORE }}.bin
+
+ - name: Upload binary to action
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ env.FILENAME_PREFIX }}${{ env.FILE_ENDING_UNDERSCORE }}
+ path: ./Firmware/RTK_Surveyor/build/esp32.esp32.esp32/${{ env.FILENAME_PREFIX }}${{ env.FILE_ENDING_UNDERSCORE }}.bin
+
+
+ - name: Push binary to Binaries Repo
+ # uses: dmnemec/copy_file_to_another_repo_action #Workaround for Issue: https://github.com/orgs/community/discussions/55820#discussioncomment-5946136
+ uses: Jason2866/copy_file_to_another_repo_action@http408_fix
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.API_GITHUB_RTK_FILE_TOKEN }}
+ with:
+ source_file: ./Firmware/RTK_Surveyor/build/esp32.esp32.esp32/${{ env.FILENAME_PREFIX }}${{ env.FILE_ENDING_UNDERSCORE }}.bin
+ destination_repo: 'sparkfun/SparkFun_RTK_Firmware_Binaries'
+ destination_folder: ''
+ user_email: 'nathan@sparkfun.com'
+ user_name: 'nseidle'
+ commit_message: 'Github Action - Updating Binary ${{ steps.dateNoScores.outputs.dateNoScores }}'
+
+ - name: Update JSON File
+ uses: "DamianReeves/write-file-action@master"
+ with:
+ path: ${{ env.JSON_FILE_NAME }}
+ write-mode: overwrite
+ contents: |
+ {"Configurations": [{"Version":"${{ env.FIRMWARE_VERSION_MAJOR }}.${{ env.FIRMWARE_VERSION_MINOR }}${{ env.JSON_ENDING }}", "URL":"https://raw.githubusercontent.com/sparkfun/SparkFun_RTK_Firmware_Binaries/main/${{ env.FILENAME_PREFIX }}${{ env.FILE_ENDING_UNDERSCORE }}.bin"}]}
+
+ - name: Push JSON to Binaries Repo
+ # uses: dmnemec/copy_file_to_another_repo_action #Workaround for Issue: https://github.com/orgs/community/discussions/55820#discussioncomment-5946136
+ uses: Jason2866/copy_file_to_another_repo_action@http408_fix
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.API_GITHUB_RTK_FILE_TOKEN }}
+ with:
+ source_file: ${{ env.JSON_FILE_NAME }}
+ destination_repo: 'sparkfun/SparkFun_RTK_Firmware_Binaries'
+ destination_folder: ''
+ user_email: 'nathan@sparkfun.com'
+ user_name: 'nseidle'
+ commit_message: 'Github Action - Updating JSON ${{ steps.dateNoScores.outputs.dateNoScores }}'
\ No newline at end of file
diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml
new file mode 100644
index 000000000..2eb741aed
--- /dev/null
+++ b/.github/workflows/mkdocs.yml
@@ -0,0 +1,16 @@
+name: Run mkdocs
+on:
+ push:
+ branches:
+ - main
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: 3.12.0
+ - run: pip install mkdocs-material mkdocs-monorepo-plugin setuptools
+ - run: pip install mkdocs-with-pdf
+ - run: mkdocs gh-deploy --force
diff --git a/.github/workflows/non-release-build.yml b/.github/workflows/non-release-build.yml
new file mode 100644
index 000000000..6a90ab236
--- /dev/null
+++ b/.github/workflows/non-release-build.yml
@@ -0,0 +1,150 @@
+name: RTK Firmware Non-Release Build
+on:
+ workflow_dispatch:
+ branches:
+
+env:
+ FILENAME_PREFIX: RTK_Surveyor_Firmware
+ FIRMWARE_VERSION_MAJOR: 99
+ FIRMWARE_VERSION_MINOR: 99
+ POINTPERFECT_TOKEN: ${{ secrets.POINTPERFECT_TOKEN }}
+
+jobs:
+ build:
+
+ name: Build
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@master
+
+ - name: Get current date
+ id: date
+ run: echo "date=$(date +'%b_%d_%Y')" >> $GITHUB_OUTPUT
+
+ - name: Get current date
+ id: dateNoScores
+ run: echo "dateNoScores=$(date +'%b %d %Y')" >> $GITHUB_OUTPUT
+
+ - name: Extract branch name
+ run: echo "BRANCH=${{github.ref_name}}" >> $GITHUB_ENV
+
+ #File_Name_v3_1.bin
+ #File_Name_RC-Jan_26_2023.bin
+ - name: Create file ending and compiler flags based on branch
+ run: |
+ echo "FILE_ENDING_UNDERSCORE=_RC-${{ steps.date.outputs.date }}" >> "$GITHUB_ENV"
+ echo "FILE_ENDING_NOUNDERSCORE=_RC-${{ steps.dateNoScores.outputs.dateNoScores }}" >> "$GITHUB_ENV"
+ echo "JSON_ENDING=-${{ steps.dateNoScores.outputs.dateNoScores }}" >> "$GITHUB_ENV"
+ echo "JSON_FILE_NAME=RTK-RC-Firmware.json" >> "$GITHUB_ENV"
+ echo "ENABLE_DEVELOPER=true" >> "$GITHUB_ENV"
+ echo "DEBUG_LEVEL=debug" >> "$GITHUB_ENV"
+
+ - name: Setup Arduino CLI
+ uses: arduino/setup-arduino-cli@v1
+
+ - name: Start config file
+ run: arduino-cli config init --additional-urls "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json"
+
+ - name: Update index
+ run: arduino-cli core update-index
+
+ #We limit the ESP32 core to v2.0.2
+ - name: Install platform
+ run: arduino-cli core install esp32:esp32@2.0.2
+
+ - name: Get Known Libraries
+ run: arduino-cli lib install
+ ArduinoJson@6.19.4
+ ESP32Time@2.0.0
+ ESP32_BleSerial@1.0.5
+ "ESP32-OTA-Pull"@1.0.0
+ Ethernet@2.0.2
+ JC_Button@2.1.2
+ PubSubClient@2.8.0
+ "SdFat"@2.1.1
+ "SparkFun LIS2DH12 Arduino Library"@1.0.3
+ "SparkFun MAX1704x Fuel Gauge Arduino Library"@1.0.4
+ "SparkFun u-blox GNSS v3"@3.0.14
+ SparkFun_WebServer_ESP32_W5500@1.5.5
+ SSLClientESP32@2.0.0
+
+ - name: Enable external libs
+ run: arduino-cli config set library.enable_unsafe_install true
+
+ - name: Get Libraries
+ run: arduino-cli lib install --git-url
+ https://github.com/sparkfun/SparkFun_Qwiic_OLED_Arduino_Library.git
+ https://github.com/me-no-dev/ESPAsyncWebServer.git
+ https://github.com/me-no-dev/AsyncTCP.git
+
+ #Incorporate ESP-Now patch into core: https://github.com/espressif/arduino-esp32/pull/7044/files
+ #- name: Patch ESP32 Core
+ # run: |
+ # cd Firmware/RTK_Surveyor/Patch/
+ # cp WiFiGeneric.cpp /home/runner/.arduino15/packages/esp32/hardware/esp32/2.0.2/libraries/WiFi/src/WiFiGeneric.cpp
+
+ #Patch Server.h to avoid https://github.com/arduino-libraries/Ethernet/issues/88#issuecomment-455498941
+ #Note: this patch can be removed if/when we upgrade to ESP32 core >= v2.0.6
+ - name: Patch ESP32 Server.h for Ethernet
+ run: |
+ cd Firmware/RTK_Surveyor/Patch/
+ cp Server.h /home/runner/.arduino15/packages/esp32/hardware/esp32/2.0.2/cores/esp32/Server.h
+
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+
+ # Configure Python - now we have Python installed, we need to provide everything needed by esptool otherwise the compile fails
+ - name: Configure Python
+ run: |
+ pip3 install pyserial
+
+ - name: Update index_html
+ run: |
+ cd Firmware/Tools
+ python index_html_zipper.py ../RTK_Surveyor/AP-Config/index.html ../RTK_Surveyor/form.h
+
+ - name: Update main_js
+ run: |
+ cd Firmware/Tools
+ python main_js_zipper.py ../RTK_Surveyor/AP-Config/src/main.js ../RTK_Surveyor/form.h
+
+ - name: Commit and push form.h
+ uses: actions-js/push@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ directory: ./Firmware/RTK_Surveyor
+ branch: ${{ env.BRANCH }}
+ message: 'Update form.h via Python'
+
+ - name: Copy custom app3M_fat9M_16MB.csv
+ run:
+ cp Firmware/app3M_fat9M_16MB.csv /home/runner/.arduino15/packages/esp32/hardware/esp32/2.0.2/tools/partitions/app3M_fat9M_16MB.csv
+
+ - name: Compile Sketch
+ run: arduino-cli compile --fqbn "esp32:esp32:esp32":DebugLevel=${{ env.DEBUG_LEVEL }} ./Firmware/RTK_Surveyor/RTK_Surveyor.ino
+ --build-property build.partitions=app3M_fat9M_16MB
+ --build-property upload.maximum_size=3145728
+ --build-property "compiler.cpp.extra_flags=\"-DPOINTPERFECT_TOKEN=$POINTPERFECT_TOKEN\" \"-DFIRMWARE_VERSION_MAJOR=$FIRMWARE_VERSION_MAJOR\" \"-DFIRMWARE_VERSION_MINOR=$FIRMWARE_VERSION_MINOR\" \"-DENABLE_DEVELOPER=${{ env.ENABLE_DEVELOPER }}\""
+ --export-binaries
+
+ - name: Create artifact name
+ run: |
+ echo "ARTIFACT=${{ env.FILENAME_PREFIX }}${{ env.FILE_ENDING_UNDERSCORE }}" >> $GITHUB_ENV
+
+ - name: Create artifact directory
+ run: |
+ cd Firmware/RTK_Surveyor/build/esp32.esp32.esp32/
+ mkdir ${{ env.ARTIFACT }}
+ mv RTK_Surveyor.ino.bin ${{ env.ARTIFACT }}
+ mv RTK_Surveyor.ino.elf ${{ env.ARTIFACT }}
+
+ - name: Upload artifact directory to action - avoid double-zip
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ env.ARTIFACT }}
+ path: Firmware/RTK_Surveyor/build/esp32.esp32.esp32/${{ env.ARTIFACT }}
+ retention-days: 7
diff --git a/.gitignore b/.gitignore
index e110aaf11..5fce5b65b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,38 +1,44 @@
-# Windows image file caches
-Thumbs.db
-ehthumbs.db
-
-#Eagle Backup files
-*.s#?
-*.b#?
-*.l#?
-*.lck
-
-# Folder config file
-Desktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msm
-*.msp
-
-# =========================
-# Operating System Files
-# =========================
-
-# OSX
-# =========================
-
+partitions.csv
+
+tokens.h
+.vscode/*
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+#Eagle Backup files
+*.s#?
+*.b#?
+*.l#?
+*.lck
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# =========================
+# Operating System Files
+# =========================
+
+# OSX
+# =========================
+
.DS_Store
.AppleDouble
.LSOverride
# Icon must ends with two \r.
-Icon
+Icon
+
# Thumbnails
._*
@@ -40,3 +46,14 @@ Icon
# Files that might appear on external disk
.Spotlight-V100
.Trashes
+
+# =========================
+# Linux Files
+# =========================
+
+Compare
+NMEA_Client
+Read_Map_File
+RTK_Reset
+Split_Messages
+X.509_crt_bundle_bin_to_c
diff --git a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v11.bin b/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v11.bin
deleted file mode 100644
index 6fc4409dd..000000000
Binary files a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v11.bin and /dev/null differ
diff --git a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v12.bin b/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v12.bin
deleted file mode 100644
index 39463a78a..000000000
Binary files a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v12.bin and /dev/null differ
diff --git a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v13.bin b/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v13.bin
deleted file mode 100644
index 9832a8005..000000000
Binary files a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v13.bin and /dev/null differ
diff --git a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v14.bin b/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v14.bin
deleted file mode 100644
index 4eca72e63..000000000
Binary files a/Binaries/For_SD_Loading/RTK_Surveyor_Firmware_v14.bin and /dev/null differ
diff --git a/Binaries/RTK_Surveyor_Firmware_v10_combined.bin b/Binaries/RTK_Surveyor_Firmware_v10_combined.bin
deleted file mode 100644
index 7e0df75b5..000000000
Binary files a/Binaries/RTK_Surveyor_Firmware_v10_combined.bin and /dev/null differ
diff --git a/Binaries/RTK_Surveyor_Firmware_v11_combined.bin b/Binaries/RTK_Surveyor_Firmware_v11_combined.bin
deleted file mode 100644
index 4d74acb5f..000000000
Binary files a/Binaries/RTK_Surveyor_Firmware_v11_combined.bin and /dev/null differ
diff --git a/Binaries/RTK_Surveyor_Firmware_v12_combined.bin b/Binaries/RTK_Surveyor_Firmware_v12_combined.bin
deleted file mode 100644
index 432f4c6c8..000000000
Binary files a/Binaries/RTK_Surveyor_Firmware_v12_combined.bin and /dev/null differ
diff --git a/Binaries/RTK_Surveyor_Firmware_v13_combined.bin b/Binaries/RTK_Surveyor_Firmware_v13_combined.bin
deleted file mode 100644
index b486a707f..000000000
Binary files a/Binaries/RTK_Surveyor_Firmware_v13_combined.bin and /dev/null differ
diff --git a/Binaries/RTK_Surveyor_Firmware_v14_combined.bin b/Binaries/RTK_Surveyor_Firmware_v14_combined.bin
deleted file mode 100644
index ee7bedeb6..000000000
Binary files a/Binaries/RTK_Surveyor_Firmware_v14_combined.bin and /dev/null differ
diff --git a/Binaries/readme.md b/Binaries/readme.md
deleted file mode 100644
index 360cd8155..000000000
--- a/Binaries/readme.md
+++ /dev/null
@@ -1,5 +0,0 @@
-This folder contains the various firmware versions for RTK Surveyor. Each binary is created by exporting a sketch binary from Arduino then combining (using the ESP32 tool) with boot_app0.bin, bootloader_qio_80m.bin, and RTK_Surveyor.ino.partitions.bin. You can update the firmware on a device by loading a binary onto the SD card and inserting it into the RTK Surveyor (see more information [here](https://learn.sparkfun.com/tutorials/sparkfun-rtk-surveyor-hookup-guide/firmware-updates-and-customization)) or by using the following CLI:
-
-esptool.exe --chip esp32 --port COM4 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0x00 RTK_Surveyor_Firmware_vxx_combined.bin
-
-Where *COM4* is replaced with the COM port that RTK Surveyor enumerated at and *RTK_Surveyor_Firmware_vxx_combined.bin* is the firmware you would like to load.
\ No newline at end of file
diff --git a/Firmware/RTKFirmware.csv b/Firmware/RTKFirmware.csv
new file mode 100644
index 000000000..7b89daee9
--- /dev/null
+++ b/Firmware/RTKFirmware.csv
@@ -0,0 +1,6 @@
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x5000,
+otadata, data, ota, 0xe000, 0x2000,
+app0, app, ota_0, 0x10000, 0x640000,
+app1, app, ota_1, 0x650000,0x640000,
+spiffs, data, spiffs, 0xc90000,0x370000,
diff --git a/Firmware/RTK_Surveyor/AP-Config/favicon.ico b/Firmware/RTK_Surveyor/AP-Config/favicon.ico
new file mode 100644
index 000000000..5c70911fb
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/favicon.ico differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/index.html b/Firmware/RTK_Surveyor/AP-Config/index.html
new file mode 100644
index 000000000..65e7c43fe
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/index.html
@@ -0,0 +1,2002 @@
+
+
+
+
+
+
+ SparkFun RTK Setup
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+67
+
+
+
+
+ Resetting
RTK device is rebooting. Please wait...
+
+
+
+ Reset Done
RTK device has now reset.
+
+
+
+ Done
Firmware update complete. RTK device is now rebooting.
+
+
+
+
+
+
+
+
+
+ RTK Firmware: v0.0
+ ZED-F9P Firmware: v0.0
+ Device Bluetooth ID: 0000
+ LLh:
+ 40.09029479,
+ -105.18505761,
+ 1560.089 (APC)
+
+ ECEF:
+ -1280206.568,
+ -4716804.403,
+ 4086665.484
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Device ID:
N/A
+ Days until keys expire:
No Keys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Networks:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 37%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SD Size:
0 MB
/ Free:
0 MB
+
+
+
+
+
+
+ |
+ |
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery0.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery0.png
new file mode 100644
index 000000000..b7750797e
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery0.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery0_Charging.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery0_Charging.png
new file mode 100644
index 000000000..026f8ed97
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery0_Charging.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery1.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery1.png
new file mode 100644
index 000000000..ec7dcb659
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery1.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery1_Charging.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery1_Charging.png
new file mode 100644
index 000000000..c11a5d972
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery1_Charging.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery2.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery2.png
new file mode 100644
index 000000000..1aa43191c
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery2.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery2_Charging.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery2_Charging.png
new file mode 100644
index 000000000..82fb4d403
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery2_Charging.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery3.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery3.png
new file mode 100644
index 000000000..9fcb5f2c3
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery3.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/Battery3_Charging.png b/Firmware/RTK_Surveyor/AP-Config/src/Battery3_Charging.png
new file mode 100644
index 000000000..ecb5a292f
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/Battery3_Charging.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/BatteryBlank.png b/Firmware/RTK_Surveyor/AP-Config/src/BatteryBlank.png
new file mode 100644
index 000000000..f95db25f3
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/BatteryBlank.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/BatteryBlank.png.gz b/Firmware/RTK_Surveyor/AP-Config/src/BatteryBlank.png.gz
new file mode 100644
index 000000000..b555e6c1d
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/BatteryBlank.png.gz differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.bundle.min.js b/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.bundle.min.js
new file mode 100644
index 000000000..68acb7a31
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.bundle.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v5.0.2 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]}},e=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},i=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i="#"+i.split("#")[1]),e=i&&"#"!==i?i.trim():null}return e},n=t=>{const e=i(t);return e&&document.querySelector(e)?e:null},s=t=>{const e=i(t);return e?document.querySelector(e):null},o=t=>{t.dispatchEvent(new Event("transitionend"))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=e=>r(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?t.findOne(e):null,l=(t,e,i)=>{Object.keys(i).forEach(n=>{const s=i[n],o=e[n],a=o&&r(o)?"element":null==(l=o)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)})},c=t=>!(!r(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),h=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),d=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?d(t.parentNode):null},u=()=>{},f=t=>t.offsetHeight,p=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},m=[],g=()=>"rtl"===document.documentElement.dir,_=t=>{var e;e=()=>{const e=p();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",()=>{m.forEach(t=>t())}),m.push(e)):e()},b=t=>{"function"==typeof t&&t()},v=(t,e,i=!0)=>{if(!i)return void b(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const r=({target:i})=>{i===e&&(s=!0,e.removeEventListener("transitionend",r),b(t))};e.addEventListener("transitionend",r),setTimeout(()=>{s||o(e)},n)},y=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},w=/[^.]*(?=\..*)\.|.*/,E=/\..*/,A=/::\d+$/,T={};let O=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},k=/^(mouseenter|mouseleave)/i,L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function D(t){const e=x(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function S(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=I(e,i,n),l=D(t),c=l[a]||(l[a]={}),h=S(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(w,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function j(t,e,i,n,s){const o=S(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function M(t){return t=t.replace(E,""),C[t]||t}const P={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=I(e,i,n),a=r!==e,l=D(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void j(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach(i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach(o=>{if(o.includes(n)){const n=s[o];j(t,e,i,n.originalHandler,n.delegationSelector)}})}(t,l,i,e.slice(1))});const h=l[r]||{};Object.keys(h).forEach(i=>{const n=i.replace(A,"");if(!a||e.includes(n)){const e=h[i];j(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=p(),s=M(e),o=e!==s,r=L.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach(t=>{Object.defineProperty(d,t,{get:()=>i[t]})}),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},H=new Map;var R={set(t,e,i){H.has(t)||H.set(t,new Map);const n=H.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>H.has(t)&&H.get(t).get(e)||null,remove(t,e){if(!H.has(t))return;const i=H.get(t);i.delete(e),0===i.size&&H.delete(t)}};class B{constructor(t){(t=a(t))&&(this._element=t,R.set(this._element,this.constructor.DATA_KEY,this))}dispose(){R.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach(t=>{this[t]=null})}_queueCallback(t,e,i=!0){v(t,e,i)}static getInstance(t){return R.get(t,this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.0.2"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return"bs."+this.NAME}static get EVENT_KEY(){return"."+this.DATA_KEY}}class W extends B{static get NAME(){return"alert"}close(t){const e=t?this._getRootElement(t):this._element,i=this._triggerCloseEvent(e);null===i||i.defaultPrevented||this._removeElement(e)}_getRootElement(t){return s(t)||t.closest(".alert")}_triggerCloseEvent(t){return P.trigger(t,"close.bs.alert")}_removeElement(t){t.classList.remove("show");const e=t.classList.contains("fade");this._queueCallback(()=>this._destroyElement(t),t,e)}_destroyElement(t){t.remove(),P.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}P.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',W.handleDismiss(new W)),_(W);class q extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=q.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function z(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function $(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}P.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');q.getOrCreateInstance(e).toggle()}),_(q);const U={setDataAttribute(t,e,i){t.setAttribute("data-bs-"+$(e),i)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+$(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=z(t.dataset[i])}),e},getDataAttribute:(t,e)=>z(t.getAttribute("data-bs-"+$(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},F={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},V={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},K="next",X="prev",Y="left",Q="right",G={ArrowLeft:Q,ArrowRight:Y};class Z extends B{constructor(e,i){super(e),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(i),this._indicatorsElement=t.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return F}static get NAME(){return"carousel"}next(){this._slide(K)}nextWhenVisible(){!document.hidden&&c(this._element)&&this.next()}prev(){this._slide(X)}pause(e){e||(this._isPaused=!0),t.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(o(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(e){this._activeElement=t.findOne(".active.carousel-item",this._element);const i=this._getItemIndex(this._activeElement);if(e>this._items.length-1||e<0)return;if(this._isSliding)return void P.one(this._element,"slid.bs.carousel",()=>this.to(e));if(i===e)return this.pause(),void this.cycle();const n=e>i?K:X;this._slide(n,this._items[e])}_getConfig(t){return t={...F,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("carousel",t,V),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?Q:Y)}_addEventListeners(){this._config.keyboard&&P.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),P.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const e=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};t.find(".carousel-item img",this._element).forEach(t=>{P.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(P.on(this._element,"pointerdown.bs.carousel",t=>e(t)),P.on(this._element,"pointerup.bs.carousel",t=>n(t)),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.carousel",t=>e(t)),P.on(this._element,"touchmove.bs.carousel",t=>i(t)),P.on(this._element,"touchend.bs.carousel",t=>n(t)))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=G[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(e){return this._items=e&&e.parentNode?t.find(".carousel-item",e.parentNode):[],this._items.indexOf(e)}_getItemByOrder(t,e){const i=t===K;return y(this._items,e,i,this._config.wrap)}_triggerSlideEvent(e,i){const n=this._getItemIndex(e),s=this._getItemIndex(t.findOne(".active.carousel-item",this._element));return P.trigger(this._element,"slide.bs.carousel",{relatedTarget:e,direction:i,from:s,to:n})}_setActiveIndicatorElement(e){if(this._indicatorsElement){const i=t.findOne(".active",this._indicatorsElement);i.classList.remove("active"),i.removeAttribute("aria-current");const n=t.find("[data-bs-target]",this._indicatorsElement);for(let t=0;t{P.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:u,from:o,to:a})};if(this._element.classList.contains("slide")){r.classList.add(d),f(r),s.classList.add(h),r.classList.add(h);const t=()=>{r.classList.remove(h,d),r.classList.add("active"),s.classList.remove("active",d,h),this._isSliding=!1,setTimeout(p,0)};this._queueCallback(t,s,!0)}else s.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,p();l&&this.cycle()}_directionToOrder(t){return[Q,Y].includes(t)?g()?t===Y?X:K:t===Y?K:X:t}_orderToDirection(t){return[K,X].includes(t)?g()?t===X?Y:Q:t===X?Q:Y:t}static carouselInterface(t,e){const i=Z.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){Z.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=s(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},n=this.getAttribute("data-bs-slide-to");n&&(i.interval=!1),Z.carouselInterface(e,i),n&&Z.getInstance(e).to(n),t.preventDefault()}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",Z.dataApiClickHandler),P.on(window,"load.bs.carousel.data-api",()=>{const e=t.find('[data-bs-ride="carousel"]');for(let t=0,i=e.length;tt===this._element);null!==o&&r.length&&(this._selector=o,this._triggerArray.push(i))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return J}static get NAME(){return"collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let e,i;this._parent&&(e=t.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===e.length&&(e=null));const n=t.findOne(this._selector);if(e){const t=e.find(t=>n!==t);if(i=t?et.getInstance(t):null,i&&i._isTransitioning)return}if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e&&e.forEach(t=>{n!==t&&et.collapseInterface(t,"hide"),i||R.set(t,"bs.collapse",null)});const s=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[s]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(s[0].toUpperCase()+s.slice(1));this._queueCallback(()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[s]="",this.setTransitioning(!1),P.trigger(this._element,"shown.bs.collapse")},this._element,!0),this._element.style[s]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",f(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),P.trigger(this._element,"hidden.bs.collapse")},this._element,!0)}setTransitioning(t){this._isTransitioning=t}_getConfig(t){return(t={...J,...t}).toggle=Boolean(t.toggle),l("collapse",t,tt),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:e}=this._config;e=a(e);const i=`[data-bs-toggle="collapse"][data-bs-parent="${e}"]`;return t.find(i,e).forEach(t=>{const e=s(t);this._addAriaAndCollapsedClass(e,[t])}),e}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const i=t.classList.contains("show");e.forEach(t=>{i?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",i)})}static collapseInterface(t,e){let i=et.getInstance(t);const n={...J,...U.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!i&&n.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(n.toggle=!1),i||(i=new et(t,n)),"string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){et.collapseInterface(this,t)}))}}P.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(e){("A"===e.target.tagName||e.delegateTarget&&"A"===e.delegateTarget.tagName)&&e.preventDefault();const i=U.getDataAttributes(this),s=n(this);t.find(s).forEach(t=>{const e=et.getInstance(t);let n;e?(null===e._parent&&"string"==typeof i.parent&&(e._config.parent=i.parent,e._parent=e._getParent()),n="toggle"):n=i,et.collapseInterface(t,n)})})),_(et);var it="top",nt="bottom",st="right",ot="left",rt=[it,nt,st,ot],at=rt.reduce((function(t,e){return t.concat([e+"-start",e+"-end"])}),[]),lt=[].concat(rt,["auto"]).reduce((function(t,e){return t.concat([e,e+"-start",e+"-end"])}),[]),ct=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function ht(t){return t?(t.nodeName||"").toLowerCase():null}function dt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function ut(t){return t instanceof dt(t).Element||t instanceof Element}function ft(t){return t instanceof dt(t).HTMLElement||t instanceof HTMLElement}function pt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof dt(t).ShadowRoot||t instanceof ShadowRoot)}var mt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];ft(s)&&ht(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});ft(n)&&ht(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function gt(t){return t.split("-")[0]}function _t(t){var e=t.getBoundingClientRect();return{width:e.width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function bt(t){var e=_t(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function vt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&pt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function yt(t){return dt(t).getComputedStyle(t)}function wt(t){return["table","td","th"].indexOf(ht(t))>=0}function Et(t){return((ut(t)?t.ownerDocument:t.document)||window.document).documentElement}function At(t){return"html"===ht(t)?t:t.assignedSlot||t.parentNode||(pt(t)?t.host:null)||Et(t)}function Tt(t){return ft(t)&&"fixed"!==yt(t).position?t.offsetParent:null}function Ot(t){for(var e=dt(t),i=Tt(t);i&&wt(i)&&"static"===yt(i).position;)i=Tt(i);return i&&("html"===ht(i)||"body"===ht(i)&&"static"===yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&ft(t)&&"fixed"===yt(t).position)return null;for(var i=At(t);ft(i)&&["html","body"].indexOf(ht(i))<0;){var n=yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ct(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var kt=Math.max,Lt=Math.min,xt=Math.round;function Dt(t,e,i){return kt(t,Lt(e,i))}function St(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function It(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var Nt={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=gt(i.placement),l=Ct(a),c=[ot,st].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return St("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:It(t,rt))}(s.padding,i),d=bt(o),u="y"===l?it:ot,f="y"===l?nt:st,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=Ot(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=Dt(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&vt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},jt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function Mt(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.offsets,r=t.position,a=t.gpuAcceleration,l=t.adaptive,c=t.roundOffsets,h=!0===c?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:xt(xt(e*n)/n)||0,y:xt(xt(i*n)/n)||0}}(o):"function"==typeof c?c(o):o,d=h.x,u=void 0===d?0:d,f=h.y,p=void 0===f?0:f,m=o.hasOwnProperty("x"),g=o.hasOwnProperty("y"),_=ot,b=it,v=window;if(l){var y=Ot(i),w="clientHeight",E="clientWidth";y===dt(i)&&"static"!==yt(y=Et(i)).position&&(w="scrollHeight",E="scrollWidth"),y=y,s===it&&(b=nt,p-=y[w]-n.height,p*=a?1:-1),s===ot&&(_=st,u-=y[E]-n.width,u*=a?1:-1)}var A,T=Object.assign({position:r},l&&jt);return a?Object.assign({},T,((A={})[b]=g?"0":"",A[_]=m?"0":"",A.transform=(v.devicePixelRatio||1)<2?"translate("+u+"px, "+p+"px)":"translate3d("+u+"px, "+p+"px, 0)",A)):Object.assign({},T,((e={})[b]=g?p+"px":"",e[_]=m?u+"px":"",e.transform="",e))}var Pt={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:gt(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,Mt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,Mt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},Ht={passive:!0},Rt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=dt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,Ht)})),a&&l.addEventListener("resize",i.update,Ht),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,Ht)})),a&&l.removeEventListener("resize",i.update,Ht)}},data:{}},Bt={left:"right",right:"left",bottom:"top",top:"bottom"};function Wt(t){return t.replace(/left|right|bottom|top/g,(function(t){return Bt[t]}))}var qt={start:"end",end:"start"};function zt(t){return t.replace(/start|end/g,(function(t){return qt[t]}))}function $t(t){var e=dt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ut(t){return _t(Et(t)).left+$t(t).scrollLeft}function Ft(t){var e=yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Vt(t,e){var i;void 0===e&&(e=[]);var n=function t(e){return["html","body","#document"].indexOf(ht(e))>=0?e.ownerDocument.body:ft(e)&&Ft(e)?e:t(At(e))}(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=dt(n),r=s?[o].concat(o.visualViewport||[],Ft(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Vt(At(r)))}function Kt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Xt(t,e){return"viewport"===e?Kt(function(t){var e=dt(t),i=Et(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+Ut(t),y:a}}(t)):ft(e)?function(t){var e=_t(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Kt(function(t){var e,i=Et(t),n=$t(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=kt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=kt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ut(t),l=-n.scrollTop;return"rtl"===yt(s||i).direction&&(a+=kt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Et(t)))}function Yt(t){return t.split("-")[1]}function Qt(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?gt(s):null,r=s?Yt(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case it:e={x:a,y:i.y-n.height};break;case nt:e={x:a,y:i.y+i.height};break;case st:e={x:i.x+i.width,y:l};break;case ot:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ct(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case"start":e[c]=e[c]-(i[h]/2-n[h]/2);break;case"end":e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function Gt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?"clippingParents":o,a=i.rootBoundary,l=void 0===a?"viewport":a,c=i.elementContext,h=void 0===c?"popper":c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=St("number"!=typeof p?p:It(p,rt)),g="popper"===h?"reference":"popper",_=t.elements.reference,b=t.rects.popper,v=t.elements[u?g:h],y=function(t,e,i){var n="clippingParents"===e?function(t){var e=Vt(At(t)),i=["absolute","fixed"].indexOf(yt(t).position)>=0&&ft(t)?Ot(t):t;return ut(i)?e.filter((function(t){return ut(t)&&vt(t,i)&&"body"!==ht(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Xt(t,i);return e.top=kt(n.top,e.top),e.right=Lt(n.right,e.right),e.bottom=Lt(n.bottom,e.bottom),e.left=kt(n.left,e.left),e}),Xt(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}(ut(v)?v:v.contextElement||Et(t.elements.popper),r,l),w=_t(_),E=Qt({reference:w,element:b,strategy:"absolute",placement:s}),A=Kt(Object.assign({},b,E)),T="popper"===h?A:w,O={top:y.top-T.top+m.top,bottom:T.bottom-y.bottom+m.bottom,left:y.left-T.left+m.left,right:T.right-y.right+m.right},C=t.modifiersData.offset;if("popper"===h&&C){var k=C[s];Object.keys(O).forEach((function(t){var e=[st,nt].indexOf(t)>=0?1:-1,i=[it,nt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function Zt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?lt:l,h=Yt(n),d=h?a?at:at.filter((function(t){return Yt(t)===h})):rt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=Gt(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[gt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}var Jt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=gt(g),b=l||(_!==g&&p?function(t){if("auto"===gt(t))return[];var e=Wt(t);return[zt(t),e,zt(e)]}(g):[Wt(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat("auto"===gt(i)?Zt(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=Gt(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=x?L?st:ot:L?nt:it;y[D]>w[D]&&(I=Wt(I));var N=Wt(I),j=[];if(o&&j.push(S[k]<=0),a&&j.push(S[I]<=0,S[N]<=0),j.every((function(t){return t}))){T=C,A=!1;break}E.set(C,j)}if(A)for(var M=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},P=p?3:1;P>0&&"break"!==M(P);P--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function te(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ee(t){return[it,st,nt,ot].some((function(e){return t[e]>=0}))}var ie={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=Gt(e,{elementContext:"reference"}),a=Gt(e,{altBoundary:!0}),l=te(r,n),c=te(a,s,o),h=ee(l),d=ee(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},ne={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=gt(t),s=[ot,it].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[ot,st].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},se={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Qt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},oe={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=Gt(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=gt(e.placement),b=Yt(e.placement),v=!b,y=Ct(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?it:ot,L="y"===y?nt:st,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],I=E[y]-g[L],N=f?-T[x]/2:0,j="start"===b?A[x]:T[x],M="start"===b?-T[x]:-A[x],P=e.elements.arrow,H=f&&P?bt(P):{width:0,height:0},R=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},B=R[k],W=R[L],q=Dt(0,A[x],H[x]),z=v?A[x]/2-N-q-B-O:j-q-B-O,$=v?-A[x]/2+N+q+W+O:M+q+W+O,U=e.elements.arrow&&Ot(e.elements.arrow),F=U?"y"===y?U.clientTop||0:U.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-F,X=E[y]+$-V;if(o){var Y=Dt(f?Lt(S,K):S,D,f?kt(I,X):I);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?it:ot,G="x"===y?nt:st,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=Dt(f?Lt(J,K):J,Z,f?kt(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function re(t,e,i){void 0===i&&(i=!1);var n,s,o=Et(e),r=_t(t),a=ft(e),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(a||!a&&!i)&&(("body"!==ht(e)||Ft(o))&&(l=(n=e)!==dt(n)&&ft(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:$t(n)),ft(e)?((c=_t(e)).x+=e.clientLeft,c.y+=e.clientTop):o&&(c.x=Ut(o))),{x:r.left+l.scrollLeft-c.x,y:r.top+l.scrollTop-c.y,width:r.width,height:r.height}}var ae={placement:"bottom",modifiers:[],strategy:"absolute"};function le(){for(var t=arguments.length,e=new Array(t),i=0;i"applyStyles"===t.name&&!1===t.enabled);this._popper=ue(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>P.on(t,"mouseover",u)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),P.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(h(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){P.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){P.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>P.off(t,"mouseover",u)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),P.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},l("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!r(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return t.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ve;if(t.classList.contains("dropstart"))return ye;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ge:me:e?be:_e}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:e,target:i}){const n=t.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(c);n.length&&y(n,i,"ArrowDown"===e,!n.includes(i)).focus()}static dropdownInterface(t,e){const i=Ae.getOrCreateInstance(t,e);if("string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){Ae.dropdownInterface(this,t)}))}static clearMenus(e){if(e&&(2===e.button||"keyup"===e.type&&"Tab"!==e.key))return;const i=t.find('[data-bs-toggle="dropdown"]');for(let t=0,n=i.length;tthis.matches('[data-bs-toggle="dropdown"]')?this:t.prev(this,'[data-bs-toggle="dropdown"]')[0];return"Escape"===e.key?(n().focus(),void Ae.clearMenus()):"ArrowUp"===e.key||"ArrowDown"===e.key?(i||n().click(),void Ae.getInstance(n())._selectMenuItem(e)):void(i&&"Space"!==e.key||Ae.clearMenus())}}P.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',Ae.dataApiKeydownHandler),P.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",Ae.dataApiKeydownHandler),P.on(document,"click.bs.dropdown.data-api",Ae.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",Ae.clearMenus),P.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),Ae.dropdownInterface(this)})),_(Ae);class Te{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,"paddingRight",e=>e+t),this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),this._setElementAttributes(".sticky-top","marginRight",e=>e-t)}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=i(Number.parseFloat(s))+"px"})}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)})}_applyManipulationCallback(e,i){r(e)?i(e):t.find(e,this._element).forEach(i)}isOverflowing(){return this.getWidth()>0}}const Oe={isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},Ce={isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"};class ke{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&f(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{b(t)})):b(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),b(t)})):b(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...Oe,..."object"==typeof t?t:{}}).rootElement=a(t.rootElement),l("backdrop",t,Ce),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),P.on(this._getElement(),"mousedown.bs.backdrop",()=>{b(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(P.off(this._element,"mousedown.bs.backdrop"),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){v(t,this._getElement(),this._config.isAnimated)}}const Le={backdrop:!0,keyboard:!0,focus:!0},xe={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class De extends B{constructor(e,i){super(e),this._config=this._getConfig(i),this._dialog=t.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new Te}static get Default(){return Le}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,"show.bs.modal",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),P.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),P.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{P.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&["A","AREA"].includes(t.target.tagName)&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(P.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),P.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),P.off(this._element,"click.dismiss.bs.modal"),P.off(this._dialog,"mousedown.dismiss.bs.modal"),this._queueCallback(()=>this._hideModal(),this._element,e)}dispose(){[window,this._dialog].forEach(t=>P.off(t,".bs.modal")),this._backdrop.dispose(),super.dispose(),P.off(document,"focusin.bs.modal")}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new ke({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...Le,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("modal",t,xe),t}_showElement(e){const i=this._isAnimated(),n=t.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,n&&(n.scrollTop=0),i&&f(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus(),this._queueCallback(()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:e})},this._dialog,i)}_enforceFocus(){P.off(document,"focusin.bs.modal"),P.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?P.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):P.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?P.on(window,"resize.bs.modal",()=>this._adjustDialog()):P.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){P.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains("modal-static")||(n||(i.overflowY="hidden"),t.add("modal-static"),this._queueCallback(()=>{t.remove("modal-static"),n||this._queueCallback(()=>{i.overflowY=""},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!g()||i&&!t&&g())&&(this._element.style.paddingLeft=e+"px"),(i&&!t&&!g()||!i&&t&&g())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=De.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=s(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,"show.bs.modal",t=>{t.defaultPrevented||P.one(e,"hidden.bs.modal",()=>{c(this)&&this.focus()})}),De.getOrCreateInstance(e).toggle(this)})),_(De);const Se={backdrop:!0,keyboard:!0,scroll:!1},Ie={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Ne extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get NAME(){return"offcanvas"}static get Default(){return Se}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||((new Te).hide(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),this._queueCallback(()=>{P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(P.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide(),this._queueCallback(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new Te).reset(),P.trigger(this._element,"hidden.bs.offcanvas")},this._element,!0)))}dispose(){this._backdrop.dispose(),super.dispose(),P.off(document,"focusin.bs.offcanvas")}_getConfig(t){return t={...Se,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("offcanvas",t,Ie),t}_initializeBackDrop(){return new ke({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){P.off(document,"focusin.bs.offcanvas"),P.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){P.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),P.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=Ne.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(e){const i=s(this);if(["A","AREA"].includes(this.tagName)&&e.preventDefault(),h(this))return;P.one(i,"hidden.bs.offcanvas",()=>{c(this)&&this.focus()});const n=t.findOne(".offcanvas.show");n&&n!==i&&Ne.getInstance(n).hide(),Ne.getOrCreateInstance(i).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",()=>t.find(".offcanvas.show").forEach(t=>Ne.getOrCreateInstance(t).show())),_(Ne);const je=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Me=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Pe=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,He=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!je.has(i)||Boolean(Me.test(t.nodeValue)||Pe.test(t.nodeValue));const n=e.filter(t=>t instanceof RegExp);for(let t=0,e=n.length;t{He(t,a)||i.removeAttribute(t.nodeName)})}return n.body.innerHTML}const Be=new RegExp("(^|\\s)bs-tooltip\\S+","g"),We=new Set(["sanitize","allowList","sanitizeFn"]),qe={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},ze={AUTO:"auto",TOP:"top",RIGHT:g()?"left":"right",BOTTOM:"bottom",LEFT:g()?"right":"left"},$e={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ue={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class Fe extends B{constructor(t,e){if(void 0===fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return $e}static get NAME(){return"tooltip"}static get Event(){return Ue}static get DefaultType(){return qe}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.Event.SHOW),i=d(this._element),n=null===i?this._element.ownerDocument.documentElement.contains(this._element):i.contains(this._element);if(t.defaultPrevented||!n)return;const s=this.getTipElement(),o=e(this.constructor.NAME);s.setAttribute("id",o),this._element.setAttribute("aria-describedby",o),this.setContent(),this._config.animation&&s.classList.add("fade");const r="function"==typeof this._config.placement?this._config.placement.call(this,s,this._element):this._config.placement,a=this._getAttachment(r);this._addAttachmentClass(a);const{container:l}=this._config;R.set(s,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(l.appendChild(s),P.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=ue(this._element,s,this._getPopperConfig(a)),s.classList.add("show");const c="function"==typeof this._config.customClass?this._config.customClass():this._config.customClass;c&&s.classList.add(...c.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{P.on(t,"mouseover",u)});const h=this.tip.classList.contains("fade");this._queueCallback(()=>{const t=this._hoverState;this._hoverState=null,P.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)},this.tip,h)}hide(){if(!this._popper)return;const t=this.getTipElement();if(P.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>P.off(t,"mouseover",u)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains("fade");this._queueCallback(()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))},this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this._config.template,this.tip=t.children[0],this.tip}setContent(){const e=this.getTipElement();this.setElementContent(t.findOne(".tooltip-inner",e),this.getTitle()),e.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return r(e)?(e=a(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Re(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this._config.title?this._config.title.call(this._element):this._config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const i=this.constructor.DATA_KEY;return(e=e||R.get(t.delegateTarget,i))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),R.set(t.delegateTarget,i,e)),e}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getAttachment(t){return ze[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach(t=>{if("click"===t)P.on(this._element,this.constructor.Event.CLICK,this._config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;P.on(this._element,e,this._config.selector,t=>this._enter(t)),P.on(this._element,i,this._config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e._config.delay&&e._config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e._config.delay&&e._config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{We.has(t)&&delete e[t]}),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:a(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),l("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Re(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this._config)for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Be);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=Fe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}_(Fe);const Ve=new RegExp("(^|\\s)bs-popover\\S+","g"),Ke={...Fe.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Xe={...Fe.DefaultType,content:"(string|element|function)"},Ye={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Qe extends Fe{static get Default(){return Ke}static get NAME(){return"popover"}static get Event(){return Ye}static get DefaultType(){return Xe}isWithContent(){return this.getTitle()||this._getContent()}getTipElement(){return this.tip||(this.tip=super.getTipElement(),this.getTitle()||t.findOne(".popover-header",this.tip).remove(),this._getContent()||t.findOne(".popover-body",this.tip).remove()),this.tip}setContent(){const e=this.getTipElement();this.setElementContent(t.findOne(".popover-header",e),this.getTitle());let i=this._getContent();"function"==typeof i&&(i=i.call(this._element)),this.setElementContent(t.findOne(".popover-body",e),i),e.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this._config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ve);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){const e=Qe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}_(Qe);const Ge={offset:10,method:"auto",target:""},Ze={offset:"number",method:"string",target:"(string|element)"};class Je extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,P.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return Ge}static get NAME(){return"scrollspy"}refresh(){const e=this._scrollElement===this._scrollElement.window?"offset":"position",i="auto"===this._config.method?e:this._config.method,s="position"===i?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.find(this._selector).map(e=>{const o=n(e),r=o?t.findOne(o):null;if(r){const t=r.getBoundingClientRect();if(t.width||t.height)return[U[i](r).top+s,o]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){P.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){if("string"!=typeof(t={...Ge,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target&&r(t.target)){let{id:i}=t.target;i||(i=e("scrollspy"),t.target.id=i),t.target="#"+i}return l("scrollspy",t,Ze),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${t}[data-bs-target="${e}"],${t}[href="${e}"]`),n=t.findOne(i.join(","));n.classList.contains("dropdown-item")?(t.findOne(".dropdown-toggle",n.closest(".dropdown")).classList.add("active"),n.classList.add("active")):(n.classList.add("active"),t.parents(n,".nav, .list-group").forEach(e=>{t.prev(e,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),t.prev(e,".nav-item").forEach(e=>{t.children(e,".nav-link").forEach(t=>t.classList.add("active"))})})),P.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:e})}_clear(){t.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=Je.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(window,"load.bs.scrollspy.data-api",()=>{t.find('[data-bs-spy="scroll"]').forEach(t=>new Je(t))}),_(Je);class ti extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let e;const i=s(this._element),n=this._element.closest(".nav, .list-group");if(n){const i="UL"===n.nodeName||"OL"===n.nodeName?":scope > li > .active":".active";e=t.find(i,n),e=e[e.length-1]}const o=e?P.trigger(e,"hide.bs.tab",{relatedTarget:this._element}):null;if(P.trigger(this._element,"show.bs.tab",{relatedTarget:e}).defaultPrevented||null!==o&&o.defaultPrevented)return;this._activate(this._element,n);const r=()=>{P.trigger(e,"hidden.bs.tab",{relatedTarget:this._element}),P.trigger(this._element,"shown.bs.tab",{relatedTarget:e})};i?this._activate(i,i.parentNode,r):r()}_activate(e,i,n){const s=(!i||"UL"!==i.nodeName&&"OL"!==i.nodeName?t.children(i,".active"):t.find(":scope > li > .active",i))[0],o=n&&s&&s.classList.contains("fade"),r=()=>this._transitionComplete(e,s,n);s&&o?(s.classList.remove("show"),this._queueCallback(r,e,!0)):r()}_transitionComplete(e,i,n){if(i){i.classList.remove("active");const e=t.findOne(":scope > .dropdown-menu .active",i.parentNode);e&&e.classList.remove("active"),"tab"===i.getAttribute("role")&&i.setAttribute("aria-selected",!1)}e.classList.add("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!0),f(e),e.classList.contains("fade")&&e.classList.add("show");let s=e.parentNode;if(s&&"LI"===s.nodeName&&(s=s.parentNode),s&&s.classList.contains("dropdown-menu")){const i=e.closest(".dropdown");i&&t.find(".dropdown-toggle",i).forEach(t=>t.classList.add("active")),e.setAttribute("aria-expanded",!0)}n&&n()}static jQueryInterface(t){return this.each((function(){const e=ti.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),h(this)||ti.getOrCreateInstance(this).show()})),_(ti);const ei={animation:"boolean",autohide:"boolean",delay:"number"},ii={animation:!0,autohide:!0,delay:5e3};class ni extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return ei}static get Default(){return ii}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove("hide"),f(this._element),this._element.classList.add("showing"),this._queueCallback(()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this._element.classList.contains("show")&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.remove("show"),this._queueCallback(()=>{this._element.classList.add("hide"),P.trigger(this._element,"hidden.bs.toast")},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose()}_getConfig(t){return t={...ii,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},l("toast",t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide()),P.on(this._element,"mouseover.bs.toast",t=>this._onInteraction(t,!0)),P.on(this._element,"mouseout.bs.toast",t=>this._onInteraction(t,!1)),P.on(this._element,"focusin.bs.toast",t=>this._onInteraction(t,!0)),P.on(this._element,"focusout.bs.toast",t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return _(ni),{Alert:W,Button:q,Carousel:Z,Collapse:et,Dropdown:Ae,Modal:De,Offcanvas:Ne,Popover:Qe,ScrollSpy:Je,Tab:ti,Toast:ni,Tooltip:Fe}}));
+//# sourceMappingURL=bootstrap.bundle.min.js.map
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.min.css b/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.min.css
new file mode 100644
index 000000000..07f9a3ef2
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.min.css
@@ -0,0 +1,7 @@
+@charset "UTF-8";/*!
+ * Bootstrap v5.0.2 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.775em; color:#888888;}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + (.5rem + 2px));padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + (1rem + 2px));padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + (.75rem + 2px))}textarea.form-control-sm{min-height:calc(1.5em + (.5rem + 2px))}textarea.form-control-lg{min-height:calc(1.5em + (1rem + 2px))}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 8 .25rem rgba(62,160,170,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#3EA0B1;border-color:#93D5E2}.btn-primary:hover{color:#fff;background-color:#338695;border-color:#3EA0B1}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#3EA0B1;border-color:#93D5E2;box-shadow:0 0 0 .25rem rgba(62,160,170,.25)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#3EA0B1;border-color:#93D5E2}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:inset 0 0 0 .25rem rgba(62,160,170,.25)}.btn-primary.disabled,.btn-primary:disabled{color:#87b9c1;background-color:#e1edef;border-color:#e1edef}.btn-secondary{color:#4D4D4D;background-color:#FFAB58;border-color:#FFD0A3}.btn-secondary:hover{color:#4D4D4D;background-color:#EB9947;border-color:#FFAB58}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#4D4D4D;background-color:#EB9947;border-color:#FFAB58;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#4D4D4D;background-color:#EB9947;border-color:#FFAB58}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#338695;border-color:#338695}.btn-outline-primary:hover{color:#338695;background-color:#fff;border-color:#338695}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:inset 0 0 0 -3px rgba(62,160,177,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#338695;border-color:#93D5E2; box-shadow:0 0 -8 .25rem rgba(0,0,0,.5)}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 8 .25rem rgba(62,160,177,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#4D4D4D;border-color:#EB9947}.btn-outline-secondary:hover{color:#4D4D4D;background-color:#fff;border-color:#EB9947}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:inset 0 0 0 3px rgba(255,208,163,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem;margin-bottom:10px;}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#0d6efd!important}.text-secondary{color:#EB9947!important}.text-success{color:#198754!important}.text-info{color:#0dcaf0!important}.text-warning{color:#ffc107!important}.text-danger{color:#dc3545!important}.text-light{color:#f8f9fa!important}.text-dark{color:#212529!important}.text-white{color:#fff!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#0d6efd!important}.bg-secondary{background-color:#6c757d!important}.bg-success{background-color:#198754!important}.bg-info{background-color:#0dcaf0!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#dc3545!important}.bg-light{background-color:#f8f9fa!important}.bg-dark{background-color:#212529!important}.bg-body{background-color:#fff!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}
+/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.min.js b/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.min.js
new file mode 100644
index 000000000..c4c0d1f95
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/src/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v4.3.1 (https://getbootstrap.com/)
+ * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t=t||self).bootstrap={},t.jQuery,t.Popper)}(this,function(t,g,u){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Ee},je="show",He="out",Re={HIDE:"hide"+De,HIDDEN:"hidden"+De,SHOW:"show"+De,SHOWN:"shown"+De,INSERTED:"inserted"+De,CLICK:"click"+De,FOCUSIN:"focusin"+De,FOCUSOUT:"focusout"+De,MOUSEENTER:"mouseenter"+De,MOUSELEAVE:"mouseleave"+De},xe="fade",Fe="show",Ue=".tooltip-inner",We=".arrow",qe="hover",Me="focus",Ke="click",Qe="manual",Be=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Fe))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(xe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,{placement:a,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:We},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),g(o).addClass(Fe),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===He&&e._leave(null,e)};if(g(this.tip).hasClass(xe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=g.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==je&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),g(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(g(this.element).trigger(i),!i.isDefaultPrevented()){if(g(n).removeClass(Fe),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ke]=!1,this._activeTrigger[Me]=!1,this._activeTrigger[qe]=!1,g(this.tip).hasClass(xe)){var r=_.getTransitionDurationFromElement(n);g(n).one(_.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Ae+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ue)),this.getTitle()),g(t).removeClass(xe+" "+Fe)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=Se(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Pe[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Qe){var e=t===qe?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===qe?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),g(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Me:qe]=!0),g(e.getTipElement()).hasClass(Fe)||e._hoverState===je?e._hoverState=je:(clearTimeout(e._timeout),e._hoverState=je,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===je&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Me:qe]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=He,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===He&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==Oe.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(be,t,this.constructor.DefaultType),t.sanitize&&(t.template=Se(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ne);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(xe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ie),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ie,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"Default",get:function(){return Le}},{key:"NAME",get:function(){return be}},{key:"DATA_KEY",get:function(){return Ie}},{key:"Event",get:function(){return Re}},{key:"EVENT_KEY",get:function(){return De}},{key:"DefaultType",get:function(){return ke}}]),i}();g.fn[be]=Be._jQueryInterface,g.fn[be].Constructor=Be,g.fn[be].noConflict=function(){return g.fn[be]=we,Be._jQueryInterface};var Ve="popover",Ye="bs.popover",ze="."+Ye,Xe=g.fn[Ve],$e="bs-popover",Ge=new RegExp("(^|\\s)"+$e+"\\S+","g"),Je=l({},Be.Default,{placement:"right",trigger:"click",content:"",template:''}),Ze=l({},Be.DefaultType,{content:"(string|element|function)"}),tn="fade",en="show",nn=".popover-header",on=".popover-body",rn={HIDE:"hide"+ze,HIDDEN:"hidden"+ze,SHOW:"show"+ze,SHOWN:"shown"+ze,INSERTED:"inserted"+ze,CLICK:"click"+ze,FOCUSIN:"focusin"+ze,FOCUSOUT:"focusout"+ze,MOUSEENTER:"mouseenter"+ze,MOUSELEAVE:"mouseleave"+ze},sn=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){g(this.getTipElement()).addClass($e+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},o.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(nn),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(on),e),t.removeClass(tn+" "+en)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ge);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",Wn='[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',qn=".dropdown-toggle",Mn="> .dropdown-menu .active",Kn=function(){function i(t){this._element=t}var t=i.prototype;return t.show=function(){var n=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&g(this._element).hasClass(Pn)||g(this._element).hasClass(Ln))){var t,i,e=g(this._element).closest(xn)[0],o=_.getSelectorFromElement(this._element);if(e){var r="UL"===e.nodeName||"OL"===e.nodeName?Un:Fn;i=(i=g.makeArray(g(e).find(r)))[i.length-1]}var s=g.Event(On.HIDE,{relatedTarget:this._element}),a=g.Event(On.SHOW,{relatedTarget:i});if(i&&g(i).trigger(s),g(this._element).trigger(a),!a.isDefaultPrevented()&&!s.isDefaultPrevented()){o&&(t=document.querySelector(o)),this._activate(this._element,e);var l=function(){var t=g.Event(On.HIDDEN,{relatedTarget:n._element}),e=g.Event(On.SHOWN,{relatedTarget:i});g(i).trigger(t),g(n._element).trigger(e)};t?this._activate(t,t.parentNode,l):l()}}},t.dispose=function(){g.removeData(this._element,wn),this._element=null},t._activate=function(t,e,n){var i=this,o=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?g(e).children(Fn):g(e).find(Un))[0],r=n&&o&&g(o).hasClass(jn),s=function(){return i._transitionComplete(t,o,n)};if(o&&r){var a=_.getTransitionDurationFromElement(o);g(o).removeClass(Hn).one(_.TRANSITION_END,s).emulateTransitionEnd(a)}else s()},t._transitionComplete=function(t,e,n){if(e){g(e).removeClass(Pn);var i=g(e.parentNode).find(Mn)[0];i&&g(i).removeClass(Pn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}if(g(t).addClass(Pn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),_.reflow(t),t.classList.contains(jn)&&t.classList.add(Hn),t.parentNode&&g(t.parentNode).hasClass(kn)){var o=g(t).closest(Rn)[0];if(o){var r=[].slice.call(o.querySelectorAll(qn));g(r).addClass(Pn)}t.setAttribute("aria-expanded",!0)}n&&n()},i._jQueryInterface=function(n){return this.each(function(){var t=g(this),e=t.data(wn);if(e||(e=new i(this),t.data(wn,e)),"string"==typeof n){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}}]),i}();g(document).on(On.CLICK_DATA_API,Wn,function(t){t.preventDefault(),Kn._jQueryInterface.call(g(this),"show")}),g.fn.tab=Kn._jQueryInterface,g.fn.tab.Constructor=Kn,g.fn.tab.noConflict=function(){return g.fn.tab=Nn,Kn._jQueryInterface};var Qn="toast",Bn="bs.toast",Vn="."+Bn,Yn=g.fn[Qn],zn={CLICK_DISMISS:"click.dismiss"+Vn,HIDE:"hide"+Vn,HIDDEN:"hidden"+Vn,SHOW:"show"+Vn,SHOWN:"shown"+Vn},Xn="fade",$n="hide",Gn="show",Jn="showing",Zn={animation:"boolean",autohide:"boolean",delay:"number"},ti={animation:!0,autohide:!0,delay:500},ei='[data-dismiss="toast"]',ni=function(){function i(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var t=i.prototype;return t.show=function(){var t=this;g(this._element).trigger(zn.SHOW),this._config.animation&&this._element.classList.add(Xn);var e=function(){t._element.classList.remove(Jn),t._element.classList.add(Gn),g(t._element).trigger(zn.SHOWN),t._config.autohide&&t.hide()};if(this._element.classList.remove($n),this._element.classList.add(Jn),this._config.animation){var n=_.getTransitionDurationFromElement(this._element);g(this._element).one(_.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},t.hide=function(t){var e=this;this._element.classList.contains(Gn)&&(g(this._element).trigger(zn.HIDE),t?this._close():this._timeout=setTimeout(function(){e._close()},this._config.delay))},t.dispose=function(){clearTimeout(this._timeout),this._timeout=null,this._element.classList.contains(Gn)&&this._element.classList.remove(Gn),g(this._element).off(zn.CLICK_DISMISS),g.removeData(this._element,Bn),this._element=null,this._config=null},t._getConfig=function(t){return t=l({},ti,g(this._element).data(),"object"==typeof t&&t?t:{}),_.typeCheckConfig(Qn,t,this.constructor.DefaultType),t},t._setListeners=function(){var t=this;g(this._element).on(zn.CLICK_DISMISS,ei,function(){return t.hide(!0)})},t._close=function(){var t=this,e=function(){t._element.classList.add($n),g(t._element).trigger(zn.HIDDEN)};if(this._element.classList.remove(Gn),this._config.animation){var n=_.getTransitionDurationFromElement(this._element);g(this._element).one(_.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},i._jQueryInterface=function(n){return this.each(function(){var t=g(this),e=t.data(Bn);if(e||(e=new i(this,"object"==typeof n&&n),t.data(Bn,e)),"string"==typeof n){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n](this)}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"DefaultType",get:function(){return Zn}},{key:"Default",get:function(){return ti}}]),i}();g.fn[Qn]=ni._jQueryInterface,g.fn[Qn].Constructor=ni,g.fn[Qn].noConflict=function(){return g.fn[Qn]=Yn,ni._jQueryInterface},function(){if("undefined"==typeof g)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var t=g.fn.jquery.split(" ")[0].split(".");if(t[0]<2&&t[1]<9||1===t[0]&&9===t[1]&&t[2]<1||4<=t[0])throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(),t.Util=_,t.Alert=p,t.Button=P,t.Carousel=lt,t.Collapse=bt,t.Dropdown=Jt,t.Modal=ve,t.Popover=sn,t.Scrollspy=Dn,t.Tab=Kn,t.Toast=ni,t.Tooltip=Be,Object.defineProperty(t,"__esModule",{value:!0})});
+//# sourceMappingURL=bootstrap.min.js.map
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.eot b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.eot
new file mode 100644
index 000000000..671b37bcb
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.eot differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.svg b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.svg
new file mode 100644
index 000000000..1f37793af
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.svg
@@ -0,0 +1,15 @@
+
+
+
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.ttf b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.ttf
new file mode 100644
index 000000000..85ae09356
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.ttf differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.woff b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.woff
new file mode 100644
index 000000000..69c2a5d66
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/fonts/icomoon.woff differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/jquery-3.6.0.min.js b/Firmware/RTK_Surveyor/AP-Config/src/jquery-3.6.0.min.js
new file mode 100644
index 000000000..c4c6022f2
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/src/jquery-3.6.0.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0= 121) {
+ select = ge("dynamicModel");
+ let newOption = new Option('Mower', '11');
+ select.add(newOption, undefined);
+ newOption = new Option('E-Scooter', '12');
+ select.add(newOption, undefined);
+ }
+ }
+ //Strings generated by RTK unit
+ else if (id.includes("sdFreeSpace")
+ || id.includes("sdSize")
+ || id.includes("hardwareID")
+ || id.includes("zedFirmwareVersion")
+ || id.includes("daysRemaining")
+ || id.includes("profile0Name")
+ || id.includes("profile1Name")
+ || id.includes("profile2Name")
+ || id.includes("profile3Name")
+ || id.includes("profile4Name")
+ || id.includes("profile5Name")
+ || id.includes("profile6Name")
+ || id.includes("profile7Name")
+ || id.includes("radioMAC")
+ || id.includes("deviceBTID")
+ || id.includes("logFileName")
+ || id.includes("batteryPercent")
+ ) {
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("rtkFirmwareVersion")) {
+ ge("rtkFirmwareVersion").innerHTML = val;
+ ge("rtkFirmwareVersionUpgrade").innerHTML = val;
+ }
+ else if (id.includes("confirmReset")) {
+ resetComplete();
+ }
+ else if (id.includes("confirmDataReceipt")) {
+ confirmDataReceipt();
+ }
+
+ else if (id.includes("profileNumber")) {
+ currentProfileNumber = val;
+ $("input[name=profileRadio][value=" + currentProfileNumber + "]").prop('checked', true);
+ }
+ else if (id.includes("firmwareUploadComplete")) {
+ firmwareUploadComplete();
+ }
+ else if (id.includes("firmwareUploadStatus")) {
+ firmwareUploadStatus(val);
+ }
+ else if (id.includes("geodeticLat")) {
+ geodeticLat = val;
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("geodeticLon")) {
+ geodeticLon = val;
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("geodeticAlt")) {
+ geodeticAlt = val;
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("ecefX")) {
+ ecefX = val;
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("ecefY")) {
+ ecefY = val;
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("ecefZ")) {
+ ecefZ = val;
+ ge(id).innerHTML = val;
+ }
+ else if (id.includes("espnowPeerCount")) {
+ if (val > 0)
+ ge("peerMACs").innerHTML = "";
+ }
+ else if (id.includes("peerMAC")) {
+ ge("peerMACs").innerHTML += val + "
";
+ }
+ else if (id.includes("stationECEF")) {
+ recordsECEF.push(val);
+ }
+ else if (id.includes("stationGeodetic")) {
+ recordsGeodetic.push(val);
+ }
+ else if (id.includes("fmName")) {
+ lastFileName = val;
+ }
+ else if (id.includes("fmSize")) {
+ fileTableText += "";
+ fileTableText += "" + lastFileName + " | ";
+ fileTableText += "" + val + " | ";
+ fileTableText += " | ";
+ fileTableText += "
";
+ }
+ else if (id.includes("fmNext")) {
+ sendFile();
+ }
+ else if (id.includes("UBX_")) {
+ var messageName = id;
+ var messageRate = val;
+ var messageNameLabel = "";
+
+ var messageData = messageName.split('_');
+ if (messageData.length >= 3) {
+ var messageType = messageData[1]; //UBX_RTCM_1074 = RTCM
+ if (lastMessageType != messageType) {
+ lastMessageType = messageType;
+ messageText += "
";
+ }
+
+ messageNameLabel = messageData[1] + "_" + messageData[2]; //RTCM_1074
+ if (messageData.length == 4) {
+ messageNameLabel = messageData[1] + "_" + messageData[2] + "_" + messageData[3]; //RTCM_4072_1
+ }
+
+ //Remove Base if seen
+ messageNameLabel = messageNameLabel.split('Base').join(''); //UBX_RTCM_1074Base
+ }
+
+ messageText += "";
+ }
+ else if (id.includes("checkingNewFirmware")) {
+ checkingNewFirmware();
+ }
+ else if (id.includes("newFirmwareVersion")) {
+ newFirmwareVersion(val);
+ }
+ else if (id.includes("gettingNewFirmware")) {
+ gettingNewFirmware();
+ }
+ else if (id.includes("otaFirmwareStatus")) {
+ otaFirmwareStatus(val);
+ }
+ else if (id.includes("batteryIconFileName")) {
+ ge("batteryIconFileName").src = val;
+ }
+ else if (id.includes("coordinateInputType")) {
+ coordinateInputType = val;
+ }
+ else if (id.includes("fixedLat")) {
+ fixedLat = val;
+ }
+ else if (id.includes("fixedLong")) {
+ fixedLong = val;
+ }
+
+ //Check boxes / radio buttons
+ else if (val == "true") {
+ try {
+ ge(id).checked = true;
+ } catch (error) {
+ console.log("Issue with ID: " + id)
+ }
+ }
+ else if (val == "false") {
+ try {
+ ge(id).checked = false;
+ } catch (error) {
+ console.log("Issue with ID: " + id)
+ }
+ }
+
+ //All regular input boxes and values
+ else {
+ try {
+ ge(id).value = val;
+ } catch (error) {
+ console.log("Issue with ID: " + id)
+ }
+ }
+ }
+ //console.log("Settings loaded");
+
+ ge("profileChangeMessage").innerHTML = '';
+ ge("resetProfileMsg").innerHTML = '';
+
+ //Don't update if all we received was coordinate info
+ if (fullPageUpdate) {
+ fullPageUpdate = false;
+
+ //Force element updates
+ ge("measurementRateHz").dispatchEvent(new CustomEvent('change'));
+ ge("baseTypeSurveyIn").dispatchEvent(new CustomEvent('change'));
+ ge("baseTypeFixed").dispatchEvent(new CustomEvent('change'));
+ ge("fixedBaseCoordinateTypeECEF").dispatchEvent(new CustomEvent('change'));
+ ge("fixedBaseCoordinateTypeGeo").dispatchEvent(new CustomEvent('change'));
+ ge("enableLogging").dispatchEvent(new CustomEvent('change'));
+ ge("enableNtripClient").dispatchEvent(new CustomEvent('change'));
+ ge("enableNtripServer").dispatchEvent(new CustomEvent('change'));
+ ge("dataPortChannel").dispatchEvent(new CustomEvent('change'));
+ ge("enableExternalPulse").dispatchEvent(new CustomEvent('change'));
+ ge("enablePointPerfectCorrections").dispatchEvent(new CustomEvent('change'));
+ ge("radioType").dispatchEvent(new CustomEvent('change'));
+ ge("antennaReferencePoint").dispatchEvent(new CustomEvent('change'));
+ ge("autoIMUmountAlignment").dispatchEvent(new CustomEvent('change'));
+ ge("enableARPLogging").dispatchEvent(new CustomEvent('change'));
+
+ updateECEFList();
+ updateGeodeticList();
+ tcpClientBoxes();
+ tcpServerBoxes();
+ udpBoxes();
+ dhcpEthernet();
+ updateLatLong();
+ }
+
+}
+
+function hide(id) {
+ ge(id).style.display = "none";
+}
+
+function show(id) {
+ ge(id).style.display = "block";
+}
+
+//Create CSV of all setting data
+function sendData() {
+ var settingCSV = "";
+
+ //Input boxes
+ var clsElements = document.querySelectorAll(".form-control, .form-dropdown");
+ for (let x = 0; x < clsElements.length; x++) {
+ settingCSV += clsElements[x].id + "," + clsElements[x].value + ",";
+ }
+
+ //Check boxes, radio buttons
+ //Remove file manager files
+ clsElements = document.querySelectorAll(".form-check-input:not(.fileManagerCheck), .form-radio");
+
+ for (let x = 0; x < clsElements.length; x++) {
+ settingCSV += clsElements[x].id + "," + clsElements[x].checked + ",";
+ }
+
+ for (let x = 0; x < recordsECEF.length; x++) {
+ settingCSV += "stationECEF" + x + ',' + recordsECEF[x] + ",";
+ }
+
+ for (let x = 0; x < recordsGeodetic.length; x++) {
+ settingCSV += "stationGeodetic" + x + ',' + recordsGeodetic[x] + ",";
+ }
+
+ console.log("Sending: " + settingCSV);
+ websocket.send(settingCSV);
+
+ sendDataTimeout = setTimeout(sendData, 2000);
+}
+
+function showError(id, errorText) {
+ ge(id + 'Error').innerHTML = '
Error: ' + errorText;
+}
+
+function clearError(id) {
+ ge(id + 'Error').innerHTML = '';
+}
+
+function showSuccess(id, msg) {
+ ge(id + 'Success').innerHTML = '
Success: ' + msg;
+}
+function clearSuccess(id) {
+ ge(id + 'Success').innerHTML = '';
+}
+
+function showMsg(id, msg, error = false) {
+ if (error == true) {
+ try {
+ ge(id).classList.remove('inlineSuccess');
+ ge(id).classList.add('inlineError');
+ }
+ catch { }
+ }
+ else {
+ try {
+ ge(id).classList.remove('inlineError');
+ ge(id).classList.add('inlineSuccess');
+ }
+ catch { }
+ }
+ ge(id).innerHTML = '
' + msg;
+}
+function showMsgError(id, msg) {
+ showMsg(id, "Error: " + msg, true);
+}
+function clearMsg(id, msg) {
+ ge(id).innerHTML = '';
+}
+
+var errorCount = 0;
+
+function checkMessageValue(id) {
+ checkElementValue(id, 0, 255, "Must be between 0 and 255", "collapseGNSSConfigMsg");
+}
+
+function collapseSection(section, caret) {
+ ge(section).classList.remove('show');
+ ge(caret).classList.remove('icon-caret-down');
+ ge(caret).classList.remove('icon-caret-up');
+ ge(caret).classList.add('icon-caret-down');
+}
+
+function validateFields() {
+ //Collapse all sections
+ collapseSection("collapseProfileConfig", "profileCaret");
+ collapseSection("collapseGNSSConfig", "gnssCaret");
+ collapseSection("collapseGNSSConfigMsg", "gnssMsgCaret");
+ collapseSection("collapseBaseConfig", "baseCaret");
+ collapseSection("collapseSensorConfig", "sensorCaret");
+ collapseSection("collapsePPConfig", "pointPerfectCaret");
+ collapseSection("collapsePortsConfig", "portsCaret");
+ collapseSection("collapseWiFiConfig", "wifiCaret");
+ collapseSection("collapseTCPUDPConfig", "tcpUdpCaret");
+ collapseSection("collapseRadioConfig", "radioCaret");
+ collapseSection("collapseSystemConfig", "systemCaret");
+ collapseSection("collapseEthernetConfig", "ethernetCaret");
+ collapseSection("collapseNTPConfig", "ntpCaret");
+
+ errorCount = 0;
+
+ //Profile Config
+ checkElementString("profileName", 1, 49, "Must be 1 to 49 characters", "collapseProfileConfig");
+
+ //GNSS Config
+ checkElementValue("measurementRateHz", 0.00012, 10, "Must be between 0.00012 and 10Hz", "collapseGNSSConfig");
+ checkConstellations();
+
+ checkElementValue("minElev", 0, 90, "Must be between 0 and 90", "collapseGNSSConfig");
+ checkElementValue("minCNO", 0, 90, "Must be between 0 and 90", "collapseGNSSConfig");
+
+ if (ge("enableNtripClient").checked) {
+ checkElementString("ntripClient_CasterHost", 1, 45, "Must be 1 to 45 characters", "collapseGNSSConfig");
+ checkElementValue("ntripClient_CasterPort", 1, 99999, "Must be 1 to 99999", "collapseGNSSConfig");
+ checkElementString("ntripClient_MountPoint", 1, 30, "Must be 1 to 30 characters", "collapseGNSSConfig");
+ checkElementCasterUser("ntripClient_CasterHost", "ntripClient_CasterUser", "rtk2go.com", "@", "Must be an email address", "collapseGNSSConfig");
+ }
+ // Don't overwrite with the defaults here. User may want to disable NTRIP but not lose the existing settings.
+ // else {
+ // clearElement("ntripClient_CasterHost", "rtk2go.com");
+ // clearElement("ntripClient_CasterPort", 2101);
+ // clearElement("ntripClient_MountPoint", "bldr_SparkFun1");
+ // clearElement("ntripClient_MountPointPW");
+ // clearElement("ntripClient_CasterUser", "test@test.com");
+ // clearElement("ntripClient_CasterUserPW", "");
+ // ge("ntripClient_TransmitGGA").checked = true;
+ // }
+
+ //Check all UBX message boxes
+ var ubxMessages = document.querySelectorAll('input[id^=UBX_]'); //match all ids starting with UBX_
+ for (let x = 0; x < ubxMessages.length; x++) {
+ var messageName = ubxMessages[x].id;
+ checkMessageValue(messageName);
+ }
+
+ //Base Config
+ if (platformPrefix != "Express Plus") {
+ if (ge("baseTypeSurveyIn").checked) {
+ checkElementValue("observationSeconds", 60, 600, "Must be between 60 to 600", "collapseBaseConfig");
+ checkElementValue("observationPositionAccuracy", 1, 5.1, "Must be between 1.0 to 5.0", "collapseBaseConfig");
+
+ clearElement("fixedEcefX", -1280206.568);
+ clearElement("fixedEcefY", -4716804.403);
+ clearElement("fixedEcefZ", 4086665.484);
+ clearElement("fixedLatText", 40.09029479);
+ clearElement("fixedLongText", -105.18505761);
+ clearElement("fixedAltitude", 1560.089);
+ clearElement("antennaHeight", 0);
+ clearElement("antennaReferencePoint", 0);
+ }
+ else {
+ clearElement("observationSeconds", 60);
+ clearElement("observationPositionAccuracy", 5.0);
+
+ if (ge("fixedBaseCoordinateTypeECEF").checked) {
+ clearElement("fixedLatText", 40.09029479);
+ clearElement("fixedLongText", -105.18505761);
+ clearElement("fixedAltitude", 1560.089);
+ clearElement("antennaHeight", 0);
+ clearElement("antennaReferencePoint", 0);
+
+ checkElementValue("fixedEcefX", -7000000, 7000000, "Must be -7000000 to 7000000", "collapseBaseConfig");
+ checkElementValue("fixedEcefY", -7000000, 7000000, "Must be -7000000 to 7000000", "collapseBaseConfig");
+ checkElementValue("fixedEcefZ", -7000000, 7000000, "Must be -7000000 to 7000000", "collapseBaseConfig");
+ }
+ else {
+ clearElement("fixedEcefX", -1280206.568);
+ clearElement("fixedEcefY", -4716804.403);
+ clearElement("fixedEcefZ", 4086665.484);
+
+ checkLatLong(); //Verify Lat/Long input type
+ checkElementValue("fixedAltitude", -11034, 8849, "Must be -11034 to 8849", "collapseBaseConfig");
+
+ checkElementValue("antennaHeight", -15000, 15000, "Must be -15000 to 15000", "collapseBaseConfig");
+ checkElementValue("antennaReferencePoint", -200.0, 200.0, "Must be -200.0 to 200.0", "collapseBaseConfig");
+ }
+ }
+
+ if (ge("enableNtripServer").checked == true) {
+ checkElementString("ntripServer_CasterHost_0", 1, 49, "Must be 1 to 49 characters", "collapseBaseConfig");
+ checkElementValue("ntripServer_CasterPort_0", 1, 99999, "Must be 1 to 99999", "collapseBaseConfig");
+ checkElementString("ntripServer_CasterUser_0", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_CasterUserPW_0", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_MountPoint_0", 1, 49, "Must be 1 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_MountPointPW_0", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_CasterHost_1", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementValue("ntripServer_CasterPort_1", 0, 99999, "Must be 0 to 99999", "collapseBaseConfig");
+ checkElementString("ntripServer_CasterUser_1", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_CasterUserPW_1", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_MountPoint_1", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ checkElementString("ntripServer_MountPointPW_1", 0, 49, "Must be 0 to 49 characters", "collapseBaseConfig");
+ }
+ // Don't overwrite with the defaults here. User may want to disable NTRIP but not lose the existing settings.
+ // else {
+ // clearElement("ntripServer_CasterHost_0", "rtk2go.com");
+ // clearElement("ntripServer_CasterPort_0", 2101);
+ // clearElement("ntripServer_CasterUser_0", "test@test.com");
+ // clearElement("ntripServer_CasterUserPW_0", "");
+ // clearElement("ntripServer_MountPoint_0", "bldr_dwntwn2");
+ // clearElement("ntripServer_MountPointPW_0", "WR5wRo4H");
+ // clearElement("ntripServer_CasterHost_1", "");
+ // clearElement("ntripServer_CasterPort_1", 0);
+ // clearElement("ntripServer_CasterUser_1", "");
+ // clearElement("ntripServer_CasterUserPW_1", "");
+ // clearElement("ntripServer_MountPoint_1", "");
+ // clearElement("ntripServer_MountPointPW_1", "");
+ // }
+ }
+
+ //PointPerfect Config
+ if (platformPrefix == "Facet L-Band" || platformPrefix == "Facet L-Band Direct") {
+ if (ge("enablePointPerfectCorrections").checked == true) {
+ value = ge("pointPerfectDeviceProfileToken").value;
+ if (value.length > 0)
+ checkElementString("pointPerfectDeviceProfileToken", 36, 36, "Must be 36 characters", "collapsePPConfig");
+ }
+ else {
+ clearElement("pointPerfectDeviceProfileToken", "");
+ ge("autoKeyRenewal").checked = true;
+ }
+ }
+
+ //Sensor Config
+ if (platformPrefix == "Express Plus") {
+ if (ge("autoIMUmountAlignment").checked == false) {
+ checkElementValue("imuYaw", 0, 360, "Must be between 0.0 to 360.0", "collapseSensorConfig");
+ checkElementValue("imuPitch", -90, 90, "Must be between -90.0 to 90.0", "collapseSensorConfig");
+ checkElementValue("imuRoll", -180, 180, "Must be between -180.0 to 180.0", "collapseSensorConfig");
+ }
+ }
+
+ //WiFi Config
+ checkElementString("wifiNetwork0SSID", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork0Password", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork1SSID", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork1Password", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork2SSID", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork2Password", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork3SSID", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+ checkElementString("wifiNetwork3Password", 0, 50, "Must be 0 to 50 characters", "collapseWiFiConfig");
+
+ //TCP/UDP Config
+ if (ge("enablePvtClient").checked) {
+ checkElementValue("pvtClientPort", 1, 65535, "Must be 1 to 65535", "collapseTCPUDPConfig");
+ checkElementString("pvtClientHost", 1, 49, "Must be 1 to 49 characters", "collapseTCPUDPConfig");
+ }
+ if (ge("enablePvtServer").checked) {
+ checkElementValue("pvtServerPort", 1, 65535, "Must be 1 to 65535", "collapseTCPUDPConfig");
+ }
+ if (ge("enablePvtUdpServer").checked) {
+ checkElementValue("pvtUdpServerPort", 1, 65535, "Must be 1 to 65535", "collapseTCPUDPConfig");
+ }
+ checkCheckboxMutex("enablePvtClient", "enablePvtServer", "TCP Client and Server can not be enabled at the same time", "collapseTCPUDPConfig");
+
+ //System Config
+ if (ge("enableLogging").checked) {
+ checkElementValue("maxLogTime_minutes", 1, 1051200, "Must be 1 to 1,051,200", "collapseSystemConfig");
+ checkElementValue("maxLogLength_minutes", 1, 1051200, "Must be 1 to 1,051,200", "collapseSystemConfig");
+ }
+ else {
+ clearElement("maxLogTime_minutes", 60 * 24);
+ clearElement("maxLogLength_minutes", 60 * 24);
+ }
+
+ if (ge("enableARPLogging").checked) {
+ checkElementValue("ARPLoggingInterval", 1, 600, "Must be 1 to 600", "collapseSystemConfig");
+ }
+ else {
+ clearElement("ARPLoggingInterval", 10);
+ }
+
+ //Ethernet
+ if (platformPrefix == "Reference Station") {
+ if (ge("ethernetDHCP").checked == false) {
+ checkElementIPAddress("ethernetIP", "Must be nnn.nnn.nnn.nnn", "collapseEthernetConfig");
+ checkElementIPAddress("ethernetDNS", "Must be nnn.nnn.nnn.nnn", "collapseEthernetConfig");
+ checkElementIPAddress("ethernetGateway", "Must be nnn.nnn.nnn.nnn", "collapseEthernetConfig");
+ checkElementIPAddress("ethernetSubnet", "Must be nnn.nnn.nnn.nnn", "collapseEthernetConfig");
+ }
+ checkElementValue("ethernetNtpPort", 0, 65535, "Must be 0 to 65535", "collapseEthernetConfig");
+ }
+
+ //NTP
+ if (platformPrefix == "Reference Station") {
+ checkElementValue("ntpPollExponent", 3, 17, "Must be 3 to 17", "collapseNTPConfig");
+ checkElementValue("ntpPrecision", -30, 0, "Must be -30 to 0", "collapseNTPConfig");
+ checkElementValue("ntpRootDelay", 0, 10000000, "Must be 0 to 10,000,000", "collapseNTPConfig");
+ 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 (platformPrefix != "Surveyor") {
+ if (ge("enableExternalPulse").checked) {
+ checkElementValue("externalPulseTimeBetweenPulse_us", 1, 60000000, "Must be 1 to 60,000,000", "collapsePortsConfig");
+ checkElementValue("externalPulseLength_us", 1, 60000000, "Must be 1 to 60,000,000", "collapsePortsConfig");
+ }
+ else {
+ clearElement("externalPulseTimeBetweenPulse_us", 100000);
+ clearElement("externalPulseLength_us", 1000000);
+ ge("externalPulsePolarity").value = 0;
+ }
+ }
+}
+
+var currentProfileNumber = 0;
+
+function changeProfile() {
+ validateFields();
+
+ if (errorCount == 1) {
+ showError('saveBtn', "Please clear " + errorCount + " error");
+ clearSuccess('saveBtn');
+ $("input[name=profileRadio][value=" + currentProfileNumber + "]").prop('checked', true);
+ }
+ else if (errorCount > 1) {
+ showError('saveBtn', "Please clear " + errorCount + " errors");
+ clearSuccess('saveBtn');
+ $("input[name=profileRadio][value=" + currentProfileNumber + "]").prop('checked', true);
+ }
+ else {
+ ge("profileChangeMessage").innerHTML = 'Loading. Please wait...';
+
+ currentProfileNumber = document.querySelector('input[name=profileRadio]:checked').value;
+
+ sendData();
+ clearError('saveBtn');
+ showSuccess('saveBtn', "Saving...");
+
+ websocket.send("setProfile," + currentProfileNumber + ",");
+
+ ge("collapseProfileConfig").classList.add('show');
+ collapseSection("collapseGNSSConfig", "gnssCaret");
+ collapseSection("collapseGNSSConfigMsg", "gnssMsgCaret");
+ collapseSection("collapseBaseConfig", "baseCaret");
+ collapseSection("collapseSensorConfig", "sensorCaret");
+ collapseSection("collapsePPConfig", "pointPerfectCaret");
+ collapseSection("collapsePortsConfig", "portsCaret");
+ collapseSection("collapseWiFiConfig", "wifiCaret");
+ collapseSection("collapseTCPUDPConfig", "tcpUdpCaret");
+ collapseSection("collapseRadioConfig", "radioCaret");
+ collapseSection("collapseSystemConfig", "systemCaret");
+ collapseSection("collapseEthernetConfig", "ethernetCaret");
+ collapseSection("collapseNTPConfig", "ntpCaret");
+ }
+}
+
+function saveConfig() {
+ validateFields();
+
+ if (errorCount == 1) {
+ showError('saveBtn', "Please clear " + errorCount + " error");
+ clearSuccess('saveBtn');
+ }
+ else if (errorCount > 1) {
+ showError('saveBtn', "Please clear " + errorCount + " errors");
+ clearSuccess('saveBtn');
+ }
+ else {
+ sendData();
+ clearError('saveBtn');
+ showSuccess('saveBtn', "Saving...");
+ }
+
+}
+
+function checkConstellations() {
+ if (ge("ubxConstellationsGPS").checked == false
+ && ge("ubxConstellationsGalileo").checked == false
+ && ge("ubxConstellationsBeiDou").checked == false
+ && ge("ubxConstellationsGLONASS").checked == false
+ ) {
+ ge("collapseGNSSConfig").classList.add('show');
+ showError('ubxConstellations', "Please choose one constellation");
+ errorCount++;
+ }
+ else
+ clearError("ubxConstellations");
+}
+
+function checkBitMapValue(id, min, max, bitMap, errorText, collapseID) {
+ value = ge(id).value;
+ mask = ge(bitMap).value;
+ if ((value < min) || (value > max) || ((mask & (1 << value)) == 0)) {
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else {
+ clearError(id);
+ }
+}
+
+//Check if Lat/Long input types are decipherable
+function checkLatLong() {
+ var id = "fixedLatText";
+ var collapseID = "collapseBaseConfig";
+ ge("detectedFormatText").value = "";
+
+ var inputTypeLat = identifyInputType(ge(id).value)
+ if (inputTypeLat == CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN) {
+ var errorText = "Coordinate format unknown";
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else if (convertedCoordinate < -180 || convertedCoordinate > 180) {
+ var errorText = "Must be -180 to 180";
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else
+ clearError(id);
+
+ id = "fixedLongText";
+ var inputTypeLong = identifyInputType(ge(id).value)
+ if (inputTypeLong == CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN) {
+ var errorText = "Coordinate format unknown";
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else if (convertedCoordinate < -180 || convertedCoordinate > 180) {
+ var errorText = "Must be -180 to 180";
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else
+ clearError(id);
+
+ if (inputTypeLong != inputTypeLat) {
+ var errorText = "Formats must match";
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ ge("detectedFormatText").innerHTML = printableInputType(CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN);
+ }
+ else
+ ge("detectedFormatText").innerHTML = printableInputType(inputTypeLat);
+}
+
+//Based on the coordinateInputType, format the lat/long text boxes
+function updateLatLong() {
+ ge("fixedLatText").value = convertInput(fixedLat, coordinateInputType);
+ ge("fixedLongText").value = convertInput(fixedLong, coordinateInputType);
+ checkLatLong(); //Updates the detected format
+}
+
+function checkElementValue(id, min, max, errorText, collapseID) {
+ value = ge(id).value;
+ if (value < min || value > max || value == "") {
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ if (collapseID == "collapseGNSSConfigMsg") ge("collapseGNSSConfig").classList.add('show');
+ errorCount++;
+ }
+ else
+ clearError(id);
+}
+
+function checkElementString(id, min, max, errorText, collapseID) {
+ value = ge(id).value;
+ if (value.length < min || value.length > max) {
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else
+ clearError(id);
+}
+
+function checkElementIPAddress(id, errorText, collapseID) {
+ value = ge(id).value;
+ var data = value.split('.');
+ if ((data.length != 4)
+ || ((data[0] == "") || (isNaN(Number(data[0]))) || (data[0] < 0) || (data[0] > 255))
+ || ((data[1] == "") || (isNaN(Number(data[1]))) || (data[1] < 0) || (data[1] > 255))
+ || ((data[2] == "") || (isNaN(Number(data[2]))) || (data[2] < 0) || (data[2] > 255))
+ || ((data[3] == "") || (isNaN(Number(data[3]))) || (data[3] < 0) || (data[3] > 255))) {
+ ge(id + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else
+ clearError(id);
+}
+
+function checkElementCasterUser(host, user, url, needs, errorText, collapseID) {
+ if (ge(host).value.toLowerCase().includes(url)) {
+ value = ge(user).value;
+ if ((value.length < 1) || (value.length > 49) || (value.includes(needs) == false)) {
+ ge(user + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else
+ clearError(user);
+ }
+ else
+ clearError(user);
+}
+
+function checkCheckboxMutex(id1, id2, errorText, collapseID) {
+ if ((ge(id1).checked) && (ge(id2).checked)) {
+ ge(id1 + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(id2 + 'Error').innerHTML = 'Error: ' + errorText;
+ ge(collapseID).classList.add('show');
+ errorCount++;
+ }
+ else {
+ clearError(id1);
+ clearError(id2);
+ }
+}
+
+function clearElement(id, value) {
+ ge(id).value = value;
+ clearError(id);
+}
+
+function resetToFactoryDefaults() {
+ ge("factoryDefaultsMsg").innerHTML = "Defaults Applied. Please wait for device reset...";
+ websocket.send("factoryDefaultReset,1,");
+}
+
+function zeroElement(id) {
+ ge(id).value = 0;
+}
+
+function zeroMessages() {
+
+ var ubxMessages = document.querySelectorAll('input[id^=UBX_]'); //match all ids starting with UBX_
+ for (let x = 0; x < ubxMessages.length; x++) {
+ var messageName = ubxMessages[x].id;
+ zeroElement(messageName);
+ }
+}
+function resetToNmeaDefaults() {
+ zeroMessages();
+ ge("UBX_NMEA_GGA").value = 1;
+ ge("UBX_NMEA_GSA").value = 1;
+ ge("UBX_NMEA_GST").value = 1;
+ ge("UBX_NMEA_GSV").value = 4;
+ ge("UBX_NMEA_RMC").value = 1;
+}
+function resetToLoggingDefaults() {
+ zeroMessages();
+ ge("UBX_NMEA_GGA").value = 1;
+ ge("UBX_NMEA_GSA").value = 1;
+ ge("UBX_NMEA_GST").value = 1;
+ ge("UBX_NMEA_GSV").value = 4;
+ ge("UBX_NMEA_RMC").value = 1;
+ ge("UBX_RXM_RAWX").value = 1;
+ ge("UBX_RXM_SFRBX").value = 1;
+}
+
+function resetToRTCMDefaults() {
+ ge("UBX_RTCM_1005Base").value = 1;
+ ge("UBX_RTCM_1074Base").value = 1;
+ ge("UBX_RTCM_1077Base").value = 0;
+ ge("UBX_RTCM_1084Base").value = 1;
+ ge("UBX_RTCM_1087Base").value = 0;
+
+ ge("UBX_RTCM_1094Base").value = 1;
+ ge("UBX_RTCM_1097Base").value = 0;
+ ge("UBX_RTCM_1124Base").value = 1;
+ ge("UBX_RTCM_1127Base").value = 0;
+ ge("UBX_RTCM_1230Base").value = 10;
+
+ ge("UBX_RTCM_4072_0Base").value = 0;
+ ge("UBX_RTCM_4072_1Base").value = 0;
+}
+
+function resetToLowBandwidthRTCM() {
+ ge("UBX_RTCM_1005Base").value = 10;
+ ge("UBX_RTCM_1074Base").value = 2;
+ ge("UBX_RTCM_1077Base").value = 0;
+ ge("UBX_RTCM_1084Base").value = 2;
+ ge("UBX_RTCM_1087Base").value = 0;
+
+ ge("UBX_RTCM_1094Base").value = 2;
+ ge("UBX_RTCM_1097Base").value = 0;
+ ge("UBX_RTCM_1124Base").value = 2;
+ ge("UBX_RTCM_1127Base").value = 0;
+ ge("UBX_RTCM_1230Base").value = 10;
+
+ ge("UBX_RTCM_4072_0Base").value = 0;
+ ge("UBX_RTCM_4072_1Base").value = 0;
+}
+
+function useECEFCoordinates() {
+ ge("fixedEcefX").value = ecefX;
+ ge("fixedEcefY").value = ecefY;
+ ge("fixedEcefZ").value = ecefZ;
+}
+function useGeodeticCoordinates() {
+ ge("fixedLatText").value = geodeticLat;
+ ge("fixedLongText").value = geodeticLon;
+ ge("fixedHAE_APC").value = geodeticAlt;
+
+ $("input[name=markRadio][value=1]").prop('checked', true);
+ $("input[name=markRadio][value=2]").prop('checked', false);
+
+ adjustHAE();
+}
+
+function startNewLog() {
+ websocket.send("startNewLog,1,");
+}
+
+function exitConfig() {
+ hide("mainPage");
+ show("resetInProcess");
+
+ websocket.send("exitAndReset,1,");
+ resetTimeout = setTimeout(exitConfig, 2000);
+}
+
+function resetComplete() {
+ clearTimeout(resetTimeout);
+ hide("mainPage");
+ hide("resetInProcess");
+ show("resetComplete");
+}
+
+//Called when the ESP32 has confirmed receipt of data over websocket from AP config page
+function confirmDataReceipt() {
+ //Determine which function sent the original data
+ if (sendDataTimeout != null) {
+ clearTimeout(sendDataTimeout);
+ showSuccess('saveBtn', "All Saved!");
+ }
+ else {
+ console.log("Unknown owner of confirmDataReceipt");
+ }
+}
+
+function firmwareUploadWait() {
+ var file = ge("submitFirmwareFile").files[0];
+ var formdata = new FormData();
+ formdata.append("submitFirmwareFile", file);
+ var ajax = new XMLHttpRequest();
+ ajax.open("POST", "/upload");
+ ajax.send(formdata);
+
+ ge("firmwareUploadMsg").innerHTML = "
Uploading, please wait...";
+}
+
+function firmwareUploadStatus(val) {
+ ge("firmwareUploadMsg").innerHTML = val;
+}
+
+function firmwareUploadComplete() {
+ show("firmwareUploadComplete");
+ hide("mainPage");
+}
+
+function forgetPairedRadios() {
+ ge("btnForgetRadiosMsg").innerHTML = "All radios forgotten.";
+ ge("peerMACs").innerHTML = "None";
+ websocket.send("forgetEspNowPeers,1,");
+}
+
+function btnResetProfile() {
+ ge("resetProfileMsg").innerHTML = "Resetting profile.";
+ websocket.send("resetProfile,1,");
+}
+
+document.addEventListener("DOMContentLoaded", (event) => {
+
+ var radios = document.querySelectorAll('input[name=profileRadio]');
+ for (var i = 0, max = radios.length; i < max; i++) {
+ radios[i].onclick = function () {
+ changeProfile();
+ }
+ }
+
+ ge("measurementRateHz").addEventListener("change", function () {
+ ge("measurementRateSec").value = 1.0 / ge("measurementRateHz").value;
+ });
+
+ ge("measurementRateSec").addEventListener("change", function () {
+ ge("measurementRateHz").value = 1.0 / ge("measurementRateSec").value;
+ });
+
+ ge("baseTypeSurveyIn").addEventListener("change", function () {
+ if (ge("baseTypeSurveyIn").checked) {
+ show("surveyInConfig");
+ hide("fixedConfig");
+ }
+ });
+
+ ge("baseTypeFixed").addEventListener("change", function () {
+ if (ge("baseTypeFixed").checked) {
+ show("fixedConfig");
+ hide("surveyInConfig");
+ }
+ });
+
+ ge("fixedBaseCoordinateTypeECEF").addEventListener("change", function () {
+ if (ge("fixedBaseCoordinateTypeECEF").checked) {
+ show("ecefConfig");
+ hide("geodeticConfig");
+ }
+ });
+
+ ge("fixedBaseCoordinateTypeGeo").addEventListener("change", function () {
+ if (ge("fixedBaseCoordinateTypeGeo").checked) {
+ hide("ecefConfig");
+ show("geodeticConfig");
+
+ if (platformPrefix == "Facet") {
+ ge("antennaReferencePoint").value = 61.4;
+ }
+ else if (platformPrefix == "Facet L-Band" || platformPrefix == "Facet L-Band Direct") {
+ ge("antennaReferencePoint").value = 69.0;
+ }
+ else {
+ ge("antennaReferencePoint").value = 0.0;
+ }
+ }
+ });
+
+ ge("enableNtripServer").addEventListener("change", function () {
+ if (ge("enableNtripServer").checked) {
+ show("ntripServerConfig");
+ }
+ else {
+ hide("ntripServerConfig");
+ }
+ });
+
+ ge("enableNtripClient").addEventListener("change", function () {
+ if (ge("enableNtripClient").checked) {
+ show("ntripClientConfig");
+ }
+ else {
+ hide("ntripClientConfig");
+ }
+ });
+
+ ge("enableFactoryDefaults").addEventListener("change", function () {
+ if (ge("enableFactoryDefaults").checked) {
+ ge("factoryDefaults").disabled = false;
+ }
+ else {
+ ge("factoryDefaults").disabled = true;
+ }
+ });
+
+ ge("dataPortChannel").addEventListener("change", function () {
+ if (ge("dataPortChannel").value == 0) {
+ show("dataPortBaudDropdown");
+ hide("externalPulseConfig");
+ }
+ else if (ge("dataPortChannel").value == 1) {
+ hide("dataPortBaudDropdown");
+ show("externalPulseConfig");
+ }
+ else {
+ hide("dataPortBaudDropdown");
+ hide("externalPulseConfig");
+ }
+ });
+
+ ge("dynamicModel").addEventListener("change", function () {
+ if (ge("dynamicModel").value != 4 && ge("enableSensorFusion").checked) {
+ ge("dynamicModelSensorFusionError").innerHTML = "
Warning: Dynamic Model not set to Automotive. Sensor Fusion is best used with the Automotive Dynamic Model.";
+ }
+ else {
+ ge("dynamicModelSensorFusionError").innerHTML = "";
+ }
+ });
+
+ ge("enableSensorFusion").addEventListener("change", function () {
+ if (ge("dynamicModel").value != 4 && ge("enableSensorFusion").checked) {
+ ge("dynamicModelSensorFusionError").innerHTML = "
Warning: Dynamic Model not set to Automotive. Sensor Fusion is best used with the Automotive Dynamic Model.";
+ }
+ else {
+ ge("dynamicModelSensorFusionError").innerHTML = "";
+ }
+ });
+
+ ge("enablePointPerfectCorrections").addEventListener("change", function () {
+ if (ge("enablePointPerfectCorrections").checked) {
+ show("ppSettingsConfig");
+ }
+ else {
+ hide("ppSettingsConfig");
+ }
+ });
+
+ ge("enableExternalPulse").addEventListener("change", function () {
+ if (ge("enableExternalPulse").checked) {
+ show("externalPulseConfigDetails");
+ }
+ else {
+ hide("externalPulseConfigDetails");
+ }
+ });
+
+ ge("radioType").addEventListener("change", function () {
+ if (ge("radioType").value == 0) {
+ hide("radioDetails");
+ }
+ else if (ge("radioType").value == 1) {
+ show("radioDetails");
+ }
+ });
+
+ ge("enableForgetRadios").addEventListener("change", function () {
+ if (ge("enableForgetRadios").checked) {
+ ge("btnForgetRadios").disabled = false;
+ }
+ else {
+ ge("btnForgetRadios").disabled = true;
+ }
+ });
+
+ ge("enableLogging").addEventListener("change", function () {
+ if (ge("enableLogging").checked) {
+ show("enableLoggingDetails");
+ }
+ else {
+ hide("enableLoggingDetails");
+ }
+ });
+
+ ge("enableARPLogging").addEventListener("change", function () {
+ if (ge("enableARPLogging").checked) {
+ show("enableARPLoggingDetails");
+ }
+ else {
+ hide("enableARPLoggingDetails");
+ }
+ });
+
+ ge("fixedAltitude").addEventListener("change", function () {
+ adjustHAE();
+ });
+
+ ge("antennaHeight").addEventListener("change", function () {
+ adjustHAE();
+ });
+
+ ge("antennaReferencePoint").addEventListener("change", function () {
+ adjustHAE();
+ });
+
+ ge("fixedHAE_APC").addEventListener("change", function () {
+ adjustHAE();
+ });
+
+ ge("autoIMUmountAlignment").addEventListener("change", function () {
+ if (ge("autoIMUmountAlignment").checked) {
+ ge("imuYaw").disabled = true;
+ ge("imuPitch").disabled = true;
+ ge("imuRoll").disabled = true;
+ }
+ else {
+ ge("imuYaw").disabled = false;
+ ge("imuPitch").disabled = false;
+ ge("imuRoll").disabled = false;
+ }
+ });
+
+})
+
+function addECEF() {
+ errorCount = 0;
+
+ nicknameECEF.value = removeBadChars(nicknameECEF.value);
+
+ checkElementString("nicknameECEF", 1, 49, "Must be 1 to 49 characters", "collapseBaseConfig");
+ checkElementValue("fixedEcefX", -7000000, 7000000, "Must be -7000000 to 7000000", "collapseBaseConfig");
+ checkElementValue("fixedEcefY", -7000000, 7000000, "Must be -7000000 to 7000000", "collapseBaseConfig");
+ checkElementValue("fixedEcefZ", -7000000, 7000000, "Must be -7000000 to 7000000", "collapseBaseConfig");
+
+ if (errorCount == 0) {
+ //Check name against the list
+ var index = 0;
+ for (; index < recordsECEF.length; ++index) {
+ var parts = recordsECEF[index].split(' ');
+ if (ge("nicknameECEF").value == parts[0]) {
+ recordsECEF[index] = nicknameECEF.value + ' ' + fixedEcefX.value + ' ' + fixedEcefY.value + ' ' + fixedEcefZ.value;
+ break;
+ }
+ }
+ if (index == recordsECEF.length)
+ recordsECEF.push(nicknameECEF.value + ' ' + fixedEcefX.value + ' ' + fixedEcefY.value + ' ' + fixedEcefZ.value);
+ }
+
+ updateECEFList();
+}
+
+function deleteECEF() {
+
+ var val = ge("StationCoordinatesECEF").value;
+ if (val > "") {
+ var parts = recordsECEF[val].split(' ');
+ var nickName = parts[0];
+
+ if (confirm("Delete location " + nickName + "?") == true) {
+ recordsECEF.splice(val, 1);
+ }
+ }
+ updateECEFList();
+}
+
+function loadECEF() {
+ var val = ge("StationCoordinatesECEF").value;
+ if (val > "") {
+ var parts = recordsECEF[val].split(' ');
+ ge("fixedEcefX").value = parts[1];
+ ge("fixedEcefY").value = parts[2];
+ ge("fixedEcefZ").value = parts[3];
+ ge("nicknameECEF").value = parts[0];
+ clearError("fixedEcefX");
+ clearError("fixedEcefY");
+ clearError("fixedEcefZ");
+ clearError("nicknameECEF");
+ }
+}
+
+//Based on recordsECEF array, update and monospace HTML list
+function updateECEFList() {
+ ge("StationCoordinatesECEF").length = 0;
+
+ if (recordsECEF.length == 0) {
+ hide("StationCoordinatesECEF");
+ nicknameECEFText.innerHTML = "No coordinates stored";
+ }
+ else {
+ show("StationCoordinatesECEF");
+ nicknameECEFText.innerHTML = "Nickname: X/Y/Z";
+ if (recordsECEF.length < 5)
+ ge("StationCoordinatesECEF").size = recordsECEF.length;
+ }
+
+ for (let index = 0; index < recordsECEF.length; ++index) {
+ var option = document.createElement('option');
+ option.text = recordsECEF[index];
+ option.value = index;
+ ge("StationCoordinatesECEF").add(option);
+ }
+
+ $("#StationCoordinatesECEF option").each(function () {
+ var parts = $(this).text().split(' ');
+ var nickname = parts[0].substring(0, 15);
+ $(this).text(nickname + ': ' + parts[1] + ' ' + parts[2] + ' ' + parts[3]).text;
+ });
+}
+
+function addGeodetic() {
+ errorCount = 0;
+
+ nicknameGeodetic.value = removeBadChars(nicknameGeodetic.value);
+
+ checkElementString("nicknameGeodetic", 1, 49, "Must be 1 to 49 characters", "collapseBaseConfig");
+ checkLatLong();
+ checkElementValue("fixedAltitude", -11034, 8849, "Must be -11034 to 8849", "collapseBaseConfig");
+ checkElementValue("antennaHeight", -15000, 15000, "Must be -15000 to 15000", "collapseBaseConfig");
+ checkElementValue("antennaReferencePoint", -200.0, 200.0, "Must be -200.0 to 200.0", "collapseBaseConfig");
+
+ if (errorCount == 0) {
+ //Check name against the list
+ var index = 0;
+ for (; index < recordsGeodetic.length; ++index) {
+ var parts = recordsGeodetic[index].split(' ');
+ if (ge("nicknameGeodetic").value == parts[0]) {
+ recordsGeodetic[index] = nicknameGeodetic.value + ' ' + fixedLatText.value + ' ' + fixedLongText.value + ' ' + fixedAltitude.value + ' ' + antennaHeight.value + ' ' + antennaReferencePoint.value;
+ break;
+ }
+ }
+ if (index == recordsGeodetic.length)
+ recordsGeodetic.push(nicknameGeodetic.value + ' ' + fixedLatText.value + ' ' + fixedLongText.value + ' ' + fixedAltitude.value + ' ' + antennaHeight.value + ' ' + antennaReferencePoint.value);
+ }
+
+ updateGeodeticList();
+}
+
+function deleteGeodetic() {
+ var val = ge("StationCoordinatesGeodetic").value;
+ if (val > "") {
+ var parts = recordsGeodetic[val].split(' ');
+ var nickName = parts[0];
+
+ if (confirm("Delete location " + nickName + "?") == true) {
+ recordsGeodetic.splice(val, 1);
+ }
+ }
+ updateGeodeticList();
+}
+
+function adjustHAE() {
+
+ var haeMethod = document.querySelector('input[name=markRadio]:checked').value;
+ var hae;
+ if (haeMethod == 1) {
+ ge("fixedHAE_APC").disabled = false;
+ ge("fixedAltitude").disabled = true;
+ hae = Number(ge("fixedHAE_APC").value) - (Number(ge("antennaHeight").value) / 1000 + Number(ge("antennaReferencePoint").value) / 1000);
+ ge("fixedAltitude").value = hae.toFixed(3);
+ }
+ else {
+ ge("fixedHAE_APC").disabled = true;
+ ge("fixedAltitude").disabled = false;
+ hae = Number(ge("fixedAltitude").value) + (Number(ge("antennaHeight").value) / 1000 + Number(ge("antennaReferencePoint").value) / 1000);
+ ge("fixedHAE_APC").value = hae.toFixed(3);
+ }
+}
+
+function loadGeodetic() {
+ var val = ge("StationCoordinatesGeodetic").value;
+ if (val > "") {
+ var parts = recordsGeodetic[val].split(' ');
+ var numParts = parts.length;
+ if (numParts >= 6) {
+ var latParts = (numParts - 4) / 2;
+ ge("nicknameGeodetic").value = parts[0];
+ ge("fixedLatText").value = parts[1];
+ if (latParts > 1) {
+ for (let moreParts = 1; moreParts < latParts; moreParts++) {
+ ge("fixedLatText").value += ' ' + parts[moreParts + 1];
+ }
+ }
+ ge("fixedLongText").value = parts[1 + latParts];
+ if (latParts > 1) {
+ for (let moreParts = 1; moreParts < latParts; moreParts++) {
+ ge("fixedLongText").value += ' ' + parts[moreParts + 1 + latParts];
+ }
+ }
+ ge("fixedAltitude").value = parts[numParts - 3];
+ ge("antennaHeight").value = parts[numParts - 2];
+ ge("antennaReferencePoint").value = parts[numParts - 1];
+
+ $("input[name=markRadio][value=1]").prop('checked', false);
+ $("input[name=markRadio][value=2]").prop('checked', true);
+
+ adjustHAE();
+
+ clearError("nicknameGeodetic");
+ clearError("fixedLatText");
+ clearError("fixedLongText");
+ clearError("fixedAltitude");
+ clearError("antennaHeight");
+ clearError("antennaReferencePoint");
+ }
+ else {
+ console.log("stationGeodetic split error");
+ }
+ }
+}
+
+//Based on recordsGeodetic array, update and monospace HTML list
+function updateGeodeticList() {
+ ge("StationCoordinatesGeodetic").length = 0;
+
+ if (recordsGeodetic.length == 0) {
+ hide("StationCoordinatesGeodetic");
+ nicknameGeodeticText.innerHTML = "No coordinates stored";
+ }
+ else {
+ show("StationCoordinatesGeodetic");
+ nicknameGeodeticText.innerHTML = "Nickname: Lat/Long/Alt";
+ if (recordsGeodetic.length < 5)
+ ge("StationCoordinatesGeodetic").size = recordsGeodetic.length;
+ }
+
+ for (let index = 0; index < recordsGeodetic.length; ++index) {
+ var option = document.createElement('option');
+ option.text = recordsGeodetic[index];
+ option.value = index;
+ ge("StationCoordinatesGeodetic").add(option);
+ }
+
+ $("#StationCoordinatesGeodetic option").each(function () {
+ var parts = $(this).text().split(' ');
+ var nickname = parts[0].substring(0, 15);
+
+ if (parts.length >= 7) {
+ $(this).text(nickname + ': ' + parts[1] + ' ' + parts[2] + ' ' + parts[3]
+ + ' ' + parts[4] + ' ' + parts[5] + ' ' + parts[6]
+ + ' ' + parts[7]).text;
+ }
+ else {
+ $(this).text(nickname + ': ' + parts[1] + ' ' + parts[2] + ' ' + parts[3]).text;
+ }
+
+ });
+}
+
+function removeBadChars(val) {
+ val = val.split(' ').join('');
+ val = val.split(',').join('');
+ val = val.split('\\').join('');
+ return (val);
+}
+
+function getFileList() {
+ if (showingFileList == false) {
+ showingFileList = true;
+
+ //If the tab was just opened, create table from scratch
+ ge("fileManagerTable").innerHTML = "";
+ fileTableText = "";
+
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET", "/listfiles", false);
+ xmlhttp.send();
+
+ parseIncoming(xmlhttp.responseText); //Process CSV data into HTML
+
+ ge("fileManagerTable").innerHTML += fileTableText;
+ }
+ else {
+ showingFileList = false;
+ }
+}
+
+function getMessageList() {
+ if (obtainedMessageList == false) {
+ obtainedMessageList = true;
+
+ ge("messageList").innerHTML = "";
+ messageText = "";
+
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET", "/listMessages", false);
+ xmlhttp.send();
+
+ parseIncoming(xmlhttp.responseText); //Process CSV data into HTML
+
+ ge("messageList").innerHTML += messageText;
+ }
+}
+
+function getMessageListBase() {
+ if (obtainedMessageListBase == false) {
+ obtainedMessageListBase = true;
+
+ ge("messageListBase").innerHTML = "";
+ messageText = "";
+
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET", "/listMessagesBase", false);
+ xmlhttp.send();
+
+ parseIncoming(xmlhttp.responseText); //Process CSV data into HTML
+
+ ge("messageListBase").innerHTML += messageText;
+ }
+}
+
+function fileManagerDownload() {
+ selectedFiles = document.querySelectorAll('input[name=fileID]:checked');
+ numberOfFilesSelected = document.querySelectorAll('input[name=fileID]:checked').length;
+ fileNumber = 0;
+ sendFile(); //Start first send
+}
+
+function sendFile() {
+ if (fileNumber == numberOfFilesSelected) return;
+ var urltocall = "/file?name=" + selectedFiles[fileNumber].id + "&action=download";
+ console.log(urltocall);
+ window.location.href = urltocall;
+
+ fileNumber++;
+}
+
+function fileManagerToggle() {
+ var checkboxes = document.querySelectorAll('input[name=fileID]');
+ for (var i = 0, n = checkboxes.length; i < n; i++) {
+ checkboxes[i].checked = ge("fileSelectAll").checked;
+ }
+}
+
+function fileManagerDelete() {
+ selectedFiles = document.querySelectorAll('input[name=fileID]:checked');
+
+ if (confirm("Delete " + selectedFiles.length + " files?") == false) {
+ return;
+ }
+
+ for (let x = 0; x < selectedFiles.length; x++) {
+ var urltocall = "/file?name=" + selectedFiles[x].id + "&action=delete";
+ xmlhttp = new XMLHttpRequest();
+
+ xmlhttp.open("GET", urltocall, false);
+ xmlhttp.send();
+ }
+
+ //Refresh file list
+ showingFileList = false;
+ getFileList();
+}
+
+function uploadFile() {
+ var file = ge("file1").files[0];
+ var formdata = new FormData();
+ formdata.append("file1", file);
+ var ajax = new XMLHttpRequest();
+ ajax.upload.addEventListener("progress", progressHandler, false);
+ ajax.addEventListener("load", completeHandler, false);
+ ajax.addEventListener("error", errorHandler, false);
+ ajax.addEventListener("abort", abortHandler, false);
+ ajax.open("POST", "/");
+ ajax.send(formdata);
+}
+function progressHandler(event) {
+ var percent = (event.loaded / event.total) * 100;
+ ge("progressBar").value = Math.round(percent);
+ ge("uploadStatus").innerHTML = Math.round(percent) + "% uploaded...";
+ if (percent >= 100) {
+ ge("uploadStatus").innerHTML = "Please wait, writing file to filesystem";
+ }
+}
+function completeHandler(event) {
+ ge("uploadStatus").innerHTML = "Upload Complete";
+ ge("progressBar").value = 0;
+
+ //Refresh file list
+ showingFileList = false;
+ getFileList();
+
+ document.getElementById("uploadStatus").innerHTML = "Upload Complete";
+}
+function errorHandler(event) {
+ ge("uploadStatus").innerHTML = "Upload Failed";
+}
+function abortHandler(event) {
+ ge("uploadStatus").innerHTML = "Upload Aborted";
+}
+
+function tcpClientBoxes() {
+ if (ge("enablePvtClient").checked) {
+ show("tcpClientSettingsConfig");
+ }
+ else {
+ hide("tcpClientSettingsConfig");
+ //ge("pvtClientPort").value = 2947;
+ }
+}
+
+function tcpServerBoxes() {
+ if (ge("enablePvtServer").checked) {
+ show("tcpServerSettingsConfig");
+ }
+ else {
+ hide("tcpServerSettingsConfig");
+ //ge("pvtServerPort").value = 2947;
+ }
+}
+
+function udpBoxes() {
+ if (ge("enablePvtUdpServer").checked) {
+ show("udpSettingsConfig");
+ }
+ else {
+ hide("udpSettingsConfig");
+ //ge("pvtUdpServerPort").value = 10110;
+ }
+}
+
+function dhcpEthernet() {
+ if (ge("ethernetDHCP").checked) {
+ hide("fixedIPSettingsConfigEthernet");
+ }
+ else {
+ show("fixedIPSettingsConfigEthernet");
+ }
+}
+
+function networkCount() {
+ var count = 0;
+
+ var wifiNetworks = document.querySelectorAll('input[id^=wifiNetwork]' && 'input[id$=SSID]');
+ for (let x = 0; x < wifiNetworks.length; x++) {
+ if (wifiNetworks[x].value.length > 0)
+ count++;
+ }
+
+ return (count);
+}
+
+function checkNewFirmware() {
+ if (networkCount() == 0) {
+ showMsgError('firmwareCheckNewMsg', "WiFi list is empty");
+ return;
+ }
+
+ ge("btnCheckNewFirmware").disabled = true;
+ showMsg('firmwareCheckNewMsg', "Connecting to WiFi", false);
+
+ var settingCSV = "";
+
+ //Send current WiFi SSID and PWs
+ var clsElements = document.querySelectorAll('input[id^=wifiNetwork]');
+ for (let x = 0; x < clsElements.length; x++) {
+ settingCSV += clsElements[x].id + "," + clsElements[x].value + ",";
+ }
+
+ if (ge("enableRCFirmware").checked == true)
+ settingCSV += "enableRCFirmware,true,";
+ else
+ settingCSV += "enableRCFirmware,false,";
+
+ settingCSV += "checkNewFirmware,1,";
+
+ console.log("firmware sending: " + settingCSV);
+ websocket.send(settingCSV);
+
+ checkNewFirmwareTimeout = setTimeout(checkNewFirmware, 2000);
+}
+
+function checkingNewFirmware() {
+ clearTimeout(checkNewFirmwareTimeout);
+ console.log("Clearing timeout for checkNewFirmwareTimeout");
+
+ showMsg('firmwareCheckNewMsg', "Checking firmware version");
+}
+
+function newFirmwareVersion(firmwareVersion) {
+ clearMsg('firmwareCheckNewMsg');
+ if (firmwareVersion == "ERROR") {
+ showMsgError('firmwareCheckNewMsg', "WiFi or Server not available");
+ hide("divGetNewFirmware");
+ ge("btnCheckNewFirmware").disabled = false;
+ return;
+ }
+ else if (firmwareVersion == "CURRENT") {
+ showMsg('firmwareCheckNewMsg', "Firmware is up to date");
+ hide("divGetNewFirmware");
+ ge("btnCheckNewFirmware").disabled = false;
+ return;
+ }
+
+ ge("btnGetNewFirmware").innerHTML = "Update to v" + firmwareVersion;
+ ge("btnGetNewFirmware").disabled = false;
+ ge("firmwareUpdateProgressBar").value = 0;
+ clearMsg('firmwareUpdateProgressMsg');
+ show("divGetNewFirmware");
+}
+
+function getNewFirmware() {
+
+ if (networkCount() == 0) {
+ showMsgError('firmwareCheckNewMsg', "WiFi list is empty");
+ hide("divGetNewFirmware");
+ ge("btnCheckNewFirmware").disabled = false;
+ return;
+ }
+
+ ge("btnGetNewFirmware").disabled = true;
+ clearMsg('firmwareCheckNewMsg');
+ showMsg('firmwareUpdateProgressMsg', "Getting new firmware");
+
+ var settingCSV = "";
+
+ //Send current WiFi SSID and PWs
+ var clsElements = document.querySelectorAll('input[id^=wifiNetwork]');
+ for (let x = 0; x < clsElements.length; x++) {
+ settingCSV += clsElements[x].id + "," + clsElements[x].value + ",";
+ }
+ settingCSV += "getNewFirmware,1,";
+
+ console.log("firmware sending: " + settingCSV);
+ websocket.send(settingCSV);
+
+ getNewFirmwareTimeout = setTimeout(getNewFirmware, 2000);
+}
+
+function gettingNewFirmware(val) {
+ if (val == "1") {
+ clearTimeout(getNewFirmwareTimeout);
+ }
+ else if (val == "ERROR") {
+ hide("divGetNewFirmware");
+ ge("btnCheckNewFirmware").disabled = false;
+ showMsg('firmwareCheckNewMsg', "Error getting new firmware", true);
+ }
+}
+
+function otaFirmwareStatus(percentComplete) {
+ clearTimeout(getNewFirmwareTimeout);
+
+ showMsg('firmwareUpdateProgressMsg', percentComplete + "% Complete");
+ ge("firmwareUpdateProgressBar").value = percentComplete;
+
+ if (percentComplete == 100) {
+ resetComplete();
+ }
+}
+
+//Given a user's string, try to identify the type and return the coordinate in DD.ddddddddd format
+function identifyInputType(userEntry) {
+ var coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN;
+ var dashCount = 0;
+ var spaceCount = 0;
+ var decimalCount = 0;
+ var lengthOfLeadingNumber = 0;
+ convertedCoordinate = 0.0; //Clear what is given to us
+
+ //Scan entry for invalid chars
+ //A valid entry has only numbers, -, ' ', and .
+ for (var x = 0; x < userEntry.length; x++) {
+
+ if (isdigit(userEntry[x])) {
+ if (decimalCount == 0) lengthOfLeadingNumber++
+ }
+ else if (userEntry[x] == '-') dashCount++; //All good
+ else if (userEntry[x] == ' ') spaceCount++; //All good
+ else if (userEntry[x] == '.') decimalCount++; //All good
+ else return (CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); //String contains invalid character
+ }
+
+ // Seven possible entry types
+ // DD.dddddd
+ // DDMM.mmmmmmm
+ // DD MM.mmmmmmm
+ // DD-MM.mmmmmmm
+ // DDMMSS.ssssss
+ // DD MM SS.ssssss
+ // DD-MM-SS.ssssss
+
+ if (decimalCount > 1) return (CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); //Just no. 40.09033470 is valid.
+ if (spaceCount > 2) return (CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); //Only 0, 1, or 2 allowed. 40 05 25.2049 is valid.
+ if (dashCount > 3) return (CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); //Only 0, 1, 2, or 3 allowed. -105-11-05.1629 is valid.
+ if (lengthOfLeadingNumber > 7) return (CoordinateTypes.COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); //Only 7 or fewer. -1051105.188992 (DDDMMSS or DDMMSS) is valid
+
+ //console.log("userEntry: " + userEntry);
+ //console.log("decimalCount: " + decimalCount);
+ //console.log("spaceCount: " + spaceCount);
+ //console.log("dashCount: " + dashCount);
+ //console.log("lengthOfLeadingNumber: " + lengthOfLeadingNumber);
+
+ var negativeSign = false;
+ if (userEntry[0] == '-') {
+ userEntry = setCharAt(userEntry, 0, ''); //Remove leading minus
+ negativeSign = true;
+ dashCount--; //Use dashCount as the internal dashes only, not the leading negative sign
+ }
+
+ if (spaceCount == 0 && dashCount == 0 && (lengthOfLeadingNumber == 7 || lengthOfLeadingNumber == 6)) //DDMMSS.ssssss
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS;
+
+ var intPortion = Math.trunc(Number(userEntry)); //Get DDDMMSS
+ var decimal = Math.trunc(intPortion / 10000); //Get DDD
+ intPortion -= (decimal * 10000);
+ var minutes = Math.trunc(intPortion / 100); //Get MM
+
+ //Find '.'
+ if (userEntry.indexOf('.') == -1)
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL;
+
+ var seconds = userEntry; //Get DDDMMSS.ssssss
+ seconds -= (decimal * 10000); //Remove DDD
+ seconds -= (minutes * 100); //Remove MM
+ convertedCoordinate = decimal + (minutes / 60.0) + (seconds / 3600.0);
+ if (negativeSign) convertedCoordinate *= -1;
+ }
+ else if (spaceCount == 0 && dashCount == 0 && (lengthOfLeadingNumber == 5 || lengthOfLeadingNumber == 4)) //DDMM.mmmmmmm
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DDMM;
+
+ var intPortion = Math.trunc(userEntry); //Get DDDMM
+ var decimal = intPortion / 100; //Get DDD
+ intPortion -= (decimal * 100);
+ var minutes = userEntry; //Get DDDMM.mmmmmmm
+ minutes -= (decimal * 100); //Remove DDD
+ convertedCoordinate = decimal + (minutes / 60.0);
+ if (negativeSign) convertedCoordinate *= -1.0;
+ }
+
+ else if (dashCount == 1) //DD-MM.mmmmmmm
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_DASH;
+
+ var data = userEntry.split('-');
+ var decimal = Number(data[0]); //Get DD
+ var minutes = Number(data[1]); //Get MM.mmmmmmm
+ convertedCoordinate = decimal + (minutes / 60.0);
+ if (negativeSign) convertedCoordinate *= -1.0;
+ }
+ else if (dashCount == 2) //DD-MM-SS.ssss
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH;
+
+ var data = userEntry.split('-');
+ var decimal = Number(data[0]); //Get DD
+ var minutes = Number(data[1]); //Get MM
+
+ //Find '.'
+ if (userEntry.indexOf('.') == -1)
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL;
+
+ var seconds = Number(data[2]); //Get SS.ssssss
+ convertedCoordinate = decimal + (minutes / 60.0) + (seconds / 3600.0);
+ if (negativeSign) convertedCoordinate *= -1.0;
+ }
+ else if (spaceCount == 0) //DD.ddddddddd
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD;
+ convertedCoordinate = userEntry;
+ if (negativeSign) convertedCoordinate *= -1.0;
+ }
+ else if (spaceCount == 1) //DD MM.mmmmmmm
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM;
+
+ var data = userEntry.split(' ');
+ var decimal = Number(data[0]); //Get DD
+ var minutes = Number(data[1]); //Get MM.mmmmmmm
+ convertedCoordinate = decimal + (minutes / 60.0);
+ if (negativeSign) convertedCoordinate *= -1.0;
+ }
+ else if (spaceCount == 2) //DD MM SS.ssssss
+ {
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS;
+
+ var data = userEntry.split(' ');
+ var decimal = Number(data[0]); //Get DD
+ var minutes = Number(data[1]); //Get MM
+
+ //Find '.'
+ if (userEntry.indexOf('.') == -1)
+ coordinateInputType = CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL;
+
+ var seconds = Number(data[2]); //Get SS.ssssss
+ convertedCoordinate = decimal + (minutes / 60.0) + (seconds / 3600.0);
+ if (negativeSign) convertedCoordinate *= -1.0;
+ }
+
+ //console.log("convertedCoordinate: " + Number(convertedCoordinate).toFixed(9));
+ //console.log("Detected type: " + printableInputType(coordinateInputType));
+ return (coordinateInputType);
+}
+
+//Given a coordinate and input type, output a string
+//So DD.ddddddddd can become 'DD MM SS.ssssss', etc
+function convertInput(coordinate, coordinateInputType) {
+ var coordinateString = "";
+
+ if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD) {
+ coordinate = Number(coordinate).toFixed(9);
+ return (coordinate);
+ }
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DDMM
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_DASH
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SYMBOL
+ ) {
+ var longitudeDegrees = Math.trunc(coordinate);
+ coordinate -= longitudeDegrees;
+ coordinate *= 60;
+ if (coordinate < 1)
+ coordinate *= -1;
+
+ coordinate = coordinate.toFixed(7);
+
+ if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DDMM)
+ coordinateString = longitudeDegrees + "" + coordinate;
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_DASH)
+ coordinateString = longitudeDegrees + "-" + coordinate;
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SYMBOL)
+ coordinateString = longitudeDegrees + "°" + coordinate + "'";
+ else
+ coordinateString = longitudeDegrees + " " + coordinate;
+ }
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_SYMBOL
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL
+ || coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL
+ ) {
+ var longitudeDegrees = Math.trunc(coordinate);
+ coordinate -= longitudeDegrees;
+ coordinate *= 60;
+ if (coordinate < 1)
+ coordinate *= -1;
+
+ var longitudeMinutes = Math.trunc(coordinate);
+ coordinate -= longitudeMinutes;
+ coordinate *= 60;
+
+ coordinate = coordinate.toFixed(6);
+
+ if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS)
+ coordinateString = longitudeDegrees + "" + longitudeMinutes + "" + coordinate;
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH)
+ coordinateString = longitudeDegrees + "-" + longitudeMinutes + "-" + coordinate;
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_SYMBOL)
+ coordinateString = longitudeDegrees + "°" + longitudeMinutes + "'" + coordinate + "\"";
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS)
+ coordinateString = longitudeDegrees + " " + longitudeMinutes + " " + coordinate;
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL)
+ coordinateString = longitudeDegrees + "" + longitudeMinutes + "" + Math.round(coordinate);
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL)
+ coordinateString = longitudeDegrees + " " + longitudeMinutes + " " + Math.round(coordinate);
+ else if (coordinateInputType == CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL)
+ coordinateString = longitudeDegrees + "-" + longitudeMinutes + "-" + Math.round(coordinate);
+ }
+
+ return (coordinateString);
+}
+
+function isdigit(c) { return /\d/.test(c); }
+
+function setCharAt(str, index, chr) {
+ if (index > str.length - 1) return str;
+ return str.substring(0, index) + chr + str.substring(index + 1);
+}
+
+//Given an input type, return a printable string
+function printableInputType(coordinateInputType) {
+ switch (coordinateInputType) {
+ default:
+ return ("Unknown");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD):
+ return ("DD.ddddddddd");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DDMM):
+ return ("DDMM.mmmmmmm");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM):
+ return ("DD MM.mmmmmmm");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_DASH):
+ return ("DD-MM.mmmmmmm");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS):
+ return ("DDMMSS.ssssss");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS):
+ return ("DD MM SS.ssssss");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH):
+ return ("DD-MM-SS.ssssss");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL):
+ return ("DDMMSS");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL):
+ return ("DD MM SS");
+ break;
+ case (CoordinateTypes.COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL):
+ return ("DD-MM-SS");
+ break;
+ }
+ return ("Unknown");
+}
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/rtk-setup-wifi.png b/Firmware/RTK_Surveyor/AP-Config/src/rtk-setup-wifi.png
new file mode 100644
index 000000000..e6e4ec8a7
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/rtk-setup-wifi.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/rtk-setup.png b/Firmware/RTK_Surveyor/AP-Config/src/rtk-setup.png
new file mode 100644
index 000000000..cd2b1d8f0
Binary files /dev/null and b/Firmware/RTK_Surveyor/AP-Config/src/rtk-setup.png differ
diff --git a/Firmware/RTK_Surveyor/AP-Config/src/style.css b/Firmware/RTK_Surveyor/AP-Config/src/style.css
new file mode 100644
index 000000000..e2d9198b2
--- /dev/null
+++ b/Firmware/RTK_Surveyor/AP-Config/src/style.css
@@ -0,0 +1,63 @@
+@font-face {
+ font-family: 'icomoon';
+ src: url('fonts/icomoon.eot?81wxq3');
+ src: url('fonts/icomoon.eot?81wxq3#iefix') format('embedded-opentype'),
+ url('fonts/icomoon.ttf?81wxq3') format('truetype'),
+ url('fonts/icomoon.woff?81wxq3') format('woff'),
+ url('fonts/icomoon.svg?81wxq3#icomoon') format('svg');
+ font-weight: normal;
+ font-style: normal;
+ font-display: block;
+}
+
+[class^="icon-"], [class*=" icon-"] {
+ /* use !important to prevent issues with browser extensions that change fonts */
+ font-family: 'icomoon' !important;
+ speak: never;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+input[type="file"] {
+ display: none;
+}
+
+.icon-close:before {
+ content: "\f00d";
+}
+.icon-remove:before {
+ content: "\f00d";
+}
+.icon-times:before {
+ content: "\f00d";
+}
+.icon-info-circle:before {
+ content: "\f05a";
+}
+.icon-floppy-o:before {
+ content: "\f0c7";
+}
+.icon-save:before {
+ content: "\f0c7";
+}
+.icon-caret-down:before {
+ content: "\f0d7";
+}
+.icon-caret-up:before {
+ content: "\f0d8";
+}
+
+.left {
+ width: 85%;
+}
+
+.right {
+ width: 7%;
+}
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/Base.ino b/Firmware/RTK_Surveyor/Base.ino
index 165520fe1..71ef8625a 100644
--- a/Firmware/RTK_Surveyor/Base.ino
+++ b/Firmware/RTK_Surveyor/Base.ino
@@ -1,230 +1,417 @@
-
-//Configure specific aspects of the receiver for base mode
+// Configure specific aspects of the receiver for base mode
bool configureUbloxModuleBase()
{
- bool response = true;
- int maxWait = 2000;
+ 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.updateZEDSettings == false && firstPowerOn == true)
+ {
+ firstPowerOn = false; // Next time user switches modes, new settings will be applied
+ log_d("Skipping ZED Base configuration");
+ return (true);
+ }
- if (productVariant == RTK_SURVEYOR)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
+ firstPowerOn = false; // If we switch between rover/base in the future, force config of module.
- i2cGNSS.checkUblox(); //Regularly poll to get latest data and any RTCM
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, storePVTdata
- if (i2cGNSS.getSurveyInActive() == true)
- {
- response = i2cGNSS.disableSurveyMode(maxWait); //Disable survey
- if (response == false)
- Serial.println(F("Disable Survey failed"));
- }
-
- //In base mode we force 1Hz
- if (i2cGNSS.getNavigationFrequency(maxWait) != 1)
- response &= i2cGNSS.setNavigationFrequency(1, maxWait);
- if (response == false)
- {
- Serial.println(F("configureUbloxModuleBase: Set rate failed"));
- return (false);
- }
-
- // Set dynamic model
- if (i2cGNSS.getDynamicModel(maxWait) != DYN_MODEL_STATIONARY)
- {
- response &= i2cGNSS.setDynamicModel(DYN_MODEL_STATIONARY, maxWait);
- if (response == false)
+ theGNSS.setNMEAGPGGAcallbackPtr(
+ nullptr); // Disable GPGGA call back that may have been set during Rover NTRIP Client mode
+
+ bool success = false;
+ int tryNo = -1;
+
+ // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS
+ // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI
+ // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being
+ // processed.
+ while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success)
{
- Serial.println(F("setDynamicModel failed"));
- return (false);
+ bool response = true;
+
+ // In Base mode we force 1Hz
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_MEAS, 1000);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_NAV, 1);
+
+ // Since we are at 1Hz, allow GSV NMEA to be reported at whatever the user has chosen
+ uint32_t spiOffset =
+ 0; // Set to 3 if using SPI to convert UART1 keys to SPI. This is brittle and non-perfect, but works.
+ if (USE_SPI_GNSS)
+ spiOffset = 3;
+ response &= theGNSS.addCfgValset(ubxMessages[8].msgConfigKey + spiOffset,
+ settings.ubxMessageRates[8]); // Update rate on module
+
+ if (USE_I2C_GNSS)
+ response &=
+ theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C,
+ 0); // Disable NMEA message that may have been set during Rover NTRIP Client mode
+
+ // Survey mode is only available on ZED-F9P modules
+ if (commandSupported(UBLOX_CFG_TMODE_MODE) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode
+
+ // Note that using UBX-CFG-TMODE3 to set the receiver mode to Survey In or to Fixed Mode, will set
+ // automatically the dynamic platform model (CFG-NAVSPG-DYNMODEL) to Stationary.
+ // response &= theGNSS.addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); //Not needed
+
+ // RTCM is only available on ZED-F9P modules
+ //
+ // For most RTK products, the GNSS is interfaced via both I2C and UART1. Configuration and PVT/HPPOS messages
+ // are configured over I2C. Any messages that need to be logged are output on UART1, and received by this code
+ // using serialGNSS. In base mode the RTK device should output RTCM over all ports: (Primary) UART2 in case the
+ //Surveyor is connected via radio to rover (Optional) I2C in case user wants base to connect to WiFi and NTRIP
+ //Caster (Seconday) USB in case the Surveyor is used as an NTRIP caster connected to SBC or other (Tertiary)
+ //UART1 in case Surveyor is sending RTCM to phone that is then NTRIP Caster
+ //
+ // But, on the Reference Station, the GNSS is interfaced via SPI. It has no access to I2C and UART1.
+ // We use the GNSS library's built-in logging buffer to mimic UART1. The code in Tasks.ino reads
+ // data from the logging buffer as if it had come from UART1.
+ // So for that product - in Base mode - we can only output RTCM on SPI, USB and UART2.
+ // If we want to log the RTCM messages, we need to add them to the logging buffer inside the GNSS library.
+ // If we want to pass them along to (e.g.) radio, we do that using processRTCM (defined below).
+
+ // Find first RTCM record in ubxMessage array
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+
+ // ubxMessageRatesBase is an array of ~12 uint8_ts
+ // ubxMessage is an array of ~80 messages
+ // We use firstRTCMRecord as an offset for the keys, but use x as the rate
+
+ if (USE_I2C_GNSS)
+ {
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey - 1,
+ settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 - 1 = I2C
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey,
+ settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1
+
+ // Disable messages on SPI
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 3,
+ 0); // UBLOX_CFG UART1 + 3 = SPI
+ }
+ }
+ else // SPI GNSS
+ {
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 3,
+ settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 3 = SPI
+
+ // Disable messages on I2C and UART1
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey - 1,
+ 0); // UBLOX_CFG UART1 - 1 = I2C
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey, 0); // UBLOX_CFG UART1
+ }
+
+ // Enable logging of these messages so the RTCM will be stored automatically in the logging buffer.
+ // This mimics the data arriving via UART1.
+ uint32_t logRTCMMessages = theGNSS.getRTCMLoggingMask();
+ logRTCMMessages |=
+ (SFE_UBLOX_FILTER_RTCM_TYPE1005 | SFE_UBLOX_FILTER_RTCM_TYPE1074 | SFE_UBLOX_FILTER_RTCM_TYPE1084 |
+ SFE_UBLOX_FILTER_RTCM_TYPE1094 | SFE_UBLOX_FILTER_RTCM_TYPE1124 | SFE_UBLOX_FILTER_RTCM_TYPE1230);
+ theGNSS.setRTCMLoggingMask(logRTCMMessages);
+ }
+
+ // Update message rates for UART2 and USB
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 1,
+ settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 1 = UART2
+ response &= theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 2,
+ settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 2 = USB
+ }
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation
+
+ response &= theGNSS.sendCfgValset(); // Closing value
+
+ if (response)
+ success = true;
}
- }
-
- //In base mode the Surveyor should output RTCM over UART2 and I2C ports:
- //(Primary) UART2 in case the Surveyor is connected via radio to rover
- //(Optional) I2C in case user wants base to connect to WiFi and NTRIP Serve to Caster
- //(Seconday) USB in case the Surveyor is used as an NTRIP caster
- //(Tertiary) UART1 in case Surveyor is sending RTCM to phone that is then NTRIP caster
- response &= enableRTCMSentences(COM_PORT_UART2);
- response &= enableRTCMSentences(COM_PORT_UART1);
- response &= enableRTCMSentences(COM_PORT_USB);
- response &= enableRTCMSentences(COM_PORT_I2C); //Enable for plain radio so we can count RTCM packets for display (State: Base-Temp - Transmitting)
-
- if (response == false)
- {
- Serial.println(F("RTCM settings failed to enable"));
- return (false);
- }
-
- return (response);
+
+ if (!success)
+ systemPrintln("Base config fail");
+
+ return (success);
}
-//Start survey
-//The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info.
-bool beginSurveyIn()
+// Start survey
+// The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info.
+bool surveyInStart()
{
- bool needSurveyReset = false;
- if (i2cGNSS.getSurveyInActive(100) == true) needSurveyReset = true;
- if (i2cGNSS.getSurveyInValid(100) == true) needSurveyReset = true;
+ theGNSS.setVal8(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode
+ delay(100);
- if (needSurveyReset == true)
- {
- Serial.println("Resetting survey");
+ bool needSurveyReset = false;
+ if (theGNSS.getSurveyInActive(100) == true)
+ needSurveyReset = true;
+ if (theGNSS.getSurveyInValid(100) == true)
+ needSurveyReset = true;
- if (resetSurvey() == false)
+ if (needSurveyReset == true)
{
- Serial.println(F("Survey reset failed"));
- if (resetSurvey() == false)
- {
- Serial.println(F("Survey reset failed - 2nd attempt"));
- }
+ systemPrintln("Resetting survey");
+
+ if (surveyInReset() == false)
+ {
+ systemPrintln("Survey reset failed - attempt 1/3");
+ if (surveyInReset() == false)
+ {
+ systemPrintln("Survey reset failed - attempt 2/3");
+ if (surveyInReset() == false)
+ {
+ systemPrintln("Survey reset failed - attempt 3/3");
+ }
+ }
+ }
+ }
+
+ bool response = true;
+ response &= theGNSS.setVal8(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable
+ response &= theGNSS.setVal32(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, settings.observationPositionAccuracy * 10000);
+ response &= theGNSS.setVal32(UBLOX_CFG_TMODE_SVIN_MIN_DUR, settings.observationSeconds);
+
+ if (response == false)
+ {
+ systemPrintln("Survey start failed");
+ return (false);
+ }
+
+ systemPrintf("Survey started. This will run until %d seconds have passed and less than %0.03f meter accuracy is "
+ "achieved.\r\n",
+ settings.observationSeconds, settings.observationPositionAccuracy);
+
+ // Wait until active becomes true
+ long maxTime = 5000;
+ long startTime = millis();
+ while (theGNSS.getSurveyInActive(100) == false)
+ {
+ delay(100);
+ if (millis() - startTime > maxTime)
+ return (false); // Reset of survey failed
}
- }
-
- bool response = i2cGNSS.enableSurveyMode(settings.observationSeconds, settings.observationPositionAccuracy, 5000); //Enable Survey in, with user parameters. Wait up to 5s.
- if (response == false)
- {
- Serial.println(F("Survey start failed"));
- return (false);
- }
-
- Serial.printf("Survey started. This will run until %d seconds have passed and less than %0.03f meter accuracy is achieved.\n\r",
- settings.observationSeconds,
- settings.observationPositionAccuracy
- );
-
- //Wait until active becomes true
- long maxTime = 5000;
- long startTime = millis();
- while(i2cGNSS.getSurveyInActive(100) == false)
- {
- delay(100);
- if(millis() - startTime > maxTime) return(false); //Reset of survey failed
- }
- return (true);
+ return (true);
}
-bool resetSurvey()
+// Slightly modified method for restarting survey-in from:
+// https://portal.u-blox.com/s/question/0D52p00009IsVoMCAV/restarting-surveyin-on-an-f9p
+bool surveyInReset()
{
- int maxWait = 2000;
-
- //Slightly modified method for restarting survey-in from: https://portal.u-blox.com/s/question/0D52p00009IsVoMCAV/restarting-surveyin-on-an-f9p
- bool response = i2cGNSS.disableSurveyMode(maxWait); //Disable survey
- delay(1000);
- response &= i2cGNSS.enableSurveyMode(1000, 400.000, maxWait); //Enable Survey in with bogus values
- delay(1000);
- response &= i2cGNSS.disableSurveyMode(maxWait); //Disable survey
-
- if(response == false)
- return(response);
-
- //Wait until active and valid becomes false
- long maxTime = 5000;
- long startTime = millis();
- while(i2cGNSS.getSurveyInActive(100) == true || i2cGNSS.getSurveyInValid(100) == true)
- {
- delay(100);
- if(millis() - startTime > maxTime) return(false); //Reset of survey failed
- }
+ bool response = true;
+
+ // Disable survey-in mode
+ response &= theGNSS.setVal8(UBLOX_CFG_TMODE_MODE, 0);
+ delay(1000);
+
+ // Enable Survey in with bogus values
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, 40 * 10000); // 40.0m
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_SVIN_MIN_DUR, 1000); // 1000s
+ response &= theGNSS.sendCfgValset();
+ delay(1000);
- return(true);
+ // Disable survey-in mode
+ response &= theGNSS.setVal8(UBLOX_CFG_TMODE_MODE, 0);
+
+ if (response == false)
+ return (response);
+
+ // Wait until active and valid becomes false
+ long maxTime = 5000;
+ long startTime = millis();
+ while (theGNSS.getSurveyInActive(100) == true || theGNSS.getSurveyInValid(100) == true)
+ {
+ delay(100);
+ if (millis() - startTime > maxTime)
+ return (false); // Reset of survey failed
+ }
+
+ return (true);
}
-//Start the base using fixed coordinates
+// Start the base using fixed coordinates
bool startFixedBase()
{
- bool response = false;
- int maxWait = 2000;
-
- if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
- {
- //Break ECEF into main and high precision parts
- //The type casting should not effect rounding of original double cast coordinate
- long majorEcefX = floor((settings.fixedEcefX * 100.0) + 0.5);
- long minorEcefX = floor((((settings.fixedEcefX * 100.0) - majorEcefX) * 100.0) + 0.5);
- long majorEcefY = floor((settings.fixedEcefY * 100) + 0.5);
- long minorEcefY = floor((((settings.fixedEcefY * 100.0) - majorEcefY) * 100.0) + 0.5);
- long majorEcefZ = floor((settings.fixedEcefZ * 100) + 0.5);
- long minorEcefZ = floor((((settings.fixedEcefZ * 100.0) - majorEcefZ) * 100.0) + 0.5);
-
- // Serial.printf("fixedEcefY (should be -4716808.5807): %0.04f\n\r", settings.fixedEcefY);
- // Serial.printf("major (should be -471680858): %ld\n\r", majorEcefY);
- // Serial.printf("minor (should be -7): %ld\n\r", minorEcefY);
-
- //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 = i2cGNSS.setStaticPosition(majorEcefX, minorEcefX,
- majorEcefY, minorEcefY,
- majorEcefZ, minorEcefZ,
- false,
- maxWait
- ); //With high precision 0.1mm parts
- }
- else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEOGRAPHIC)
- {
- //Break coordinates into main and high precision parts
- //The type casting should not effect rounding of original double cast coordinate
- int64_t majorLat = settings.fixedLat * 10000000;
- int64_t minorLat = ((settings.fixedLat * 10000000) - majorLat) * 100;
- int64_t majorLong = settings.fixedLong * 10000000;
- int64_t minorLong = ((settings.fixedLong * 10000000) - majorLong) * 100;
- int32_t majorAlt = settings.fixedAltitude * 100;
- int32_t minorAlt = ((settings.fixedAltitude * 100) - majorAlt) * 100;
-
- // Serial.printf("fixedLong (should be -105.184774720): %0.09f\n\r", settings.fixedLong);
- // Serial.printf("major (should be -1051847747): %lld\n\r", majorLat);
- // Serial.printf("minor (should be -20): %lld\n\r", minorLat);
- //
- // Serial.printf("fixedLat (should be 40.090335429): %0.09f\n\r", settings.fixedLat);
- // Serial.printf("major (should be 400903354): %lld\n\r", majorLong);
- // Serial.printf("minor (should be 29): %lld\n\r", minorLong);
- //
- // Serial.printf("fixedAlt (should be 1560.2284): %0.04f\n\r", settings.fixedAltitude);
- // Serial.printf("major (should be 156022): %ld\n\r", majorAlt);
- // Serial.printf("minor (should be 84): %ld\n\r", minorAlt);
-
- response = i2cGNSS.setStaticPosition(
- majorLat, minorLat,
- majorLong, minorLong,
- majorAlt, minorAlt,
- true, //Use lat/long as input
- maxWait);
- }
-
- return (response);
+ bool response = true;
+ int retries = 0;
+ uint16_t maxWait = 1100;
+
+ do {
+ if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
+ {
+ // Break ECEF into main and high precision parts
+ // The type casting should not effect rounding of original double cast coordinate
+ long majorEcefX = floor((settings.fixedEcefX * 100.0) + 0.5);
+ long minorEcefX = floor((((settings.fixedEcefX * 100.0) - majorEcefX) * 100.0) + 0.5);
+ long majorEcefY = floor((settings.fixedEcefY * 100) + 0.5);
+ long minorEcefY = floor((((settings.fixedEcefY * 100.0) - majorEcefY) * 100.0) + 0.5);
+ long majorEcefZ = floor((settings.fixedEcefZ * 100) + 0.5);
+ long minorEcefZ = floor((((settings.fixedEcefZ * 100.0) - majorEcefZ) * 100.0) + 0.5);
+
+ // systemPrintf("fixedEcefY (should be -4716808.5807): %0.04f\r\n", settings.fixedEcefY);
+ // systemPrintf("major (should be -471680858): %ld\r\n", majorEcefY);
+ // systemPrintf("minor (should be -7): %ld\r\n", minorEcefY);
+
+ // 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 &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 0); // Position in ECEF
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_ECEF_X, majorEcefX);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_ECEF_X_HP, minorEcefX);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_ECEF_Y, majorEcefY);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_ECEF_Y_HP, minorEcefY);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_ECEF_Z, majorEcefZ);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_ECEF_Z_HP, minorEcefZ);
+ response &= theGNSS.sendCfgValset();
+ }
+ else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC)
+ {
+ // Add height of instrument (HI) to fixed altitude
+ // https://www.e-education.psu.edu/geog862/node/1853
+ // For example, if HAE is at 100.0m, + 2m stick + 73mm ARP = 102.073
+ float totalFixedAltitude =
+ settings.fixedAltitude + (settings.antennaHeight / 1000.0) + (settings.antennaReferencePoint / 1000.0);
+
+ // Break coordinates into main and high precision parts
+ // The type casting should not effect rounding of original double cast coordinate
+ int64_t majorLat = settings.fixedLat * 10000000;
+ int64_t minorLat = ((settings.fixedLat * 10000000) - majorLat) * 100;
+ int64_t majorLong = settings.fixedLong * 10000000;
+ int64_t minorLong = ((settings.fixedLong * 10000000) - majorLong) * 100;
+ int32_t majorAlt = totalFixedAltitude * 100;
+ int32_t minorAlt = ((totalFixedAltitude * 100) - majorAlt) * 100;
+
+ // systemPrintf("fixedLong (should be -105.184774720): %0.09f\r\n", settings.fixedLong);
+ // systemPrintf("major (should be -1051847747): %lld\r\n", majorLat);
+ // systemPrintf("minor (should be -20): %lld\r\n", minorLat);
+ //
+ // systemPrintf("fixedLat (should be 40.090335429): %0.09f\r\n", settings.fixedLat);
+ // systemPrintf("major (should be 400903354): %lld\r\n", majorLong);
+ // systemPrintf("minor (should be 29): %lld\r\n", minorLong);
+ //
+ // systemPrintf("fixedAlt (should be 1560.2284): %0.04f\r\n", settings.fixedAltitude);
+ // systemPrintf("major (should be 156022): %ld\r\n", majorAlt);
+ // systemPrintf("minor (should be 84): %ld\r\n", minorAlt);
+
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 1); // Position in LLH
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_LAT, majorLat);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_LAT_HP, minorLat);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_LON, majorLong);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_LON_HP, minorLong);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_HEIGHT, majorAlt);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_HEIGHT_HP, minorAlt);
+ response &= theGNSS.sendCfgValset(maxWait);
+ }
+
+ if (!response) {
+ systemPrint("startFixedBase failed! ");
+ if (retries <= 2) {
+ systemPrintln("Retrying...");
+ delay(1000);
+ maxWait = 2200;
+ }
+ else {
+ systemPrintln("Giving up and going into Rover mode!");
+ }
+ }
+ } while ((!response) && (++retries <= 3));
+
+ return (response);
+}
+
+// This function gets called from the SparkFun u-blox Arduino Library.
+// As each RTCM byte comes in you can specify what to do with it
+// Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc.
+void DevUBLOXGNSS::processRTCM(uint8_t incoming)
+{
+ // We need to prevent ntripServerProcessRTCM from writing data via Ethernet (SPI W5500)
+ // during an SPI checkUbloxSpi...
+ // We can pass incoming to ntripServerProcessRTCM if the GNSS is I2C or the variant does not have Ethernet.
+ // For the Ref Stn, processRTCMBuffer is called manually from inside ntripServerUpdate
+ if ((USE_SPI_GNSS) && (HAS_ETHERNET))
+ return;
+
+ // Check for too many digits
+ if (settings.enableResetDisplay == true)
+ {
+ if (rtcmPacketsSent > 99)
+ rtcmPacketsSent = 1; // Trim to two digits to avoid overlap
+ }
+ else
+ {
+ if (rtcmPacketsSent > 999)
+ rtcmPacketsSent = 1; // Trim to three digits to avoid log icon and increasing bar
+ }
+
+ // Determine if we should check this byte with the RTCM checker or simply pass it along
+ bool passAlongIncomingByte = true;
+
+ if (settings.enableRtcmMessageChecking == true)
+ passAlongIncomingByte &= checkRtcmMessage(incoming);
+
+ // Give this byte to the various possible transmission methods
+ if (passAlongIncomingByte)
+ {
+ rtcmLastReceived = millis();
+ rtcmBytesSent++;
+
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ ntripServerProcessRTCM(serverIndex, incoming);
+
+ espnowProcessRTCM(incoming);
+ }
}
-//This function gets called from the SparkFun u-blox Arduino Library.
-//As each RTCM byte comes in you can specify what to do with it
-//Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc.
-void SFE_UBLOX_GNSS::processRTCM(uint8_t incoming)
+// For Ref Stn (USE_SPI_GNSS and HAS_ETHERNET), call ntripServerProcessRTCM manually if there is RTCM data in the buffer
+void processRTCMBuffer()
{
- //Count outgoing packets for display
- //Assume 1Hz RTCM transmissions
- if (millis() - lastRTCMPacketSent > 500)
- {
- lastRTCMPacketSent = millis();
- rtcmPacketsSent++;
- }
-
- //Check for too many digits
- if (logIncreasing == true)
- {
- if (rtcmPacketsSent > 999) rtcmPacketsSent = 1; //Trim to three digits to avoid log icon
- }
- else
- {
- if (rtcmPacketsSent > 9999) rtcmPacketsSent = 1;
- }
-
-#ifdef COMPILE_WIFI
- if (caster.connected() == true)
- {
- caster.write(incoming); //Send this byte to socket
- casterBytesSent++;
- lastServerSent_ms = millis();
- }
-#endif
+ if ((USE_I2C_GNSS) || (!HAS_ETHERNET))
+ return;
+
+ // Check if there is any data waiting in the RTCM buffer
+ uint16_t rtcmBytesAvail = theGNSS.rtcmBufferAvailable();
+ if (rtcmBytesAvail > 0)
+ {
+ // Check for too many digits
+ if (settings.enableResetDisplay == true)
+ {
+ if (rtcmPacketsSent > 99)
+ rtcmPacketsSent = 1; // Trim to two digits to avoid overlap
+ }
+ else
+ {
+ if (rtcmPacketsSent > 999)
+ rtcmPacketsSent = 1; // Trim to three digits to avoid log icon and increasing bar
+ }
+
+ while (rtcmBytesAvail > 0)
+ {
+ uint8_t incoming;
+
+ if (theGNSS.extractRTCMBufferData(&incoming, 1) != 1)
+ return;
+
+ rtcmBytesAvail--;
+
+ // Data in the u-blox library RTCM buffer is pre-checked. We don't need to check it again here.
+
+ rtcmLastReceived = millis();
+ rtcmBytesSent++;
+
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ ntripServerProcessRTCM(serverIndex, incoming);
+
+ espnowProcessRTCM(incoming);
+ }
+ }
}
diff --git a/Firmware/RTK_Surveyor/Begin.ino b/Firmware/RTK_Surveyor/Begin.ino
index 5aac7590e..9f4c77b0c 100644
--- a/Firmware/RTK_Surveyor/Begin.ino
+++ b/Firmware/RTK_Surveyor/Begin.ino
@@ -1,386 +1,1434 @@
-//Initial startup functions for GNSS, SD, display, radio, etc
+/*------------------------------------------------------------------------------
+Begin.ino
-//Based on hardware features, determine if this is RTK Surveyor or RTK Express hardware
-//Must be called after Wire.begin so that we can do I2C tests
+ This module implements the initial startup functions for GNSS, SD, display,
+ radio, etc.
+------------------------------------------------------------------------------*/
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+#define MAX_ADC_VOLTAGE 3300 // Millivolts
+
+// Testing shows the combined ADC+resistors is under a 1% window
+#define TOLERANCE 5.20 // Percent: 94.8% - 105.2%
+
+//----------------------------------------
+// Hardware initialization functions
+//----------------------------------------
+// Determine if the measured value matches the product ID value
+// idWithAdc applies resistor tolerance using worst-case tolerances:
+// Upper threshold: R1 down by TOLERANCE, R2 up by TOLERANCE
+// Lower threshold: R1 up by TOLERANCE, R2 down by TOLERANCE
+bool idWithAdc(uint16_t mvMeasured, float r1, float r2)
+{
+ float lowerThreshold;
+ float upperThreshold;
+
+ // ADC input
+ // r1 KOhms | r2 KOhms
+ // MAX_ADC_VOLTAGE -----/\/\/\/\-----+-----/\/\/\/\----- Ground
+
+ // Return true if the mvMeasured value is within the tolerance range
+ // of the mvProduct value
+ upperThreshold = ceil(MAX_ADC_VOLTAGE * (r2 * (1.0 + (TOLERANCE / 100.0))) /
+ ((r1 * (1.0 - (TOLERANCE / 100.0))) + (r2 * (1.0 + (TOLERANCE / 100.0)))));
+ lowerThreshold = floor(MAX_ADC_VOLTAGE * (r2 * (1.0 - (TOLERANCE / 100.0))) /
+ ((r1 * (1.0 + (TOLERANCE / 100.0))) + (r2 * (1.0 - (TOLERANCE / 100.0)))));
+
+ // systemPrintf("r1: %0.2f r2: %0.2f lowerThreshold: %0.0f mvMeasured: %d upperThreshold: %0.0f\r\n", r1, r2,
+ // lowerThreshold, mvMeasured, upperThreshold);
+
+ return (upperThreshold > mvMeasured) && (mvMeasured > lowerThreshold);
+}
+
+// Use a pair of resistors on pin 35 to ID the board type
+// If the ID resistors are not available then use a variety of other methods
+// (I2C, GPIO test, etc) to ID the board.
+// Assume no hardware interfaces have been started so we need to start/stop any hardware
+// used in tests accordingly.
+void identifyBoard()
+{
+ // Use ADC to check the resistor divider
+ int pin_deviceID = 35;
+ uint16_t idValue = analogReadMilliVolts(pin_deviceID);
+ log_d("Board ADC ID (mV): %d", idValue);
+
+ // Order the following ID checks, by millivolt values high to low
+
+ // Facet L-Band Direct: 4.7/1 --> 534mV < 579mV < 626mV
+ if (idWithAdc(idValue, 4.7, 1))
+ productVariant = RTK_FACET_LBAND_DIRECT;
+
+ // Express: 10/3.3 --> 761mV < 819mV < 879mV
+ else if (idWithAdc(idValue, 10, 3.3))
+ productVariant = RTK_EXPRESS;
+
+ // Reference Station: 20/10 --> 1031mV < 1100mV < 1171mV
+ else if (idWithAdc(idValue, 20, 10))
+ {
+ productVariant = REFERENCE_STATION;
+ // We can't auto-detect the ZED version if the firmware is in configViaEthernet mode,
+ // so fake it here - otherwise messageSupported always returns false
+ zedFirmwareVersionInt = 112;
+ }
+ // Facet: 10/10 --> 1571mV < 1650mV < 1729mV
+ else if (idWithAdc(idValue, 10, 10))
+ productVariant = RTK_FACET;
+
+ // Facet L-Band: 10/20 --> 2129mV < 2200mV < 2269mV
+ else if (idWithAdc(idValue, 10, 20))
+ productVariant = RTK_FACET_LBAND;
+
+ // Express+: 3.3/10 --> 2421mV < 2481mV < 2539mV
+ else if (idWithAdc(idValue, 3.3, 10))
+ productVariant = RTK_EXPRESS_PLUS;
+
+ // ID resistors do not exist for the following:
+ // Surveyor
+ // Unknown
+ else
+ {
+ log_d("Out of band or nonexistent resistor IDs");
+ productVariant = RTK_UNKNOWN; // Need to wait until the GNSS and Accel have been initialized
+ }
+}
+
+// Setup any essential power pins
+// E.g. turn on power for the display before beginDisplay
+void initializePowerPins()
+{
+ if (productVariant == REFERENCE_STATION)
+ {
+ // v10
+ // Pin Allocations:
+ // D0 : Boot + Boot Button
+ // D1 : Serial TX (CH340 RX)
+ // D2 : SDIO DAT0 - via 74HC4066 switch
+ // D3 : Serial RX (CH340 TX)
+ // D4 : SDIO DAT1
+ // D5 : GNSS Chip Select
+ // D12 : SDIO DAT2 - via 74HC4066 switch
+ // D13 : SDIO DAT3
+ // D14 : SDIO CLK
+ // D15 : SDIO CMD - via 74HC4066 switch
+ // D16 : Serial1 RXD : Note: connected to the I/O connector only - not to the ZED-F9P
+ // D17 : Serial1 TXD : Note: connected to the I/O connector only - not to the ZED-F9P
+ // D18 : SPI SCK
+ // D19 : SPI POCI
+ // D21 : I2C SDA
+ // D22 : I2C SCL
+ // D23 : SPI PICO
+ // D25 : GNSS Time Pulse
+ // D26 : STAT LED
+ // D27 : Ethernet Chip Select
+ // D32 : PWREN
+ // D33 : Ethernet Interrupt
+ // A34 : GNSS TX RDY
+ // A35 : Board Detect (1.1V)
+ // A36 : microSD card detect
+ // A39 : Unused analog pin - used to generate random values for SSL
+
+ pin_baseStatusLED = 26;
+ pin_peripheralPowerControl = 32;
+ pin_Ethernet_CS = 27;
+ pin_GNSS_CS = 5;
+ pin_GNSS_TimePulse = 25;
+ pin_adc39 = 39;
+ pin_zed_tx_ready = 34;
+ pin_microSD_CardDetect = 36;
+ pin_Ethernet_Interrupt = 33;
+ pin_setupButton = 0;
+
+ pin_radio_rx = 17; // Radio RX In = ESP TX Out
+ pin_radio_tx = 16; // Radio TX Out = ESP RX In
+
+ pinMode(pin_Ethernet_CS, OUTPUT);
+ digitalWrite(pin_Ethernet_CS, HIGH);
+ pinMode(pin_GNSS_CS, OUTPUT);
+ digitalWrite(pin_GNSS_CS, HIGH);
+
+ pinMode(pin_peripheralPowerControl, OUTPUT);
+ digitalWrite(pin_peripheralPowerControl, HIGH); // Turn on SD, W5500, etc
+ delay(100);
+ }
+}
+
+// Based on hardware features, determine if this is RTK Surveyor or RTK Express hardware
+// Must be called after beginI2C (Wire.begin) so that we can do I2C tests
+// Must be called after beginGNSS so the GNSS type is known
void beginBoard()
{
- //Use ADC to check 50% resistor divider
- int pin_adc_rtk_facet = 35;
- if (analogReadMilliVolts(pin_adc_rtk_facet) > (3300 / 2 * 0.9) && analogReadMilliVolts(pin_adc_rtk_facet) < (3300 / 2 * 1.1))
- {
- productVariant = RTK_FACET;
- }
- else if (isConnected(0x19) == true) //Check for accelerometer
- {
- productVariant = RTK_EXPRESS;
- }
- else
- {
- productVariant = RTK_SURVEYOR;
- }
-
- //Setup hardware pins
- if (productVariant == RTK_SURVEYOR)
- {
- pin_batteryLevelLED_Red = 32;
- pin_batteryLevelLED_Green = 33;
- pin_positionAccuracyLED_1cm = 2;
- pin_positionAccuracyLED_10cm = 15;
- pin_positionAccuracyLED_100cm = 13;
- pin_baseStatusLED = 4;
- pin_bluetoothStatusLED = 12;
- pin_baseSwitch = 5;
- pin_microSD_CS = 25;
- pin_zed_tx_ready = 26;
- pin_zed_reset = 27;
- pin_batteryLevel_alert = 36;
-
- strcpy(platformFilePrefix, "SFE_Surveyor");
- strcpy(platformPrefix, "Surveyor");
- }
- else if (productVariant == RTK_EXPRESS)
- {
- pin_muxA = 2;
- pin_muxB = 4;
- pin_powerSenseAndControl = 13;
- pin_setupButton = 14;
- pin_microSD_CS = 25;
- pin_dac26 = 26;
- pin_powerFastOff = 27;
- pin_adc39 = 39;
-
- pinMode(pin_powerSenseAndControl, INPUT_PULLUP);
- pinMode(pin_powerFastOff, INPUT);
-
- if (esp_reset_reason() == ESP_RST_POWERON)
- {
- powerOnCheck(); //Only do check if we POR start
- }
-
- pinMode(pin_setupButton, INPUT_PULLUP);
-
- setMuxport(settings.dataPortChannel); //Set mux to user's choice: NMEA, I2C, PPS, or DAC
-
- strcpy(platformFilePrefix, "SFE_Express");
- strcpy(platformPrefix, "Express");
- }
- else if (productVariant == RTK_FACET)
- {
- //v11
- pin_muxA = 2;
- pin_muxB = 0;
- pin_powerSenseAndControl = 13;
- pin_peripheralPowerControl = 14;
- pin_microSD_CS = 25;
- pin_dac26 = 26;
- pin_powerFastOff = 27;
- pin_adc39 = 39;
-
- pinMode(pin_powerSenseAndControl, INPUT_PULLUP);
- pinMode(pin_powerFastOff, INPUT);
-
- if (esp_reset_reason() == ESP_RST_POWERON)
- {
- powerOnCheck(); //Only do check if we POR start
- }
-
- pinMode(pin_peripheralPowerControl, OUTPUT);
- digitalWrite(pin_peripheralPowerControl, HIGH); //Turn on SD, ZED, etc
-
- setMuxport(settings.dataPortChannel); //Set mux to user's choice: NMEA, I2C, PPS, or DAC
-
- delay(1000);
-
- strcpy(platformFilePrefix, "SFE_Facet");
- strcpy(platformPrefix, "Facet");
- }
-
- Serial.printf("SparkFun RTK %s v%d.%d-%s\r\n", platformPrefix, FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, __DATE__);
-
- //For all boards, check reset reason. If reset was due to wdt or panic, append last log
- if (esp_reset_reason() == ESP_RST_POWERON)
- {
- reuseLastLog = false; //Start new log
- }
- else
- {
- reuseLastLog = true; //Attempt to reuse previous log
-
- Serial.print("Reset reason: ");
- switch (esp_reset_reason())
- {
- case ESP_RST_UNKNOWN: Serial.println(F("ESP_RST_UNKNOWN")); break;
- case ESP_RST_POWERON : Serial.println(F("ESP_RST_POWERON")); break;
- case ESP_RST_SW : Serial.println(F("ESP_RST_SW")); break;
- case ESP_RST_PANIC : Serial.println(F("ESP_RST_PANIC")); break;
- case ESP_RST_INT_WDT : Serial.println(F("ESP_RST_INT_WDT")); break;
- case ESP_RST_TASK_WDT : Serial.println(F("ESP_RST_TASK_WDT")); break;
- case ESP_RST_WDT : Serial.println(F("ESP_RST_WDT")); break;
- case ESP_RST_DEEPSLEEP : Serial.println(F("ESP_RST_DEEPSLEEP")); break;
- case ESP_RST_BROWNOUT : Serial.println(F("ESP_RST_BROWNOUT")); break;
- case ESP_RST_SDIO : Serial.println(F("ESP_RST_SDIO")); break;
- default : Serial.println(F("Unknown"));
- }
- }
+ if (productVariant == RTK_UNKNOWN)
+ {
+ if (isConnected(0x19) == true) // Check for accelerometer
+ {
+ if (zedModuleType == PLATFORM_F9P)
+ productVariant = RTK_EXPRESS;
+ else if (zedModuleType == PLATFORM_F9R)
+ productVariant = RTK_EXPRESS_PLUS;
+ }
+ else
+ {
+ // Detect RTK Expresses (v1.3 and below) that do not have an accel or device ID resistors
+
+ // On a Surveyor, pin 34 is not connected. On Express, 34 is connected to ZED_TX_READY
+ const int pin_ZedTxReady = 34;
+ uint16_t pinValue = analogReadMilliVolts(pin_ZedTxReady);
+ log_d("Alternate ID pinValue (mV): %d\r\n", pinValue); // Surveyor = 142 to 152, //Express = 3129
+ if (pinValue > 3000)
+ {
+ if (zedModuleType == PLATFORM_F9P)
+ productVariant = RTK_EXPRESS;
+ else if (zedModuleType == PLATFORM_F9R)
+ productVariant = RTK_EXPRESS_PLUS;
+ }
+ else
+ productVariant = RTK_SURVEYOR;
+ }
+ }
+
+ // Setup hardware pins
+ if (productVariant == RTK_SURVEYOR)
+ {
+ pin_batteryLevelLED_Red = 32;
+ pin_batteryLevelLED_Green = 33;
+ pin_positionAccuracyLED_1cm = 2;
+ pin_positionAccuracyLED_10cm = 15;
+ pin_positionAccuracyLED_100cm = 13;
+ pin_baseStatusLED = 4;
+ pin_bluetoothStatusLED = 12;
+ pin_setupButton = 5;
+ pin_microSD_CS = 25;
+ pin_zed_tx_ready = 26;
+ pin_zed_reset = 27;
+ pin_batteryLevel_alert = 36;
+
+ // Bug in ZED-F9P v1.13 firmware causes RTK LED to not light when RTK Floating with SBAS on.
+ // The following changes the POR default but will be overwritten by settings in NVM or settings file
+ settings.ubxConstellations[1].enabled = false;
+ }
+ else if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS_PLUS)
+ {
+ pin_muxA = 2;
+ pin_muxB = 4;
+ pin_powerSenseAndControl = 13;
+ pin_setupButton = 14;
+ pin_microSD_CS = 25;
+ pin_dac26 = 26;
+ pin_powerFastOff = 27;
+ pin_adc39 = 39;
+
+ pinMode(pin_powerSenseAndControl, INPUT_PULLUP);
+ pinMode(pin_powerFastOff, INPUT);
+
+ if (esp_reset_reason() == ESP_RST_POWERON)
+ {
+ powerOnCheck(); // Only do check if we POR start
+ }
+
+ pinMode(pin_setupButton, INPUT_PULLUP);
+
+ setMuxport(settings.dataPortChannel); // Set mux to user's choice: NMEA, I2C, PPS, or DAC
+ }
+ else if (productVariant == RTK_FACET || productVariant == RTK_FACET_LBAND ||
+ productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ // v11
+ pin_muxA = 2;
+ pin_muxB = 0;
+ pin_powerSenseAndControl = 13;
+ pin_peripheralPowerControl = 14;
+ pin_microSD_CS = 25;
+ pin_dac26 = 26;
+ pin_powerFastOff = 27;
+ pin_adc39 = 39;
+
+ pin_radio_rx = 33;
+ pin_radio_tx = 32;
+ pin_radio_rst = 15;
+ pin_radio_pwr = 4;
+ pin_radio_cts = 5;
+ // pin_radio_rts = 255; //Not implemented
+
+ pinMode(pin_powerSenseAndControl, INPUT_PULLUP);
+ pinMode(pin_powerFastOff, INPUT);
+
+ if (esp_reset_reason() == ESP_RST_POWERON)
+ {
+ powerOnCheck(); // Only do check if we POR start
+ }
+
+ pinMode(pin_peripheralPowerControl, OUTPUT);
+ digitalWrite(pin_peripheralPowerControl, HIGH); // Turn on SD, ZED, etc
+
+ setMuxport(settings.dataPortChannel); // Set mux to user's choice: NMEA, I2C, PPS, or DAC
+
+ // CTS is active low. ESP32 pin 5 has pullup at POR. We must drive it low.
+ pinMode(pin_radio_cts, OUTPUT);
+ digitalWrite(pin_radio_cts, LOW);
+
+ if (productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ // Override the default setting if a user has not explicitly configured the setting
+ if (settings.useI2cForLbandCorrectionsConfigured == false)
+ settings.useI2cForLbandCorrections = false;
+ }
+ }
+ else if (productVariant == REFERENCE_STATION)
+ {
+ // No powerOnCheck
+
+ settings.enablePrintBatteryMessages = false; // No pesky battery messages
+ }
+
+ displaySfeFlame();
+
+ char versionString[21];
+ getFirmwareVersion(versionString, sizeof(versionString), true);
+ systemPrintf("SparkFun RTK %s %s\r\n", platformPrefix, versionString);
+
+ // Get unit MAC address
+ esp_read_mac(wifiMACAddress, ESP_MAC_WIFI_STA);
+ memcpy(btMACAddress, wifiMACAddress, sizeof(wifiMACAddress));
+ btMACAddress[5] +=
+ 2; // Convert MAC address to Bluetooth MAC (add 2):
+ // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system.html#mac-address
+ memcpy(ethernetMACAddress, wifiMACAddress, sizeof(wifiMACAddress));
+ ethernetMACAddress[5] += 3; // Convert MAC address to Ethernet MAC (add 3)
+
+ // For all boards, check reset reason. If reset was due to wdt or panic, append last log
+ loadSettingsPartial(); // Loads settings from LFS
+ if ((esp_reset_reason() == ESP_RST_POWERON) || (esp_reset_reason() == ESP_RST_SW))
+ {
+ reuseLastLog = false; // Start new log
+
+ if (settings.enableResetDisplay == true)
+ {
+ settings.resetCount = 0;
+ recordSystemSettingsToFileLFS(settingsFileName); // Avoid overwriting LittleFS settings onto SD
+ }
+ settings.resetCount = 0;
+ }
+ else
+ {
+ reuseLastLog = true; // Attempt to reuse previous log
+
+ if (settings.enableResetDisplay == true)
+ {
+ settings.resetCount++;
+ systemPrintf("resetCount: %d\r\n", settings.resetCount);
+ recordSystemSettingsToFileLFS(settingsFileName); // Avoid overwriting LittleFS settings onto SD
+ }
+
+ systemPrint("Reset reason: ");
+ switch (esp_reset_reason())
+ {
+ case ESP_RST_UNKNOWN:
+ systemPrintln("ESP_RST_UNKNOWN");
+ break;
+ case ESP_RST_POWERON:
+ systemPrintln("ESP_RST_POWERON");
+ break;
+ case ESP_RST_SW:
+ systemPrintln("ESP_RST_SW");
+ break;
+ case ESP_RST_PANIC:
+ systemPrintln("ESP_RST_PANIC");
+ break;
+ case ESP_RST_INT_WDT:
+ systemPrintln("ESP_RST_INT_WDT");
+ break;
+ case ESP_RST_TASK_WDT:
+ systemPrintln("ESP_RST_TASK_WDT");
+ break;
+ case ESP_RST_WDT:
+ systemPrintln("ESP_RST_WDT");
+ break;
+ case ESP_RST_DEEPSLEEP:
+ systemPrintln("ESP_RST_DEEPSLEEP");
+ break;
+ case ESP_RST_BROWNOUT:
+ systemPrintln("ESP_RST_BROWNOUT");
+ break;
+ case ESP_RST_SDIO:
+ systemPrintln("ESP_RST_SDIO");
+ break;
+ default:
+ systemPrintln("Unknown");
+ }
+ }
}
void beginSD()
{
- pinMode(pin_microSD_CS, OUTPUT);
- digitalWrite(pin_microSD_CS, HIGH); //Be sure SD is deselected
-
- if (settings.enableSD == true)
- {
- //Max power up time is 250ms: https://www.kingston.com/datasheets/SDCIT-specsheet-64gb_en.pdf
- //Max current is 200mA average across 1s, peak 300mA
- delay(10);
-
- if (sd.begin(SdSpiConfig(pin_microSD_CS, DEDICATED_SPI, SD_SCK_MHZ(settings.spiFrequency), &spi)) == false)
- {
- int tries = 0;
- int maxTries = 2;
- for ( ; tries < maxTries ; tries++)
- {
- Serial.printf("SD init failed. Trying again %d out of %d\n\r", tries + 1, maxTries);
-
- delay(250); //Give SD more time to power up, then try again
- if (sd.begin(SdSpiConfig(pin_microSD_CS, DEDICATED_SPI, SD_SCK_MHZ(settings.spiFrequency), &spi)) == true) break;
- }
-
- if (tries == maxTries)
- {
- Serial.println(F("SD init failed. Is card present? Formatted?"));
- digitalWrite(pin_microSD_CS, HIGH); //Be sure SD is deselected
- online.microSD = false;
+ if(sdCardForcedOffline == true)
return;
- }
- }
- //Change to root directory. All new file creation will be in root.
- if (sd.chdir() == false)
+ bool gotSemaphore;
+
+ online.microSD = false;
+ gotSemaphore = false;
+
+ while (settings.enableSD == true)
{
- Serial.println(F("SD change directory failed"));
- online.microSD = false;
- return;
+ // Setup SD card access semaphore
+ if (sdCardSemaphore == nullptr)
+ sdCardSemaphore = xSemaphoreCreateMutex();
+ else if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_shortWait_ms) != pdPASS)
+ {
+ // This is OK since a retry will occur next loop
+ log_d("sdCardSemaphore failed to yield, Begin.ino line %d", __LINE__);
+ break;
+ }
+ gotSemaphore = true;
+ markSemaphore(FUNCTION_BEGINSD);
+
+ if (USE_SPI_MICROSD)
+ {
+ log_d("Initializing microSD - using SPI, SdFat and SdFile");
+
+ pinMode(pin_microSD_CS, OUTPUT);
+ digitalWrite(pin_microSD_CS, HIGH); // Be sure SD is deselected
+ resetSPI(); // Re-initialize the SPI/SD interface
+
+ // Do a quick test to see if a card is present
+ int tries = 0;
+ int maxTries = 5;
+ while (tries < maxTries)
+ {
+ if (sdPresent() == true)
+ break;
+ // log_d("SD present failed. Trying again %d out of %d", tries + 1, maxTries);
+
+ // Max power up time is 250ms: https://www.kingston.com/datasheets/SDCIT-specsheet-64gb_en.pdf
+ // Max current is 200mA average across 1s, peak 300mA
+ delay(10);
+ tries++;
+ }
+ if (tries == maxTries)
+ break; // Give up loop
+
+ // If an SD card is present, allow SdFat to take over
+ log_d("SD card detected - using SPI and SdFat");
+
+ // Allocate the data structure that manages the microSD card
+ if (!sd)
+ {
+ sd = new SdFat();
+ if (!sd)
+ {
+ log_d("Failed to allocate the SdFat structure!");
+ break;
+ }
+ }
+
+ if (settings.spiFrequency > 16)
+ {
+ systemPrintln("Error: SPI Frequency out of range. Default to 16MHz");
+ settings.spiFrequency = 16;
+ }
+
+ resetSPI(); // Re-initialize the SPI/SD interface
+
+ if (sd->begin(SdSpiConfig(pin_microSD_CS, SHARED_SPI, SD_SCK_MHZ(settings.spiFrequency))) == false)
+ {
+ tries = 0;
+ maxTries = 1;
+ for (; tries < maxTries; tries++)
+ {
+ log_d("SD init failed - using SPI and SdFat. Trying again %d out of %d", tries + 1, maxTries);
+
+ delay(250); // Give SD more time to power up, then try again
+ if (sd->begin(SdSpiConfig(pin_microSD_CS, SHARED_SPI, SD_SCK_MHZ(settings.spiFrequency))) == true)
+ break;
+ }
+
+ if (tries == maxTries)
+ {
+ systemPrintln("SD init failed - using SPI and SdFat. Is card formatted?");
+ digitalWrite(pin_microSD_CS, HIGH); // Be sure SD is deselected
+
+ sdCardForcedOffline = true; //Prevent future scans for SD cards
+
+ // Check reset count and prevent rolling reboot
+ if (settings.resetCount < 5)
+ {
+ if (settings.forceResetOnSDFail == true)
+ ESP.restart();
+ }
+ break;
+ }
+ }
+
+ // Change to root directory. All new file creation will be in root.
+ if (sd->chdir() == false)
+ {
+ systemPrintln("SD change directory failed");
+ break;
+ }
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ // Check to see if a card is present
+ if (sdPresent() == false)
+ break; // Give up on loop
+
+ systemPrintln("Initializing microSD - using SDIO, SD_MMC and File");
+
+ // SDIO MMC
+ if (SD_MMC.begin() == false)
+ {
+ int tries = 0;
+ int maxTries = 1;
+ for (; tries < maxTries; tries++)
+ {
+ log_d("SD init failed - using SD_MMC. Trying again %d out of %d", tries + 1, maxTries);
+
+ delay(250); // Give SD more time to power up, then try again
+ if (SD_MMC.begin() == true)
+ break;
+ }
+
+ if (tries == maxTries)
+ {
+ systemPrintln("SD init failed - using SD_MMC. Is card formatted?");
+
+ // Check reset count and prevent rolling reboot
+ if (settings.resetCount < 5)
+ {
+ if (settings.forceResetOnSDFail == true)
+ ESP.restart();
+ }
+ break;
+ }
+ }
+ }
+#else // COMPILE_SD_MMC
+ else
+ {
+ log_d("SD_MMC not compiled");
+ break; // No SD available.
+ }
+#endif // COMPILE_SD_MMC
+
+ if (createTestFile() == false)
+ {
+ systemPrintln("Failed to create test file. Format SD card with 'SD Card Formatter'.");
+ displaySDFail(5000);
+ break;
+ }
+
+ // Load firmware file from the microSD card if it is present
+ scanForFirmware();
+
+ // Mark card not yet usable for logging
+ sdCardSize = 0;
+ outOfSDSpace = true;
+
+ systemPrintln("microSD: Online");
+ online.microSD = true;
+ break;
}
- //Setup FAT file access semaphore
- if (xFATSemaphore == NULL)
+ // Free the semaphore
+ if (sdCardSemaphore && gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore); // Make the file system available for use
+}
+
+void endSD(bool alreadyHaveSemaphore, bool releaseSemaphore)
+{
+ // Disable logging
+ endLogging(alreadyHaveSemaphore, false);
+
+ // Done with the SD card
+ if (online.microSD)
{
- xFATSemaphore = xSemaphoreCreateMutex();
- if (xFATSemaphore != NULL)
- xSemaphoreGive(xFATSemaphore); //Make the file system available for use
+ if (USE_SPI_MICROSD)
+ sd->end();
+#ifdef COMPILE_SD_MMC
+ else
+ SD_MMC.end();
+#endif // COMPILE_SD_MMC
+
+ online.microSD = false;
+ systemPrintln("microSD: Offline");
}
- if (createTestFile() == false)
+ // Free the caches for the microSD card
+ if (USE_SPI_MICROSD)
{
- Serial.println(F("Failed to create test file. Format SD card with 'SD Card Formatter'."));
- displaySDFail(5000);
- online.microSD = false;
- return;
+ if (sd)
+ {
+ delete sd;
+ sd = nullptr;
+ }
}
- online.microSD = true;
- }
- else
- {
- online.microSD = false;
- }
+ // Release the semaphore
+ if (releaseSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
+}
+
+// Attempt to de-init the SD card - SPI only
+// https://github.com/greiman/SdFat/issues/351
+void resetSPI()
+{
+ if (USE_SPI_MICROSD)
+ {
+ pinMode(pin_microSD_CS, OUTPUT);
+ digitalWrite(pin_microSD_CS, HIGH); // De-select SD card
+
+ // Flush SPI interface
+ SPI.begin();
+ SPI.beginTransaction(SPISettings(400000, MSBFIRST, SPI_MODE0));
+ for (int x = 0; x < 10; x++)
+ SPI.transfer(0XFF);
+ SPI.endTransaction();
+ SPI.end();
+
+ digitalWrite(pin_microSD_CS, LOW); // Select SD card
+
+ // Flush SD interface
+ SPI.begin();
+ SPI.beginTransaction(SPISettings(400000, MSBFIRST, SPI_MODE0));
+ for (int x = 0; x < 10; x++)
+ SPI.transfer(0XFF);
+ SPI.endTransaction();
+ SPI.end();
+
+ digitalWrite(pin_microSD_CS, HIGH); // Deselet SD card
+ }
}
-//We do not start the UART2 for GNSS->BT reception here because the interrupts would be pinned to core 1
-//competing with I2C interrupts
-//See issue: https://github.com/espressif/arduino-esp32/issues/3386
-//We instead start a task that runs on core 0, that then begins serial
+// We want the UART2 interrupts to be pinned to core 0 to avoid competing with I2C interrupts
+// We do not start the UART2 for GNSS->BT reception here because the interrupts would be pinned to core 1
+// We instead start a task that runs on core 0, that then begins serial
+// See issue: https://github.com/espressif/arduino-esp32/issues/3386
void beginUART2()
{
- if (startUART2TaskHandle == NULL) xTaskCreatePinnedToCore(
- startUART2Task,
- "UARTStart", //Just for humans
- 2000, //Stack Size
- NULL, //Task input parameter
- 0, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
- &startUART2TaskHandle, //Task handle
- 0); //Core where task should run, 0=core, 1=Arduino
-
- while (uart2Started == false) //Wait for task to run once
- delay(1);
+ size_t length;
+
+ // Determine the length of data to be retained in the ring buffer
+ // after discarding the oldest data
+ length = settings.gnssHandlerBufferSize;
+ rbOffsetEntries = (length >> 1) / AVERAGE_SENTENCE_LENGTH_IN_BYTES;
+ length = settings.gnssHandlerBufferSize + (rbOffsetEntries * sizeof(RING_BUFFER_OFFSET));
+ ringBuffer = nullptr;
+ rbOffsetArray = (RING_BUFFER_OFFSET *)malloc(length);
+ if (!rbOffsetArray)
+ {
+ rbOffsetEntries = 0;
+ systemPrintln("ERROR: Failed to allocate the ring buffer!");
+ }
+ else
+ {
+ ringBuffer = (uint8_t *)&rbOffsetArray[rbOffsetEntries];
+ rbOffsetArray[0] = 0;
+ if (pinUART2TaskHandle == nullptr)
+ xTaskCreatePinnedToCore(
+ pinUART2Task,
+ "UARTStart", // Just for humans
+ 2000, // Stack Size
+ nullptr, // Task input parameter
+ 0, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest
+ &pinUART2TaskHandle, // Task handle
+ settings.gnssUartInterruptsCore); // Core where task should run, 0=core, 1=Arduino
+
+ while (uart2pinned == false) // Wait for task to run once
+ delay(1);
+ }
}
-//ESP32 requires the creation of an EEPROM space
-void beginEEPROM()
+// Assign UART2 interrupts to the core that started the task. See:
+// https://github.com/espressif/arduino-esp32/issues/3386
+void pinUART2Task(void *pvParameters)
{
- if (EEPROM.begin(EEPROM_SIZE) == false)
- Serial.println(F("beginEEPROM: Failed to initialize EEPROM"));
- else
- online.eeprom = true;
+ // Note: ESP32 2.0.6 does some strange auto-bauding thing here which takes 20s to complete if there is no data for
+ // it to auto-baud.
+ // That's fine for most RTK products, but causes the Ref Stn to stall for 20s. However, it doesn't stall with
+ // ESP32 2.0.2... Uncomment these lines to prevent the stall if/when we upgrade to ESP32 ~2.0.6.
+ // #if defined(REF_STN_GNSS_DEBUG)
+ // if (ENABLE_DEVELOPER && productVariant == REFERENCE_STATION)
+ // #else // REF_STN_GNSS_DEBUG
+ // if (USE_I2C_GNSS)
+ // #endif // REF_STN_GNSS_DEBUG
+ {
+ serialGNSS.setRxBufferSize(
+ settings.uartReceiveBufferSize); // TODO: work out if we can reduce or skip this when using SPI GNSS
+ serialGNSS.setTimeout(settings.serialTimeoutGNSS); // Requires serial traffic on the UART pins for detection
+ serialGNSS.begin(settings.dataPortBaud); // UART2 on pins 16/17 for SPP. The ZED-F9P will be configured to
+ // output NMEA over its UART1 at the same rate.
+
+ // 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
+ }
+
+ uart2pinned = true;
+
+ vTaskDelete(nullptr); // Delete task once it has run once
+}
+
+void beginFS()
+{
+ if (online.fs == false)
+ {
+ if (LittleFS.begin(true) == false) // Format LittleFS if begin fails
+ {
+ systemPrintln("Error: LittleFS not online");
+ }
+ else
+ {
+ systemPrintln("LittleFS Started");
+ online.fs = true;
+ }
+ }
}
-void beginDisplay()
+// Check if configureViaEthernet.txt exists
+// Used to indicate if SparkFun_WebServer_ESP32_W5500 needs _exclusive_ access to SPI and interrupts
+bool checkConfigureViaEthernet()
{
- //0x3D is default on Qwiic board
- if (isConnected(0x3D) == true || isConnected(0x3C) == true)
- {
- online.display = true;
+ if (online.fs == false)
+ return false;
+
+ if (LittleFS.exists("/configureViaEthernet.txt"))
+ {
+ log_d("LittleFS configureViaEthernet.txt exists");
+ LittleFS.remove("/configureViaEthernet.txt");
+ return true;
+ }
+
+ return false;
+}
+
+// Force configure-via-ethernet mode by creating configureViaEthernet.txt in LittleFS
+// Used to indicate if SparkFun_WebServer_ESP32_W5500 needs _exclusive_ access to SPI and interrupts
+bool forceConfigureViaEthernet()
+{
+ if (online.fs == false)
+ return false;
+
+ if (LittleFS.exists("/configureViaEthernet.txt"))
+ {
+ log_d("LittleFS configureViaEthernet.txt already exists");
+ return true;
+ }
- oled.setI2CTransactionSize(64); //Increase to page size of 64. Slight speed improvement over 32 bytes.
+ File cveFile = LittleFS.open("/configureViaEthernet.txt", FILE_WRITE);
+ cveFile.close();
- displaySplash();
- }
+ if (LittleFS.exists("/configureViaEthernet.txt"))
+ return true;
+
+ log_d("Unable to create configureViaEthernet.txt on LittleFS");
+ return false;
}
-//Connect to and configure ZED-F9P
+// Connect to ZED module and identify particulars
void beginGNSS()
{
- if (i2cGNSS.begin() == false)
- {
- //Try again with power on delay
- delay(1000); //Wait for ZED-F9P to power up before it can respond to ACK
- if (i2cGNSS.begin() == false)
- {
- Serial.println(F("u-blox GNSS not detected at default I2C address. Hard stop."));
- displayGNSSFail(0);
- blinkError(ERROR_NO_I2C);
- }
- }
-
- //Increase transactions to reduce transfer time
- i2cGNSS.i2cTransactionSize = 128;
-
- //Check the firmware version of the ZED-F9P. Based on Example21_ModuleInfo.
- if (i2cGNSS.getModuleInfo(1100) == true) // Try to get the module info
- {
- strcpy(zedFirmwareVersion, i2cGNSS.minfo.extension[1]);
-
- //i2cGNSS.minfo.extension[1] looks like 'FWVER=HPG 1.12'
- //Replace = with - to avoid NVM parsing issues
- char *ptr = strchr(zedFirmwareVersion, '=');
- if (ptr != NULL)
- zedFirmwareVersion[ptr - zedFirmwareVersion] = ':';
-
- Serial.print(F("ZED-F9P firmware: "));
- Serial.println(zedFirmwareVersion);
-
- // if (strcmp(i2cGNSS.minfo.extension[1], latestZEDFirmware) != 0)
- // {
- // Serial.print(F("The ZED-F9P appears to have outdated firmware. Found: "));
- // Serial.println(i2cGNSS.minfo.extension[1]);
- // Serial.print(F("The Surveyor works best with "));
- // Serial.println(latestZEDFirmware);
- // Serial.print(F("Please upgrade using u-center."));
- // Serial.println();
- // }
- // else
- // {
- // Serial.println(F("ZED-F9P firmware is current"));
- // }
- }
-
- bool response = configureUbloxModule();
- if (response == false)
- {
- //Try once more
- Serial.println(F("Failed to configure module. Trying again."));
- delay(1000);
- response = configureUbloxModule();
+ // Skip if going into configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ log_d("configureViaEthernet: skipping beginGNSS");
+ return;
+ }
+
+ // If we're using SPI, then increase the logging buffer
+ if (USE_SPI_GNSS)
+ {
+ SPI.begin(); // Begin SPI here - beginSD has not yet been called
+
+ // setFileBufferSize must be called _before_ .begin
+ // Use gnssHandlerBufferSize for now. TODO: work out if the SPI GNSS needs its own buffer size setting
+ // Also used by Tasks.ino
+ theGNSS.setFileBufferSize(settings.gnssHandlerBufferSize);
+ theGNSS.setRTCMBufferSize(settings.gnssHandlerBufferSize);
+ }
+
+ if (USE_I2C_GNSS)
+ {
+ if (theGNSS.begin() == false)
+ {
+ log_d("GNSS Failed to begin. Trying again.");
+ // Try again with power on delay
+ delay(1000); // Wait for ZED-F9P to power up before it can respond to ACK
+ if (theGNSS.begin() == false)
+ {
+ log_d("GNSS offline");
+ displayGNSSFail(1000);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (theGNSS.begin(SPI, pin_GNSS_CS) == false)
+ {
+ log_d("GNSS Failed to begin. Trying again.");
+
+ // Try again with power on delay
+ delay(1000); // Wait for ZED-F9P to power up before it can respond to ACK
+ if (theGNSS.begin(SPI, pin_GNSS_CS) == false)
+ {
+ log_d("GNSS offline");
+ displayGNSSFail(1000);
+ return;
+ }
+ }
+
+ if (theGNSS.getFileBufferSize() != settings.gnssHandlerBufferSize) // Need to call getFileBufferSize after begin
+ {
+ log_d("GNSS offline - no RAM for file buffer");
+ displayGNSSFail(1000);
+ return;
+ }
+ if (theGNSS.getRTCMBufferSize() != settings.gnssHandlerBufferSize) // Need to call getRTCMBufferSize after begin
+ {
+ log_d("GNSS offline - no RAM for RTCM buffer");
+ displayGNSSFail(1000);
+ return;
+ }
+ }
+
+ // Increase transactions to reduce transfer time
+ if (USE_I2C_GNSS)
+ theGNSS.i2cTransactionSize = 128;
+
+ // Auto-send Valset messages before the buffer is completely full
+ theGNSS.autoSendCfgValsetAtSpaceRemaining(16);
+
+ // Check the firmware version of the ZED-F9P. Based on Example21_ModuleInfo.
+ if (theGNSS.getModuleInfo(1100) == true) // Try to get the module info
+ {
+ // Clear the module type. Default to PLATFORM_F9P below - if needed
+ zedModuleType = 0;
+
+ // Determine if we have a ZED-F9P (Express/Facet) or an ZED-F9R (Express Plus/Facet Plus)
+ if (strstr(theGNSS.getModuleName(), "ZED-F9P") != nullptr)
+ zedModuleType = PLATFORM_F9P;
+ else if (strstr(theGNSS.getModuleName(), "ZED-F9R") != nullptr)
+ zedModuleType = PLATFORM_F9R;
+
+ // Reconstruct the firmware version
+ snprintf(zedFirmwareVersion, sizeof(zedFirmwareVersion), "%s %d.%02d", theGNSS.getFirmwareType(),
+ theGNSS.getFirmwareVersionHigh(), theGNSS.getFirmwareVersionLow());
+
+ // Construct the firmware version as uint8_t. Note: will fail above 2.55!
+ zedFirmwareVersionInt = (theGNSS.getFirmwareVersionHigh() * 100) + theGNSS.getFirmwareVersionLow();
+
+ // Check if this is known firmware
+ //"1.51" - ZED-F9P released November, 2024
+ //"1.50" - ZED-F9P released July, 2024
+ //"1.32" - ZED-F9P released May, 2022
+ //"1.30" - ZED-F9P (HPG) released Dec, 2021. Also ZED-F9R (HPS) released Sept, 2022
+ //"1.21" - F9R HPS v1.21
+ //"1.20" - Mostly for F9R HPS 1.20, but also F9P HPG v1.20
+
+ const uint8_t knownFirmwareVersions[] = {151, 150, 132, 130, 121, 120, 113, 112, 100};
+ bool knownFirmware = false;
+ for (uint8_t i = 0; i < (sizeof(knownFirmwareVersions) / sizeof(uint8_t)); i++)
+ {
+ if (zedFirmwareVersionInt == knownFirmwareVersions[i])
+ knownFirmware = true;
+ }
+
+ if (!knownFirmware)
+ {
+ systemPrintf("Unknown firmware version: %s\r\n", zedFirmwareVersion);
+ // Let's be clever and allow ZED-F9P firmware versions higher than knownFirmwareVersions[0]
+ if ((zedModuleType == PLATFORM_F9P) && (zedFirmwareVersionInt > knownFirmwareVersions[0]))
+ {
+ zedFirmwareVersionInt = knownFirmwareVersions[0];
+ systemPrintf("Assuming firmware compatibility with %d.%02d\r\n", zedFirmwareVersionInt / 100, zedFirmwareVersionInt % 100);
+ }
+ else
+ {
+ zedFirmwareVersionInt = 99; // 0.99 invalid firmware version
+ }
+ }
+
+ if (zedModuleType == 0)
+ {
+ systemPrintf("Unknown ZED module: %s. Assuming compatibility with ZED-F9P\r\n", theGNSS.getModuleName());
+ zedModuleType = PLATFORM_F9P;
+ }
+
+ printZEDInfo(); // Print module type and firmware version
+ }
+
+ UBX_SEC_UNIQID_data_t chipID;
+ if (theGNSS.getUniqueChipId(&chipID))
+ {
+ snprintf(zedUniqueId, sizeof(zedUniqueId), "%s", theGNSS.getUniqueChipIdStr(&chipID));
+ }
+
+ online.gnss = true;
+}
+
+// Configuration can take >1s so configure during splash
+void configureGNSS()
+{
+ // Skip if going into configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ log_d("configureViaEthernet: skipping configureGNSS");
+ return;
+ }
+
+ if (online.gnss == false)
+ return;
+
+ // Check if the ubxMessageRates or ubxMessageRatesBase need to be defaulted
+ checkMessageRates();
+
+ theGNSS.setAutoPVTcallbackPtr(&storePVTdata); // Enable automatic NAV PVT messages with callback to storePVTdata
+ theGNSS.setAutoHPPOSLLHcallbackPtr(
+ &storeHPdata); // Enable automatic NAV HPPOSLLH messages with callback to storeHPdata
+ theGNSS.setRTCM1005InputcallbackPtr(
+ &storeRTCM1005data); // Configure a callback for RTCM 1005 - parsed from pushRawData
+ theGNSS.setRTCM1006InputcallbackPtr(
+ &storeRTCM1006data); // Configure a callback for RTCM 1006 - parsed from pushRawData
+
+ if (HAS_GNSS_TP_INT)
+ theGNSS.setAutoTIMTPcallbackPtr(
+ &storeTIMTPdata); // Enable automatic TIM TP messages with callback to storeTIMTPdata
+
+ if (HAS_ANTENNA_SHORT_OPEN)
+ {
+ theGNSS.newCfgValset();
+
+ theGNSS.addCfgValset(UBLOX_CFG_HW_ANT_CFG_SHORTDET, 1); // Enable antenna short detection
+ theGNSS.addCfgValset(UBLOX_CFG_HW_ANT_CFG_OPENDET, 1); // Enable antenna open detection
+
+ if (theGNSS.sendCfgValset())
+ {
+ theGNSS.setAutoMONHWcallbackPtr(
+ &storeMONHWdata); // Enable automatic MON HW messages with callback to storeMONHWdata
+ }
+ else
+ {
+ systemPrintln("Failed to configure GNSS antenna detection");
+ }
+ }
+
+ // 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
+ // the settings file or internal settings.
+ if (settings.updateZEDSettings == false)
+ {
+ log_d("Skipping ZED configuration");
+ return;
+ }
+
+ bool response = configureUbloxModule();
if (response == false)
{
- Serial.println(F("Failed to configure module. Hard stop."));
- displayGNSSFail(0);
- blinkError(ERROR_GPS_CONFIG_FAIL);
+ // Try once more
+ systemPrintln("Failed to configure GNSS module. Trying again.");
+ delay(1000);
+ response = configureUbloxModule();
+
+ if (response == false)
+ {
+ systemPrintln("Failed to configure GNSS module.");
+ displayGNSSFail(1000);
+ online.gnss = false;
+ return;
+ }
}
- }
- Serial.println(F("GNSS configuration complete"));
+ systemPrintln("GNSS configuration complete");
+}
+
+// Begin interrupts
+void beginInterrupts()
+{
+ // Skip if going into configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ log_d("configureViaEthernet: skipping beginInterrupts");
+ return;
+ }
- online.gnss = true;
+ if (HAS_GNSS_TP_INT) // If the GNSS Time Pulse is connected, use it as an interrupt to set the clock accurately
+ {
+ pinMode(pin_GNSS_TimePulse, INPUT);
+ attachInterrupt(pin_GNSS_TimePulse, tpISR, RISING);
+ }
+
+#ifdef COMPILE_ETHERNET
+ if (HAS_ETHERNET)
+ {
+ pinMode(pin_Ethernet_Interrupt, INPUT_PULLUP); // Prepare the interrupt pin
+ attachInterrupt(pin_Ethernet_Interrupt, ethernetISR, FALLING); // Attach the interrupt
+ }
+#endif // COMPILE_ETHERNET
}
-//Set LEDs for output and configure PWM
+// Set LEDs for output and configure PWM
void beginLEDs()
{
- if (productVariant == RTK_SURVEYOR)
- {
- pinMode(pin_positionAccuracyLED_1cm, OUTPUT);
- pinMode(pin_positionAccuracyLED_10cm, OUTPUT);
- pinMode(pin_positionAccuracyLED_100cm, OUTPUT);
- pinMode(pin_baseStatusLED, OUTPUT);
- pinMode(pin_bluetoothStatusLED, OUTPUT);
- pinMode(pin_baseSwitch, INPUT_PULLUP); //HIGH = rover, LOW = base
-
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- digitalWrite(pin_baseStatusLED, LOW);
- digitalWrite(pin_bluetoothStatusLED, LOW);
-
- ledcSetup(ledRedChannel, freq, resolution);
- ledcSetup(ledGreenChannel, freq, resolution);
-
- ledcAttachPin(pin_batteryLevelLED_Red, ledRedChannel);
- ledcAttachPin(pin_batteryLevelLED_Green, ledGreenChannel);
-
- ledcWrite(ledRedChannel, 0);
- ledcWrite(ledGreenChannel, 0);
- }
+ if (productVariant == RTK_SURVEYOR)
+ {
+ pinMode(pin_positionAccuracyLED_1cm, OUTPUT);
+ pinMode(pin_positionAccuracyLED_10cm, OUTPUT);
+ pinMode(pin_positionAccuracyLED_100cm, OUTPUT);
+ pinMode(pin_baseStatusLED, OUTPUT);
+ pinMode(pin_bluetoothStatusLED, OUTPUT);
+ pinMode(pin_setupButton, INPUT_PULLUP); // HIGH = rover, LOW = base
+
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ digitalWrite(pin_baseStatusLED, LOW);
+ digitalWrite(pin_bluetoothStatusLED, LOW);
+
+ ledcSetup(ledRedChannel, pwmFreq, pwmResolution);
+ ledcSetup(ledGreenChannel, pwmFreq, pwmResolution);
+ ledcSetup(ledBTChannel, pwmFreq, pwmResolution);
+
+ ledcAttachPin(pin_batteryLevelLED_Red, ledRedChannel);
+ ledcAttachPin(pin_batteryLevelLED_Green, ledGreenChannel);
+ ledcAttachPin(pin_bluetoothStatusLED, ledBTChannel);
+
+ ledcWrite(ledRedChannel, 0);
+ ledcWrite(ledGreenChannel, 0);
+ ledcWrite(ledBTChannel, 0);
+ }
+ else if (productVariant == REFERENCE_STATION)
+ {
+ pinMode(pin_baseStatusLED, OUTPUT);
+ digitalWrite(pin_baseStatusLED, LOW);
+ }
}
-//Configure the on board MAX17048 fuel gauge
+// Configure the on board MAX17048 fuel gauge
void beginFuelGauge()
{
- // Set up the MAX17048 LiPo fuel gauge
- if (lipo.begin() == false)
- {
- Serial.println(F("MAX17048 not detected. Continuing."));
- return;
- }
+ if (HAS_NO_BATTERY)
+ return; // Reference station does not have a battery
+
+ // Set up the MAX17048 LiPo fuel gauge
+ if (lipo.begin() == false)
+ {
+ systemPrintln("Fuel gauge not detected.");
+ return;
+ }
+
+ online.battery = true;
+
+ // Always use hibernate mode
+ if (lipo.getHIBRTActThr() < 0xFF)
+ lipo.setHIBRTActThr((uint8_t)0xFF);
+ if (lipo.getHIBRTHibThr() < 0xFF)
+ lipo.setHIBRTHibThr((uint8_t)0xFF);
+
+ systemPrintln("Fuel gauge configuration complete");
- //Always use hibernate mode
- if (lipo.getHIBRTActThr() < 0xFF) lipo.setHIBRTActThr((uint8_t)0xFF);
- if (lipo.getHIBRTHibThr() < 0xFF) lipo.setHIBRTHibThr((uint8_t)0xFF);
+ checkBatteryLevels(); // Force check so you see battery level immediately at power on
- Serial.println(F("MAX17048 configuration complete"));
+ // Check to see if we are dangerously low
+ if (battLevel < 5 && battChangeRate < 0.5) // 5% and not charging
+ {
+ systemPrintln("Battery too low. Please charge. Shutting down...");
+
+ if (online.display == true)
+ displayMessage("Charge Battery", 0);
+
+ delay(2000);
- online.battery = true;
+ powerDown(false); // Don't display 'Shutting Down'
+ }
}
-//Begin accelerometer if available
+// Begin accelerometer if available
void beginAccelerometer()
{
- if (accel.begin() == false)
- {
- online.accelerometer = false;
- return;
- }
+ if (accel.begin() == false)
+ {
+ online.accelerometer = false;
- //The larger the avgAmount the faster we should read the sensor
- //accel.setDataRate(LIS2DH12_ODR_100Hz); //6 measurements a second
- accel.setDataRate(LIS2DH12_ODR_400Hz); //25 measurements a second
+ return;
+ }
+
+ // The larger the avgAmount the faster we should read the sensor
+ // accel.setDataRate(LIS2DH12_ODR_100Hz); //6 measurements a second
+ accel.setDataRate(LIS2DH12_ODR_400Hz); // 25 measurements a second
- Serial.println(F("Accelerometer configuration complete"));
+ systemPrintln("Accelerometer configuration complete");
- online.accelerometer = true;
+ online.accelerometer = true;
}
-//Depending on platform and previous power down state, set system state
+// Depending on platform and previous power down state, set system state
void beginSystemState()
{
- if (productVariant == RTK_SURVEYOR)
- {
- //Assume Rover. checkButtons() will correct as needed.
- systemState = STATE_ROVER_NOT_STARTED;
- buttonPreviousState = BUTTON_BASE;
- }
- if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS)
- {
- systemState = settings.lastState; //Return to system state previous to power down.
-
- if (systemState == STATE_ROVER_NOT_STARTED)
- buttonPreviousState = BUTTON_ROVER;
- else if (systemState == STATE_BASE_NOT_STARTED)
- buttonPreviousState = BUTTON_BASE;
+ if (systemState > STATE_NOT_SET)
+ {
+ systemPrintln("Unknown state - factory reset");
+ factoryReset(false); // We do not have the SD semaphore
+ }
+
+ if (productVariant == RTK_SURVEYOR)
+ {
+ if (settings.lastState == STATE_NOT_SET) // Default
+ {
+ systemState = STATE_ROVER_NOT_STARTED;
+ settings.lastState = systemState;
+ }
+
+ // If the rocker switch was moved while off, force module settings
+ // When switch is set to '1' = BASE, pin will be shorted to ground
+ if (settings.lastState == STATE_ROVER_NOT_STARTED && digitalRead(pin_setupButton) == LOW)
+ settings.updateZEDSettings = true;
+ else if (settings.lastState == STATE_BASE_NOT_STARTED && digitalRead(pin_setupButton) == HIGH)
+ settings.updateZEDSettings = true;
+
+ systemState = STATE_ROVER_NOT_STARTED; // Assume Rover. ButtonCheckTask() will correct as needed.
+
+ setupBtn = new Button(pin_setupButton); // Create the button in memory
+ // Allocation failure handled in ButtonCheckTask
+ }
+ else if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS_PLUS)
+ {
+ if (settings.lastState == STATE_NOT_SET) // Default
+ {
+ systemState = STATE_ROVER_NOT_STARTED;
+ settings.lastState = systemState;
+ }
+
+ if (online.lband == false)
+ systemState =
+ settings
+ .lastState; // Return to either Rover or Base Not Started. The last state previous to power down.
+ else
+ systemState = STATE_KEYS_STARTED; // Begin process for getting new keys
+
+ setupBtn = new Button(pin_setupButton); // Create the button in memory
+ powerBtn = new Button(pin_powerSenseAndControl); // Create the button in memory
+ // Allocation failures handled in ButtonCheckTask
+ }
+ else if (productVariant == RTK_FACET || productVariant == RTK_FACET_LBAND ||
+ productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ if (settings.lastState == STATE_NOT_SET) // Default
+ {
+ systemState = STATE_ROVER_NOT_STARTED;
+ settings.lastState = systemState;
+ }
+
+ if (online.lband == false)
+ systemState =
+ settings
+ .lastState; // Return to either Rover or Base Not Started. The last state previous to power down.
+ else
+ systemState = STATE_KEYS_STARTED; // Begin process for getting new keys
+
+ firstRoverStart = true; // Allow user to enter test screen during first rover start
+ if (systemState == STATE_BASE_NOT_STARTED)
+ firstRoverStart = false;
+
+ powerBtn = new Button(pin_powerSenseAndControl); // Create the button in memory
+ // Allocation failure handled in ButtonCheckTask
+ }
+ else if (productVariant == REFERENCE_STATION)
+ {
+ if (settings.lastState == STATE_NOT_SET) // Default
+ {
+ systemState = STATE_BASE_NOT_STARTED;
+ settings.lastState = systemState;
+ }
+
+ systemState =
+ settings
+ .lastState; // Return to either NTP, Base or Rover Not Started. The last state previous to power down.
+
+ setupBtn = new Button(pin_setupButton); // Create the button in memory
+ // Allocation failure handled in ButtonCheckTask
+ }
+
+ // Starts task for monitoring button presses
+ if (ButtonCheckTaskHandle == nullptr)
+ xTaskCreate(ButtonCheckTask,
+ "BtnCheck", // Just for humans
+ buttonTaskStackSize, // Stack Size
+ nullptr, // Task input parameter
+ ButtonCheckTaskPriority,
+ &ButtonCheckTaskHandle); // Task handle
+}
+
+// Setup the timepulse output on the PPS pin for external triggering
+// Setup TM2 time stamp input as need
+bool beginExternalTriggers()
+{
+ // Skip if going into configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ log_d("configureViaEthernet: skipping beginExternalTriggers");
+ return (false);
+ }
+
+ if (online.gnss == false)
+ return (false);
+
+ // If our settings haven't changed, trust ZED's settings
+ if (settings.updateZEDSettings == 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;
+
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio)
+ response &=
+ theGNSS.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 &= theGNSS.addCfgValset(UBLOX_CFG_TP_TP1_ENA, settings.enableExternalPulse); // Enable/disable timepulse
+ response &=
+ theGNSS.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 &= theGNSS.addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us
+ response &= theGNSS.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 &= theGNSS.addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1,
+ settings.externalPulseTimeBetweenPulse_us); // Set the period between pulses is us
+ response &=
+ theGNSS.addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, settings.externalPulseLength_us); // Set the pulse length in us
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("beginExternalTriggers config failed");
+
+ if (settings.enableExternalHardwareEventLogging == true)
+ {
+ theGNSS.setAutoTIMTM2callbackPtr(
+ &eventTriggerReceived); // Enable automatic TIM TM2 messages with callback to eventTriggerReceived
+ }
else
- buttonPreviousState = BUTTON_ROVER;
- }
+ theGNSS.setAutoTIMTM2callbackPtr(nullptr);
+
+ return (response);
+}
+
+void beginIdleTasks()
+{
+ if (settings.enablePrintIdleTime == true)
+ {
+ char taskName[32];
+
+ for (int index = 0; index < MAX_CPU_CORES; index++)
+ {
+ snprintf(taskName, sizeof(taskName), "IdleTask%d", index);
+ if (idleTaskHandle[index] == nullptr)
+ xTaskCreatePinnedToCore(
+ idleTask,
+ taskName, // Just for humans
+ 2000, // Stack Size
+ nullptr, // Task input parameter
+ 0, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest
+ &idleTaskHandle[index], // Task handle
+ index); // Core where task should run, 0=core, 1=Arduino
+ }
+ }
+}
+
+void beginI2C()
+{
+ if (pinI2CTaskHandle == nullptr)
+ xTaskCreatePinnedToCore(
+ pinI2CTask,
+ "I2CStart", // Just for humans
+ 2000, // Stack Size
+ nullptr, // Task input parameter
+ 0, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest
+ &pinI2CTaskHandle, // Task handle
+ settings.i2cInterruptsCore); // Core where task should run, 0=core, 1=Arduino
+
+ while (i2cPinned == false) // Wait for task to run once
+ delay(1);
+}
+
+// Assign I2C interrupts to the core that started the task. See: https://github.com/espressif/arduino-esp32/issues/3386
+void pinI2CTask(void *pvParameters)
+{
+ bool i2cBusAvailable;
+ uint32_t timer;
+
+ Wire.begin(); // Start I2C on core the core that was chosen when the task was started
+ // Wire.setClock(400000);
+
+ // Display the device addresses
+ i2cBusAvailable = false;
+ for (uint8_t addr = 0; addr < 127; addr++)
+ {
+ // begin/end wire transmission to see if the bus is responding correctly
+ // All good: 0ms, response 2
+ // SDA/SCL shorted: 1000ms timeout, response 5
+ // SCL/VCC shorted: 14ms, response 5
+ // SCL/GND shorted: 1000ms, response 5
+ // SDA/VCC shorted: 1000ms, response 5
+ // SDA/GND shorted: 14ms, response 5
+ timer = millis();
+ Wire.beginTransmission(addr);
+ if (Wire.endTransmission() == 0)
+ {
+ i2cBusAvailable = true;
+ switch (addr)
+ {
+ default: {
+ systemPrintf("0x%02x\r\n", addr);
+ break;
+ }
+
+ case 0x19: {
+ systemPrintf("0x%02x - LIS2DH12 Accelerometer\r\n", addr);
+ break;
+ }
+
+ case 0x36: {
+ systemPrintf("0x%02x - MAX17048 Fuel Gauge\r\n", addr);
+ break;
+ }
+
+ case 0x3d: {
+ systemPrintf("0x%02x - SSD1306 (64x48) OLED Driver\r\n", addr);
+ break;
+ }
+
+ case 0x42: {
+ systemPrintf("0x%02x - u-blox ZED-F9P GNSS Receiver\r\n", addr);
+ break;
+ }
+
+ case 0x43: {
+ systemPrintf("0x%02x - u-blox NEO-D9S-00B Correction Data Receiver\r\n", addr);
+ break;
+ }
+
+ case 0x60: {
+ systemPrintf("0x%02x - Crypto Coprocessor\r\n", addr);
+ break;
+ }
+ }
+ }
+ else if ((millis() - timer) > 3)
+ {
+ systemPrintln("Error: I2C Bus Not Responding");
+ i2cBusAvailable = false;
+ break;
+ }
+ }
+
+ // Update the I2C status
+ online.i2c = i2cBusAvailable;
+ i2cPinned = true;
+ vTaskDelete(nullptr); // Delete task once it has run once
+}
+
+// Depending on radio selection, begin hardware
+void radioStart()
+{
+ if (settings.radioType == RADIO_EXTERNAL)
+ {
+ espnowStop();
+
+ // Nothing to start. UART2 of ZED is connected to external Radio port and is configured at
+ // configureUbloxModule()
+ }
+ else if (settings.radioType == RADIO_ESPNOW)
+ espnowStart();
+}
+
+// Start task to determine SD card size
+void beginSDSizeCheckTask()
+{
+ if (sdSizeCheckTaskHandle == nullptr)
+ {
+ xTaskCreate(sdSizeCheckTask, // Function to call
+ "SDSizeCheck", // Just for humans
+ sdSizeCheckStackSize, // Stack Size
+ nullptr, // Task input parameter
+ sdSizeCheckTaskPriority, // Priority
+ &sdSizeCheckTaskHandle); // Task handle
+
+ log_d("sdSizeCheck Task started");
+ }
+}
+
+void deleteSDSizeCheckTask()
+{
+ // Delete task once it's complete
+ if (sdSizeCheckTaskHandle != nullptr)
+ {
+ vTaskDelete(sdSizeCheckTaskHandle);
+ sdSizeCheckTaskHandle = nullptr;
+ sdSizeCheckTaskComplete = false;
+ log_d("sdSizeCheck Task deleted");
+ }
+}
+
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// Time Pulse ISR
+// Triggered by the rising edge of the time pulse signal, indicates the top-of-second.
+// Set the ESP32 RTC to UTC
+
+void tpISR()
+{
+ unsigned long millisNow = millis();
+ if (!inMainMenu) // Skip this if the menu is open
+ {
+ if (online.rtc) // Only sync if the RTC has been set via PVT first
+ {
+ if (timTpUpdated) // Only sync if timTpUpdated is true
+ {
+ if (millisNow - lastRTCSync >
+ syncRTCInterval) // Only sync if it is more than syncRTCInterval since the last sync
+ {
+ if (millisNow < (timTpArrivalMillis + 999)) // Only sync if the GNSS time is not stale
+ {
+ if (fullyResolved) // Only sync if GNSS time is fully resolved
+ {
+ if (tAcc < 5000) // Only sync if the tAcc is better than 5000ns
+ {
+ // To perform the time zone adjustment correctly, it's easiest if we convert the GNSS
+ // time and date into Unix epoch first and then apply the timeZone offset
+ uint32_t epochSecs = timTpEpoch;
+ uint32_t epochMicros = timTpMicros;
+ epochSecs += settings.timeZoneSeconds;
+ epochSecs += settings.timeZoneMinutes * 60;
+ epochSecs += settings.timeZoneHours * 60 * 60;
+
+ // Set the internal system time
+ rtc.setTime(epochSecs, epochMicros);
+
+ lastRTCSync = millis();
+ rtcSyncd = true;
+
+ gnssSyncTv.tv_sec = epochSecs; // Store the timeval of the sync
+ gnssSyncTv.tv_usec = epochMicros;
+
+ if (syncRTCInterval < 59000) // From now on, sync every minute
+ syncRTCInterval = 59000;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/Firmware/RTK_Surveyor/Bluetooth.ino b/Firmware/RTK_Surveyor/Bluetooth.ino
new file mode 100644
index 000000000..7648605d0
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Bluetooth.ino
@@ -0,0 +1,354 @@
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ Bluetooth State Values:
+ BT_OFF = 0,
+ BT_NOTCONNECTED,
+ BT_CONNECTED,
+
+ Bluetooth States:
+
+ BT_OFF (Using WiFi)
+ | ^
+ Use Bluetooth | | Use WiFi
+ bluetoothStart | | bluetoothStop
+ v |
+ BT_NOTCONNECTED
+ | ^
+ Client connected | | Client disconnected
+ v |
+ BT_CONNECTED
+
+ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+//----------------------------------------
+// Locals - compiled out
+//----------------------------------------
+
+static volatile BTState bluetoothState = BT_OFF;
+
+#ifdef COMPILE_BT
+BTSerialInterface *bluetoothSerial;
+
+//----------------------------------------
+// Bluetooth Routines - compiled out
+//----------------------------------------
+
+// Call back for when BT connection event happens (connected/disconnect)
+// Used for updating the bluetoothState state machine
+void bluetoothCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
+{
+ if (event == ESP_SPP_SRV_OPEN_EVT)
+ {
+ systemPrintln("BT client Connected");
+ bluetoothState = BT_CONNECTED;
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_bluetoothStatusLED, HIGH);
+ }
+
+ if (event == ESP_SPP_CLOSE_EVT)
+ {
+ systemPrintln("BT client disconnected");
+
+ btPrintEcho = false;
+ btPrintEchoExit = true; // Force exit all config menus
+ printEndpoint = PRINT_ENDPOINT_SERIAL;
+
+ bluetoothState = BT_NOTCONNECTED;
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_bluetoothStatusLED, LOW);
+ }
+}
+
+#endif // COMPILE_BT
+
+//----------------------------------------
+// Global Bluetooth Routines
+//----------------------------------------
+
+// Return the Bluetooth state
+byte bluetoothGetState()
+{
+#ifdef COMPILE_BT
+ return bluetoothState;
+#else // COMPILE_BT
+ return BT_OFF;
+#endif // COMPILE_BT
+}
+
+// Read data from the Bluetooth device
+int bluetoothRead(uint8_t *buffer, int length)
+{
+#ifdef COMPILE_BT
+ return bluetoothSerial->readBytes(buffer, length);
+#else // COMPILE_BT
+ return 0;
+#endif // COMPILE_BT
+}
+
+// Read data from the Bluetooth device
+uint8_t bluetoothRead()
+{
+#ifdef COMPILE_BT
+ return bluetoothSerial->read();
+#else // COMPILE_BT
+ return 0;
+#endif // COMPILE_BT
+}
+
+// Determine if data is available
+bool bluetoothRxDataAvailable()
+{
+#ifdef COMPILE_BT
+ return bluetoothSerial->available();
+#else // COMPILE_BT
+ return false;
+#endif // COMPILE_BT
+}
+
+// Write data to the Bluetooth device
+int bluetoothWrite(const uint8_t *buffer, int length)
+{
+#ifdef COMPILE_BT
+ // BLE write does not handle 0 length requests correctly
+ if (length > 0)
+ return bluetoothSerial->write(buffer, length);
+ else
+ return 0;
+#else // COMPILE_BT
+ return 0;
+#endif // COMPILE_BT
+}
+
+// Write data to the Bluetooth device
+int bluetoothWrite(uint8_t value)
+{
+#ifdef COMPILE_BT
+ return bluetoothSerial->write(value);
+#else // COMPILE_BT
+ return 0;
+#endif // COMPILE_BT
+}
+
+// Flush Bluetooth device
+void bluetoothFlush()
+{
+#ifdef COMPILE_BT
+ bluetoothSerial->flush();
+#else // COMPILE_BT
+ return;
+#endif // COMPILE_BT
+}
+
+// Get MAC, start radio
+// Tack device's MAC address to end of friendly broadcast name
+// This allows multiple units to be on at same time
+void bluetoothStart()
+{
+#ifdef COMPILE_BT
+ if (bluetoothState == BT_OFF)
+ {
+ char stateName[11] = {0};
+ if (systemState >= STATE_ROVER_NOT_STARTED && systemState <= STATE_ROVER_RTK_FIX)
+ strncpy(stateName, "Rover-", sizeof(stateName) - 1);
+ else if (systemState >= STATE_BASE_NOT_STARTED && systemState <= STATE_BASE_FIXED_TRANSMITTING)
+ strncpy(stateName, "Base-", sizeof(stateName) - 1);
+ else
+ {
+ strncpy(stateName, "Rover-", sizeof(stateName) - 1);
+ log_d("State out of range for Bluetooth Broadcast: %d", systemState);
+ }
+
+ char productName[50] = {0};
+ strncpy(productName, platformPrefix, sizeof(productName));
+
+ // BLE is limited to ~28 characters in the device name. Shorten platformPrefix if needed.
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ {
+ if (strcmp(productName, "Facet L-Band Direct") == 0)
+ {
+ strncpy(productName, "Facet L-Band", sizeof(productName));
+ }
+ }
+
+ snprintf(deviceName, sizeof(deviceName), "%s %s%02X%02X", productName, stateName, btMACAddress[4],
+ btMACAddress[5]);
+
+ if (strlen(deviceName) > 28)
+ {
+ if (ENABLE_DEVELOPER)
+ systemPrintf("Warning! The Bluetooth device name '%s' is %d characters long. It may not work in BLE mode.\r\n", deviceName,
+ strlen(deviceName));
+ }
+
+ // Select Bluetooth setup
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_OFF)
+ return;
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP)
+ bluetoothSerial = new BTClassicSerial();
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ bluetoothSerial = new BTLESerial();
+
+ // Not yet implemented
+ // if (pinBluetoothTaskHandle == nullptr)
+ // xTaskCreatePinnedToCore(
+ // pinBluetoothTask,
+ // "BluetoothStart", // Just for humans
+ // 2000, // Stack Size
+ // nullptr, // Task input parameter
+ // 0, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the
+ // lowest &pinBluetoothTaskHandle, // Task handle settings.bluetoothInterruptsCore); //
+ // Core where task should run, 0=core, 1=Arduino
+
+ // while (bluetoothPinned == false) // Wait for task to run once
+ // delay(1);
+
+ if (bluetoothSerial->begin(deviceName, false, settings.sppRxQueueSize, settings.sppTxQueueSize) ==
+ false) // localName, isMaster, rxBufferSize, txBufferSize
+ {
+ systemPrintln("An error occurred initializing Bluetooth");
+
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_bluetoothStatusLED, LOW);
+ return;
+ }
+
+ // Set PIN to 1234 so we can connect to older BT devices, but not require a PIN for modern device pairing
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/5
+ // https://github.com/espressif/esp-idf/issues/1541
+ //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
+
+ esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE; // Requires pin 1234 on old BT dongle, No prompt on new BT dongle
+ // esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_OUT; //Works but prompts for either pin (old) or 'Does this 6 pin
+ // appear on the device?' (new)
+
+ esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
+
+ esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
+ esp_bt_pin_code_t pin_code;
+ pin_code[0] = '1';
+ pin_code[1] = '2';
+ pin_code[2] = '3';
+ pin_code[3] = '4';
+ esp_bt_gap_set_pin(pin_type, 4, pin_code);
+ //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+ bluetoothSerial->register_callback(bluetoothCallback); // Controls BT Status LED on Surveyor
+ bluetoothSerial->setTimeout(250);
+
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP)
+ systemPrint("Bluetooth SPP broadcasting as: ");
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ systemPrint("Bluetooth Low-Energy broadcasting as: ");
+
+ systemPrintln(deviceName);
+
+ // Start task for controlling Bluetooth pair LED
+ if (productVariant == RTK_SURVEYOR)
+ {
+ ledcWrite(ledBTChannel, 255); // Turn on BT LED
+ btLEDTask.detach(); // Slow down the BT LED blinker task
+ btLEDTask.attach(btLEDTaskPace2Hz, updateBTled); // Rate in seconds, callback
+ }
+
+ bluetoothState = BT_NOTCONNECTED;
+ reportHeapNow(false);
+ }
+#endif // COMPILE_BT
+}
+
+// Assign Bluetooth interrupts to the core that started the task. See:
+// https://github.com/espressif/arduino-esp32/issues/3386
+void pinBluetoothTask(void *pvParameters)
+{
+#ifdef COMPILE_BT
+ if (bluetoothSerial->begin(deviceName, false, settings.sppRxQueueSize, settings.sppTxQueueSize) ==
+ false) // localName, isMaster, rxBufferSize,
+ {
+ systemPrintln("An error occurred initializing Bluetooth");
+
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_bluetoothStatusLED, LOW);
+ }
+
+ bluetoothPinned = true;
+
+ vTaskDelete(nullptr); // Delete task once it has run once
+#endif // COMPILE_BT
+}
+
+// This function stops BT so that it can be restarted later
+// It also releases as much system resources as possible so that WiFi/caster is more stable
+void bluetoothStop()
+{
+#ifdef COMPILE_BT
+ if (bluetoothState == BT_NOTCONNECTED || bluetoothState == BT_CONNECTED)
+ {
+ bluetoothSerial->register_callback(nullptr);
+ bluetoothSerial->flush(); // Complete any transfers
+ bluetoothSerial->disconnect(); // Drop any clients
+ bluetoothSerial->end(); // bluetoothSerial->end() will release significant RAM (~100k!) but a
+ // bluetoothSerial->start will crash.
+
+ log_d("Bluetooth turned off");
+
+ bluetoothState = BT_OFF;
+ reportHeapNow(false);
+ }
+#endif // COMPILE_BT
+ bluetoothIncomingRTCM = false;
+}
+
+// Test the bidirectional communication through UART2
+void bluetoothTest(bool runTest)
+{
+ // Verify the ESP UART2 can communicate TX/RX to ZED UART1
+ const char *bluetoothStatusText;
+
+ if (online.gnss == true)
+ {
+ if (runTest && (zedUartPassed == false) && (USE_I2C_GNSS))
+ {
+ tasksStopUART2(); // Stop absoring ZED serial via task
+
+ theGNSS.setVal32(UBLOX_CFG_UART1_BAUDRATE,
+ (115200 * 2)); // Defaults to 230400 to maximize message output support
+ serialGNSS.begin((115200 * 2)); // UART2 on pins 16/17 for SPP. The ZED-F9P will be configured to output
+ // NMEA over its UART1 at the same rate.
+
+ SFE_UBLOX_GNSS_SERIAL myGNSS;
+ if (myGNSS.begin(serialGNSS) == true) // begin() attempts 3 connections
+ {
+ zedUartPassed = true;
+ bluetoothStatusText = (settings.bluetoothRadioType == BLUETOOTH_RADIO_OFF) ? "Off" : "Online";
+ }
+ else
+ bluetoothStatusText = "Offline";
+
+ theGNSS.setVal32(UBLOX_CFG_UART1_BAUDRATE,
+ settings.dataPortBaud); // Defaults to 230400 to maximize message output support
+ serialGNSS.begin(settings.dataPortBaud); // UART2 on pins 16/17 for SPP. The ZED-F9P will be configured to
+ // output NMEA over its UART1 at the same rate.
+
+ tasksStartUART2(); // Return to normal operation
+ }
+ else
+ bluetoothStatusText = (settings.bluetoothRadioType == BLUETOOTH_RADIO_OFF) ? "Off" : "Online";
+ }
+ else
+ bluetoothStatusText = "GNSS Offline";
+
+ // Display Bluetooth MAC address and test results
+ char macAddress[5];
+ snprintf(macAddress, sizeof(macAddress), "%02X%02X", btMACAddress[4], btMACAddress[5]);
+ systemPrint("Bluetooth ");
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ systemPrint("Low Energy ");
+ systemPrint("(");
+ systemPrint(macAddress);
+ systemPrint("): ");
+ systemPrintln(bluetoothStatusText);
+}
diff --git a/Firmware/RTK_Surveyor/Buttons.ino b/Firmware/RTK_Surveyor/Buttons.ino
index e3c49fb16..3ce9ae014 100644
--- a/Firmware/RTK_Surveyor/Buttons.ino
+++ b/Firmware/RTK_Surveyor/Buttons.ino
@@ -1,250 +1,66 @@
-void checkButtons()
+// User has pressed the power button to turn on the system
+// Was it an accidental bump or do they really want to turn on?
+// Let's make sure they continue to press for 500ms
+void powerOnCheck()
{
- if (productVariant == RTK_SURVEYOR)
- {
- //Check rover switch and configure module accordingly
- //When switch is set to '1' = BASE, pin will be shorted to ground
- if (digitalRead(pin_baseSwitch) == LOW) //Switch is set to base mode
- {
- if (buttonPreviousState == BUTTON_ROVER)
- {
- buttonPreviousState = BUTTON_BASE;
- changeState(STATE_BASE_NOT_STARTED);
- }
- }
- else if (digitalRead(pin_baseSwitch) == HIGH) //Switch is set to Rover
- {
- if (buttonPreviousState == BUTTON_BASE)
- {
- buttonPreviousState = BUTTON_ROVER;
- changeState(STATE_ROVER_NOT_STARTED);
- }
- }
- }
- else if (productVariant == RTK_EXPRESS)
- {
- //Check to see if user is pressing both buttons simultaneously - show test screen
- if (digitalRead(pin_powerSenseAndControl) == LOW && digitalRead(pin_setupButton) == LOW)
- {
- delay(debounceDelay); //Debounce
- if (digitalRead(pin_powerSenseAndControl) == LOW && digitalRead(pin_setupButton) == LOW)
- {
- displayTest();
- setupButtonState = BUTTON_RELEASED;
- }
- }
-
- //Check power button
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- if (digitalRead(pin_powerSenseAndControl) == LOW && powerPressedStartTime == 0)
+ powerPressedStartTime = millis();
+ if (pin_powerSenseAndControl >= 0)
+ if (digitalRead(pin_powerSenseAndControl) == LOW)
+ delay(500);
+
+ if (FIRMWARE_VERSION_MAJOR == 99)
{
- //Debounce check
- delay(debounceDelay);
- if (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- powerPressedStartTime = millis();
- }
+ // Do not check button if this is a locally compiled developer firmware
}
- else if (digitalRead(pin_powerSenseAndControl) == LOW && powerPressedStartTime > 0)
+ else
{
- //Debounce check
- delay(debounceDelay);
- if (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- if ((millis() - powerPressedStartTime) > 2000)
- {
- powerDown(true);
- }
- }
+ if (pin_powerSenseAndControl >= 0)
+ if (digitalRead(pin_powerSenseAndControl) != LOW)
+ powerDown(false); // Power button tap. Returning to off state.
}
- else if (digitalRead(pin_powerSenseAndControl) == HIGH && powerPressedStartTime > 0)
- {
- //Debounce check
- delay(debounceDelay);
- if (digitalRead(pin_powerSenseAndControl) == HIGH)
- {
- Serial.print("Power button released after ms: ");
- Serial.println(millis() - powerPressedStartTime);
- powerPressedStartTime = 0; //Reset var to return to normal 'on' state
- setupByPowerButton = true; //Notify base/rover setup
- }
- }
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ powerPressedStartTime = 0; // Reset var to return to normal 'on' state
+}
- //Check setup button and configure module accordingly
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- if (digitalRead(pin_setupButton) == LOW || setupByPowerButton == true)
- {
- delay(debounceDelay); //Debounce
- if (digitalRead(pin_setupButton) == LOW || setupByPowerButton == true)
- {
- //User is pressing button
- if (setupButtonState == BUTTON_RELEASED)
- {
- setupButtonState = BUTTON_PRESSED;
+// If we have a power button tap, or if the display is not yet started (no I2C!)
+// then don't display a shutdown screen
+void powerDown(bool displayInfo)
+{
+ // Disable SD card use
+ endSD(false, false);
- setupByPowerButton = false;
+ // Prevent other tasks from logging, even if access to the microSD card was denied
+ online.logging = false;
- //Toggle between Rover and Base system states
- if (buttonPreviousState == BUTTON_ROVER)
- {
- buttonPreviousState = BUTTON_BASE;
- changeState(STATE_BASE_NOT_STARTED);
- }
- else if (buttonPreviousState == BUTTON_BASE)
- {
- buttonPreviousState = BUTTON_ROVER;
- changeState(STATE_ROVER_NOT_STARTED);
- }
- } //End button state check
- } //End debounce button check
- } //End first button check
- else if (digitalRead(pin_setupButton) == HIGH && setupButtonState == BUTTON_PRESSED)
- {
- //Return to unpressed state
- setupButtonState = BUTTON_RELEASED;
- }
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- }//end productVariant = Express
+ // 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();
- else if (productVariant == RTK_FACET)
- {
- //Check power button
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- if (digitalRead(pin_powerSenseAndControl) == LOW && powerPressedStartTime == 0)
- {
- //Debounce check
- delay(debounceDelay);
- if (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- powerPressedStartTime = millis();
- }
- }
- else if (digitalRead(pin_powerSenseAndControl) == LOW && powerPressedStartTime > 0)
+ if (displayInfo == true)
{
- //Debounce check
- delay(debounceDelay);
- if (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- if ((millis() - powerPressedStartTime) > 2000)
- {
- powerDown(true);
- }
- }
+ displayShutdown();
+ delay(2000);
}
- else if (digitalRead(pin_powerSenseAndControl) == HIGH && powerPressedStartTime > 0)
- {
- //Debounce check
- delay(debounceDelay);
- if (digitalRead(pin_powerSenseAndControl) == HIGH)
- {
- Serial.print("Power button released after ms: ");
- Serial.println(millis() - powerPressedStartTime);
- powerPressedStartTime = 0; //Reset var to return to normal 'on' state
- }
- }
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- //Check setup button and configure module accordingly
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- if (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- delay(debounceDelay); //Debounce
- if (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- //User is pressing button
- if (setupButtonState == BUTTON_RELEASED)
- {
- setupButtonState = BUTTON_PRESSED;
+ beginLEDs(); // Turn LEDs off
- //Toggle between Rover and Base system states
- if (buttonPreviousState == BUTTON_ROVER)
- {
- buttonPreviousState = BUTTON_BASE;
- changeState(STATE_BASE_NOT_STARTED);
- }
- else if (buttonPreviousState == BUTTON_BASE)
- {
- buttonPreviousState = BUTTON_ROVER;
- changeState(STATE_ROVER_NOT_STARTED);
- }
- } //End button state check
- } //End debounce button check
- } //End first button check
- else if (digitalRead(pin_powerSenseAndControl) == HIGH && setupButtonState == BUTTON_PRESSED)
+ if (pin_powerSenseAndControl >= 0)
{
- //Return to unpressed state
- setupButtonState = BUTTON_RELEASED;
+ pinMode(pin_powerSenseAndControl, OUTPUT);
+ digitalWrite(pin_powerSenseAndControl, LOW);
}
- //Check to see if user is pressing both buttons simultaneously - show test screen
- // if (digitalRead(pin_powerSenseAndControl) == LOW && digitalRead(pin_setupButton) == LOW)
- // {
- // delay(debounceDelay); //Debounce
- // if (digitalRead(pin_powerSenseAndControl) == LOW && digitalRead(pin_setupButton) == LOW)
- // {
- // displayTest();
- // setupButtonState = BUTTON_RELEASED;
- // buttonPreviousState = BUTTON_ROVER;
- // changeState(STATE_ROVER_NOT_STARTED);
- // }
- // }
- //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- }//end productVariant = Facet
-}
-
-//User has pressed the power button to turn on the system
-//Was it an accidental bump or do they really want to turn on?
-//Let's make sure they continue to press for two seconds
-void powerOnCheck()
-{
- powerPressedStartTime = millis();
- while (digitalRead(pin_powerSenseAndControl) == LOW)
- {
- delay(100); //Wait for user to stop pressing button.
-
- if (millis() - powerPressedStartTime > 500)
- break;
- }
-
- if (millis() - powerPressedStartTime < 500)
- powerDown(false); //Power button tap. Returning to off state.
-
- powerPressedStartTime = 0; //Reset var to return to normal 'on' state
-}
-
-//If we have a power button tap, or if the display is not yet started (no I2C!)
-//then don't display a shutdown screen
-void powerDown(bool displayInfo)
-{
- if (online.logging == true)
- {
- //Attempt to write to file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile()
- //Wait up to 1000ms
- if (xSemaphoreTake(xFATSemaphore, 1000 / portTICK_PERIOD_MS) == pdPASS)
+ if (pin_powerFastOff >= 0)
{
- //Close down file system
- ubxFile.sync();
- ubxFile.close();
- //xSemaphoreGive(xFATSemaphore); //Do not release semaphore
- } //End xFATSemaphore
-
- online.logging = false;
- }
-
- if (displayInfo == true)
- {
- displayShutdown();
- delay(2000);
- }
-
- pinMode(pin_powerSenseAndControl, OUTPUT);
- digitalWrite(pin_powerSenseAndControl, LOW);
+ pinMode(pin_powerFastOff, OUTPUT);
+ digitalWrite(pin_powerFastOff, LOW);
+ }
- pinMode(pin_powerFastOff, OUTPUT);
- digitalWrite(pin_powerFastOff, LOW);
+ if ((productVariant == RTK_FACET) || (productVariant == RTK_FACET_LBAND) ||
+ (productVariant == RTK_FACET_LBAND_DIRECT) || (productVariant == REFERENCE_STATION))
+ digitalWrite(pin_peripheralPowerControl, LOW);
- while (1)
- delay(1);
+ while (1)
+ delay(1);
}
diff --git a/Firmware/RTK_Surveyor/Developer.ino b/Firmware/RTK_Surveyor/Developer.ino
new file mode 100644
index 000000000..91c8387d4
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Developer.ino
@@ -0,0 +1,145 @@
+/*
+pvtServer.ino
+
+ The code in this module is only compiled when features are disabled in developer
+ mode (ENABLE_DEVELOPER defined).
+*/
+
+#ifndef COMPILE_ETHERNET
+
+//----------------------------------------
+// Ethernet
+//----------------------------------------
+
+void menuEthernet() {systemPrintln("Ethernet not compiled");}
+void ethernetBegin() {}
+IPAddress ethernetGetIpAddress() {return IPAddress((uint32_t)0);}
+void ethernetUpdate() {}
+void ethernetVerifyTables() {}
+
+void ethernetPvtClientSendData(uint8_t *data, uint16_t length) {}
+void ethernetPvtClientUpdate() {}
+
+void ethernetWebServerStartESP32W5500() {}
+void ethernetWebServerStopESP32W5500() {}
+
+//----------------------------------------
+// NTP: Network Time Protocol
+//----------------------------------------
+
+void menuNTP() {systemPrint("NTP not compiled");}
+void ntpServerBegin() {}
+void ntpServerUpdate() {}
+void ntpValidateTables() {}
+void ntpServerStop() {}
+
+#endif // COMPILE_ETHERNET
+
+#if !COMPILE_NETWORK
+
+//----------------------------------------
+// Network layer
+//----------------------------------------
+
+void menuNetwork() {systemPrint("Network not compiled");}
+void networkUpdate() {}
+void networkVerifyTables() {}
+void networkStop(uint8_t networkType) {}
+
+//----------------------------------------
+// NTRIP client
+//----------------------------------------
+
+void ntripClientPrintStatus() {systemPrint("NTRIP Client not compiled");}
+void ntripClientStop(bool clientAllocated) {online.ntripClient = false;}
+void ntripClientUpdate() {}
+void ntripClientValidateTables() {}
+
+//----------------------------------------
+// NTRIP server
+//----------------------------------------
+
+bool ntripServerIsCasting(int serverIndex) {return false;}
+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 ntripServerUpdate() {}
+void ntripServerValidateTables() {}
+
+//----------------------------------------
+// OTA client
+//----------------------------------------
+
+void otaVerifyTables() {}
+
+//----------------------------------------
+// PVT client
+//----------------------------------------
+
+int32_t pvtClientSendData(uint16_t dataHead) {return 0;}
+void pvtClientUpdate() {}
+void pvtClientValidateTables() {}
+void pvtClientZeroTail() {}
+void discardPvtClientBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {}
+
+//----------------------------------------
+// PVT UDP server
+//----------------------------------------
+
+int32_t pvtUdpServerSendData(uint16_t dataHead) {return 0;}
+void pvtUdpServerStop() {}
+void pvtUdpServerUpdate() {}
+void pvtUdpServerZeroTail() {}
+void discardPvtUdpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {}
+
+#endif // COMPILE_NETWORK
+
+//----------------------------------------
+// Web Server
+//----------------------------------------
+
+#ifndef COMPILE_AP
+
+bool startWebServer(bool startWiFi = true, int httpPort = 80)
+{
+ systemPrintln("AP not compiled");
+ return false;
+}
+void stopWebServer() {}
+bool parseIncomingSettings() {return false;}
+
+#endif // COMPILE_AP
+#ifndef COMPILE_WIFI
+
+//----------------------------------------
+// PVT server
+//----------------------------------------
+
+int32_t pvtServerSendData(uint16_t dataHead) {return 0;}
+void pvtServerStop() {}
+void pvtServerUpdate() {}
+void pvtServerZeroTail() {}
+void pvtServerValidateTables() {}
+void discardPvtServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail) {}
+
+//----------------------------------------
+// WiFi
+//----------------------------------------
+
+void menuWiFi() {systemPrintln("WiFi not compiled");};
+bool wifiConnect(unsigned long timeout) {return false;}
+IPAddress wifiGetGatewayIpAddress() {return IPAddress((uint32_t)0);}
+IPAddress wifiGetIpAddress() {return IPAddress((uint32_t)0);}
+int wifiGetRssi() {return -999;}
+bool wifiInConfigMode() {return false;}
+bool wifiIsConnected() {return false;}
+bool wifiIsNeeded() {return false;}
+int wifiNetworkCount() {return 0;}
+void wifiPrintNetworkInfo() {}
+void wifiStart() {}
+void wifiStop() {}
+void wifiUpdate() {}
+void wifiShutdown() {}
+#define WIFI_STOP() {}
+
+#endif // COMPILE_WIFI
diff --git a/Firmware/RTK_Surveyor/Display.ino b/Firmware/RTK_Surveyor/Display.ino
index 3826de29f..ded614fb7 100644
--- a/Firmware/RTK_Surveyor/Display.ino
+++ b/Firmware/RTK_Surveyor/Display.ino
@@ -1,1423 +1,3414 @@
-//Given the system state, display the appropriate information
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+// A bitfield is used to flag which icon needs to be illuminated
+// systemState will dictate most of the icons needed
+
+// The radio area (top left corner of display) has three spots for icons
+// Left/Center/Right
+// Left Radio spot
+#define ICON_WIFI_SYMBOL_0_LEFT (1 << 0) // 0, 0
+#define ICON_WIFI_SYMBOL_1_LEFT (1 << 1) // 0, 0
+#define ICON_WIFI_SYMBOL_2_LEFT (1 << 2) // 0, 0
+#define ICON_WIFI_SYMBOL_3_LEFT (1 << 3) // 0, 0
+#define ICON_BT_SYMBOL_LEFT (1 << 4) // 0, 0
+#define ICON_MAC_ADDRESS (1 << 5) // 0, 3
+#define ICON_ESPNOW_SYMBOL_0_LEFT (1 << 6) // 0, 0
+#define ICON_ESPNOW_SYMBOL_1_LEFT (1 << 7) // 0, 0
+#define ICON_ESPNOW_SYMBOL_2_LEFT (1 << 8) // 0, 0
+#define ICON_ESPNOW_SYMBOL_3_LEFT (1 << 9) // 0, 0
+#define ICON_DOWN_ARROW_LEFT (1 << 10) // 0, 0
+#define ICON_UP_ARROW_LEFT (1 << 11) // 0, 0
+#define ICON_BLANK_LEFT (1 << 12) // 0, 0
+
+// Center Radio spot
+#define ICON_MAC_ADDRESS_2DIGIT (1 << 13) // 13, 3
+#define ICON_BT_SYMBOL_CENTER (1 << 14) // 10, 0
+#define ICON_DOWN_ARROW_CENTER (1 << 15) // 0, 0
+#define ICON_UP_ARROW_CENTER (1 << 16) // 0, 0
+
+// Right Radio Spot
+#define ICON_WIFI_SYMBOL_0_RIGHT (1 << 17) // center, 0
+#define ICON_WIFI_SYMBOL_1_RIGHT (1 << 18) // center, 0
+#define ICON_WIFI_SYMBOL_2_RIGHT (1 << 19) // center, 0
+#define ICON_WIFI_SYMBOL_3_RIGHT (1 << 20) // center, 0
+#define ICON_BASE_TEMPORARY (1 << 21) // center, 0
+#define ICON_BASE_FIXED (1 << 22) // center, 0
+#define ICON_ROVER_FUSION (1 << 23) // center, 2
+#define ICON_ROVER_FUSION_EMPTY (1 << 24) // center, 2
+#define ICON_DYNAMIC_MODEL (1 << 25) // 27, 0
+#define ICON_DOWN_ARROW_RIGHT (1 << 26) // center, 0
+#define ICON_UP_ARROW_RIGHT (1 << 27) // center, 0
+#define ICON_BLANK_RIGHT (1 << 28) // center, 0
+
+// Left + Center Radio spot
+#define ICON_IP_ADDRESS (1 << 29)
+
+// Right top
+#define ICON_BATTERY (1 << 0) // 45, 0
+
+// Left center
+#define ICON_CROSS_HAIR (1 << 1) // 0, 18
+#define ICON_CROSS_HAIR_DUAL (1 << 2) // 0, 18
+
+// Right center
+#define ICON_HORIZONTAL_ACCURACY (1 << 3) // 16, 20
+
+// Left bottom
+#define ICON_SIV_ANTENNA (1 << 4) // 2, 35
+#define ICON_SIV_ANTENNA_LBAND (1 << 5) // 2, 35
+
+// Right bottom
+#define ICON_LOGGING (1 << 6) // right, bottom
+
+// Left center
+#define ICON_CLOCK (1 << 7)
+#define ICON_CLOCK_ACCURACY (1 << 8)
+
+// Right top
+#define ICON_ETHERNET (1 << 9)
+
+// Right bottom
+#define ICON_LOGGING_NTP (1 << 10)
+
+// Left bottom
+#define ICON_ANTENNA_SHORT (1 << 11)
+#define ICON_ANTENNA_OPEN (1 << 12)
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+static QwiicMicroOLED oled;
+static uint32_t blinking_icons;
+static uint32_t icons;
+static uint32_t iconsRadio;
+
+unsigned long ssidDisplayTimer = 0;
+bool ssidDisplayFirstHalf = false;
+
+// Fonts
+#include
+#include
+#include
+
+// Icons
+#include "icons.h"
+
+//----------------------------------------
+// Routines
+//----------------------------------------
+
+void beginDisplay()
+{
+ blinking_icons = 0;
+
+ // At this point we have not identified the RTK platform
+ // If it's surveyor, there won't be a display and we have a 100ms delay
+ // If it's other platforms, we will try 3 times
+ int maxTries = 3;
+ for (int x = 0; x < maxTries; x++)
+ {
+ if (oled.begin() == true)
+ {
+ online.display = true;
+
+ systemPrintln("Display started");
+
+ oled.erase();
+ return;
+ }
+
+ delay(50); // Give display time to startup before attempting again
+ }
+
+ systemPrintln("Display not detected");
+}
+
+// Display the SparkFun logo
+void displaySfeFlame()
+{
+ if (online.display == true)
+ {
+ oled.erase();
+ displayBitmap(0, 0, logoSparkFun_Width, logoSparkFun_Height, logoSparkFun);
+ oled.display();
+ splashStart = millis();
+ }
+}
+
+// Avoid code repetition
+void displayBatteryVsEthernet()
+{
+ if (HAS_BATTERY)
+ icons |= ICON_BATTERY; // Top right
+ else // if (HAS_ETHERNET)
+ {
+ if (online.ethernetStatus == ETH_NOT_STARTED)
+ blinking_icons &= ~ICON_ETHERNET; // If Ethernet has not stated because not needed, don't display the icon
+ else if (online.ethernetStatus == ETH_CONNECTED)
+ blinking_icons |= ICON_ETHERNET; // Don't blink if link is up
+ else
+ blinking_icons ^= ICON_ETHERNET;
+ icons |= (blinking_icons & ICON_ETHERNET); // Top Right
+ }
+}
+void displaySivVsOpenShort()
+{
+ if (!HAS_ANTENNA_SHORT_OPEN)
+ icons |= paintSIV();
+ else
+ {
+ if (aStatus == SFE_UBLOX_ANTENNA_STATUS_SHORT)
+ {
+ blinking_icons ^= ICON_ANTENNA_SHORT;
+ icons |= (blinking_icons & ICON_ANTENNA_SHORT);
+ }
+ else if (aStatus == SFE_UBLOX_ANTENNA_STATUS_OPEN)
+ {
+ blinking_icons ^= ICON_ANTENNA_OPEN;
+ icons |= (blinking_icons & ICON_ANTENNA_OPEN);
+ }
+ else
+ {
+ blinking_icons &= ~ICON_ANTENNA_SHORT;
+ blinking_icons &= ~ICON_ANTENNA_OPEN;
+ icons |= paintSIV();
+ }
+ }
+}
+
+// Given the system state, display the appropriate information
void updateDisplay()
{
- //Update the display if connected
- if (online.display == true)
- {
- if (millis() - lastDisplayUpdate > 500) //Update display at 2Hz
- {
- lastDisplayUpdate = millis();
-
- oled.clear(PAGE); // Clear the display's internal buffer
-
- switch (systemState)
- {
- case (STATE_ROVER_NOT_STARTED):
- //Do nothing. Static display shown during state change.
- break;
- case (STATE_ROVER_NO_FIX):
- paintRoverNoFix();
- break;
- case (STATE_ROVER_FIX):
- paintRoverFix();
- break;
- case (STATE_ROVER_RTK_FLOAT):
- paintRoverRTKFloat();
- break;
- case (STATE_ROVER_RTK_FIX):
- paintRoverRTKFix();
- break;
- case (STATE_BASE_NOT_STARTED):
- //Do nothing. Static display shown during state change.
- break;
- case (STATE_BASE_TEMP_SETTLE):
- paintBaseTempSettle();
- break;
- case (STATE_BASE_TEMP_SURVEY_STARTED):
- paintBaseTempSurveyStarted();
- break;
- case (STATE_BASE_TEMP_TRANSMITTING):
- paintBaseTempTransmitting();
- break;
- case (STATE_BASE_TEMP_WIFI_STARTED):
- paintBaseTempWiFiStarted();
- break;
- case (STATE_BASE_TEMP_WIFI_CONNECTED):
- paintBaseTempWiFiConnected();
- break;
- case (STATE_BASE_TEMP_CASTER_STARTED):
- paintBaseTempCasterStarted();
- break;
- case (STATE_BASE_TEMP_CASTER_CONNECTED):
- paintBaseTempCasterConnected();
- break;
- case (STATE_BASE_FIXED_NOT_STARTED):
- paintBaseFixedNotStarted();
- break;
- case (STATE_BASE_FIXED_TRANSMITTING):
- paintBaseFixedTransmitting();
- break;
- case (STATE_BASE_FIXED_WIFI_STARTED):
- paintBaseFixedWiFiStarted();
- break;
- case (STATE_BASE_FIXED_WIFI_CONNECTED):
- paintBaseFixedWiFiConnected();
- break;
- case (STATE_BASE_FIXED_CASTER_STARTED):
- paintBaseFixedCasterStarted();
- break;
- case (STATE_BASE_FIXED_CASTER_CONNECTED):
- paintBaseFixedCasterConnected();
- break;
- default:
- displayError((char*)"Display");
- break;
- }
-
- oled.display(); //Push internal buffer to display
- }
- }
+ // Update the display if connected
+ if (online.display == true)
+ {
+ if (millis() - lastDisplayUpdate > 500 || forceDisplayUpdate == true) // Update display at 2Hz
+ {
+ lastDisplayUpdate = millis();
+ forceDisplayUpdate = false;
+
+ oled.reset(false); // Incase of previous corruption, force re-alignment of CGRAM. Do not init buffers as it
+ // takes time and causes screen to blink.
+
+ oled.erase();
+
+ icons = 0;
+ iconsRadio = 0;
+ switch (systemState)
+ {
+
+ /*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 0| ******* ** ** *****************
+ 1| * * ** ** * *
+ 2| * ***** * ** ****** * *** *** *** *
+ 3|* * * * ** * * * *** *** *** ***
+ 4| * *** * ** * * **** * * * *** *** *** *
+ 5| * * ** ** ** * * **** * * * *** *** *** *
+ 6| * ****** * * * * * *** *** *** *
+ 7| *** **** * * * * * *** *** *** *
+ 8| * ** * * * * * *** *** *** ***
+ 9| * * * * * *** *** *** *
+ 10| * * * *
+ 11| ****** *****************
+ 12|
+ 13|
+ 14|
+ 15|
+ 16|
+ 17|
+ 18| *
+ 19| *
+ 20| *******
+ 21| * * * *** *** ***
+ 22| * * * * * * * * *
+ 23| * * * * * * * * *
+ 24| * * * ** * * * * * *
+ 25|******* ******* ** * * *
+ 26| * * * * * * * * *
+ 27| * * * * * * * * *
+ 28| * * * * * * * * *
+ 29| * * * ** * * ** * * * *
+ 30| ******* ** *** ** *** ***
+ 31| *
+ 32| *
+ 33|
+ 34|
+ 35|
+ 36| ** *******
+ 37| * * *** *** * **
+ 38| * * * * * * * * **
+ 39| * * * * * * * * *
+ 40| * * ** * * * * * ***** *
+ 41| * * ** * * * *
+ 42| * * * * * * * ***** *
+ 43| ** * * * * * * *
+ 44| **** * * * * * * ***** *
+ 45| ** **** ** * * * * * *
+ 46| ** ** *** *** * *
+ 47| ****** *********
+ */
+
+ case (STATE_ROVER_NOT_STARTED):
+ icons = ICON_CROSS_HAIR // Center left
+ | ICON_HORIZONTAL_ACCURACY // Center right
+ | ICON_LOGGING; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+ case (STATE_ROVER_NO_FIX):
+ icons = ICON_CROSS_HAIR // Center left
+ | ICON_HORIZONTAL_ACCURACY // Center right
+ | ICON_LOGGING; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+ case (STATE_ROVER_FIX):
+ icons = ICON_CROSS_HAIR // Center left
+ | ICON_HORIZONTAL_ACCURACY // Center right
+ | ICON_LOGGING; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+ case (STATE_ROVER_RTK_FLOAT):
+ blinking_icons ^= ICON_CROSS_HAIR_DUAL;
+ icons = (blinking_icons & ICON_CROSS_HAIR_DUAL) // Center left
+ | ICON_HORIZONTAL_ACCURACY // Center right
+ | ICON_LOGGING; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+ case (STATE_ROVER_RTK_FIX):
+ icons = ICON_CROSS_HAIR_DUAL // Center left
+ | ICON_HORIZONTAL_ACCURACY // Center right
+ | ICON_LOGGING; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+
+ case (STATE_BASE_NOT_STARTED):
+ // Do nothing. Static display shown during state change.
+ break;
+
+ // Start of base / survey in / NTRIP mode
+ // Screen is displayed while we are waiting for horz accuracy to drop to appropriate level
+ // Blink crosshair icon until we have we have horz accuracy < user defined level
+ case (STATE_BASE_TEMP_SETTLE):
+ blinking_icons ^= ICON_CROSS_HAIR;
+ icons = (blinking_icons & ICON_CROSS_HAIR) // Center left
+ | ICON_HORIZONTAL_ACCURACY // Center right
+ | ICON_LOGGING; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+ case (STATE_BASE_TEMP_SURVEY_STARTED):
+ icons = ICON_LOGGING; // Bottom right
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ paintBaseTempSurveyStarted();
+ break;
+ case (STATE_BASE_TEMP_TRANSMITTING):
+ icons = ICON_LOGGING; // Bottom right
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ paintRTCM();
+ break;
+ case (STATE_BASE_FIXED_NOT_STARTED):
+ icons = 0; // Top right
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ break;
+ case (STATE_BASE_FIXED_TRANSMITTING):
+ icons = ICON_LOGGING; // Bottom right
+ displayBatteryVsEthernet(); // Top right
+ iconsRadio = setRadioIcons(); // Top left
+ paintRTCM();
+ break;
+
+ case (STATE_NTPSERVER_NOT_STARTED):
+ case (STATE_NTPSERVER_NO_SYNC):
+ blinking_icons ^= ICON_CLOCK;
+ icons = (blinking_icons & ICON_CLOCK) // Center left
+ | ICON_CLOCK_ACCURACY; // Center right
+ displaySivVsOpenShort(); // Bottom left
+ if (online.ethernetStatus == ETH_CONNECTED)
+ blinking_icons |= ICON_ETHERNET; // Don't blink if link is up
+ else
+ blinking_icons ^= ICON_ETHERNET;
+ icons |= (blinking_icons & ICON_ETHERNET); // Top Right
+ iconsRadio = ICON_IP_ADDRESS; // Top left
+ break;
+
+ case (STATE_NTPSERVER_SYNC):
+ icons = ICON_CLOCK // Center left
+ | ICON_CLOCK_ACCURACY // Center right
+ | ICON_LOGGING_NTP; // Bottom right
+ displaySivVsOpenShort(); // Bottom left
+ if (online.ethernetStatus == ETH_CONNECTED)
+ blinking_icons |= ICON_ETHERNET; // Don't blink if link is up
+ else
+ blinking_icons ^= ICON_ETHERNET;
+ icons |= (blinking_icons & ICON_ETHERNET); // Top Right
+ iconsRadio = ICON_IP_ADDRESS; // Top left
+ 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_BUBBLE_LEVEL):
+ paintBubbleLevel();
+ break;
+ case (STATE_PROFILE):
+ paintProfile(displayProfile);
+ break;
+ case (STATE_MARK_EVENT):
+ // Do nothing. Static display shown during state change.
+ break;
+ case (STATE_DISPLAY_SETUP):
+ paintDisplaySetup();
+ break;
+ case (STATE_WIFI_CONFIG_NOT_STARTED):
+ displayWiFiConfigNotStarted(); // Display 'WiFi Config'
+ break;
+ case (STATE_WIFI_CONFIG):
+ iconsRadio = setWiFiIcon(); // Blink WiFi in center
+ displayWiFiConfig(); // Display SSID and IP
+ break;
+ case (STATE_TEST):
+ paintSystemTest();
+ break;
+ case (STATE_TESTING):
+ paintSystemTest();
+ break;
+
+ case (STATE_KEYS_STARTED):
+ paintRTCWait();
+ break;
+ case (STATE_KEYS_NEEDED):
+ // Do nothing. Quick, fall through state.
+ break;
+ case (STATE_KEYS_WIFI_STARTED):
+ iconsRadio = setWiFiIcon(); // Blink WiFi in center
+ paintGettingKeys();
+ break;
+ case (STATE_KEYS_WIFI_CONNECTED):
+ iconsRadio = setWiFiIcon(); // Blink WiFi in center
+ paintGettingKeys();
+ break;
+ case (STATE_KEYS_WIFI_TIMEOUT):
+ // Do nothing. Quick, fall through state.
+ break;
+ case (STATE_KEYS_EXPIRED):
+ // Do nothing. Quick, fall through state.
+ break;
+ case (STATE_KEYS_DAYS_REMAINING):
+ // Do nothing. Quick, fall through state.
+ break;
+ case (STATE_KEYS_LBAND_CONFIGURE):
+ paintLBandConfigure();
+ break;
+ case (STATE_KEYS_LBAND_ENCRYPTED):
+ // Do nothing. Quick, fall through state.
+ break;
+ case (STATE_KEYS_PROVISION_WIFI_STARTED):
+ iconsRadio = setWiFiIcon(); // Blink WiFi in center
+ paintGettingKeys();
+ break;
+ case (STATE_KEYS_PROVISION_WIFI_CONNECTED):
+ iconsRadio = setWiFiIcon(); // Blink WiFi in center
+ paintGettingKeys();
+ break;
+
+ case (STATE_ESPNOW_PAIRING_NOT_STARTED):
+ paintEspNowPairing();
+ break;
+ case (STATE_ESPNOW_PAIRING):
+ paintEspNowPairing();
+ break;
+
+ case (STATE_SHUTDOWN):
+ displayShutdown();
+ break;
+ default:
+ systemPrintf("Unknown display: %d\r\n", systemState);
+ displayError("Display");
+ break;
+ }
+
+ // Top left corner - Radio icon indicators take three spots (left/center/right)
+ // Allowed icon combinations:
+ // Bluetooth + Rover/Base
+ // WiFi + Bluetooth + Rover/Base
+ // ESP-Now + Bluetooth + Rover/Base
+ // ESP-Now + Bluetooth + WiFi
+ // See setRadioIcons() for the icon selection logic
+
+ // Left spot
+ if (iconsRadio & ICON_MAC_ADDRESS)
+ {
+ char macAddress[5];
+ const uint8_t *rtkMacAddress = getMacAddress();
+
+ // Print four characters of MAC
+ snprintf(macAddress, sizeof(macAddress), "%02X%02X", rtkMacAddress[4], rtkMacAddress[5]);
+ oled.setFont(QW_FONT_5X7); // Set font to smallest
+ oled.setCursor(0, 3);
+ oled.print(macAddress);
+ }
+ else if (iconsRadio & ICON_BT_SYMBOL_LEFT)
+ displayBitmap(1, 0, BT_Symbol_Width, BT_Symbol_Height, BT_Symbol);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_0_LEFT)
+ displayBitmap(0, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_0);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_1_LEFT)
+ displayBitmap(0, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_1);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_2_LEFT)
+ displayBitmap(0, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_2);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_3_LEFT)
+ displayBitmap(0, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_3);
+ else if (iconsRadio & ICON_ESPNOW_SYMBOL_0_LEFT)
+ displayBitmap(0, 0, ESPNOW_Symbol_Width, ESPNOW_Symbol_Height, ESPNOW_Symbol_0);
+ else if (iconsRadio & ICON_ESPNOW_SYMBOL_1_LEFT)
+ displayBitmap(0, 0, ESPNOW_Symbol_Width, ESPNOW_Symbol_Height, ESPNOW_Symbol_1);
+ else if (iconsRadio & ICON_ESPNOW_SYMBOL_2_LEFT)
+ displayBitmap(0, 0, ESPNOW_Symbol_Width, ESPNOW_Symbol_Height, ESPNOW_Symbol_2);
+ else if (iconsRadio & ICON_ESPNOW_SYMBOL_3_LEFT)
+ displayBitmap(0, 0, ESPNOW_Symbol_Width, ESPNOW_Symbol_Height, ESPNOW_Symbol_3);
+ else if (iconsRadio & ICON_DOWN_ARROW_LEFT)
+ displayBitmap(1, 0, DownloadArrow_Width, DownloadArrow_Height, DownloadArrow);
+ else if (iconsRadio & ICON_UP_ARROW_LEFT)
+ displayBitmap(1, 0, UploadArrow_Width, UploadArrow_Height, UploadArrow);
+ else if (iconsRadio & ICON_BLANK_LEFT)
+ {
+ ;
+ }
+
+ // Center radio spots
+ if (iconsRadio & ICON_BT_SYMBOL_CENTER)
+ {
+ // Moved to center to give space for ESP NOW icon on far left
+ displayBitmap(16, 0, BT_Symbol_Width, BT_Symbol_Height, BT_Symbol);
+ }
+ else if (iconsRadio & ICON_MAC_ADDRESS_2DIGIT)
+ {
+ char macAddress[5];
+ const uint8_t *rtkMacAddress = getMacAddress();
+
+ // Print only last two digits of MAC
+ snprintf(macAddress, sizeof(macAddress), "%02X", rtkMacAddress[5]);
+ oled.setFont(QW_FONT_5X7); // Set font to smallest
+ oled.setCursor(14, 3);
+ oled.print(macAddress);
+ }
+ else if (iconsRadio & ICON_DOWN_ARROW_CENTER)
+ displayBitmap(16, 0, DownloadArrow_Width, DownloadArrow_Height, DownloadArrow);
+ else if (iconsRadio & ICON_UP_ARROW_CENTER)
+ displayBitmap(16, 0, UploadArrow_Width, UploadArrow_Height, UploadArrow);
+
+ // Radio third spot
+ if (iconsRadio & ICON_WIFI_SYMBOL_0_RIGHT)
+ displayBitmap(28, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_0);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_1_RIGHT)
+ displayBitmap(28, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_1);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_2_RIGHT)
+ displayBitmap(28, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_2);
+ else if (iconsRadio & ICON_WIFI_SYMBOL_3_RIGHT)
+ displayBitmap(28, 0, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol_3);
+ else if ((iconsRadio & ICON_DYNAMIC_MODEL) && (online.gnss == true))
+ paintDynamicModel();
+ else if (iconsRadio & ICON_BASE_TEMPORARY)
+ displayBitmap(28, 0, BaseTemporary_Width, BaseTemporary_Height, BaseTemporary);
+ else if (iconsRadio & ICON_BASE_FIXED)
+ displayBitmap(28, 0, BaseFixed_Width, BaseFixed_Height, BaseFixed); // true - blend with other pixels
+ else if (iconsRadio & ICON_DOWN_ARROW_RIGHT)
+ displayBitmap(31, 0, DownloadArrow_Width, DownloadArrow_Height, DownloadArrow);
+ else if (iconsRadio & ICON_UP_ARROW_RIGHT)
+ displayBitmap(31, 0, UploadArrow_Width, UploadArrow_Height, UploadArrow);
+ else if (iconsRadio & ICON_BLANK_RIGHT)
+ {
+ ;
+ }
+
+ // Left + center spot
+ if (iconsRadio & ICON_IP_ADDRESS)
+ paintIPAddress();
+
+ // Top right corner
+ if (icons & ICON_BATTERY)
+ paintBatteryLevel();
+ else if (icons & ICON_ETHERNET)
+ displayBitmap(45, 0, Ethernet_Icon_Width, Ethernet_Icon_Height, Ethernet_Icon);
+
+ // Center left
+ if (icons & ICON_CROSS_HAIR)
+ displayBitmap(0, 18, CrossHair_Width, CrossHair_Height, CrossHair);
+ else if (icons & ICON_CROSS_HAIR_DUAL)
+ displayBitmap(0, 18, CrossHairDual_Width, CrossHairDual_Height, CrossHairDual);
+ else if (icons & ICON_CLOCK)
+ paintClock();
+
+ // Center right
+ if (icons & ICON_HORIZONTAL_ACCURACY)
+ paintHorizontalAccuracy();
+ else if (icons & ICON_CLOCK_ACCURACY)
+ paintClockAccuracy();
+
+ // Bottom left corner
+ if (icons & ICON_SIV_ANTENNA)
+ displayBitmap(2, 35, SIV_Antenna_Width, SIV_Antenna_Height, SIV_Antenna);
+ else if (icons & ICON_SIV_ANTENNA_LBAND)
+ displayBitmap(2, 35, SIV_Antenna_LBand_Width, SIV_Antenna_LBand_Height, SIV_Antenna_LBand);
+ else if (icons & ICON_ANTENNA_SHORT)
+ displayBitmap(2, 35, Antenna_Short_Width, Antenna_Short_Height, Antenna_Short);
+ else if (icons & ICON_ANTENNA_OPEN)
+ displayBitmap(2, 35, Antenna_Open_Width, Antenna_Open_Height, Antenna_Open);
+
+ // Bottom right corner
+ if (icons & ICON_LOGGING)
+ paintLogging();
+ else if (icons & ICON_LOGGING_NTP)
+ paintLoggingNTP(true); // NTP, no pulse
+
+ oled.display(); // Push internal buffer to display
+ }
+ } // End display online
}
void displaySplash()
{
- if (online.display == true)
- {
- //Init and display splash
- oled.begin(); // Initialize the OLED
- oled.clear(PAGE); // Clear the display's internal memory
+ if (online.display == true)
+ {
+ // Display SparkFun Logo for at least 1/10 of a second
+ unsigned long minSplashFor = 100;
+ if (productVariant == REFERENCE_STATION) // Reference station starts up very quickly. Keep splash on for longer
+ minSplashFor = 1000;
+ while ((millis() - splashStart) < minSplashFor)
+ delay(10);
- oled.setCursor(10, 2); //x, y
- oled.setFontType(0); //Set font to smallest
- oled.print(F("SparkFun"));
+ oled.erase();
- oled.setCursor(21, 13);
- oled.setFontType(1);
- oled.print(F("RTK"));
+ int yPos = 0;
+ int fontHeight = 8;
- int textX;
- int textY;
- int textKerning;
+ printTextCenter("SparkFun", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- if (productVariant == RTK_SURVEYOR)
- {
- textX = 2;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Surveyor", textX, textY, textKerning);
+ yPos = yPos + fontHeight + 2;
+ printTextCenter("RTK", yPos, QW_FONT_8X16, 1, false);
+
+ yPos = yPos + fontHeight + 5;
+ printTextCenter(productDisplayNames[productVariant], yPos, QW_FONT_8X16, 1, false);
+
+ yPos = yPos + fontHeight + 7;
+ char unitFirmware[50];
+ getFirmwareVersion(unitFirmware, sizeof(unitFirmware), false);
+ printTextCenter(unitFirmware, yPos, QW_FONT_5X7, 1, false);
+
+ oled.display();
+
+ // Start the timer for the splash screen display
+ splashStart = millis();
}
- else if (productVariant == RTK_EXPRESS)
+}
+
+void displayShutdown()
+{
+ displayMessage("Shutting Down...", 0);
+}
+
+// Displays a small error message then hard freeze
+// Text wraps and is small but legible
+void displayError(const char *errorMessage)
+{
+ if (online.display == true)
{
- textX = 3;
- textY = 25;
- textKerning = 9;
- oled.setFontType(1);
- printTextwithKerning((char*)"Express", textX, textY, textKerning);
+ oled.erase(); // Clear the display's internal buffer
+
+ oled.setCursor(0, 0); // x, y
+ oled.setFont(QW_FONT_5X7); // Set font to smallest
+ oled.print("Error:");
+
+ oled.setCursor(2, 10);
+ // oled.setFont(QW_FONT_8X16);
+ oled.print(errorMessage);
+
+ oled.display(); // Push internal buffer to display
+
+ while (1)
+ delay(10); // Hard freeze
}
- else if (productVariant == RTK_FACET)
+}
+
+/*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 0| *****************
+ 1| * *
+ 2| * *** *** *** *
+ 3| * *** *** *** ***
+ 4| * *** *** *** *
+ 5| * *** *** *** *
+ 6| * *** *** *** *
+ 7| * *** *** *** *
+ 8| * *** *** *** ***
+ 9| * *** *** *** *
+ 10| * *
+ 11| *****************
+*/
+
+// Print the classic battery icon with levels
+void paintBatteryLevel()
+{
+ if (online.display == true)
{
- textX = 11;
- textY = 25;
- textKerning = 9;
- oled.setFontType(1);
- printTextwithKerning((char*)"Facet", textX, textY, textKerning);
+ // Current battery charge level
+ if (battLevel < 25)
+ displayBitmap(45, 0, Battery_0_Width, Battery_0_Height, Battery_0);
+ else if (battLevel < 50)
+ displayBitmap(45, 0, Battery_1_Width, Battery_1_Height, Battery_1);
+ else if (battLevel < 75)
+ displayBitmap(45, 0, Battery_2_Width, Battery_2_Height, Battery_2);
+ else // batt level > 75
+ displayBitmap(45, 0, Battery_3_Width, Battery_3_Height, Battery_3);
}
-
- oled.setCursor(20, 41);
- oled.setFontType(0); //Set font to smallest
- oled.printf("v%d.%d", FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR);
- oled.display();
- }
}
-void displayShutdown()
+/*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 0|
+ 1|
+ 2|
+ 3| *** *** *** ***
+ 4|* * * * * * * *
+ 5|* * * * * * * *
+ 6| *** *** *** ***
+ 7|* * * * * * * *
+ 8|* * * * * * * *
+ 9| *** *** *** ***
+ 10|
+ 11|
+
+ or
+
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 0| *
+ 1| **
+ 2| ***
+ 3| * * **
+ 4| ** * **
+ 5| *****
+ 6| ***
+ 7| ***
+ 8| *****
+ 9| ** * **
+ 10| * * **
+ 11| ***
+ 12| **
+ 13| *
+
+ or
+
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 0| ******* **
+ 1| * * **
+ 2| * ***** * **
+ 3|* * * * **
+ 4| * *** * **
+ 5| * * ** ** **
+ 6| * ******
+ 7| *** ****
+ 8| * **
+*/
+
+// Set bits to turn on various icons in the Radio area
+// ie: Bluetooth, WiFi, ESP Now, Mode indicators, as well as sub states of each (MAC, Blinking, Arrows, etc), depending
+// on connection state This function has all the logic to determine how a shared icon spot should act. ie: if we need an
+// up arrow, blink the ESP Now icon, etc. This function merely sets the bits to what should be displayed. The main
+// updateDisplay() function pushes bits to screen.
+uint32_t setRadioIcons()
{
- if (online.display == true)
- {
- //Show shutdown text
- oled.clear(PAGE); // Clear the display's internal memory
+ uint32_t icons = 0;
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ if (online.display == true)
+ {
+ // There are three spots for icons in the Wireless area, left/center/right
+ // There are three radios that could be active: Bluetooth (always indicated), WiFi (if enabled), ESP-Now (if
+ // enabled) Because of lack of space we will indicate the Base/Rover if only two radios or less are active
+
+ // 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 (wifiState > WIFI_STATE_OFF)
+ numberOfRadios++;
+ if (espnowState > ESPNOW_OFF)
+ numberOfRadios++;
+
+ // Bluetooth only
+ if (numberOfRadios == 1)
+ {
+ icons |= setBluetoothIcon_OneRadio();
- int textX = 2;
- int textY = 10;
- int textKerning = 8;
+ icons |= setModeIcon(); // Turn on Rover/Base type icons
+ }
- printTextwithKerning((char*)"Shutting", textX, textY, textKerning);
+ else if (numberOfRadios == 2)
+ {
+ icons |= setBluetoothIcon_TwoRadios();
- textX = 4;
- textY = 25;
- textKerning = 9;
- oled.setFontType(1);
+ // Do we have WiFi or ESP
+ if (wifiState > WIFI_STATE_OFF)
+ icons |= setWiFiIcon_TwoRadios();
+ else if (espnowState > ESPNOW_OFF)
+ icons |= setESPNowIcon_TwoRadios();
- printTextwithKerning((char*)"Down...", textX, textY, textKerning);
+ icons |= setModeIcon(); // Turn on Rover/Base type icons
+ }
+
+ else if (numberOfRadios == 3)
+ {
+ // Bluetooth is center
+ icons |= setBluetoothIcon_TwoRadios();
- oled.display();
- }
+ // ESP Now is left
+ icons |= setESPNowIcon_TwoRadios();
+
+ // WiFi is right
+ icons |= setWiFiIcon_ThreeRadios();
+
+ // No Rover/Base icons
+ }
+ }
+
+ return icons;
}
-//Displays a small error message then hard freeze
-//Text wraps and is small but legible
-void displayError(char * errorMessage)
+// Bluetooth is in left position
+// Set Bluetooth icons (MAC, Connected, arrows) in left position
+uint32_t setBluetoothIcon_OneRadio()
{
- if (online.display == true)
- {
- oled.clear(PAGE); // Clear the display's internal buffer
+ uint32_t icons = 0;
- oled.setCursor(0, 0); //x, y
- oled.setFontType(0); //Set font to smallest
- oled.print(F("Error:"));
+ if (bluetoothGetState() != BT_CONNECTED)
+ icons |= ICON_MAC_ADDRESS;
+ else if (bluetoothGetState() == BT_CONNECTED)
+ {
+ // Limit how often we update this spot
+ if (millis() - firstRadioSpotTimer > 2000)
+ {
+ firstRadioSpotTimer = millis();
- oled.setCursor(2, 10);
- //oled.setFontType(1);
- oled.print(errorMessage);
+ if (bluetoothIncomingRTCM == true || bluetoothOutgoingRTCM == true)
+ firstRadioSpotBlink ^= 1; // Share the spot
+ else
+ firstRadioSpotBlink = false;
+ }
- oled.display(); //Push internal buffer to display
+ if (firstRadioSpotBlink == false)
+ icons |= ICON_BT_SYMBOL_LEFT;
+ else
+ {
+ // Share the spot. Determine if we need to indicate Up, or Down
+ if (bluetoothIncomingRTCM == true)
+ {
+ icons |= ICON_DOWN_ARROW_LEFT;
+ bluetoothIncomingRTCM = false; // Reset, set during UART RX task.
+ }
+ else if (bluetoothOutgoingRTCM == true)
+ {
+ icons |= ICON_UP_ARROW_LEFT;
+ bluetoothOutgoingRTCM = false; // Reset, set during UART BT send bytes task.
+ }
+ else
+ icons |= ICON_BT_SYMBOL_LEFT;
+ }
+ }
- while (1) delay(10); //Hard freeze
- }
+ return icons;
}
-//Print the classic battery icon with levels
-void paintBatteryLevel()
+// Bluetooth is in center position
+// Set Bluetooth icons (MAC, Connected, arrows) in left position
+uint32_t setBluetoothIcon_TwoRadios()
+{
+ uint32_t icons = 0;
+
+ if (bluetoothGetState() != BT_CONNECTED)
+ icons |= ICON_MAC_ADDRESS_2DIGIT;
+ else if (bluetoothGetState() == BT_CONNECTED)
+ {
+ // Limit how often we update this spot
+ if (millis() - secondRadioSpotTimer > 2000)
+ {
+ secondRadioSpotTimer = millis();
+
+ if (bluetoothIncomingRTCM == true || bluetoothOutgoingRTCM == true)
+ secondRadioSpotBlink ^= 1; // Share the spot
+ else
+ secondRadioSpotBlink = false;
+ }
+
+ if (secondRadioSpotBlink == false)
+ icons |= ICON_BT_SYMBOL_CENTER;
+ else
+ {
+ // Share the spot. Determine if we need to indicate Up, or Down
+ if (bluetoothIncomingRTCM == true)
+ {
+ icons |= ICON_DOWN_ARROW_CENTER;
+ bluetoothIncomingRTCM = false; // Reset, set during UART RX task.
+ }
+ else if (bluetoothOutgoingRTCM == true)
+ {
+ icons |= ICON_UP_ARROW_CENTER;
+ bluetoothOutgoingRTCM = false; // Reset, set during UART BT send bytes task.
+ }
+ else
+ icons |= ICON_BT_SYMBOL_CENTER;
+ }
+ }
+
+ return icons;
+}
+
+// Bluetooth is in center position
+// Set ESP Now icon (Solid, arrows, blinking) in left position
+uint32_t setESPNowIcon_TwoRadios()
{
- if (online.display == true)
- {
- //Current battery charge level
- if (battLevel < 25)
- oled.drawIcon(45, 0, Battery_0_Width, Battery_0_Height, Battery_0, sizeof(Battery_0), true);
- else if (battLevel < 50)
- oled.drawIcon(45, 0, Battery_1_Width, Battery_1_Height, Battery_1, sizeof(Battery_1), true);
- else if (battLevel < 75)
- oled.drawIcon(45, 0, Battery_2_Width, Battery_2_Height, Battery_2, sizeof(Battery_2), true);
- else //batt level > 75
- oled.drawIcon(45, 0, Battery_3_Width, Battery_3_Height, Battery_3, sizeof(Battery_3), true);
- }
-}
-
-//Display Bluetooth icon, Bluetooth MAC, or WiFi depending on connection state
-void paintWirelessIcon()
-{
- if (online.display == true)
- {
- //Bluetooth icon if paired, or Bluetooth MAC address if not paired
- if (radioState == BT_CONNECTED)
- {
- oled.drawIcon(4, 0, BT_Symbol_Width, BT_Symbol_Height, BT_Symbol, sizeof(BT_Symbol), true);
- }
- else if (radioState == WIFI_ON_NOCONNECTION)
- {
- //Blink WiFi icon
- if (millis() - lastWifiIconUpdate > 500)
- {
- lastWifiIconUpdate = millis();
- if (wifiIconDisplayed == false)
+ uint32_t icons = 0;
+
+ if (espnowState == ESPNOW_PAIRED)
+ {
+ // Limit how often we update this spot
+ if (millis() - firstRadioSpotTimer > 2000)
{
- wifiIconDisplayed = true;
+ firstRadioSpotTimer = millis();
- //Draw the icon
- oled.drawIcon(6, 1, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol, sizeof(WiFi_Symbol), true);
+ if (espnowIncomingRTCM == true || espnowOutgoingRTCM == true)
+ firstRadioSpotBlink ^= 1; // Share the spot
+ else
+ firstRadioSpotBlink = false;
+ }
+
+ if (firstRadioSpotBlink == false)
+ {
+ if (espnowIncomingRTCM == true)
+ {
+ // Based on RSSI, select icon
+ if (espnowRSSI >= -40)
+ icons |= ICON_ESPNOW_SYMBOL_3_LEFT;
+ else if (espnowRSSI >= -60)
+ icons |= ICON_ESPNOW_SYMBOL_2_LEFT;
+ else if (espnowRSSI >= -80)
+ icons |= ICON_ESPNOW_SYMBOL_1_LEFT;
+ else if (espnowRSSI > -255)
+ icons |= ICON_ESPNOW_SYMBOL_0_LEFT;
+ }
+ else // ESP radio is active, but not receiving RTCM
+ {
+ icons |= ICON_ESPNOW_SYMBOL_3_LEFT; // Full symbol
+ }
}
else
- wifiIconDisplayed = false;
- }
+ {
+ // Share the spot. Determine if we need to indicate Up, or Down
+ if (espnowIncomingRTCM == true)
+ {
+ icons |= ICON_DOWN_ARROW_LEFT;
+ espnowIncomingRTCM = false; // Reset, set during ESP Now data received call back
+ }
+ else if (espnowOutgoingRTCM == true)
+ {
+ icons |= ICON_UP_ARROW_LEFT;
+ espnowOutgoingRTCM = false; // Reset, set during espnowProcessRTCM()
+ }
+ else
+ {
+ icons |= ICON_ESPNOW_SYMBOL_3_LEFT; // Full symbol
+
+ // TODO catch RSSI here
+ }
+ }
}
- else if (radioState == WIFI_CONNECTED)
+
+ else // We are not paired, blink icon
{
- //Solid WiFi icon
- oled.drawIcon(6, 1, WiFi_Symbol_Width, WiFi_Symbol_Height, WiFi_Symbol, sizeof(WiFi_Symbol), true);
+ // Limit how often we update this spot
+ if (millis() - firstRadioSpotTimer > 2000)
+ {
+ firstRadioSpotTimer = millis();
+ firstRadioSpotBlink ^= 1; // Share the spot
+ }
+
+ if (firstRadioSpotBlink == false)
+ icons |= ICON_ESPNOW_SYMBOL_3_LEFT; // Full symbol
+ else
+ icons |= ICON_BLANK_LEFT;
}
- else
+
+ return icons;
+}
+
+// Bluetooth is in center position
+// Set WiFi icon (Solid, arrows, blinking) in left position
+uint32_t setWiFiIcon_TwoRadios()
+{
+ uint32_t icons = 0;
+
+ if (wifiState == WIFI_STATE_CONNECTED)
{
- char macAddress[5];
- sprintf(macAddress, "%02X%02X", unitMACAddress[4], unitMACAddress[5]);
- oled.setFontType(0); //Set font to smallest
- oled.setCursor(0, 4);
- oled.print(macAddress);
+ // Limit how often we update this spot
+ if (millis() - firstRadioSpotTimer > 2000)
+ {
+ firstRadioSpotTimer = millis();
+
+ if (netIncomingRTCM == true || netOutgoingRTCM == true)
+ firstRadioSpotBlink ^= 1; // Share the spot
+ else
+ firstRadioSpotBlink = false;
+ }
+
+ if (firstRadioSpotBlink == false)
+ {
+#ifdef COMPILE_WIFI
+ int wifiRSSI = WiFi.RSSI();
+#else // COMPILE_WIFI
+ int wifiRSSI = -40; // Dummy
+#endif // COMPILE_WIFI
+ // Based on RSSI, select icon
+ if (wifiRSSI >= -40)
+ icons |= ICON_WIFI_SYMBOL_3_LEFT;
+ else if (wifiRSSI >= -60)
+ icons |= ICON_WIFI_SYMBOL_2_LEFT;
+ else if (wifiRSSI >= -80)
+ icons |= ICON_WIFI_SYMBOL_1_LEFT;
+ else
+ icons |= ICON_WIFI_SYMBOL_0_LEFT;
+ }
+ else
+ {
+ // Share the spot. Determine if we need to indicate Up, or Down
+ if (netIncomingRTCM == true)
+ {
+ icons |= ICON_DOWN_ARROW_LEFT;
+ netIncomingRTCM = false; // Reset, set during NTRIP Client
+ }
+ else if (netOutgoingRTCM == true)
+ {
+ icons |= ICON_UP_ARROW_LEFT;
+ netOutgoingRTCM = false; // Reset, set during NTRIP Server
+ }
+ else
+ {
+#ifdef COMPILE_WIFI
+ int wifiRSSI = WiFi.RSSI();
+#else // COMPILE_WIFI
+ int wifiRSSI = -40; // Dummy
+#endif // COMPILE_WIFI
+ // Based on RSSI, select icon
+ if (wifiRSSI >= -40)
+ icons |= ICON_WIFI_SYMBOL_3_LEFT;
+ else if (wifiRSSI >= -60)
+ icons |= ICON_WIFI_SYMBOL_2_LEFT;
+ else if (wifiRSSI >= -80)
+ icons |= ICON_WIFI_SYMBOL_1_LEFT;
+ else
+ icons |= ICON_WIFI_SYMBOL_0_LEFT;
+ }
+ }
+ }
+
+ else // We are not paired, blink icon
+ {
+ // Limit how often we update this spot
+ if (millis() - firstRadioSpotTimer > 2000)
+ {
+ firstRadioSpotTimer = millis();
+ firstRadioSpotBlink ^= 1; // Share the spot
+ }
+
+ if (firstRadioSpotBlink == false)
+ icons |= ICON_WIFI_SYMBOL_3_LEFT; // Full symbol
+ else
+ icons |= ICON_BLANK_LEFT;
}
- }
+
+ return (icons);
}
-//Display cross hairs and horizontal accuracy
-//Display double circle if we have RTK (blink = float, solid = fix)
-void paintHorizontalAccuracy()
+// Bluetooth is in center position
+// Set WiFi icon (Solid, arrows, blinking) in right position
+uint32_t setWiFiIcon_ThreeRadios()
{
- if (online.display == true)
- {
- //Blink crosshair icon until we achieve <5m horz accuracy (user definable)
- if (systemState == STATE_BASE_TEMP_SETTLE)
+ uint32_t icons = 0;
+
+ if (wifiState == WIFI_STATE_CONNECTED)
{
- if (millis() - lastCrosshairIconUpdate > 500)
- {
- lastCrosshairIconUpdate = millis();
- if (crosshairIconDisplayed == false)
+ // Limit how often we update this spot
+ if (millis() - thirdRadioSpotTimer > 2000)
{
- crosshairIconDisplayed = true;
+ thirdRadioSpotTimer = millis();
- //Draw the icon
- oled.drawIcon(0, 18, CrossHair_Width, CrossHair_Height, CrossHair, sizeof(CrossHair), true);
+ if (netIncomingRTCM == true || netOutgoingRTCM == true)
+ thirdRadioSpotBlink ^= 1; // Share the spot
+ else
+ thirdRadioSpotBlink = false;
+ }
+
+ if (thirdRadioSpotBlink == false)
+ {
+#ifdef COMPILE_WIFI
+ int wifiRSSI = WiFi.RSSI();
+#else // COMPILE_WIFI
+ int wifiRSSI = -40; // Dummy
+#endif // COMPILE_WIFI
+ // Based on RSSI, select icon
+ if (wifiRSSI >= -40)
+ icons |= ICON_WIFI_SYMBOL_3_RIGHT;
+ else if (wifiRSSI >= -60)
+ icons |= ICON_WIFI_SYMBOL_2_RIGHT;
+ else if (wifiRSSI >= -80)
+ icons |= ICON_WIFI_SYMBOL_1_RIGHT;
+ else
+ icons |= ICON_WIFI_SYMBOL_0_RIGHT;
}
else
- crosshairIconDisplayed = false;
- }
+ {
+ // Share the spot. Determine if we need to indicate Up, or Down
+ if (netIncomingRTCM == true)
+ {
+ icons |= ICON_DOWN_ARROW_RIGHT;
+ netIncomingRTCM = false; // Reset, set during NTRIP Client
+ }
+ else if (netOutgoingRTCM == true)
+ {
+ icons |= ICON_UP_ARROW_RIGHT;
+ netOutgoingRTCM = false; // Reset, set during NTRIP Server
+ }
+ else
+ {
+#ifdef COMPILE_WIFI
+ int wifiRSSI = WiFi.RSSI();
+#else // COMPILE_WIFI
+ int wifiRSSI = -40; // Dummy
+#endif // COMPILE_WIFI
+ // Based on RSSI, select icon
+ if (wifiRSSI >= -40)
+ icons |= ICON_WIFI_SYMBOL_3_RIGHT;
+ else if (wifiRSSI >= -60)
+ icons |= ICON_WIFI_SYMBOL_2_RIGHT;
+ else if (wifiRSSI >= -80)
+ icons |= ICON_WIFI_SYMBOL_1_RIGHT;
+ else
+ icons |= ICON_WIFI_SYMBOL_0_RIGHT;
+ }
+ }
}
- else if (systemState == STATE_ROVER_RTK_FLOAT)
+
+ else // We are not paired, blink icon
{
- if (millis() - lastCrosshairIconUpdate > 500)
- {
- lastCrosshairIconUpdate = millis();
- if (crosshairIconDisplayed == false)
+ // Limit how often we update this spot
+ if (millis() - thirdRadioSpotTimer > 2000)
{
- crosshairIconDisplayed = true;
+ thirdRadioSpotTimer = millis();
+ thirdRadioSpotBlink ^= 1; // Share the spot
+ }
+
+ if (thirdRadioSpotBlink == false)
+ icons |= ICON_WIFI_SYMBOL_3_RIGHT; // Full symbol
+ else
+ icons |= ICON_BLANK_RIGHT;
+ }
+
+ return (icons);
+}
+
+// Bluetooth and ESP Now icons off. WiFi in middle.
+// Blink while no clients are connected
+uint32_t setWiFiIcon()
+{
+ uint32_t icons = 0;
- //Draw dual crosshair
- oled.drawIcon(0, 18, CrossHairDual_Width, CrossHairDual_Height, CrossHairDual, sizeof(CrossHairDual), true);
+ if (online.display == true)
+ {
+ if (wifiState == WIFI_STATE_CONNECTED)
+ {
+ icons |= ICON_WIFI_SYMBOL_3_RIGHT;
}
else
- crosshairIconDisplayed = false;
- }
+ {
+ // Limit how often we update this spot
+ if (millis() - thirdRadioSpotTimer > 1000)
+ {
+ thirdRadioSpotTimer = millis();
+ thirdRadioSpotBlink ^= 1; // Blink this icon
+ }
+
+ if (thirdRadioSpotBlink == false)
+ icons |= ICON_BLANK_RIGHT;
+ else
+ icons |= ICON_WIFI_SYMBOL_3_RIGHT;
+ }
}
- else if (systemState == STATE_ROVER_RTK_FIX)
+
+ return (icons);
+}
+
+// Based on system state, turn on the various Rover, Base, Fixed Base icons
+uint32_t setModeIcon()
+{
+ uint32_t icons = 0;
+
+ switch (systemState)
{
- //Draw dual crosshair
- oled.drawIcon(0, 18, CrossHairDual_Width, CrossHairDual_Height, CrossHairDual, sizeof(CrossHairDual), true);
+ case (STATE_ROVER_NOT_STARTED):
+ break;
+ case (STATE_ROVER_NO_FIX):
+ icons |= ICON_DYNAMIC_MODEL;
+ break;
+ case (STATE_ROVER_FIX):
+ icons |= ICON_DYNAMIC_MODEL;
+ break;
+ case (STATE_ROVER_RTK_FLOAT):
+ icons |= ICON_DYNAMIC_MODEL;
+ break;
+ case (STATE_ROVER_RTK_FIX):
+ icons |= ICON_DYNAMIC_MODEL;
+ break;
+
+ case (STATE_BASE_NOT_STARTED):
+ // Do nothing. Static display shown during state change.
+ break;
+ case (STATE_BASE_TEMP_SETTLE):
+ icons |= blinkBaseIcon(ICON_BASE_TEMPORARY);
+ break;
+ case (STATE_BASE_TEMP_SURVEY_STARTED):
+ icons |= blinkBaseIcon(ICON_BASE_TEMPORARY);
+ break;
+ case (STATE_BASE_TEMP_TRANSMITTING):
+ icons |= ICON_BASE_TEMPORARY;
+ break;
+ case (STATE_BASE_FIXED_NOT_STARTED):
+ // Do nothing. Static display shown during state change.
+ break;
+ case (STATE_BASE_FIXED_TRANSMITTING):
+ icons |= ICON_BASE_FIXED;
+ break;
+
+ case (STATE_NTPSERVER_NOT_STARTED):
+ case (STATE_NTPSERVER_NO_SYNC):
+ case (STATE_NTPSERVER_SYNC):
+ break;
+
+ default:
+ break;
}
- else
+ return (icons);
+}
+
+uint32_t blinkBaseIcon(uint32_t iconType)
+{
+ uint32_t icons = 0;
+
+ // Limit how often we update this spot
+ if (millis() - thirdRadioSpotTimer > 1000)
{
- //Draw crosshair
- oled.drawIcon(0, 18, CrossHair_Width, CrossHair_Height, CrossHair, sizeof(CrossHair), true);
+ thirdRadioSpotTimer = millis();
+ thirdRadioSpotBlink ^= 1; // Share the spot
}
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.setCursor(16, 20); //x, y
+ if (thirdRadioSpotBlink == false)
+ icons |= iconType;
+ else
+ icons |= ICON_BLANK_RIGHT;
+
+ return icons;
+}
+
+/*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 17|
+ 18|
+ 19|
+ 20|
+ 21| *** *** ***
+ 22| * * * * * *
+ 23| * * * * * *
+ 24| ** * * * * * *
+ 25| ** * * *
+ 26| * * * * * *
+ 27| * * * * * *
+ 28| * * * * * *
+ 29| ** * * ** * * * *
+ 30| ** *** ** *** ***
+ 31|
+ 32|
+*/
+
+// Display horizontal accuracy
+void paintHorizontalAccuracy()
+{
+ oled.setFont(QW_FONT_8X16); // Set font to type 1: 8x16
+ oled.setCursor(16, 20); // x, y
oled.print(":");
- float hpa = i2cGNSS.getHorizontalAccuracy() / 10000.0;
- if (hpa > 30.0)
+
+ if (online.gnss == false)
{
- oled.print(F(">30m"));
+ oled.print("N/A");
}
- else if (hpa > 9.9)
+ else if (horizontalAccuracy > 30.0)
{
- oled.print(hpa, 1); //Print down to decimeter
+ oled.print(">30m");
}
- else if (hpa > 1.0)
+ else if (horizontalAccuracy > 9.9)
{
- oled.print(hpa, 2); //Print down to centimeter
+ oled.print(horizontalAccuracy, 1); // Print down to decimeter
+ }
+ else if (horizontalAccuracy > 1.0)
+ {
+ oled.print(horizontalAccuracy, 2); // Print down to centimeter
}
else
{
- oled.print("."); //Remove leading zero
- oled.printf("%03d", (int)(hpa * 1000)); //Print down to millimeter
+ oled.print("."); // Remove leading zero
+ oled.printf("%03d", (int)(horizontalAccuracy * 1000)); // Print down to millimeter
}
- }
}
-//Draw either a rover or base icon depending on screen
-//Draw a different base if we have fixed coordinate base type
-void paintBaseState()
+// Display clock with moving hands
+void paintClock()
+{
+ // Animate icon to show system running
+ static uint8_t clockIconDisplayed = 3;
+ clockIconDisplayed++; // Goto next icon
+ clockIconDisplayed %= 4; // Wrap
+
+ if (clockIconDisplayed == 0)
+ displayBitmap(0, 18, Clock_Icon_Width, Clock_Icon_Height, Clock_Icon_1);
+ else if (clockIconDisplayed == 1)
+ displayBitmap(0, 18, Clock_Icon_Width, Clock_Icon_Height, Clock_Icon_2);
+ else if (clockIconDisplayed == 2)
+ displayBitmap(0, 18, Clock_Icon_Width, Clock_Icon_Height, Clock_Icon_3);
+ else
+ displayBitmap(0, 18, Clock_Icon_Width, Clock_Icon_Height, Clock_Icon_4);
+}
+
+// Display clock accuracy tAcc
+void paintClockAccuracy()
{
- if (online.display == true)
- {
- if (systemState == STATE_ROVER_NO_FIX ||
- systemState == STATE_ROVER_FIX ||
- systemState == STATE_ROVER_RTK_FLOAT ||
- systemState == STATE_ROVER_RTK_FIX)
+ oled.setFont(QW_FONT_8X16); // Set font to type 1: 8x16
+ oled.setCursor(16, 20); // x, y
+ oled.print(":");
+
+ if (online.gnss == false)
+ {
+ oled.print(" N/A");
+ }
+ else if (tAcc < 10) // 9 or less : show as 9ns
+ {
+ oled.print(tAcc);
+ displayBitmap(36, 20, Millis_Icon_Width, Millis_Icon_Height, Nanos_Icon);
+ }
+ else if (tAcc < 100) // 99 or less : show as 99ns
+ {
+ oled.print(tAcc);
+ displayBitmap(44, 20, Millis_Icon_Width, Millis_Icon_Height, Nanos_Icon);
+ }
+ else if (tAcc < 10000) // 9999 or less : show as 9.9μs
+ {
+ oled.print(tAcc / 1000);
+ oled.print(".");
+ oled.print((tAcc / 100) % 10);
+ displayBitmap(52, 20, Millis_Icon_Width, Millis_Icon_Height, Micros_Icon);
+ }
+ else if (tAcc < 100000) // 99999 or less : show as 99μs
{
- oled.drawIcon(27, 3, Rover_Width, Rover_Height, Rover, sizeof(Rover), true);
+ oled.print(tAcc / 1000);
+ displayBitmap(44, 20, Millis_Icon_Width, Millis_Icon_Height, Micros_Icon);
}
- else if (systemState == STATE_BASE_TEMP_SETTLE ||
- systemState == STATE_BASE_TEMP_SURVEY_STARTED //Turn on base icon solid (blink crosshair in paintHorzAcc)
- )
+ else if (tAcc < 10000000) // 9999999 or less : show as 9.9ms
{
- //Blink base icon until survey is complete
- if (millis() - lastBaseIconUpdate > 500)
- {
- lastBaseIconUpdate = millis();
- if (baseIconDisplayed == false)
+ oled.print(tAcc / 1000000);
+ oled.print(".");
+ oled.print((tAcc / 100000) % 10);
+ displayBitmap(52, 20, Millis_Icon_Width, Millis_Icon_Height, Millis_Icon);
+ }
+ else // if (tAcc >= 100000)
+ {
+ oled.print(">10");
+ displayBitmap(52, 20, Millis_Icon_Width, Millis_Icon_Height, Millis_Icon);
+ }
+}
+
+/*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 0| **
+ 1| **
+ 2| ******
+ 3| * *
+ 4| * * **** * *
+ 5| * * **** * *
+ 6| * * * *
+ 7| * * * *
+ 8| * * * *
+ 9| * * * *
+ 10| * *
+ 11| ******
+ 12|
+*/
+
+// Draw the rover icon depending on screen
+void paintDynamicModel()
+{
+ // Display icon associated with current Dynamic Model
+ switch (settings.dynamicModel)
+ {
+ case (DYN_MODEL_PORTABLE):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_1_Portable);
+ break;
+ case (DYN_MODEL_STATIONARY):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_2_Stationary);
+ break;
+ case (DYN_MODEL_PEDESTRIAN):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_3_Pedestrian);
+ break;
+ case (DYN_MODEL_AUTOMOTIVE):
+ // Normal rover for ZED-F9P, fusion rover for ZED-F9R
+ if (zedModuleType == PLATFORM_F9P)
+ {
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_4_Automotive);
+ }
+ else if (zedModuleType == PLATFORM_F9R)
{
- baseIconDisplayed = true;
+ // Blink fusion rover until we have calibration
+ if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 0) // Initializing
+ {
+ // Blink Fusion Rover icon until sensor calibration is complete
+ if (millis() - lastBaseIconUpdate > 500)
+ {
+ lastBaseIconUpdate = millis();
+ if (baseIconDisplayed == false)
+ {
+ baseIconDisplayed = true;
+
+ // Draw the icon
+ displayBitmap(28, 2, Rover_Fusion_Width, Rover_Fusion_Height, Rover_Fusion);
+ }
+ else
+ baseIconDisplayed = false;
+ }
+ }
+ else if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 1) // Calibrated
+ {
+ // Solid fusion rover
+ displayBitmap(28, 2, Rover_Fusion_Width, Rover_Fusion_Height, Rover_Fusion);
+ }
+ else if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 2 ||
+ theGNSS.packetUBXESFSTATUS->data.fusionMode == 3) // Suspended or disabled
+ {
+ // Empty rover
+ displayBitmap(28, 2, Rover_Fusion_Empty_Width, Rover_Fusion_Empty_Height, Rover_Fusion_Empty);
+ }
+ }
+ break;
+ case (DYN_MODEL_SEA):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_5_Sea);
+ break;
+ case (DYN_MODEL_AIRBORNE1g):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_6_Airborne1g);
+ break;
+ case (DYN_MODEL_AIRBORNE2g):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_7_Airborne2g);
+ break;
+ case (DYN_MODEL_AIRBORNE4g):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_8_Airborne4g);
+ break;
+ case (DYN_MODEL_WRIST):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_9_Wrist);
+ break;
+ case (DYN_MODEL_BIKE):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_10_Bike);
+ break;
+ case (DYN_MODEL_MOWER):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_11_Mower);
+ break;
+ case (DYN_MODEL_ESCOOTER):
+ displayBitmap(28, 0, DynamicModel_Width, DynamicModel_Height, DynamicModel_12_EScooter);
+ break;
+ }
+}
+
+/*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 35|
+ 36| **
+ 37| * * *** ***
+ 38| * * * * * * *
+ 39| * * * * * * *
+ 40| * * ** * * * *
+ 41| * * ** * *
+ 42| * * * * * *
+ 43| ** * * * * *
+ 44| **** * * * * *
+ 45| ** **** ** * * * *
+ 46| ** ** *** ***
+ 47| ******
+*/
+
+// Select satellite icon and draw sats in view
+// Blink icon if no fix
+uint32_t paintSIV()
+{
+ uint32_t blinking;
+ uint32_t icons;
+
+ oled.setFont(QW_FONT_8X16); // Set font to type 1: 8x16
+ oled.setCursor(16, 36); // x, y
+ oled.print(":");
+
+ if (online.gnss)
+ {
+ if (fixType == 0) // 0 = No Fix
+ oled.print("0");
+ else
+ oled.print(numSV);
+
+ paintResets();
+
+ // Determine which icon to display
+ icons = 0;
+ if (lbandCorrectionsReceived)
+ blinking = ICON_SIV_ANTENNA_LBAND;
+ else
+ blinking = ICON_SIV_ANTENNA;
- //Draw the icon
- oled.drawIcon(27, 0, BaseTemporary_Width, BaseTemporary_Height, BaseTemporary, sizeof(BaseTemporary), true); //true - blend with other pixels
+ // Determine if there is a fix
+ if (fixType == 3 || fixType == 4 || fixType == 5) // 3D, 3D+DR, or Time
+ {
+ // Fix, turn on icon
+ icons = blinking;
}
else
- baseIconDisplayed = false;
- }
+ {
+ // Blink satellite dish icon if we don't have a fix
+ blinking_icons ^= blinking;
+ if (blinking_icons & blinking)
+ icons = blinking;
+ }
+ } // End gnss online
+ else
+ {
+ oled.print("X");
+
+ icons = ICON_SIV_ANTENNA;
+ }
+ return icons;
+}
+
+/*
+ 111111111122222222223333333333444444444455555555556666
+ 0123456789012345678901234567890123456789012345678901234567890123
+ .----------------------------------------------------------------
+ 35|
+ 36| *******
+ 37| * **
+ 38| * **
+ 39| * *
+ 40| * ***** *
+ 41| * *
+ 42| * ***** *
+ 43| * *
+ 44| * ***** *
+ 45| * *
+ 46| * *
+ 47| *********
+*/
+
+// Draw log icon
+// Turn off icon if log file fails to get bigger
+void paintLogging()
+{
+ // Animate icon to show system running
+ loggingIconDisplayed++; // Goto next icon
+ loggingIconDisplayed %= 4; // Wrap
+#ifdef COMPILE_ETHERNET
+ if ((online.logging == true) && (logIncreasing || ntpLogIncreasing))
+#else // COMPILE_ETHERNET
+ if ((online.logging == true) && (logIncreasing))
+#endif // COMPILE_ETHERNET
+ {
+ if (loggingType == LOGGING_STANDARD)
+ {
+ if (loggingIconDisplayed == 0)
+ displayBitmap(64 - Logging_0_Width, 48 - Logging_0_Height, Logging_0_Width, Logging_0_Height,
+ Logging_0);
+ else if (loggingIconDisplayed == 1)
+ displayBitmap(64 - Logging_1_Width, 48 - Logging_1_Height, Logging_1_Width, Logging_1_Height,
+ Logging_1);
+ else if (loggingIconDisplayed == 2)
+ displayBitmap(64 - Logging_2_Width, 48 - Logging_2_Height, Logging_2_Width, Logging_2_Height,
+ Logging_2);
+ else if (loggingIconDisplayed == 3)
+ displayBitmap(64 - Logging_3_Width, 48 - Logging_3_Height, Logging_3_Width, Logging_3_Height,
+ Logging_3);
+ }
+ else if (loggingType == LOGGING_PPP)
+ {
+ if (loggingIconDisplayed == 0)
+ displayBitmap(64 - Logging_0_Width, 48 - Logging_0_Height, Logging_0_Width, Logging_0_Height,
+ Logging_0);
+ else if (loggingIconDisplayed == 1)
+ displayBitmap(64 - Logging_1_Width, 48 - Logging_1_Height, Logging_1_Width, Logging_1_Height,
+ Logging_PPP_1);
+ else if (loggingIconDisplayed == 2)
+ displayBitmap(64 - Logging_2_Width, 48 - Logging_2_Height, Logging_2_Width, Logging_2_Height,
+ Logging_PPP_2);
+ else if (loggingIconDisplayed == 3)
+ displayBitmap(64 - Logging_3_Width, 48 - Logging_3_Height, Logging_3_Width, Logging_3_Height,
+ Logging_PPP_3);
+ }
+ else if (loggingType == LOGGING_CUSTOM)
+ {
+ if (loggingIconDisplayed == 0)
+ displayBitmap(64 - Logging_0_Width, 48 - Logging_0_Height, Logging_0_Width, Logging_0_Height,
+ Logging_0);
+ else if (loggingIconDisplayed == 1)
+ displayBitmap(64 - Logging_1_Width, 48 - Logging_1_Height, Logging_1_Width, Logging_1_Height,
+ Logging_Custom_1);
+ else if (loggingIconDisplayed == 2)
+ displayBitmap(64 - Logging_2_Width, 48 - Logging_2_Height, Logging_2_Width, Logging_2_Height,
+ Logging_Custom_2);
+ else if (loggingIconDisplayed == 3)
+ displayBitmap(64 - Logging_3_Width, 48 - Logging_3_Height, Logging_3_Width, Logging_3_Height,
+ Logging_Custom_3);
+ }
}
- else if (systemState == STATE_BASE_TEMP_TRANSMITTING ||
- systemState == STATE_BASE_TEMP_WIFI_STARTED ||
- systemState == STATE_BASE_TEMP_WIFI_CONNECTED ||
- systemState == STATE_BASE_TEMP_CASTER_STARTED ||
- systemState == STATE_BASE_TEMP_CASTER_CONNECTED)
+ else
{
- //Draw the icon
- oled.drawIcon(27, 0, BaseTemporary_Width, BaseTemporary_Height, BaseTemporary, sizeof(BaseTemporary), true); //true - blend with other pixels
+ const int pulseX = 64 - 4;
+ const int pulseY = oled.getHeight();
+ int height;
+
+ // Paint pulse to show system activity
+ height = loggingIconDisplayed << 2;
+ if (height)
+ {
+ oled.line(pulseX, pulseY, pulseX, pulseY - height);
+ oled.line(pulseX - 1, pulseY, pulseX - 1, pulseY - height);
+ }
}
- else if (systemState == STATE_BASE_FIXED_TRANSMITTING ||
- systemState == STATE_BASE_FIXED_WIFI_STARTED ||
- systemState == STATE_BASE_FIXED_WIFI_CONNECTED ||
- systemState == STATE_BASE_FIXED_CASTER_STARTED ||
- systemState == STATE_BASE_FIXED_CASTER_CONNECTED)
+}
+
+void paintLoggingNTP(bool noPulse)
+{
+ // Animate icon to show system running
+ loggingIconDisplayed++; // Goto next icon
+ loggingIconDisplayed %= 4; // Wrap
+#ifdef COMPILE_ETHERNET // Some redundancy here. paintLoggingNTP should only be called if Ethernet is present
+ if ((online.logging == true) && (logIncreasing || ntpLogIncreasing))
+#else // COMPILE_ETHERNET
+ if ((online.logging == true) && (logIncreasing))
+#endif // COMPILE_ETHERNET
{
- //Draw the icon
- oled.drawIcon(27, 0, BaseFixed_Width, BaseFixed_Height, BaseFixed, sizeof(BaseFixed), true); //true - blend with other pixels
+ if (loggingIconDisplayed == 0)
+ displayBitmap(64 - Logging_0_Width, 48 - Logging_0_Height, Logging_0_Width, Logging_0_Height, Logging_0);
+ else if (loggingIconDisplayed == 1)
+ displayBitmap(64 - Logging_1_Width, 48 - Logging_1_Height, Logging_1_Width, Logging_1_Height,
+ Logging_NTP_1);
+ else if (loggingIconDisplayed == 2)
+ displayBitmap(64 - Logging_2_Width, 48 - Logging_2_Height, Logging_2_Width, Logging_2_Height,
+ Logging_NTP_2);
+ else if (loggingIconDisplayed == 3)
+ displayBitmap(64 - Logging_3_Width, 48 - Logging_3_Height, Logging_3_Width, Logging_3_Height,
+ Logging_NTP_3);
+ }
+ else if (!noPulse)
+ {
+ const int pulseX = 64 - 4;
+ const int pulseY = oled.getHeight();
+ int height;
+
+ // Paint pulse to show system activity
+ height = loggingIconDisplayed << 2;
+ if (height)
+ {
+ oled.line(pulseX, pulseY, pulseX, pulseY - height);
+ oled.line(pulseX - 1, pulseY, pulseX - 1, pulseY - height);
+ }
}
- }
}
-//Draw satellite icon and sats in view
-//Blink icon if no fix
-void paintSIV()
+// Survey in is running. Show 3D Mean and elapsed time.
+void paintBaseTempSurveyStarted()
{
- if (online.display == true)
- {
- //Blink satellite dish icon if we don't have a fix
- if (i2cGNSS.getFixType() == 3 || i2cGNSS.getFixType() == 5) //3D or Time
+ oled.setFont(QW_FONT_5X7);
+ oled.setCursor(0, 23); // x, y
+ oled.print("Mean:");
+
+ oled.setCursor(29, 20); // x, y
+ oled.setFont(QW_FONT_8X16);
+ if (svinMeanAccuracy < 10.0) // Error check
+ oled.print(svinMeanAccuracy, 2);
+ else
+ oled.print(">10");
+
+ if (!HAS_ANTENNA_SHORT_OPEN)
{
- //Fix, turn on icon
- oled.drawIcon(2, 35, Antenna_Width, Antenna_Height, Antenna, sizeof(Antenna), true);
+ oled.setCursor(0, 39); // x, y
+ oled.setFont(QW_FONT_5X7);
+ oled.print("Time:");
}
else
{
- if (millis() - lastSatelliteDishIconUpdate > 500)
- {
- //Serial.println("SIV Blink");
- lastSatelliteDishIconUpdate = millis();
- if (satelliteDishIconDisplayed == false)
+ static uint32_t blinkers = 0;
+ if (aStatus == SFE_UBLOX_ANTENNA_STATUS_SHORT)
{
- satelliteDishIconDisplayed = true;
-
- //Draw the icon
- oled.drawIcon(2, 35, Antenna_Width, Antenna_Height, Antenna, sizeof(Antenna), true);
+ blinkers ^= ICON_ANTENNA_SHORT;
+ if (blinkers & ICON_ANTENNA_SHORT)
+ displayBitmap(2, 35, Antenna_Short_Width, Antenna_Short_Height, Antenna_Short);
+ }
+ else if (aStatus == SFE_UBLOX_ANTENNA_STATUS_OPEN)
+ {
+ blinkers ^= ICON_ANTENNA_OPEN;
+ if (blinkers & ICON_ANTENNA_OPEN)
+ displayBitmap(2, 35, Antenna_Open_Width, Antenna_Open_Height, Antenna_Open);
}
else
- satelliteDishIconDisplayed = false;
- }
+ {
+ blinkers &= ~ICON_ANTENNA_SHORT;
+ blinkers &= ~ICON_ANTENNA_OPEN;
+ oled.setCursor(0, 39); // x, y
+ oled.setFont(QW_FONT_5X7);
+ oled.print("Time:");
+ }
}
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.setCursor(16, 36); //x, y
- oled.print(":");
+ oled.setCursor(30, 36); // x, y
+ oled.setFont(QW_FONT_8X16);
+ if (svinObservationTime < 1000) // Error check
+ oled.print(svinObservationTime);
+ else
+ oled.print("0");
+}
+
+// Given text, a position, and kerning, print text to display
+// This is helpful for squishing or stretching a string to appropriately fill the display
+void printTextwithKerning(const char *newText, uint8_t xPos, uint8_t yPos, uint8_t kerning)
+{
+ for (int x = 0; x < strlen(newText); x++)
+ {
+ oled.setCursor(xPos, yPos);
+ oled.print(newText[x]);
+ xPos += kerning;
+ }
+}
+
+// Show transmission of RTCM correction data packets to NTRIP caster
+void paintRTCM()
+{
+ int yPos = 17;
+
+ // Determine if the NTRIP Server is casting
+ bool casting = false;
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ casting |= online.ntripServer[serverIndex];
+
+ if (casting)
+ printTextCenter("Casting", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ else
+ printTextCenter("Xmitting", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- if (i2cGNSS.getFixType() == 0) //0 = No Fix
+ if (!HAS_ANTENNA_SHORT_OPEN)
{
- oled.print("0");
+ oled.setCursor(0, 39); // x, y
+ oled.setFont(QW_FONT_5X7);
+ oled.print("RTCM:");
}
else
{
- oled.print(i2cGNSS.getSIV());
+ static uint32_t blinkers = 0;
+ if (aStatus == SFE_UBLOX_ANTENNA_STATUS_SHORT)
+ {
+ blinkers ^= ICON_ANTENNA_SHORT;
+ if (blinkers & ICON_ANTENNA_SHORT)
+ displayBitmap(2, 35, Antenna_Short_Width, Antenna_Short_Height, Antenna_Short);
+ }
+ else if (aStatus == SFE_UBLOX_ANTENNA_STATUS_OPEN)
+ {
+ blinkers ^= ICON_ANTENNA_OPEN;
+ if (blinkers & ICON_ANTENNA_OPEN)
+ displayBitmap(2, 35, Antenna_Open_Width, Antenna_Open_Height, Antenna_Open);
+ }
+ else
+ {
+ blinkers &= ~ICON_ANTENNA_SHORT;
+ blinkers &= ~ICON_ANTENNA_OPEN;
+ oled.setCursor(0, 39); // x, y
+ oled.setFont(QW_FONT_5X7);
+ oled.print("RTCM:");
+ }
}
- }
+
+ if (rtcmPacketsSent < 100)
+ oled.setCursor(30, 36); // x, y - Give space for two digits
+ else
+ oled.setCursor(28, 36); // x, y - Push towards colon to make room for log icon
+
+ oled.setFont(QW_FONT_8X16); // Set font to type 1: 8x16
+ oled.print(rtcmPacketsSent); // rtcmPacketsSent is controlled in processRTCM()
+
+ paintResets();
}
-//Draw log icon
-//Turn off icon if log file fails to get bigger
-void paintLogging()
+// Show connecting to NTRIP caster service
+void paintConnectingToNtripCaster()
+{
+ int yPos = 18;
+ printTextCenter("Caster", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+
+ int textX = 3;
+ int textY = 33;
+ int textKerning = 6;
+ oled.setFont(QW_FONT_8X16);
+
+ printTextwithKerning("Connecting", textX, textY, textKerning);
+}
+
+// Scroll through IP address. Wipe with spaces both ends.
+void paintIPAddress()
{
- if (online.display == true)
- {
- if (logIncreasing == true)
+ char ipAddress[32];
+ snprintf(ipAddress, sizeof(ipAddress), " %d.%d.%d.%d ",
+#ifdef COMPILE_ETHERNET
+ Ethernet.localIP()[0], Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]);
+#else // COMPILE_ETHERNET
+ 0, 0, 0, 0);
+#endif // COMPILE_ETHERNET
+
+ static uint8_t ipAddressPosition = 0;
+
+ // Check if IP address is all single digits and can be printed without scrolling
+ if (strlen(ipAddress) <= 21)
+ ipAddressPosition = 7;
+
+ // Print seven characters of IP address
+ char printThis[9];
+ snprintf(printThis, sizeof(printThis), "%c%c%c%c%c%c%c", ipAddress[ipAddressPosition + 0],
+ ipAddress[ipAddressPosition + 1], ipAddress[ipAddressPosition + 2], ipAddress[ipAddressPosition + 3],
+ ipAddress[ipAddressPosition + 4], ipAddress[ipAddressPosition + 5], ipAddress[ipAddressPosition + 6]);
+
+ oled.setFont(QW_FONT_5X7); // Set font to smallest
+ oled.setCursor(0, 3);
+ oled.print(printThis);
+
+ ipAddressPosition++; // Increment the print position
+ if (ipAddress[ipAddressPosition + 7] == 0) // Wrap
+ ipAddressPosition = 0;
+}
+
+void displayBaseStart(uint16_t displayTime)
+{
+ if (online.display == true)
{
- //Animate icon to show system running
- if (millis() - lastLoggingIconUpdate > 500)
- {
- lastLoggingIconUpdate = millis();
+ oled.erase();
- if (loggingIconDisplayed == 0)
- oled.drawIcon(64 - Logging_0_Width, 48 - Logging_0_Height, Logging_0_Width, Logging_0_Height, Logging_0, sizeof(Logging_0), true); //Draw the icon
- else if (loggingIconDisplayed == 1)
- oled.drawIcon(64 - Logging_1_Width, 48 - Logging_1_Height, Logging_1_Width, Logging_1_Height, Logging_1, sizeof(Logging_1), true); //Draw the icon
- else if (loggingIconDisplayed == 2)
- oled.drawIcon(64 - Logging_2_Width, 48 - Logging_2_Height, Logging_2_Width, Logging_2_Height, Logging_2, sizeof(Logging_2), true); //Draw the icon
- else if (loggingIconDisplayed == 3)
- oled.drawIcon(64 - Logging_3_Width, 48 - Logging_3_Height, Logging_3_Width, Logging_3_Height, Logging_3, sizeof(Logging_3), true); //Draw the icon
+ uint8_t fontHeight = 15; // Assume fontsize 1
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
+
+ printTextCenter("Base", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ oled.display();
+
+ oled.display();
+
+ delay(displayTime);
+ }
+}
+
+void displayBaseSuccess(uint16_t displayTime)
+{
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 15; // Assume fontsize 1
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
+
+ printTextCenter("Base", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Started", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+
+ oled.display();
+
+ delay(displayTime);
+ }
+}
+
+void displayBaseFail(uint16_t displayTime)
+{
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 15; // Assume fontsize 1
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
+
+ printTextCenter("Base", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Failed", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+
+ oled.display();
+
+ delay(displayTime);
+ }
+}
+
+void displayGNSSFail(uint16_t displayTime)
+{
+ displayMessage("GNSS Failed", displayTime);
+}
+
+void displayNoWiFi(uint16_t displayTime)
+{
+ displayMessage("No WiFi", displayTime);
+}
+
+void displayNoSSIDs(uint16_t displayTime)
+{
+ displayMessage("No SSIDs", displayTime);
+}
+
+void displayAccountExpired(uint16_t displayTime)
+{
+ displayMessage("Account Expired", displayTime);
+}
+
+void displayNotListed(uint16_t displayTime)
+{
+ displayMessage("Not Listed", displayTime);
+}
+
+void displayRoverStart(uint16_t displayTime)
+{
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
+
+ printTextCenter("Rover", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ // printTextCenter("Started", yPos + fontHeight, QW_FONT_8X16, 1, false); //text, y, font type, kerning,
+ // inverted
+
+ oled.display();
+
+ delay(displayTime);
+ }
+}
+
+void displayNoRingBuffer(uint16_t displayTime)
+{
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 8;
+ uint8_t yPos = oled.getHeight() / 3 - fontHeight;
+
+ printTextCenter("Fix GNSS", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Handler", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Buffer Sz", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- loggingIconDisplayed++; //Goto next icon
- loggingIconDisplayed %= 4; //Wrap
- }
+ oled.display();
+
+ delay(displayTime);
}
- }
}
-//Base screen. Display BLE, rover, battery, HorzAcc and SIV
-//Blink SIV until fix
-void paintRoverNoFix()
+void displayRoverSuccess(uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
-
- paintWirelessIcon(); //Top left corner
+ if (online.display == true)
+ {
+ oled.erase();
- paintBaseState(); //Top center
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- paintHorizontalAccuracy();
+ printTextCenter("Rover", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Started", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- paintSIV();
+ oled.display();
- paintLogging();
- }
+ delay(displayTime);
+ }
}
-//Currently identical to RoverNoFix because paintSIV and paintHorizontalAccuracy takes into account system states
-void paintRoverFix()
+void displayRoverFail(uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
-
- paintWirelessIcon(); //Top left corner
+ if (online.display == true)
+ {
+ oled.erase();
- paintBaseState(); //Top center
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- paintHorizontalAccuracy();
+ printTextCenter("Rover", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Failed", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- paintSIV();
+ oled.display();
- paintLogging();
- }
+ delay(displayTime);
+ }
}
-//Currently identical to RoverNoFix because paintSIV and paintHorizontalAccuracy takes into account system states
-void paintRoverRTKFloat()
+void displayAccelFail(uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ if (online.display == true)
+ {
+ oled.erase();
- paintWirelessIcon(); //Top left corner
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- paintBaseState(); //Top center
+ printTextCenter("Accel", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Failed", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- paintHorizontalAccuracy();
+ oled.display();
- paintSIV();
+ delay(displayTime);
+ }
+}
- paintLogging();
- }
+// When user enters serial config menu the display will freeze so show splash while config happens
+void displaySerialConfig()
+{
+ displayMessage("Serial Config", 0);
}
-void paintRoverRTKFix()
+// Display during blocking stop during to prevent screen freeze
+void displayWiFiConnect()
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ displayMessage("WiFi Connect", 0);
+}
- paintWirelessIcon(); //Top left corner
+// When user enters WiFi Config mode from setup, show splash while config happens
+void displayWiFiConfigNotStarted()
+{
+ displayMessage("WiFi Config", 0);
+}
- paintBaseState(); //Top center
+void displayWiFiConfig()
+{
+ int yPos = WiFi_Symbol_Height + 2;
+ int fontHeight = 8;
- paintHorizontalAccuracy();
+ const int displayMaxCharacters =
+ 10; // Characters before pixels start getting cut off. 11 characters can cut off a few pixels.
- paintSIV();
+ printTextCenter("SSID:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- paintLogging();
- }
-}
+ yPos = yPos + fontHeight + 1;
-//Start of base / survey in / NTRIP mode
-//Screen is displayed while we are waiting for horz accuracy to drop to appropriate level
-//Blink crosshair icon until we have we have horz accuracy < user defined level
-void paintBaseTempSettle()
-{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ // 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();
- paintWirelessIcon(); //Top left corner
+ if (ssidDisplayFirstHalf == false)
+ ssidDisplayFirstHalf = true;
+ else
+ ssidDisplayFirstHalf = false;
+ }
- paintBaseState(); //Top center
+ // Convert current SSID to string
+ char mySSID[50] = {'\0'};
- paintHorizontalAccuracy(); //2nd line
+#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());
- paintSIV();
+ // 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");
- paintLogging();
- }
-}
+ else
+ snprintf(mySSID, sizeof(mySSID), "%s", "Error");
+ }
+#else // COMPILE_WIFI
+ snprintf(mySSID, sizeof(mySSID), "%s", "!Compiled");
+#endif // COMPILE_WIFI
-//Survey in is running. Show 3D Mean and elapsed time.
-void paintBaseTempSurveyStarted()
-{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ char mySSIDFront[displayMaxCharacters + 1]; // 1 for null terminator
+ char mySSIDBack[displayMaxCharacters + 1]; // 1 for null terminator
- paintWirelessIcon(); //Top left corner
+ // Trim SSID to a max length
+ strncpy(mySSIDFront, mySSID, displayMaxCharacters);
- paintBaseState(); //Top center
+ if (strlen(mySSID) > displayMaxCharacters)
+ strncpy(mySSIDBack, mySSID + (strlen(mySSID) - displayMaxCharacters), displayMaxCharacters);
+ else
+ strncpy(mySSIDBack, mySSID, displayMaxCharacters);
- oled.setFontType(0);
- oled.setCursor(0, 23); //x, y
- oled.print("Mean:");
+ mySSIDFront[displayMaxCharacters] = '\0';
+ mySSIDBack[displayMaxCharacters] = '\0';
- oled.setCursor(29, 20); //x, y
- oled.setFontType(1);
- if (svinMeanAccuracy < 10.0) //Error check
- oled.print(svinMeanAccuracy, 2);
+ if (ssidDisplayFirstHalf == true)
+ printTextCenter(mySSIDFront, yPos, QW_FONT_5X7, 1, false);
else
- oled.print(">10");
+ printTextCenter(mySSIDBack, yPos, QW_FONT_5X7, 1, false);
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("Time:");
+ yPos = yPos + fontHeight + 3;
+ printTextCenter("IP:", yPos, QW_FONT_5X7, 1, false);
- oled.setCursor(30, 36); //x, y
- oled.setFontType(1);
- if (svinObservationTime < 1000) //Error check
- oled.print(svinObservationTime);
- else
- oled.print("0");
+ yPos = yPos + fontHeight + 1;
- paintLogging();
- }
-}
+#ifdef COMPILE_AP
+ IPAddress myIpAddress;
+ if (WiFi.getMode() == WIFI_AP)
+ myIpAddress = WiFi.softAPIP();
+ else
+ myIpAddress = WiFi.localIP();
-//Show transmission of RTCM packets
-void paintBaseTempTransmitting()
-{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ // Convert to string
+ char myIP[20] = {'\0'};
+ snprintf(myIP, sizeof(myIP), "%d.%d.%d.%d", myIpAddress[0], myIpAddress[1], myIpAddress[2], myIpAddress[3]);
- paintWirelessIcon(); //Top left corner
+ char myIPFront[displayMaxCharacters + 1]; // 1 for null terminator
+ char myIPBack[displayMaxCharacters + 1]; // 1 for null terminator
- paintBaseState(); //Top center
+ strncpy(myIPFront, myIP, displayMaxCharacters);
- int textX = 1;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Xmitting", textX, textY, textKerning);
+ if (strlen(myIP) > displayMaxCharacters)
+ strncpy(myIPBack, myIP + (strlen(myIP) - displayMaxCharacters), displayMaxCharacters);
+ else
+ strncpy(myIPBack, myIP, displayMaxCharacters);
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ myIPFront[displayMaxCharacters] = '\0';
+ myIPBack[displayMaxCharacters] = '\0';
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
+ if (ssidDisplayFirstHalf == true)
+ printTextCenter(myIPFront, yPos, QW_FONT_5X7, 1, false);
else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ printTextCenter(myIPBack, yPos, QW_FONT_5X7, 1, false);
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+#else // COMPILE_AP
+ printTextCenter("!Compiled", yPos, QW_FONT_5X7, 1, false);
+#endif // COMPILE_AP
+}
- paintLogging();
- }
+// When user does a factory reset, let us know
+void displaySytemReset()
+{
+ displayMessage("Factory Reset", 0);
}
-//Show transmission of RTCM packets
-//Blink WiFi icon
-void paintBaseTempWiFiStarted()
+void displaySurveyStart(uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ if (online.display == true)
+ {
+ oled.erase();
- paintWirelessIcon(); //Top left corner
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- paintBaseState(); //Top center
+ printTextCenter("Survey", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ // printTextCenter("Started", yPos + fontHeight, QW_FONT_8X16, 1, false); //text, y, font type, kerning,
+ // inverted
- int textX = 1;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Xmitting", textX, textY, textKerning);
+ oled.display();
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ delay(displayTime);
+ }
+}
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
- else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+void displaySurveyStarted(uint16_t displayTime)
+{
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+ printTextCenter("Survey", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Started", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- paintLogging();
- }
+ oled.display();
+
+ delay(displayTime);
+ }
}
-//Show transmission of RTCM packets
-//Solid WiFi icon
-//This is identical to paintBaseTempWiFiStarted
-void paintBaseTempWiFiConnected()
+// If the SD card is detected but is not formatted correctly, display warning
+void displaySDFail(uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
-
- paintWirelessIcon(); //Top left corner
+ if (online.display == true)
+ {
+ oled.erase();
- paintBaseState(); //Top center
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- int textX = 1;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Xmitting", textX, textY, textKerning);
+ printTextCenter("Format", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("SD Card", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ oled.display();
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
- else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ delay(displayTime);
+ }
+}
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+// Draw a frame at outside edge
+void drawFrame()
+{
+ // Init and draw box at edge to see screen alignment
+ int xMax = 63;
+ int yMax = 47;
+ oled.line(0, 0, xMax, 0); // Top
+ oled.line(0, 0, 0, yMax); // Left
+ oled.line(0, yMax, xMax, yMax); // Bottom
+ oled.line(xMax, 0, xMax, yMax); // Right
+}
- paintLogging();
- }
+void displayForcedFirmwareUpdate()
+{
+ displayMessage("Forced Update", 0);
}
-//Show connecting to caster service
-//Solid WiFi icon
-void paintBaseTempCasterStarted()
+void displayFirmwareUpdateProgress(int percentComplete)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ // Update the display if connected
+ if (online.display == true)
+ {
+ oled.erase(); // Clear the display's internal buffer
+
+ int yPos = 3;
+ int fontHeight = 8;
- paintWirelessIcon(); //Top left corner
+ printTextCenter("Firmware", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- paintBaseState(); //Top center
+ yPos = yPos + fontHeight + 1;
+ printTextCenter("Update", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- int textX = 11;
- int textY = 17;
- int textKerning = 8;
+ yPos = yPos + fontHeight + 3;
+ char temp[50];
+ snprintf(temp, sizeof(temp), "%d%%", percentComplete);
+ printTextCenter(temp, yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"Caster", textX, textY, textKerning);
+ oled.display(); // Push internal buffer to display
+ }
+}
- textX = 3;
- textY = 33;
- textKerning = 6;
- oled.setFontType(1);
+void displayEventMarked(uint16_t displayTime)
+{
+ displayMessage("Event Marked", displayTime);
+}
- printTextwithKerning((char*)"Connecting", textX, textY, textKerning);
- }
+void displayNoLogging(uint16_t displayTime)
+{
+ displayMessage("No Logging", displayTime);
}
-//Show transmission of RTCM packets to caster service
-//Solid WiFi icon
-void paintBaseTempCasterConnected()
+void displayMarked(uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ displayMessage("Marked", displayTime);
+}
- paintWirelessIcon(); //Top left corner
+void displayMarkFailure(uint16_t displayTime)
+{
+ displayMessage("Mark Failure", displayTime);
+}
- paintBaseState(); //Top center
+void displayNotMarked(uint16_t displayTime)
+{
+ displayMessage("Not Marked", displayTime);
+}
- int textX = 4;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Casting", textX, textY, textKerning);
+// Show 'Loading Home2' profile identified
+// Profiles may not be sequential (user might have empty profile #2, but filled #3) so we load the profile unit, not the
+// number
+void paintProfile(uint8_t profileUnit)
+{
+ char profileMessage[20]; //'Loading HomeStar' max of ~18 chars
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ char profileName[8 + 1];
+ if (getProfileNameFromUnit(profileUnit, profileName, sizeof(profileName)) ==
+ true) // Load the profile name, limited to 8 chars
+ {
+ settings.updateZEDSettings = true; // When this profile is loaded next, force system to update ZED settings.
+ recordSystemSettings(); // Before switching, we need to record the current settings to LittleFS and SD
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
- else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ // Lookup profileNumber based on unit
+ uint8_t profileNumber = getProfileNumberFromUnit(profileUnit);
+ recordProfileNumber(profileNumber); // Update internal settings with user's choice, mark unit for config update
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+ log_d("Going to profile number %d from unit %d, name '%s'", profileNumber, profileUnit, profileName);
- paintLogging();
- }
+ snprintf(profileMessage, sizeof(profileMessage), "Loading %s", profileName);
+ displayMessage(profileMessage, 2000);
+ ESP.restart(); // Profiles require full restart to take effect
+ }
}
-//Show transmission of RTCM packets
-void paintBaseFixedNotStarted()
+// Display unit self-tests until user presses a button to exit
+// Allows operator to check:
+// Display alignment
+// Internal connections to: SD, Accel, Fuel guage, GNSS
+// External connections: Loop back test on DATA
+void paintSystemTest()
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ static uint8_t systemTestDisplayNumber = 0; // Tracks which test screen we're looking at.
+ static unsigned long systemTestDisplayTime = millis(); // Timestamp for swapping the graphic during testing
+
+ if (online.display == true)
+ {
+ // Main info display
+ if (systemTestDisplayNumber == 0)
+ {
+ int xOffset = 2;
+ int yOffset = 2;
+
+ int charHeight = 7;
+
+ drawFrame(); // Outside edge
+
+ // Test SD, accel, batt, GNSS, mux
+ oled.setFont(QW_FONT_5X7); // Set font to smallest
+
+ oled.setCursor(xOffset, yOffset + (0 * charHeight)); // x, y
+ oled.print("ZV:");
+ oled.print(zedFirmwareVersionInt);
+
+ // ZED-F9P goes to 150
+ if (zedModuleType == PLATFORM_F9P)
+ {
+ if (zedFirmwareVersionInt < 150)
+ oled.print("-FAI");
+ else
+ oled.print("-OK");
+ }
+
+ // ZED-F9R goes to 130
+ else if (zedModuleType == PLATFORM_F9R)
+ {
+ if (zedFirmwareVersionInt < 130)
+ oled.print("-FAI");
+ else
+ oled.print("-OK");
+ }
+
+ oled.setCursor(xOffset, yOffset + (1 * charHeight)); // 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 (productVariant != REFERENCE_STATION)
+ {
+ oled.setCursor(xOffset, yOffset + (2 * charHeight)); // x, y
+ oled.print("Batt:");
+ if (online.battery == true)
+ oled.print("OK");
+ else
+ oled.print("FAIL");
+ }
+
+ //Check for satellites in view
+ oled.setCursor(xOffset, yOffset + (3 * charHeight)); // x, y
+ oled.print("SIV:");
+ if (online.gnss == true)
+ {
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, eventTriggerReceived
+
+ int satsInView = numSV;
+ if (satsInView > 5)
+ {
+ oled.print("OK");
+ oled.print("/");
+ oled.print(satsInView);
+ }
+ else
+ oled.print("FAIL");
+ }
+ else
+ oled.print("FAIL");
+
+ if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS_PLUS || productVariant == RTK_FACET ||
+ productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ oled.setCursor(xOffset, yOffset + (4 * charHeight)); // x, y
+ oled.print("Mux:");
+
+ // Set mux to channel 3 and toggle pin and verify with loop back jumper wire inserted by test technician
+
+ setMuxport(MUX_ADC_DAC); // Set mux to DAC so we can toggle back/forth
+ pinMode(pin_dac26, OUTPUT);
+ pinMode(pin_adc39, INPUT_PULLUP);
+
+ digitalWrite(pin_dac26, HIGH);
+ if (digitalRead(pin_adc39) == HIGH)
+ {
+ digitalWrite(pin_dac26, LOW);
+ if (digitalRead(pin_adc39) == LOW)
+ oled.print("OK");
+ else
+ oled.print("FAIL");
+ }
+ else
+ oled.print("FAIL");
+ }
+
+ // Get the last two digits of MAC
+ char macAddress[5];
+ const uint8_t *rtkMacAddress = getMacAddress();
+ snprintf(macAddress, sizeof(macAddress), "%02X%02X", rtkMacAddress[4], rtkMacAddress[5]);
+
+ // Display MAC address
+ oled.setCursor(xOffset, yOffset + (5 * charHeight)); // x, y
+ oled.print(macAddress);
+ oled.print(":");
+
+ // Verify the ESP UART2 can communicate TX/RX to ZED UART1
+ if ((USE_I2C_GNSS) && (zedUartPassed == false))
+ {
+ systemPrintln("GNSS test");
+
+ setMuxport(MUX_UBLOX_NMEA); // Set mux to UART so we can debug over data port
+ delay(20);
+
+ // Clear out buffer before starting
+ while (serialGNSS.available())
+ serialGNSS.read();
+ serialGNSS.flush();
+
+ SFE_UBLOX_GNSS_SERIAL myGNSS;
+
+ // begin() attempts 3 connections
+ if (myGNSS.begin(serialGNSS) == true)
+ {
+
+ zedUartPassed = true;
+ oled.print("OK");
+ }
+ else
+ oled.print("FAIL");
+ }
+ else
+ oled.print("OK");
+ } // End display 0
+
+ // Display LBand Info
+ if (systemTestDisplayNumber == 1)
+ {
+ int xOffset = 2;
+ int yOffset = 2;
+
+ int charHeight = 7;
+
+ drawFrame(); // Outside edge
+
+ // Test L-Band
+ oled.setFont(QW_FONT_5X7); // Set font to smallest
- paintWirelessIcon(); //Top left corner
+ oled.setCursor(xOffset, yOffset + (0 * charHeight)); // x, y
+ oled.print("LBand:");
+ if (online.lband == true)
+ oled.print("OK");
+ else
+ oled.print("FAIL");
+ } // End display 1
- paintBaseState(); //Top center
- }
+ if (productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ // Toggle between two displays
+ if (millis() - systemTestDisplayTime > 3000)
+ {
+ systemTestDisplayTime = millis();
+ systemTestDisplayNumber++;
+ systemTestDisplayNumber %= 2;
+ }
+ }
+ }
}
-//Show transmission of RTCM packets
-void paintBaseFixedTransmitting()
-{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+// Globals but only used for Bubble Level
+double averagedRoll = 0.0;
+double averagedPitch = 0.0;
- paintWirelessIcon(); //Top left corner
+// A bubble level
+void paintBubbleLevel()
+{
+ if (online.accelerometer == true)
+ {
+ forceDisplayUpdate = true; // Update the display as quickly as possible
- paintBaseState(); //Top center
+ getAngles();
- int textX = 1;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Xmitting", textX, textY, textKerning);
+ // Draw dot in middle
+ oled.pixel(oled.getWidth() / 2, oled.getHeight() / 2);
+ oled.pixel(oled.getWidth() / 2 + 1, oled.getHeight() / 2);
+ oled.pixel(oled.getWidth() / 2, oled.getHeight() / 2 + 1);
+ oled.pixel(oled.getWidth() / 2 + 1, oled.getHeight() / 2 + 1);
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ // Draw circle relative to dot
+ const int radiusLarge = 10;
+ const int radiusSmall = 4;
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
+ oled.circle(oled.getWidth() / 2 - averagedPitch, oled.getHeight() / 2 + averagedRoll, radiusLarge);
+ oled.circle(oled.getWidth() / 2 - averagedPitch, oled.getHeight() / 2 + averagedRoll, radiusSmall);
+ }
else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ {
+ displayAccelFail(0);
+ }
+}
+
+void getAngles()
+{
+ if (online.accelerometer == true)
+ {
+ averagedRoll = 0.0;
+ averagedPitch = 0.0;
+ const int avgAmount = 16;
+
+ // Take an average readings
+ for (int reading = 0; reading < avgAmount; reading++)
+ {
+ while (accel.available() == false)
+ delay(1);
+
+ float accelX = 0;
+ float accelY = 0;
+ float accelZ = 0;
+
+ // Express Accel orientation is different from Facet
+ if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS_PLUS)
+ {
+ accelX = accel.getX();
+ accelZ = accel.getY();
+ accelY = accel.getZ();
+ accelZ *= -1.0;
+ accelX *= -1.0;
+ }
+ else if (productVariant == RTK_FACET || productVariant == RTK_FACET_LBAND ||
+ productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ accelZ = accel.getX();
+ accelX = accel.getY();
+ accelY = accel.getZ();
+ accelZ *= -1.0;
+ accelY *= -1.0;
+ accelX *= -1.0;
+ }
+
+ double roll = atan2(accelY, accelZ) * 57.3;
+ double pitch = atan2((-accelX), sqrt(accelY * accelY + accelZ * accelZ)) * 57.3;
+
+ averagedRoll += roll;
+ averagedPitch += pitch;
+ }
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+ averagedRoll /= (float)avgAmount;
+ averagedPitch /= (float)avgAmount;
- paintLogging();
- }
+ // Avoid -0 since we're not printing the decimal portion
+ if (averagedRoll < 0.5 && averagedRoll > -0.5)
+ averagedRoll = 0;
+ if (averagedPitch < 0.5 && averagedPitch > -0.5)
+ averagedPitch = 0;
+ }
}
-//Show transmission of RTCM packets
-//Blink WiFi icon
-void paintBaseFixedWiFiStarted()
+// Display the setup profiles
+void paintDisplaySetupProfile(const char *firstState)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ int index;
+ int itemsDisplayed;
+ char profileName[8 + 1];
- paintWirelessIcon(); //Top left corner
+ // Display the first state if this is the first profile
+ itemsDisplayed = 0;
+ if (displayProfile == 0)
+ {
+ printTextCenter(firstState, 12 * itemsDisplayed, QW_FONT_8X16, 1, false);
+ itemsDisplayed++;
+ }
- paintBaseState(); //Top center
+ // Display Bubble if this is the second profile
+ if (displayProfile <= 1)
+ {
+ printTextCenter("Bubble", 12 * itemsDisplayed, QW_FONT_8X16, 1, false);
+ itemsDisplayed++;
+ }
- int textX = 1;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Xmitting", textX, textY, textKerning);
+ // Display Config if this is the third profile
+ if (displayProfile <= 2)
+ {
+ printTextCenter("Config", 12 * itemsDisplayed, QW_FONT_8X16, 1, false);
+ itemsDisplayed++;
+ }
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ // displayProfile itemsDisplayed index
+ // 0 3 0
+ // 1 2 0
+ // 2 1 0
+ // 3 0 0
+ // 4 0 1
+ // 5 0 2
+ // n >= 3 0 n - 3
+
+ // Display the profile names
+ for (index = (displayProfile >= 3) ? displayProfile - 3 : 0; itemsDisplayed < 4; itemsDisplayed++)
+ {
+ // Lookup next available profile, limit to 8 characters
+ getProfileNameFromUnit(index, profileName, sizeof(profileName));
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
- else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ profileName[6] = 0; // Shorten profileName to 6 characters
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+ char miniProfileName[16] = {0};
+ snprintf(miniProfileName, sizeof(miniProfileName), "%d_%s", index, profileName); // Prefix with index #
- paintLogging();
- }
+ printTextCenter(miniProfileName, 12 * itemsDisplayed, QW_FONT_8X16, 1, itemsDisplayed == 3);
+ index++;
+ }
}
-//Show transmission of RTCM packets
-//Solid WiFi icon
-//This is identical to paintBaseTempWiFiStarted
-void paintBaseFixedWiFiConnected()
+// Show different menu 'buttons' to allow user to pause on one to select it
+void paintDisplaySetup()
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ if (zedModuleType == PLATFORM_F9P)
+ {
+ if (setupState == STATE_MARK_EVENT)
+ {
+ if (productVariant == REFERENCE_STATION)
+ {
+ // setupState defaults to STATE_MARK_EVENT, which is not a valid state for the Ref Stn.
+ // It will be corrected by ButtonCheckTask. Until then, display but don't highlight an option.
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("NTP", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Cfg Eth", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, true); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, true); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_ROVER_NOT_STARTED)
+ {
+ if (productVariant == REFERENCE_STATION)
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, true);
+ printTextCenter("NTP", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Cfg Eth", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, true);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, true);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_BASE_NOT_STARTED)
+ {
+ if (productVariant == REFERENCE_STATION)
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, true); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("NTP", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Cfg Eth", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, true);
+ printTextCenter("Bubble", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, true);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_NTPSERVER_NOT_STARTED)
+ {
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("NTP", 12 * 2, QW_FONT_8X16, 1, true);
+ printTextCenter("Cfg Eth", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_BUBBLE_LEVEL)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ // We should never get here, but just in case
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ }
+ else if (setupState == STATE_CONFIG_VIA_ETH_NOT_STARTED)
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("NTP", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Cfg Eth", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else if (setupState == STATE_WIFI_CONFIG_NOT_STARTED)
+ {
+ if (productVariant == REFERENCE_STATION)
+ {
+ printTextCenter("Rover", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("NTP", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Cfg Eth", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("CfgWiFi", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else if (online.accelerometer)
+ {
+ printTextCenter("Rover", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ }
+
+ // If we are on an L-Band unit, display GetKeys option
+ else if (setupState == STATE_KEYS_NEEDED)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("GetKeys", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ printTextCenter("Rover", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("GetKeys", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ }
+
+ else if (setupState == STATE_ESPNOW_PAIRING_NOT_STARTED)
+ {
+ if (productVariant == REFERENCE_STATION)
+ {
+ printTextCenter("NTP", 12 * 0, QW_FONT_8X16, 1, false); // string, y, font type, kerning, inverted
+ printTextCenter("Cfg Eth", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("CfgWiFi", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else if (productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ // If we are on an L-Band unit, scroll GetKeys option
+ if (online.accelerometer)
+ {
+ printTextCenter("Bubble", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("GetKeys", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("GetKeys", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ }
+ else if (online.accelerometer)
+ {
+ printTextCenter("Base", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ printTextCenter("Rover", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Base", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ }
+
+ else if (setupState == STATE_PROFILE)
+ paintDisplaySetupProfile("Base");
+ } // end type F9P
+ else if (zedModuleType == PLATFORM_F9R)
+ {
+ if (setupState == STATE_MARK_EVENT)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, true); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, true); // string, y, font type, kerning, inverted
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_ROVER_NOT_STARTED)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, true);
+ printTextCenter("Bubble", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, true);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_BUBBLE_LEVEL)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 2, QW_FONT_8X16, 1, true);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ else
+ {
+ // We should never get here, but just in case
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, true);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_WIFI_CONFIG_NOT_STARTED)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, true);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, false);
+ }
+ }
+ else if (setupState == STATE_ESPNOW_PAIRING_NOT_STARTED)
+ {
+ if (online.accelerometer)
+ {
+ printTextCenter("Rover", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Bubble", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ else
+ {
+ printTextCenter("Mark", 12 * 0, QW_FONT_8X16, 1, false);
+ printTextCenter("Rover", 12 * 1, QW_FONT_8X16, 1, false);
+ printTextCenter("Config", 12 * 2, QW_FONT_8X16, 1, false);
+ printTextCenter("E-Pair", 12 * 3, QW_FONT_8X16, 1, true);
+ }
+ }
+ else if (setupState == STATE_PROFILE)
+ paintDisplaySetupProfile("Rover");
+ } // end type F9R
+}
- paintWirelessIcon(); //Top left corner
+// Given text, and location, print text center of the screen
+void printTextCenter(const char *text, uint8_t yPos, QwiicFont &fontType, uint8_t kerning,
+ bool highlight) // text, y, font type, kearning, inverted
+{
+ oled.setFont(fontType);
+ oled.setDrawMode(grROPXOR);
- paintBaseState(); //Top center
+ uint8_t fontWidth = fontType.width;
+ if (fontWidth == 8)
+ fontWidth = 7; // 8x16, but widest character is only 7 pixels.
- int textX = 1;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Xmitting", textX, textY, textKerning);
+ uint8_t xStart = (oled.getWidth() / 2) - ((strlen(text) * (fontWidth + kerning)) / 2) + 1;
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+ uint8_t xPos = xStart;
+ for (int x = 0; x < strlen(text); x++)
+ {
+ oled.setCursor(xPos, yPos);
+ oled.print(text[x]);
+ xPos += fontWidth + kerning;
+ }
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
- else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ if (highlight) // Draw a box, inverted over text
+ {
+ uint8_t textPixelWidth = strlen(text) * (fontWidth + kerning);
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+ // Error check
+ int xBoxStart = xStart - 5;
+ if (xBoxStart < 0)
+ xBoxStart = 0;
+ int xBoxEnd = textPixelWidth + 9;
+ if (xBoxEnd > oled.getWidth() - 1)
+ xBoxEnd = oled.getWidth() - 1;
- paintLogging();
- }
+ oled.rectangleFill(xBoxStart, yPos, xBoxEnd, 12, 1); // x, y, width, height, color
+ }
}
-//Show connecting to caster service
-//Solid WiFi icon
-void paintBaseFixedCasterStarted()
+// Given a message (one or two words) display centered
+void displayMessage(const char *message, uint16_t displayTime)
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ if (online.display == true)
+ {
+ char temp[21];
+ uint8_t fontHeight = 15; // Assume fontsize 1
+
+ // Count words based on spaces
+ uint8_t wordCount = 0;
+ strncpy(temp, message, sizeof(temp) - 1); // strtok modifies the message so make copy
+ char *token = strtok(temp, " ");
+ while (token != nullptr)
+ {
+ wordCount++;
+ token = strtok(nullptr, " ");
+ }
- paintWirelessIcon(); //Top left corner
+ uint8_t yPos = (oled.getHeight() / 2) - (fontHeight / 2);
+ if (wordCount == 2)
+ yPos -= (fontHeight / 2);
- paintBaseState(); //Top center
+ oled.erase();
- int textX = 11;
- int textY = 18;
- int textKerning = 8;
+ // drawFrame();
- printTextwithKerning((char*)"Caster", textX, textY, textKerning);
+ strncpy(temp, message, sizeof(temp) - 1);
+ token = strtok(temp, " ");
+ while (token != nullptr)
+ {
+ printTextCenter(token, yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ token = strtok(nullptr, " ");
+ yPos += fontHeight;
+ }
- textX = 3;
- textY = 33;
- textKerning = 6;
- oled.setFontType(1);
+ oled.display();
- printTextwithKerning((char*)"Connecting", textX, textY, textKerning);
- }
+ delay(displayTime);
+ }
}
-//Show transmission of RTCM packets to caster service
-//Solid WiFi icon
-void paintBaseFixedCasterConnected()
+void paintResets()
{
- if (online.display == true)
- {
- paintBatteryLevel(); //Top right corner
+ if (settings.enableResetDisplay == true)
+ {
+ oled.setFont(QW_FONT_5X7); // Small font
+ oled.setCursor(16 + (8 * 3) + 7, 38); // x, y
- paintWirelessIcon(); //Top left corner
+ if (settings.enablePrintBufferOverrun == false)
+ oled.print(settings.resetCount);
+ else
+ oled.print(settings.resetCount + bufferOverruns);
+ }
+}
- paintBaseState(); //Top center
+// Wrapper to avoid needing to pass width/height data twice
+void displayBitmap(uint8_t x, uint8_t y, uint8_t imageWidth, uint8_t imageHeight, const uint8_t *imageData)
+{
+ oled.bitmap(x, y, x + imageWidth, y + imageHeight, (uint8_t *)imageData, imageWidth, imageHeight);
+}
- int textX = 4;
- int textY = 17;
- int textKerning = 8;
- oled.setFontType(1);
- printTextwithKerning((char*)"Casting", textX, textY, textKerning);
+void displayKeysUpdated()
+{
+ displayMessage("Keys Updated", 2000);
+}
- oled.setCursor(0, 39); //x, y
- oled.setFontType(0);
- oled.print("RTCM:");
+void paintKeyDaysRemaining(int daysRemaining, uint16_t displayTime)
+{
+ // 28 days
+ // until PP
+ // keys expire
- if (rtcmPacketsSent < 100)
- oled.setCursor(30, 36); //x, y - Give space for two digits
- else
- oled.setCursor(28, 36); //x, y - Push towards colon to make room for log icon
+ if (online.display == true)
+ {
+ oled.erase();
- oled.setFontType(1); //Set font to type 1: 8x16
- oled.print(rtcmPacketsSent); //rtcmPacketsSent is controlled in processRTCM()
+ if (daysRemaining < 0)
+ daysRemaining = 0;
- paintLogging();
- }
-}
+ int rightSideStart = 24; // Force the small text to rightside of screen
-void displayBaseStart(uint16_t displayTime)
-{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ oled.setFont(QW_FONT_LARGENUM);
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ String days = String(daysRemaining);
+ int dayTextWidth = oled.getStringWidth(days);
- int textX = 18;
- int textY = 10;
- int textKerning = 8;
+ int largeTextX = (rightSideStart / 2) - (dayTextWidth / 2); // Center point for x coord
- printTextwithKerning((char*)"Base", textX, textY, textKerning);
+ oled.setCursor(largeTextX, 0);
+ oled.print(daysRemaining);
- oled.display();
+ oled.setFont(QW_FONT_5X7);
- delay(displayTime);
- }
-}
+ int x = ((oled.getWidth() - rightSideStart) / 2) + rightSideStart; // Center point for x coord
+ int y = 0;
+ int fontHeight = 10;
+ int textX;
-void displayBaseSuccess(uint16_t displayTime)
-{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ textX = x - (oled.getStringWidth("days") / 2); // Starting point of text
+ oled.setCursor(textX, y);
+ oled.print("Days");
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ y += fontHeight;
+ textX = x - (oled.getStringWidth("Until") / 2);
+ oled.setCursor(textX, y);
+ oled.print("Until");
- int textX = 18;
- int textY = 10;
- int textKerning = 8;
+ y += fontHeight;
+ textX = x - (oled.getStringWidth("PP") / 2);
+ oled.setCursor(textX, y);
+ oled.print("PP");
- printTextwithKerning((char*)"Base", textX, textY, textKerning);
+ y += fontHeight;
+ textX = x - (oled.getStringWidth("Keys") / 2);
+ oled.setCursor(textX, y);
+ oled.print("Keys");
- textX = 5;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ y += fontHeight;
+ textX = x - (oled.getStringWidth("Expire") / 2);
+ oled.setCursor(textX, y);
+ oled.print("Expire");
- printTextwithKerning((char*)"Started", textX, textY, textKerning);
- oled.display();
+ oled.display();
- delay(displayTime);
- }
+ delay(displayTime);
+ }
}
-void displayBaseFail(uint16_t displayTime)
+void paintKeyWiFiFail(uint16_t displayTime)
{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ // PP
+ // Update
+ // Failed
+ // No WiFi
+
+ if (online.display == true)
+ {
+ oled.erase();
+
+ oled.setFont(QW_FONT_8X16);
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ int y = 0;
+ int fontHeight = 13;
- int textX = 18;
- int textY = 10;
- int textKerning = 8;
+ printTextCenter("PP", y, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"Base", textX, textY, textKerning);
+ y += fontHeight;
+ printTextCenter("Update", y, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- textX = 10;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ y += fontHeight;
+ printTextCenter("Failed", y, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"Failed", textX, textY, textKerning);
- oled.display();
+ y += fontHeight + 1;
+ printTextCenter("No WiFi", y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- delay(displayTime);
- }
+ oled.display();
+
+ delay(displayTime);
+ }
}
-void displayGNSSFail(uint16_t displayTime)
+void paintNtripWiFiFail(uint16_t displayTime, bool Client)
{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ // NTRIP
+ // Client or Server
+ // Failed
+ // No WiFi
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ if (online.display == true)
+ {
+ oled.erase();
- int textX = 18;
- int textY = 10;
- int textKerning = 8;
+ int y = 0;
+ int fontHeight = 13;
- printTextwithKerning((char*)"GNSS", textX, textY, textKerning);
+ const char *string = Client ? "Client" : "Server";
- textX = 10;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ printTextCenter("NTRIP", y, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"Failed", textX, textY, textKerning);
- oled.display();
+ y += fontHeight;
+ printTextCenter(string, y, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- delay(displayTime);
- }
-}
+ y += fontHeight;
+ printTextCenter("Failed", y, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
-void displayRoverStart(uint16_t displayTime)
-{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ y += fontHeight + 1;
+ printTextCenter("No WiFi", y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ oled.display();
- int textX = 14;
- int textY = 10;
- int textKerning = 8;
+ delay(displayTime);
+ }
+}
- printTextwithKerning((char*)"Rover", textX, textY, textKerning);
+void paintKeysExpired()
+{
+ displayMessage("Keys Expired", 4000);
+}
- oled.display();
+void paintLBandConfigure()
+{
+ displayMessage("L-Band Config", 0);
+}
- delay(displayTime);
- }
+void paintGettingKeys()
+{
+ displayMessage("Getting Keys", 0);
}
-void displayRoverSuccess(uint16_t displayTime)
+void paintGettingEthernetIP()
{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ displayMessage("Getting IP", 0);
+}
- oled.setCursor(21, 13);
- oled.setFontType(1);
+// If an L-Band is indoors without reception, we have a ~2s wait for the RTC to come online
+// Display something while we wait
+void paintRTCWait()
+{
+ displayMessage("RTC Wait", 0);
+}
- int textX = 14;
- int textY = 10;
- int textKerning = 8;
+void paintKeyProvisionFail(uint16_t displayTime)
+{
+ // Whitelist Error
- printTextwithKerning((char*)"Rover", textX, textY, textKerning);
+ // ZTP
+ // Failed
+ // ID:
+ // 10chars
- textX = 5;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ if (online.display == true)
+ {
+ oled.erase();
- printTextwithKerning((char*)"Started", textX, textY, textKerning);
- oled.display();
+ oled.setFont(QW_FONT_5X7);
- delay(displayTime);
- }
-}
+ int y = 0;
+ int fontHeight = 8;
-void displayRoverFail(uint16_t displayTime)
-{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ printTextCenter("ZTP", y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+
+ y += fontHeight;
+ printTextCenter("Failed", y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ y += fontHeight;
+ printTextCenter("ID:", y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- int textX = 14;
- int textY = 10;
- int textKerning = 8;
+ // The MAC address is 12 characters long so we have to split it onto two lines
+ char hardwareID[13];
+ const uint8_t *rtkMacAddress = getMacAddress();
- printTextwithKerning((char*)"Rover", textX, textY, textKerning);
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X", rtkMacAddress[0], rtkMacAddress[1], rtkMacAddress[2]);
+ y += fontHeight;
+ printTextCenter(hardwareID, y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- textX = 10;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X", rtkMacAddress[3], rtkMacAddress[4], rtkMacAddress[5]);
+ y += fontHeight;
+ printTextCenter(hardwareID, y, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"Failed", textX, textY, textKerning);
- oled.display();
+ oled.display();
- delay(displayTime);
- }
+ delay(displayTime);
+ }
}
-//When user enter serial config menu the display will freeze so show splash while config happens
-void displaySerialConfig()
+// Show screen while ESP-Now is pairing
+void paintEspNowPairing()
{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ displayMessage("ESP-Now Pairing", 0);
+}
+void paintEspNowPaired()
+{
+ displayMessage("ESP-Now Paired", 2000);
+}
- oled.setCursor(21, 13);
- oled.setFontType(1);
+void displayNtpStart(uint16_t displayTime)
+{
+ if (online.display == true)
+ {
+ oled.erase();
- int textX = 10;
- int textY = 10;
- int textKerning = 8;
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- printTextwithKerning((char*)"Serial", textX, textY, textKerning);
+ printTextCenter("NTP", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- textX = 10;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ oled.display();
- printTextwithKerning((char*)"Config", textX, textY, textKerning);
- oled.display();
- }
+ delay(displayTime);
+ }
}
-void displaySurveyStart(uint16_t displayTime)
+void displayNtpStarted(uint16_t displayTime)
{
- if (online.display == true)
- {
- oled.clear(PAGE);
-
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ if (online.display == true)
+ {
+ oled.erase();
- int textX = 10;
- int textY = 10;
- int textKerning = 8;
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- printTextwithKerning((char*)"Survey", textX, textY, textKerning);
+ printTextCenter("NTP", yPos, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Started", yPos + fontHeight, QW_FONT_8X16, 1, false); // text, y, font type, kerning, inverted
- oled.display();
+ oled.display();
- delay(displayTime);
- }
+ delay(displayTime);
+ }
}
-void displaySurveyStarted(uint16_t displayTime)
+void displayNtpNotReady(uint16_t displayTime)
{
- if (online.display == true)
- {
- oled.clear(PAGE);
-
- oled.setCursor(21, 13);
- oled.setFontType(1);
-
- int textX = 10;
- int textY = 10;
- int textKerning = 8;
+ if (online.display == true)
+ {
+ oled.erase();
- printTextwithKerning((char*)"Survey", textX, textY, textKerning);
+ uint8_t fontHeight = 8;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- textX = 6;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ printTextCenter("Ethernet", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Not Ready", yPos + fontHeight, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"Started", textX, textY, textKerning);
- oled.display();
+ oled.display();
- delay(displayTime);
- }
+ delay(displayTime);
+ }
}
-//If the SD card is detected but is not formatted correctly, display warning
-void displaySDFail(uint16_t displayTime)
+void displayNTPFail(uint16_t displayTime)
{
- if (online.display == true)
- {
- oled.clear(PAGE);
-
- oled.setCursor(21, 13);
- oled.setFontType(1);
-
- int textX = 11;
- int textY = 10;
- int textKerning = 8;
+ if (online.display == true)
+ {
+ oled.erase();
- printTextwithKerning((char*)"Format", textX, textY, textKerning);
+ uint8_t fontHeight = 8;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
- textX = 7;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ printTextCenter("NTP", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ printTextCenter("Failed", yPos + fontHeight, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- printTextwithKerning((char*)"SD Card", textX, textY, textKerning);
- oled.display();
+ oled.display();
- delay(displayTime);
- }
+ delay(displayTime);
+ }
}
-//Draw a frame at outside edge
-void drawFrame()
+void displayConfigViaEthNotStarted(uint16_t displayTime)
{
- //Init and draw box at edge to see screen alignment
- int xMax = 63;
- int yMax = 47;
- oled.line(0, 0, xMax, 0); //Top
- oled.line(0, 0, 0, yMax); //Left
- oled.line(0, yMax, xMax, yMax); //Bottom
- oled.line(xMax, 0, xMax, yMax); //Right
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 8;
+ uint8_t yPos = fontHeight;
+
+ printTextCenter("Configure", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Via", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Ethernet", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Restart", yPos, QW_FONT_5X7, 1, true); // text, y, font type, kerning, inverted
+
+ oled.display();
+
+ delay(displayTime);
+ }
}
-//Display unit self-tests until user presses a button to exit
-//Allows operator to check:
-// Display alignment
-// Internal connections to: SD, Accel, Fuel guage, GNSS
-// External connections: Loop back test on DATA
-void displayTest()
+void displayConfigViaEthStarted(uint16_t displayTime)
{
- if (online.display == true)
- {
- int xOffset = 2;
- int yOffset = 2;
-
- int charHeight = 7;
-
- inTestMode = true; //Reroutes bluetooth bytes
-
- char macAddress[5];
- sprintf(macAddress, "%02X%02X", unitMACAddress[4], unitMACAddress[5]);
-
- //Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted
- //even if there is no GPS fix. We use it to test serial output.
- i2cGNSS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_UART2, 1); //Enable message every second
-
- oled.clear(PAGE); // Clear the display's internal memory
-
- drawFrame(); //Outside edge
-
- oled.setFontType(0); //Set font to smallest
- oled.setCursor(xOffset, yOffset); //x, y
- oled.print(F("Test Menu"));
-
- oled.display();
-
- //Wait for user to stop pressing buttons
- if (productVariant == RTK_EXPRESS)
- {
- while (digitalRead(pin_setupButton) == LOW || digitalRead(pin_powerSenseAndControl) == LOW)
- delay(10);
- }
- else if (productVariant == RTK_FACET)
- {
- while (digitalRead(pin_powerSenseAndControl) == LOW)
- delay(10);
- }
-
- //For Surveyor, we need to monitor the rocker switch
- ButtonState previousRockerSwitch = BUTTON_ROVER;
- if (productVariant == RTK_SURVEYOR)
- {
- if (digitalRead(pin_baseSwitch) == LOW) //Switch is set to Base
- previousRockerSwitch = BUTTON_BASE;
- }
-
- //Update display until user presses the setup button
- while (1)
- {
- //Check for user interaction
- if (productVariant == RTK_EXPRESS)
- {
- if (digitalRead(pin_setupButton) == LOW) break;
- }
- else if (productVariant == RTK_FACET)
- {
- while (digitalRead(pin_powerSenseAndControl) == LOW)
- delay(10);
- }
- else if (productVariant == RTK_SURVEYOR)
- {
- //Check if rocker switch moved
- if (digitalRead(pin_baseSwitch) == HIGH && //Switch is set to Rover
- previousRockerSwitch == BUTTON_BASE) break;
- if (digitalRead(pin_baseSwitch) == LOW && //Switch is set to Base
- previousRockerSwitch == BUTTON_ROVER) break;
- }
-
- oled.clear(PAGE); // Clear the display's internal memory
-
- drawFrame(); //Outside edge
-
- //Test SD, accel, batt, GNSS, mux
- oled.setFontType(0); //Set font to smallest
- oled.setCursor(xOffset, yOffset); //x, y
- oled.print(F("SD:"));
-
- if (online.microSD == false)
- beginSD(); //Test if SD is present
- if (online.microSD == true)
- oled.print(F("OK"));
- else
- oled.print(F("FAIL"));
-
- oled.setCursor(xOffset, yOffset + (1 * charHeight) ); //x, y
- oled.print(F("Accel:"));
- if (online.accelerometer == true)
- oled.print(F("OK"));
- else
- oled.print(F("FAIL"));
-
- oled.setCursor(xOffset, yOffset + (2 * charHeight) ); //x, y
- oled.print(F("Batt:"));
- if (online.battery == true)
- oled.print(F("OK"));
- else
- oled.print(F("FAIL"));
-
- i2cGNSS.checkUblox();
- oled.setCursor(xOffset, yOffset + (3 * charHeight) ); //x, y
- oled.print(F("GNSS:"));
- int satsInView = i2cGNSS.getSIV();
- if (online.gnss == true && satsInView > 8)
- {
- oled.print(F("OK"));
- oled.print(F("/"));
- oled.print(satsInView);
- }
- else
- oled.print(F("FAIL"));
-
- oled.setCursor(xOffset, yOffset + (4 * charHeight) ); //x, y
- oled.print(F("Mux:"));
-
- //Set mux to channel 3 and toggle pin and verify with loop back jumper wire inserted by test technician
-
- setMuxport(MUX_ADC_DAC); //Set mux to DAC so we can toggle back/forth
- pinMode(pin_dac26, OUTPUT);
- pinMode(pin_adc39, INPUT_PULLUP);
-
- digitalWrite(pin_dac26, HIGH);
- if (digitalRead(pin_adc39) == HIGH)
- {
- digitalWrite(pin_dac26, LOW);
- if (digitalRead(pin_adc39) == LOW)
- oled.print(F("OK"));
- else
- oled.print(F("FAIL"));
- }
- else
- oled.print(F("FAIL"));
+ if (online.display == true)
+ {
+ oled.erase();
+
+ uint8_t fontHeight = 8;
+ uint8_t yPos = fontHeight;
- //Display MAC address
- oled.setCursor(xOffset, yOffset + (5 * charHeight) ); //x, y
- oled.print(macAddress);
- oled.print(":");
- if (incomingBTTest == 0)
- oled.print(F("FAIL"));
- else
- {
- oled.write(incomingBTTest);
- oled.print(F("-OK"));
- }
+ printTextCenter("Configure", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Via", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Ethernet", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += fontHeight;
+ printTextCenter("Started", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
- //Display incoming BT characters
+ oled.display();
- oled.display();
- delay(250);
+ delay(displayTime);
}
+}
- // Serial.println(F("Any character received over Blueooth connection will be displayed here"));
+void displayConfigViaEthernet()
+{
+#ifdef COMPILE_ETHERNET
- inTestMode = false; //Reroutes bluetooth bytes
+ if (online.display == true)
+ {
+ oled.erase();
- setMuxport(settings.dataPortChannel); //Return mux to original channel
+ uint8_t xPos = (oled.getWidth() / 2) - (Ethernet_Icon_Width / 2);
+ uint8_t yPos = Ethernet_Icon_Height / 2;
- //Disable RTCM sentences
- i2cGNSS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_UART2, 0);
+ static bool blink = 0;
+ blink ^= 1;
- oled.clear(PAGE); // Clear the display's internal memory
+ if (ETH.linkUp() || blink)
+ displayBitmap(xPos, yPos, Ethernet_Icon_Width, Ethernet_Icon_Height, Ethernet_Icon);
- drawFrame(); //Outside edge
+ yPos += Ethernet_Icon_Height * 1.5;
- oled.setFontType(0); //Set font to smallest
- oled.setCursor(xOffset, yOffset); //x, y
- oled.print(F("Stop Test"));
+ printTextCenter("IP:", yPos, QW_FONT_5X7, 1, false); // text, y, font type, kerning, inverted
+ yPos += 8;
- oled.display();
+ char ipAddress[40];
+ IPAddress localIP = ETH.localIP();
+ snprintf(ipAddress, sizeof(ipAddress), " %d.%d.%d.%d ", localIP[0], localIP[1], localIP[2],
+ localIP[3]);
- //Wait for user to stop pressing buttons
- if (productVariant == RTK_EXPRESS)
- {
- while (digitalRead(pin_setupButton) == LOW)
- delay(10);
- }
- else if (productVariant == RTK_FACET)
- {
- while (digitalRead(pin_powerSenseAndControl) == LOW)
- delay(10);
- }
+ static uint8_t ipAddressPosition = 0;
- delay(500);
- }
-}
+ // Print ten characters of IP address
+ char printThis[12];
-void displayForcedFirmwareUpdate()
-{
- if (online.display == true)
- {
- oled.clear(PAGE);
+ // Check if the IP address is <= 10 chars and will fit without scrolling
+ if (strlen(ipAddress) <= 28)
+ ipAddressPosition = 9;
+ else if (strlen(ipAddress) <= 30)
+ ipAddressPosition = 10;
- oled.setCursor(21, 13);
- oled.setFontType(1);
+ snprintf(printThis, sizeof(printThis), "%c%c%c%c%c%c%c%c%c%c", ipAddress[ipAddressPosition + 0],
+ ipAddress[ipAddressPosition + 1], ipAddress[ipAddressPosition + 2], ipAddress[ipAddressPosition + 3],
+ ipAddress[ipAddressPosition + 4], ipAddress[ipAddressPosition + 5], ipAddress[ipAddressPosition + 6],
+ ipAddress[ipAddressPosition + 7], ipAddress[ipAddressPosition + 8], ipAddress[ipAddressPosition + 9]);
- int textX = 11;
- int textY = 10;
- int textKerning = 8;
+ oled.setCursor(0, yPos);
+ oled.print(printThis);
- printTextwithKerning((char*)"Forced", textX, textY, textKerning);
+ ipAddressPosition++; // Increment the print position
+ if (ipAddress[ipAddressPosition + 10] == 0) // Wrap
+ ipAddressPosition = 0;
- textX = 11;
- textY = 25;
- textKerning = 8;
- oled.setFontType(1);
+ oled.display();
+ }
+
+#else // COMPILE_ETHERNET
+ uint8_t fontHeight = 15;
+ uint8_t yPos = oled.getHeight() / 2 - fontHeight;
+ printTextCenter("!Compiled", yPos, QW_FONT_5X7, 1, false);
+#endif // COMPILE_ETHERNET
+}
- printTextwithKerning((char*)"Update", textX, textY, textKerning);
- oled.display();
- }
+const uint8_t *getMacAddress()
+{
+ static const uint8_t zero[6] = {0, 0, 0, 0, 0, 0};
+
+#ifdef COMPILE_BT
+ if (bluetoothState != BT_OFF)
+ return btMACAddress;
+#endif // COMPILE_BT
+#ifdef COMPILE_WIFI
+ if (wifiState != WIFI_STATE_OFF)
+ return wifiMACAddress;
+#endif // COMPILE_WIFI
+#ifdef COMPILE_ETHERNET
+ if ((online.ethernetStatus >= ETH_STARTED_CHECK_CABLE) && (online.ethernetStatus <= ETH_CONNECTED))
+ return ethernetMACAddress;
+#endif // COMPILE_ETHERNET
+ return zero;
}
diff --git a/Firmware/RTK_Surveyor/ESPNOW.ino b/Firmware/RTK_Surveyor/ESPNOW.ino
new file mode 100644
index 000000000..820c61cff
--- /dev/null
+++ b/Firmware/RTK_Surveyor/ESPNOW.ino
@@ -0,0 +1,483 @@
+/*
+ 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.
+*/
+
+// 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)
+ // systemPrintln("Delivery Success");
+ // else
+ // systemPrintln("Delivery Fail");
+}
+#endif // COMPILE_ESPNOW
+
+// Callback when data is received
+void espnowOnDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len)
+{
+#ifdef COMPILE_ESPNOW
+ if (espnowState == ESPNOW_PAIRING)
+ {
+ if (len == sizeof(PairMessage)) // First error check
+ {
+ PairMessage pairMessage;
+ memcpy(&pairMessage, incomingData, sizeof(pairMessage));
+
+ // Check CRC
+ uint8_t tempCRC = 0;
+ for (int x = 0; x < 6; x++)
+ tempCRC += pairMessage.macAddress[x];
+
+ if (tempCRC == pairMessage.crc) // 2nd error check
+ {
+ memcpy(&receivedMAC, pairMessage.macAddress, 6);
+ espnowSetState(ESPNOW_MAC_RECEIVED);
+ }
+ // else Pair CRC failed
+ }
+ }
+ else
+ {
+ espnowRSSI = packetRSSI; // Record this packets RSSI as an ESP NOW packet
+
+ // Pass RTCM bytes (presumably) from ESP NOW out ESP32-UART2 to ZED-UART1 / SPI
+ if (USE_I2C_GNSS)
+ serialGNSS.write(incomingData, len);
+ else
+ theGNSS.pushRawData((uint8_t *)incomingData, len);
+ if (!inMainMenu)
+ log_d("ESPNOW received %d RTCM bytes, pushed to ZED, RSSI: %d", len, espnowRSSI);
+
+ espnowIncomingRTCM = true;
+ lastEspnowRssiUpdate = millis();
+ }
+#endif // COMPILE_ESPNOW
+}
+
+// Callback for all RX Packets
+// Get RSSI of all incoming management packets: https://esp32.com/viewtopic.php?t=13889
+#ifdef COMPILE_ESPNOW
+void promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
+{
+ // All espnow traffic uses action frames which are a subtype of the mgmnt frames so filter out everything else.
+ if (type != WIFI_PKT_MGMT)
+ return;
+
+ const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buf;
+ packetRSSI = ppkt->rx_ctrl.rssi;
+}
+#endif // COMPILE_ESPNOW
+
+// If WiFi is already enabled, simply add the LR protocol
+// If the radio is off entirely, start the radio, turn on only the LR protocol
+void espnowStart()
+{
+#ifdef COMPILE_ESPNOW
+
+ esp_err_t response;
+
+ if (wifiState == WIFI_STATE_OFF && espnowState == ESPNOW_OFF)
+ {
+ if (WiFi.getMode() != WIFI_STA)
+ WiFi.mode(WIFI_STA);
+
+ // Radio is off, turn it on
+ // esp_wifi_set_protocol requires WiFi to be started
+ response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR); // Stops WiFi Station.
+ if (response != ESP_OK)
+ systemPrintf("espnowStart: Error setting ESP-Now lone protocol: %s\r\n", esp_err_to_name(response));
+ else
+ log_d("WiFi off, ESP-Now added to protocols");
+ }
+ // If WiFi is on but ESP NOW is off, then enable LR protocol
+ else if (wifiState > WIFI_STATE_OFF && espnowState == ESPNOW_OFF)
+ {
+ if (WiFi.getMode() != WIFI_STA)
+ WiFi.mode(WIFI_STA);
+
+ // Enable WiFi + ESP-Now
+ // Enable long range, PHY rate of ESP32 will be 512Kbps or 256Kbps
+ // esp_wifi_set_protocol requires WiFi to be started
+ response = esp_wifi_set_protocol(WIFI_IF_STA,
+ WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR);
+ if (response != ESP_OK)
+ systemPrintf("espnowStart: Error setting ESP-Now + WiFi protocols: %s\r\n", esp_err_to_name(response));
+ else
+ log_d("WiFi on, ESP-Now added to protocols");
+ }
+
+ // If ESP-Now is already active, do nothing
+ else
+ {
+ log_d("ESP-Now already on");
+ }
+
+ // Init ESP-NOW
+ if (esp_now_init() != ESP_OK)
+ {
+ systemPrintln("espnowStart: Error starting ESP-Now");
+ return;
+ }
+
+ // Use promiscuous callback to capture RSSI of packet
+ response = esp_wifi_set_promiscuous(true);
+ if (response != ESP_OK)
+ systemPrintf("espnowStart: Error setting promiscuous mode: %s\r\n", esp_err_to_name(response));
+
+ esp_wifi_set_promiscuous_rx_cb(&promiscuous_rx_cb);
+
+ // Register callbacks
+ // esp_now_register_send_cb(espnowOnDataSent);
+ esp_now_register_recv_cb(espnowOnDataReceived);
+
+ if (settings.espnowPeerCount == 0)
+ {
+ espnowSetState(ESPNOW_ON);
+ }
+ else
+ {
+ // If we already have peers, move to paired state
+ espnowSetState(ESPNOW_PAIRED);
+
+ log_d("Adding %d espnow peers", settings.espnowPeerCount);
+ for (int x = 0; x < settings.espnowPeerCount; x++)
+ {
+ if (esp_now_is_peer_exist(settings.espnowPeers[x]) == true)
+ log_d("Peer already exists");
+ else
+ {
+ esp_err_t result = espnowAddPeer(settings.espnowPeers[x]);
+ if (result != ESP_OK)
+ log_d("Failed to add peer #%d", x);
+ }
+ }
+ }
+
+ if (settings.espnowBroadcast == true)
+ {
+ // Add broadcast peer if override is turned on
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+ if (esp_now_is_peer_exist(broadcastMac) == true)
+ log_d("Broadcast peer already exists");
+ else
+ {
+ esp_err_t result = espnowAddPeer(broadcastMac, false); // Encryption not support for broadcast MAC
+ if (result != ESP_OK)
+ log_d("Failed to add broadcast peer");
+ }
+ }
+
+ 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)
+ return;
+
+ // Turn off promiscuous WiFi mode
+ esp_err_t response = esp_wifi_set_promiscuous(false);
+ if (response != ESP_OK)
+ systemPrintf("espnowStop: Failed to set promiscuous mode: %s\r\n", esp_err_to_name(response));
+
+ esp_wifi_set_promiscuous_rx_cb(nullptr);
+
+ // Deregister callbacks
+ // esp_now_unregister_send_cb();
+ response = esp_now_unregister_recv_cb();
+ if (response != ESP_OK)
+ systemPrintf("espnowStop: Failed to unregister receive callback: %s\r\n", esp_err_to_name(response));
+
+ // Forget all ESP-Now Peers
+ for (int x = 0; x < settings.espnowPeerCount; x++)
+ espnowRemovePeer(settings.espnowPeers[x]);
+
+ if (WiFi.getMode() != WIFI_STA)
+ WiFi.mode(WIFI_STA);
+
+ // Leave WiFi with default settings (no WIFI_PROTOCOL_LR for ESP NOW)
+ // esp_wifi_set_protocol requires WiFi to be started
+ response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N);
+ if (response != ESP_OK)
+ systemPrintf("espnowStop: Error setting WiFi protocols: %s\r\n", esp_err_to_name(response));
+ else
+ log_d("WiFi on, ESP-Now added to protocols");
+
+ // Deinit ESP-NOW
+ if (esp_now_deinit() != ESP_OK)
+ {
+ systemPrintln("Error deinitializing ESP-NOW");
+ return;
+ }
+
+ espnowSetState(ESPNOW_OFF);
+
+ if (wifiState == WIFI_STATE_OFF)
+ {
+ // ESP Now was the only thing using the radio so turn WiFi radio off entirely
+ WiFi.mode(WIFI_OFF);
+
+ log_d("WiFi Radio off entirely");
+ }
+ // If WiFi is on, then restart WiFi
+ else if (wifiState > WIFI_STATE_OFF)
+ {
+ log_d("ESP-Now starting WiFi");
+ wifiStart(); // Force WiFi to restart
+ }
+
+#endif // COMPILE_ESPNOW
+}
+
+// Start ESP-Now if needed, put ESP-Now into broadcast state
+void espnowBeginPairing()
+{
+ espnowStart();
+
+ // To begin pairing, we must add the broadcast MAC to the peer list
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ espnowAddPeer(broadcastMac, false); // Encryption is not supported for multicast addresses
+
+ espnowSetState(ESPNOW_PAIRING);
+}
+
+// Regularly call during pairing to see if we've received a Pairing message
+bool espnowIsPaired()
+{
+#ifdef COMPILE_ESPNOW
+ if (espnowState == ESPNOW_MAC_RECEIVED)
+ {
+
+ if (settings.espnowBroadcast == false)
+ {
+ // Remove broadcast peer
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ espnowRemovePeer(broadcastMac);
+ }
+
+ if (esp_now_is_peer_exist(receivedMAC) == true)
+ log_d("Peer already exists");
+ else
+ {
+ // Add new peer to system
+ espnowAddPeer(receivedMAC);
+
+ // Record this MAC to peer list
+ memcpy(settings.espnowPeers[settings.espnowPeerCount], receivedMAC, 6);
+ settings.espnowPeerCount++;
+ settings.espnowPeerCount %= ESPNOW_MAX_PEERS;
+ }
+
+ // Send message directly to the received MAC (not unicast), then exit
+ espnowSendPairMessage(receivedMAC);
+
+ // Enable radio. User may have arrived here from the setup menu rather than serial menu.
+ settings.radioType = RADIO_ESPNOW;
+
+ recordSystemSettings(); // Record radioType and espnowPeerCount to NVM
+
+ 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;
+
+ // Get unit MAC address
+ memcpy(pairMessage.macAddress, wifiMACAddress, 6);
+ pairMessage.encrypt = false;
+ pairMessage.channel = 0;
+
+ pairMessage.crc = 0; // Calculate CRC
+ for (int x = 0; x < 6; x++)
+ pairMessage.crc += wifiMACAddress[x];
+
+ return (esp_now_send(sendToMac, (uint8_t *)&pairMessage, sizeof(pairMessage))); // Send packet to given MAC
+#else // COMPILE_ESPNOW
+ return (ESP_OK);
+#endif // COMPILE_ESPNOW
+}
+
+// Add a given MAC address to the peer list
+esp_err_t espnowAddPeer(uint8_t *peerMac)
+{
+ return (espnowAddPeer(peerMac, true)); // Encrypt by default
+}
+
+esp_err_t espnowAddPeer(uint8_t *peerMac, bool encrypt)
+{
+#ifdef COMPILE_ESPNOW
+ esp_now_peer_info_t peerInfo;
+
+ memcpy(peerInfo.peer_addr, peerMac, 6);
+ peerInfo.channel = 0;
+ peerInfo.ifidx = WIFI_IF_STA;
+ // memcpy(peerInfo.lmk, "RTKProductsLMK56", 16);
+ // peerInfo.encrypt = encrypt;
+ peerInfo.encrypt = false;
+
+ esp_err_t result = esp_now_add_peer(&peerInfo);
+ if (result != ESP_OK)
+ log_d("Failed to add peer: 0x%02X%02X%02X%02X%02X%02X", peerMac[0], peerMac[1], peerMac[2], 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)
+ log_d("Failed to remove peer: %s", esp_err_to_name(response));
+
+ 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)
+ systemPrint("*");
+ espnowState = newState;
+
+ systemPrint("espnowState: ");
+ switch (newState)
+ {
+ case ESPNOW_OFF:
+ systemPrintln("ESPNOW_OFF");
+ break;
+ case ESPNOW_ON:
+ systemPrintln("ESPNOW_ON");
+ break;
+ case ESPNOW_PAIRING:
+ systemPrintln("ESPNOW_PAIRING");
+ break;
+ case ESPNOW_MAC_RECEIVED:
+ systemPrintln("ESPNOW_MAC_RECEIVED");
+ break;
+ case ESPNOW_PAIRED:
+ systemPrintln("ESPNOW_PAIRED");
+ break;
+ default:
+ systemPrintf("Unknown ESPNOW state: %d\r\n", newState);
+ break;
+ }
+}
+
+void espnowProcessRTCM(byte incoming)
+{
+#ifdef COMPILE_ESPNOW
+ if (espnowState == ESPNOW_PAIRED)
+ {
+ // Move this byte into ESP NOW to send buffer
+ espnowOutgoing[espnowOutgoingSpot++] = incoming;
+ espnowLastAdd = millis();
+
+ if (espnowOutgoingSpot == sizeof(espnowOutgoing))
+ {
+ espnowOutgoingSpot = 0; // Wrap
+
+ if (settings.espnowBroadcast == false)
+ esp_now_send(0, (uint8_t *)&espnowOutgoing, sizeof(espnowOutgoing)); // Send packet to all peers
+ else
+ {
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ esp_now_send(broadcastMac, (uint8_t *)&espnowOutgoing,
+ sizeof(espnowOutgoing)); // Send packet via broadcast
+ }
+
+ delay(10); // We need a small delay between sending multiple packets
+
+ espnowBytesSent += sizeof(espnowOutgoing);
+
+ espnowOutgoingRTCM = true;
+ }
+ }
+#endif // COMPILE_ESPNOW
+}
+
+// A blocking function that is used to pair two devices
+// either through the serial menu or AP config
+void espnowStaticPairing()
+{
+ systemPrintln("Begin ESP NOW Pairing");
+
+ // Start ESP-Now if needed, put ESP-Now into broadcast state
+ espnowBeginPairing();
+
+ // Begin sending our MAC every 250ms until a remote device sends us there info
+ randomSeed(millis());
+
+ systemPrintln("Begin pairing. Place other unit in pairing mode. Press any key to exit.");
+ clearBuffer();
+
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+ bool exitPair = false;
+ while (exitPair == false)
+ {
+ if (systemAvailable())
+ {
+ systemPrintln("User pressed button. Pairing canceled.");
+ break;
+ }
+
+ int timeout = 1000 + random(0, 100); // Delay 1000 to 1100ms
+ for (int x = 0; x < timeout; x++)
+ {
+ delay(1);
+
+ if (espnowIsPaired() == true) // Check if we've received a pairing message
+ {
+ systemPrintln("Pairing compete");
+ exitPair = true;
+ break;
+ }
+ }
+
+ espnowSendPairMessage(broadcastMac); // Send unit's MAC address over broadcast, no ack, no encryption
+
+ systemPrintln("Scanning for other radio...");
+ }
+}
diff --git a/Firmware/RTK_Surveyor/Esp32Timer.h b/Firmware/RTK_Surveyor/Esp32Timer.h
new file mode 100644
index 000000000..d2563ff8d
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Esp32Timer.h
@@ -0,0 +1,254 @@
+// Esp32Timer.h
+
+#ifndef __ESP32_TIMER_H__
+#define __ESP32_TIMER_H__
+
+extern void systemPrintf(const char * format, ...);
+
+#define TIMG0 0x3ff5f000
+#define TIMG1 0x3ff60000
+
+#define TIMG_T0CONFIG_REG 0x0000 // RW
+#define TIMG_T0LO_REG 0x0004 // RO
+#define TIMG_T0HI_REG 0x0008 // RO
+#define TIMG_T0UPDATE_REG 0x000c // WO
+#define TIMG_T0ALARMLO_REG 0x0010 // RW
+#define TIMG_T0ALARMHI_REG 0x0014 // RW
+#define TIMG_T0LOADLO_REG 0x0018 // RW
+#define TIMG_T0LOADHI_REG 0x001c // RW
+#define TIMG_T0LOAD_REG 0x0020 // WO
+
+#define TIMG_T1CONFIG_REG 0x0024 // RW
+#define TIMG_T1LO_REG 0x0028 // RO
+#define TIMG_T1HI_REG 0x002c // RO
+#define TIMG_T1UPDATE_REG 0x0030 // WO
+#define TIMG_T1ALARMLO_REG 0x0034 // RW
+#define TIMG_T1ALARMHI_REG 0x0038 // RW
+#define TIMG_T1LOADLO_REG 0x003c // RW
+#define TIMG_T1LOADHI_REG 0x0040 // RW
+#define TIMG_T1LOAD_REG 0x0044 // WO
+
+#define TIMG_T_WDTCONFIG0_REG 0x0048 // RW
+#define TIMG_T_WDTCONFIG1_REG 0x004c // RW, Clock prescale * 12.5ns
+#define TIMG_T_WDTCONFIG2_REG 0x0050 // RW
+#define TIMG_T_WDTCONFIG3_REG 0x0054 // RW
+#define TIMG_T_WDTCONFIG4_REG 0x0058 // RW
+#define TIMG_T_WDTCONFIG5_REG 0x005c // RW
+#define TIMG_T_WDTFEED_REG 0x0060 // WO
+#define TIMG_T_WDTWPROTECT_REG 0x0064 // RW
+
+#define TIMG_RTCCALICFG_REG 0x0068 // varies
+#define TIMG_RTCCALICFG1_REG 0x006c // RO
+
+#define TIMG_T_INT_ENA_REG 0x0098 // RW
+#define TIMG_T_INT_RAW_REG 0x009c // RO
+#define TIMG_T_INT_ST_REG 0x00a0 // RO
+#define TIMG_T_INT_CLR_REG 0x00a4 // WO
+
+// TIMG_TxCONFIG_REG
+#define TIMG_Tx_EN 0x80000000 // Enable the timer
+#define TIMG_Tx_INCREASE 0x40000000 // Timer value increases every clock tick
+#define TIMG_Tx_AUTORELOAD 0x20000000 // Reload timer upon alarm
+#define TIMG_Tx_DIVIDER 0x1ffff000 // Clock prescale value
+#define TIMG_Tx_EDGE_INT_EN 0x00000800 // Alarm generates edge interrupt
+#define TIMG_Tx_LEVEL_INT_EN 0x00000400 // Alarm generates level interrupt
+#define TIMG_Tx_ALARM_EN 0x00000200 // Alarm enable
+
+// TIMG_T_WDTCONFIG0_REG
+#define TIMG_T_WDT_EN 0x80000000 // Enable MWDT
+
+#define TIMG_T_WDT_STG0 0x60000000 // Stage 0 configuration
+#define TIMG_T_WDT_STG0_RST_SYSTEM 0x60000000
+#define TIMG_T_WDT_STG0_RST_CPU 0x40000000
+#define TIMG_T_WDT_STG0_INTERRUPT 0x20000000
+#define TIMG_T_WDT_STG0_OFF 0x00000000
+
+#define TIMG_T_WDT_STG1 0x18000000 // Stage 1 configuration
+#define TIMG_T_WDT_STG1_RST_SYSTEM 0x18000000
+#define TIMG_T_WDT_STG1_RST_CPU 0x10000000
+#define TIMG_T_WDT_STG1_INTERRUPT 0x08000000
+#define TIMG_T_WDT_STG1_OFF 0x00000000
+
+#define TIMG_T_WDT_STG2 0x06000000 // Stage 2 configuration
+#define TIMG_T_WDT_STG2_RST_SYSTEM 0x06000000
+#define TIMG_T_WDT_STG2_RST_CPU 0x04000000
+#define TIMG_T_WDT_STG2_INTERRUPT 0x02000000
+#define TIMG_T_WDT_STG2_OFF 0x00000000
+
+#define TIMG_T_WDT_STG3 0x01800000 // Stage 3 configuration
+#define TIMG_T_WDT_STG3_RST_SYSTEM 0x01800000
+#define TIMG_T_WDT_STG3_RST_CPU 0x01000000
+#define TIMG_T_WDT_STG3_INTERRUPT 0x00800000
+#define TIMG_T_WDT_STG3_OFF 0x00000000
+
+#define TIMG_T_WDT_EDGE_INT_EN 0x00400000 // Enable edge interrupts
+#define TIMG_T_WDT_LEVEL_INT_EN 0x00200000 // Enable level interrupts
+
+#define TIMG_T_WDT_CPU_RESET_LENGTH 0x001c0000 // CPU reset pulse width
+#define TIMG_T_WDT_CPU_RESET_3200ns 0x001c0000
+#define TIMG_T_WDT_CPU_RESET_1600ns 0x00180000
+#define TIMG_T_WDT_CPU_RESET_800ns 0x00140000
+#define TIMG_T_WDT_CPU_RESET_500ns 0x00100000
+#define TIMG_T_WDT_CPU_RESET_400ns 0x000c0000
+#define TIMG_T_WDT_CPU_RESET_300ns 0x00080000
+#define TIMG_T_WDT_CPU_RESET_200ns 0x00040000
+#define TIMG_T_WDT_CPU_RESET_100ns 0x00000000
+
+#define TIMG_T_WDT_SYS_RESET_LENGTH 0x00038000 // System reset pulse width
+#define TIMG_T_WDT_SYS_RESET_3200ns 0x00038000
+#define TIMG_T_WDT_SYS_RESET_1600ns 0x00030000
+#define TIMG_T_WDT_SYS_RESET_800ns 0x00028000
+#define TIMG_T_WDT_SYS_RESET_500ns 0x00020000
+#define TIMG_T_WDT_SYS_RESET_400ns 0x00018000
+#define TIMG_T_WDT_SYS_RESET_300ns 0x00010000
+#define TIMG_T_WDT_SYS_RESET_200ns 0x00008000
+#define TIMG_T_WDT_SYS_RESET_100ns 0x00000000
+
+#define TIMG_T_WDT_FLASHBOOT_MOD_EN 0x00004000
+
+double printClockPeriod(uint32_t config)
+{
+ uint32_t clocks;
+ double clockPeriod;
+ double multiplier;
+ const char * units;
+
+ clocks = config >> 16;
+ clockPeriod = 0.0000000125 * clocks;
+ if (clockPeriod >= 1.)
+ {
+ units = "Sec";
+ multiplier = 1;
+ }
+ else if (clockPeriod >= 0.001)
+ {
+ units = "mSec";
+ multiplier = 1000.;
+ }
+ else if (clockPeriod >= 0.000001)
+ {
+ units = "uSec";
+ multiplier = 1000000;
+ }
+ else
+ {
+ units = "nSec";
+ multiplier = 1000000000.;
+ }
+ systemPrintf(" Clock period: %7.3f %s (%d - 12.5 nSec clocks)", clockPeriod * multiplier, units, clocks);
+ return clockPeriod;
+}
+
+void printWdtTimeout(double clockPeriod, uint32_t clocks)
+{
+ double multiplier;
+ double timeout;
+ const char * units;
+
+ timeout = clockPeriod * (double)clocks;
+ if (timeout >= 1.)
+ {
+ units = "Sec";
+ multiplier = 1;
+ }
+ else if (timeout >= 0.001)
+ {
+ units = "mSec";
+ multiplier = 1000.;
+ }
+ else if (timeout >= 0.000000)
+ {
+ units = "uSec";
+ multiplier = 1000000.;
+ }
+ else
+ {
+ units = "nSec";
+ multiplier = 1000000000.;
+ }
+ systemPrintf(", timeout: %5.1f %s (%d clocks)\r\n", timeout * multiplier, units, clocks);
+}
+
+void printWdt(intptr_t baseAddress)
+{
+ double clockPeriod;
+ const char * const config[] =
+ {
+ "Off",
+ "Interrupt",
+ "Reset CPU",
+ "Reset System"
+ };
+ uint32_t protect;
+ const int pulseWidth[] = {100, 200, 300, 400, 500, 800, 1600, 3200};
+ uint32_t value[6];
+
+ systemPrintf("0x%08x: Watch Dog Timer\r\n", baseAddress);
+ value[0] = *(uint32_t *)(baseAddress + TIMG_T_WDTCONFIG0_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTCONFIG0_REG\r\n", value[0]);
+ value[1] = *(uint32_t *)(baseAddress + TIMG_T_WDTCONFIG1_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTCONFIG1_REG\r\n", value[1]);
+ value[2] = *(uint32_t *)(baseAddress + TIMG_T_WDTCONFIG2_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTCONFIG2_REG\r\n", value[2]);
+ value[3] = *(uint32_t *)(baseAddress + TIMG_T_WDTCONFIG3_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTCONFIG3_REG\r\n", value[3]);
+ value[4] = *(uint32_t *)(baseAddress + TIMG_T_WDTCONFIG4_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTCONFIG4_REG\r\n", value[4]);
+ value[5] = *(uint32_t *)(baseAddress + TIMG_T_WDTCONFIG5_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTCONFIG5_REG\r\n", value[5]);
+ protect = *(uint32_t *)(baseAddress + TIMG_T_WDTWPROTECT_REG);
+ systemPrintf(" 0x%08x: TIMG_T_WDTWPROTECT_REG\r\n", protect);
+
+ if (value[0] & TIMG_T_WDT_EN)
+ {
+ // TIMG_T_WDTCONFIG0_REG
+ systemPrintf(" Watch dog enabled\r\n");
+ clockPeriod = printClockPeriod(value[1]);
+ systemPrintf(" Stage %d: %s", 0, config[(value[0] >> 29) & 3]);
+ if (value[0] & TIMG_T_WDT_STG0_RST_SYSTEM)
+ printWdtTimeout(clockPeriod, value[2]);
+ systemPrintf("\r\n");
+ systemPrintf(" Stage %d: %s", 1, config[(value[0] >> 27) & 3]);
+ if (value[0] & TIMG_T_WDT_STG1_RST_SYSTEM)
+ printWdtTimeout(clockPeriod, value[3]);
+ systemPrintf("\r\n");
+ systemPrintf(" Stage %d: %s", 2, config[(value[0] >> 25) & 3]);
+ if (value[0] & TIMG_T_WDT_STG2_RST_SYSTEM)
+ printWdtTimeout(clockPeriod, value[4]);
+ systemPrintf("\r\n");
+ systemPrintf(" Stage %d: %s", 3, config[(value[0] >> 23) & 3]);
+ if (value[0] & TIMG_T_WDT_STG3_RST_SYSTEM)
+ printWdtTimeout(clockPeriod, value[5]);
+ systemPrintf("\r\n");
+ if ((value[0] & TIMG_T_WDT_STG0_INTERRUPT)
+ || (value[0] & TIMG_T_WDT_STG1_INTERRUPT)
+ || (value[0] & TIMG_T_WDT_STG2_INTERRUPT)
+ || (value[0] & TIMG_T_WDT_STG3_INTERRUPT))
+ {
+ if (value[0] & TIMG_T_WDT_EDGE_INT_EN)
+ systemPrintf(" Generate edge interrupt\r\n");
+ if (value[0] & TIMG_T_WDT_LEVEL_INT_EN)
+ systemPrintf(" Generate level interrupt\r\n");
+ }
+ if (((value[0] & TIMG_T_WDT_STG0_RST_SYSTEM) == TIMG_T_WDT_STG0_RST_CPU)
+ || ((value[0] & TIMG_T_WDT_STG1_RST_SYSTEM) == TIMG_T_WDT_STG1_RST_CPU)
+ || ((value[0] & TIMG_T_WDT_STG2_RST_SYSTEM) == TIMG_T_WDT_STG2_RST_CPU)
+ || ((value[0] & TIMG_T_WDT_STG3_RST_SYSTEM) == TIMG_T_WDT_STG3_RST_CPU))
+ {
+ systemPrintf(" CPU reset pulse: %d nSec\r\n", pulseWidth[(value[0] >> 18) & 7]);
+ }
+ if (((value[0] & TIMG_T_WDT_STG0_RST_SYSTEM) == TIMG_T_WDT_STG0_RST_SYSTEM)
+ || ((value[0] & TIMG_T_WDT_STG1_RST_SYSTEM) == TIMG_T_WDT_STG1_RST_SYSTEM)
+ || ((value[0] & TIMG_T_WDT_STG2_RST_SYSTEM) == TIMG_T_WDT_STG2_RST_SYSTEM)
+ || ((value[0] & TIMG_T_WDT_STG3_RST_SYSTEM) == TIMG_T_WDT_STG3_RST_SYSTEM))
+ {
+ systemPrintf(" System reset pulse: %d nSec\r\n", pulseWidth[(value[0] >> 15) & 7]);
+ }
+ if (value[0] & TIMG_T_WDT_FLASHBOOT_MOD_EN)
+ systemPrintf(" Flash boot protection enabled\r\n");
+ }
+ else
+ systemPrintf(" Watch dog disabled\r\n");
+}
+
+#endif // __ESP32_TIMER_H__
diff --git a/Firmware/RTK_Surveyor/Ethernet.ino b/Firmware/RTK_Surveyor/Ethernet.ino
new file mode 100644
index 000000000..9273715b7
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Ethernet.ino
@@ -0,0 +1,385 @@
+#ifdef COMPILE_ETHERNET
+
+// Get the Ethernet parameters
+void menuEthernet()
+{
+ if (!HAS_ETHERNET)
+ {
+ clearBuffer(); // Empty buffer of any newline chars
+ return;
+ }
+
+ bool restartEthernet = false;
+
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Ethernet");
+ systemPrintln();
+
+ systemPrint("1) Ethernet Config: ");
+ if (settings.ethernetDHCP)
+ systemPrintln("DHCP");
+ else
+ systemPrintln("Fixed IP");
+
+ if (!settings.ethernetDHCP)
+ {
+ systemPrint("2) Fixed IP Address: ");
+ systemPrintln(settings.ethernetIP.toString().c_str());
+ systemPrint("3) DNS: ");
+ systemPrintln(settings.ethernetDNS.toString().c_str());
+ systemPrint("4) Gateway: ");
+ systemPrintln(settings.ethernetGateway.toString().c_str());
+ systemPrint("5) Subnet Mask: ");
+ systemPrintln(settings.ethernetSubnet.toString().c_str());
+ }
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ {
+ settings.ethernetDHCP ^= 1;
+ restartEthernet = true;
+ }
+ else if ((!settings.ethernetDHCP) && (incoming == 2))
+ {
+ systemPrint("Enter new IP Address: ");
+ char tempStr[20];
+ if (getIPAddress(tempStr, sizeof(tempStr)) == INPUT_RESPONSE_VALID)
+ {
+ String tempString = String(tempStr);
+ settings.ethernetIP.fromString(tempString);
+ restartEthernet = true;
+ }
+ else
+ systemPrint("Error: invalid IP Address");
+ }
+ else if ((!settings.ethernetDHCP) && (incoming == 3))
+ {
+ systemPrint("Enter new DNS: ");
+ char tempStr[20];
+ if (getIPAddress(tempStr, sizeof(tempStr)) == INPUT_RESPONSE_VALID)
+ {
+ String tempString = String(tempStr);
+ settings.ethernetDNS.fromString(tempString);
+ restartEthernet = true;
+ }
+ else
+ systemPrint("Error: invalid DNS");
+ }
+ else if ((!settings.ethernetDHCP) && (incoming == 4))
+ {
+ systemPrint("Enter new Gateway: ");
+ char tempStr[20];
+ if (getIPAddress(tempStr, sizeof(tempStr)) == INPUT_RESPONSE_VALID)
+ {
+ String tempString = String(tempStr);
+ settings.ethernetGateway.fromString(tempString);
+ restartEthernet = true;
+ }
+ else
+ systemPrint("Error: invalid Gateway");
+ }
+ else if ((!settings.ethernetDHCP) && (incoming == 5))
+ {
+ systemPrint("Enter new Subnet Mask: ");
+ char tempStr[20];
+ if (getIPAddress(tempStr, sizeof(tempStr)) == INPUT_RESPONSE_VALID)
+ {
+ String tempString = String(tempStr);
+ settings.ethernetSubnet.fromString(tempString);
+ restartEthernet = true;
+ }
+ else
+ systemPrint("Error: invalid Subnet Mask");
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+
+ if (restartEthernet) // Restart Ethernet to use the new ethernet settings
+ {
+ ethernetRestart();
+ }
+}
+
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// Ethernet routines
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Regularly called to update the Ethernet status
+void ethernetBegin()
+{
+ if (HAS_ETHERNET == false)
+ return;
+
+ // Skip if going into configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ log_d("configureViaEthernet: skipping ethernetBegin");
+ return;
+ }
+
+ if (!ethernetIsNeeded())
+ return;
+
+ if (PERIODIC_DISPLAY(PD_ETHERNET_STATE))
+ {
+ PERIODIC_CLEAR(PD_ETHERNET_STATE);
+ ethernetDisplayState();
+ systemPrintln();
+ }
+ switch (online.ethernetStatus)
+ {
+ case (ETH_NOT_STARTED):
+ Ethernet.init(pin_Ethernet_CS);
+
+ // First we start Ethernet without DHCP to detect if a cable is connected
+ // DHCP causes system freeze for ~62 seconds so we avoid it until a cable is connected
+ Ethernet.begin(ethernetMACAddress, settings.ethernetIP, settings.ethernetDNS, settings.ethernetGateway,
+ settings.ethernetSubnet);
+
+ if (Ethernet.hardwareStatus() == EthernetNoHardware) // Check that a W5n00 has been detected
+ {
+ log_d("Ethernet hardware not found");
+ online.ethernetStatus = ETH_CAN_NOT_BEGIN;
+ return;
+ }
+
+ online.ethernetStatus = ETH_STARTED_CHECK_CABLE;
+ lastEthernetCheck = millis(); // Wait a full second before checking the cable
+
+ break;
+
+ case (ETH_STARTED_CHECK_CABLE):
+ if (millis() - lastEthernetCheck > 1000) // Check for cable every second
+ {
+ lastEthernetCheck = millis();
+
+ if (Ethernet.linkStatus() == LinkON)
+ {
+ log_d("Ethernet cable detected");
+
+ if (settings.ethernetDHCP)
+ {
+ paintGettingEthernetIP();
+ online.ethernetStatus = ETH_STARTED_START_DHCP;
+ }
+ else
+ {
+ systemPrintln("Ethernet started with static IP");
+ online.ethernetStatus = ETH_CONNECTED;
+ }
+ }
+ else
+ {
+ // log_d("No cable detected");
+ }
+ }
+ break;
+
+ case (ETH_STARTED_START_DHCP):
+ if (millis() - lastEthernetCheck > 1000) // Try DHCP every second
+ {
+ lastEthernetCheck = millis();
+
+ if (Ethernet.begin(ethernetMACAddress, 20000)) // Restart Ethernet with DHCP. Use 20s timeout
+ {
+ log_d("Ethernet started with DHCP");
+ online.ethernetStatus = ETH_CONNECTED;
+ }
+ }
+ break;
+
+ case (ETH_CONNECTED):
+ if (Ethernet.linkStatus() == LinkOFF)
+ {
+ log_d("Ethernet cable disconnected!");
+ online.ethernetStatus = ETH_STARTED_CHECK_CABLE;
+ }
+ break;
+
+ case (ETH_CAN_NOT_BEGIN):
+ break;
+
+ default:
+ log_d("Unknown status");
+ break;
+ }
+}
+
+// Display the Ethernet state
+void ethernetDisplayState()
+{
+ if (online.ethernetStatus >= ethernetStateEntries)
+ systemPrint("UNKNOWN");
+ else
+ systemPrint(ethernetStates[online.ethernetStatus]);
+}
+
+// Return the IP address for the Ethernet controller
+IPAddress ethernetGetIpAddress()
+{
+ return Ethernet.localIP();
+}
+
+// Determine if Ethernet is needed. Saves RAM...
+bool ethernetIsNeeded()
+{
+ // Does NTP need Ethernet?
+ if (systemState >= STATE_NTPSERVER_NOT_STARTED && systemState <= STATE_NTPSERVER_SYNC)
+ return true;
+
+ // Does Base mode NTRIP Server need Ethernet?
+ if (settings.enableNtripServer == true &&
+ (systemState >= STATE_BASE_NOT_STARTED && systemState <= STATE_BASE_FIXED_TRANSMITTING)
+ )
+ return true;
+
+ // Does Rover mode NTRIP Client need Ethernet?
+ if (settings.enableNtripClient == true &&
+ (systemState >= STATE_ROVER_NOT_STARTED && systemState <= STATE_ROVER_RTK_FIX)
+ )
+ return true;
+
+ // Does PVT client or server need Ethernet?
+ if (settings.enablePvtClient || settings.enablePvtServer
+ || settings.enablePvtUdpServer || settings.enableAutoFirmwareUpdate)
+ return true;
+
+ return false;
+}
+
+// Ethernet (W5500) ISR
+// Triggered by the falling edge of the W5500 interrupt signal - indicates the arrival of a packet
+// Record the time the packet arrived
+void ethernetISR()
+{
+ // Don't check or clear the interrupt here -
+ // it may clash with a GNSS SPI transaction and cause a wdt timeout.
+ // Do it in updateEthernet
+ gettimeofday((timeval *)ðernetNtpTv, nullptr); // Record the time of the NTP interrupt
+}
+
+// Restart the Ethernet controller
+void ethernetRestart()
+{
+ // Reset online.ethernetStatus so ethernetBegin will call Ethernet.begin to use the new settings
+ online.ethernetStatus = ETH_NOT_STARTED;
+
+ // NTP Server
+ ntpServerStop();
+
+ // NTRIP?
+}
+
+// Update the Ethernet state
+void ethernetUpdate()
+{
+ // Skip if in configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ // log_d("configureViaEthernet: skipping updateEthernet");
+ return;
+ }
+
+ if (!HAS_ETHERNET)
+ return;
+
+ if (online.ethernetStatus == ETH_CAN_NOT_BEGIN)
+ return;
+
+ ethernetBegin(); // This updates the link status
+
+ // Maintain the ethernet connection
+ if ((online.ethernetStatus >= ETH_STARTED_CHECK_CABLE) && (online.ethernetStatus <= ETH_CONNECTED))
+ switch (Ethernet.maintain())
+ {
+ case 1:
+ // renewed fail
+ if (settings.enablePrintEthernetDiag && (!inMainMenu))
+ systemPrintln("Ethernet: Error: renewed fail");
+ ethernetRestart(); // Restart Ethernet
+ break;
+
+ case 2:
+ // renewed success
+ if (settings.enablePrintEthernetDiag && (!inMainMenu))
+ {
+ systemPrint("Ethernet: Renewed success. IP address: ");
+ systemPrintln(Ethernet.localIP());
+ }
+ break;
+
+ case 3:
+ // rebind fail
+ if (settings.enablePrintEthernetDiag && (!inMainMenu))
+ systemPrintln("Ethernet: Error: rebind fail");
+ ethernetRestart(); // Restart Ethernet
+ break;
+
+ case 4:
+ // rebind success
+ if (settings.enablePrintEthernetDiag && (!inMainMenu))
+ {
+ systemPrint("Ethernet: Rebind success. IP address: ");
+ systemPrintln(Ethernet.localIP());
+ }
+ break;
+
+ default:
+ // nothing happened
+ break;
+ }
+}
+
+// Verify the Ethernet tables
+void ethernetVerifyTables()
+{
+ // Verify the table lengths
+ if (ethernetStateEntries != ETH_MAX_STATE)
+ reportFatalError("Please fix ethernetStates table to match ethernetStatus_e");
+}
+
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// Web server routines
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Start Ethernet WebServer ESP32 W5500 - needs exclusive access to WiFi, SPI and Interrupts
+void ethernetWebServerStartESP32W5500()
+{
+ // Configure the W5500
+ // To be called before ETH.begin()
+ ESP32_W5500_onEvent();
+
+ // start the ethernet connection and the server:
+ // Use DHCP dynamic IP
+ // bool begin(int POCI_GPIO, int PICO_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ,
+ // int SPI_HOST, uint8_t *W5500_Mac = W5500_Default_Mac, bool installIsrService = true);
+ ETH.begin(pin_POCI, pin_PICO, pin_SCK, pin_Ethernet_CS, pin_Ethernet_Interrupt, 25, SPI3_HOST, ethernetMACAddress);
+
+ if (!settings.ethernetDHCP)
+ ETH.config(settings.ethernetIP, settings.ethernetGateway, settings.ethernetSubnet, settings.ethernetDNS);
+
+ if (ETH.linkUp())
+ ESP32_W5500_waitForConnect();
+}
+
+// Stop the Ethernet web server
+void ethernetWebServerStopESP32W5500()
+{
+ ETH.end(); // This is _really_ important. It undoes the low-level changes to SPI and interrupts
+}
+
+#endif // COMPILE_ETHERNET
diff --git a/Firmware/RTK_Surveyor/FileSdFatMMC.h b/Firmware/RTK_Surveyor/FileSdFatMMC.h
new file mode 100644
index 000000000..a1dbc2a36
--- /dev/null
+++ b/Firmware/RTK_Surveyor/FileSdFatMMC.h
@@ -0,0 +1,239 @@
+// Define a hybrid class which can support both SdFat SdFile and SD_MMC File
+
+#ifdef COMPILE_SD_MMC
+
+// #include "FS.h"
+#include "SD_MMC.h" //Also includes FS.h
+
+class FileSdFatMMC : public SdFile, public File
+
+#else // COMPILE_SD_MMC
+
+class FileSdFatMMC : public SdFile
+
+#endif // COMPILE_SD_MMC
+
+{
+ public:
+ FileSdFatMMC()
+ {
+ if (USE_SPI_MICROSD)
+ _sdFile = new SdFile;
+#ifdef COMPILE_SD_MMC
+ else
+ _file = new File;
+#endif // COMPILE_SD_MMC
+ };
+
+ ~FileSdFatMMC()
+ {
+ if (USE_SPI_MICROSD)
+ {
+ ;
+ // if (_sdFile) //operator bool
+ // delete _sdFile;
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ ;
+ // if (_file) //operator bool
+ // delete _file;
+ }
+#endif // COMPILE_SD_MMC
+ };
+
+ operator bool()
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile;
+#ifdef COMPILE_SD_MMC
+ else
+ return _file;
+#endif // COMPILE_SD_MMC
+ return false; // Keep the compiler happy
+ };
+
+ size_t println(const char printMe[])
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->println(printMe);
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->println(printMe);
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ bool open(const char *filepath, oflag_t mode)
+ {
+ if (USE_SPI_MICROSD)
+ {
+ if (_sdFile->open(filepath, mode) == true)
+ return true;
+ return false;
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (mode & O_APPEND)
+ *_file = SD_MMC.open(filepath, FILE_APPEND);
+ else if (mode & O_WRITE)
+ *_file = SD_MMC.open(filepath, FILE_WRITE);
+ else // if (mode & O_READ)
+ *_file = SD_MMC.open(filepath, FILE_READ);
+ if (_file) // operator bool
+ return true;
+ return false;
+ }
+#endif // COMPILE_SD_MMC
+ return false; // Keep the compiler happy
+ };
+
+ uint32_t size()
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->size();
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->size();
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ uint32_t position()
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->position();
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->position();
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ int available()
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->available();
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->available();
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ int read(uint8_t *buf, uint16_t nbyte)
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->read(buf, nbyte);
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->read(buf, nbyte);
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ size_t write(const uint8_t *buf, size_t size)
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->write(buf, size);
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->write(buf, size);
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ void close()
+ {
+ if (USE_SPI_MICROSD)
+ _sdFile->close();
+#ifdef COMPILE_SD_MMC
+ else
+ _file->close();
+#endif // COMPILE_SD_MMC
+ };
+
+ void flush()
+ {
+ if (USE_SPI_MICROSD)
+ _sdFile->sync();
+#ifdef COMPILE_SD_MMC
+ else
+ _file->flush();
+#endif // COMPILE_SD_MMC
+ };
+
+ void updateFileAccessTimestamp()
+ {
+ if (USE_SPI_MICROSD)
+ {
+ if (online.rtc == true)
+ {
+ // ESP32Time returns month:0-11
+ _sdFile->timestamp(T_ACCESS, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay(), rtc.getHour(true),
+ rtc.getMinute(), rtc.getSecond());
+ _sdFile->timestamp(T_WRITE, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay(), rtc.getHour(true),
+ rtc.getMinute(), rtc.getSecond());
+ }
+ }
+ };
+
+ void updateFileCreateTimestamp()
+ {
+ if (USE_SPI_MICROSD)
+ {
+ if (online.rtc == true)
+ {
+ _sdFile->timestamp(T_CREATE, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay(), rtc.getHour(true),
+ rtc.getMinute(), rtc.getSecond()); // ESP32Time returns month:0-11
+ }
+ }
+ };
+
+ void sync()
+ {
+ if (USE_SPI_MICROSD)
+ _sdFile->sync();
+ };
+
+ int fileSize()
+ {
+ if (USE_SPI_MICROSD)
+ return _sdFile->fileSize();
+#ifdef COMPILE_SD_MMC
+ else
+ return _file->size();
+#endif // COMPILE_SD_MMC
+ return 0; // Keep the compiler happy
+ };
+
+ protected:
+ SdFile *_sdFile;
+#ifdef COMPILE_SD_MMC
+ File *_file;
+#endif // COMPILE_SD_MMC
+};
+
+// Update the file access and write time with date and time obtained from GNSS
+// These are SdFile-specific. SD_MMC does this automatically
+void updateDataFileAccess(SdFile *dataFile)
+{
+ if (online.rtc == true)
+ {
+ // ESP32Time returns month:0-11
+ dataFile->timestamp(T_ACCESS, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay(), rtc.getHour(true),
+ rtc.getMinute(), rtc.getSecond());
+ dataFile->timestamp(T_WRITE, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay(), rtc.getHour(true),
+ rtc.getMinute(), rtc.getSecond());
+ }
+}
+
+// Update the file create time with date and time obtained from GNSS
+void updateDataFileCreate(SdFile *dataFile)
+{
+ if (online.rtc == true)
+ dataFile->timestamp(T_CREATE, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay(), rtc.getHour(true),
+ rtc.getMinute(), rtc.getSecond()); // ESP32Time returns month:0-11
+}
diff --git a/Firmware/RTK_Surveyor/Form.ino b/Firmware/RTK_Surveyor/Form.ino
new file mode 100644
index 000000000..8c7647b72
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Form.ino
@@ -0,0 +1,2095 @@
+/*------------------------------------------------------------------------------
+Form.ino
+
+ Start and stop the web-server, provide the form and handle browser input.
+------------------------------------------------------------------------------*/
+
+#ifdef COMPILE_AP
+
+// Once connected to the access point for WiFi Config, the ESP32 sends current setting values in one long string to
+// websocket After user clicks 'save', data is validated via main.js and a long string of values is returned.
+
+bool websocketConnected = false;
+
+class CaptiveRequestHandler : public AsyncWebHandler
+{
+ public:
+ // https://en.wikipedia.org/wiki/Captive_portal
+ String urls[5] = {"/hotspot-detect.html", "/library/test/success.html", "/generate_204", "/ncsi.txt",
+ "/check_network_status.txt"};
+ CaptiveRequestHandler()
+ {
+ }
+ virtual ~CaptiveRequestHandler()
+ {
+ }
+
+ bool canHandle(AsyncWebServerRequest *request)
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ if (request->url().equals(urls[i]))
+ return true;
+ }
+ return false;
+ }
+
+ // Provide a custom small site for redirecting the user to the config site
+ // HTTP redirect does not work and the relative links on the default config site do not work, because the phone is
+ // requesting a different server
+ void handleRequest(AsyncWebServerRequest *request)
+ {
+ String logmessage = "Captive Portal Client:" + request->client()->remoteIP().toString() + " " + request->url();
+ systemPrintln(logmessage);
+ AsyncResponseStream *response = request->beginResponseStream("text/html");
+ response->print("RTK Config");
+ response->print("");
+ response->printf("
",
+ WiFi.softAPIP().toString().c_str());
+ response->printf("
Configure your RTK receiver here
",
+ WiFi.softAPIP().toString().c_str());
+ response->print("
");
+ request->send(response);
+ }
+};
+
+// Start webserver in AP mode
+bool startWebServer(bool startWiFi = true, 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) // Exits calling wifiConnect()
+ break;
+
+ if (settings.mdnsEnable == true)
+ {
+ if (MDNS.begin("rtk") == false) // This should make the module findable from 'rtk.local' in browser
+ systemPrintln("Error setting up MDNS responder!");
+ else
+ MDNS.addService("http", "tcp", 80); // Add service to MDNS-SD
+ }
+
+ incomingSettings = (char *)malloc(AP_CONFIG_SETTING_SIZE);
+ if (!incomingSettings)
+ {
+ systemPrintln("ERROR: Failed to allocate incomingSettings");
+ break;
+ }
+ memset(incomingSettings, 0, AP_CONFIG_SETTING_SIZE);
+
+ // Pre-load settings CSV
+ settingsCSV = (char *)malloc(AP_CONFIG_SETTING_SIZE);
+ if (!settingsCSV)
+ {
+ systemPrintln("ERROR: Failed to allocate settingsCSV");
+ break;
+ }
+ createSettingsString(settingsCSV);
+
+ webserver = new AsyncWebServer(httpPort);
+ if (!webserver)
+ {
+ systemPrintln("ERROR: Failed to allocate webserver");
+ break;
+ }
+ websocket = new AsyncWebSocket("/ws");
+ if (!websocket)
+ {
+ systemPrintln("ERROR: Failed to allocate websocket");
+ break;
+ }
+
+ if (settings.enableCaptivePortal == true)
+ webserver->addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // only when requested from AP
+
+ websocket->onEvent(onWsEvent);
+ webserver->addHandler(websocket);
+
+ // * index.html (not gz'd)
+ // * favicon.ico
+
+ // * /src/bootstrap.bundle.min.js - Needed for popper
+ // * /src/bootstrap.min.css
+ // * /src/bootstrap.min.js
+ // * /src/jquery-3.6.0.min.js
+ // * /src/main.js (not gz'd)
+ // * /src/rtk-setup.png
+ // * /src/style.css
+
+ // * /src/fonts/icomoon.eot
+ // * /src/fonts/icomoon.svg
+ // * /src/fonts/icomoon.ttf
+ // * /src/fonts/icomoon.woof
+
+ // * /listfiles responds with a CSV of files and sizes in root
+ // * /listMessages responds with a CSV of messages supported by this platform
+ // * /listMessagesBase responds with a CSV of RTCM Base messages supported by this platform
+ // * /file allows the download or deletion of a file
+
+ webserver->onNotFound(notFound);
+
+ webserver->onFileUpload(
+ handleUpload); // Run handleUpload function when any file is uploaded. Must be before server.on() calls.
+
+ webserver->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/html", index_html, sizeof(index_html));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/plain", favicon_ico, sizeof(favicon_ico));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/bootstrap.bundle.min.js", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", bootstrap_bundle_min_js,
+ sizeof(bootstrap_bundle_min_js));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/bootstrap.min.css", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/css", bootstrap_min_css, sizeof(bootstrap_min_css));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/bootstrap.min.js", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/javascript", bootstrap_min_js, sizeof(bootstrap_min_js));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/jquery-3.6.0.min.js", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/javascript", jquery_js, sizeof(jquery_js));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/main.js", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/javascript", main_js, sizeof(main_js));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/rtk-setup.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response;
+ if (productVariant == REFERENCE_STATION)
+ response = request->beginResponse_P(200, "image/png", rtkSetup_png, sizeof(rtkSetup_png));
+ else
+ response = request->beginResponse_P(200, "image/png", rtkSetupWiFi_png, sizeof(rtkSetupWiFi_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ // Battery icons
+ webserver->on("/src/BatteryBlank.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", batteryBlank_png, sizeof(batteryBlank_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery0.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery0_png, sizeof(battery0_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery1.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery1_png, sizeof(battery1_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery2.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery2_png, sizeof(battery2_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery3.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery3_png, sizeof(battery3_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/Battery0_Charging.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery0_Charging_png, sizeof(battery0_Charging_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery1_Charging.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery1_Charging_png, sizeof(battery1_Charging_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery2_Charging.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery2_Charging_png, sizeof(battery2_Charging_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+ webserver->on("/src/Battery3_Charging.png", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "image/png", battery3_Charging_png, sizeof(battery3_Charging_png));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/style.css", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", style_css, sizeof(style_css));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/fonts/icomoon.eot", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/plain", icomoon_eot, sizeof(icomoon_eot));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/fonts/icomoon.svg", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/plain", icomoon_svg, sizeof(icomoon_svg));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/fonts/icomoon.ttf", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/plain", icomoon_ttf, sizeof(icomoon_ttf));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ webserver->on("/src/fonts/icomoon.woof", HTTP_GET, [](AsyncWebServerRequest *request) {
+ AsyncWebServerResponse *response =
+ request->beginResponse_P(200, "text/plain", icomoon_woof, sizeof(icomoon_woof));
+ response->addHeader("Content-Encoding", "gzip");
+ request->send(response);
+ });
+
+ // Handler for the /upload form POST
+ webserver->on(
+ "/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handleFirmwareFileUpload);
+
+ // Handler for file manager
+ webserver->on("/listfiles", HTTP_GET, [](AsyncWebServerRequest *request) {
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
+ systemPrintln(logmessage);
+ String files;
+ getFileList(files);
+ request->send(200, "text/plain", files);
+ });
+
+ // Handler for supported messages list
+ webserver->on("/listMessages", HTTP_GET, [](AsyncWebServerRequest *request) {
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
+ systemPrintln(logmessage);
+ String messages;
+ createMessageList(messages);
+ request->send(200, "text/plain", messages);
+ });
+
+ // Handler for supported RTCM/Base messages list
+ webserver->on("/listMessagesBase", HTTP_GET, [](AsyncWebServerRequest *request) {
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
+ systemPrintln(logmessage);
+ String messageList;
+ createMessageListBase(messageList);
+ request->send(200, "text/plain", messageList);
+ });
+
+ // Handler for file manager
+ webserver->on("/file", HTTP_GET, [](AsyncWebServerRequest *request) { handleFileManager(request); });
+
+ webserver->begin();
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Web Server Started");
+ reportHeapNow(false);
+ return true;
+ } while (0);
+
+ // Release the resources
+ stopWebServer();
+ return false;
+}
+
+void stopWebServer()
+{
+ if (webserver != nullptr)
+ {
+ webserver->end();
+ free(webserver);
+ webserver = nullptr;
+ }
+
+ if (websocket != nullptr)
+ {
+ delete websocket;
+ websocket = nullptr;
+ }
+
+ if (settingsCSV != nullptr)
+ {
+ free(settingsCSV);
+ settingsCSV = nullptr;
+ }
+
+ if (incomingSettings != nullptr)
+ {
+ free(incomingSettings);
+ incomingSettings = nullptr;
+ }
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Web Server Stopped");
+ reportHeapNow(false);
+}
+
+void notFound(AsyncWebServerRequest *request)
+{
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
+ systemPrintln(logmessage);
+ request->send(404, "text/plain", "Not found");
+}
+
+// Handler for firmware file downloads
+static void handleFileManager(AsyncWebServerRequest *request)
+{
+ // This section does not tolerate semaphore transactions
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
+
+ if (request->hasParam("name") && request->hasParam("action"))
+ {
+ const char *fileName = request->getParam("name")->value().c_str();
+ const char *fileAction = request->getParam("action")->value().c_str();
+
+ logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url() +
+ "?name=" + String(fileName) + "&action=" + String(fileAction);
+
+ char slashFileName[60];
+ snprintf(slashFileName, sizeof(slashFileName), "/%s", request->getParam("name")->value().c_str());
+
+ bool fileExists;
+ if (USE_SPI_MICROSD)
+ {
+ fileExists = sd->exists(slashFileName);
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ fileExists = SD_MMC.exists(slashFileName);
+ }
+#endif // COMPILE_SD_MMC
+
+ if (fileExists == false)
+ {
+ systemPrintln(logmessage + " ERROR: file does not exist");
+ request->send(400, "text/plain", "ERROR: file does not exist");
+ }
+ else
+ {
+ systemPrintln(logmessage + " file exists");
+
+ if (strcmp(fileAction, "download") == 0)
+ {
+ logmessage += " downloaded";
+
+ if (managerFileOpen == false)
+ {
+ // Allocate the managerTempFile
+ if (!managerTempFile)
+ {
+ managerTempFile = new FileSdFatMMC;
+ if (!managerTempFile)
+ {
+ systemPrintln("Failed to allocate managerTempFile!");
+ return;
+ }
+ }
+
+ if (managerTempFile->open(slashFileName, O_READ) == true)
+ managerFileOpen = true;
+ else
+ systemPrintln("Error: File Manager failed to open file");
+ }
+ else
+ {
+ // File is already in use. Wait your turn.
+ request->send(202, "text/plain", "ERROR: File already downloading");
+ }
+
+ int dataAvailable;
+ dataAvailable = managerTempFile->size() - managerTempFile->position();
+
+ AsyncWebServerResponse *response = request->beginResponse(
+ "text/plain", dataAvailable, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
+ uint32_t bytes = 0;
+ uint32_t availableBytes;
+ availableBytes = managerTempFile->available();
+
+ if (availableBytes > maxLen)
+ {
+ bytes = managerTempFile->read(buffer, maxLen);
+ }
+ else
+ {
+ bytes = managerTempFile->read(buffer, availableBytes);
+ managerTempFile->close();
+
+ managerFileOpen = false;
+
+ websocket->textAll("fmNext,1,"); // Tell browser to send next file if needed
+ }
+
+ return bytes;
+ });
+
+ response->addHeader("Cache-Control", "no-cache");
+ response->addHeader("Content-Disposition", "attachment; filename=" + String(fileName));
+ response->addHeader("Access-Control-Allow-Origin", "*");
+ request->send(response);
+ }
+ else if (strcmp(fileAction, "delete") == 0)
+ {
+ logmessage += " deleted";
+ if (USE_SPI_MICROSD)
+ sd->remove(slashFileName);
+#ifdef COMPILE_SD_MMC
+ else
+ SD_MMC.remove(slashFileName);
+#endif // COMPILE_SD_MMC
+ request->send(200, "text/plain", "Deleted File: " + String(fileName));
+ }
+ else
+ {
+ logmessage += " ERROR: invalid action param supplied";
+ request->send(400, "text/plain", "ERROR: invalid action param supplied");
+ }
+ systemPrintln(logmessage);
+ }
+ }
+ else
+ {
+ request->send(400, "text/plain", "ERROR: name and action params required");
+ }
+}
+
+// Handler for firmware file upload
+static void handleFirmwareFileUpload(AsyncWebServerRequest *request, String fileName, size_t index, uint8_t *data,
+ size_t len, bool final)
+{
+ if (!index)
+ {
+ // Check file name against valid firmware names
+ const char *BIN_EXT = "bin";
+ const char *BIN_HEADER = "RTK_Surveyor_Firmware";
+
+ int fnameLen = fileName.length();
+ char fname[fnameLen + 2] = {'/'}; // Filename must start with / or VERY bad things happen on SD_MMC
+ fileName.toCharArray(&fname[1], fnameLen + 1);
+ fname[fnameLen + 1] = '\0'; // Terminate array
+
+ // Check 'bin' extension
+ if (strcmp(BIN_EXT, &fname[strlen(fname) - strlen(BIN_EXT)]) == 0)
+ {
+ // Check for 'RTK_Surveyor_Firmware' start of file name
+ if (strncmp(fname, BIN_HEADER, strlen(BIN_HEADER)) == 0)
+ {
+ // Begin update process
+ if (!Update.begin(UPDATE_SIZE_UNKNOWN))
+ {
+ Update.printError(Serial);
+ return request->send(400, "text/plain", "OTA could not begin");
+ }
+ }
+ else
+ {
+ systemPrintf("Unknown: %s\r\n", fname);
+ return request->send(400, "text/html", "Error: Unknown file type");
+ }
+ }
+ else
+ {
+ systemPrintf("Unknown: %s\r\n", fname);
+ return request->send(400, "text/html", "Error: Unknown file type");
+ }
+ }
+
+ // Write chunked data to the free sketch space
+ if (len)
+ {
+ if (Update.write(data, len) != len)
+ return request->send(400, "text/plain", "OTA could not begin");
+ else
+ {
+ binBytesSent += len;
+
+ // Send an update to browser every 100k
+ if (binBytesSent - binBytesLastUpdate > 100000)
+ {
+ binBytesLastUpdate = binBytesSent;
+
+ char bytesSentMsg[100];
+ snprintf(bytesSentMsg, sizeof(bytesSentMsg), "%'d bytes sent", binBytesSent);
+
+ systemPrintf("bytesSentMsg: %s\r\n", bytesSentMsg);
+
+ char statusMsg[200] = {'\0'};
+ stringRecord(statusMsg, "firmwareUploadStatus",
+ bytesSentMsg); // Convert to "firmwareUploadMsg,11214 bytes sent,"
+
+ systemPrintf("msg: %s\r\n", statusMsg);
+ websocket->textAll(statusMsg);
+ }
+ }
+ }
+
+ if (final)
+ {
+ if (!Update.end(true))
+ {
+ Update.printError(Serial);
+ return request->send(400, "text/plain", "Could not end OTA");
+ }
+ else
+ {
+ websocket->textAll("firmwareUploadComplete,1,");
+ systemPrintln("Firmware update complete. Restarting");
+ delay(500);
+ ESP.restart();
+ }
+ }
+}
+
+// Events triggered by web sockets
+void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data,
+ size_t len)
+{
+ if (type == WS_EVT_CONNECT)
+ {
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Websocket client connected");
+ client->text(settingsCSV);
+ lastDynamicDataUpdate = millis();
+ websocketConnected = true;
+ }
+ else if (type == WS_EVT_DISCONNECT)
+ {
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Websocket client disconnected");
+
+ // User has either refreshed the page or disconnected. Recompile the current settings.
+ createSettingsString(settingsCSV);
+ websocketConnected = false;
+ }
+ else if (type == WS_EVT_DATA)
+ {
+ if (currentlyParsingData == false)
+ {
+ for (int i = 0; i < len; i++)
+ {
+ incomingSettings[incomingSettingsSpot++] = data[i];
+ incomingSettingsSpot %= AP_CONFIG_SETTING_SIZE;
+ }
+ timeSinceLastIncomingSetting = millis();
+ }
+ }
+ else
+ {
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("onWsEvent: unrecognised AwsEventType %d\r\n", type);
+ }
+}
+// Create a csv string with current settings
+void createSettingsString(char *newSettings)
+{
+ char tagText[32];
+ char nameText[64];
+
+ newSettings[0] = '\0'; // Erase current settings string
+
+ // System Info
+ char apPlatformPrefix[80];
+ strncpy(apPlatformPrefix, platformPrefixTable[productVariant], sizeof(apPlatformPrefix));
+ stringRecord(newSettings, "platformPrefix", apPlatformPrefix);
+
+ char apRtkFirmwareVersion[86];
+ getFirmwareVersion(apRtkFirmwareVersion, sizeof(apRtkFirmwareVersion), true);
+ stringRecord(newSettings, "rtkFirmwareVersion", apRtkFirmwareVersion);
+
+ if (!configureViaEthernet) // ZED type is unknown if we are in configure-via-ethernet mode
+ {
+ char apZedPlatform[50];
+ if (zedModuleType == PLATFORM_F9P)
+ strcpy(apZedPlatform, "ZED-F9P");
+ else if (zedModuleType == PLATFORM_F9R)
+ strcpy(apZedPlatform, "ZED-F9R");
+
+ char apZedFirmwareVersion[80];
+ snprintf(apZedFirmwareVersion, sizeof(apZedFirmwareVersion), "%s Firmware: %s ID: %s", apZedPlatform,
+ zedFirmwareVersion, zedUniqueId);
+ stringRecord(newSettings, "zedFirmwareVersion", apZedFirmwareVersion);
+ stringRecord(newSettings, "zedFirmwareVersionInt", zedFirmwareVersionInt);
+ }
+ else
+ {
+ char apZedFirmwareVersion[80];
+ snprintf(apZedFirmwareVersion, sizeof(apZedFirmwareVersion), "ZED-F9: Unknown");
+ stringRecord(newSettings, "zedFirmwareVersion", apZedFirmwareVersion);
+ }
+
+ char apDeviceBTID[30];
+ snprintf(apDeviceBTID, sizeof(apDeviceBTID), "Device Bluetooth ID: %02X%02X", btMACAddress[4], btMACAddress[5]);
+ stringRecord(newSettings, "deviceBTID", apDeviceBTID);
+
+ // GNSS Config
+ stringRecord(newSettings, "measurementRateHz", 1000.0 / settings.measurementRate, 2); // 2 = decimals to print
+ stringRecord(newSettings, "dynamicModel", settings.dynamicModel);
+ stringRecord(newSettings, "ubxConstellationsGPS", settings.ubxConstellations[0].enabled); // GPS
+ stringRecord(newSettings, "ubxConstellationsSBAS", settings.ubxConstellations[1].enabled); // SBAS
+ stringRecord(newSettings, "ubxConstellationsGalileo", settings.ubxConstellations[2].enabled); // Galileo
+ stringRecord(newSettings, "ubxConstellationsBeiDou", settings.ubxConstellations[3].enabled); // BeiDou
+ stringRecord(newSettings, "ubxConstellationsGLONASS", settings.ubxConstellations[5].enabled); // GLONASS
+
+ // Base Config
+ stringRecord(newSettings, "baseTypeSurveyIn", !settings.fixedBase);
+ stringRecord(newSettings, "baseTypeFixed", settings.fixedBase);
+ stringRecord(newSettings, "observationSeconds", settings.observationSeconds);
+ stringRecord(newSettings, "observationPositionAccuracy", settings.observationPositionAccuracy, 2);
+
+ if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
+ {
+ stringRecord(newSettings, "fixedBaseCoordinateTypeECEF", true);
+ stringRecord(newSettings, "fixedBaseCoordinateTypeGeo", false);
+ }
+ else
+ {
+ stringRecord(newSettings, "fixedBaseCoordinateTypeECEF", false);
+ stringRecord(newSettings, "fixedBaseCoordinateTypeGeo", true);
+ }
+
+ stringRecord(newSettings, "fixedEcefX", settings.fixedEcefX, 3);
+ stringRecord(newSettings, "fixedEcefY", settings.fixedEcefY, 3);
+ stringRecord(newSettings, "fixedEcefZ", settings.fixedEcefZ, 3);
+ stringRecord(newSettings, "fixedLat", settings.fixedLat, haeNumberOfDecimals);
+ stringRecord(newSettings, "fixedLong", settings.fixedLong, haeNumberOfDecimals);
+ stringRecord(newSettings, "fixedAltitude", settings.fixedAltitude, 4);
+
+ stringRecord(newSettings, "enableNtripServer", settings.enableNtripServer);
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char name[50];
+ sprintf(name, "ntripServer_%s_%d", "CasterHost", serverIndex);
+ stringRecord(newSettings, name, &settings.ntripServer_CasterHost[serverIndex][0]);
+ sprintf(name, "ntripServer_%s_%d", "CasterPort", serverIndex);
+ stringRecord(newSettings, name, settings.ntripServer_CasterPort[serverIndex]);
+ sprintf(name, "ntripServer_%s_%d", "CasterUser", serverIndex);
+ stringRecord(newSettings, name, &settings.ntripServer_CasterUser[serverIndex][0]);
+ sprintf(name, "ntripServer_%s_%d", "CasterUserPW", serverIndex);
+ stringRecord(newSettings, name, &settings.ntripServer_CasterUserPW[serverIndex][0]);
+ sprintf(name, "ntripServer_%s_%d", "MountPoint", serverIndex);
+ stringRecord(newSettings, name, &settings.ntripServer_MountPoint[serverIndex][0]);
+ sprintf(name, "ntripServer_%s_%d", "MountPointPW", serverIndex);
+ stringRecord(newSettings, name, &settings.ntripServer_MountPointPW[serverIndex][0]);
+ }
+
+ stringRecord(newSettings, "enableNtripClient", settings.enableNtripClient);
+ stringRecord(newSettings, "ntripClient_CasterHost", settings.ntripClient_CasterHost);
+ stringRecord(newSettings, "ntripClient_CasterPort", settings.ntripClient_CasterPort);
+ stringRecord(newSettings, "ntripClient_CasterUser", settings.ntripClient_CasterUser);
+ stringRecord(newSettings, "ntripClient_CasterUserPW", settings.ntripClient_CasterUserPW);
+ stringRecord(newSettings, "ntripClient_MountPoint", settings.ntripClient_MountPoint);
+ stringRecord(newSettings, "ntripClient_MountPointPW", settings.ntripClient_MountPointPW);
+ stringRecord(newSettings, "ntripClient_TransmitGGA", settings.ntripClient_TransmitGGA);
+
+ // Sensor Fusion Config
+ stringRecord(newSettings, "enableSensorFusion", settings.enableSensorFusion);
+ stringRecord(newSettings, "autoIMUmountAlignment", settings.autoIMUmountAlignment);
+
+ // System Config
+ stringRecord(newSettings, "enableUART2UBXIn", settings.enableUART2UBXIn);
+ stringRecord(newSettings, "enableLogging", settings.enableLogging);
+ stringRecord(newSettings, "enableARPLogging", settings.enableARPLogging);
+ stringRecord(newSettings, "ARPLoggingInterval", settings.ARPLoggingInterval_s);
+ stringRecord(newSettings, "maxLogTime_minutes", settings.maxLogTime_minutes);
+ stringRecord(newSettings, "maxLogLength_minutes", settings.maxLogLength_minutes);
+
+ char sdCardSizeChar[20];
+ String cardSize;
+ stringHumanReadableSize(cardSize, sdCardSize);
+ cardSize.toCharArray(sdCardSizeChar, sizeof(sdCardSizeChar));
+ char sdFreeSpaceChar[20];
+ String freeSpace;
+ stringHumanReadableSize(freeSpace, sdFreeSpace);
+ freeSpace.toCharArray(sdFreeSpaceChar, sizeof(sdFreeSpaceChar));
+
+ stringRecord(newSettings, "sdFreeSpace", sdFreeSpaceChar);
+ stringRecord(newSettings, "sdSize", sdCardSizeChar);
+
+ stringRecord(newSettings, "enableResetDisplay", settings.enableResetDisplay);
+
+ // Ethernet
+ stringRecord(newSettings, "ethernetDHCP", settings.ethernetDHCP);
+ char ipAddressChar[20];
+ snprintf(ipAddressChar, sizeof(ipAddressChar), "%s", settings.ethernetIP.toString().c_str());
+ stringRecord(newSettings, "ethernetIP", ipAddressChar);
+ snprintf(ipAddressChar, sizeof(ipAddressChar), "%s", settings.ethernetDNS.toString().c_str());
+ stringRecord(newSettings, "ethernetDNS", ipAddressChar);
+ snprintf(ipAddressChar, sizeof(ipAddressChar), "%s", settings.ethernetGateway.toString().c_str());
+ stringRecord(newSettings, "ethernetGateway", ipAddressChar);
+ snprintf(ipAddressChar, sizeof(ipAddressChar), "%s", settings.ethernetSubnet.toString().c_str());
+ stringRecord(newSettings, "ethernetSubnet", ipAddressChar);
+ stringRecord(newSettings, "httpPort", settings.httpPort);
+ stringRecord(newSettings, "ethernetNtpPort", settings.ethernetNtpPort);
+ stringRecord(newSettings, "pvtClientPort", settings.pvtClientPort);
+ stringRecord(newSettings, "pvtClientHost", settings.pvtClientHost);
+
+ // Network layer
+ stringRecord(newSettings, "defaultNetworkType", settings.defaultNetworkType);
+ stringRecord(newSettings, "enableNetworkFailover", settings.enableNetworkFailover);
+
+ // NTP
+ stringRecord(newSettings, "ntpPollExponent", settings.ntpPollExponent);
+ stringRecord(newSettings, "ntpPrecision", settings.ntpPrecision);
+ stringRecord(newSettings, "ntpRootDelay", settings.ntpRootDelay);
+ stringRecord(newSettings, "ntpRootDispersion", settings.ntpRootDispersion);
+ stringRecord(newSettings, "ntpPollExponent", settings.ntpPollExponent);
+ char ntpRefId[5];
+ snprintf(ntpRefId, sizeof(ntpRefId), "%s", settings.ntpReferenceId);
+ stringRecord(newSettings, "ntpReferenceId", ntpRefId);
+
+ // Automatic firmware update settings
+ stringRecord(newSettings, "enableAutoFirmwareUpdate", settings.enableAutoFirmwareUpdate);
+ stringRecord(newSettings, "autoFirmwareCheckMinutes", settings.autoFirmwareCheckMinutes);
+
+ // Turn on SD display block last
+ stringRecord(newSettings, "sdMounted", online.microSD);
+
+ // Port Config
+ stringRecord(newSettings, "dataPortBaud", settings.dataPortBaud);
+ stringRecord(newSettings, "radioPortBaud", settings.radioPortBaud);
+ stringRecord(newSettings, "dataPortChannel", settings.dataPortChannel);
+
+ // L-Band
+ char hardwareID[13];
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X%02X%02X%02X", lbandMACAddress[0], lbandMACAddress[1],
+ lbandMACAddress[2], lbandMACAddress[3], lbandMACAddress[4], lbandMACAddress[5]);
+ stringRecord(newSettings, "hardwareID", hardwareID);
+
+ char apDaysRemaining[20];
+ if (strlen(settings.pointPerfectCurrentKey) > 0)
+ {
+#ifdef COMPILE_L_BAND
+ int daysRemaining = daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1);
+ snprintf(apDaysRemaining, sizeof(apDaysRemaining), "%d", daysRemaining);
+#endif // COMPILE_L_BAND
+ }
+ else
+ snprintf(apDaysRemaining, sizeof(apDaysRemaining), "No Keys");
+
+ stringRecord(newSettings, "daysRemaining", apDaysRemaining);
+
+ stringRecord(newSettings, "pointPerfectDeviceProfileToken", settings.pointPerfectDeviceProfileToken);
+ stringRecord(newSettings, "enablePointPerfectCorrections", settings.enablePointPerfectCorrections);
+ stringRecord(newSettings, "autoKeyRenewal", settings.autoKeyRenewal);
+ stringRecord(newSettings, "geographicRegion", settings.geographicRegion);
+
+ // External PPS/Triggers
+ stringRecord(newSettings, "enableExternalPulse", settings.enableExternalPulse);
+ stringRecord(newSettings, "externalPulseTimeBetweenPulse_us", settings.externalPulseTimeBetweenPulse_us);
+ stringRecord(newSettings, "externalPulseLength_us", settings.externalPulseLength_us);
+ stringRecord(newSettings, "externalPulsePolarity", settings.externalPulsePolarity);
+ stringRecord(newSettings, "enableExternalHardwareEventLogging", settings.enableExternalHardwareEventLogging);
+
+ // Profiles
+ stringRecord(
+ newSettings, "profileName",
+ profileNames[profileNumber]); // Must come before profile number so AP config page JS has name before number
+ stringRecord(newSettings, "profileNumber", profileNumber);
+ for (int index = 0; index < MAX_PROFILE_COUNT; index++)
+ {
+ snprintf(tagText, sizeof(tagText), "profile%dName", index);
+ snprintf(nameText, sizeof(nameText), "%d: %s", index + 1, profileNames[index]);
+ stringRecord(newSettings, tagText, nameText);
+ }
+ // stringRecord(newSettings, "activeProfiles", activeProfiles);
+
+ // System state at power on. Convert various system states to either Rover or Base or NTP.
+ int lastState; // 0 = Rover, 1 = Base, 2 = NTP
+ if (productVariant == REFERENCE_STATION)
+ {
+ lastState = 1; // Default Base
+ 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)
+ lastState = 2;
+ }
+ else
+ {
+ lastState = 0; // Default Rover
+ if (settings.lastState >= STATE_BASE_NOT_STARTED && settings.lastState <= STATE_BASE_FIXED_TRANSMITTING)
+ lastState = 1;
+ }
+ stringRecord(newSettings, "baseRoverSetup", lastState);
+
+ // Bluetooth radio type
+ stringRecord(newSettings, "bluetoothRadioType", settings.bluetoothRadioType);
+
+ // Current coordinates come from HPPOSLLH call back
+ stringRecord(newSettings, "geodeticLat", latitude, haeNumberOfDecimals);
+ stringRecord(newSettings, "geodeticLon", longitude, haeNumberOfDecimals);
+ stringRecord(newSettings, "geodeticAlt", altitude, 3);
+
+ double ecefX = 0;
+ double ecefY = 0;
+ double ecefZ = 0;
+
+ geodeticToEcef(latitude, longitude, altitude, &ecefX, &ecefY, &ecefZ);
+
+ stringRecord(newSettings, "ecefX", ecefX, 3);
+ stringRecord(newSettings, "ecefY", ecefY, 3);
+ stringRecord(newSettings, "ecefZ", ecefZ, 3);
+
+ // Antenna height and ARP
+ stringRecord(newSettings, "antennaHeight", settings.antennaHeight);
+ stringRecord(newSettings, "antennaReferencePoint", settings.antennaReferencePoint, 1);
+
+ // Radio / ESP-Now settings
+ char radioMAC[18]; // Send radio MAC
+ snprintf(radioMAC, sizeof(radioMAC), "%02X:%02X:%02X:%02X:%02X:%02X", wifiMACAddress[0], wifiMACAddress[1],
+ wifiMACAddress[2], wifiMACAddress[3], wifiMACAddress[4], wifiMACAddress[5]);
+ stringRecord(newSettings, "radioMAC", radioMAC);
+ stringRecord(newSettings, "radioType", settings.radioType);
+ stringRecord(newSettings, "espnowPeerCount", settings.espnowPeerCount);
+ for (int index = 0; index < settings.espnowPeerCount; index++)
+ {
+ snprintf(tagText, sizeof(tagText), "peerMAC%d", index);
+ snprintf(nameText, sizeof(nameText), "%02X:%02X:%02X:%02X:%02X:%02X", settings.espnowPeers[index][0],
+ settings.espnowPeers[index][1], settings.espnowPeers[index][2], settings.espnowPeers[index][3],
+ settings.espnowPeers[index][4], settings.espnowPeers[index][5]);
+ stringRecord(newSettings, tagText, nameText);
+ }
+ stringRecord(newSettings, "espnowBroadcast", settings.espnowBroadcast);
+
+ stringRecord(newSettings, "logFileName", logFileName);
+
+ if (HAS_NO_BATTERY) // Ref Stn does not have a battery
+ {
+ stringRecord(newSettings, "batteryIconFileName", (char *)"src/BatteryBlank.png");
+ stringRecord(newSettings, "batteryPercent", (char *)" ");
+ }
+ else
+ {
+ // Determine battery icon
+ int iconLevel = 0;
+ if (battLevel < 25)
+ iconLevel = 0;
+ else if (battLevel < 50)
+ iconLevel = 1;
+ else if (battLevel < 75)
+ iconLevel = 2;
+ else // batt level > 75
+ iconLevel = 3;
+
+ char batteryIconFileName[sizeof("src/Battery2_Charging.png__")]; // sizeof() includes 1 for \0 termination
+
+ if (externalPowerConnected)
+ snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d_Charging.png", iconLevel);
+ else
+ snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d.png", iconLevel);
+
+ stringRecord(newSettings, "batteryIconFileName", batteryIconFileName);
+
+ // Determine battery percent
+ char batteryPercent[sizeof("+100%__")];
+ int tempLevel = battLevel;
+ if (tempLevel > 100)
+ tempLevel = 100;
+
+ if (externalPowerConnected)
+ snprintf(batteryPercent, sizeof(batteryPercent), "+%d%%", tempLevel);
+ else
+ snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", tempLevel);
+ stringRecord(newSettings, "batteryPercent", batteryPercent);
+ }
+
+ stringRecord(newSettings, "minElev", settings.minElev);
+ stringRecord(newSettings, "imuYaw", settings.imuYaw);
+ stringRecord(newSettings, "imuPitch", settings.imuPitch);
+ stringRecord(newSettings, "imuRoll", settings.imuRoll);
+ stringRecord(newSettings, "sfDisableWheelDirection", settings.sfDisableWheelDirection);
+ stringRecord(newSettings, "sfCombineWheelTicks", settings.sfCombineWheelTicks);
+ stringRecord(newSettings, "rateNavPrio", settings.rateNavPrio);
+ stringRecord(newSettings, "sfUseSpeed", settings.sfUseSpeed);
+ stringRecord(newSettings, "coordinateInputType", settings.coordinateInputType);
+ // stringRecord(newSettings, "lbandFixTimeout_seconds", settings.lbandFixTimeout_seconds);
+
+ if (zedModuleType == PLATFORM_F9R)
+ stringRecord(newSettings, "minCNO", settings.minCNO_F9R);
+ else
+ stringRecord(newSettings, "minCNO", settings.minCNO_F9P);
+
+ stringRecord(newSettings, "mdnsEnable", settings.mdnsEnable);
+
+ // Add ECEF and Geodetic station data to the end of settings
+ for (int index = 0; index < COMMON_COORDINATES_MAX_STATIONS; index++) // Arbitrary 50 station limit
+ {
+ // stationInfo example: LocationA,-1280206.568,-4716804.403,4086665.484
+ char stationInfo[100];
+
+ // Try SD, then LFS
+ if (getFileLineSD(stationCoordinateECEFFileName, index, stationInfo, sizeof(stationInfo)) ==
+ true) // fileName, lineNumber, array, arraySize
+ {
+ trim(stationInfo); // Remove trailing whitespace
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("ECEF SD station %d - found: %s\r\n", index, stationInfo);
+
+ replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side
+ snprintf(tagText, sizeof(tagText), "stationECEF%d", index);
+ stringRecord(newSettings, tagText, stationInfo);
+ }
+ else if (getFileLineLFS(stationCoordinateECEFFileName, index, stationInfo, sizeof(stationInfo)) ==
+ true) // fileName, lineNumber, array, arraySize
+ {
+ trim(stationInfo); // Remove trailing whitespace
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("ECEF LFS station %d - found: %s\r\n", index, stationInfo);
+
+ replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side
+ snprintf(tagText, sizeof(tagText), "stationECEF%d", index);
+ stringRecord(newSettings, tagText, stationInfo);
+ }
+ else
+ {
+ // We could not find this line
+ break;
+ }
+ }
+
+ for (int index = 0; index < COMMON_COORDINATES_MAX_STATIONS; index++) // Arbitrary 50 station limit
+ {
+ // stationInfo example: LocationA,40.09029479,-105.18505761,1560.089
+ char stationInfo[100];
+
+ // Try SD, then LFS
+ if (getFileLineSD(stationCoordinateGeodeticFileName, index, stationInfo, sizeof(stationInfo)) ==
+ true) // fileName, lineNumber, array, arraySize
+ {
+ trim(stationInfo); // Remove trailing whitespace
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("Geo SD station %d - found: %s\r\n", index, stationInfo);
+
+ replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side
+ snprintf(tagText, sizeof(tagText), "stationGeodetic%d", index);
+ stringRecord(newSettings, tagText, stationInfo);
+ }
+ else if (getFileLineLFS(stationCoordinateGeodeticFileName, index, stationInfo, sizeof(stationInfo)) ==
+ true) // fileName, lineNumber, array, arraySize
+ {
+ trim(stationInfo); // Remove trailing whitespace
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("Geo LFS station %d - found: %s\r\n", index, stationInfo);
+
+ replaceCharacter(stationInfo, ',', ' '); // Change all , to ' ' for easier parsing on the JS side
+ snprintf(tagText, sizeof(tagText), "stationGeodetic%d", index);
+ stringRecord(newSettings, tagText, stationInfo);
+ }
+ else
+ {
+ // We could not find this line
+ break;
+ }
+ }
+
+ // Add WiFi credential table
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ snprintf(tagText, sizeof(tagText), "wifiNetwork%dSSID", x);
+ stringRecord(newSettings, tagText, settings.wifiNetworks[x].ssid);
+
+ snprintf(tagText, sizeof(tagText), "wifiNetwork%dPassword", x);
+ stringRecord(newSettings, tagText, settings.wifiNetworks[x].password);
+ }
+
+ // Drop downs on the AP config page expect a value, whereas bools get stringRecord as true/false
+ if (settings.wifiConfigOverAP == true)
+ stringRecord(newSettings, "wifiConfigOverAP", 1); // 1 = AP mode, 0 = WiFi
+ else
+ stringRecord(newSettings, "wifiConfigOverAP", 0); // 1 = AP mode, 0 = WiFi
+
+ stringRecord(newSettings, "enablePvtServer", settings.enablePvtServer);
+ stringRecord(newSettings, "enablePvtClient", settings.enablePvtClient);
+ stringRecord(newSettings, "pvtServerPort", settings.pvtServerPort);
+ stringRecord(newSettings, "enablePvtUdpServer", settings.enablePvtUdpServer);
+ stringRecord(newSettings, "pvtUdpServerPort", settings.pvtUdpServerPort);
+ stringRecord(newSettings, "enableRCFirmware", enableRCFirmware);
+
+ // New settings not yet integrated
+ //...
+
+ strcat(newSettings, "\0");
+ systemPrintf("newSettings len: %d\r\n", strlen(newSettings));
+ systemPrintf("newSettings: %s\r\n", newSettings);
+}
+
+// Create a csv string with the dynamic data to update (current coordinates, battery level, etc)
+void createDynamicDataString(char *settingsCSV)
+{
+ settingsCSV[0] = '\0'; // Erase current settings string
+
+ // Current coordinates come from HPPOSLLH call back
+ stringRecord(settingsCSV, "geodeticLat", latitude, haeNumberOfDecimals);
+ stringRecord(settingsCSV, "geodeticLon", longitude, haeNumberOfDecimals);
+ stringRecord(settingsCSV, "geodeticAlt", altitude, 3);
+
+ double ecefX = 0;
+ double ecefY = 0;
+ double ecefZ = 0;
+
+ geodeticToEcef(latitude, longitude, altitude, &ecefX, &ecefY, &ecefZ);
+
+ stringRecord(settingsCSV, "ecefX", ecefX, 3);
+ stringRecord(settingsCSV, "ecefY", ecefY, 3);
+ stringRecord(settingsCSV, "ecefZ", ecefZ, 3);
+
+ if (HAS_NO_BATTERY) // Ref Stn does not have a battery
+ {
+ stringRecord(settingsCSV, "batteryIconFileName", (char *)"src/BatteryBlank.png");
+ stringRecord(settingsCSV, "batteryPercent", (char *)" ");
+ }
+ else
+ {
+ // Determine battery icon
+ int iconLevel = 0;
+ if (battLevel < 25)
+ iconLevel = 0;
+ else if (battLevel < 50)
+ iconLevel = 1;
+ else if (battLevel < 75)
+ iconLevel = 2;
+ else // batt level > 75
+ iconLevel = 3;
+
+ char batteryIconFileName[sizeof("src/Battery2_Charging.png__")]; // sizeof() includes 1 for \0 termination
+
+ if (externalPowerConnected)
+ snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d_Charging.png", iconLevel);
+ else
+ snprintf(batteryIconFileName, sizeof(batteryIconFileName), "src/Battery%d.png", iconLevel);
+
+ stringRecord(settingsCSV, "batteryIconFileName", batteryIconFileName);
+
+ // Determine battery percent
+ char batteryPercent[sizeof("+100%__")];
+ if (externalPowerConnected)
+ snprintf(batteryPercent, sizeof(batteryPercent), "+%d%%", battLevel);
+ else
+ snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", battLevel);
+ stringRecord(settingsCSV, "batteryPercent", batteryPercent);
+ }
+
+ strcat(settingsCSV, "\0");
+}
+
+// Given a settingName, and string value, update a given setting
+void updateSettingWithValue(const char *settingName, const char *settingValueStr)
+{
+ char *ptr;
+ double settingValue = strtod(settingValueStr, &ptr);
+
+ bool settingValueBool = false;
+ if (strcmp(settingValueStr, "true") == 0)
+ settingValueBool = true;
+
+ if (strcmp(settingName, "maxLogTime_minutes") == 0)
+ settings.maxLogTime_minutes = settingValue;
+ else if (strcmp(settingName, "maxLogLength_minutes") == 0)
+ settings.maxLogLength_minutes = settingValue;
+ else if (strcmp(settingName, "measurementRateHz") == 0)
+ {
+ settings.measurementRate = (int)(1000.0 / settingValue);
+
+ // This is one of the first settings to be received. If seen, remove the station files.
+ removeFile(stationCoordinateECEFFileName);
+ removeFile(stationCoordinateGeodeticFileName);
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Station coordinate files removed");
+ }
+ else if (strcmp(settingName, "dynamicModel") == 0)
+ settings.dynamicModel = settingValue;
+ else if (strcmp(settingName, "baseTypeFixed") == 0)
+ settings.fixedBase = settingValueBool;
+ else if (strcmp(settingName, "observationSeconds") == 0)
+ settings.observationSeconds = settingValue;
+ else if (strcmp(settingName, "observationPositionAccuracy") == 0)
+ settings.observationPositionAccuracy = settingValue;
+ else if (strcmp(settingName, "fixedBaseCoordinateTypeECEF") == 0)
+ settings.fixedBaseCoordinateType =
+ !settingValueBool; // When ECEF is true, fixedBaseCoordinateType = 0 (COORD_TYPE_ECEF)
+ else if (strcmp(settingName, "fixedEcefX") == 0)
+ settings.fixedEcefX = settingValue;
+ else if (strcmp(settingName, "fixedEcefY") == 0)
+ settings.fixedEcefY = settingValue;
+ else if (strcmp(settingName, "fixedEcefZ") == 0)
+ settings.fixedEcefZ = settingValue;
+ else if (strcmp(settingName, "fixedLatText") == 0)
+ {
+ double newCoordinate = 0.0;
+ CoordinateInputType newCoordinateInputType =
+ coordinateIdentifyInputType((char *)settingValueStr, &newCoordinate);
+ if (newCoordinateInputType == COORDINATE_INPUT_TYPE_INVALID_UNKNOWN)
+ settings.fixedLat = 0.0;
+ else
+ {
+ settings.fixedLat = newCoordinate;
+ settings.coordinateInputType = newCoordinateInputType;
+ }
+ }
+ else if (strcmp(settingName, "fixedLongText") == 0)
+ {
+ // Lat defines the settings.coordinateInputType. Don't update it here
+ double newCoordinate = 0.0;
+ if (coordinateIdentifyInputType((char *)settingValueStr, &newCoordinate) ==
+ COORDINATE_INPUT_TYPE_INVALID_UNKNOWN)
+ settings.fixedLong = 0.0;
+ else
+ settings.fixedLong = newCoordinate;
+ }
+ else if (strcmp(settingName, "fixedAltitude") == 0)
+ settings.fixedAltitude = settingValue;
+ else if (strcmp(settingName, "dataPortBaud") == 0)
+ settings.dataPortBaud = settingValue;
+ else if (strcmp(settingName, "radioPortBaud") == 0)
+ settings.radioPortBaud = settingValue;
+ else if (strcmp(settingName, "enableUART2UBXIn") == 0)
+ settings.enableUART2UBXIn = settingValueBool;
+ else if (strcmp(settingName, "enableLogging") == 0)
+ settings.enableLogging = settingValueBool;
+ else if (strcmp(settingName, "enableARPLogging") == 0)
+ settings.enableARPLogging = settingValueBool;
+ else if (strcmp(settingName, "ARPLoggingInterval") == 0)
+ settings.ARPLoggingInterval_s = settingValue;
+ else if (strcmp(settingName, "dataPortChannel") == 0)
+ settings.dataPortChannel = (muxConnectionType_e)settingValue;
+ else if (strcmp(settingName, "autoIMUmountAlignment") == 0)
+ settings.autoIMUmountAlignment = settingValueBool;
+ else if (strcmp(settingName, "enableSensorFusion") == 0)
+ settings.enableSensorFusion = settingValueBool;
+ else if (strcmp(settingName, "enableResetDisplay") == 0)
+ settings.enableResetDisplay = settingValueBool;
+
+ else if (strcmp(settingName, "enableExternalPulse") == 0)
+ settings.enableExternalPulse = settingValueBool;
+ else if (strcmp(settingName, "externalPulseTimeBetweenPulse_us") == 0)
+ settings.externalPulseTimeBetweenPulse_us = settingValue;
+ else if (strcmp(settingName, "externalPulseLength_us") == 0)
+ settings.externalPulseLength_us = settingValue;
+ else if (strcmp(settingName, "externalPulsePolarity") == 0)
+ settings.externalPulsePolarity = (pulseEdgeType_e)settingValue;
+ else if (strcmp(settingName, "enableExternalHardwareEventLogging") == 0)
+ settings.enableExternalHardwareEventLogging = settingValueBool;
+ else if (strcmp(settingName, "profileName") == 0)
+ {
+ strcpy(settings.profileName, settingValueStr);
+ setProfileName(profileNumber); // Copy the current settings.profileName into the array of profile names at
+ // location profileNumber
+ }
+ else if (strcmp(settingName, "enableNtripServer") == 0)
+ settings.enableNtripServer = settingValueBool;
+
+ else if (strcmp(settingName, "enableNtripClient") == 0)
+ settings.enableNtripClient = settingValueBool;
+ else if (strcmp(settingName, "ntripClient_CasterHost") == 0)
+ strcpy(settings.ntripClient_CasterHost, settingValueStr);
+ else if (strcmp(settingName, "ntripClient_CasterPort") == 0)
+ settings.ntripClient_CasterPort = settingValue;
+ else if (strcmp(settingName, "ntripClient_CasterUser") == 0)
+ strcpy(settings.ntripClient_CasterUser, settingValueStr);
+ else if (strcmp(settingName, "ntripClient_CasterUserPW") == 0)
+ strcpy(settings.ntripClient_CasterUserPW, settingValueStr);
+ else if (strcmp(settingName, "ntripClient_MountPoint") == 0)
+ strcpy(settings.ntripClient_MountPoint, settingValueStr);
+ else if (strcmp(settingName, "ntripClient_MountPointPW") == 0)
+ strcpy(settings.ntripClient_MountPointPW, settingValueStr);
+ else if (strcmp(settingName, "ntripClient_TransmitGGA") == 0)
+ settings.ntripClient_TransmitGGA = settingValueBool;
+
+ else if (strcmp(settingName, "serialTimeoutGNSS") == 0)
+ settings.serialTimeoutGNSS = settingValue;
+ else if (strcmp(settingName, "pointPerfectDeviceProfileToken") == 0)
+ strcpy(settings.pointPerfectDeviceProfileToken, settingValueStr);
+ else if (strcmp(settingName, "enablePointPerfectCorrections") == 0)
+ settings.enablePointPerfectCorrections = settingValueBool;
+ else if (strcmp(settingName, "autoKeyRenewal") == 0)
+ settings.autoKeyRenewal = settingValueBool;
+ else if (strcmp(settingName, "antennaHeight") == 0)
+ settings.antennaHeight = settingValue;
+ else if (strcmp(settingName, "antennaReferencePoint") == 0)
+ settings.antennaReferencePoint = settingValue;
+ else if (strcmp(settingName, "bluetoothRadioType") == 0)
+ settings.bluetoothRadioType = (BluetoothRadioType_e)settingValue; // 0 = SPP, 1 = BLE, 2 = Off
+ else if (strcmp(settingName, "espnowBroadcast") == 0)
+ settings.espnowBroadcast = settingValueBool;
+ else if (strcmp(settingName, "radioType") == 0)
+ settings.radioType = (RadioType_e)settingValue; // 0 = Radio off, 1 = ESP-Now
+ else if (strcmp(settingName, "baseRoverSetup") == 0)
+ {
+ // 0 = Rover, 1 = Base, 2 = NTP
+ settings.lastState = STATE_ROVER_NOT_STARTED; // Default
+ if (settingValue == 1)
+ settings.lastState = STATE_BASE_NOT_STARTED;
+ if (settingValue == 2)
+ settings.lastState = STATE_NTPSERVER_NOT_STARTED;
+ }
+ else if (strstr(settingName, "stationECEF") != nullptr)
+ {
+ replaceCharacter((char *)settingValueStr, ' ', ','); // Replace all ' ' with ',' before recording to file
+ recordLineToSD(stationCoordinateECEFFileName, settingValueStr);
+ recordLineToLFS(stationCoordinateECEFFileName, settingValueStr);
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("%s recorded\r\n", settingValueStr);
+ }
+ else if (strstr(settingName, "stationGeodetic") != nullptr)
+ {
+ replaceCharacter((char *)settingValueStr, ' ', ','); // Replace all ' ' with ',' before recording to file
+ recordLineToSD(stationCoordinateGeodeticFileName, settingValueStr);
+ recordLineToLFS(stationCoordinateGeodeticFileName, settingValueStr);
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("%s recorded\r\n", settingValueStr);
+ }
+ else if (strcmp(settingName, "pvtServerPort") == 0)
+ settings.pvtServerPort = settingValue;
+ else if (strcmp(settingName, "pvtUdpServerPort") == 0)
+ settings.pvtUdpServerPort = settingValue;
+ else if (strcmp(settingName, "wifiConfigOverAP") == 0)
+ {
+ if (settingValue == 1) // Drop downs come back as a value
+ settings.wifiConfigOverAP = true;
+ else
+ settings.wifiConfigOverAP = false;
+ }
+
+ else if (strcmp(settingName, "enablePvtClient") == 0)
+ settings.enablePvtClient = settingValueBool;
+ else if (strcmp(settingName, "enablePvtServer") == 0)
+ settings.enablePvtServer = settingValueBool;
+ else if (strcmp(settingName, "enablePvtUdpServer") == 0)
+ settings.enablePvtUdpServer = settingValueBool;
+ else if (strcmp(settingName, "enableRCFirmware") == 0)
+ enableRCFirmware = settingValueBool;
+ else if (strcmp(settingName, "minElev") == 0)
+ settings.minElev = settingValue;
+ else if (strcmp(settingName, "imuYaw") == 0)
+ settings.imuYaw = settingValue * 100; // Comes in as 0 to 360.0 but stored as 0 to 36,000
+ else if (strcmp(settingName, "imuPitch") == 0)
+ settings.imuPitch = settingValue * 100; // Comes in as -90 to 90.0 but stored as -9000 to 9000
+ else if (strcmp(settingName, "imuRoll") == 0)
+ settings.imuRoll = settingValue * 100; // Comes in as -180 to 180.0 but stored as -18000 to 18000
+ else if (strcmp(settingName, "sfDisableWheelDirection") == 0)
+ settings.sfDisableWheelDirection = settingValueBool;
+ else if (strcmp(settingName, "sfCombineWheelTicks") == 0)
+ settings.sfCombineWheelTicks = settingValueBool;
+ else if (strcmp(settingName, "rateNavPrio") == 0)
+ settings.rateNavPrio = settingValue;
+ else if (strcmp(settingName, "minCNO") == 0)
+ {
+ if (zedModuleType == PLATFORM_F9R)
+ settings.minCNO_F9R = settingValue;
+ else
+ settings.minCNO_F9P = settingValue;
+ }
+
+ else if (strcmp(settingName, "ethernetDHCP") == 0)
+ settings.ethernetDHCP = settingValueBool;
+ else if (strcmp(settingName, "ethernetIP") == 0)
+ {
+ String tempString = String(settingValueStr);
+ settings.ethernetIP.fromString(tempString);
+ }
+ else if (strcmp(settingName, "ethernetDNS") == 0)
+ {
+ String tempString = String(settingValueStr);
+ settings.ethernetDNS.fromString(tempString);
+ }
+ else if (strcmp(settingName, "ethernetGateway") == 0)
+ {
+ String tempString = String(settingValueStr);
+ settings.ethernetGateway.fromString(tempString);
+ }
+ else if (strcmp(settingName, "ethernetSubnet") == 0)
+ {
+ String tempString = String(settingValueStr);
+ settings.ethernetSubnet.fromString(tempString);
+ }
+ else if (strcmp(settingName, "httpPort") == 0)
+ settings.httpPort = settingValue;
+ else if (strcmp(settingName, "ethernetNtpPort") == 0)
+ settings.ethernetNtpPort = settingValue;
+ else if (strcmp(settingName, "pvtClientPort") == 0)
+ settings.pvtClientPort = settingValue;
+ else if (strcmp(settingName, "pvtClientHost") == 0)
+ strcpy(settings.pvtClientHost, settingValueStr);
+
+ // Network layer
+ else if (strcmp(settingName, "defaultNetworkType") == 0)
+ settings.defaultNetworkType = settingValue;
+ else if (strcmp(settingName, "enableNetworkFailover") == 0)
+ settings.enableNetworkFailover = settingValue;
+
+ // NTP
+ else if (strcmp(settingName, "ntpPollExponent") == 0)
+ settings.ntpPollExponent = settingValue;
+ else if (strcmp(settingName, "ntpPrecision") == 0)
+ settings.ntpPrecision = settingValue;
+ else if (strcmp(settingName, "ntpRootDelay") == 0)
+ settings.ntpRootDelay = settingValue;
+ else if (strcmp(settingName, "ntpRootDispersion") == 0)
+ settings.ntpRootDispersion = settingValue;
+ else if (strcmp(settingName, "ntpReferenceId") == 0)
+ {
+ strcpy(settings.ntpReferenceId, settingValueStr);
+ for (int i = strlen(settingValueStr); i < 5; i++)
+ settings.ntpReferenceId[i] = 0;
+ }
+ else if (strcmp(settingName, "mdnsEnable") == 0)
+ settings.mdnsEnable = settingValueBool;
+
+ // Automatic firmware update settings
+ else if (strcmp(settingName, "enableAutoFirmwareUpdate") == 0)
+ settings.enableAutoFirmwareUpdate = settingValueBool;
+ else if (strcmp(settingName, "autoFirmwareCheckMinutes") == 0)
+ settings.autoFirmwareCheckMinutes = settingValueBool;
+
+ else if (strcmp(settingName, "geographicRegion") == 0)
+ settings.geographicRegion = settingValue;
+
+ // Unused variables - read to avoid errors
+ else if (strcmp(settingName, "measurementRateSec") == 0)
+ {
+ }
+ else if (strcmp(settingName, "baseTypeSurveyIn") == 0)
+ {
+ }
+ else if (strcmp(settingName, "fixedBaseCoordinateTypeGeo") == 0)
+ {
+ }
+ else if (strcmp(settingName, "saveToArduino") == 0)
+ {
+ }
+ else if (strcmp(settingName, "enableFactoryDefaults") == 0)
+ {
+ }
+ else if (strcmp(settingName, "enableFirmwareUpdate") == 0)
+ {
+ }
+ else if (strcmp(settingName, "enableForgetRadios") == 0)
+ {
+ }
+ else if (strcmp(settingName, "nicknameECEF") == 0)
+ {
+ }
+ else if (strcmp(settingName, "nicknameGeodetic") == 0)
+ {
+ }
+ else if (strcmp(settingName, "fileSelectAll") == 0)
+ {
+ }
+ else if (strcmp(settingName, "fixedHAE_APC") == 0)
+ {
+ }
+ else if (strcmp(settingName, "measurementRateSecBase") == 0)
+ {
+ }
+
+ // Special actions
+ else if (strcmp(settingName, "firmwareFileName") == 0)
+ {
+ mountSDThenUpdate(settingValueStr);
+
+ // If update is successful, it will force system reset and not get here.
+
+ if (productVariant == REFERENCE_STATION)
+ requestChangeState(STATE_BASE_NOT_STARTED); // If update failed, return to Base mode.
+ else
+ requestChangeState(STATE_ROVER_NOT_STARTED); // If update failed, return to Rover mode.
+ }
+ else if (strcmp(settingName, "factoryDefaultReset") == 0)
+ factoryReset(false); // We do not have the sdSemaphore
+ else if (strcmp(settingName, "exitAndReset") == 0)
+ {
+ // Confirm receipt
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Sending reset confirmation");
+
+ websocket->textAll("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();
+ }
+ }
+
+ ESP.restart();
+ }
+ else if (strcmp(settingName, "setProfile") == 0)
+ {
+ // Change to new profile
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("Changing to profile number %d\r\n", settingValue);
+ changeProfileNumber(settingValue);
+
+ // Load new profile into system
+ loadSettings();
+
+ // Send new settings to browser. Re-use settingsCSV to avoid stack.
+ memset(settingsCSV, 0, AP_CONFIG_SETTING_SIZE); // Clear any garbage from settings array
+
+ createSettingsString(settingsCSV);
+
+ if (settings.debugWiFiConfig == true)
+ {
+ systemPrintf("Sending profile %d\r\n", settingValue);
+ systemPrintf("Profile contents: %s\r\n", settingsCSV);
+ }
+ websocket->textAll(settingsCSV);
+ }
+ else if (strcmp(settingName, "resetProfile") == 0)
+ {
+ settingsToDefaults(); // Overwrite our current settings with defaults
+
+ recordSystemSettings(); // Overwrite profile file and NVM with these settings
+
+ // Get bitmask of active profiles
+ activeProfiles = loadProfileNames();
+
+ // Send new settings to browser. Re-use settingsCSV to avoid stack.
+ memset(settingsCSV, 0, AP_CONFIG_SETTING_SIZE); // Clear any garbage from settings array
+
+ createSettingsString(settingsCSV);
+
+ if (settings.debugWiFiConfig == true)
+ {
+ systemPrintf("Sending reset profile %d\r\n", settingValue);
+ systemPrintf("Profile contents: %s\r\n", settingsCSV);
+ }
+ websocket->textAll(settingsCSV);
+ }
+ else if (strcmp(settingName, "forgetEspNowPeers") == 0)
+ {
+ // Forget all ESP-Now Peers
+ for (int x = 0; x < settings.espnowPeerCount; x++)
+ espnowRemovePeer(settings.espnowPeers[x]);
+ settings.espnowPeerCount = 0;
+ }
+ else if (strcmp(settingName, "startNewLog") == 0)
+ {
+ if (settings.enableLogging == true && online.logging == true)
+ {
+ endLogging(false, true); //(gotSemaphore, releaseSemaphore) Close file. Reset parser stats.
+ beginLogging(); // Create new file based on current RTC.
+ setLoggingType(); // Determine if we are standard, PPP, or custom. Changes logging icon accordingly.
+
+ char newFileNameCSV[sizeof("logFileName,") + sizeof(logFileName) + 1];
+ snprintf(newFileNameCSV, sizeof(newFileNameCSV), "logFileName,%s,", logFileName);
+
+ websocket->textAll(newFileNameCSV); // Tell the config page the name of the file we just created
+ }
+ }
+ else if (strcmp(settingName, "checkNewFirmware") == 0)
+ {
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Checking for new OTA Pull firmware");
+
+ websocket->textAll("checkingNewFirmware,1,"); // Tell the config page we received their request
+
+ char reportedVersion[20];
+ char newVersionCSV[100];
+
+ // Get firmware version from server
+ 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.debugWiFiConfig == true)
+ systemPrintln("New version detected");
+ snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,%s,", reportedVersion);
+ }
+ else
+ {
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("No new firmware available");
+ snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,CURRENT,");
+ }
+ }
+ else
+ {
+ // Failed to get version number
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Sending error to AP config page");
+ snprintf(newVersionCSV, sizeof(newVersionCSV), "newFirmwareVersion,ERROR,");
+ }
+
+ websocket->textAll(newVersionCSV);
+ }
+ else if (strcmp(settingName, "getNewFirmware") == 0)
+ {
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Getting new OTA Pull firmware");
+
+ websocket->textAll("gettingNewFirmware,1,"); // Tell the config page we received their request
+
+ apConfigFirmwareUpdateInProcess = true;
+ otaUpdate();
+
+ // We get here if WiFi failed to connect
+ websocket->textAll("gettingNewFirmware,ERROR,");
+ }
+
+ // Check for bulk settings (constellations and message rates)
+ // Must be last on else list
+ else
+ {
+ bool knownSetting = false;
+
+ // Scan for WiFi credentials
+ if (knownSetting == false)
+ {
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ char tempString[100]; // wifiNetwork0Password=parachutes
+ snprintf(tempString, sizeof(tempString), "wifiNetwork%dSSID", x);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(settings.wifiNetworks[x].ssid, settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ else
+ {
+ snprintf(tempString, sizeof(tempString), "wifiNetwork%dPassword", x);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(settings.wifiNetworks[x].password, settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Scan for constellation settings
+ if (knownSetting == false)
+ {
+ for (int x = 0; x < MAX_CONSTELLATIONS; x++)
+ {
+ char tempString[50]; // ubxConstellationsSBAS
+ snprintf(tempString, sizeof(tempString), "ubxConstellations%s", settings.ubxConstellations[x].textName);
+
+ if (strcmp(settingName, tempString) == 0)
+ {
+ settings.ubxConstellations[x].enabled = settingValueBool;
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for message settings
+ if (knownSetting == false)
+ {
+ char tempString[50];
+
+ for (int x = 0; x < MAX_UBX_MSG; x++)
+ {
+ snprintf(tempString, sizeof(tempString), "%s", ubxMessages[x].msgTextName); // UBX_RTCM_1074
+ if (strcmp(settingName, tempString) == 0)
+ {
+ settings.ubxMessageRates[x] = settingValue;
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for Base RTCM message settings
+ if (knownSetting == false)
+ {
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+
+ char tempString[50];
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ snprintf(tempString, sizeof(tempString), "%sBase",
+ ubxMessages[firstRTCMRecord + x].msgTextName); // UBX_RTCM_1074Base
+ if (strcmp(settingName, tempString) == 0)
+ {
+ settings.ubxMessageRatesBase[x] = settingValue;
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServerCasterHost
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterHost_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings.ntripServer_CasterHost[serverIndex][0], settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServerCasterPort
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterPort_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ settings.ntripServer_CasterPort[serverIndex] = settingValue;
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServerCasterUser
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterUser_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings.ntripServer_CasterUser[serverIndex][0], settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServerCasterUserPW
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterUserPW_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings.ntripServer_CasterUserPW[serverIndex][0], settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServerMountPoint
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_MountPoint_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings.ntripServer_MountPoint[serverIndex][0], settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServerMountPointPW
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_MountPointPW_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings.ntripServer_MountPointPW[serverIndex][0], settingValueStr);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Last catch
+ if (knownSetting == false)
+ {
+ systemPrintf("Unknown '%s': %0.3lf\r\n", settingName, settingValue);
+ }
+ } // End last strcpy catch
+}
+
+// Add record with int
+void stringRecord(char *settingsCSV, const char *id, int settingValue)
+{
+ char record[100];
+ snprintf(record, sizeof(record), "%s,%d,", id, settingValue);
+ strcat(settingsCSV, record);
+}
+
+// Add record with uint32_t
+void stringRecord(char *settingsCSV, const char *id, uint32_t settingValue)
+{
+ char record[100];
+ snprintf(record, sizeof(record), "%s,%d,", id, settingValue);
+ strcat(settingsCSV, record);
+}
+
+// Add record with double
+void stringRecord(char *settingsCSV, const char *id, double settingValue, int decimalPlaces)
+{
+ char format[10];
+ snprintf(format, sizeof(format), "%%0.%dlf", decimalPlaces); // Create '%0.09lf'
+
+ char formattedValue[20];
+ snprintf(formattedValue, sizeof(formattedValue), format, settingValue);
+
+ char record[100];
+ snprintf(record, sizeof(record), "%s,%s,", id, formattedValue);
+ strcat(settingsCSV, record);
+}
+
+// Add record with bool
+void stringRecord(char *settingsCSV, const char *id, bool settingValue)
+{
+ char temp[10];
+ if (settingValue == true)
+ strcpy(temp, "true");
+ else
+ strcpy(temp, "false");
+
+ char record[100];
+ snprintf(record, sizeof(record), "%s,%s,", id, temp);
+ strcat(settingsCSV, record);
+}
+
+// Add record with string
+void stringRecord(char *settingsCSV, const char *id, char *settingValue)
+{
+ char record[100];
+ snprintf(record, sizeof(record), "%s,%s,", id, settingValue);
+ strcat(settingsCSV, record);
+}
+
+// Add record with uint64_t
+void stringRecord(char *settingsCSV, const char *id, uint64_t settingValue)
+{
+ char record[100];
+ snprintf(record, sizeof(record), "%s,%lld,", id, settingValue);
+ strcat(settingsCSV, record);
+}
+
+// Break CSV into setting constituents
+// Can't use strtok because we may have two commas next to each other, ie
+// measurementRateHz,4.00,measurementRateSec,,dynamicModel,0,
+bool parseIncomingSettings()
+{
+ char settingName[100] = {'\0'};
+ char valueStr[150] = {'\0'}; // stationGeodetic1,ANameThatIsTooLongToBeDisplayed 40.09029479 -105.18505761 1560.089
+
+ char *commaPtr = incomingSettings;
+ char *headPtr = incomingSettings;
+
+ int counter = 0;
+ int maxAttempts = 500;
+ while (*headPtr) // Check if we've reached the end of the string
+ {
+ // Spin to first comma
+ commaPtr = strstr(headPtr, ",");
+ if (commaPtr != nullptr)
+ {
+ *commaPtr = '\0';
+ strcpy(settingName, headPtr);
+ headPtr = commaPtr + 1;
+ }
+
+ commaPtr = strstr(headPtr, ",");
+ if (commaPtr != nullptr)
+ {
+ *commaPtr = '\0';
+ strcpy(valueStr, headPtr);
+ headPtr = commaPtr + 1;
+ }
+
+ // log_d("settingName: %s value: %s", settingName, valueStr);
+
+ updateSettingWithValue(settingName, valueStr);
+
+ // Avoid infinite loop if response is malformed
+ counter++;
+ if (counter == maxAttempts)
+ {
+ systemPrintln("Error: Incoming settings malformed.");
+ break;
+ }
+ }
+
+ if (counter < maxAttempts)
+ {
+ // Confirm receipt
+ if (settings.debugWiFiConfig == true)
+ systemPrintln("Sending receipt confirmation of settings");
+ websocket->textAll("confirmDataReceipt,1,");
+ }
+
+ return (true);
+}
+
+// When called, responds with the root folder list of files on SD card
+// Name and size are formatted in CSV, formatted to html by JS
+void getFileList(String &returnText)
+{
+ returnText = "";
+
+ // Update the SD Size and Free Space
+ String cardSize;
+ stringHumanReadableSize(cardSize, sdCardSize);
+ returnText += "sdSize," + cardSize + ",";
+ String freeSpace;
+ stringHumanReadableSize(freeSpace, sdFreeSpace);
+ returnText += "sdFreeSpace," + freeSpace + ",";
+
+ char fileName[50]; // Handle long file names
+
+ // Attempt to gain access to the SD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_FILEMANAGER_UPLOAD1);
+
+ if (USE_SPI_MICROSD)
+ {
+ SdFile root;
+ root.open("/"); // Open root
+ SdFile file;
+ uint16_t fileCount = 0;
+
+ while (file.openNext(&root, O_READ))
+ {
+ if (file.isFile())
+ {
+ fileCount++;
+
+ file.getName(fileName, sizeof(fileName));
+
+ String fileSize;
+ stringHumanReadableSize(fileSize, file.fileSize());
+ returnText += "fmName," + String(fileName) + ",fmSize," + fileSize + ",";
+ }
+ }
+
+ root.close();
+ file.close();
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ File root = SD_MMC.open("/"); // Open root
+
+ if (root && root.isDirectory())
+ {
+ uint16_t fileCount = 0;
+
+ File file = root.openNextFile();
+ while (file)
+ {
+ if (!file.isDirectory())
+ {
+ fileCount++;
+
+ String fileSize;
+ stringHumanReadableSize(fileSize, file.size());
+ returnText += "fmName," + String(file.name()) + ",fmSize," + fileSize + ",";
+ }
+
+ file = root.openNextFile();
+ }
+ }
+
+ root.close();
+ }
+#endif // COMPILE_SD_MMC
+
+ xSemaphoreGive(sdCardSemaphore);
+ }
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+
+ // This is an error because the current settings no longer match the settings
+ // on the microSD card, and will not be restored to the expected settings!
+ systemPrintf("sdCardSemaphore failed to yield, held by %s, Form.ino line %d\r\n", semaphoreHolder, __LINE__);
+ }
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str());
+}
+
+// When called, responds with the messages supported on this platform
+// Message name and current rate are formatted in CSV, formatted to html by JS
+void createMessageList(String &returnText)
+{
+ returnText = "";
+
+ for (int messageNumber = 0; messageNumber < MAX_UBX_MSG; messageNumber++)
+ {
+ if (messageSupported(messageNumber) == true)
+ returnText += String(ubxMessages[messageNumber].msgTextName) + "," +
+ String(settings.ubxMessageRates[messageNumber]) + ","; // UBX_RTCM_1074,4,
+ }
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str());
+}
+
+// When called, responds with the RTCM/Base messages supported on this platform
+// Message name and current rate are formatted in CSV, formatted to html by JS
+void createMessageListBase(String &returnText)
+{
+ returnText = "";
+
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+
+ for (int messageNumber = 0; messageNumber < MAX_UBX_MSG_RTCM; messageNumber++)
+ {
+ if (messageSupported(firstRTCMRecord + messageNumber) == true)
+ returnText += String(ubxMessages[messageNumber + firstRTCMRecord].msgTextName) + "Base," +
+ String(settings.ubxMessageRatesBase[messageNumber]) + ","; // UBX_RTCM_1074Base,4,
+ }
+
+ if (settings.debugWiFiConfig == true)
+ systemPrintf("returnText (%d bytes): %s\r\n", returnText.length(), returnText.c_str());
+}
+
+// Handles uploading of user files to SD
+void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
+{
+ String logmessage = "";
+
+ if (!index)
+ {
+ logmessage = "Upload Start: " + String(filename);
+
+ int fileNameLen = filename.length();
+ char tempFileName[fileNameLen + 2] = {'/'}; // Filename must start with / or VERY bad things happen on SD_MMC
+ filename.toCharArray(&tempFileName[1], fileNameLen + 1);
+ tempFileName[fileNameLen + 1] = '\0'; // Terminate array
+
+ // Allocate the managerTempFile
+ if (!managerTempFile)
+ {
+ managerTempFile = new FileSdFatMMC;
+ if (!managerTempFile)
+ {
+ systemPrintln("Failed to allocate managerTempFile!");
+ return;
+ }
+ }
+ // Attempt to gain access to the SD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_FILEMANAGER_UPLOAD1);
+
+ managerTempFile->open(tempFileName, O_CREAT | O_APPEND | O_WRITE);
+
+ xSemaphoreGive(sdCardSemaphore);
+ }
+
+ systemPrintln(logmessage);
+ }
+
+ if (len)
+ {
+ // Attempt to gain access to the SD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_FILEMANAGER_UPLOAD2);
+
+ managerTempFile->write(data, len); // stream the incoming chunk to the opened file
+
+ xSemaphoreGive(sdCardSemaphore);
+ }
+ }
+
+ if (final)
+ {
+ logmessage = "Upload Complete: " + String(filename) + ",size: " + String(index + len);
+
+ // Attempt to gain access to the SD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_FILEMANAGER_UPLOAD3);
+
+ managerTempFile->updateFileCreateTimestamp(); // Update the file create time & date
+
+ managerTempFile->close();
+
+ xSemaphoreGive(sdCardSemaphore);
+ }
+
+ systemPrintln(logmessage);
+ request->redirect("/");
+ }
+}
+
+#endif // COMPILE_AP
diff --git a/Firmware/RTK_Surveyor/GpsMessageParser.h b/Firmware/RTK_Surveyor/GpsMessageParser.h
new file mode 100644
index 000000000..50a32c8ef
--- /dev/null
+++ b/Firmware/RTK_Surveyor/GpsMessageParser.h
@@ -0,0 +1,146 @@
+/*------------------------------------------------------------------------------
+GpsMessageParser.h
+
+ Constant and routine declarations for the GPS message parser.
+------------------------------------------------------------------------------*/
+
+#ifndef __GPS_MESSAGE_PARSER_H__
+#define __GPS_MESSAGE_PARSER_H__
+
+#include
+
+#include "crc24q.h" // 24-bit CRC-24Q cyclic redundancy checksum for RTCM parsing
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+#define PARSE_BUFFER_LENGTH 3000 // Some USB RAWX messages can be > 2k
+
+enum
+{
+ SENTENCE_TYPE_NONE = 0,
+
+ // Add new sentence types below in alphabetical order
+ SENTENCE_TYPE_NMEA,
+ SENTENCE_TYPE_RTCM,
+ SENTENCE_TYPE_UBX,
+};
+
+//----------------------------------------
+// Types
+//----------------------------------------
+
+typedef struct _PARSE_STATE *P_PARSE_STATE;
+
+// Parse routine
+typedef uint8_t (*PARSE_ROUTINE)(P_PARSE_STATE parse, // Parser state
+ uint8_t data); // Incoming data byte
+
+// End of message callback routine
+typedef void (*PARSE_EOM_CALLBACK)(P_PARSE_STATE parse, // Parser state
+ uint8_t type); // Message type
+
+typedef struct _PARSE_STATE
+{
+ PARSE_ROUTINE state; // Parser state routine
+ PARSE_EOM_CALLBACK eomCallback; // End of message callback routine
+ const char *parserName; // Name of parser
+ uint32_t crc; // RTCM computed CRC
+ uint32_t rtcmCrc; // Computed CRC value for the RTCM message
+ uint32_t invalidRtcmCrcs; // Number of bad RTCM CRCs detected
+ uint16_t bytesRemaining; // Bytes remaining in RTCM CRC calculation
+ uint16_t length; // Message length including line termination
+ uint16_t maxLength; // Maximum message length including line termination
+ uint16_t message; // RTCM message number
+ uint16_t nmeaLength; // Length of the NMEA message without line termination
+ uint8_t buffer[PARSE_BUFFER_LENGTH]; // Buffer containing the message
+ uint8_t nmeaMessageName[16]; // Message name
+ uint8_t nmeaMessageNameLength; // Length of the message name
+ uint8_t ck_a; // U-blox checksum byte 1
+ uint8_t ck_b; // U-blox checksum byte 2
+ bool computeCrc; // Compute the CRC when true
+} PARSE_STATE;
+
+//----------------------------------------
+// Macros
+//----------------------------------------
+
+#ifdef PARSE_NMEA_MESSAGES
+#define NMEA_PREAMBLE nmeaPreamble,
+#else
+#define NMEA_PREAMBLE
+#endif // PARSE_NMEA_MESSAGES
+
+#ifdef PARSE_RTCM_MESSAGES
+#define RTCM_PREAMBLE rtcmPreamble,
+#else
+#define RTCM_PREAMBLE
+#endif // PARSE_RTCM_MESSAGES
+
+#ifdef PARSE_UBLOX_MESSAGES
+#define UBLOX_PREAMBLE ubloxPreamble,
+#else
+#define UBLOX_PREAMBLE
+#endif // PARSE_UBLOX_MESSAGES
+
+#define GPS_PARSE_TABLE \
+PARSE_ROUTINE const gpsParseTable[] = \
+{ \
+ NMEA_PREAMBLE \
+ RTCM_PREAMBLE \
+ UBLOX_PREAMBLE \
+}; \
+ \
+const int gpsParseTableEntries = sizeof(gpsParseTable) / sizeof(gpsParseTable[0]);
+
+//----------------------------------------
+// External values
+//----------------------------------------
+
+extern PARSE_ROUTINE const gpsParseTable[];
+extern const int gpsParseTableEntries;
+
+//----------------------------------------
+// External routines
+//----------------------------------------
+
+// Main parser routine
+uint8_t gpsMessageParserFirstByte(PARSE_STATE *parse, uint8_t data);
+
+// NMEA parse routines
+uint8_t nmeaPreamble(PARSE_STATE *parse, uint8_t data);
+uint8_t nmeaFindFirstComma(PARSE_STATE *parse, uint8_t data);
+uint8_t nmeaFindAsterisk(PARSE_STATE *parse, uint8_t data);
+uint8_t nmeaChecksumByte1(PARSE_STATE *parse, uint8_t data);
+uint8_t nmeaChecksumByte2(PARSE_STATE *parse, uint8_t data);
+uint8_t nmeaLineTermination(PARSE_STATE *parse, uint8_t data);
+
+// RTCM parse routines
+uint8_t rtcmPreamble(PARSE_STATE *parse, uint8_t data);
+uint8_t rtcmReadLength1(PARSE_STATE *parse, uint8_t data);
+uint8_t rtcmReadLength2(PARSE_STATE *parse, uint8_t data);
+uint8_t rtcmReadMessage1(PARSE_STATE *parse, uint8_t data);
+uint8_t rtcmReadMessage2(PARSE_STATE *parse, uint8_t data);
+uint8_t rtcmReadData(PARSE_STATE *parse, uint8_t data);
+uint8_t rtcmReadCrc(PARSE_STATE *parse, uint8_t data);
+
+// u-blox parse routines
+uint8_t ubloxPreamble(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxSync2(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxClass(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxId(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxLength1(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxLength2(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxPayload(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxCkA(PARSE_STATE *parse, uint8_t data);
+uint8_t ubloxCkB(PARSE_STATE *parse, uint8_t data);
+
+// External print routines
+void printNmeaChecksumError(PARSE_STATE *parse);
+void printRtcmChecksumError(PARSE_STATE *parse);
+void printRtcmMaxLength(PARSE_STATE *parse);
+void printUbloxChecksumError(PARSE_STATE *parse);
+void printUbloxInvalidData(PARSE_STATE *parse);
+
+#endif // __GPS_MESSAGE_PARSER_H__
diff --git a/Firmware/RTK_Surveyor/GpsMessageParser.ino b/Firmware/RTK_Surveyor/GpsMessageParser.ino
new file mode 100644
index 000000000..74d5d643c
--- /dev/null
+++ b/Firmware/RTK_Surveyor/GpsMessageParser.ino
@@ -0,0 +1,27 @@
+/*------------------------------------------------------------------------------
+GpsMessageParser.ino
+
+ Parse messages from GPS radios
+------------------------------------------------------------------------------*/
+
+// Wait for the first byte in the GPS message
+uint8_t gpsMessageParserFirstByte(PARSE_STATE *parse, uint8_t data)
+{
+ int index;
+ PARSE_ROUTINE parseRoutine;
+ uint8_t sentenceType;
+
+ // Walk through the parse table
+ for (index = 0; index < gpsParseTableEntries; index++)
+ {
+ parseRoutine = gpsParseTable[index];
+ sentenceType = parseRoutine(parse, data);
+ if (sentenceType)
+ return sentenceType;
+ }
+
+ // preamble byte not found
+ parse->length = 0;
+ parse->state = gpsMessageParserFirstByte;
+ return SENTENCE_TYPE_NONE;
+}
diff --git a/Firmware/RTK_Surveyor/NTP.ino b/Firmware/RTK_Surveyor/NTP.ino
new file mode 100644
index 000000000..d07ce6169
--- /dev/null
+++ b/Firmware/RTK_Surveyor/NTP.ino
@@ -0,0 +1,1022 @@
+/*------------------------------------------------------------------------------
+NTP.ino
+
+ This module implements the network time protocol (NTP).
+
+ NTP Testing using Ethernet:
+
+ Raspberry Pi Setup:
+ * Install Raspberry Pi OS
+ * Edit /etc/systemd/timesyncd.conf
+ * Remove '#" from in front of NTP= line
+ * Set NTP= line to:
+ NTP="your NTP server address" "addresses from FallbackNTPK= line"
+ * without the double quotes
+ * Force a time update using:
+ sudo systemctl restart systemd-timesyncd.service
+
+ NTP Testing on Raspberry Pi:
+ * Log into the Raspberry Pi system
+ * Start the terminal program
+ * Display the time server using:
+ timedatectl timesync-status
+ * Verify that the Server specifies your NTP server IP address
+ * Force a time update using:
+ sudo systemctl restart systemd-timesyncd.service
+
+ Test Setup:
+
+ RTK Reference Station Raspberry Pi
+ ^ NTP Server
+ | Ethernet cable ^
+ v |
+ Ethernet Switch <-----------------'
+ ^
+ | Ethernet cable
+ v
+ Internet Firewall
+ ^
+ | Ethernet cable
+ v
+ Modem
+ ^
+ |
+ v
+ Internet
+ ^
+ |
+ v
+ NTP Server
+
+------------------------------------------------------------------------------*/
+
+#ifdef COMPILE_ETHERNET
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+enum NTP_STATE
+{
+ NTP_STATE_OFF,
+ NTP_STATE_NETWORK_STARTING,
+ NTP_STATE_NETWORK_CONNECTED,
+ NTP_STATE_SERVER_RUNNING,
+ // Insert new states here
+ NTP_STATE_MAX
+};
+
+const char * const ntpServerStateName[] =
+{
+ "NTP_STATE_OFF",
+ "NTP_STATE_NETWORK_STARTING",
+ "NTP_STATE_NETWORK_CONNECTED",
+ "NTP_STATE_SERVER_RUNNING"
+};
+const int ntpServerStateNameEntries = sizeof(ntpServerStateName) / sizeof(ntpServerStateName[0]);
+
+const RtkMode_t ntpServerMode = RTK_MODE_NTP;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+static derivedEthernetUDP *ntpServer; // This will be instantiated when we know the NTP port
+static uint8_t ntpServerState;
+static volatile uint8_t ntpSockIndex; // The W5500 socket index for NTP - so we can enable and read the correct interrupt
+static uint32_t lastLoggedNTPRequest;
+
+//----------------------------------------
+// Menu to get the NTP settings
+//----------------------------------------
+
+void menuNTP()
+{
+ if (!HAS_ETHERNET)
+ {
+ clearBuffer(); // Empty buffer of any newline chars
+ return;
+ }
+
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: NTP");
+ systemPrintln();
+
+ systemPrint("1) Poll Exponent: 2^");
+ systemPrintln(settings.ntpPollExponent);
+
+ systemPrint("2) Precision: 2^");
+ systemPrintln(settings.ntpPrecision);
+
+ systemPrint("3) Root Delay (us): ");
+ systemPrintln(settings.ntpRootDelay);
+
+ systemPrint("4) Root Dispersion (us): ");
+ systemPrintln(settings.ntpRootDispersion);
+
+ systemPrint("5) Reference ID: ");
+ systemPrintln(settings.ntpReferenceId);
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ {
+ systemPrint("Enter new poll exponent (2^, Min 3, Max 17): ");
+ long newVal = getNumber();
+ if ((newVal >= 3) && (newVal <= 17))
+ settings.ntpPollExponent = newVal;
+ else
+ systemPrintln("Error: poll exponent out of range");
+ }
+ else if (incoming == 2)
+ {
+ systemPrint("Enter new precision (2^, Min -30, Max 0): ");
+ long newVal = getNumber();
+ if ((newVal >= -30) && (newVal <= 0))
+ settings.ntpPrecision = newVal;
+ else
+ systemPrintln("Error: precision out of range");
+ }
+ else if (incoming == 3)
+ {
+ systemPrint("Enter new root delay (us): ");
+ long newVal = getNumber();
+ if ((newVal >= 0) && (newVal <= 1000000))
+ settings.ntpRootDelay = newVal;
+ else
+ systemPrintln("Error: root delay out of range");
+ }
+ else if (incoming == 4)
+ {
+ systemPrint("Enter new root dispersion (us): ");
+ long newVal = getNumber();
+ if ((newVal >= 0) && (newVal <= 1000000))
+ settings.ntpRootDispersion = newVal;
+ else
+ systemPrintln("Error: root dispersion out of range");
+ }
+ else if (incoming == 5)
+ {
+ systemPrint("Enter new Reference ID (4 Chars Max): ");
+ char newId[5];
+ if (getString(newId, 5) == INPUT_RESPONSE_VALID)
+ {
+ int i = 0;
+ for (; i < strlen(newId); i++)
+ settings.ntpReferenceId[i] = newId[i];
+ for (; i < 5; i++)
+ settings.ntpReferenceId[i] = 0;
+ }
+ else
+ systemPrintln("Error: invalid Reference ID");
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// NTP Packet storage and utilities
+
+struct NTPpacket
+{
+ static const uint8_t NTPpacketSize = 48;
+
+ uint8_t packet[NTPpacketSize]; // Copy of the NTP packet
+ void setPacket(uint8_t *ptr)
+ {
+ memcpy(packet, ptr, NTPpacketSize);
+ }
+ void getPacket(uint8_t *ptr)
+ {
+ memcpy(ptr, packet, NTPpacketSize);
+ }
+
+ const uint32_t NTPtoUnixOffset = 2208988800; // NTP starts at Jan 1st 1900. Unix starts at Jan 1st 1970.
+
+ uint8_t LiVnMode; // Leap Indicator, Version Number, Mode
+
+ // Leap Indicator is 2 bits :
+ // 00 : No warning
+ // 01 : Last minute of the day has 61s
+ // 10 : Last minute of the day has 59 s
+ // 11 : Alarm condition (clock not synchronized)
+ const uint8_t defaultLeapInd = 0;
+ uint8_t LI()
+ {
+ return LiVnMode >> 6;
+ }
+ void LI(uint8_t val)
+ {
+ LiVnMode = (LiVnMode & 0x3F) | ((val & 0x03) << 6);
+ }
+
+ // Version Number is 3 bits. NTP version is currently four (4)
+ const uint8_t defaultVersion = 4;
+ uint8_t VN()
+ {
+ return (LiVnMode >> 3) & 0x07;
+ }
+ void VN(uint8_t val)
+ {
+ LiVnMode = (LiVnMode & 0xC7) | ((val & 0x07) << 3);
+ }
+
+ // Mode is 3 bits:
+ // 0 : Reserved
+ // 1 : Symmetric active
+ // 2 : Symmetric passive
+ // 3 : Client
+ // 4 : Server
+ // 5 : Broadcast
+ // 6 : NTP control message
+ // 7 : Reserved for private use
+ const uint8_t defaultMode = 4;
+ uint8_t mode()
+ {
+ return (LiVnMode & 0x07);
+ }
+ void mode(uint8_t val)
+ {
+ LiVnMode = (LiVnMode & 0xF8) | (val & 0x07);
+ }
+
+ // Stratum is 8 bits:
+ // 0 : Unspecified
+ // 1 : Reference clock (e.g., radio clock)
+ // 2-15 : Secondary server (via NTP)
+ // 16-255 : Unreachable
+ //
+ // We'll use 1 = Reference Clock
+ const uint8_t defaultStratum = 1;
+ uint8_t stratum;
+
+ // Poll exponent
+ // This is an eight-bit unsigned integer indicating the maximum interval between successive messages,
+ // in seconds to the nearest power of two.
+ // In the reference implementation, the values can range from 3 (8 s) through 17 (36 h).
+ //
+ // RFC 5905 suggests 6-10. We'll use 6. 2^6 = 64 seconds
+ const uint8_t defaultPollExponent = 6;
+ uint8_t pollExponent;
+
+ // Precision
+ // This is an eight-bit signed integer indicating the precision of the system clock,
+ // in seconds to the nearest power of two. For instance, a value of -18 corresponds to a precision of about 4us.
+ //
+ // tAcc is usually around 1us. So we'll use -20 (0xEC). 2^-20 = 0.95us
+ const int8_t defaultPrecision = -20; // 0xEC
+ int8_t precision;
+
+ // Root delay
+ // This is a 32-bit, unsigned, fixed-point number indicating the total round-trip delay to the reference clock,
+ // in seconds with fraction point between bits 15 and 16. In contrast to the calculated peer round-trip delay,
+ // which can take both positive and negative values, this value is always positive.
+ //
+ // We are the reference clock, so we'll use zero (0x00000000).
+ const uint32_t defaultRootDelay = 0x00000000;
+ uint32_t rootDelay;
+
+ // Root dispersion
+ // This is a 32-bit, unsigned, fixed-point number indicating the maximum error relative to the reference clock,
+ // in seconds with fraction point between bits 15 and 16.
+ //
+ // Tricky... Could depend on interrupt service time? Maybe go with ~1ms?
+ const uint32_t defaultRootDispersion = 0x00000042; // 1007us
+ uint32_t rootDispersion;
+
+ // Reference identifier
+ // This is a 32-bit code identifying the particular reference clock. The interpretation depends on the value in
+ // the stratum field. For stratum 0 (unsynchronized), this is a four-character ASCII (American Standard Code for
+ // Information Interchange) string called the kiss code, which is used for debugging and monitoring purposes.
+ // GPS : Global Positioning System
+ const uint8_t referenceIdLen = 4;
+ const char defaultReferenceId[4] = {'G', 'P', 'S', 0};
+ char referenceId[4];
+
+ // Reference timestamp
+ // This is the local time at which the system clock was last set or corrected, in 64-bit NTP timestamp format.
+ uint32_t referenceTimestampSeconds;
+ uint32_t referenceTimestampFraction;
+
+ // Originate timestamp
+ // This is the local time at which the request departed the client for the server, in 64-bit NTP timestamp format.
+ uint32_t originateTimestampSeconds;
+ uint32_t originateTimestampFraction;
+
+ // Receive timestamp
+ // This is the local time at which the request arrived at the server, in 64-bit NTP timestamp format.
+ uint32_t receiveTimestampSeconds;
+ uint32_t receiveTimestampFraction;
+
+ // Transmit timestamp
+ // This is the local time at which the reply departed the server for the client, in 64-bit NTP timestamp format.
+ uint32_t transmitTimestampSeconds;
+ uint32_t transmitTimestampFraction;
+
+ typedef union {
+ int8_t signed8;
+ uint8_t unsigned8;
+ } unsignedSigned8;
+
+ uint32_t extractUnsigned32(uint8_t *ptr)
+ {
+ uint32_t val = 0;
+ val |= *ptr++ << 24; // NTP data is Big-Endian
+ val |= *ptr++ << 16;
+ val |= *ptr++ << 8;
+ val |= *ptr++;
+ return val;
+ }
+
+ void insertUnsigned32(uint8_t *ptr, uint32_t val)
+ {
+ *ptr++ = val >> 24; // NTP data is Big-Endian
+ *ptr++ = (val >> 16) & 0xFF;
+ *ptr++ = (val >> 8) & 0xFF;
+ *ptr++ = val & 0xFF;
+ }
+
+ // Extract the data from an NTP packet into the correct fields
+ void extract()
+ {
+ uint8_t *ptr = packet;
+
+ LiVnMode = *ptr++;
+ stratum = *ptr++;
+ pollExponent = *ptr++;
+
+ unsignedSigned8 converter8;
+ converter8.unsigned8 = *ptr++; // Convert to int8_t without ambiguity
+ precision = converter8.signed8;
+
+ rootDelay = extractUnsigned32(ptr);
+ ptr += 4;
+ rootDispersion = extractUnsigned32(ptr);
+ ptr += 4;
+
+ for (uint8_t i = 0; i < referenceIdLen; i++)
+ referenceId[i] = *ptr++;
+
+ referenceTimestampSeconds = extractUnsigned32(ptr);
+ ptr += 4;
+ referenceTimestampFraction =
+ extractUnsigned32(ptr); // Note: the fraction is in increments of (1 / 2^32) secs, not microseconds
+ ptr += 4;
+ originateTimestampSeconds = extractUnsigned32(ptr);
+ ptr += 4;
+ originateTimestampFraction = extractUnsigned32(ptr);
+ ptr += 4;
+ receiveTimestampSeconds = extractUnsigned32(ptr);
+ ptr += 4;
+ receiveTimestampFraction = extractUnsigned32(ptr);
+ ptr += 4;
+ transmitTimestampSeconds = extractUnsigned32(ptr);
+ ptr += 4;
+ transmitTimestampFraction = extractUnsigned32(ptr);
+ ptr += 4;
+ }
+
+ // Insert the data from the fields into an NTP packet
+ void insert()
+ {
+ uint8_t *ptr = packet;
+
+ *ptr++ = LiVnMode;
+ *ptr++ = stratum;
+ *ptr++ = pollExponent;
+
+ unsignedSigned8 converter8;
+ converter8.signed8 = precision;
+ *ptr++ = converter8.unsigned8; // Convert to uint8_t without ambiguity
+
+ insertUnsigned32(ptr, rootDelay);
+ ptr += 4;
+ insertUnsigned32(ptr, rootDispersion);
+ ptr += 4;
+
+ for (uint8_t i = 0; i < 4; i++)
+ *ptr++ = referenceId[i];
+
+ insertUnsigned32(ptr, referenceTimestampSeconds);
+ ptr += 4;
+ insertUnsigned32(
+ ptr,
+ referenceTimestampFraction); // Note: the fraction is in increments of (1 / 2^32) secs, not microseconds
+ ptr += 4;
+ insertUnsigned32(ptr, originateTimestampSeconds);
+ ptr += 4;
+ insertUnsigned32(ptr, originateTimestampFraction);
+ ptr += 4;
+ insertUnsigned32(ptr, receiveTimestampSeconds);
+ ptr += 4;
+ insertUnsigned32(ptr, receiveTimestampFraction);
+ ptr += 4;
+ insertUnsigned32(ptr, transmitTimestampSeconds);
+ ptr += 4;
+ insertUnsigned32(ptr, transmitTimestampFraction);
+ ptr += 4;
+ }
+
+ uint32_t convertMicrosToSecsAndFraction(uint32_t val) // 16-bit fraction used by root delay and dispersion
+ {
+ double secs = val;
+ secs /= 1000000.0; // Convert micros to seconds
+ secs = floor(secs); // Convert to integer, round down
+
+ double microsecs = val;
+ microsecs -= secs * 1000000.0; // Subtract the seconds
+ microsecs /= 1000000.0; // Convert micros to seconds
+ microsecs *= pow(2.0, 16.0); // Convert to 16-bit fraction
+
+ uint32_t result = ((uint32_t)secs) << 16;
+ result |= ((uint32_t)microsecs) & 0xFFFF;
+ return (result);
+ }
+
+ uint32_t convertMicrosToFraction(uint32_t val) // 32-bit fraction used by the timestamps
+ {
+ val %= 1000000; // Just in case
+ double v = val; // Convert micros to double
+ v /= 1000000.0; // Convert micros to seconds
+ v *= pow(2.0, 32.0); // Convert to fraction
+ return (uint32_t)v;
+ }
+
+ uint32_t convertFractionToMicros(uint32_t val) // 32-bit fraction used by the timestamps
+ {
+ double v = val; // Convert fraction to double
+ v /= pow(2.0, 32.0); // Convert fraction to seconds
+ v *= 1000000.0; // Convert to micros
+ uint32_t ret = (uint32_t)v;
+ ret %= 1000000; // Just in case
+ return ret;
+ }
+
+ uint32_t convertNTPsecondsToUnix(uint32_t val)
+ {
+ return (val - NTPtoUnixOffset);
+ }
+
+ uint32_t convertUnixSecondsToNTP(uint32_t val)
+ {
+ return (val + NTPtoUnixOffset);
+ }
+};
+
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// NTP process one request
+// recTv contains the timeval the NTP packet was received - from the W5500 interrupt
+// syncTv contains the timeval when the RTC was last sync'd
+// ntpDiag will contain useful diagnostics
+bool ntpProcessOneRequest(bool process, const timeval *recTv, const timeval *syncTv, char *ntpDiag = nullptr,
+ size_t ntpDiagSize = 0); // Header
+bool ntpProcessOneRequest(bool process, const timeval *recTv, const timeval *syncTv, char *ntpDiag, size_t ntpDiagSize)
+{
+ bool processed = false;
+
+ if (ntpDiag != nullptr)
+ *ntpDiag = 0; // Clear any existing diagnostics
+
+ int packetDataSize = ntpServer->parsePacket();
+
+ IPAddress remoteIP = ntpServer->remoteIP();
+ uint16_t remotePort = ntpServer->remotePort();
+
+ if (ntpDiag != nullptr) // Add the packet size and remote IP/Port to the diagnostics
+ {
+ snprintf(ntpDiag, ntpDiagSize, "NTP request from: Remote IP: %d.%d.%d.%d Remote Port: %d\r\n", remoteIP[0],
+ remoteIP[1], remoteIP[2], remoteIP[3], remotePort);
+ }
+
+ if (packetDataSize && (packetDataSize >= NTPpacket::NTPpacketSize))
+ {
+ // Read the NTP packet
+ NTPpacket packet;
+
+ ntpServer->read((char *)&packet.packet, NTPpacket::NTPpacketSize); // Copy the NTP data into our packet
+
+ // If process is false, return now
+ if (!process)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf),
+ "NTP request ignored. Time has not been synchronized - or not in NTP mode.\r\n");
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ return false;
+ }
+
+ packet.extract(); // Extract the raw data into fields
+
+ packet.LI(packet.defaultLeapInd); // Clear the leap second adjustment. TODO: set this correctly using
+ // getLeapSecondEvent from the GNSS
+ packet.VN(packet.defaultVersion); // Set the version number
+ packet.mode(packet.defaultMode); // Set the mode
+ packet.stratum = packet.defaultStratum; // Set the stratum
+ packet.pollExponent = settings.ntpPollExponent; // Set the poll interval
+ packet.precision = settings.ntpPrecision; // Set the precision
+ packet.rootDelay = packet.convertMicrosToSecsAndFraction(settings.ntpRootDelay); // Set the Root Delay
+ packet.rootDispersion =
+ packet.convertMicrosToSecsAndFraction(settings.ntpRootDispersion); // Set the Root Dispersion
+ for (uint8_t i = 0; i < packet.referenceIdLen; i++)
+ packet.referenceId[i] = settings.ntpReferenceId[i]; // Set the reference Id
+
+ // REF: http://support.ntp.org/bin/view/Support/DraftRfc2030
+ // '.. the client sets the Transmit Timestamp field in the request
+ // to the time of day according to the client clock in NTP timestamp format.'
+ // '.. The server copies this field to the originate timestamp in the reply and
+ // sets the Receive Timestamp and Transmit Timestamp fields to the time of day
+ // according to the server clock in NTP timestamp format.'
+
+ // Important note: the NTP Era started January 1st 1900.
+ // tv will contain the time based on the Unix epoch (January 1st 1970)
+ // We need to adjust...
+
+ // First, add the client transmit timestamp to our diagnostics
+ if (ntpDiag != nullptr)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf), "Originate Timestamp (Client Transmit): %u.%06u\r\n",
+ packet.transmitTimestampSeconds, packet.convertFractionToMicros(packet.transmitTimestampFraction));
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+
+ // Copy the client transmit timestamp into the originate timestamp
+ packet.originateTimestampSeconds = packet.transmitTimestampSeconds;
+ packet.originateTimestampFraction = packet.transmitTimestampFraction;
+
+ // Set the receive timestamp to the time we received the packet (logged by the W5500 interrupt)
+ uint32_t recUnixSeconds = recTv->tv_sec;
+ recUnixSeconds -= settings.timeZoneSeconds; // Subtract the time zone offset to convert recTv to Unix time
+ recUnixSeconds -= settings.timeZoneMinutes * 60;
+ recUnixSeconds -= settings.timeZoneHours * 60 * 60;
+ packet.receiveTimestampSeconds = packet.convertUnixSecondsToNTP(recUnixSeconds); // Unix -> NTP
+ packet.receiveTimestampFraction = packet.convertMicrosToFraction(recTv->tv_usec); // Micros to 1/2^32
+
+ // Add the receive timestamp to the diagnostics
+ if (ntpDiag != nullptr)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf), "Received Timestamp: %u.%06u\r\n",
+ packet.receiveTimestampSeconds, packet.convertFractionToMicros(packet.receiveTimestampFraction));
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+
+ // Add when our clock was last sync'd
+ uint32_t syncUnixSeconds = syncTv->tv_sec;
+ syncUnixSeconds -= settings.timeZoneSeconds; // Subtract the time zone offset to convert recTv to Unix time
+ syncUnixSeconds -= settings.timeZoneMinutes * 60;
+ syncUnixSeconds -= settings.timeZoneHours * 60 * 60;
+ packet.referenceTimestampSeconds = packet.convertUnixSecondsToNTP(syncUnixSeconds); // Unix -> NTP
+ packet.referenceTimestampFraction = packet.convertMicrosToFraction(syncTv->tv_usec); // Micros to 1/2^32
+
+ // Add that to the diagnostics
+ if (ntpDiag != nullptr)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf), "Reference Timestamp (Last Sync): %u.%06u\r\n",
+ packet.referenceTimestampSeconds,
+ packet.convertFractionToMicros(packet.referenceTimestampFraction));
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+
+ // Add the transmit time - i.e. now!
+ timeval txTime;
+ gettimeofday(&txTime, nullptr);
+ uint32_t nowUnixSeconds = txTime.tv_sec;
+ nowUnixSeconds -= settings.timeZoneSeconds; // Subtract the time zone offset to convert recTv to Unix time
+ nowUnixSeconds -= settings.timeZoneMinutes * 60;
+ nowUnixSeconds -= settings.timeZoneHours * 60 * 60;
+ packet.transmitTimestampSeconds = packet.convertUnixSecondsToNTP(nowUnixSeconds); // Unix -> NTP
+ packet.transmitTimestampFraction = packet.convertMicrosToFraction(txTime.tv_usec); // Micros to 1/2^32
+
+ packet.insert(); // Copy the data fields back into the buffer
+
+ // Now transmit the response to the client.
+ ntpServer->beginPacket(remoteIP, remotePort);
+ ntpServer->write(packet.packet, NTPpacket::NTPpacketSize);
+ int result = ntpServer->endPacket();
+ processed = true;
+
+ // Add our server transmit time to the diagnostics
+ if (ntpDiag != nullptr)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf), "Transmit Timestamp: %u.%06u\r\n",
+ packet.transmitTimestampSeconds, packet.convertFractionToMicros(packet.transmitTimestampFraction));
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+
+ /*
+ // Add the socketSendUDP result to the diagnostics
+ if (ntpDiag != nullptr)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf), "socketSendUDP result: %d\r\n", result);
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+ */
+
+ /*
+ // Add the packet to the diagnostics
+ if (ntpDiag != nullptr)
+ {
+ char tmpbuf[128];
+ snprintf(tmpbuf, sizeof(tmpbuf), "Packet: ");
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ for (int i = 0; i < NTPpacket::NTPpacketSize; i++)
+ {
+ snprintf(tmpbuf, sizeof(tmpbuf), "%02X ", packet.packet[i]);
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+ snprintf(tmpbuf, sizeof(tmpbuf), "\r\n");
+ strlcat(ntpDiag, tmpbuf, ntpDiagSize);
+ }
+ */
+ }
+ return processed;
+}
+
+// Configure specific aspects of the receiver for NTP mode
+bool configureUbloxModuleNTP()
+{
+ if (!HAS_GNSS_TP_INT)
+ return (false);
+
+ if (online.gnss == false)
+ return (false);
+
+ // If our settings haven't changed, and this is first config since power on, trust ZED's settings
+ // Unless this is a Ref Syn - where the GNSS has no battery-backed RAM
+ if (productVariant != REFERENCE_STATION && settings.updateZEDSettings == 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.
+
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, storePVTdata
+
+ theGNSS.setNMEAGPGGAcallbackPtr(
+ nullptr); // Disable GPGGA call back that may have been set during Rover NTRIP Client mode
+
+ int tryNo = -1;
+ bool success = false;
+
+ // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS
+ // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI
+ // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being
+ // processed.
+ while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success)
+ {
+ bool response = true;
+
+ // In NTP mode we force 1Hz
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_MEAS, 1000);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_NAV, 1);
+
+ // Survey mode is only available on ZED-F9P modules
+ if (zedModuleType == PLATFORM_F9P)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode
+
+ // Set dynamic model to stationary
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, DYN_MODEL_STATIONARY); // Set dynamic model
+
+ // Set time pulse to 1Hz (100:900)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio)
+ response &=
+ theGNSS.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 &= theGNSS.addCfgValset(UBLOX_CFG_TP_TP1_ENA, 1); // Enable timepulse
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_POL_TP1, 1); // 1 = rising edge
+
+ // While the module is _locking_ to GNSS time, turn off pulse
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us
+ response &= theGNSS.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 (100ms high, 900ms low)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, 1000000); // Set the period between pulses is us
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, 100000); // Set the pulse length in us
+
+ // Ensure pulse is aligned to top-of-second. This is the default. Set it here just to make sure.
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_ALIGN_TO_TOW_TP1, 1);
+
+ // Set the time grid to UTC. This is the default. Set it here just to make sure.
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_TIMEGRID_TP1, 0); // 0=UTC; 1=GPS
+
+ // Sync to GNSS. This is the default. Set it here just to make sure.
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TP_SYNC_GNSS_TP1, 1);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation
+
+ // Ensure PVT, HPPOSLLH and TP messages are being output at 1Hz on the correct port
+ if (USE_I2C_GNSS)
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_PVT_I2C, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_I2C, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_TIM_TP_I2C, 1);
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_ESF_STATUS_I2C, 1);
+ }
+ }
+ else
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_PVT_SPI, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_SPI, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_TIM_TP_SPI, 1);
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_UBX_ESF_STATUS_SPI, 1);
+ }
+ }
+
+ response &= theGNSS.sendCfgValset(); // Closing value
+
+ if (response)
+ success = true;
+ }
+
+ if (!success)
+ systemPrintln("NTP config fail");
+
+ return (success);
+}
+
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// NTP Server routines
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Update the state of the NTP server state machine
+void ntpServerSetState(uint8_t newState)
+{
+ if ((settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_STATE)) && (!inMainMenu))
+ {
+ if (ntpServerState == newState)
+ systemPrint("*");
+ else
+ systemPrintf("%s --> ", ntpServerStateName[ntpServerState]);
+ }
+ ntpServerState = newState;
+ if (settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_STATE))
+ {
+ PERIODIC_CLEAR(PD_NTP_SERVER_STATE);
+ if (newState >= NTP_STATE_MAX)
+ {
+ systemPrintf("Unknown NTP Server state: %d\r\n", newState);
+ reportFatalError("Unknown NTP Server state");
+ }
+ else if (!inMainMenu)
+ systemPrintln(ntpServerStateName[ntpServerState]);
+ }
+}
+
+// Stop the NTP server
+void ntpServerStop()
+{
+ // Mark the NTP server as off
+ online.NTPServer = false;
+
+ // Release the NTP server memory
+ if (ntpServer)
+ {
+ w5500DisableSocketInterrupt(ntpSockIndex); // Disable the receive interrupt
+ ntpServer->stop();
+ delete ntpServer;
+ ntpServer = nullptr;
+ if (!inMainMenu)
+ reportHeapNow(settings.debugNtp);
+ }
+
+ // Release the network resources
+ if (networkGetUserNetwork(NETWORK_USER_NTP_SERVER))
+ networkUserClose(NETWORK_USER_NTP_SERVER);
+
+ // Stop the NTP server
+ ntpServerSetState(NTP_STATE_OFF);
+}
+
+// Update the NTP server state
+void ntpServerUpdate()
+{
+ char ntpDiag[512]; // Char array to hold diagnostic messages
+
+ if (!HAS_ETHERNET)
+ return;
+
+ // Shutdown the NTP server when the mode or setting changes
+ if (NEQ_RTK_MODE(ntpServerMode))
+ {
+ if (ntpServerState > NTP_STATE_OFF)
+ ntpServerStop();
+ return;
+ }
+
+ // Process the NTP state
+ DMW_st(ntpServerSetState, ntpServerState);
+ switch (ntpServerState)
+ {
+ default:
+ break;
+
+ case NTP_STATE_OFF:
+ // Determine if the NTP server is enabled
+ if (EQ_RTK_MODE(ntpServerMode))
+ {
+ // Start the network
+ if (networkUserOpen(NETWORK_USER_NTP_SERVER, NETWORK_TYPE_ETHERNET))
+ ntpServerSetState(NTP_STATE_NETWORK_STARTING);
+ }
+ break;
+
+ // Wait for the network conection
+ case NTP_STATE_NETWORK_STARTING:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTP_SERVER))
+ // Stop the NTP server, restart it if possible
+ ntpServerStop();
+
+ // Determine if the network is connected
+ else if (networkUserConnected(NETWORK_USER_NTP_SERVER))
+ ntpServerSetState(NTP_STATE_NETWORK_CONNECTED);
+ break;
+
+ case NTP_STATE_NETWORK_CONNECTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTP_SERVER))
+ // Stop the NTP server, restart it if possible
+ ntpServerStop();
+
+ // Attempt to start the NTP server
+ else
+ {
+ ntpServer = new derivedEthernetUDP;
+ if (!ntpServer)
+ // Insufficient memory to start the NTP server
+ ntpServerStop();
+ else
+ {
+ // Start the NTP server
+ ntpServer->begin(settings.ethernetNtpPort);
+ ntpSockIndex = ntpServer->getSockIndex(); // Get the socket index
+ w5500ClearSocketInterrupts(); // Clear all interrupts
+ w5500EnableSocketInterrupt(ntpSockIndex); // Enable the RECV interrupt for the desired socket index
+ online.NTPServer = true;
+ if (!inMainMenu)
+ reportHeapNow(settings.debugNtp);
+ ntpServerSetState(NTP_STATE_SERVER_RUNNING);
+ }
+ }
+ break;
+
+ case NTP_STATE_SERVER_RUNNING:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTP_SERVER))
+ // Stop the NTP server, restart it if possible
+ ntpServerStop();
+
+ else
+ {
+ if (w5500CheckSocketInterrupt(ntpSockIndex))
+ w5500ClearSocketInterrupt(ntpSockIndex); // Clear the socket interrupt here
+
+ // Check for new NTP requests - if the time has been sync'd
+ bool processed = ntpProcessOneRequest(systemState == STATE_NTPSERVER_SYNC, (const timeval *)ðernetNtpTv,
+ (const timeval *)&gnssSyncTv, ntpDiag, sizeof(ntpDiag));
+ if (processed)
+ {
+ // Print the diagnostics - if enabled
+ if ((settings.debugNtp || PERIODIC_DISPLAY(PD_NTP_SERVER_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_NTP_SERVER_DATA);
+ systemPrint(ntpDiag);
+ }
+
+ // Log the NTP request to file - if enabled
+ if (settings.enableNTPFile)
+ {
+ // Gain access to the SPI controller for the microSD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_NTPEVENT);
+
+ // Get the marks file name
+ char fileName[32];
+ bool fileOpen = false;
+ bool sdCardWasOnline;
+ int year;
+ int month;
+ int day;
+
+ // Get the date
+ year = rtc.getYear();
+ month = rtc.getMonth() + 1;
+ day = rtc.getDay();
+
+ // Build the file name
+ snprintf(fileName, sizeof(fileName), "/NTP_Requests_%04d_%02d_%02d.txt", year, month, day);
+
+ // Try to gain access the SD card
+ sdCardWasOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ if (online.microSD == true)
+ {
+ // Check if the NTP file already exists
+ bool ntpFileExists = false;
+ if (USE_SPI_MICROSD)
+ {
+ ntpFileExists = sd->exists(fileName);
+ }
+ #ifdef COMPILE_SD_MMC
+ else
+ {
+ ntpFileExists = SD_MMC.exists(fileName);
+ }
+ #endif // COMPILE_SD_MMC
+
+ // Open the NTP file
+ FileSdFatMMC ntpFile;
+
+ if (ntpFileExists)
+ {
+ if (ntpFile && ntpFile.open(fileName, O_APPEND | O_WRITE))
+ {
+ fileOpen = true;
+ ntpFile.updateFileCreateTimestamp();
+ }
+ }
+ else
+ {
+ if (ntpFile && ntpFile.open(fileName, O_CREAT | O_WRITE))
+ {
+ fileOpen = true;
+ ntpFile.updateFileAccessTimestamp();
+
+ // If you want to add a file header, do it here
+ }
+ }
+
+ if (fileOpen)
+ {
+ // Write the NTP request to the file
+ ntpFile.write((const uint8_t *)ntpDiag, strlen(ntpDiag));
+
+ // Update the file to create time & date
+ ntpFile.updateFileCreateTimestamp();
+
+ // Close the mark file
+ ntpFile.close();
+ }
+
+ // Dismount the SD card
+ if (!sdCardWasOnline)
+ endSD(true, false);
+ }
+
+ // Done with the SPI controller
+ xSemaphoreGive(sdCardSemaphore);
+
+ lastLoggedNTPRequest = millis();
+ ntpLogIncreasing = true;
+ } // End sdCardSemaphore
+ }
+ }
+
+ if (millis() > (lastLoggedNTPRequest + 5000))
+ ntpLogIncreasing = false;
+ }
+ break;
+ }
+
+ // Periodically display the NTP server state
+ if (PERIODIC_DISPLAY(PD_NTP_SERVER_STATE))
+ ntpServerSetState(ntpServerState);
+}
+
+// Verify the NTP tables
+void ntpValidateTables()
+{
+ if (ntpServerStateNameEntries != NTP_STATE_MAX)
+ reportFatalError("Fix ntpServerStateNameEntries to match NTP_STATE");
+}
+
+#endif // COMPILE_ETHERNET
diff --git a/Firmware/RTK_Surveyor/NVM.ino b/Firmware/RTK_Surveyor/NVM.ino
index c38a639de..8701ead8d 100644
--- a/Firmware/RTK_Surveyor/NVM.ino
+++ b/Firmware/RTK_Surveyor/NVM.ino
@@ -1,488 +1,1833 @@
+/*
+ For any new setting added to the settings struct, we must add it to setting file
+ recording and logging, and to the WiFi AP load/read in the following places:
+
+ recordSystemSettingsToFile();
+ parseLine();
+ createSettingsString();
+ updateSettingWithValue();
+
+ form.h also needs to be updated to include a space for user input. This is best
+ edited in the index.html and main.js files.
+*/
+
+// We use the LittleFS library to store user profiles in SPIFFs
+// Move selected user profile from SPIFFs into settings struct (RAM)
+// We originally used EEPROM but it was limited to 4096 bytes. Each settings struct is ~4000 bytes
+// so multiple user profiles wouldn't fit. Prefences was limited to a single putBytes of ~3000 bytes.
+// So we moved again to SPIFFs. It's being replaced by LittleFS so here we are.
void loadSettings()
{
- //First load any settings from NVM
- //After, we'll load settings from config file if available
- //We'll then re-record settings so that the settings from the file over-rides internal NVM settings
-
- //Check to see if EEPROM is blank
- uint32_t testRead = 0;
- if (EEPROM.get(0, testRead) == 0xFFFFFFFF)
- {
- Serial.println(F("EEPROM is blank. Default settings applied"));
- recordSystemSettings(); //Record default settings to EEPROM and config file. At power on, settings are in default state
- }
-
- //Check that the current settings struct size matches what is stored in EEPROM
- //Misalignment happens when we add a new feature or setting
- int tempSize = 0;
- EEPROM.get(0, tempSize); //Load the sizeOfSettings
- if (tempSize != sizeof(settings))
- {
- Serial.println(F("Settings wrong size. Default settings applied"));
- recordSystemSettings(); //Record default settings to EEPROM and config file. At power on, settings are in default state
- }
-
- //Check that the rtkIdentifier is correct
- //(It is possible for two different versions of the code to have the same sizeOfSettings - which causes problems!)
- int tempIdentifier = 0;
- EEPROM.get(sizeof(int), tempIdentifier); //Load the identifier from the EEPROM location after sizeOfSettings (int)
- if (tempIdentifier != RTK_IDENTIFIER)
- {
- Serial.println(F("Settings are not valid for this variant of RTK Surveyor. Default settings applied"));
- recordSystemSettings(); //Record default settings to EEPROM and config file. At power on, settings are in default state
- }
-
- //Read current settings
- EEPROM.get(0, settings);
-
- loadSystemSettingsFromFile(); //Load any settings from config file. This will over-write any pre-existing EEPROM settings.
- //Record these new settings to EEPROM and config file to be sure they are the same
- //(do this even if loadSystemSettingsFromFile returned false)
- recordSystemSettings();
+ // If we have a profile in both LFS and SD, the SD settings will overwrite LFS
+ loadSystemSettingsFromFileLFS(settingsFileName, &settings);
+
+ // Temp store any variables from LFS that should override SD
+ int resetCount = settings.resetCount;
+
+ loadSystemSettingsFromFileSD(settingsFileName, &settings);
+ settings.resetCount = resetCount;
+
+ // Change empty profile name to 'Profile1' etc
+ if (strlen(settings.profileName) == 0)
+ snprintf(settings.profileName, sizeof(settings.profileName), "Profile%d", profileNumber + 1);
+
+ // Record these settings to LittleFS and SD file to be sure they are the same
+ recordSystemSettings();
+
+ // Get bitmask of active profiles
+ activeProfiles = loadProfileNames();
+
+ systemPrintf("Profile '%s' loaded\r\n", profileNames[profileNumber]);
+}
+
+// Set the settingsFileName and coordinate file names used many places
+void setSettingsFileName()
+{
+ snprintf(settingsFileName, sizeof(settingsFileName), "/%s_Settings_%d.txt", platformFilePrefix, profileNumber);
+ snprintf(stationCoordinateECEFFileName, sizeof(stationCoordinateECEFFileName), "/StationCoordinates-ECEF_%d.csv",
+ profileNumber);
+ snprintf(stationCoordinateGeodeticFileName, sizeof(stationCoordinateGeodeticFileName),
+ "/StationCoordinates-Geodetic_%d.csv", profileNumber);
+}
+
+// Load only LFS settings without recording
+// Used at very first boot to test for resetCounter
+void loadSettingsPartial()
+{
+ // First, look up the last used profile number
+ loadProfileNumber();
+
+ // Set the settingsFileName used many places
+ setSettingsFileName();
+
+ loadSystemSettingsFromFileLFS(settingsFileName, &settings);
}
-//Record the current settings struct to EEPROM and then to config file
void recordSystemSettings()
{
- settings.sizeOfSettings = sizeof(settings);
- if (settings.sizeOfSettings > EEPROM_SIZE)
- {
- displayError((char*)"EEPROM");
+ settings.sizeOfSettings = sizeof(settings); // Update to current setting size
+
+ recordSystemSettingsToFileSD(settingsFileName); // Record to SD if available
+ recordSystemSettingsToFileLFS(settingsFileName); // Record to LFS if available
+}
+
+// Export the current settings to a config file on SD
+// We share the recording with LittleFS so this is all the semphore and SD specific handling
+void recordSystemSettingsToFileSD(char *fileName)
+{
+ bool gotSemaphore = false;
+ bool wasSdCardOnline;
- while (1) //Hard freeze
+ // Try to gain access the SD card
+ wasSdCardOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ while (online.microSD == true)
{
- Serial.printf("Size of settings is %d bytes\n\r", sizeof(settings));
- Serial.println(F("Increase the EEPROM footprint!"));
- delay(1000);
+ // Attempt to write to file system. This avoids collisions with file writing from other functions like
+ // updateLogs()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_RECORDSETTINGS);
+
+ gotSemaphore = true;
+
+ if (USE_SPI_MICROSD)
+ {
+ if (sd->exists(fileName))
+ {
+ log_d("Removing from SD: %s", fileName);
+ sd->remove(fileName);
+ }
+
+ SdFile settingsFile; // FAT32
+ if (settingsFile.open(fileName, O_CREAT | O_APPEND | O_WRITE) == false)
+ {
+ systemPrintln("Failed to create settings file");
+ break;
+ }
+
+ updateDataFileCreate(&settingsFile); // Update the file to create time & date
+
+ recordSystemSettingsToFile((File *)&settingsFile); // Record all the settings via strings to file
+
+ updateDataFileAccess(&settingsFile); // Update the file access time & date
+
+ settingsFile.close();
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (SD_MMC.exists(fileName))
+ {
+ log_d("Removing from SD: %s", fileName);
+ SD_MMC.remove(fileName);
+ }
+
+ File settingsFile = SD_MMC.open(fileName, FILE_WRITE);
+
+ if (!settingsFile)
+ {
+ systemPrintln("Failed to create settings file");
+ break;
+ }
+
+ recordSystemSettingsToFile(&settingsFile); // Record all the settings via strings to file
+
+ settingsFile.close();
+ }
+#endif // COMPILE_SD_MMC
+
+ log_d("Settings recorded to SD: %s", fileName);
+ }
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+
+ // This is an error because the current settings no longer match the settings
+ // on the microSD card, and will not be restored to the expected settings!
+ systemPrintf("sdCardSemaphore failed to yield, held by %s, NVM.ino line %d\r\n", semaphoreHolder, __LINE__);
+ }
+ break;
}
- }
- EEPROM.put(0, settings);
- EEPROM.commit();
- delay(1); //Give CPU time to pet WDT
- recordSystemSettingsToFile();
+ // Release access the SD card
+ if (online.microSD && (!wasSdCardOnline))
+ endSD(gotSemaphore, true);
+ else if (gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
}
-//Export the current settings to a config file
-void recordSystemSettingsToFile()
+// Export the current settings to a config file on SD
+// We share the recording with LittleFS so this is all the semphore and SD specific handling
+void recordSystemSettingsToFileLFS(char *fileName)
{
- if (online.microSD == true)
- {
- //Attempt to write to file system. This avoids collisions with file writing from other functions like updateLogs()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
- {
- //Assemble settings file name
- char settingsFileName[40]; //SFE_Surveyor_Settings.txt
- strcpy(settingsFileName, platformFilePrefix);
- strcat(settingsFileName, "_Settings.txt");
-
- if (sd.exists(settingsFileName))
- sd.remove(settingsFileName);
-
- SdFile settingsFile; //FAT32
- if (settingsFile.open(settingsFileName, O_CREAT | O_APPEND | O_WRITE) == false)
- {
- Serial.println(F("Failed to create settings file"));
- return;
- }
- if (online.gnss)
- updateDataFileCreate(&settingsFile); // Update the file to create time & date
-
- settingsFile.println("sizeOfSettings=" + (String)settings.sizeOfSettings);
- settingsFile.println("rtkIdentifier=" + (String)settings.rtkIdentifier);
-
- char firmwareVersion[30]; //v1.3 December 31 2021
- sprintf(firmwareVersion, "v%d.%d-%s", FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, __DATE__);
- settingsFile.println("rtkFirmwareVersion=" + (String)firmwareVersion);
-
- settingsFile.println("zedFirmwareVersion=" + (String)zedFirmwareVersion);
- settingsFile.println("printDebugMessages=" + (String)settings.printDebugMessages);
- settingsFile.println("enableSD=" + (String)settings.enableSD);
- settingsFile.println("enableDisplay=" + (String)settings.enableDisplay);
- settingsFile.println("maxLogTime_minutes=" + (String)settings.maxLogTime_minutes);
- settingsFile.println("observationSeconds=" + (String)settings.observationSeconds);
- settingsFile.println("observationPositionAccuracy=" + (String)settings.observationPositionAccuracy);
- settingsFile.println("fixedBase=" + (String)settings.fixedBase);
- settingsFile.println("fixedBaseCoordinateType=" + (String)settings.fixedBaseCoordinateType);
- settingsFile.println("fixedEcefX=" + (String)settings.fixedEcefX);
- settingsFile.println("fixedEcefY=" + (String)settings.fixedEcefY);
- settingsFile.println("fixedEcefZ=" + (String)settings.fixedEcefZ);
-
- //Print Lat/Long doubles with 9 decimals
- char longPrint[20]; //-105.123456789
- sprintf(longPrint, "%0.9f", settings.fixedLat);
- settingsFile.println("fixedLat=" + (String)longPrint);
- sprintf(longPrint, "%0.9f", settings.fixedLong);
- settingsFile.println("fixedLong=" + (String)longPrint);
- sprintf(longPrint, "%0.4f", settings.fixedAltitude);
- settingsFile.println("fixedAltitude=" + (String)longPrint);
-
- settingsFile.println("dataPortBaud=" + (String)settings.dataPortBaud);
- settingsFile.println("radioPortBaud=" + (String)settings.radioPortBaud);
- settingsFile.println("enableNtripServer=" + (String)settings.enableNtripServer);
- settingsFile.println("casterHost=" + (String)settings.casterHost);
- settingsFile.println("casterPort=" + (String)settings.casterPort);
- settingsFile.println("mountPoint=" + (String)settings.mountPoint);
- settingsFile.println("mountPointPW=" + (String)settings.mountPointPW);
- settingsFile.println("wifiSSID=" + (String)settings.wifiSSID);
- settingsFile.println("wifiPW=" + (String)settings.wifiPW);
- settingsFile.println("surveyInStartingAccuracy=" + (String)settings.surveyInStartingAccuracy);
- settingsFile.println("measurementRate=" + (String)settings.measurementRate);
- settingsFile.println("navigationRate=" + (String)settings.navigationRate);
- settingsFile.println("enableI2Cdebug=" + (String)settings.enableI2Cdebug);
- settingsFile.println("enableHeapReport=" + (String)settings.enableHeapReport);
- settingsFile.println("enableTaskReports=" + (String)settings.enableTaskReports);
- settingsFile.println("dataPortChannel=" + (String)settings.dataPortChannel);
- settingsFile.println("spiFrequency=" + (String)settings.spiFrequency);
- settingsFile.println("sppRxQueueSize=" + (String)settings.sppRxQueueSize);
- settingsFile.println("sppTxQueueSize=" + (String)settings.sppTxQueueSize);
- settingsFile.println("dynamicModel=" + (String)settings.dynamicModel);
- settingsFile.println("lastState=" + (String)settings.lastState);
- settingsFile.println("throttleDuringSPPCongestion=" + (String)settings.throttleDuringSPPCongestion);
-
- //Record constellation settings
- for (int x = 0 ; x < MAX_CONSTELLATIONS ; x++)
- {
- char tempString[50]; //constellation.BeiDou=1
- sprintf(tempString, "constellation.%s=%d", ubxConstellations[x].textName, ubxConstellations[x].enabled);
- settingsFile.println(tempString);
- }
-
- //Record message settings
- for (int x = 0 ; x < MAX_UBX_MSG ; x++)
- {
- char tempString[50]; //message.nmea_dtm.msgRate=5
- sprintf(tempString, "message.%s.msgRate=%d", ubxMessages[x].msgTextName, ubxMessages[x].msgRate);
- settingsFile.println(tempString);
- }
-
- if (online.gnss)
- updateDataFileAccess(&settingsFile); // Update the file access time & date
-
- settingsFile.close();
-
- xSemaphoreGive(xFATSemaphore);
- }
- }
+ if (online.fs == true)
+ {
+ if (LittleFS.exists(fileName))
+ {
+ LittleFS.remove(fileName);
+ log_d("Removing LittleFS: %s", fileName);
+ }
+
+ File settingsFile = LittleFS.open(fileName, FILE_WRITE);
+ if (!settingsFile)
+ {
+ log_d("Failed to write to settings file %s", fileName);
+ }
+ else
+ {
+ recordSystemSettingsToFile(&settingsFile); // Record all the settings via strings to file
+ settingsFile.close();
+ log_d("Settings recorded to LittleFS: %s", fileName);
+ }
+ }
}
-//If a config file exists on the SD card, load them and overwrite the local settings
-//Heavily based on ReadCsvFile from SdFat library
-//Returns true if some settings were loaded from a file
-//Returns false if a file was not opened/loaded
-bool loadSystemSettingsFromFile()
+// Write the settings struct to a clear text file
+void recordSystemSettingsToFile(File *settingsFile)
{
- if (online.microSD == true)
- {
- //Attempt to access file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile() and F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
- {
- //Assemble settings file name
- char settingsFileName[40]; //SFE_Surveyor_Settings.txt
- strcpy(settingsFileName, platformFilePrefix);
- strcat(settingsFileName, "_Settings.txt");
-
- if (sd.exists(settingsFileName))
- {
- SdFile settingsFile; //FAT32
- if (settingsFile.open(settingsFileName, O_READ) == false)
- {
- Serial.println(F("Failed to open settings file"));
- xSemaphoreGive(xFATSemaphore);
- return (false);
- }
-
- char line[60];
- int lineNumber = 0;
-
- while (settingsFile.available()) {
-
- //Get the next line from the file
- //int n = getLine(&settingsFile, line, sizeof(line)); //Use with SD library
- int n = settingsFile.fgets(line, sizeof(line)); //Use with SdFat library
- if (n <= 0) {
- Serial.printf("Failed to read line %d from settings file\r\n", lineNumber);
- }
- else if (line[n - 1] != '\n' && n == (sizeof(line) - 1)) {
- Serial.printf("Settings line %d too long\r\n", lineNumber);
+ settingsFile->printf("%s=%d\r\n", "sizeOfSettings", settings.sizeOfSettings);
+ settingsFile->printf("%s=%d\r\n", "rtkIdentifier", settings.rtkIdentifier);
+
+ char firmwareVersion[30]; // v1.3 December 31 2021
+ getFirmwareVersion(firmwareVersion, sizeof(firmwareVersion), true);
+ settingsFile->printf("%s=%s\r\n", "rtkFirmwareVersion", firmwareVersion);
+
+ settingsFile->printf("%s=%s\r\n", "zedFirmwareVersion", zedFirmwareVersion);
+
+ settingsFile->printf("%s=%s\r\n", "zedUniqueId", zedUniqueId);
+
+ if (productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ settingsFile->printf("%s=%s\r\n", "neoFirmwareVersion", neoFirmwareVersion);
+
+ settingsFile->printf("%s=%d\r\n", "printDebugMessages", settings.printDebugMessages);
+ settingsFile->printf("%s=%d\r\n", "enableSD", settings.enableSD);
+ settingsFile->printf("%s=%d\r\n", "enableDisplay", settings.enableDisplay);
+ settingsFile->printf("%s=%d\r\n", "maxLogTime_minutes", settings.maxLogTime_minutes);
+ settingsFile->printf("%s=%d\r\n", "maxLogLength_minutes", settings.maxLogLength_minutes);
+ settingsFile->printf("%s=%d\r\n", "observationSeconds", settings.observationSeconds);
+ settingsFile->printf("%s=%0.2f\r\n", "observationPositionAccuracy", settings.observationPositionAccuracy);
+ settingsFile->printf("%s=%d\r\n", "fixedBase", settings.fixedBase);
+ settingsFile->printf("%s=%d\r\n", "fixedBaseCoordinateType", settings.fixedBaseCoordinateType);
+ settingsFile->printf("%s=%0.3f\r\n", "fixedEcefX", settings.fixedEcefX); //-1280206.568
+ settingsFile->printf("%s=%0.3f\r\n", "fixedEcefY", settings.fixedEcefY);
+ settingsFile->printf("%s=%0.3f\r\n", "fixedEcefZ", settings.fixedEcefZ);
+ settingsFile->printf("%s=%0.9f\r\n", "fixedLat", settings.fixedLat); // 40.09029479
+ settingsFile->printf("%s=%0.9f\r\n", "fixedLong", settings.fixedLong);
+ settingsFile->printf("%s=%0.4f\r\n", "fixedAltitude", settings.fixedAltitude);
+ settingsFile->printf("%s=%d\r\n", "dataPortBaud", settings.dataPortBaud);
+ settingsFile->printf("%s=%d\r\n", "radioPortBaud", settings.radioPortBaud);
+ settingsFile->printf("%s=%0.1f\r\n", "surveyInStartingAccuracy", settings.surveyInStartingAccuracy);
+ settingsFile->printf("%s=%d\r\n", "measurementRate", settings.measurementRate);
+ settingsFile->printf("%s=%d\r\n", "navigationRate", settings.navigationRate);
+ settingsFile->printf("%s=%d\r\n", "enableI2Cdebug", settings.enableI2Cdebug);
+ settingsFile->printf("%s=%d\r\n", "enableHeapReport", settings.enableHeapReport);
+ settingsFile->printf("%s=%d\r\n", "enableTaskReports", settings.enableTaskReports);
+ settingsFile->printf("%s=%d\r\n", "dataPortChannel", (uint8_t)settings.dataPortChannel);
+ settingsFile->printf("%s=%d\r\n", "spiFrequency", settings.spiFrequency);
+ settingsFile->printf("%s=%d\r\n", "sppRxQueueSize", settings.sppRxQueueSize);
+ settingsFile->printf("%s=%d\r\n", "sppTxQueueSize", settings.sppTxQueueSize);
+ settingsFile->printf("%s=%d\r\n", "dynamicModel", settings.dynamicModel);
+ settingsFile->printf("%s=%d\r\n", "lastState", settings.lastState);
+ settingsFile->printf("%s=%d\r\n", "enableSensorFusion", settings.enableSensorFusion);
+ settingsFile->printf("%s=%d\r\n", "autoIMUmountAlignment", settings.autoIMUmountAlignment);
+ settingsFile->printf("%s=%d\r\n", "enableResetDisplay", settings.enableResetDisplay);
+ settingsFile->printf("%s=%d\r\n", "enableExternalPulse", settings.enableExternalPulse);
+ settingsFile->printf("%s=%llu\r\n", "externalPulseTimeBetweenPulse_us", settings.externalPulseTimeBetweenPulse_us);
+ settingsFile->printf("%s=%llu\r\n", "externalPulseLength_us", settings.externalPulseLength_us);
+ settingsFile->printf("%s=%d\r\n", "externalPulsePolarity", settings.externalPulsePolarity);
+ settingsFile->printf("%s=%d\r\n", "enableExternalHardwareEventLogging",
+ settings.enableExternalHardwareEventLogging);
+ settingsFile->printf("%s=%s\r\n", "profileName", settings.profileName);
+ settingsFile->printf("%s=%d\r\n", "enableNtripServer", settings.enableNtripServer);
+ settingsFile->printf("%s=%d\r\n", "ntripServer_StartAtSurveyIn", settings.ntripServer_StartAtSurveyIn);
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ settingsFile->printf("%s_%d=%s\r\n", "ntripServer_CasterHost", serverIndex, &settings.ntripServer_CasterHost[serverIndex][0]);
+ settingsFile->printf("%s_%d=%d\r\n", "ntripServer_CasterPort", serverIndex, settings.ntripServer_CasterPort[serverIndex]);
+ settingsFile->printf("%s_%d=%s\r\n", "ntripServer_CasterUser", serverIndex, &settings.ntripServer_CasterUser[serverIndex][0]);
+ settingsFile->printf("%s_%d=%s\r\n", "ntripServer_CasterUserPW", serverIndex, &settings.ntripServer_CasterUserPW[serverIndex][0]);
+ settingsFile->printf("%s_%d=%s\r\n", "ntripServer_MountPoint", serverIndex, &settings.ntripServer_MountPoint[serverIndex][0]);
+ settingsFile->printf("%s_%d=%s\r\n", "ntripServer_MountPointPW", serverIndex, &settings.ntripServer_MountPointPW[serverIndex][0]);
+ }
+ settingsFile->printf("%s=%d\r\n", "enableNtripClient", settings.enableNtripClient);
+ settingsFile->printf("%s=%s\r\n", "ntripClient_CasterHost", settings.ntripClient_CasterHost);
+ settingsFile->printf("%s=%d\r\n", "ntripClient_CasterPort", settings.ntripClient_CasterPort);
+ settingsFile->printf("%s=%s\r\n", "ntripClient_CasterUser", settings.ntripClient_CasterUser);
+ settingsFile->printf("%s=%s\r\n", "ntripClient_CasterUserPW", settings.ntripClient_CasterUserPW);
+ settingsFile->printf("%s=%s\r\n", "ntripClient_MountPoint", settings.ntripClient_MountPoint);
+ settingsFile->printf("%s=%s\r\n", "ntripClient_MountPointPW", settings.ntripClient_MountPointPW);
+ settingsFile->printf("%s=%d\r\n", "ntripClient_TransmitGGA", settings.ntripClient_TransmitGGA);
+ settingsFile->printf("%s=%d\r\n", "serialTimeoutGNSS", settings.serialTimeoutGNSS);
+
+ // Point Perfect
+ settingsFile->printf("%s=%s\r\n", "pointPerfectDeviceProfileToken", settings.pointPerfectDeviceProfileToken);
+ settingsFile->printf("%s=%d\r\n", "enablePointPerfectCorrections", settings.enablePointPerfectCorrections);
+ settingsFile->printf("%s=%d\r\n", "autoKeyRenewal", settings.autoKeyRenewal);
+ settingsFile->printf("%s=%s\r\n", "pointPerfectClientID", settings.pointPerfectClientID);
+ settingsFile->printf("%s=%s\r\n", "pointPerfectBrokerHost", settings.pointPerfectBrokerHost);
+ settingsFile->printf("%s=%s\r\n", "pointPerfectLBandTopic", settings.pointPerfectLBandTopic);
+ settingsFile->printf("%s=%s\r\n", "pointPerfectCurrentKey", settings.pointPerfectCurrentKey);
+ settingsFile->printf("%s=%llu\r\n", "pointPerfectCurrentKeyDuration", settings.pointPerfectCurrentKeyDuration);
+ settingsFile->printf("%s=%llu\r\n", "pointPerfectCurrentKeyStart", settings.pointPerfectCurrentKeyStart);
+ settingsFile->printf("%s=%s\r\n", "pointPerfectNextKey", settings.pointPerfectNextKey);
+ settingsFile->printf("%s=%llu\r\n", "pointPerfectNextKeyDuration", settings.pointPerfectNextKeyDuration);
+ settingsFile->printf("%s=%llu\r\n", "pointPerfectNextKeyStart", settings.pointPerfectNextKeyStart);
+ settingsFile->printf("%s=%llu\r\n", "lastKeyAttempt", settings.lastKeyAttempt);
+ settingsFile->printf("%s=%d\r\n", "debugPpCertificate", settings.debugPpCertificate);
+
+ settingsFile->printf("%s=%d\r\n", "updateZEDSettings", settings.updateZEDSettings);
+ settingsFile->printf("%s=%d\r\n", "enableLogging", settings.enableLogging);
+ settingsFile->printf("%s=%d\r\n", "enableARPLogging", settings.enableARPLogging);
+ settingsFile->printf("%s=%d\r\n", "ARPLoggingInterval_s", settings.ARPLoggingInterval_s);
+ settingsFile->printf("%s=%d\r\n", "timeZoneHours", settings.timeZoneHours);
+ settingsFile->printf("%s=%d\r\n", "timeZoneMinutes", settings.timeZoneMinutes);
+ settingsFile->printf("%s=%d\r\n", "timeZoneSeconds", settings.timeZoneSeconds);
+ settingsFile->printf("%s=%d\r\n", "enablePrintState", settings.enablePrintState);
+ settingsFile->printf("%s=%d\r\n", "debugWifiState", settings.debugWifiState);
+ settingsFile->printf("%s=%d\r\n", "debugNtripClientState", settings.debugNtripClientState);
+ settingsFile->printf("%s=%d\r\n", "debugNtripServerState", settings.debugNtripServerState);
+ settingsFile->printf("%s=%d\r\n", "enablePrintPosition", settings.enablePrintPosition);
+ settingsFile->printf("%s=%d\r\n", "enablePrintIdleTime", settings.enablePrintIdleTime);
+ settingsFile->printf("%s=%d\r\n", "enableMarksFile", settings.enableMarksFile);
+ settingsFile->printf("%s=%d\r\n", "enableUART2UBXIn", settings.enableUART2UBXIn);
+ settingsFile->printf("%s=%d\r\n", "enablePrintBatteryMessages", settings.enablePrintBatteryMessages);
+ settingsFile->printf("%s=%d\r\n", "enablePrintRoverAccuracy", settings.enablePrintRoverAccuracy);
+ settingsFile->printf("%s=%d\r\n", "enablePrintBadMessages", settings.enablePrintBadMessages);
+ settingsFile->printf("%s=%d\r\n", "enablePrintLogFileMessages", settings.enablePrintLogFileMessages);
+ settingsFile->printf("%s=%d\r\n", "enablePrintLogFileStatus", settings.enablePrintLogFileStatus);
+ settingsFile->printf("%s=%d\r\n", "enablePrintRingBufferOffsets", settings.enablePrintRingBufferOffsets);
+ settingsFile->printf("%s=%d\r\n", "debugNtripServerRtcm", settings.debugNtripServerRtcm);
+ settingsFile->printf("%s=%d\r\n", "debugNtripClientRtcm", settings.debugNtripClientRtcm);
+ settingsFile->printf("%s=%d\r\n", "enablePrintStates", settings.enablePrintStates);
+ settingsFile->printf("%s=%d\r\n", "enablePrintDuplicateStates", settings.enablePrintDuplicateStates);
+ settingsFile->printf("%s=%d\r\n", "enablePrintRtcSync", settings.enablePrintRtcSync);
+ settingsFile->printf("%s=%d\r\n", "debugNtp", settings.debugNtp);
+ settingsFile->printf("%s=%d\r\n", "enablePrintEthernetDiag", settings.enablePrintEthernetDiag);
+ settingsFile->printf("%s=%d\r\n", "radioType", settings.radioType);
+
+ // Network layer
+ settingsFile->printf("%s=%d\r\n", "defaultNetworkType", settings.defaultNetworkType);
+ settingsFile->printf("%s=%d\r\n", "debugNetworkLayer", settings.debugNetworkLayer);
+ settingsFile->printf("%s=%d\r\n", "enableNetworkFailover", settings.enableNetworkFailover);
+ settingsFile->printf("%s=%d\r\n", "printNetworkStatus", settings.printNetworkStatus);
+
+ // Record peer MAC addresses
+ for (int x = 0; x < settings.espnowPeerCount; x++)
+ {
+ char tempString[50]; // espnowPeers.1=B4,C1,33,42,DE,01,
+ snprintf(tempString, sizeof(tempString), "espnowPeers.%d=%02X,%02X,%02X,%02X,%02X,%02X,", x,
+ settings.espnowPeers[x][0], settings.espnowPeers[x][1], settings.espnowPeers[x][2],
+ settings.espnowPeers[x][3], settings.espnowPeers[x][4], settings.espnowPeers[x][5]);
+ settingsFile->println(tempString);
+ }
+ settingsFile->printf("%s=%d\r\n", "espnowPeerCount", settings.espnowPeerCount);
+ settingsFile->printf("%s=%d\r\n", "enableRtcmMessageChecking", settings.enableRtcmMessageChecking);
+ settingsFile->printf("%s=%d\r\n", "bluetoothRadioType", settings.bluetoothRadioType);
+ settingsFile->printf("%s=%d\r\n", "enablePvtClient", settings.enablePvtClient);
+ settingsFile->printf("%s=%d\r\n", "enablePvtServer", settings.enablePvtServer);
+ settingsFile->printf("%s=%d\r\n", "enablePvtUdpServer", settings.enablePvtUdpServer);
+ settingsFile->printf("%s=%d\r\n", "debugPvtClient", settings.debugPvtClient);
+ settingsFile->printf("%s=%d\r\n", "debugPvtServer", settings.debugPvtServer);
+ settingsFile->printf("%s=%d\r\n", "debugPvtUdpServer", settings.debugPvtUdpServer);
+ settingsFile->printf("%s=%d\r\n", "espnowBroadcast", settings.espnowBroadcast);
+ settingsFile->printf("%s=%d\r\n", "antennaHeight", settings.antennaHeight);
+ settingsFile->printf("%s=%0.2f\r\n", "antennaReferencePoint", settings.antennaReferencePoint);
+ settingsFile->printf("%s=%d\r\n", "echoUserInput", settings.echoUserInput);
+ settingsFile->printf("%s=%d\r\n", "uartReceiveBufferSize", settings.uartReceiveBufferSize);
+ settingsFile->printf("%s=%d\r\n", "gnssHandlerBufferSize", settings.gnssHandlerBufferSize);
+ settingsFile->printf("%s=%d\r\n", "enablePrintBufferOverrun", settings.enablePrintBufferOverrun);
+ settingsFile->printf("%s=%d\r\n", "enablePrintSDBuffers", settings.enablePrintSDBuffers);
+ settingsFile->printf("%s=%d\r\n", "periodicDisplay", settings.periodicDisplay);
+ settingsFile->printf("%s=%d\r\n", "periodicDisplayInterval", settings.periodicDisplayInterval);
+ settingsFile->printf("%s=%d\r\n", "rebootSeconds", settings.rebootSeconds);
+ settingsFile->printf("%s=%d\r\n", "forceResetOnSDFail", settings.forceResetOnSDFail);
+
+ // Record WiFi credential table
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ char tempString[100]; // wifiNetwork0Password=parachutes
+
+ snprintf(tempString, sizeof(tempString), "wifiNetwork%dSSID=%s", x, settings.wifiNetworks[x].ssid);
+ settingsFile->println(tempString);
+ snprintf(tempString, sizeof(tempString), "wifiNetwork%dPassword=%s", x, settings.wifiNetworks[x].password);
+ settingsFile->println(tempString);
+ }
+
+ settingsFile->printf("%s=%d\r\n", "wifiConfigOverAP", settings.wifiConfigOverAP);
+ settingsFile->printf("%s=%d\r\n", "pvtServerPort", settings.pvtServerPort);
+ settingsFile->printf("%s=%d\r\n", "pvtUdpServerPort", settings.pvtUdpServerPort);
+ settingsFile->printf("%s=%d\r\n", "minElev", settings.minElev);
+
+ settingsFile->printf("%s=%d\r\n", "imuYaw", settings.imuYaw);
+ settingsFile->printf("%s=%d\r\n", "imuPitch", settings.imuPitch);
+ settingsFile->printf("%s=%d\r\n", "imuRoll", settings.imuRoll);
+ settingsFile->printf("%s=%d\r\n", "sfDisableWheelDirection", settings.sfDisableWheelDirection);
+ settingsFile->printf("%s=%d\r\n", "sfCombineWheelTicks", settings.sfCombineWheelTicks);
+ settingsFile->printf("%s=%d\r\n", "rateNavPrio", settings.rateNavPrio);
+ settingsFile->printf("%s=%d\r\n", "sfUseSpeed", settings.sfUseSpeed);
+ settingsFile->printf("%s=%d\r\n", "coordinateInputType", settings.coordinateInputType);
+ settingsFile->printf("%s=%d\r\n", "lbandFixTimeout_seconds", settings.lbandFixTimeout_seconds);
+ settingsFile->printf("%s=%d\r\n", "minCNO_F9R", settings.minCNO_F9R);
+ settingsFile->printf("%s=%d\r\n", "minCNO_F9P", settings.minCNO_F9P);
+ settingsFile->printf("%s=%d\r\n", "shutdownNoChargeTimeout_s", settings.shutdownNoChargeTimeout_s);
+ settingsFile->printf("%s=%d\r\n", "disableSetupButton", settings.disableSetupButton);
+ settingsFile->printf("%s=%d\r\n", "useI2cForLbandCorrections", settings.useI2cForLbandCorrections);
+ settingsFile->printf("%s=%d\r\n", "useI2cForLbandCorrectionsConfigured",
+ settings.useI2cForLbandCorrectionsConfigured);
+
+ // Record constellation settings
+ for (int x = 0; x < MAX_CONSTELLATIONS; x++)
+ {
+ char tempString[50]; // constellation.BeiDou=1
+ snprintf(tempString, sizeof(tempString), "constellation.%s=%d", settings.ubxConstellations[x].textName,
+ settings.ubxConstellations[x].enabled);
+ settingsFile->println(tempString);
+ }
+
+ // Record message settings
+ for (int x = 0; x < MAX_UBX_MSG; x++)
+ {
+ char tempString[50]; // message.nmea_dtm.msgRate=5
+ snprintf(tempString, sizeof(tempString), "message.%s.msgRate=%d", ubxMessages[x].msgTextName,
+ settings.ubxMessageRates[x]);
+ settingsFile->println(tempString);
+ }
+
+ // Record Base RTCM message settings
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ char tempString[50]; // messageBase.UBX_RTCM_1094.msgRate=5
+ snprintf(tempString, sizeof(tempString), "messageBase.%s.msgRate=%d",
+ ubxMessages[firstRTCMRecord + x].msgTextName, settings.ubxMessageRatesBase[x]);
+ settingsFile->println(tempString);
+ }
+
+ // Ethernet
+ {
+ settingsFile->printf("%s=%s\r\n", "ethernetIP", settings.ethernetIP.toString().c_str());
+ settingsFile->printf("%s=%s\r\n", "ethernetDNS", settings.ethernetDNS.toString().c_str());
+ settingsFile->printf("%s=%s\r\n", "ethernetGateway", settings.ethernetGateway.toString().c_str());
+ settingsFile->printf("%s=%s\r\n", "ethernetSubnet", settings.ethernetSubnet.toString().c_str());
+ settingsFile->printf("%s=%d\r\n", "httpPort", settings.httpPort);
+ settingsFile->printf("%s=%d\r\n", "ethernetNtpPort", settings.ethernetNtpPort);
+ settingsFile->printf("%s=%d\r\n", "ethernetDHCP", settings.ethernetDHCP);
+ settingsFile->printf("%s=%d\r\n", "enableNTPFile", settings.enableNTPFile);
+ settingsFile->printf("%s=%d\r\n", "pvtClientPort", settings.pvtClientPort);
+ settingsFile->printf("%s=%s\r\n", "pvtClientHost", settings.pvtClientHost);
+ }
+
+ // NTP
+ {
+ settingsFile->printf("%s=%d\r\n", "ntpPollExponent", settings.ntpPollExponent);
+ settingsFile->printf("%s=%d\r\n", "ntpPrecision", settings.ntpPrecision);
+ settingsFile->printf("%s=%d\r\n", "ntpRootDelay", settings.ntpRootDelay);
+ settingsFile->printf("%s=%d\r\n", "ntpRootDispersion", settings.ntpRootDispersion);
+ settingsFile->printf("%s=%s\r\n", "ntpReferenceId", settings.ntpReferenceId);
+ }
+
+ settingsFile->printf("%s=%d\r\n", "mdnsEnable", settings.mdnsEnable);
+ settingsFile->printf("%s=%d\r\n", "serialGNSSRxFullThreshold", settings.serialGNSSRxFullThreshold);
+ settingsFile->printf("%s=%d\r\n", "btReadTaskPriority", settings.btReadTaskPriority);
+ settingsFile->printf("%s=%d\r\n", "gnssReadTaskPriority", settings.gnssReadTaskPriority);
+ settingsFile->printf("%s=%d\r\n", "handleGnssDataTaskPriority", settings.handleGnssDataTaskPriority);
+ settingsFile->printf("%s=%d\r\n", "btReadTaskCore", settings.btReadTaskCore);
+ settingsFile->printf("%s=%d\r\n", "gnssReadTaskCore", settings.gnssReadTaskCore);
+ settingsFile->printf("%s=%d\r\n", "handleGnssDataTaskCore", settings.handleGnssDataTaskCore);
+ settingsFile->printf("%s=%d\r\n", "gnssUartInterruptsCore", settings.gnssUartInterruptsCore);
+ settingsFile->printf("%s=%d\r\n", "bluetoothInterruptsCore", settings.bluetoothInterruptsCore);
+ settingsFile->printf("%s=%d\r\n", "i2cInterruptsCore", settings.i2cInterruptsCore);
+ settingsFile->printf("%s=%d\r\n", "rtcmTimeoutBeforeUsingLBand_s", settings.rtcmTimeoutBeforeUsingLBand_s);
+
+ // Automatic Firmware Update
+ settingsFile->printf("%s=%d\r\n", "autoFirmwareCheckMinutes", settings.autoFirmwareCheckMinutes);
+ settingsFile->printf("%s=%d\r\n", "debugFirmwareUpdate", settings.debugFirmwareUpdate);
+ settingsFile->printf("%s=%d\r\n", "enableAutoFirmwareUpdate", settings.enableAutoFirmwareUpdate);
+
+ settingsFile->printf("%s=%d\r\n", "debugLBand", settings.debugLBand);
+ settingsFile->printf("%s=%d\r\n", "enableCaptivePortal", settings.enableCaptivePortal);
+ settingsFile->printf("%s=%d\r\n", "enableZedUsb", settings.enableZedUsb);
+ settingsFile->printf("%s=%d\r\n", "debugWiFiConfig", settings.debugWiFiConfig);
+
+ settingsFile->printf("%s=%d\r\n", "geographicRegion", settings.geographicRegion);
+
+ // Add new settings above <------------------------------------------------------------>
+}
+
+// Given a fileName, parse the file and load the given settings struct
+// Returns true if some settings were loaded from a file
+// Returns false if a file was not opened/loaded
+bool loadSystemSettingsFromFileSD(char *fileName, Settings *settings)
+{
+ bool gotSemaphore = false;
+ bool status = false;
+ bool wasSdCardOnline;
+
+ // Try to gain access the SD card
+ wasSdCardOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ while (online.microSD == true)
+ {
+ // Attempt to access file system. This avoids collisions with file writing from other functions like
+ // recordSystemSettingsToFile() and F9PSerialReadTask()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_LOADSETTINGS);
+
+ gotSemaphore = true;
+
+ if (USE_SPI_MICROSD)
+ {
+ if (!sd->exists(fileName))
+ {
+ log_d("File %s not found", fileName);
+ break;
+ }
+
+ SdFile settingsFile; // FAT32
+ if (settingsFile.open(fileName, O_READ) == false)
+ {
+ systemPrintln("Failed to open settings file");
+ break;
+ }
+
+ char line[100];
+ int lineNumber = 0;
+
+ while (settingsFile.available())
+ {
+ // Get the next line from the file
+ int n = settingsFile.fgets(line, sizeof(line));
+ if (n <= 0)
+ {
+ systemPrintf("Failed to read line %d from settings file\r\n", lineNumber);
+ }
+ else if (line[n - 1] != '\n' && n == (sizeof(line) - 1))
+ {
+ systemPrintf("Settings line %d too long\r\n", lineNumber);
+ if (lineNumber == 0)
+ {
+ // If we can't read the first line of the settings file, give up
+ systemPrintln("Giving up on settings file");
+ break;
+ }
+ }
+ else if (parseLine(line, settings) == false)
+ {
+ systemPrintf("Failed to parse line %d: %s\r\n", lineNumber, line);
+ if (lineNumber == 0)
+ {
+ // If we can't read the first line of the settings file, give up
+ systemPrintln("Giving up on settings file");
+ break;
+ }
+ }
+
+ lineNumber++;
+ }
+
+ // systemPrintln("Config file read complete");
+ settingsFile.close();
+ status = true;
+ break;
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (!SD_MMC.exists(fileName))
+ {
+ log_d("File %s not found", fileName);
+ break;
+ }
+
+ File settingsFile = SD_MMC.open(fileName, FILE_READ);
+
+ if (!settingsFile)
+ {
+ systemPrintln("Failed to open settings file");
+ break;
+ }
+
+ char line[100];
+ int lineNumber = 0;
+
+ while (settingsFile.available())
+ {
+ // Get the next line from the file
+ int n = getLine(&settingsFile, line, sizeof(line));
+ if (n <= 0)
+ {
+ systemPrintf("Failed to read line %d from settings file\r\n", lineNumber);
+ }
+ else if (line[n - 1] != '\n' && n == (sizeof(line) - 1))
+ {
+ systemPrintf("Settings line %d too long\r\n", lineNumber);
+ if (lineNumber == 0)
+ {
+ // If we can't read the first line of the settings file, give up
+ systemPrintln("Giving up on settings file");
+ break;
+ }
+ }
+ else if (parseLine(line, settings) == false)
+ {
+ systemPrintf("Failed to parse line %d: %s\r\n", lineNumber, line);
+ if (lineNumber == 0)
+ {
+ // If we can't read the first line of the settings file, give up
+ systemPrintln("Giving up on settings file");
+ break;
+ }
+ }
+
+ lineNumber++;
+ }
+
+ // systemPrintln("Config file read complete");
+ settingsFile.close();
+ status = true;
+ break;
+ }
+#endif // COMPILE_SD_MMC
+ } // End Semaphore check
+ else
+ {
+ // This is an error because if the settings exist on the microSD card that
+ // those settings are not overriding the current settings as documented!
+ systemPrintf("sdCardSemaphore failed to yield, NVM.ino line %d\r\n", __LINE__);
+ }
+ break;
+ } // End SD online
+
+ if (online.microSD != true)
+ log_d("Config file read failed: SD offline");
+
+ // Release access the SD card
+ if (online.microSD && (!wasSdCardOnline))
+ endSD(gotSemaphore, true);
+ else if (gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
+
+ return status;
+}
+
+// Given a fileName, parse the file and load the given settings struct
+// Returns true if some settings were loaded from a file
+// Returns false if a file was not opened/loaded
+bool loadSystemSettingsFromFileLFS(char *fileName, Settings *settings)
+{
+ // log_d("reading setting fileName: %s", fileName);
+
+ File settingsFile = LittleFS.open(fileName, FILE_READ);
+ if (!settingsFile)
+ {
+ // log_d("settingsFile not found in LittleFS\r\n");
+ return (false);
+ }
+
+ char line[100];
+ int lineNumber = 0;
+
+ while (settingsFile.available())
+ {
+ // Get the next line from the file
+ int n;
+ n = getLine(&settingsFile, line, sizeof(line));
+
+ if (n <= 0)
+ {
+ systemPrintf("Failed to read line %d from settings file\r\n", lineNumber);
+ }
+ else if (line[n - 1] != '\n' && n == (sizeof(line) - 1))
+ {
+ systemPrintf("Settings line %d too long\r\n", lineNumber);
if (lineNumber == 0)
{
- //If we can't read the first line of the settings file, give up
- Serial.println(F("Giving up on settings file"));
- xSemaphoreGive(xFATSemaphore);
- return (false);
+ // If we can't read the first line of the settings file, give up
+ systemPrintln("Giving up on settings file");
+ return (false);
}
- }
- else if (parseLine(line) == false) {
- Serial.printf("Failed to parse line %d: %s\r\n", lineNumber, line);
+ }
+ else if (parseLine(line, settings) == false)
+ {
+ systemPrintf("Failed to parse line %d: %s\r\n", lineNumber, line);
if (lineNumber == 0)
{
- //If we can't read the first line of the settings file, give up
- Serial.println(F("Giving up on settings file"));
- xSemaphoreGive(xFATSemaphore);
- return (false);
+ // If we can't read the first line of the settings file, give up
+ systemPrintln("Giving up on settings file");
+ return (false);
}
- }
+ }
- lineNumber++;
+ lineNumber++;
+ if (lineNumber > 400) // Arbitrary limit. Catch corrupt files.
+ {
+ log_d("Giving up reading file: %s", fileName);
+ break;
}
+ }
- //Serial.println(F("Config file read complete"));
- settingsFile.close();
- xSemaphoreGive(xFATSemaphore);
- return (true);
- }
- else
- {
- Serial.println(F("No config file found. Using settings from EEPROM."));
- //The defaults of the struct will be recorded to a file later on.
- xSemaphoreGive(xFATSemaphore);
- return (false);
- }
+ settingsFile.close();
+ return (true);
+}
+
+// Convert a given line from file into a settingName and value
+// Sets the setting if the name is known
+bool parseLine(char *str, Settings *settings)
+{
+ char *ptr;
- } //End Semaphore check
- } //End SD online
+ // Set strtok start of line.
+ str = strtok(str, "=");
+ if (!str)
+ {
+ log_d("Fail");
+ return false;
+ }
+
+ // Store this setting name
+ char settingName[100];
+ snprintf(settingName, sizeof(settingName), "%s", str);
+
+ double d = 0.0;
+ char settingString[100] = "";
+
+ // Move pointer to end of line
+ str = strtok(nullptr, "\n");
+ if (!str)
+ {
+ // This line does not contain a \n or the settingString is zero length
+ // so there is nothing to parse
+ // https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/77
+ }
+ else
+ {
+ // if (strcmp(settingName, "ntripServer_CasterHost") == 0) //Debug
+ // if (strcmp(settingName, "profileName") == 0) //Debug
+ // systemPrintf("Found problem spot raw: %s\r\n", str);
+
+ // Assume the value is a string such as 8d8a48b. The leading number causes skipSpace to fail.
+ // If settingString has a mix of letters and numbers, just convert to string
+ snprintf(settingString, sizeof(settingString), "%s", str);
+
+ // Check if string is mixed: 8a011EF, 192.168.1.1, -102.4, t6-h4$, etc.
+ bool hasSymbol = false;
+ int decimalCount = 0;
+ for (int x = 0; x < strlen(settingString); x++)
+ {
+ if (settingString[x] == '.')
+ decimalCount++;
+ else if (x == 0 && settingString[x] == '-')
+ {
+ ; // Do nothing
+ }
+ else if (isAlpha(settingString[x]))
+ hasSymbol = true;
+ else if (isDigit(settingString[x]) == false)
+ hasSymbol = true;
+ }
+
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/274
+ if (hasSymbol || decimalCount > 1)
+ {
+ // It's a mix. Skip strtod.
+
+ // if (strcmp(settingName, "ntripServer_CasterHost") == 0) //Debug
+ // systemPrintf("Skipping strtod - settingString: %s\r\n", settingString);
+ }
+ else
+ {
+ // Attempt to convert string to double
+ d = strtod(str, &ptr);
+
+ if (d == 0.0) // strtod failed, may be string or may be 0 but let it pass
+ {
+ snprintf(settingString, sizeof(settingString), "%s", str);
+ }
+ else
+ {
+ if (str == ptr || *skipSpace(ptr))
+ return false; // Check str pointer
+ }
+ }
+ }
+
+ // log_d("settingName: %s - value: %s - d: %0.9f", settingName, settingString, d);
+
+ // Get setting name
+ if (strcmp(settingName, "sizeOfSettings") == 0)
+ {
+ // We may want to cause a factory reset from the settings file rather than the menu
+ // If user sets sizeOfSettings to -1 in config file, RTK Surveyor will factory reset
+ if (d == -1)
+ {
+ // Erase file system, erase settings file, reset u-blox module, display message on OLED
+ factoryReset(true); // We already have the SD semaphore
+ }
+
+ // Check to see if this setting file is compatible with this version of RTK Surveyor
+ if (d != sizeof(Settings))
+ log_d("Settings size is %d but current firmware expects %d. Attempting to use settings from file.", (int)d,
+ sizeof(Settings));
+ }
+
+ else if (strcmp(settingName, "rtkIdentifier") == 0)
+ {
+ } // Do nothing. Just read it to avoid 'Unknown setting' error
+ else if (strcmp(settingName, "rtkFirmwareVersion") == 0)
+ {
+ } // Do nothing. Just read it to avoid 'Unknown setting' error
+ else if (strcmp(settingName, "zedFirmwareVersion") == 0)
+ {
+ } // Do nothing. Just read it to avoid 'Unknown setting' error
+ else if (strcmp(settingName, "zedUniqueId") == 0)
+ {
+ } // Do nothing. Just read it to avoid 'Unknown setting' error
+ else if (strcmp(settingName, "neoFirmwareVersion") == 0)
+ {
+ } // Do nothing. Just read it to avoid 'Unknown setting' error
- Serial.println(F("Config file read failed: SD offline"));
- return (false); //SD offline
+ else if (strcmp(settingName, "printDebugMessages") == 0)
+ settings->printDebugMessages = d;
+ else if (strcmp(settingName, "enableSD") == 0)
+ settings->enableSD = d;
+ else if (strcmp(settingName, "enableDisplay") == 0)
+ settings->enableDisplay = d;
+ else if (strcmp(settingName, "maxLogTime_minutes") == 0)
+ settings->maxLogTime_minutes = d;
+ else if (strcmp(settingName, "maxLogLength_minutes") == 0)
+ settings->maxLogLength_minutes = d;
+ else if (strcmp(settingName, "observationSeconds") == 0)
+ {
+ if (settings->observationSeconds !=
+ d) // If a setting for the ZED has changed, apply, and trigger module config update
+ {
+ settings->observationSeconds = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "observationPositionAccuracy") == 0)
+ {
+ if (settings->observationPositionAccuracy != d)
+ {
+ settings->observationPositionAccuracy = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedBase") == 0)
+ {
+ if (settings->fixedBase != d)
+ {
+ settings->fixedBase = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedBaseCoordinateType") == 0)
+ {
+ if (settings->fixedBaseCoordinateType != d)
+ {
+ settings->fixedBaseCoordinateType = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedEcefX") == 0)
+ {
+ if (settings->fixedEcefX != d)
+ {
+ settings->fixedEcefX = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedEcefY") == 0)
+ {
+ if (settings->fixedEcefY != d)
+ {
+ settings->fixedEcefY = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedEcefZ") == 0)
+ {
+ if (settings->fixedEcefZ != d)
+ {
+ settings->fixedEcefZ = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedLat") == 0)
+ {
+ if (settings->fixedLat != d)
+ {
+ settings->fixedLat = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedLong") == 0)
+ {
+ if (settings->fixedLong != d)
+ {
+ settings->fixedLong = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "fixedAltitude") == 0)
+ {
+ if (settings->fixedAltitude != d)
+ {
+ settings->fixedAltitude = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "dataPortBaud") == 0)
+ {
+ if (settings->dataPortBaud != d)
+ {
+ settings->dataPortBaud = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "radioPortBaud") == 0)
+ {
+ if (settings->radioPortBaud != d)
+ {
+ settings->radioPortBaud = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "surveyInStartingAccuracy") == 0)
+ settings->surveyInStartingAccuracy = d;
+ else if (strcmp(settingName, "measurementRate") == 0)
+ {
+ if (settings->measurementRate != d)
+ {
+ settings->measurementRate = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "navigationRate") == 0)
+ {
+ if (settings->navigationRate != d)
+ {
+ settings->navigationRate = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "enableI2Cdebug") == 0)
+ settings->enableI2Cdebug = d;
+ else if (strcmp(settingName, "enableHeapReport") == 0)
+ settings->enableHeapReport = d;
+ else if (strcmp(settingName, "enableTaskReports") == 0)
+ settings->enableTaskReports = d;
+ else if (strcmp(settingName, "dataPortChannel") == 0)
+ settings->dataPortChannel = (muxConnectionType_e)d;
+ else if (strcmp(settingName, "spiFrequency") == 0)
+ settings->spiFrequency = d;
+ else if (strcmp(settingName, "enableLogging") == 0)
+ settings->enableLogging = d;
+ else if (strcmp(settingName, "enableARPLogging") == 0)
+ settings->enableARPLogging = d;
+ else if (strcmp(settingName, "ARPLoggingInterval_s") == 0)
+ settings->ARPLoggingInterval_s = d;
+ else if (strcmp(settingName, "enableMarksFile") == 0)
+ settings->enableMarksFile = d;
+ else if (strcmp(settingName, "enableNTPFile") == 0)
+ settings->enableNTPFile = d;
+ else if (strcmp(settingName, "enableUART2UBXIn") == 0)
+ settings->enableUART2UBXIn = d;
+ else if (strcmp(settingName, "sppRxQueueSize") == 0)
+ settings->sppRxQueueSize = d;
+ else if (strcmp(settingName, "sppTxQueueSize") == 0)
+ settings->sppTxQueueSize = d;
+ else if (strcmp(settingName, "dynamicModel") == 0)
+ {
+ if (settings->dynamicModel != d)
+ {
+ settings->dynamicModel = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "lastState") == 0)
+ {
+ if (settings->lastState != (SystemState)d)
+ {
+ settings->lastState = (SystemState)d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "enableSensorFusion") == 0)
+ {
+ if (settings->enableSensorFusion != d)
+ {
+ settings->enableSensorFusion = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "autoIMUmountAlignment") == 0)
+ {
+ if (settings->autoIMUmountAlignment != d)
+ {
+ settings->autoIMUmountAlignment = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "enableResetDisplay") == 0)
+ settings->enableResetDisplay = d;
+ else if (strcmp(settingName, "enableExternalPulse") == 0)
+ {
+ if (settings->enableExternalPulse != d)
+ {
+ settings->enableExternalPulse = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "externalPulseTimeBetweenPulse_us") == 0)
+ {
+ if (settings->externalPulseTimeBetweenPulse_us != d)
+ {
+ settings->externalPulseTimeBetweenPulse_us = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "externalPulseLength_us") == 0)
+ {
+ if (settings->externalPulseLength_us != d)
+ {
+ settings->externalPulseLength_us = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "externalPulsePolarity") == 0)
+ {
+ if (settings->externalPulsePolarity != (pulseEdgeType_e)d)
+ {
+ settings->externalPulsePolarity = (pulseEdgeType_e)d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "enableExternalHardwareEventLogging") == 0)
+ {
+ if (settings->enableExternalHardwareEventLogging != d)
+ {
+ settings->enableExternalHardwareEventLogging = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "profileName") == 0)
+ strcpy(settings->profileName, settingString);
+ else if (strcmp(settingName, "enableNtripServer") == 0)
+ settings->enableNtripServer = d;
+ else if (strcmp(settingName, "ntripServer_StartAtSurveyIn") == 0)
+ settings->ntripServer_StartAtSurveyIn = d;
+ else if (strcmp(settingName, "enableNtripClient") == 0)
+ settings->enableNtripClient = d;
+ else if (strcmp(settingName, "ntripClient_CasterHost") == 0)
+ strcpy(settings->ntripClient_CasterHost, settingString);
+ else if (strcmp(settingName, "ntripClient_CasterPort") == 0)
+ settings->ntripClient_CasterPort = d;
+ else if (strcmp(settingName, "ntripClient_CasterUser") == 0)
+ strcpy(settings->ntripClient_CasterUser, settingString);
+ else if (strcmp(settingName, "ntripClient_CasterUserPW") == 0)
+ strcpy(settings->ntripClient_CasterUserPW, settingString);
+ else if (strcmp(settingName, "ntripClient_MountPoint") == 0)
+ strcpy(settings->ntripClient_MountPoint, settingString);
+ else if (strcmp(settingName, "ntripClient_MountPointPW") == 0)
+ strcpy(settings->ntripClient_MountPointPW, settingString);
+ else if (strcmp(settingName, "ntripClient_TransmitGGA") == 0)
+ settings->ntripClient_TransmitGGA = d;
+ else if (strcmp(settingName, "serialTimeoutGNSS") == 0)
+ settings->serialTimeoutGNSS = d;
+
+ // Point Perfect
+ else if (strcmp(settingName, "pointPerfectDeviceProfileToken") == 0)
+ strcpy(settings->pointPerfectDeviceProfileToken, settingString);
+ else if (strcmp(settingName, "enablePointPerfectCorrections") == 0)
+ settings->enablePointPerfectCorrections = d;
+ else if (strcmp(settingName, "autoKeyRenewal") == 0)
+ settings->autoKeyRenewal = d;
+ else if (strcmp(settingName, "pointPerfectClientID") == 0)
+ strcpy(settings->pointPerfectClientID, settingString);
+ else if (strcmp(settingName, "pointPerfectBrokerHost") == 0)
+ strcpy(settings->pointPerfectBrokerHost, settingString);
+ else if (strcmp(settingName, "pointPerfectLBandTopic") == 0)
+ strcpy(settings->pointPerfectLBandTopic, settingString);
+
+ else if (strcmp(settingName, "pointPerfectCurrentKey") == 0)
+ strcpy(settings->pointPerfectCurrentKey, settingString);
+ else if (strcmp(settingName, "pointPerfectCurrentKeyDuration") == 0)
+ settings->pointPerfectCurrentKeyDuration = d;
+ else if (strcmp(settingName, "pointPerfectCurrentKeyStart") == 0)
+ settings->pointPerfectCurrentKeyStart = d;
+
+ else if (strcmp(settingName, "pointPerfectNextKey") == 0)
+ strcpy(settings->pointPerfectNextKey, settingString);
+ else if (strcmp(settingName, "pointPerfectNextKeyDuration") == 0)
+ settings->pointPerfectNextKeyDuration = d;
+ else if (strcmp(settingName, "pointPerfectNextKeyStart") == 0)
+ settings->pointPerfectNextKeyStart = d;
+
+ else if (strcmp(settingName, "lastKeyAttempt") == 0)
+ settings->lastKeyAttempt = d;
+ else if (strcmp(settingName, "debugPpCertificate") == 0)
+ settings->debugPpCertificate = d;
+
+ else if (strcmp(settingName, "updateZEDSettings") == 0)
+ {
+ if (settings->updateZEDSettings != d)
+ settings->updateZEDSettings = true; // If there is a discrepancy, push ZED reconfig
+ }
+ else if (strcmp(settingName, "timeZoneHours") == 0)
+ settings->timeZoneHours = d;
+ else if (strcmp(settingName, "timeZoneMinutes") == 0)
+ settings->timeZoneMinutes = d;
+ else if (strcmp(settingName, "timeZoneSeconds") == 0)
+ settings->timeZoneSeconds = d;
+ else if (strcmp(settingName, "enablePrintState") == 0)
+ settings->enablePrintState = d;
+ else if (strcmp(settingName, "debugWifiState") == 0)
+ settings->debugWifiState = d;
+ else if (strcmp(settingName, "debugNtripClientState") == 0)
+ settings->debugNtripClientState = d;
+ else if (strcmp(settingName, "debugNtripServerState") == 0)
+ settings->debugNtripServerState = d;
+ else if (strcmp(settingName, "enablePrintPosition") == 0)
+ settings->enablePrintPosition = d;
+ else if (strcmp(settingName, "enablePrintIdleTime") == 0)
+ settings->enablePrintIdleTime = d;
+ else if (strcmp(settingName, "enablePrintBatteryMessages") == 0)
+ settings->enablePrintBatteryMessages = d;
+ else if (strcmp(settingName, "enablePrintRoverAccuracy") == 0)
+ settings->enablePrintRoverAccuracy = d;
+ else if (strcmp(settingName, "enablePrintBadMessages") == 0)
+ settings->enablePrintBadMessages = d;
+ else if (strcmp(settingName, "enablePrintLogFileMessages") == 0)
+ settings->enablePrintLogFileMessages = d;
+ else if (strcmp(settingName, "enablePrintLogFileStatus") == 0)
+ settings->enablePrintLogFileStatus = d;
+ else if (strcmp(settingName, "enablePrintRingBufferOffsets") == 0)
+ settings->enablePrintRingBufferOffsets = d;
+ else if (strcmp(settingName, "debugNtripServerRtcm") == 0)
+ settings->debugNtripServerRtcm = d;
+ else if (strcmp(settingName, "debugNtripClientRtcm") == 0)
+ settings->debugNtripClientRtcm = d;
+ else if (strcmp(settingName, "enablePrintStates") == 0)
+ settings->enablePrintStates = d;
+ else if (strcmp(settingName, "enablePrintDuplicateStates") == 0)
+ settings->enablePrintDuplicateStates = d;
+ else if (strcmp(settingName, "enablePrintRtcSync") == 0)
+ settings->enablePrintRtcSync = d;
+ else if (strcmp(settingName, "debugNtp") == 0)
+ settings->debugNtp = d;
+ else if (strcmp(settingName, "enablePrintEthernetDiag") == 0)
+ settings->enablePrintEthernetDiag = d;
+ else if (strcmp(settingName, "radioType") == 0)
+ settings->radioType = (RadioType_e)d;
+ else if (strcmp(settingName, "espnowPeerCount") == 0)
+ settings->espnowPeerCount = d;
+ else if (strcmp(settingName, "enableRtcmMessageChecking") == 0)
+ settings->enableRtcmMessageChecking = d;
+ else if (strcmp(settingName, "radioType") == 0)
+ settings->radioType = (RadioType_e)d;
+ else if (strcmp(settingName, "bluetoothRadioType") == 0)
+ settings->bluetoothRadioType = (BluetoothRadioType_e)d;
+ else if (strcmp(settingName, "enablePvtClient") == 0)
+ settings->enablePvtClient = d;
+ else if (strcmp(settingName, "enablePvtServer") == 0)
+ settings->enablePvtServer = d;
+ else if (strcmp(settingName, "enablePvtUdpServer") == 0)
+ settings->enablePvtUdpServer = d;
+ else if (strcmp(settingName, "debugPvtClient") == 0)
+ settings->debugPvtClient = d;
+ else if (strcmp(settingName, "debugPvtServer") == 0)
+ settings->debugPvtServer = d;
+ else if (strcmp(settingName, "debugPvtUdpServer") == 0)
+ settings->debugPvtUdpServer = d;
+ else if (strcmp(settingName, "espnowBroadcast") == 0)
+ settings->espnowBroadcast = d;
+ else if (strcmp(settingName, "antennaHeight") == 0)
+ settings->antennaHeight = d;
+ else if (strcmp(settingName, "antennaReferencePoint") == 0)
+ settings->antennaReferencePoint = d;
+ else if (strcmp(settingName, "echoUserInput") == 0)
+ settings->echoUserInput = d;
+ else if (strcmp(settingName, "uartReceiveBufferSize") == 0)
+ settings->uartReceiveBufferSize = d;
+ else if (strcmp(settingName, "gnssHandlerBufferSize") == 0)
+ settings->gnssHandlerBufferSize = d;
+ else if (strcmp(settingName, "enablePrintBufferOverrun") == 0)
+ settings->enablePrintBufferOverrun = d;
+ else if (strcmp(settingName, "enablePrintSDBuffers") == 0)
+ settings->enablePrintSDBuffers = d;
+ else if (strcmp(settingName, "periodicDisplay") == 0)
+ settings->periodicDisplay = d;
+ else if (strcmp(settingName, "periodicDisplayInterval") == 0)
+ settings->periodicDisplayInterval = d;
+ else if (strcmp(settingName, "rebootSeconds") == 0)
+ settings->rebootSeconds = d;
+ else if (strcmp(settingName, "forceResetOnSDFail") == 0)
+ settings->forceResetOnSDFail = d;
+ else if (strcmp(settingName, "wifiConfigOverAP") == 0)
+ settings->wifiConfigOverAP = d;
+ else if (strcmp(settingName, "pvtServerPort") == 0)
+ settings->pvtServerPort = d;
+ else if (strcmp(settingName, "pvtUdpServerPort") == 0)
+ settings->pvtUdpServerPort = d;
+ else if (strcmp(settingName, "minElev") == 0)
+ {
+ if (settings->minElev != d)
+ {
+ settings->minElev = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "imuYaw") == 0)
+ {
+ if (settings->imuYaw != d)
+ {
+ settings->imuYaw = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "imuPitch") == 0)
+ {
+ if (settings->imuPitch != d)
+ {
+ settings->imuPitch = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "imuRoll") == 0)
+ {
+ if (settings->imuRoll != d)
+ {
+ settings->imuRoll = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "sfDisableWheelDirection") == 0)
+ {
+ if (settings->sfDisableWheelDirection != d)
+ {
+ settings->sfDisableWheelDirection = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "sfCombineWheelTicks") == 0)
+ {
+ if (settings->sfCombineWheelTicks != d)
+ {
+ settings->sfCombineWheelTicks = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "rateNavPrio") == 0)
+ {
+ if (settings->rateNavPrio != d)
+ {
+ settings->rateNavPrio = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "sfUseSpeed") == 0)
+ {
+ if (settings->sfUseSpeed != d)
+ {
+ settings->sfUseSpeed = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ // Ethernet
+ else if (strcmp(settingName, "ethernetIP") == 0)
+ {
+ String addr = String(settingString);
+ settings->ethernetIP.fromString(addr);
+ }
+ else if (strcmp(settingName, "ethernetDNS") == 0)
+ {
+ String addr = String(settingString);
+ settings->ethernetDNS.fromString(addr);
+ }
+ else if (strcmp(settingName, "ethernetGateway") == 0)
+ {
+ String addr = String(settingString);
+ settings->ethernetGateway.fromString(addr);
+ }
+ else if (strcmp(settingName, "ethernetSubnet") == 0)
+ {
+ String addr = String(settingString);
+ settings->ethernetSubnet.fromString(addr);
+ }
+ else if (strcmp(settingName, "httpPort") == 0)
+ settings->httpPort = d;
+ else if (strcmp(settingName, "ethernetNtpPort") == 0)
+ settings->ethernetNtpPort = d;
+ else if (strcmp(settingName, "ethernetDHCP") == 0)
+ settings->ethernetDHCP = d;
+ else if (strcmp(settingName, "pvtClientPort") == 0)
+ settings->pvtClientPort = d;
+ else if (strcmp(settingName, "pvtClientHost") == 0)
+ strcpy(settings->pvtClientHost, settingString);
+ // NTP
+ else if (strcmp(settingName, "ntpPollExponent") == 0)
+ settings->ntpPollExponent = d;
+ else if (strcmp(settingName, "ntpPrecision") == 0)
+ settings->ntpPrecision = d;
+ else if (strcmp(settingName, "ntpRootDelay") == 0)
+ settings->ntpRootDelay = d;
+ else if (strcmp(settingName, "ntpRootDispersion") == 0)
+ settings->ntpRootDispersion = d;
+ else if (strcmp(settingName, "ntpReferenceId") == 0)
+ {
+ strcpy(settings->ntpReferenceId, settingString);
+ for (int i = strlen(settingString); i < 5; i++)
+ settings->ntpReferenceId[i] = 0;
+ }
+ else if (strcmp(settingName, "coordinateInputType") == 0)
+ settings->coordinateInputType = (CoordinateInputType)d;
+ else if (strcmp(settingName, "lbandFixTimeout_seconds") == 0)
+ settings->lbandFixTimeout_seconds = d;
+ else if (strcmp(settingName, "minCNO_F9R") == 0)
+ {
+ if (settings->minCNO_F9R != d)
+ {
+ settings->minCNO_F9R = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "minCNO_F9P") == 0)
+ {
+ if (settings->minCNO_F9P != d)
+ {
+ settings->minCNO_F9P = d;
+ settings->updateZEDSettings = true;
+ }
+ }
+ else if (strcmp(settingName, "mdnsEnable") == 0)
+ settings->mdnsEnable = d;
+ else if (strcmp(settingName, "serialGNSSRxFullThreshold") == 0)
+ settings->serialGNSSRxFullThreshold = d;
+ else if (strcmp(settingName, "btReadTaskPriority") == 0)
+ settings->btReadTaskPriority = d;
+ else if (strcmp(settingName, "gnssReadTaskPriority") == 0)
+ settings->gnssReadTaskPriority = d;
+ else if (strcmp(settingName, "handleGnssDataTaskPriority") == 0)
+ settings->handleGnssDataTaskPriority = d;
+ else if (strcmp(settingName, "btReadTaskCore") == 0)
+ settings->btReadTaskCore = d;
+ else if (strcmp(settingName, "gnssReadTaskCore") == 0)
+ settings->gnssReadTaskCore = d;
+ else if (strcmp(settingName, "handleGnssDataTaskCore") == 0)
+ settings->handleGnssDataTaskCore = d;
+ else if (strcmp(settingName, "gnssUartInterruptsCore") == 0)
+ settings->gnssUartInterruptsCore = d;
+ else if (strcmp(settingName, "bluetoothInterruptsCore") == 0)
+ settings->bluetoothInterruptsCore = d;
+ else if (strcmp(settingName, "i2cInterruptsCore") == 0)
+ settings->i2cInterruptsCore = d;
+ else if (strcmp(settingName, "shutdownNoChargeTimeout_s") == 0)
+ settings->shutdownNoChargeTimeout_s = d;
+ else if (strcmp(settingName, "disableSetupButton") == 0)
+ settings->disableSetupButton = d;
+ else if (strcmp(settingName, "useI2cForLbandCorrections") == 0)
+ settings->useI2cForLbandCorrections = d;
+ else if (strcmp(settingName, "useI2cForLbandCorrectionsConfigured") == 0)
+ settings->useI2cForLbandCorrectionsConfigured = d;
+
+ // Network layer
+ else if (strcmp(settingName, "defaultNetworkType") == 0)
+ settings->defaultNetworkType = d;
+ else if (strcmp(settingName, "debugNetworkLayer") == 0)
+ settings->debugNetworkLayer = d;
+ else if (strcmp(settingName, "enableNetworkFailover") == 0)
+ settings->enableNetworkFailover = d;
+ else if (strcmp(settingName, "printNetworkStatus") == 0)
+ settings->printNetworkStatus = d;
+ else if (strcmp(settingName, "rtcmTimeoutBeforeUsingLBand_s") == 0)
+ settings->rtcmTimeoutBeforeUsingLBand_s = d;
+
+ // Automatic Firmware Update
+ else if (strcmp(settingName, "autoFirmwareCheckMinutes") == 0)
+ settings->autoFirmwareCheckMinutes = d;
+ else if (strcmp(settingName, "debugFirmwareUpdate") == 0)
+ settings->debugFirmwareUpdate = d;
+ else if (strcmp(settingName, "enableAutoFirmwareUpdate") == 0)
+ settings->enableAutoFirmwareUpdate = d;
+
+ else if (strcmp(settingName, "debugLBand") == 0)
+ settings->debugLBand = d;
+ else if (strcmp(settingName, "enableCaptivePortal") == 0)
+ settings->enableCaptivePortal = d;
+ else if (strcmp(settingName, "enableZedUsb") == 0)
+ settings->enableZedUsb = d;
+ else if (strcmp(settingName, "debugWiFiConfig") == 0)
+ settings->debugWiFiConfig = d;
+
+ else if (strcmp(settingName, "geographicRegion") == 0)
+ settings->geographicRegion = d;
+
+ // Add new settings above
+ //<------------------------------------------------------------>
+
+ // Check for bulk settings (WiFi credentials, constellations, message rates, ESPNOW Peers)
+ // Must be last on else list
+ else
+ {
+ bool knownSetting = false;
+
+ // Scan for WiFi settings
+ if (knownSetting == false)
+ {
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ char tempString[100]; // wifiNetwork0Password=parachutes
+ snprintf(tempString, sizeof(tempString), "wifiNetwork%dSSID", x);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(settings->wifiNetworks[x].ssid, settingString);
+ knownSetting = true;
+ break;
+ }
+ else
+ {
+ snprintf(tempString, sizeof(tempString), "wifiNetwork%dPassword", x);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(settings->wifiNetworks[x].password, settingString);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Scan for constellation settings
+ if (knownSetting == false)
+ {
+ for (int x = 0; x < MAX_CONSTELLATIONS; x++)
+ {
+ char tempString[50]; // constellation.GPS=1
+ snprintf(tempString, sizeof(tempString), "constellation.%s", settings->ubxConstellations[x].textName);
+
+ if (strcmp(settingName, tempString) == 0)
+ {
+ if (settings->ubxConstellations[x].enabled != d)
+ {
+ settings->ubxConstellations[x].enabled = d;
+ settings->updateZEDSettings = true;
+ }
+
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for message settings
+ if (knownSetting == false)
+ {
+ for (int x = 0; x < MAX_UBX_MSG; x++)
+ {
+ char tempString[50]; // message.nmea_dtm.msgRate=5
+ snprintf(tempString, sizeof(tempString), "message.%s.msgRate", ubxMessages[x].msgTextName);
+
+ if (strcmp(settingName, tempString) == 0)
+ {
+ if (settings->ubxMessageRates[x] != d)
+ {
+ settings->ubxMessageRates[x] = d;
+ settings->updateZEDSettings = true;
+ }
+
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for Base RTCM message settings
+ if (knownSetting == false)
+ {
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ char tempString[50]; // messageBase.UBX_RTCM_1094.msgRate=5
+
+ snprintf(tempString, sizeof(tempString), "messageBase.%s.msgRate",
+ ubxMessages[firstRTCMRecord + x].msgTextName);
+
+ if (strcmp(settingName, tempString) == 0)
+ {
+ if (settings->ubxMessageRatesBase[x] != d)
+ {
+ settings->ubxMessageRatesBase[x] = d;
+ settings->updateZEDSettings = true;
+ }
+
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ESPNOW peers
+ if (knownSetting == false)
+ {
+ for (int x = 0; x < ESPNOW_MAX_PEERS; x++)
+ {
+ char tempString[50]; // espnowPeers.1=B4,C1,33,42,DE,01,
+ snprintf(tempString, sizeof(tempString), "espnowPeers.%d", x);
+
+ if (strcmp(settingName, tempString) == 0)
+ {
+ uint8_t macAddress[6];
+ uint8_t macByte = 0;
+
+ char *token = strtok(settingString, ","); // Break string up on ,
+ while (token != nullptr && macByte < sizeof(macAddress))
+ {
+ settings->espnowPeers[x][macByte++] = (uint8_t)strtol(token, nullptr, 16);
+ token = strtok(nullptr, ",");
+ }
+
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServer_CasterHost
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterHost_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings->ntripServer_CasterHost[serverIndex][0], settingString);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServer_CasterPort
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterPort_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ settings->ntripServer_CasterPort[serverIndex] = d;
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServer_CasterUser
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterUser_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings->ntripServer_CasterUser[serverIndex][0], settingString);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServer_CasterUserPW
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_CasterUserPW_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings->ntripServer_CasterUserPW[serverIndex][0], settingString);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServer_MountPoint
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_MountPoint_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings->ntripServer_MountPoint[serverIndex][0], settingString);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Scan for ntripServer_MountPointPW
+ if (knownSetting == false)
+ {
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ {
+ char tempString[50];
+ snprintf(tempString, sizeof(tempString), "ntripServer_MountPointPW_%d", serverIndex);
+ if (strcmp(settingName, tempString) == 0)
+ {
+ strcpy(&settings->ntripServer_MountPointPW[serverIndex][0], settingString);
+ knownSetting = true;
+ break;
+ }
+ }
+ }
+
+ // Last catch
+ if (knownSetting == false)
+ {
+ log_d("Unknown setting %s", settingName);
+ }
+ }
+
+ return (true);
+}
+
+// The SD library doesn't have a fgets function like SD fat so recreate it here
+// Read the current line in the file until we hit a EOL char \r or \n
+int getLine(File *openFile, char *lineChars, int lineSize)
+{
+ int count = 0;
+ while (openFile->available())
+ {
+ byte incoming = openFile->read();
+ if (incoming == '\r' || incoming == '\n')
+ {
+ // Sometimes a line has multiple terminators
+ while (openFile->peek() == '\r' || openFile->peek() == '\n')
+ openFile->read(); // Dump it to prevent next line read corruption
+ break;
+ }
+
+ lineChars[count++] = incoming;
+
+ if (count == lineSize - 1)
+ break; // Stop before overun of buffer
+ }
+ lineChars[count] = '\0'; // Terminate string
+ return (count);
}
// Check for extra characters in field or find minus sign.
-char* skipSpace(char* str) {
- while (isspace(*str)) str++;
- return str;
+char *skipSpace(char *str)
+{
+ while (isspace(*str))
+ str++;
+ return str;
+}
+
+// Load the special profileNumber file in LittleFS and return one byte value
+void loadProfileNumber()
+{
+ if (profileNumber < MAX_PROFILE_COUNT)
+ return; // Only load it once
+
+ File fileProfileNumber = LittleFS.open("/profileNumber.txt", FILE_READ);
+ if (!fileProfileNumber)
+ {
+ log_d("profileNumber.txt not found");
+ settings.updateZEDSettings = true; // Force module update
+ recordProfileNumber(0); // Record profile
+ }
+ else
+ {
+ profileNumber = fileProfileNumber.read();
+ fileProfileNumber.close();
+ }
+
+ // We have arbitrary limit of user profiles
+ if (profileNumber >= MAX_PROFILE_COUNT)
+ {
+ log_d("ProfileNumber invalid. Going to zero.");
+ settings.updateZEDSettings = true; // Force module update
+ recordProfileNumber(0); // Record profile
+ }
+
+ log_d("Using profile #%d", profileNumber);
}
-//Convert a given line from file into a settingName and value
-//Sets the setting if the name is known
-bool parseLine(char* str) {
- char* ptr;
-
- //Debug
- //Serial.printf("Line contents: %s", str);
- //Serial.flush();
-
- // Set strtok start of line.
- str = strtok(str, "=");
- if (!str) return false;
-
- //Store this setting name
- char settingName[40];
- sprintf(settingName, "%s", str);
-
- //Move pointer to end of line
- str = strtok(nullptr, "\n");
- if (!str) return false;
-
- //Serial.printf("s = %s\r\n", str);
- //Serial.flush();
-
- //Attempt to convert string to double.
- double d = strtod(str, &ptr);
-
- //Serial.printf("d = %lf\r\n", d);
- //Serial.flush();
-
- char settingValue[50];
- if (d == 0.0) //strtod failed, may be string or may be 0 but let it pass
- {
- sprintf(settingValue, "%s", str);
- }
- else
- {
- if (str == ptr || *skipSpace(ptr)) return false; //Check str pointer
-
- //See issue https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/47
- sprintf(settingValue, "%1.0lf", d); //Catch when the input is pure numbers (strtod was successful), store as settingValue
- }
-
- // Get setting name
- if (strcmp(settingName, "sizeOfSettings") == 0)
- {
- //We may want to cause a factory reset from the settings file rather than the menu
- //If user sets sizeOfSettings to -1 in config file, RTK Surveyor will factory reset
- if (d == -1)
- {
- eepromErase();
-
- //Assemble settings file name
- char settingsFileName[40]; //SFE_Surveyor_Settings.txt
- strcpy(settingsFileName, platformFilePrefix);
- strcat(settingsFileName, "_Settings.txt");
- sd.remove(settingsFileName);
-
- Serial.printf("RTK %s has been factory reset via settings file. Freezing. Please restart and open terminal at 115200bps.\n\r", platformPrefix);
-
- while (1)
- delay(1); //Prevent CPU freakout
- }
-
- //Check to see if this setting file is compatible with this version of RTK Surveyor
- if (d != sizeof(settings))
- Serial.printf("Warning: Settings size is %d but current firmware expects %d. Attempting to use settings from file.\r\n", (int)d, sizeof(settings));
-
- }
- else if (strcmp(settingName, "rtkIdentifier") == 0)
- settings.rtkIdentifier = d;
- else if (strcmp(settingName, "rtkFirmwareVersion") == 0)
- {} //Do nothing. Just read it to avoid 'Unknown setting' error
- else if (strcmp(settingName, "zedFirmwareVersion") == 0)
- {} //Do nothing. Just read it to avoid 'Unknown setting' error
- else if (strcmp(settingName, "printDebugMessages") == 0)
- settings.printDebugMessages = d;
- else if (strcmp(settingName, "enableSD") == 0)
- settings.enableSD = d;
- else if (strcmp(settingName, "enableDisplay") == 0)
- settings.enableDisplay = d;
- else if (strcmp(settingName, "maxLogTime_minutes") == 0)
- settings.maxLogTime_minutes = d;
- else if (strcmp(settingName, "observationSeconds") == 0)
- settings.observationSeconds = d;
- else if (strcmp(settingName, "observationPositionAccuracy") == 0)
- settings.observationPositionAccuracy = d;
- else if (strcmp(settingName, "fixedBase") == 0)
- settings.fixedBase = d;
- else if (strcmp(settingName, "fixedBaseCoordinateType") == 0)
- settings.fixedBaseCoordinateType = d;
- else if (strcmp(settingName, "fixedEcefX") == 0)
- settings.fixedEcefX = d;
- else if (strcmp(settingName, "fixedEcefY") == 0)
- settings.fixedEcefY = d;
- else if (strcmp(settingName, "fixedEcefZ") == 0)
- settings.fixedEcefZ = d;
- else if (strcmp(settingName, "fixedLat") == 0)
- settings.fixedLat = d;
- else if (strcmp(settingName, "fixedLong") == 0)
- settings.fixedLong = d;
- else if (strcmp(settingName, "fixedAltitude") == 0)
- settings.fixedAltitude = d;
- else if (strcmp(settingName, "dataPortBaud") == 0)
- settings.dataPortBaud = d;
- else if (strcmp(settingName, "radioPortBaud") == 0)
- settings.radioPortBaud = d;
- else if (strcmp(settingName, "enableNtripServer") == 0)
- settings.enableNtripServer = d;
- else if (strcmp(settingName, "casterHost") == 0)
- strcpy(settings.casterHost, settingValue);
- else if (strcmp(settingName, "casterPort") == 0)
- settings.casterPort = d;
- else if (strcmp(settingName, "mountPoint") == 0)
- strcpy(settings.mountPoint, settingValue);
- else if (strcmp(settingName, "mountPointPW") == 0)
- strcpy(settings.mountPointPW, settingValue);
- else if (strcmp(settingName, "wifiSSID") == 0)
- strcpy(settings.wifiSSID, settingValue);
- else if (strcmp(settingName, "wifiPW") == 0)
- strcpy(settings.wifiPW, settingValue);
- else if (strcmp(settingName, "surveyInStartingAccuracy") == 0)
- settings.surveyInStartingAccuracy = d;
- else if (strcmp(settingName, "measurementRate") == 0)
- settings.measurementRate = d;
- else if (strcmp(settingName, "navigationRate") == 0)
- settings.navigationRate = d;
- else if (strcmp(settingName, "enableI2Cdebug") == 0)
- settings.enableI2Cdebug = d;
- else if (strcmp(settingName, "enableHeapReport") == 0)
- settings.enableHeapReport = d;
- else if (strcmp(settingName, "enableTaskReports") == 0)
- settings.enableTaskReports = d;
- else if (strcmp(settingName, "dataPortChannel") == 0)
- settings.dataPortChannel = (muxConnectionType_e)d;
- else if (strcmp(settingName, "spiFrequency") == 0)
- settings.spiFrequency = d;
- else if (strcmp(settingName, "enableLogging") == 0)
- settings.enableLogging = d;
- else if (strcmp(settingName, "sppRxQueueSize") == 0)
- settings.sppRxQueueSize = d;
- else if (strcmp(settingName, "sppTxQueueSize") == 0)
- settings.sppTxQueueSize = d;
- else if (strcmp(settingName, "dynamicModel") == 0)
- settings.dynamicModel = d;
- else if (strcmp(settingName, "lastState") == 0)
- settings.lastState = (SystemState)d;
- else if (strcmp(settingName, "throttleDuringSPPCongestion") == 0)
- settings.throttleDuringSPPCongestion = d;
-
- //Check for bulk settings (constellations and message rates)
- //Must be last on else list
- else
- {
- bool knownSetting = false;
-
- //Scan for constellation settings
- if (knownSetting == false)
- {
- for (int x = 0 ; x < MAX_CONSTELLATIONS ; x++)
- {
- char tempString[50]; //constellation.GPS=1
- sprintf(tempString, "constellation.%s", ubxConstellations[x].textName);
-
- if (strcmp(settingName, tempString) == 0)
- {
- ubxConstellations[x].enabled = d;
- knownSetting = true;
- break;
- }
- }
- }
-
- //Scan for message settings
- if (knownSetting == false)
- {
- for (int x = 0 ; x < MAX_UBX_MSG ; x++)
- {
- char tempString[50]; //message.nmea_dtm.msgRate=5
- sprintf(tempString, "message.%s.msgRate", ubxMessages[x].msgTextName);
-
- if (strcmp(settingName, tempString) == 0)
- {
- ubxMessages[x].msgRate = d;
- knownSetting = true;
- break;
- }
- }
- }
-
- //Last catch
- if (knownSetting == false)
- {
- Serial.printf("Unknown setting %s on line: %s\r\n", settingName, str);
- }
- }
-
- return (true);
+// Record the given profile number as well as a config bool
+void recordProfileNumber(uint8_t newProfileNumber)
+{
+ profileNumber = newProfileNumber;
+ File fileProfileNumber = LittleFS.open("/profileNumber.txt", FILE_WRITE);
+ if (!fileProfileNumber)
+ {
+ log_d("profileNumber.txt failed to open");
+ return;
+ }
+ fileProfileNumber.write(newProfileNumber);
+ fileProfileNumber.close();
}
-//ESP32 doesn't have erase command so we do it here
-void eepromErase()
+// Populate profileNames[][] based on names found in LittleFS and SD
+// If both SD and LittleFS contain a profile, SD wins.
+uint8_t loadProfileNames()
{
- for (int i = 0 ; i < EEPROM_SIZE ; i++) {
- EEPROM.write(i, 0xFF); //Reset to all 1s
- }
- EEPROM.commit();
+ int profiles = 0;
+
+ for (int x = 0; x < MAX_PROFILE_COUNT; x++)
+ profileNames[x][0] = '\0'; // Ensure every profile name is terminated
+
+ // Check LittleFS and SD for profile names
+ for (int x = 0; x < MAX_PROFILE_COUNT; x++)
+ {
+ char fileName[60];
+ snprintf(fileName, sizeof(fileName), "/%s_Settings_%d.txt", platformFilePrefix, x);
+
+ if (getProfileName(fileName, profileNames[x], sizeof(profileNames[x])) == true)
+ // Mark this profile as active
+ profiles |= 1 << x;
+ }
+
+ return (profiles);
}
-//The SD library doesn't have a fgets function like SD fat so recreate it here
-//Read the current line in the file until we hit a EOL char \r or \n
-int getLine(File * openFile, char * lineChars, int lineSize)
+// Given a profile number, copy the current settings.profileName into the array of profile names
+void setProfileName(uint8_t ProfileNumber)
{
- int count = 0;
- while (openFile->available())
- {
- byte incoming = openFile->read();
- if (incoming == '\r' || incoming == '\n')
+ // Update the name in the array of profile names
+ strncpy(profileNames[profileNumber], settings.profileName, sizeof(profileNames[0]) - 1);
+
+ // Mark this profile as active
+ activeProfiles |= 1 << ProfileNumber;
+}
+
+// Open the clear text file, scan for 'profileName' and return the string
+// Returns true if successfully found tag in file, length may be zero
+// Looks at LittleFS first, then SD
+bool getProfileName(char *fileName, char *profileName, uint8_t profileNameLength)
+{
+ // Create a temporary settings struc on the heap (not the stack because it is ~4500 bytes)
+ Settings *tempSettings = new Settings;
+
+ // If we have a profile in both LFS and SD, SD wins
+ bool responseLFS = loadSystemSettingsFromFileLFS(fileName, tempSettings);
+ bool responseSD = loadSystemSettingsFromFileSD(fileName, tempSettings);
+
+ // Zero terminate the profile name
+ *profileName = 0;
+ if (responseLFS == true || responseSD == true)
+ snprintf(profileName, profileNameLength, "%s", tempSettings->profileName); // snprintf handles null terminator
+
+ delete tempSettings;
+
+ return (responseLFS | responseSD);
+}
+
+// Loads a given profile name.
+// Profiles may not be sequential (user might have empty profile #2, but filled #3) so we load the profile unit, not the
+// number Return true if successful
+bool getProfileNameFromUnit(uint8_t profileUnit, char *profileName, uint8_t profileNameLength)
+{
+ uint8_t located = 0;
+
+ // Step through possible profiles looking for the specified unit
+ for (int x = 0; x < MAX_PROFILE_COUNT; x++)
{
- //Sometimes a line has multiple terminators
- while (openFile->peek() == '\r' || openFile->peek() == '\n')
- openFile->read(); //Dump it to prevent next line read corruption
- break;
+ if (activeProfiles & (1 << x))
+ {
+ if (located == profileUnit)
+ {
+ snprintf(profileName, profileNameLength, "%s", profileNames[x]); // snprintf handles null terminator
+ return (true);
+ }
+
+ located++; // Valid settingFileName but not the unit we are looking for
+ }
+ }
+ log_d("Profile unit %d not found", profileUnit);
+
+ return (false);
+}
+
+// Return profile number based on units
+// Profiles may not be sequential (user might have empty profile #2, but filled #3) so we look up the profile unit and
+// return the count
+uint8_t getProfileNumberFromUnit(uint8_t profileUnit)
+{
+ uint8_t located = 0;
+
+ // Step through possible profiles looking for the 1st, 2nd, 3rd, or 4th unit
+ for (int x = 0; x < MAX_PROFILE_COUNT; x++)
+ {
+ if (activeProfiles & (1 << x))
+ {
+ if (located == profileUnit)
+ return (x);
+
+ located++; // Valid settingFileName but not the unit we are looking for
+ }
+ }
+ log_d("Profile unit %d not found", profileUnit);
+
+ return (0);
+}
+
+// Record large character blob to file
+void recordFile(const char *fileID, char *fileContents, uint32_t fileSize)
+{
+ char fileName[80];
+ snprintf(fileName, sizeof(fileName), "/%s_%s_%d.txt", platformFilePrefix, fileID, profileNumber);
+
+ if (LittleFS.exists(fileName))
+ {
+ LittleFS.remove(fileName);
+ log_d("Removing LittleFS: %s", fileName);
}
- lineChars[count++] = incoming;
+ File fileToWrite = LittleFS.open(fileName, FILE_WRITE);
+ if (!fileToWrite)
+ {
+ log_d("Failed to write to file %s", fileName);
+ }
+ else
+ {
+ fileToWrite.write((uint8_t *)fileContents, fileSize); // Store cert into file
+ fileToWrite.close();
+ log_d("File recorded to LittleFS: %s", fileName);
+ }
+}
+
+// Read file into given char array
+void loadFile(const char *fileID, char *fileContents)
+{
+ char fileName[80];
+ snprintf(fileName, sizeof(fileName), "/%s_%s_%d.txt", platformFilePrefix, fileID, profileNumber);
- if (count == lineSize - 1)
- break; //Stop before overun of buffer
- }
- lineChars[count] = '\0'; //Terminate string
- return (count);
+ File fileToRead = LittleFS.open(fileName, FILE_READ);
+ if (fileToRead)
+ {
+ fileToRead.read((uint8_t *)fileContents, fileToRead.size()); // Read contents into pointer
+ fileToRead.close();
+ log_d("File loaded from LittleFS: %s", fileName);
+ }
+ else
+ {
+ log_d("Failed to read from LittleFS: %s", fileName);
+ }
}
diff --git a/Firmware/RTK_Surveyor/Network.ino b/Firmware/RTK_Surveyor/Network.ino
new file mode 100644
index 000000000..5211b6919
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Network.ino
@@ -0,0 +1,1316 @@
+/*------------------------------------------------------------------------------
+Network.ino
+
+ This module implements the network layer. An overview of the network stack
+ is shown below:
+
+ Application Layer:
+
+ NTRIP Server NTRIP Client TCP client
+ ^ ^ ^
+ | | |
+ | | |
+ '-------+------+-------+-----'
+ ^ ^
+ | |
+ | V
+ | Service Layer: DHCP, DNS, SSL, HTTP, ...
+ | ^
+ | |
+ V V
+ Network (Client) Layer
+ ^
+ |
+ .------------+------------.
+ | |
+ V V
+ Ethernet WiFi
+
+ Network States:
+
+ .-------------------->NETWORK_STATE_OFF
+ | |
+ | |
+ | | restart
+ | | or
+ | | networkUserOpen()
+ | networkStop() | networkStart()
+ | |
+ | |
+ | |
+ | V
+ +<-------------------NETWORK_STATE_DELAY--------------------------------.
+ ^ | |
+ | | Delay complete |
+ | | |
+ | V V
+ +<----------------NETWORK_STATE_CONNECTING----------------------------->+
+ ^ | Network Failed |
+ | | Media connected |
+ | networkUserClose() | Retry |
+ | && V |
+ | activeUsers == 0 +<----------------. |
+ | | | networkUserClose() |
+ | V | && |
+ +<------------------NETWORK_STATE_IN_USE--------' activeUsers != 0 |
+ ^ | |
+ | | Network failed |
+ | | or |
+ | | networkStop() |
+ | V |
+ | +<----------------------------------------'
+ | |
+ | V
+ | +<----------------.
+ | | | networkUserClose()
+ | V | &&
+ '-------------------NETWORK_WAIT_NO_USERS-------' activeUsers != 0
+
+ Network testing on an RTK Reference Station using NTRIP client:
+
+ 1. Network retries using Ethernet, no WiFi setup:
+ * Remove Ethernet cable, expecting retry Ethernet after delay
+ * Progressive delay maxes out at 8 minutes
+ * After cable is plugged in NTRIP client restarts
+
+ 2. Network retries using WiFi, use an invalid SSID, default network is WiFi,
+ failover disabled:
+ * WiFi fails to connect, expecting retry WiFi after delay
+ * Progressive delay maxes out at 8 minutes
+ * After a valid SSID is set, the NTRIP client restarts
+
+ Network testing on Reference Station using NTRIP server:
+
+ 1. Network retries using Ethernet, no WiFi setup:
+ * Remove Ethernet cable, expecting retry Ethernet after delay
+ * Progressive delay maxes out at 8 minutes
+ * After cable is plugged in NTRIP server restarts
+
+ 2. Network retries using WiFi, use an invalid SSID, default network is WiFi,
+ failover disabled:
+ * WiFi fails to connect, expecting retry WiFi after delay
+ * Progressive delay maxes out at 8 minutes
+ * After cable is plugged in NTRIP server restarts
+
+ Network failover testing on Reference Station, WiFi setup, failover enabled:
+
+ 1. Using NTRIP client:
+ * Remove Ethernet cable, expecting failover to WiFi with no delay and
+ NTRIP client restarts
+ * Disable WiFi at access point, expecting failover to Ethernet with no
+ delay, NTRIP client restarts
+
+ 2. Using NTRIP server:
+ * Remove Ethernet cable, expecting failover to WiFi with no delay and
+ NTRIP server restarts
+ * Disable WiFi at access point, expecting failover to Ethernet with no
+ delay, NTRIP server restarts
+
+ Test Setup:
+
+ RTK Reference Station
+ ^ ^
+ WiFi | | Ethernet cable
+ v v
+ WiFi Access Point <-----------> Ethernet Switch
+ Ethernet ^
+ Cable | Ethernet cable
+ v
+ Internet Firewall
+ ^
+ | Ethernet cable
+ v
+ Modem
+ ^
+ |
+ v
+ Internet
+ ^
+ |
+ v
+ NTRIP Caster
+
+ Possible NTRIP Casters
+
+ * https://emlid.com/ntrip-caster/
+ * http://rtk2go.com/
+ * private SNIP NTRIP caster
+------------------------------------------------------------------------------*/
+
+#if COMPILE_NETWORK
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+#define NETWORK_CONNECTION_TIMEOUT (15 * 1000) // Timeout for network media connection
+#define NETWORK_DELAY_BEFORE_RETRY (75 * 100) // Delay between network connection retries
+#define NETWORK_IP_ADDRESS_DISPLAY (12 * 1000) // Delay in milliseconds between display of IP address
+#define NETWORK_MAX_IDLE_TIME 500 // Maximum network idle time before shutdown
+#define NETWORK_MAX_RETRIES 7 // 7.5, 15, 30, 60, 2m, 4m, 8m
+
+// Specify which network to use next when a network failure occurs
+const uint8_t networkFailover[] =
+{
+ NETWORK_TYPE_ETHERNET, // WiFi --> Ethernet
+ NETWORK_TYPE_WIFI, // Ethernet --> WiFi
+};
+const int networkFailoverEntries = sizeof(networkFailover) / sizeof(networkFailover[0]);
+
+// List of network names
+const char * const networkName[] =
+{
+ "WiFi", // NETWORK_TYPE_WIFI
+ "Ethernet", // NETWORK_TYPE_ETHERNET
+ "Hardware Default", // NETWORK_TYPE_DEFAULT
+ "Active", // NETWORK_TYPE_ACTIVE
+};
+const int networkNameEntries = sizeof(networkName) / sizeof(networkName[0]);
+
+// List of state names
+const char * const networkState[] =
+{
+ "NETWORK_STATE_OFF",
+ "NETWORK_STATE_DELAY",
+ "NETWORK_STATE_CONNECTING",
+ "NETWORK_STATE_IN_USE",
+ "NETWORK_STATE_WAIT_NO_USERS",
+};
+const int networkStateEntries = sizeof(networkState) / sizeof(networkState[0]);
+
+// List of network users
+const char * const networkUser[] =
+{
+ "NTP Server",
+ "NTRIP Client",
+ "OTA Firmware Update",
+ "PVT Client",
+ "PVT Server",
+ "PVT UDP Server",
+ "NTRIP Server 0",
+ "NTRIP Server 1",
+};
+const int networkUserEntries = sizeof(networkUser) / sizeof(networkUser[0]);
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+static NETWORK_DATA networkData = {NETWORK_TYPE_ACTIVE, NETWORK_TYPE_ACTIVE};
+static uint32_t networkLastIpAddressDisplayMillis[NETWORK_TYPE_MAX];
+
+//----------------------------------------
+// Menu to get the common network settings
+//----------------------------------------
+void menuNetwork()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Network");
+ systemPrintln();
+
+ //------------------------------
+ // Display the PVT client menu items
+ //------------------------------
+
+ // Display the menu
+ systemPrintf("1) PVT Client: %s\r\n", settings.enablePvtClient ? "Enabled" : "Disabled");
+ if (settings.enablePvtClient)
+ {
+ systemPrintf("2) PVT Client Host: %s\r\n", settings.pvtClientHost);
+ systemPrintf("3) PVT Client Port: %ld\r\n", settings.pvtClientPort);
+ }
+
+ //------------------------------
+ // Display the PVT server menu items
+ //------------------------------
+
+ systemPrintf("4) PVT Server: %s\r\n", settings.enablePvtServer ? "Enabled" : "Disabled");
+
+ if (settings.enablePvtServer)
+ systemPrintf("5) PVT Server Port: %ld\r\n", settings.pvtServerPort);
+
+ systemPrintf("6) PVT UDP Server: %s\r\n", settings.enablePvtUdpServer ? "Enabled" : "Disabled");
+
+ if (settings.enablePvtUdpServer)
+ systemPrintf("7) PVT UDP Server Port: %ld\r\n", settings.pvtUdpServerPort);
+
+ if (HAS_ETHERNET)
+ {
+ //------------------------------
+ // Display the network layer menu items
+ //------------------------------
+
+ systemPrint("d) Default network: ");
+ networkPrintName(settings.defaultNetworkType);
+ systemPrintln();
+
+ systemPrint("f) Ethernet / WiFi Failover: ");
+ systemPrintf("%s\r\n", settings.enableNetworkFailover ? "Enabled" : "Disabled");
+ }
+
+ //------------------------------
+ // Finish the menu and get the input
+ //------------------------------
+
+ systemPrintln("x) Exit");
+ byte incoming = getCharacterNumber();
+
+ //------------------------------
+ // Get the PVT client parameters
+ //------------------------------
+
+ // Toggle PVT client enable
+ if (incoming == 1)
+ settings.enablePvtClient ^= 1;
+
+ // Get the PVT client host
+ else if ((incoming == 2) && settings.enablePvtClient)
+ {
+ char hostname[sizeof(settings.pvtClientHost)];
+
+ systemPrint("Enter PVT client host name / address: ");
+
+ // Get the host name or IP address
+ memset(hostname, 0, sizeof(hostname));
+ getString(hostname, sizeof(hostname) - 1);
+ strcpy(settings.pvtClientHost, hostname);
+
+ // Remove any http:// or https:// prefix from host name
+ // strtok modifies string to be parsed so we create a copy
+ strncpy(hostname, settings.pvtClientHost, sizeof(hostname) - 1);
+ char *token = strtok(hostname, "//");
+ if (token != nullptr)
+ {
+ token = strtok(nullptr, "//"); // Advance to data after //
+ if (token != nullptr)
+ strcpy(settings.pvtClientHost, token);
+ }
+ }
+
+ // Get the PVT client port number
+ else if ((incoming == 3) && settings.enablePvtClient)
+ {
+ systemPrint("Enter the PVT client port number to use (0 to 65535): ");
+ int portNumber = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((portNumber != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (portNumber != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if ((portNumber < 0) || (portNumber > 65535))
+ systemPrintln("Error: Port number out of range");
+ else
+ {
+ settings.pvtClientPort = portNumber; // Recorded to NVM and file at main menu exit
+ }
+ }
+ }
+
+ //------------------------------
+ // Get the PVT server parameters
+ //------------------------------
+
+ else if (incoming == 4)
+ // Toggle WiFi NEMA server
+ settings.enablePvtServer ^= 1;
+
+ else if (incoming == 5)
+ {
+ systemPrint("Enter the TCP port to use (0 to 65535): ");
+ int portNumber = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((portNumber != INPUT_RESPONSE_GETNUMBER_EXIT) && (portNumber != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (portNumber < 0 || portNumber > 65535)
+ systemPrintln("Error: TCP Port out of range");
+ else
+ settings.pvtServerPort = portNumber; // Recorded to NVM and file at main menu exit
+ }
+ }
+
+ //------------------------------
+ // Get the PVT UDP server parameters
+ //------------------------------
+
+ else if (incoming == 6)
+ // Toggle WiFi UDP NEMA server
+ settings.enablePvtUdpServer ^= 1;
+
+ else if (incoming == 7 && settings.enablePvtUdpServer)
+ {
+ systemPrint("Enter the UDP port to use (0 to 65535): ");
+ int portNumber = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((portNumber != INPUT_RESPONSE_GETNUMBER_EXIT) && (portNumber != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (portNumber < 0 || portNumber > 65535)
+ systemPrintln("Error: UDP Port out of range");
+ else
+ settings.pvtUdpServerPort = portNumber; // Recorded to NVM and file at main menu exit
+ }
+ }
+
+ //------------------------------
+ // Get the network layer parameters
+ //------------------------------
+
+ else if ((incoming == 'd') && HAS_ETHERNET)
+ {
+ // Toggle the network type
+ settings.defaultNetworkType += 1;
+ if (settings.defaultNetworkType > NETWORK_TYPE_USE_DEFAULT)
+ settings.defaultNetworkType = 0;
+ }
+ else if ((incoming == 'f') && HAS_ETHERNET)
+ {
+ // Toggle failover support
+ settings.enableNetworkFailover ^= 1;
+ }
+
+ //------------------------------
+ // Handle exit and invalid input
+ //------------------------------
+
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+}
+
+//----------------------------------------
+// Allocate a network client
+//----------------------------------------
+NetworkClient * networkClient(uint8_t user, bool useSSL)
+{
+ NetworkClient * client;
+ int type;
+
+ type = networkGetType(user);
+#if defined(COMPILE_ETHERNET)
+ if (type == NETWORK_TYPE_ETHERNET)
+ {
+ if (useSSL)
+ client = new NetworkEthernetSslClient();
+ else
+ client = new NetworkEthernetClient;
+ }
+ else
+#endif // COMPILE_ETHERNET
+ {
+#if defined(COMPILE_WIFI)
+ if (useSSL)
+ client = new NetworkWiFiSslClient();
+ else
+ client = new NetworkWiFiClient();
+#else // COMPILE_WIFI
+ client = nullptr;
+#endif // COMPILE_WIFI
+ }
+ return client;
+}
+
+//----------------------------------------
+// Display the IP address
+//----------------------------------------
+void networkDisplayIpAddress(uint8_t networkType)
+{
+ char ipAddress[32];
+ NETWORK_DATA * network;
+
+ network = &networkData;
+// network = networkGet(networkType, false);
+ if (network && (networkType == network->type) && (network->state >= NETWORK_STATE_IN_USE))
+ {
+ if (settings.debugNetworkLayer || settings.printNetworkStatus)
+ {
+ strcpy(ipAddress, networkGetIpAddress(networkType).toString().c_str());
+ if (network->type == NETWORK_TYPE_WIFI)
+ systemPrintf("%s IP address: %s, RSSI: %d\r\n", networkName[network->type], ipAddress, wifiGetRssi());
+ else
+ systemPrintf("%s IP address: %s\r\n", networkName[network->type], ipAddress);
+
+ // The address was just displayed
+ networkLastIpAddressDisplayMillis[networkType] = millis();
+ }
+ }
+}
+
+//----------------------------------------
+// Get the network type
+//----------------------------------------
+NETWORK_DATA * networkGet(uint8_t networkType, bool updateRequestedNetwork)
+{
+ NETWORK_DATA * network;
+ uint8_t selectedNetworkType;
+
+ do
+ {
+ network = &networkData;
+
+ // Translate the default network type
+ selectedNetworkType = networkTranslateNetworkType(networkType, false);
+ if (settings.debugNetworkLayer && (networkType != selectedNetworkType))
+ systemPrintf("networkGet, networkType: %s --> %s\r\n",
+ networkName[networkType], networkName[selectedNetworkType]);
+ networkType = selectedNetworkType;
+
+ // Select the network
+ if (updateRequestedNetwork && ((network->state < NETWORK_STATE_CONNECTING)
+ || (network->state == NETWORK_STATE_WAIT_NO_USERS)))
+ {
+ selectedNetworkType = network->requestedNetwork;
+ if ((selectedNetworkType == NETWORK_TYPE_ACTIVE)
+ && (networkType <= NETWORK_TYPE_USE_DEFAULT))
+ selectedNetworkType = networkType;
+ else if ((selectedNetworkType == NETWORK_TYPE_USE_DEFAULT)
+ && (networkType < NETWORK_TYPE_MAX))
+ selectedNetworkType = networkType;
+ if (settings.debugNetworkLayer && (network->requestedNetwork != selectedNetworkType))
+ systemPrintf("networkUserOpen, network->requestedNetwork: %s --> %s\r\n",
+ networkName[network->requestedNetwork],
+ networkName[selectedNetworkType]);
+ network->requestedNetwork = selectedNetworkType;
+
+ // Update the network type before connecting to the network
+ if (network->state < NETWORK_STATE_CONNECTING)
+ {
+ if (settings.debugNetworkLayer && (network->type != selectedNetworkType))
+ systemPrintf("networkUserOpen, network->type: %s --> %s\r\n",
+ networkName[network->type], networkName[selectedNetworkType]);
+ network->type = selectedNetworkType;
+ }
+ }
+
+ // Determine if the network was found
+ if ((network->state == NETWORK_STATE_OFF)
+ || (networkType == network->type)
+ || (networkType == NETWORK_TYPE_ACTIVE))
+ break;
+
+ // Network not available if another device is using it
+ network = nullptr;
+ } while (0);
+
+ // Return the network
+ return network;
+}
+
+//----------------------------------------
+// Get the IP address
+//----------------------------------------
+IPAddress networkGetIpAddress(uint8_t networkType)
+{
+ if (networkType == NETWORK_TYPE_ETHERNET)
+ return ethernetGetIpAddress();
+ if (networkType == NETWORK_TYPE_WIFI)
+ return wifiGetIpAddress();
+ return IPAddress((uint32_t)0);
+}
+
+//----------------------------------------
+// Get the network type
+//----------------------------------------
+uint8_t networkGetType(uint8_t user)
+{
+ NETWORK_DATA * network;
+
+ network = networkGetUserNetwork(user);
+ if (network)
+ return network->type;
+ return NETWORK_TYPE_WIFI;
+}
+
+//----------------------------------------
+// Get the network with this active user
+//----------------------------------------
+NETWORK_DATA * networkGetUserNetwork(NETWORK_USER user)
+{
+ NETWORK_DATA * network;
+ int networkType;
+ NETWORK_USER userMask;
+
+ // Locate the network for this user
+ userMask = 1 << user;
+ for (networkType = 0; networkType < NETWORK_TYPE_MAX; networkType++)
+ {
+ network = networkGet(networkType, false);
+ if (network && ((network->activeUsers & userMask)
+ || (network->userOpens & userMask)))
+ return network;
+ }
+
+ return nullptr; //User is not active on any network
+}
+
+//----------------------------------------
+// Perform the common network initialization
+//----------------------------------------
+void networkInitialize(NETWORK_DATA * network)
+{
+ uint8_t requestedNetwork;
+ NETWORK_USER userOpens;
+
+ // Save the values
+ requestedNetwork = network->requestedNetwork;
+ if (settings.debugNetworkLayer && (requestedNetwork != network->type))
+ systemPrintf("networkInitialize, network->type: %s --> %s\r\n",
+ networkName[network->type], networkName[requestedNetwork]);
+ userOpens = network->userOpens;
+
+ // Initialize the network
+ memset(network, 0, sizeof(*network));
+
+ // Complete the initialization
+ network->requestedNetwork = requestedNetwork;
+ network->type = requestedNetwork;
+ network->userOpens = userOpens;
+ network->timeout = 2;
+ network->timerStart = millis();
+}
+
+//----------------------------------------
+// Determine if the network is connected to the media
+//----------------------------------------
+bool networkIsConnected(NETWORK_DATA * network)
+{
+ // Determine the network is connected
+ if (network && (network->state == NETWORK_STATE_IN_USE))
+ return networkIsMediaConnected(network);
+ return false;
+}
+
+//----------------------------------------
+// Determine if the network is connected to the media
+//----------------------------------------
+bool networkIsTypeConnected(uint8_t networkType)
+{
+ // Determine the network is connected
+ return networkIsConnected(networkGet(networkType, false));
+}
+
+//----------------------------------------
+// Determine if the network is connected to the media
+//----------------------------------------
+bool networkIsMediaConnected(NETWORK_DATA * network)
+{
+ bool isConnected;
+
+ // Determine if the network is connected to the media
+ switch (network->type)
+ {
+ default:
+ isConnected = false;
+ break;
+
+ case NETWORK_TYPE_ETHERNET:
+ isConnected = (online.ethernetStatus == ETH_CONNECTED);
+ break;
+
+ case NETWORK_TYPE_WIFI:
+ isConnected = wifiIsConnected();
+ break;
+ }
+
+ // Verify that the network has an IP address
+ if (isConnected && (networkGetIpAddress(network->type) != 0))
+ {
+ networkPeriodicallyDisplayIpAddress();
+ return true;
+ }
+
+ // The network is not ready for use
+ return false;
+}
+
+//----------------------------------------
+// Determine if the network is off
+//----------------------------------------
+bool networkIsOff(uint8_t networkType)
+{
+ NETWORK_DATA * network;
+
+ network = networkGet(networkType, false);
+ return network && (network->state == NETWORK_STATE_OFF);
+}
+
+//----------------------------------------
+// Determine if the network is shutting down
+//----------------------------------------
+bool networkIsShuttingDown(uint8_t user)
+{
+ NETWORK_DATA * network;
+
+ network = networkGetUserNetwork(user);
+ return network && (network->state == NETWORK_STATE_WAIT_NO_USERS);
+}
+
+//----------------------------------------
+// Periodically display the IP address
+//----------------------------------------
+void networkPeriodicallyDisplayIpAddress()
+{
+ if (PERIODIC_DISPLAY(PD_ETHERNET_IP_ADDRESS))
+ {
+ PERIODIC_CLEAR(PD_ETHERNET_IP_ADDRESS);
+ networkDisplayIpAddress(NETWORK_TYPE_ETHERNET);
+ }
+ if (PERIODIC_DISPLAY(PD_WIFI_IP_ADDRESS))
+ {
+ PERIODIC_CLEAR(PD_WIFI_IP_ADDRESS);
+ networkDisplayIpAddress(NETWORK_TYPE_WIFI);
+ }
+}
+
+//----------------------------------------
+// Print the name associated with a network type
+//----------------------------------------
+void networkPrintName(uint8_t networkType)
+{
+ if (networkType > NETWORK_TYPE_USE_DEFAULT)
+ systemPrint("Unknown");
+ else if (HAS_ETHERNET)
+ systemPrint(networkName[networkType]);
+ else
+ systemPrint(networkName[NETWORK_TYPE_WIFI]);
+}
+
+//----------------------------------------
+// Attempt to restart the network
+//----------------------------------------
+void networkRestart(uint8_t user)
+{
+ // Determine if restart is possible
+ networkRestartNetwork(networkGetUserNetwork(user));
+}
+
+void networkRestartNetwork(NETWORK_DATA * network)
+{
+ // Determine if restart is possible
+ if (network && (!network->shutdown))
+
+ // The network was not stopped, allow it to be restarted
+ network->restart = true;
+}
+
+//----------------------------------------
+// Retry the network connection
+//----------------------------------------
+void networkRetry(NETWORK_DATA * network, uint8_t previousNetworkType)
+{
+ uint8_t networkType;
+ int seconds;
+ uint8_t users;
+
+ // Determine the delay multiplier
+ network->connectionAttempt += 1;
+ if (network->connectionAttempt > NETWORK_MAX_RETRIES)
+ // Use the maximum delay and continue retrying the network connection
+ network->connectionAttempt = NETWORK_MAX_RETRIES;
+
+ // Compute the delay between retries
+ network->timeout = NETWORK_DELAY_BEFORE_RETRY << (network->connectionAttempt - 1);
+
+ // Determine if failover is possible
+ if (HAS_ETHERNET && (wifiNetworkCount() > 0) && settings.enableNetworkFailover
+ && (network->requestedNetwork >= NETWORK_TYPE_MAX))
+ {
+ // Get the next failover network
+ networkType = networkFailover[previousNetworkType];
+ if (settings.debugNetworkLayer || settings.printNetworkStatus)
+ {
+ systemPrint("Network failover: ");
+ systemPrint(networkName[previousNetworkType]);
+ systemPrint("-->");
+ systemPrintln(networkName[networkType]);
+ }
+
+ // Initialize the network
+ network->requestedNetwork = networkType;
+ }
+
+ // Display the delay
+ if ((settings.debugNetworkLayer || settings.printNetworkStatus) && network->timeout)
+ {
+ seconds = network->timeout / 1000;
+ if (seconds < 120)
+ systemPrintf("Network delaying %d seconds before connection\r\n", seconds);
+ else
+ systemPrintf("Network delaying %d minutes before connection\r\n", seconds / 60);
+ }
+
+ // Start the delay between network connection retries
+ network->timerStart = millis();
+ networkSetState(network, NETWORK_STATE_DELAY);
+}
+
+//----------------------------------------
+// Set the next state for the network state machine
+//----------------------------------------
+void networkSetState(NETWORK_DATA * network, byte newState)
+{
+ // Display the state transition
+ if (settings.debugNetworkLayer)
+ {
+ // Display the network state
+ systemPrint("Network State: ");
+ if (newState != network->state)
+ systemPrintf("%s --> ", networkState[network->state]);
+ else
+ systemPrint("*");
+
+ // Display the new network state
+ if (newState >= networkStateEntries)
+ {
+ systemPrintf("Unknown network layer state (%d)\r\n", newState);
+ reportFatalError("Unknown network layer state");
+ }
+ else
+ systemPrintf("%s\r\n", networkState[newState]);
+ }
+
+ // Validate the network state
+ if (newState >= NETWORK_STATE_MAX)
+ reportFatalError("Invalid network state");
+
+ // Set the new state
+ network->state = newState;
+}
+
+//----------------------------------------
+// Shutdown access to the network hardware
+//----------------------------------------
+void networkShutdownHardware(NETWORK_DATA * network)
+{
+ // Stop WiFi if necessary
+ if (network->type == NETWORK_TYPE_WIFI)
+ {
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network stopping WiFi");
+ wifiShutdown();
+ }
+}
+
+//----------------------------------------
+// Start the network
+//----------------------------------------
+void networkStart(uint8_t networkType)
+{
+ NETWORK_DATA * network;
+
+ // Validate the network type
+ if (networkType >= NETWORK_TYPE_LAST)
+ reportFatalError("Attempting to start an invalid network type!");
+
+ // Start the network layer
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network request to start %s\r\n", networkName[networkType]);
+
+ // Get the network data
+ network = networkGet(networkType, false);
+ if (!network)
+ reportFatalError("Network failed to get the network structure");
+ else
+ {
+ // Verify that the network is stopped
+ if (network->state != NETWORK_STATE_OFF)
+ systemPrintf("Network already started!\r\n");
+ else
+ {
+ // Start the network layer
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network layer starting %s\r\n", networkName[network->type]);
+
+ // Initialize the network
+ networkInitialize(network);
+
+ // Delay before starting the network
+ networkSetState(network, NETWORK_STATE_DELAY);
+ }
+ }
+}
+
+//----------------------------------------
+// Stop the network
+//----------------------------------------
+void networkStop(uint8_t networkType)
+{
+ NETWORK_DATA * network;
+ bool restart;
+ int serverIndex;
+ bool shutdown;
+ int user;
+
+ do
+ {
+ // Validate the network type
+ if (networkType >= NETWORK_TYPE_MAX)
+ reportFatalError("Attempt to shutdown invalid network type!");
+
+ // Shutdown all networks
+ if (networkType >= NETWORK_TYPE_MAX)
+ {
+ for (networkType = 0; networkType < NETWORK_TYPE_MAX; networkType++)
+ networkStop(networkType);
+ break;
+ }
+
+ // Determine if the network is running
+ network = networkGet(networkType, false);
+ if ((!network) || (networkType != network->type))
+ // The network is already stopped
+ break;
+
+ // Save the shutdown status
+ shutdown = network->shutdown;
+
+ // Stop the clients of this network
+ for (user = 0; user < (sizeof(network->activeUsers) * 8); user++)
+ {
+ // Determine if the network client is active
+ if (network->activeUsers & (1 << user))
+ {
+ // When user calls networkUserClose don't recursively
+ // call networkStop
+ network->shutdown = false;
+
+ // Stop the network client
+ switch(user)
+ {
+ default:
+ if ((user >= NETWORK_USER_NTRIP_SERVER)
+ && (user < (NETWORK_USER_NTRIP_SERVER + NTRIP_SERVER_MAX)))
+ {
+ serverIndex = user - NETWORK_USER_NTRIP_SERVER;
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping NTRIP server");
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+ }
+ break;
+
+ case NETWORK_USER_NTP_SERVER:
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping NTP server");
+ ntpServerStop();
+ break;
+
+ case NETWORK_USER_NTRIP_CLIENT:
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping NTRIP client");
+ ntripClientStop(true); // Note: was ntripClientRestart();
+ break;
+
+ case NETWORK_USER_OTA_FIRMWARE_UPDATE:
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping OTA firmware update");
+ otaStop();
+ break;
+
+ case NETWORK_USER_PVT_CLIENT:
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping PVT client");
+ pvtClientStop();
+ break;
+
+ case NETWORK_USER_PVT_SERVER:
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping PVT server");
+ pvtServerStop();
+ break;
+
+ case NETWORK_USER_PVT_UDP_SERVER:
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping PVT UDP server");
+ pvtUdpServerStop();
+ break;
+ }
+ }
+ }
+
+ // Restore the shutdown status
+ network->shutdown = shutdown;
+
+ // Determine if the network can be stopped now
+ if ((network->state < NETWORK_STATE_IN_USE) || (!network->activeUsers))
+ {
+ // Remember the current network info
+ restart = network->restart;
+
+ // Stop the network layer
+ networkShutdownHardware(network);
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer stopping");
+
+ // Initialize the network layer
+ // requestedNetwork is set below upon entry to NETWORK_STATE_CONNECTING and
+ // indicates the network desired upon restart
+ // userOpens may be non-zero and indicates users waiting for network restart
+ // activeUsers is already zero
+ // Don't initialize connectionAttempt
+ // networkRetry or networkStart initializes:
+ // connectionAttempt
+ // timeout
+ // timerStart
+ network->restart = false;
+ network->shutdown = false;
+ networkSetState(network, NETWORK_STATE_OFF);
+
+ // Restart the network if requested
+ if (restart)
+ {
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer restarting");
+ networkRetry(network, network->type);
+ }
+
+ // Update the network type
+ network->type = network->requestedNetwork;
+ break;
+ }
+
+ // Start shutting down the network and wait for users to detect the shutdown
+ if (network->state != NETWORK_STATE_WAIT_NO_USERS)
+ {
+ network->shutdown = true;
+ if (settings.debugNetworkLayer)
+ systemPrintln("Network layer waiting for users to stop!");
+ networkSetState(network, NETWORK_STATE_WAIT_NO_USERS);
+ }
+ } while (0);
+}
+
+//----------------------------------------
+// Translate the network type
+//----------------------------------------
+uint8_t networkTranslateNetworkType(uint8_t networkType, bool translateActive)
+{
+ uint8_t newNetworkType;
+//systemPrintf("networkTranslateNetworkType(%s, %s) called\r\n", networkName[networkType], translateActive ? "true" : "false");
+
+ // Get the default network type
+ newNetworkType = networkType;
+ if ((newNetworkType == NETWORK_TYPE_USE_DEFAULT)
+ || (translateActive && (newNetworkType == NETWORK_TYPE_ACTIVE)))
+ newNetworkType = settings.defaultNetworkType;
+
+ // Translate the default network type
+ if (newNetworkType == NETWORK_TYPE_USE_DEFAULT)
+ {
+ if (HAS_ETHERNET)
+ newNetworkType = NETWORK_TYPE_ETHERNET;
+ else
+ newNetworkType = NETWORK_TYPE_WIFI;
+ }
+ return newNetworkType;
+}
+
+//----------------------------------------
+// Update the network device state
+//----------------------------------------
+void networkTypeUpdate(uint8_t networkType)
+{
+ char errorMsg[64];
+ NETWORK_DATA * network;
+
+ // Update the physical network connections
+ switch (networkType)
+ {
+ case NETWORK_TYPE_WIFI:
+ wifiUpdate();
+ break;
+
+ case NETWORK_TYPE_ETHERNET:
+ ethernetUpdate();
+ break;
+ }
+
+ // Locate an active network
+ network = &networkData;
+ if ((network->type != networkType) && (network->state >= NETWORK_STATE_CONNECTING))
+ return;
+
+ // Process the network state
+ DMW_if
+ networkSetState(network, network->state);
+ switch (network->state)
+ {
+ default:
+ sprintf(errorMsg, "Invalid network state (%d) during update!", network->state);
+ reportFatalError(errorMsg);
+ break;
+
+ // Leave the network off
+ case NETWORK_STATE_OFF:
+ break;
+
+ // Pause before making the network connection
+ case NETWORK_STATE_DELAY:
+ // Determine if the network is shutting down
+ if (network->shutdown)
+ {
+ NETWORK_STOP(network->type);
+ }
+
+ // Delay before starting the network
+ else if ((millis() - network->timerStart) >= network->timeout)
+ {
+ // Start the network
+ network->type = networkTranslateNetworkType(network->requestedNetwork, true);
+ if (settings.debugNetworkLayer && (network->type != network->requestedNetwork))
+ systemPrintf("networkTypeUpdate, network->type: %s --> %s\r\n",
+ networkName[network->requestedNetwork], networkName[network->type]);
+ if (settings.debugNetworkLayer)
+ systemPrintf("networkTypeUpdate, network->requestedNetwork: %s --> %s\r\n",
+ networkName[network->requestedNetwork],
+ networkName[NETWORK_TYPE_ACTIVE]);
+ network->requestedNetwork = NETWORK_TYPE_ACTIVE;
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network starting %s\r\n", networkName[network->type]);
+ if (network->type == NETWORK_TYPE_WIFI)
+ wifiStart();
+ network->timerStart = millis();
+ network->timeout = NETWORK_CONNECTION_TIMEOUT;
+ networkSetState(network, NETWORK_STATE_CONNECTING);
+ }
+ break;
+
+ // Wait for the network connection
+ case NETWORK_STATE_CONNECTING:
+ // Determine if the network is shutting down
+ if (network->shutdown)
+ {
+ NETWORK_STOP(network->type);
+ }
+
+ // Determine if the connection failed
+ else if ((millis() - network->timerStart) >= network->timeout)
+ {
+ // Retry the network connection
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network: %s connection timed out\r\n", networkName[network->type]);
+ networkRestartNetwork(network);
+ NETWORK_STOP(network->type);
+ }
+
+ // Determine if the RTK host is connected to the network
+ else if (networkIsMediaConnected(network))
+ {
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network connected to %s\r\n", networkName[network->type]);
+ network->timerStart = millis();
+ network->timeout = NETWORK_MAX_IDLE_TIME;
+ network->activeUsers = network->userOpens;
+ networkSetState(network, NETWORK_STATE_IN_USE);
+ networkDisplayIpAddress(network->type);
+ }
+ break;
+
+ // There is at least one active user of the network connection
+ case NETWORK_STATE_IN_USE:
+ // Determine if the network is shutting down
+ if (network->shutdown)
+ {
+ NETWORK_STOP(network->type);
+ }
+
+ // Verify that the RTK device is still connected to the network
+ else if (!networkIsMediaConnected(network))
+ {
+ // The network failed
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network: %s connection failed!\r\n", networkName[network->type]);
+ networkRestartNetwork(network);
+ NETWORK_STOP(network->type);
+ }
+
+ // Check for the idle timeout
+ else if ((millis() - network->timerStart) >= network->timeout)
+ {
+ // Determine if the network is in use
+ network->timerStart = millis();
+ if (network->activeUsers)
+ {
+ // Network in use, reduce future connection delays
+ network->connectionAttempt = 0;
+
+ // Set the next time that network idle should be checked
+ network->timeout = NETWORK_MAX_IDLE_TIME;
+ }
+
+ // Without users there is no need for the network.
+ else
+ {
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network shutting down %s, no users\r\n", networkName[network->type]);
+ NETWORK_STOP(network->type);
+ }
+ }
+ break;
+
+ case NETWORK_STATE_WAIT_NO_USERS:
+ // Stop the network when all the users are removed
+ if (!network->activeUsers)
+ NETWORK_STOP(network->type);
+ break;
+ }
+
+ // Periodically display the state
+ if (PERIODIC_DISPLAY(PD_NETWORK_STATE))
+ networkSetState(network, network->state);
+}
+
+//----------------------------------------
+// Maintain the network connections
+//----------------------------------------
+void networkUpdate()
+{
+ uint8_t networkType;
+
+ // Update the network layer
+ for (networkType = 0; networkType < NETWORK_TYPE_MAX; networkType++)
+ networkTypeUpdate(networkType);
+ if (PERIODIC_DISPLAY(PD_NETWORK_STATE))
+ PERIODIC_CLEAR(PD_NETWORK_STATE);
+
+ // Update the network services
+ ntpServerUpdate(); // Process any received NTP requests
+ ntripClientUpdate(); // Check the NTRIP client connection and move data NTRIP --> ZED
+ ntripServerUpdate(); // Check the NTRIP server connection and move data ZED --> NTRIP
+ otaClientUpdate(); // Perform automatic over-the-air firmware updates
+ pvtClientUpdate(); // Turn on the PVT client as needed
+ pvtServerUpdate(); // Turn on the PVT server as needed
+ pvtUdpServerUpdate(); // Turn on the PVT UDP server as needed
+
+ // Display the IP addresses
+ networkPeriodicallyDisplayIpAddress();
+}
+
+//----------------------------------------
+// Stop a user of the network
+//----------------------------------------
+void networkUserClose(uint8_t user)
+{
+ char errorText[64];
+ NETWORK_DATA * network;
+ NETWORK_USER userMask;
+
+ // Verify the user number
+ if (user >= NETWORK_USER_MAX)
+ {
+ sprintf(errorText, "Invalid network user (%d)", user);
+ reportFatalError(errorText);
+ }
+ else
+ {
+ // Verify that this user is running
+ userMask = 1 << user;
+ network = networkGetUserNetwork(user);
+ if (network && (network->userOpens & userMask))
+ {
+ // Done with this network user
+ network->activeUsers &= ~userMask;
+ network->userOpens &= ~userMask;
+ if (settings.debugNetworkLayer)
+ {
+ systemPrintf("Network stopping user %s", networkUser[user]);
+ if (network->state != NETWORK_STATE_OFF)
+ systemPrintf(" on %s", networkName[network->type]);
+ systemPrintln();
+ }
+
+ // Shutdown the network if requested
+ if (network->shutdown && (!network->activeUsers))
+ NETWORK_STOP(network->type);
+ }
+
+ // The network user is not running
+ else
+ {
+ sprintf(errorText, "Network user %s is already idle", networkUser[user]);
+ reportFatalError(errorText);
+ }
+ }
+}
+
+//----------------------------------------
+// Determine if the network user is connected to the media
+//----------------------------------------
+bool networkUserConnected(NETWORK_USER user)
+{
+ NETWORK_DATA * network;
+
+ network = networkGetUserNetwork(user);
+ if (network && (network->state != NETWORK_STATE_WAIT_NO_USERS))
+ return networkIsConnected(network);
+ return false;
+}
+
+//----------------------------------------
+// Start a user of the network
+//----------------------------------------
+bool networkUserOpen(uint8_t user, uint8_t networkType)
+{
+ char errorText[64];
+ NETWORK_DATA * network;
+ NETWORK_USER userMask;
+
+ do
+ {
+ // Verify the user number
+ if (user >= NETWORK_USER_MAX)
+ {
+ sprintf(errorText, "Invalid network user (%d)", user);
+ reportFatalError(errorText);
+ break;
+ }
+
+ // Determine if the network is available
+ network = networkGet(networkType, true);
+ if (network && (network->state != NETWORK_STATE_WAIT_NO_USERS) && (!network->shutdown))
+ {
+ userMask = 1 << user;
+ if ((network->activeUsers >> user) & 1)
+ {
+ reportFatalError("Network user already started!");
+ break;
+ }
+
+ // Start the user
+ if (settings.debugNetworkLayer)
+ systemPrintf("Network starting user %s on %s\r\n", networkUser[user], networkName[network->type]);
+ switch (network->state)
+ {
+ case NETWORK_STATE_OFF:
+ networkStart(network->type);
+ break;
+
+ case NETWORK_STATE_IN_USE:
+ network->activeUsers |= userMask;
+ break;
+ }
+ network->userOpens |= userMask;
+ return true;
+ }
+ } while (0);
+
+ // The network user was not started
+ return false;
+}
+
+// Verify the network layer tables
+void networkVerifyTables()
+{
+ // Verify the table lengths
+ if (networkFailoverEntries != NETWORK_TYPE_MAX)
+ reportFatalError("Fix networkFailover table to match NetworkTypes");
+ if (networkNameEntries != NETWORK_TYPE_LAST)
+ reportFatalError("Fix networkName table to match NetworkTypes");
+ if (networkStateEntries != NETWORK_STATE_MAX)
+ reportFatalError("Fix networkState table to match NetworkStates");
+ if (networkUserEntries != NETWORK_USER_MAX)
+ reportFatalError("Fix networkUser table to match NetworkUsers");
+}
+
+#endif // COMPILE_NETWORK
diff --git a/Firmware/RTK_Surveyor/NetworkClient.h b/Firmware/RTK_Surveyor/NetworkClient.h
new file mode 100644
index 000000000..0ceba6fa4
--- /dev/null
+++ b/Firmware/RTK_Surveyor/NetworkClient.h
@@ -0,0 +1,345 @@
+#ifndef __NETWORK_CLIENT_H__
+#define __NETWORK_CLIENT_H__
+
+extern uint8_t networkGetType(uint8_t user);
+
+class NetworkClient : public Client
+{
+ protected:
+
+ Client * _client; // Ethernet or WiFi client
+ bool _friendClass;
+ uint8_t _networkType;
+
+ public:
+
+ //------------------------------
+ // Create the network client
+ //------------------------------
+ NetworkClient(Client * client, uint8_t networkType)
+ {
+ _friendClass = true;
+ _networkType = networkType;
+ _client = client;
+ }
+
+ NetworkClient(uint8_t user)
+ {
+ _friendClass = false;
+ _networkType = networkGetType(user);
+#if defined(COMPILE_ETHERNET)
+ if (_networkType == NETWORK_TYPE_ETHERNET)
+ _client = new EthernetClient;
+ else
+#endif // COMPILE_ETHERNET
+#if defined(COMPILE_WIFI)
+ _client = new WiFiClient;
+#else // COMPILE_WIFI
+ _client = nullptr;
+#endif // COMPILE_WIFI
+ };
+
+ //------------------------------
+ // Delete the network client
+ //------------------------------
+ ~NetworkClient()
+ {
+ if (_client)
+ {
+ _client->stop();
+ if (!_friendClass)
+ delete _client;
+ _client = nullptr;
+ }
+ };
+
+ //------------------------------
+ // Determine if receive data is available
+ //------------------------------
+
+ int available()
+ {
+ if (_client)
+ return _client->available();
+ return 0;
+ }
+
+ //------------------------------
+ // Determine if the network client was allocated
+ //------------------------------
+
+ operator bool()
+ {
+ return _client;
+ }
+
+ //------------------------------
+ // Connect to the server
+ //------------------------------
+
+ int connect(IPAddress ip, uint16_t port)
+ {
+ if (_client)
+ return _client->connect(ip, port);
+ return 0;
+ }
+
+ int connect(const char *host, uint16_t port)
+ {
+ if (_client)
+ return _client->connect(host, port);
+ return 0;
+ }
+
+ //------------------------------
+ // Determine if the client is connected to the server
+ //------------------------------
+
+ uint8_t connected()
+ {
+ if (_client)
+ return _client->connected();
+ return 0;
+ }
+
+ //------------------------------
+ // Finish transmitting all the data to the server
+ //------------------------------
+
+ void flush()
+ {
+ if (_client)
+ _client->flush();
+ }
+
+ //------------------------------
+ // Look at the next received byte in the data stream
+ //------------------------------
+
+ int peek()
+ {
+ if (_client)
+ return _client->peek();
+ return -1;
+ }
+
+ //------------------------------
+ // Display the network client status
+ //------------------------------
+ size_t print(const char *printMe)
+ {
+ if (_client)
+ return _client->print(printMe);
+ return 0;
+ };
+
+ //------------------------------
+ // Receive a data byte from the server
+ //------------------------------
+
+ int read()
+ {
+ if (_client)
+ return _client->read();
+ return 0;
+ }
+
+ //------------------------------
+ // Receive a buffer of data from the server
+ //------------------------------
+
+ int read(uint8_t *buf, size_t size)
+ {
+ if (_client)
+ return _client->read(buf, size);
+ return 0;
+ }
+
+ //------------------------------
+ // Get the remote IP address
+ //------------------------------
+
+ IPAddress remoteIP()
+ {
+#if defined(COMPILE_ETHERNET)
+ if (_networkType == NETWORK_TYPE_ETHERNET)
+ return ((EthernetClient *)_client)->remoteIP();
+#endif // COMPILE_ETHERNET
+#if defined(COMPILE_WIFI)
+ if (_networkType == NETWORK_TYPE_WIFI)
+ return ((WiFiClient *)_client)->remoteIP();
+#endif // COMPILE_WIFI
+ return IPAddress((uint32_t)0);
+ }
+
+ //------------------------------
+ // Get the remote port number
+ //------------------------------
+
+ uint16_t remotePort()
+ {
+#if defined(COMPILE_ETHERNET)
+ if (_networkType == NETWORK_TYPE_ETHERNET)
+ return ((EthernetClient *)_client)->remotePort();
+#endif // COMPILE_ETHERNET
+#if defined(COMPILE_WIFI)
+ if (_networkType == NETWORK_TYPE_WIFI)
+ return ((WiFiClient *)_client)->remotePort();
+#endif // COMPILE_WIFI
+ return 0;
+ }
+
+ //------------------------------
+ // Stop the network client
+ //------------------------------
+
+ void stop()
+ {
+ if (_client)
+ _client->stop();
+ }
+
+ //------------------------------
+ // Send a data byte to the server
+ //------------------------------
+
+ size_t write(uint8_t b)
+ {
+ if (_client)
+ return _client->write(b);
+ return 0;
+ }
+
+ //------------------------------
+ // Send a buffer of data to the server
+ //------------------------------
+
+ size_t write(const uint8_t *buf, size_t size)
+ {
+ if (_client)
+ return _client->write(buf, size);
+ return 0;
+ }
+
+ protected:
+
+ //------------------------------
+ // Return the IP address
+ //------------------------------
+
+ uint8_t* rawIPAddress(IPAddress& addr)
+ {
+ return Client::rawIPAddress(addr);
+ }
+
+ //------------------------------
+ // Declare the friend classes
+ //------------------------------
+
+ friend class NetworkEthernetClient;
+ friend class NetworkEthernetSslClient;
+ friend class NetworkWiFiClient;
+ friend class NetworkWiFiSslClient;
+};
+
+#ifdef COMPILE_ETHERNET
+class NetworkEthernetClient : public NetworkClient
+{
+ private:
+
+ EthernetClient _ethernetClient;
+
+ public:
+
+ NetworkEthernetClient() :
+ NetworkClient(&_ethernetClient, NETWORK_TYPE_ETHERNET)
+ {
+ }
+
+ NetworkEthernetClient(EthernetClient& client) :
+ _ethernetClient{client},
+ NetworkClient(&_ethernetClient, NETWORK_TYPE_ETHERNET)
+ {
+ }
+
+ ~NetworkEthernetClient()
+ {
+ this->~NetworkClient();
+ }
+};
+
+class NetworkEthernetSslClient : public NetworkClient
+{
+ protected:
+
+ EthernetClient _ethernetClient;
+ SSLClientESP32 _sslClient;
+
+ public:
+
+ NetworkEthernetSslClient() :
+ _sslClient(),
+ NetworkClient(&_sslClient, NETWORK_TYPE_ETHERNET)
+ {
+ _sslClient.setClient(&_ethernetClient);
+ _sslClient.setCACertBundle(x509CertificateBundle);
+ }
+
+ ~NetworkEthernetSslClient()
+ {
+ this->~NetworkClient();
+ }
+};
+#endif // COMPILE_ETHERNET
+
+#ifdef COMPILE_WIFI
+class NetworkWiFiClient : public NetworkClient
+{
+ protected:
+
+ WiFiClient _client;
+
+ public:
+
+ NetworkWiFiClient() :
+ NetworkClient(&_client, NETWORK_TYPE_WIFI)
+ {
+ }
+
+ NetworkWiFiClient(WiFiClient& client) :
+ _client{client},
+ NetworkClient(&_client, NETWORK_TYPE_WIFI)
+ {
+ }
+
+ ~NetworkWiFiClient()
+ {
+ this->~NetworkClient();
+ }
+};
+
+class NetworkWiFiSslClient : public NetworkClient
+{
+ protected:
+
+ WiFiClient _wifiClient;
+ SSLClientESP32 _sslClient;
+
+ public:
+
+ NetworkWiFiSslClient() :
+ _sslClient(),
+ NetworkClient(&_sslClient, NETWORK_TYPE_WIFI)
+ {
+ _sslClient.setClient(&_wifiClient);
+ _sslClient.setCACertBundle(x509CertificateBundle);
+ }
+
+ ~NetworkWiFiSslClient()
+ {
+ this->~NetworkClient();
+ }
+};
+#endif // COMPILE_WIFI
+
+#endif // __NETWORK_CLIENT_H__
diff --git a/Firmware/RTK_Surveyor/NetworkUDP.h b/Firmware/RTK_Surveyor/NetworkUDP.h
new file mode 100644
index 000000000..3548d0c75
--- /dev/null
+++ b/Firmware/RTK_Surveyor/NetworkUDP.h
@@ -0,0 +1,306 @@
+#ifndef __NETWORK_UDP_H__
+#define __NETWORK_UDP_H__
+
+extern uint8_t networkGetType(uint8_t user);
+
+class NetworkUDP : public UDP
+{
+ protected:
+
+ UDP * _udp; // Ethernet or WiFi udp
+ bool _friendClass;
+ uint8_t _networkType;
+
+ public:
+
+ //------------------------------
+ // Create the network client
+ //------------------------------
+ NetworkUDP(UDP * udp, uint8_t networkType)
+ {
+ _friendClass = true;
+ _networkType = networkType;
+ _udp = udp;
+ }
+
+ NetworkUDP(uint8_t user)
+ {
+ _friendClass = false;
+ _networkType = networkGetType(user);
+#if defined(COMPILE_ETHERNET)
+ if (_networkType == NETWORK_TYPE_ETHERNET)
+ _udp = new EthernetUDP;
+ else
+#endif // COMPILE_ETHERNET
+#if defined(COMPILE_WIFI)
+ _udp = new WiFiUDP;
+#else // COMPILE_WIFI
+ _udp = nullptr;
+#endif // COMPILE_WIFI
+ };
+
+ //------------------------------
+ // Delete the network client
+ //------------------------------
+ ~NetworkUDP()
+ {
+ if (_udp)
+ {
+ _udp->stop();
+ if (!_friendClass)
+ delete _udp;
+ _udp = nullptr;
+ }
+ };
+
+
+ //------------------------------
+ // Determine if the network client was allocated
+ //------------------------------
+
+ operator bool()
+ {
+ return _udp;
+ }
+
+ //------------------------------
+ // Start to the server
+ //------------------------------
+
+ uint8_t begin(uint16_t port)
+ {
+ if (_udp)
+ return _udp->begin(port);
+ return 0;
+ }
+
+ //------------------------------
+ // Stop the network client
+ //------------------------------
+
+ void stop()
+ {
+ if (_udp)
+ _udp->stop();
+ }
+
+ //------------------------------
+ // Determine if receive data is available
+ //------------------------------
+
+ int available()
+ {
+ if (_udp)
+ return _udp->available();
+ return 0;
+ }
+
+ //------------------------------
+ // Read available data
+ //------------------------------
+
+ int read()
+ {
+ if (_udp)
+ return _udp->read();
+ return 0;
+ }
+
+ //------------------------------
+ // Read available data
+ //------------------------------
+
+ int read(unsigned char* buf, size_t length)
+ {
+ if (_udp)
+ return _udp->read(buf, length);
+ return 0;
+ }
+
+ //------------------------------
+ // Read available data
+ //------------------------------
+
+ int read(char* buf, size_t length)
+ {
+ if (_udp)
+ return _udp->read(buf, length);
+ return 0;
+ }
+
+ //------------------------------
+ // Look at the next received byte in the data stream
+ //------------------------------
+
+ int peek()
+ {
+ if (_udp)
+ return _udp->peek();
+ return 0;
+ }
+
+ //------------------------------
+ // Finish transmitting all the data
+ //------------------------------
+
+ void flush()
+ {
+ if (_udp)
+ _udp->flush();
+ }
+
+ //------------------------------
+ // Send a data byte to the server
+ //------------------------------
+
+ size_t write(uint8_t b)
+ {
+ if (_udp)
+ return _udp->write(b);
+ return 0;
+ }
+
+ //------------------------------
+ // Send a buffer of data to the server
+ //------------------------------
+
+ size_t write(const uint8_t *buf, size_t size)
+ {
+ if (_udp)
+ return _udp->write(buf, size);
+ return 0;
+ }
+
+ //------------------------------
+ // Begin a UDP packet
+ //------------------------------
+
+ int beginPacket(IPAddress ip, uint16_t port)
+ {
+ if (_udp)
+ return _udp->beginPacket(ip, port);
+ return 0;
+ }
+
+ //------------------------------
+ // Begin a UDP packet
+ //------------------------------
+
+ int beginPacket(const char* host, uint16_t port)
+ {
+ if (_udp)
+ return _udp->beginPacket(host, port);
+ return 0;
+ }
+
+ //------------------------------
+ // Parse UDP packet
+ //------------------------------
+
+ int parsePacket()
+ {
+ if (_udp)
+ return _udp->parsePacket();
+ return 0;
+ }
+
+ //------------------------------
+ // End the current UDP packet
+ //------------------------------
+
+ int endPacket()
+ {
+ if (_udp)
+ return _udp->endPacket();
+ return 0;
+ }
+
+ //------------------------------
+ // Get the remote IP address
+ //------------------------------
+
+ IPAddress remoteIP()
+ {
+#if defined(COMPILE_ETHERNET)
+ if (_networkType == NETWORK_TYPE_ETHERNET)
+ return ((EthernetUDP *)_udp)->remoteIP();
+#endif // COMPILE_ETHERNET
+#if defined(COMPILE_WIFI)
+ if (_networkType == NETWORK_TYPE_WIFI)
+ return ((WiFiUDP *)_udp)->remoteIP();
+#endif // COMPILE_WIFI
+ return IPAddress((uint32_t)0);
+ }
+
+ //------------------------------
+ // Get the remote port number
+ //------------------------------
+
+ uint16_t remotePort()
+ {
+#if defined(COMPILE_ETHERNET)
+ if (_networkType == NETWORK_TYPE_ETHERNET)
+ return ((EthernetUDP *)_udp)->remotePort();
+#endif // COMPILE_ETHERNET
+#if defined(COMPILE_WIFI)
+ if (_networkType == NETWORK_TYPE_WIFI)
+ return ((WiFiUDP *)_udp)->remotePort();
+#endif // COMPILE_WIFI
+ return 0;
+ }
+
+ protected:
+
+ //------------------------------
+ // Declare the friend classes
+ //------------------------------
+
+ friend class NetworkEthernetUdp;
+ friend class NetworkWiFiUdp;
+};
+
+#ifdef COMPILE_ETHERNET
+class NetworkEthernetUdp : public NetworkUDP
+{
+ private:
+
+ EthernetUDP _udp;
+
+ public:
+
+ NetworkEthernetUdp(EthernetUDP& udp) :
+ _udp{udp},
+ NetworkUDP(&_udp, NETWORK_TYPE_ETHERNET)
+ {
+ }
+
+ ~NetworkEthernetUdp()
+ {
+ this->~NetworkUDP();
+ }
+};
+#endif // COMPILE_ETHERNET
+
+#ifdef COMPILE_WIFI
+class NetworkWiFiUdp : public NetworkUDP
+{
+ private:
+
+ WiFiUDP _udp;
+
+ public:
+
+ NetworkWiFiUdp(WiFiUDP& udp) :
+ _udp{udp},
+ NetworkUDP(&_udp, NETWORK_TYPE_WIFI)
+ {
+ }
+
+ ~NetworkWiFiUdp()
+ {
+ this->~NetworkUDP();
+ }
+};
+#endif // COMPILE_WIFI
+
+#endif // __NETWORK_CLIENT_H__
diff --git a/Firmware/RTK_Surveyor/NtripClient.ino b/Firmware/RTK_Surveyor/NtripClient.ino
new file mode 100644
index 000000000..7b01b4a6c
--- /dev/null
+++ b/Firmware/RTK_Surveyor/NtripClient.ino
@@ -0,0 +1,893 @@
+/*------------------------------------------------------------------------------
+NtripClient.ino
+
+ The NTRIP client sits on top of the network layer and receives correction data
+ from an NTRIP caster that is provided to the ZED (GNSS radio).
+
+ Satellite ... Satellite
+ | | |
+ | | |
+ | V |
+ | RTK |
+ '------> Base <------'
+ Station
+ |
+ | NTRIP Server sends correction data
+ V
+ NTRIP Caster
+ |
+ | NTRIP Client receives correction data
+ V
+ Bluetooth RTK Network: NMEA Client
+ .---------------- Rover --------------------------.
+ | | |
+ | NMEA | Network: NEMA Server | NMEA
+ | position | NEMA position data | position
+ | data V | data
+ | Computer or |
+ '------------> Cell Phone <-----------------------'
+ for display
+
+ NTRIP Client Testing:
+
+ Using Ethernet on RTK Reference Station:
+
+ 1. Network failure - Disconnect Ethernet cable at RTK Reference Station,
+ expecting retry NTRIP client connection after network restarts
+
+ Using WiFi on RTK Express or RTK Reference Station:
+
+ 1. Internet link failure - Disconnect Ethernet cable between Ethernet
+ switch and the firewall to simulate an internet failure, expecting
+ retry NTRIP client connection after delay
+
+ 2. Internet outage - Disconnect Ethernet cable between Ethernet
+ switch and the firewall to simulate an internet failure, expecting
+ retries to exceed the connection limit causing the NTRIP client to
+ shutdown after about 2 hours. Restarting the NTRIP client may be
+ done by rebooting the RTK or by using the configuration menus to
+ turn off and on the NTRIP client.
+
+ Test Setup:
+
+ RTK Express RTK Reference Station
+ ^ ^ ^
+ WiFi | WiFi | | Ethernet cable
+ v v v
+ WiFi Access Point <-----------> Ethernet Switch
+ Ethernet ^
+ Cable | Ethernet cable
+ v
+ Internet Firewall
+ ^
+ | Ethernet cable
+ v
+ Modem
+ ^
+ |
+ v
+ Internet
+ ^
+ |
+ v
+ NTRIP Caster
+
+ Possible NTRIP Casters
+
+ * https://emlid.com/ntrip-caster/
+ * http://rtk2go.com/
+ * private SNIP NTRIP caster
+------------------------------------------------------------------------------*/
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ 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_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
+ NTRIP_CLIENT_CONNECTED: Connected to the NTRIP caster
+
+ NTRIP_CLIENT_OFF
+ | ^
+ ntripClientStart | | ntripClientShutdown()
+ v |
+ NTRIP_CLIENT_ON <--------------.
+ | |
+ | | ntripClientRestart()
+ v Fail |
+ NTRIP_CLIENT_NETWORK_STARTED ------->+
+ | ^
+ | |
+ v |
+ NTRIP_CLIENT_NETWORK_CONNECTED |
+ | |
+ | |
+ v Fail |
+ NTRIP_CLIENT_CONNECTING ---------->+
+ | ^
+ | |
+ v Fail |
+ NTRIP_CLIENT_WAIT_RESPONSE -------->+
+ | ^
+ | |
+ v Fail |
+ NTRIP_CLIENT_CONNECTED -----------'
+
+ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+
+#if COMPILE_NETWORK
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+// Size of the credentials buffer in bytes
+static const int CREDENTIALS_BUFFER_SIZE = 512;
+
+// Give up connecting after this number of attempts
+// Connection attempts are throttled to increase the time between attempts
+// 30 attempts with 15 second increases will take almost two hours
+static const int MAX_NTRIP_CLIENT_CONNECTION_ATTEMPTS = 30;
+
+// NTRIP caster response timeout
+static const uint32_t NTRIP_CLIENT_RESPONSE_TIMEOUT = 10 * 1000; // Milliseconds
+
+// NTRIP client receive data timeout
+static const uint32_t NTRIP_CLIENT_RECEIVE_DATA_TIMEOUT = 30 * 1000; // Milliseconds
+
+// Most incoming data is around 500 bytes but may be larger
+static const int RTCM_DATA_SIZE = 512 * 4;
+
+// NTRIP client server request buffer size
+static const int SERVER_BUFFER_SIZE = CREDENTIALS_BUFFER_SIZE + 3;
+
+static const int NTRIPCLIENT_MS_BETWEEN_GGA = 5000; // 5s between transmission of GGA messages, if enabled
+
+// NTRIP client connection delay before resetting the connect accempt counter
+static const int NTRIP_CLIENT_CONNECTION_TIME = 5 * 60 * 1000;
+
+// Define the NTRIP client states
+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_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
+ NTRIP_CLIENT_CONNECTED, // Connected to the NTRIP caster
+ // Insert new states here
+ NTRIP_CLIENT_STATE_MAX // Last entry in the state list
+};
+
+const char * const ntripClientStateName[] =
+{
+ "NTRIP_CLIENT_OFF",
+ "NTRIP_CLIENT_ON",
+ "NTRIP_CLIENT_NETWORK_STARTED",
+ "NTRIP_CLIENT_NETWORK_CONNECTED",
+ "NTRIP_CLIENT_CONNECTING",
+ "NTRIP_CLIENT_WAIT_RESPONSE",
+ "NTRIP_CLIENT_CONNECTED"
+};
+
+const int ntripClientStateNameEntries = sizeof(ntripClientStateName) / sizeof(ntripClientStateName[0]);
+
+const RtkMode_t ntripClientMode = RTK_MODE_ROVER
+ | RTK_MODE_BASE_SURVEY_IN;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+// The network connection to the NTRIP caster to obtain RTCM data.
+static NetworkClient *ntripClient;
+static volatile uint8_t ntripClientState = NTRIP_CLIENT_OFF;
+
+// 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 int ntripClientConnectionAttempts; // Count the number of connection attempts between restarts
+static uint32_t ntripClientConnectionAttemptTimeout;
+static int ntripClientConnectionAttemptsTotal; // Count the number of connection attempts absolutely
+
+// NTRIP client timer usage:
+// * Reconnection delay
+// * Measure the connection response time
+// * Receive NTRIP data timeout
+static uint32_t ntripClientTimer;
+static uint32_t ntripClientStartTime; // For calculating uptime
+
+// Throttle GGA transmission to Caster to 1 report every 5 seconds
+unsigned long lastGGAPush;
+
+//----------------------------------------
+// NTRIP Client Routines
+//----------------------------------------
+
+bool ntripClientConnect()
+{
+ if (!ntripClient)
+ return false;
+
+ // Remove any http:// or https:// prefix from host name
+ char hostname[51];
+ strncpy(hostname, settings.ntripClient_CasterHost,
+ sizeof(hostname) - 1); // strtok modifies string to be parsed so we create a copy
+ char *token = strtok(hostname, "//");
+ if (token != nullptr)
+ {
+ token = strtok(nullptr, "//"); // Advance to data after //
+ if (token != nullptr)
+ strcpy(settings.ntripClient_CasterHost, token);
+ }
+
+ if (settings.debugNtripClientState)
+ systemPrintf("NTRIP Client connecting to %s:%d\r\n",
+ settings.ntripClient_CasterHost,
+ settings.ntripClient_CasterPort);
+
+ int connectResponse = ntripClient->connect(settings.ntripClient_CasterHost, settings.ntripClient_CasterPort);
+
+ if (connectResponse < 1)
+ {
+ if (settings.debugNtripClientState)
+ systemPrintf("NTRIP Client connection to NTRIP caster %s:%d failed\r\n",
+ settings.ntripClient_CasterHost,
+ settings.ntripClient_CasterPort);
+ return false;
+ }
+
+ // Set up the server request (GET)
+ char serverRequest[SERVER_BUFFER_SIZE];
+ int length;
+ snprintf(serverRequest, SERVER_BUFFER_SIZE, "GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun_RTK_%s_",
+ settings.ntripClient_MountPoint, platformPrefix);
+ length = strlen(serverRequest);
+ getFirmwareVersion(&serverRequest[length], SERVER_BUFFER_SIZE - 2 - length, false);
+ length = strlen(serverRequest);
+ serverRequest[length++] = '\r';
+ serverRequest[length++] = '\n';
+ serverRequest[length++] = 0;
+
+ // Set up the credentials
+ char credentials[CREDENTIALS_BUFFER_SIZE];
+ if (strlen(settings.ntripClient_CasterUser) == 0)
+ {
+ strncpy(credentials, "Accept: */*\r\nConnection: close\r\n", sizeof(credentials) - 1);
+ }
+ else
+ {
+ // Pass base64 encoded user:pw
+ char userCredentials[sizeof(settings.ntripClient_CasterUser) + sizeof(settings.ntripClient_CasterUserPW) +
+ 1]; // The ':' takes up a spot
+ snprintf(userCredentials, sizeof(userCredentials), "%s:%s", settings.ntripClient_CasterUser,
+ settings.ntripClient_CasterUserPW);
+
+ if (settings.debugNtripClientState)
+ {
+ systemPrint("NTRIP Client sending credentials: ");
+ systemPrintln(userCredentials);
+ }
+
+ // Encode with ESP32 built-in library
+ base64 b;
+ String strEncodedCredentials = b.encode(userCredentials);
+ char encodedCredentials[strEncodedCredentials.length() + 1];
+ strEncodedCredentials.toCharArray(encodedCredentials,
+ sizeof(encodedCredentials)); // Convert String to char array
+
+ snprintf(credentials, sizeof(credentials), "Authorization: Basic %s\r\n", encodedCredentials);
+ }
+
+ // Add the encoded credentials to the server request
+ strncat(serverRequest, credentials, SERVER_BUFFER_SIZE - 1);
+ strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE - 1);
+
+ if (settings.debugNtripClientState)
+ {
+ systemPrint("NTRIP Client serverRequest size: ");
+ systemPrint(strlen(serverRequest));
+ systemPrint(" of ");
+ systemPrint(sizeof(serverRequest));
+ systemPrintln(" bytes available");
+ systemPrintln("NTRIP Client sending server request: ");
+ systemPrintln(serverRequest);
+ }
+
+ // Send the server request
+ ntripClient->write((const uint8_t *)serverRequest, strlen(serverRequest));
+ ntripClientTimer = millis();
+ return true;
+}
+
+// Determine if another connection is possible or if the limit has been reached
+bool ntripClientConnectLimitReached()
+{
+ int seconds;
+
+ // Retry the connection a few times
+ bool limitReached = (ntripClientConnectionAttempts >= MAX_NTRIP_CLIENT_CONNECTION_ATTEMPTS);
+
+ // Attempt to restart the network if possible
+ if (settings.enableNtripClient && (!limitReached))
+ networkRestart(NETWORK_USER_NTRIP_CLIENT);
+
+ // Restart the NTRIP client
+ ntripClientStop(limitReached || (!settings.enableNtripClient));
+
+ ntripClientConnectionAttempts++;
+ ntripClientConnectionAttemptsTotal++;
+ if (settings.debugNtripClientState)
+ ntripClientPrintStatus();
+
+ if (limitReached == false)
+ {
+ if (ntripClientConnectionAttempts == 1)
+ ntripClientConnectionAttemptTimeout = 15 * 1000L; // Wait 15s
+ else if (ntripClientConnectionAttempts == 2)
+ ntripClientConnectionAttemptTimeout = 30 * 1000L; // Wait 30s
+ else if (ntripClientConnectionAttempts == 3)
+ ntripClientConnectionAttemptTimeout = 1 * 60 * 1000L; // Wait 1 minute
+ else if (ntripClientConnectionAttempts == 4)
+ ntripClientConnectionAttemptTimeout = 2 * 60 * 1000L; // Wait 2 minutes
+ else
+ ntripClientConnectionAttemptTimeout =
+ (ntripClientConnectionAttempts - 4) * 5 * 60 * 1000L; // Wait 5, 10, 15, etc minutes between attempts
+
+ // Display the delay before starting the NTRIP client
+ if (settings.debugNtripClientState && ntripClientConnectionAttemptTimeout)
+ {
+ seconds = ntripClientConnectionAttemptTimeout / 1000;
+ if (seconds < 120)
+ systemPrintf("NTRIP Client trying again in %d seconds.\r\n", seconds);
+ else
+ systemPrintf("NTRIP Client trying again in %d minutes.\r\n", seconds / 60);
+ }
+ }
+ else
+ // No more connection attempts, switching to Bluetooth
+ systemPrintln("NTRIP Client connection attempts exceeded!");
+ return limitReached;
+}
+
+// Print the NTRIP client state summary
+void ntripClientPrintStateSummary()
+{
+ switch (ntripClientState)
+ {
+ default:
+ systemPrintf("Unknown: %d", ntripClientState);
+ break;
+ case NTRIP_CLIENT_OFF:
+ systemPrint("Disconnected");
+ break;
+ case NTRIP_CLIENT_ON:
+ case NTRIP_CLIENT_NETWORK_STARTED:
+ case NTRIP_CLIENT_NETWORK_CONNECTED:
+ case NTRIP_CLIENT_CONNECTING:
+ case NTRIP_CLIENT_WAIT_RESPONSE:
+ systemPrint("Connecting");
+ break;
+ case NTRIP_CLIENT_CONNECTED:
+ systemPrint("Connected");
+ break;
+ }
+}
+
+// Print the NTRIP Client status
+void ntripClientPrintStatus()
+{
+ uint32_t days;
+ byte hours;
+ uint64_t milliseconds;
+ byte minutes;
+ byte seconds;
+
+ // Display NTRIP Client status and uptime
+ if (settings.enableNtripClient &&
+ ((systemState >= STATE_ROVER_NOT_STARTED) && (systemState <= STATE_ROVER_RTK_FIX)))
+ {
+ systemPrint("NTRIP Client ");
+ ntripClientPrintStateSummary();
+ systemPrintf(" - %s/%s:%d", settings.ntripClient_CasterHost,
+ settings.ntripClient_MountPoint, settings.ntripClient_CasterPort);
+
+ if (ntripClientState == NTRIP_CLIENT_CONNECTED)
+ // Use ntripClientTimer since it gets reset after each successful data
+ // receiption from the NTRIP caster
+ milliseconds = ntripClientTimer - ntripClientStartTime;
+ else
+ {
+ milliseconds = ntripClientStartTime;
+ systemPrint(" Last");
+ }
+
+ // Display the uptime
+ days = milliseconds / MILLISECONDS_IN_A_DAY;
+ milliseconds %= MILLISECONDS_IN_A_DAY;
+
+ hours = milliseconds / MILLISECONDS_IN_AN_HOUR;
+ milliseconds %= MILLISECONDS_IN_AN_HOUR;
+
+ minutes = milliseconds / MILLISECONDS_IN_A_MINUTE;
+ milliseconds %= MILLISECONDS_IN_A_MINUTE;
+
+ seconds = milliseconds / MILLISECONDS_IN_A_SECOND;
+ milliseconds %= MILLISECONDS_IN_A_SECOND;
+
+ systemPrint(" Uptime: ");
+ systemPrintf("%d %02d:%02d:%02d.%03lld (Reconnects: %d)\r\n",
+ days, hours, minutes, seconds, milliseconds, ntripClientConnectionAttemptsTotal);
+ }
+}
+
+// Determine if NTRIP client data is available
+int ntripClientReceiveDataAvailable()
+{
+ return ntripClient->available();
+}
+
+// Read the response from the NTRIP client
+void ntripClientResponse(char *response, size_t maxLength)
+{
+ char *responseEnd;
+
+ // Make sure that we can zero terminate the response
+ responseEnd = &response[maxLength - 1];
+
+ // Read bytes from the caster and store them
+ while ((response < responseEnd) && (ntripClientReceiveDataAvailable() > 0))
+ {
+ *response++ = ntripClient->read();
+ }
+
+ // Zero terminate the response
+ *response = '\0';
+}
+
+// Restart the NTRIP client
+void ntripClientRestart()
+{
+ // Save the previous uptime value
+ if (ntripClientState == NTRIP_CLIENT_CONNECTED)
+ ntripClientStartTime = ntripClientTimer - ntripClientStartTime;
+ ntripClientConnectLimitReached();
+}
+
+// Update the state of the NTRIP client state machine
+// PERIODIC_DISPLAY(PD_NTRIP_CLIENT_STATE) is handled by ntripClientUpdate
+void ntripClientSetState(uint8_t newState)
+{
+ if (settings.debugNtripClientState)
+ {
+ if (ntripClientState == newState)
+ systemPrint("NTRIP client: *");
+ else
+ systemPrintf("NTRIP client: %s --> ", ntripClientStateName[ntripClientState]);
+ }
+ ntripClientState = newState;
+ if (settings.debugNtripClientState)
+ {
+ if (ntripClientState >= NTRIP_CLIENT_STATE_MAX)
+ {
+ systemPrintf("Unknown client state %d\r\n", ntripClientState);
+ reportFatalError("Unknown NTRIP Client state");
+ }
+ else
+ systemPrintln(ntripClientStateName[ntripClientState]);
+ }
+}
+
+// Shutdown the NTRIP client
+void ntripClientShutdown()
+{
+ ntripClientStop(true);
+}
+
+// Start the NTRIP client
+void ntripClientStart()
+{
+ // Display the heap state
+ reportHeapNow(settings.debugNtripClientState);
+
+ // Start the NTRIP client
+ systemPrintln("NTRIP Client start");
+ ntripClientStop(false);
+}
+
+// Shutdown or restart the NTRIP client
+void ntripClientStop(bool shutdown)
+{
+ if (ntripClient)
+ {
+ // Break the NTRIP client connection if necessary
+ if (ntripClient->connected())
+ ntripClient->stop();
+
+ // Free the NTRIP client resources
+ delete ntripClient;
+ ntripClient = nullptr;
+ reportHeapNow(settings.debugNtripClientState);
+ }
+
+ // Increase timeouts if we started the network
+ if (ntripClientState > NTRIP_CLIENT_ON)
+ {
+ // Mark the Client stop so that we don't immediately attempt re-connect to Caster
+ ntripClientTimer = millis();
+
+ // Done with the network
+ if (networkGetUserNetwork(NETWORK_USER_NTRIP_CLIENT))
+ networkUserClose(NETWORK_USER_NTRIP_CLIENT);
+ }
+
+ // Return the Main Talker ID to "GN".
+ if (online.gnss)
+ {
+ theGNSS.setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA
+ theGNSS.setNMEAGPGGAcallbackPtr(nullptr); // Remove callback
+ }
+
+ // Determine the next NTRIP client state
+ online.ntripClient = false;
+ netIncomingRTCM = false;
+ if (shutdown)
+ {
+ ntripClientSetState(NTRIP_CLIENT_OFF);
+ settings.enableNtripClient = false;
+ ntripClientConnectionAttempts = 0;
+ ntripClientConnectionAttemptTimeout = 0;
+ }
+ else
+ ntripClientSetState(NTRIP_CLIENT_ON);
+}
+
+// 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 maxTimeBeforeHangup_ms
+void ntripClientUpdate()
+{
+ // Shutdown the NTRIP client when the mode or setting changes
+ DMW_st(ntripClientSetState, ntripClientState);
+ if (NEQ_RTK_MODE(ntripClientMode) || (!settings.enableNtripClient))
+ {
+ if (ntripClientState > NTRIP_CLIENT_OFF)
+ {
+ ntripClientStop(true);
+ ntripClientConnectionAttempts = 0;
+ ntripClientConnectionAttemptTimeout = 0;
+ ntripClientSetState(NTRIP_CLIENT_OFF);
+ }
+ }
+
+ // Enable the network and the NTRIP client if requested
+ switch (ntripClientState)
+ {
+ case NTRIP_CLIENT_OFF:
+ if (EQ_RTK_MODE(ntripClientMode) && settings.enableNtripClient)
+ ntripClientStart();
+ break;
+
+ // Start the network
+ case NTRIP_CLIENT_ON:
+ if (networkUserOpen(NETWORK_USER_NTRIP_CLIENT, NETWORK_TYPE_ACTIVE))
+ ntripClientSetState(NTRIP_CLIENT_NETWORK_STARTED);
+ break;
+
+ // Wait for a network media connection
+ case NTRIP_CLIENT_NETWORK_STARTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripClientStop(true); // Note: was ntripClientRestart();
+
+ // Determine if the network is connected to the media
+ else if (networkUserConnected(NETWORK_USER_NTRIP_CLIENT))
+ {
+ // Allocate the ntripClient structure
+ ntripClient = new NetworkClient(NETWORK_USER_NTRIP_CLIENT);
+ if (!ntripClient)
+ {
+ // Failed to allocate the ntripClient structure
+ systemPrintln("ERROR: Failed to allocate the ntripClient structure!");
+ ntripClientShutdown();
+ }
+ else
+ {
+ reportHeapNow(settings.debugNtripClientState);
+
+ // The network is available for the NTRIP client
+ ntripClientSetState(NTRIP_CLIENT_NETWORK_CONNECTED);
+ }
+ }
+ break;
+
+ case NTRIP_CLIENT_NETWORK_CONNECTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripClientStop(true); // Note: was ntripClientRestart();
+
+ // If GGA transmission is enabled, wait for GNSS lock before connecting to NTRIP Caster
+ // If GGA transmission is not enabled, start connecting to NTRIP Caster
+ else if ((!settings.ntripClient_TransmitGGA) || (fixType >= 3) && (fixType <= 5))
+ {
+ // Delay before opening the NTRIP client connection
+ if ((millis() - ntripClientTimer) >= ntripClientConnectionAttemptTimeout)
+ {
+ // Open connection to NTRIP caster service
+ if (!ntripClientConnect())
+ {
+ // Assume service not available
+ if (ntripClientConnectLimitReached()) // Updates ntripClientConnectionAttemptTimeout
+ systemPrintln("NTRIP caster failed to connect. Do you have your caster address and port correct?");
+ }
+ else
+ {
+ // Socket opened to NTRIP system
+ if (settings.debugNtripClientState)
+ systemPrintf("NTRIP Client waiting for response from %s:%d\r\n",
+ settings.ntripClient_CasterHost,
+ settings.ntripClient_CasterPort);
+ ntripClientSetState(NTRIP_CLIENT_WAIT_RESPONSE);
+ }
+ }
+ }
+ break;
+
+ case NTRIP_CLIENT_WAIT_RESPONSE:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripClientStop(true); // Note: was ntripClientRestart();
+
+ // Check for no response from the caster service
+ else if (ntripClientReceiveDataAvailable() <
+ strlen("ICY 200 OK")) // Wait until at least a few bytes have arrived
+ {
+ // Check for response timeout
+ if (millis() - ntripClientTimer > NTRIP_CLIENT_RESPONSE_TIMEOUT)
+ {
+ // NTRIP web service did not respond
+ if (ntripClientConnectLimitReached()) // Updates ntripClientConnectionAttemptTimeout
+ systemPrintln("NTRIP Caster failed to respond. Do you have your caster address and port correct?");
+ }
+ }
+ else
+ {
+ // Caster web service responded
+ char response[512];
+ ntripClientResponse(&response[0], sizeof(response));
+
+ if (settings.debugNtripClientState)
+ systemPrintf("Caster Response: %s\r\n", response);
+ else
+ log_d("Caster Response: %s", response);
+
+ // Look for various responses
+ if (strstr(response, "200") != nullptr) //'200' found
+ {
+ // We got a response, now check it for possible errors
+ if (strcasestr(response, "banned") != nullptr)
+ {
+ systemPrintf("NTRIP Client connected to caster but caster responded with banned error: %s\r\n",
+ response);
+
+ ntripClientConnectLimitReached(); //Re-attempted after a period of time. Shuts down NTRIP Client if limit reached.
+ }
+ else if (strcasestr(response, "sandbox") != nullptr)
+ {
+ systemPrintf("NTRIP Client connected to caster but caster responded with sandbox error: %s\r\n",
+ response);
+
+ ntripClientConnectLimitReached(); //Re-attempted after a period of time. Shuts down NTRIP Client if limit reached.
+ }
+ else if (strcasestr(response, "SOURCETABLE") != nullptr)
+ {
+ systemPrintf("Caster may not have mountpoint %s. Caster responded with problem: %s\r\n",
+ settings.ntripClient_MountPoint, response);
+
+ // Stop NTRIP client operations
+ ntripClientShutdown();
+ }
+ else
+ {
+ // We successfully connected
+ // Timeout receiving NTRIP data, retry the NTRIP client connection
+ if (online.rtc && online.gnss)
+ {
+ int hours;
+ int minutes;
+ int seconds;
+
+ seconds = rtc.getLocalEpoch() % SECONDS_IN_A_DAY;
+ hours = seconds / SECONDS_IN_AN_HOUR;
+ seconds -= hours * SECONDS_IN_AN_HOUR;
+ minutes = seconds / SECONDS_IN_A_MINUTE;
+ seconds -= minutes * SECONDS_IN_A_MINUTE;
+ systemPrintf("NTRIP Client connected to %s:%d at %d:%02d:%02d\r\n",
+ settings.ntripClient_CasterHost, settings.ntripClient_CasterPort, hours, minutes,
+ seconds);
+ }
+ else
+ systemPrintf("NTRIP Client connected to %s:%d\r\n", settings.ntripClient_CasterHost,
+ settings.ntripClient_CasterPort);
+
+ // Connection is now open, start the NTRIP receive data timer
+ ntripClientTimer = millis();
+
+ if (settings.ntripClient_TransmitGGA == true)
+ {
+ // Set the Main Talker ID to "GP". The NMEA GGA messages will be GPGGA instead of GNGGA
+ theGNSS.setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 1);
+ theGNSS.setNMEAGPGGAcallbackPtr(&pushGPGGA); // Set up the callback for GPGGA
+
+ float measurementFrequency = (1000.0 / settings.measurementRate) / 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);
+ theGNSS.setVal8(
+ UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C,
+ measurementFrequency); // Enable GGA over I2C. Tell the module to output GGA every second
+
+ lastGGAPush =
+ millis() - NTRIPCLIENT_MS_BETWEEN_GGA; // Force immediate transmission of GGA message
+ }
+
+ // We don't use a task because we use I2C hardware (and don't have a semphore).
+ online.ntripClient = true;
+ ntripClientStartTime = millis();
+ ntripClientSetState(NTRIP_CLIENT_CONNECTED);
+ }
+ }
+ else if (strstr(response, "401") != nullptr)
+ {
+ // Look for '401 Unauthorized'
+ systemPrintf(
+ "NTRIP Caster responded with unauthorized error: %s. Are you sure your caster credentials are correct?\r\n",
+ response);
+
+ // Stop NTRIP client operations
+ ntripClientShutdown();
+ }
+ // Other errors returned by the caster
+ else
+ {
+ systemPrintf("NTRIP Client connected but caster responded with problem: %s\r\n", response);
+
+ // Stop NTRIP client operations
+ ntripClientShutdown();
+ }
+ }
+ break;
+
+ case NTRIP_CLIENT_CONNECTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripClientStop(true); // Note: was ntripClientRestart();
+
+ // Check for a broken connection
+ else if (!ntripClient->connected())
+ {
+ // Broken connection, retry the NTRIP client connection
+ systemPrintln("NTRIP Client connection to caster was broken");
+ ntripClientRestart();
+ }
+ else
+ {
+ // Handle other types of NTRIP connection failures to prevent
+ // hammering the NTRIP caster with rapid connection attempts.
+ // A fast reconnect is reasonable after a long NTRIP caster
+ // connection. However increasing backoff delays should be
+ // added when the NTRIP caster fails after a short connection
+ // interval.
+ if (((millis() - ntripClientStartTime) > NTRIP_CLIENT_CONNECTION_TIME)
+ && (ntripClientConnectionAttempts || ntripClientConnectionAttemptTimeout))
+ {
+ // After a long connection period, reset the attempt counter
+ ntripClientConnectionAttempts = 0;
+ ntripClientConnectionAttemptTimeout = 0;
+ if (settings.debugNtripClientState)
+ systemPrintln("NTRIP Client resetting connection attempt counter and timeout");
+ }
+
+ // Check for timeout receiving NTRIP data
+ if (ntripClientReceiveDataAvailable() == 0)
+ {
+ // Don't fail during retransmission attempts
+ if ((millis() - ntripClientTimer) > NTRIP_CLIENT_RECEIVE_DATA_TIMEOUT)
+ {
+ // Timeout receiving NTRIP data, retry the NTRIP client connection
+ if (online.rtc && online.gnss)
+ {
+ int hours;
+ int minutes;
+ int seconds;
+
+ seconds = rtc.getLocalEpoch() % SECONDS_IN_A_DAY;
+ hours = seconds / SECONDS_IN_AN_HOUR;
+ seconds -= hours * SECONDS_IN_AN_HOUR;
+ minutes = seconds / SECONDS_IN_A_MINUTE;
+ seconds -= minutes * SECONDS_IN_A_MINUTE;
+ systemPrintf("NTRIP Client timeout receiving data at %d:%02d:%02d\r\n",
+ hours, minutes, seconds);
+ }
+ else
+ systemPrintln("NTRIP Client timeout receiving data");
+ ntripClientRestart();
+ }
+ }
+ else
+ {
+ // Receive data from the NTRIP Caster
+ uint8_t rtcmData[RTCM_DATA_SIZE];
+ size_t rtcmCount = 0;
+
+ // Collect any available RTCM data
+ if (ntripClientReceiveDataAvailable() > 0)
+ {
+ rtcmCount = ntripClient->read(rtcmData, sizeof(rtcmData));
+ if (rtcmCount)
+ {
+ // Restart the NTRIP receive data timer
+ ntripClientTimer = millis();
+
+ // Record the arrival of RTCM from the WiFi connection. This resets the RTCM timeout used on the L-Band.
+ rtcmLastPacketReceived = millis();
+
+ // Push RTCM to GNSS module over I2C / SPI
+ theGNSS.pushRawData(rtcmData, rtcmCount);
+ netIncomingRTCM = true;
+
+ if ((settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA))
+ && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_NTRIP_CLIENT_DATA);
+ systemPrintf("NTRIP Client received %d RTCM bytes, pushed to ZED\r\n", rtcmCount);
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ // Periodically display the NTRIP client state
+ if (PERIODIC_DISPLAY(PD_NTRIP_CLIENT_STATE))
+ {
+ systemPrintf("NTRIP client state: %s\r\n", ntripClientStateName[ntripClientState]);
+ PERIODIC_CLEAR(PD_NTRIP_CLIENT_STATE);
+ }
+}
+
+// Verify the NTRIP client tables
+void ntripClientValidateTables()
+{
+ if (ntripClientStateNameEntries != NTRIP_CLIENT_STATE_MAX)
+ reportFatalError("Fix ntripClientStateNameEntries to match NTRIPClientState");
+}
+
+void pushGPGGA(NMEA_GGA_data_t *nmeaData)
+{
+ // 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
diff --git a/Firmware/RTK_Surveyor/NtripServer.ino b/Firmware/RTK_Surveyor/NtripServer.ino
new file mode 100644
index 000000000..32a133d2a
--- /dev/null
+++ b/Firmware/RTK_Surveyor/NtripServer.ino
@@ -0,0 +1,856 @@
+/*------------------------------------------------------------------------------
+NtripServer.ino
+
+ The NTRIP server sits on top of the network layer and sends correction data
+ from the ZED (GNSS radio) to an NTRIP caster.
+
+ Satellite ... Satellite
+ | | |
+ | | |
+ | V |
+ | RTK |
+ '------> Base <------'
+ Station
+ |
+ | NTRIP Server sends correction data
+ V
+ NTRIP Caster
+ |
+ | NTRIP Client receives correction data
+ V
+ Bluetooth RTK Network: NMEA Client
+ .---------------- Rover --------------------------.
+ | | |
+ | NMEA | Network: NEMA Server | NMEA
+ | position | NEMA position data | position
+ | data V | data
+ | Computer or |
+ '------------> Cell Phone <-----------------------'
+ for display
+
+ NTRIP Server Testing:
+
+ Using Ethernet on RTK Reference Station:
+
+ 1. Network failure - Disconnect Ethernet cable at RTK Reference Station,
+ expecting retry NTRIP server connection after network restarts
+
+ Using WiFi on RTK Express or RTK Reference Station:
+
+ 1. Internet link failure - Disconnect Ethernet cable between Ethernet
+ switch and the firewall to simulate an internet failure, expecting
+ retry NTRIP server connection after delay
+
+ 2. Internet outage - Disconnect Ethernet cable between Ethernet
+ switch and the firewall to simulate an internet failure, expecting
+ retries to exceed the connection limit causing the NTRIP server to
+ shutdown after about 25 hours and 18 minutes. Restarting the NTRIP
+ server may be done by rebooting the RTK or by using the configuration
+ menus to turn off and on the NTRIP server.
+
+ Test Setup:
+
+ RTK Reference Station
+ ^ ^
+ WiFi | | Ethernet cable
+ v v
+ WiFi Access Point <-----------> Ethernet Switch
+ Ethernet ^
+ Cable | Ethernet cable
+ v
+ Internet Firewall
+ ^
+ | Ethernet cable
+ v
+ Modem
+ ^
+ |
+ v
+ Internet
+ ^
+ |
+ v
+ NTRIP Caster
+
+ Possible NTRIP Casters
+
+ * https://emlid.com/ntrip-caster/
+ * http://rtk2go.com/
+ * private SNIP NTRIP caster
+------------------------------------------------------------------------------*/
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ 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_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
+ NTRIP_SERVER_AUTHORIZATION: Validate the credentials
+ NTRIP_SERVER_CASTING: Sending correction data to the NTRIP caster
+
+ NTRIP_SERVER_OFF
+ | ^
+ ntripServerStart | | ntripServerShutdown()
+ v |
+ .---------> NTRIP_SERVER_ON <-------------------.
+ | | |
+ | | | ntripServerRestart()
+ | v Fail |
+ | NTRIP_SERVER_NETWORK_STARTED ------------->+
+ | | ^
+ | | |
+ | v Fail |
+ | NTRIP_SERVER_NETWORK_CONNECTED ----------->+
+ | | ^
+ | | Network |
+ | v Fail |
+ | NTRIP_SERVER_WAIT_GNSS_DATA -------------->+
+ | | ^
+ | | Discard Data Network |
+ | v Fail |
+ | NTRIP_SERVER_CONNECTING ---------------->+
+ | | ^
+ | | Discard Data Network |
+ | v Fail |
+ | NTRIP_SERVER_AUTHORIZATION -------------->+
+ | | ^
+ | | Discard Data Network |
+ | v Fail |
+ | NTRIP_SERVER_CASTING -----------------'
+ | |
+ | | Data timeout
+ | |
+ | | Close Server connection
+ '------------------'
+
+ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+
+#if COMPILE_NETWORK
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+// Give up connecting after this number of attempts
+// Connection attempts are throttled by increasing the time between attempts by
+// 5 minutes. The NTRIP server stops retrying after 25 hours and 18 minutes
+static const int MAX_NTRIP_SERVER_CONNECTION_ATTEMPTS = 28;
+
+// NTRIP server connection delay before resetting the connect accempt counter
+static const int NTRIP_SERVER_CONNECTION_TIME = 5 * 60 * 1000;
+
+// Define the NTRIP server states
+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_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
+ NTRIP_SERVER_AUTHORIZATION, // Validate the credentials
+ NTRIP_SERVER_CASTING, // Sending correction data to the NTRIP caster
+ // Insert new states here
+ NTRIP_SERVER_STATE_MAX // Last entry in the state list
+};
+
+const char * const ntripServerStateName[] =
+{
+ "NTRIP_SERVER_OFF",
+ "NTRIP_SERVER_ON",
+ "NTRIP_SERVER_NETWORK_STARTED",
+ "NTRIP_SERVER_NETWORK_CONNECTED",
+ "NTRIP_SERVER_WAIT_GNSS_DATA",
+ "NTRIP_SERVER_CONNECTING",
+ "NTRIP_SERVER_AUTHORIZATION",
+ "NTRIP_SERVER_CASTING"
+};
+
+const int ntripServerStateNameEntries = sizeof(ntripServerStateName) / sizeof(ntripServerStateName[0]);
+
+const RtkMode_t ntripServerMode = RTK_MODE_BASE_FIXED;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+// NTRIP Servers
+static NTRIP_SERVER_DATA ntripServerArray[NTRIP_SERVER_MAX];
+
+//----------------------------------------
+// NTRIP Server Routines
+//----------------------------------------
+
+// Initiate a connection to the NTRIP caster
+bool ntripServerConnectCaster(int serverIndex)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+ const int SERVER_BUFFER_SIZE = 512;
+ char serverBuffer[SERVER_BUFFER_SIZE];
+
+ // Remove any http:// or https:// prefix from host name
+ char hostname[51];
+ strncpy(hostname, settings.ntripServer_CasterHost[serverIndex],
+ sizeof(hostname) - 1); // strtok modifies string to be parsed so we create a copy
+ char *token = strtok(hostname, "//");
+ if (token != nullptr)
+ {
+ token = strtok(nullptr, "//"); // Advance to data after //
+ if (token != nullptr)
+ strcpy(settings.ntripServer_CasterHost[serverIndex], token);
+ }
+
+ if (settings.debugNtripServerState)
+ systemPrintf("NTRIP Server %d connecting to %s:%d\r\n", serverIndex,
+ settings.ntripServer_CasterHost[serverIndex],
+ settings.ntripServer_CasterPort[serverIndex]);
+
+ // Attempt a connection to the NTRIP caster
+ if (!ntripServer->networkClient->connect(settings.ntripServer_CasterHost[serverIndex],
+ settings.ntripServer_CasterPort[serverIndex]))
+ {
+ if (settings.debugNtripServerState)
+ systemPrintf("NTRIP Server %d connection to NTRIP caster %s:%d failed\r\n",
+ serverIndex,
+ settings.ntripServer_CasterHost[serverIndex],
+ settings.ntripServer_CasterPort[serverIndex]);
+ return false;
+ }
+
+ if (settings.debugNtripServerState)
+ systemPrintf("NTRIP Server %d sending authorization credentials\r\n", serverIndex);
+
+ // Build the authorization credentials message
+ // * Mount point
+ // * Password
+ // * Agent
+ snprintf(serverBuffer, SERVER_BUFFER_SIZE, "SOURCE %s /%s\r\nSource-Agent: NTRIP SparkFun_RTK_%s/\r\n\r\n",
+ settings.ntripServer_MountPointPW[serverIndex],
+ settings.ntripServer_MountPoint[serverIndex], platformPrefix);
+ int length = strlen(serverBuffer);
+ getFirmwareVersion(&serverBuffer[length], sizeof(serverBuffer) - length, false);
+
+ // Send the authorization credentials to the NTRIP caster
+ ntripServer->networkClient->write((const uint8_t *)serverBuffer, strlen(serverBuffer));
+ return true;
+}
+
+// Determine if the connection limit has been reached
+bool ntripServerConnectLimitReached(int serverIndex)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+ int seconds;
+
+ // Retry the connection a few times
+ bool limitReached = (ntripServer->connectionAttempts >= MAX_NTRIP_SERVER_CONNECTION_ATTEMPTS);
+
+ // Attempt to restart the network if possible
+ if (settings.enableNtripServer && (!limitReached))
+ networkRestart(NETWORK_USER_NTRIP_SERVER + serverIndex);
+
+ ntripServerStop(serverIndex, limitReached || (!settings.enableNtripServer));
+
+ ntripServer->connectionAttempts++;
+ ntripServer->connectionAttemptsTotal++;
+ if (settings.debugNtripServerState)
+ ntripServerPrintStatus(serverIndex);
+
+ if (limitReached == false)
+ {
+ if (ntripServer->connectionAttempts == 1)
+ ntripServer->connectionAttemptTimeout = 15 * 1000L; // Wait 15s
+ else if (ntripServer->connectionAttempts == 2)
+ ntripServer->connectionAttemptTimeout = 30 * 1000L; // Wait 30s
+ else if (ntripServer->connectionAttempts == 3)
+ ntripServer->connectionAttemptTimeout = 1 * 60 * 1000L; // Wait 1 minute
+ else if (ntripServer->connectionAttempts == 4)
+ ntripServer->connectionAttemptTimeout = 2 * 60 * 1000L; // Wait 2 minutes
+ else
+ ntripServer->connectionAttemptTimeout =
+ (ntripServer->connectionAttempts - 4) * 5 * 60 * 1000L; // Wait 5, 10, 15, etc minutes between attempts
+
+ // Display the delay before starting the NTRIP server
+ if (settings.debugNtripServerState && ntripServer->connectionAttemptTimeout)
+ {
+ seconds = ntripServer->connectionAttemptTimeout / 1000;
+ if (seconds < 120)
+ systemPrintf("NTRIP Server %d trying again in %d seconds.\r\n", serverIndex, seconds);
+ else
+ systemPrintf("NTRIP Server %d trying again in %d minutes.\r\n", serverIndex, seconds / 60);
+ }
+ }
+ else
+ // No more connection attempts
+ systemPrintf("NTRIP Server %d connection attempts exceeded!\r\n", serverIndex);
+ return limitReached;
+}
+
+// Print the NTRIP server state summary
+void ntripServerPrintStateSummary(int serverIndex)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+
+ switch (ntripServer->state)
+ {
+ default:
+ systemPrintf("Unknown: %d", ntripServer->state);
+ break;
+ case NTRIP_SERVER_OFF:
+ systemPrint("Disconnected");
+ break;
+ case NTRIP_SERVER_ON:
+ case NTRIP_SERVER_NETWORK_STARTED:
+ case NTRIP_SERVER_NETWORK_CONNECTED:
+ case NTRIP_SERVER_WAIT_GNSS_DATA:
+ case NTRIP_SERVER_CONNECTING:
+ case NTRIP_SERVER_AUTHORIZATION:
+ systemPrint("Connecting");
+ break;
+ case NTRIP_SERVER_CASTING:
+ systemPrint("Connected");
+ break;
+ }
+}
+
+// Print the NTRIP server status
+void ntripServerPrintStatus (int serverIndex)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+ uint64_t milliseconds;
+ uint32_t days;
+ byte hours;
+ byte minutes;
+ byte seconds;
+
+ if (settings.enableNtripServer == true &&
+ (systemState >= STATE_BASE_NOT_STARTED && systemState <= STATE_BASE_FIXED_TRANSMITTING))
+ {
+ systemPrintf("NTRIP Server %d ", serverIndex);
+ ntripServerPrintStateSummary(serverIndex);
+ systemPrintf(" - %s/%s:%d", settings.ntripServer_CasterHost[serverIndex],
+ settings.ntripServer_MountPoint[serverIndex],
+ settings.ntripServer_CasterPort[serverIndex]);
+
+ if (ntripServer->state == NTRIP_SERVER_CASTING)
+ // Use ntripServer->timer since it gets reset after each successful data
+ // receiption from the NTRIP caster
+ milliseconds = ntripServer->timer - ntripServer->startTime;
+ else
+ {
+ milliseconds = ntripServer->startTime;
+ systemPrint(" Last");
+ }
+
+ // Display the uptime
+ days = milliseconds / MILLISECONDS_IN_A_DAY;
+ milliseconds %= MILLISECONDS_IN_A_DAY;
+
+ hours = milliseconds / MILLISECONDS_IN_AN_HOUR;
+ milliseconds %= MILLISECONDS_IN_AN_HOUR;
+
+ minutes = milliseconds / MILLISECONDS_IN_A_MINUTE;
+ milliseconds %= MILLISECONDS_IN_A_MINUTE;
+
+ seconds = milliseconds / MILLISECONDS_IN_A_SECOND;
+ milliseconds %= MILLISECONDS_IN_A_SECOND;
+
+ systemPrint(" Uptime: ");
+ systemPrintf("%d %02d:%02d:%02d.%03lld (Reconnects: %d)\r\n",
+ days, hours, minutes, seconds, milliseconds, ntripServer->connectionAttemptsTotal);
+ }
+}
+
+// This function gets called as each RTCM byte comes in
+void ntripServerProcessRTCM(int serverIndex, uint8_t incoming)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+
+ if (ntripServer->state == NTRIP_SERVER_CASTING)
+ {
+ // Generate and print timestamp if needed
+ uint32_t currentMilliseconds;
+ if (online.rtc)
+ {
+ // Timestamp the RTCM messages
+ currentMilliseconds = millis();
+ if (((settings.debugNtripServerRtcm && ((currentMilliseconds - ntripServer->previousMilliseconds) > 5))
+ || PERIODIC_DISPLAY(PD_NTRIP_SERVER_DATA)) && (!settings.enableRtcmMessageChecking)
+ && (!inMainMenu) && ntripServer->bytesSent)
+ {
+ PERIODIC_CLEAR(PD_NTRIP_SERVER_DATA);
+ printTimeStamp();
+ // 1 2 3
+ // 123456789012345678901234567890
+ // YYYY-mm-dd HH:MM:SS.xxxrn0
+ struct tm timeinfo = rtc.getTimeStruct();
+ char timestamp[30];
+ strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &timeinfo);
+ systemPrintf(" Tx%d RTCM: %s.%03ld, %d bytes sent\r\n", serverIndex, timestamp, rtc.getMillis(), ntripServer->zedBytesSent);
+ ntripServer->zedBytesSent = 0;
+ }
+ ntripServer->previousMilliseconds = currentMilliseconds;
+ }
+
+ // If we have not gotten new RTCM bytes for a period of time, assume end of frame
+ if (((millis() - ntripServer->timer) > 100) && (ntripServer->bytesSent > 0))
+ {
+ if ((!inMainMenu) && settings.debugNtripServerState)
+ systemPrintf("NTRIP Server %d transmitted %d RTCM bytes to Caster\r\n", serverIndex, ntripServer->bytesSent);
+
+ ntripServer->bytesSent = 0;
+ }
+
+ if (ntripServer->networkClient->connected())
+ {
+ ntripServer->networkClient->write(incoming); // Send this byte to socket
+ ntripServer->bytesSent++;
+ ntripServer->zedBytesSent++;
+ ntripServer->timer = millis();
+ netOutgoingRTCM = true;
+ }
+ }
+
+ // Indicate that the GNSS is providing correction data
+ else if (ntripServer->state == NTRIP_SERVER_WAIT_GNSS_DATA)
+ {
+ ntripServerSetState(ntripServer, NTRIP_SERVER_CONNECTING);
+ rtcmParsingState = RTCM_TRANSPORT_STATE_WAIT_FOR_PREAMBLE_D3;
+ }
+}
+
+// Read the authorization response from the NTRIP caster
+void ntripServerResponse(int serverIndex, char *response, size_t maxLength)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+ char *responseEnd;
+
+ // Make sure that we can zero terminate the response
+ responseEnd = &response[maxLength - 1];
+
+ // Read bytes from the caster and store them
+ while ((response < responseEnd) && ntripServer->networkClient->available())
+ *response++ = ntripServer->networkClient->read();
+
+ // Zero terminate the response
+ *response = '\0';
+}
+
+// Restart the NTRIP server
+void ntripServerRestart(int serverIndex)
+{
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+
+ // Save the previous uptime value
+ if (ntripServer->state == NTRIP_SERVER_CASTING)
+ ntripServer->startTime = ntripServer->timer - ntripServer->startTime;
+ ntripServerConnectLimitReached(serverIndex);
+}
+
+// Update the state of the NTRIP server state machine
+void ntripServerSetState(NTRIP_SERVER_DATA * ntripServer, uint8_t newState)
+{
+ int serverIndex = -999;
+ for (int index = 0; index < NTRIP_SERVER_MAX; index++)
+ {
+ if (ntripServer == &ntripServerArray[index])
+ {
+ serverIndex = index;
+ break;
+ }
+ }
+
+ // PERIODIC_DISPLAY(PD_NTRIP_SERVER_STATE) is handled by ntripServerUpdate
+ if (settings.debugNtripServerState)
+ {
+ if (ntripServer->state == newState)
+ systemPrintf("NTRIP server %d: *", serverIndex); // If the state is not changing - print *
+ else
+ systemPrintf("NTRIP server %d: %s --> ", serverIndex, ntripServerStateName[ntripServer->state]);
+ }
+ ntripServer->state = newState;
+ if (settings.debugNtripServerState)
+ {
+ if (ntripServer->state >= NTRIP_SERVER_STATE_MAX)
+ {
+ systemPrintf("Unknown server state %d\r\n", ntripServer->state);
+ reportFatalError("Unknown NTRIP Server state");
+ }
+ else
+ systemPrintln(ntripServerStateName[ntripServer->state]);
+ }
+}
+
+// Shutdown the NTRIP server
+void ntripServerShutdown(int serverIndex)
+{
+ ntripServerStop(serverIndex, true);
+}
+
+// Start the NTRIP server
+void ntripServerStart(int serverIndex)
+{
+ // Display the heap state
+ reportHeapNow(settings.debugNtripServerState);
+
+ // Start the NTRIP server
+ systemPrintf("NTRIP Server %d start\r\n", serverIndex);
+ ntripServerStop(serverIndex, false);
+}
+
+// Shutdown or restart the NTRIP server
+void ntripServerStop(int serverIndex, bool shutdown)
+{
+ bool enabled;
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+
+ if (ntripServer->networkClient)
+ {
+ // Break the NTRIP server connection if necessary
+ if (ntripServer->networkClient->connected())
+ ntripServer->networkClient->stop();
+
+ // Free the NTRIP server resources
+ delete ntripServer->networkClient;
+ ntripServer->networkClient = nullptr;
+ reportHeapNow(settings.debugNtripServerState);
+ }
+
+ // Increase timeouts if we started the network
+ if (ntripServer->state > NTRIP_SERVER_ON)
+ {
+ // Mark the Server stop so that we don't immediately attempt re-connect to Caster
+ ntripServer->timer = millis();
+
+ // Done with the network
+ if (networkGetUserNetwork(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ networkUserClose(NETWORK_USER_NTRIP_SERVER + serverIndex);
+ }
+
+ // Determine the next NTRIP server state
+ online.ntripServer[serverIndex] = false;
+ if (shutdown
+ || (!settings.ntripServer_CasterHost[serverIndex][0])
+ || (!settings.ntripServer_CasterPort[serverIndex])
+ || (!settings.ntripServer_MountPoint[serverIndex][0]))
+ {
+ if (shutdown)
+ {
+ if (settings.debugNtripServerState)
+ systemPrintf("ntripServerStop server %d shutdown requested\r\n", serverIndex);
+ }
+ else
+ {
+ if (settings.debugNtripServerState && (!settings.ntripServer_CasterHost[serverIndex][0]))
+ systemPrintf("ntripServerStop server %d caster host not configured!\r\n", serverIndex);
+ if (settings.debugNtripServerState && (!settings.ntripServer_CasterPort[serverIndex]))
+ systemPrintf("ntripServerStop server %d caster port not configured!\r\n", serverIndex);
+ if (settings.debugNtripServerState && (!settings.ntripServer_MountPoint[serverIndex][0]))
+ systemPrintf("ntripServerStop server %d mount point not configured!\r\n", serverIndex);
+ }
+ ntripServerSetState(ntripServer, NTRIP_SERVER_OFF);
+ ntripServer->connectionAttempts = 0;
+ ntripServer->connectionAttemptTimeout = 0;
+
+ // Determine if any of the NTRIP servers are enabled
+ enabled = false;
+ for (int index = 0; index < NTRIP_SERVER_MAX; index++)
+ if (online.ntripServer[index])
+ {
+ enabled = true;
+ break;
+ }
+ //settings.enableNtripServer = enabled;
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Why? Setting settings.enableNtripServer to false means
+ // the server connections cannot be (re)started without setting settings.enableNtripServer back
+ // to true via the menu / web config... Was the intent to close the network connection when all
+ // servers have disconnected?
+ }
+ else
+ {
+ systemPrintf("ntripServerStop server %d start requested\r\n", serverIndex);
+ ntripServerSetState(ntripServer, NTRIP_SERVER_ON);
+ }
+}
+
+// Update the NTRIP server state machine
+void ntripServerUpdate(int serverIndex)
+{
+ // Get the NTRIP data structure
+ NTRIP_SERVER_DATA * ntripServer = &ntripServerArray[serverIndex];
+
+ // For Ref Stn, process any RTCM data waiting in the u-blox library RTCM Buffer
+ // This causes the state change from NTRIP_SERVER_WAIT_GNSS_DATA to NTRIP_SERVER_CONNECTING
+ processRTCMBuffer();
+
+ // Shutdown the NTRIP server when the mode or setting changes
+ DMW_ds(ntripServerSetState, ntripServer); // DMW: set the server state to the same state - causes a print
+ if (NEQ_RTK_MODE(ntripServerMode) || (!settings.enableNtripServer))
+ {
+ if (ntripServer->state > NTRIP_SERVER_OFF)
+ {
+ ntripServerStop(serverIndex, true); // This was false. Needs checking. TODO
+ ntripServer->connectionAttempts = 0; // Duplicate? ntripServerStop does this... TODO
+ ntripServer->connectionAttemptTimeout = 0; // Duplicate? ntripServerStop does this... TODO
+ ntripServerSetState(ntripServer, NTRIP_SERVER_OFF); // Duplicate? ntripServerStop does this... TODO
+ }
+ }
+
+ // Enable the network and the NTRIP server if requested
+ switch (ntripServer->state)
+ {
+ case NTRIP_SERVER_OFF:
+ if (EQ_RTK_MODE(ntripServerMode) && settings.enableNtripServer
+ && settings.ntripServer_CasterHost[serverIndex][0]
+ && settings.ntripServer_CasterPort[serverIndex]
+ && settings.ntripServer_MountPoint[serverIndex][0])
+ {
+ ntripServerStart(serverIndex);
+ }
+ break;
+
+ // Start the network
+ case NTRIP_SERVER_ON:
+ if (networkUserOpen(NETWORK_USER_NTRIP_SERVER + serverIndex, NETWORK_TYPE_ACTIVE))
+ ntripServerSetState(ntripServer, NTRIP_SERVER_NETWORK_STARTED);
+ break;
+
+ // Wait for a network media connection
+ case NTRIP_SERVER_NETWORK_STARTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+
+ // Determine if the network is connected to the media
+ else if (networkUserConnected(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ {
+ // Allocate the ntripServer structure
+ ntripServer->networkClient = new NetworkClient(NETWORK_USER_NTRIP_SERVER + serverIndex);
+ if (!ntripServer->networkClient)
+ {
+ // Failed to allocate the ntripServer structure
+ systemPrintf("ERROR: Failed to allocate the ntripServer %d structure!\r\n", serverIndex);
+ ntripServerShutdown(serverIndex);
+ }
+ else
+ {
+ reportHeapNow(settings.debugNtripServerState);
+
+ // The network is available for the NTRIP server
+ ntripServerSetState(ntripServer, NTRIP_SERVER_NETWORK_CONNECTED);
+ }
+ }
+ break;
+
+ // Network available
+ case NTRIP_SERVER_NETWORK_CONNECTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+
+ else if (settings.enableNtripServer
+ && (millis() - ntripServer->lastConnectionAttempt > ntripServer->connectionAttemptTimeout))
+ {
+ // No RTCM correction data sent yet
+ rtcmPacketsSent = 0;
+
+ // Open socket to NTRIP caster
+ ntripServerSetState(ntripServer, NTRIP_SERVER_WAIT_GNSS_DATA);
+ }
+ break;
+
+ // Wait for GNSS correction data
+ case NTRIP_SERVER_WAIT_GNSS_DATA:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+
+ // State change handled in ntripServerProcessRTCM
+ break;
+
+ // Initiate the connection to the NTRIP caster
+ case NTRIP_SERVER_CONNECTING:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+
+ // Delay before opening the NTRIP server connection
+ else if ((millis() - ntripServer->timer) >= ntripServer->connectionAttemptTimeout)
+ {
+ // Attempt a connection to the NTRIP caster
+ if (!ntripServerConnectCaster(serverIndex))
+ {
+ // Assume service not available
+ if (ntripServerConnectLimitReached(serverIndex)) // Update ntripServer->connectionAttemptTimeout
+ systemPrintf("NTRIP Server %d failed to connect! Do you have your caster address and port correct?\r\n", serverIndex);
+ }
+ else
+ {
+ // Connection open to NTRIP caster, wait for the authorization response
+ ntripServer->timer = millis();
+ ntripServerSetState(ntripServer, NTRIP_SERVER_AUTHORIZATION);
+ }
+ }
+ break;
+
+ // Wait for authorization response
+ case NTRIP_SERVER_AUTHORIZATION:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+
+ // Check if caster service responded
+ else if (ntripServer->networkClient->available() < strlen("ICY 200 OK")) // Wait until at least a few bytes have arrived
+ {
+ // Check for response timeout
+ if (millis() - ntripServer->timer > 10000)
+ {
+ if (ntripServerConnectLimitReached(serverIndex))
+ systemPrintf("Caster %d failed to respond. Do you have your caster address and port correct?\r\n", serverIndex);
+ }
+ }
+ else
+ {
+ // NTRIP caster's authorization response received
+ char response[512];
+ ntripServerResponse(serverIndex, response, sizeof(response));
+
+ if (settings.debugNtripServerState)
+ systemPrintf("Server %d Response: %s\r\n", serverIndex, response);
+ else
+ log_d("Server %d Response: %s", serverIndex, response);
+
+ // Look for various responses
+ if (strstr(response, "200") != nullptr) //'200' found
+ {
+ // We got a response, now check it for possible errors
+ if (strcasestr(response, "banned") != nullptr)
+ {
+ systemPrintf("NTRIP Server %d connected to caster but caster responded with banned error: %s\r\n",
+ serverIndex, response);
+
+ // Stop NTRIP Server operations
+ ntripServerShutdown(serverIndex);
+ }
+ else if (strcasestr(response, "sandbox") != nullptr)
+ {
+ systemPrintf("NTRIP Server %d connected to caster but caster responded with sandbox error: %s\r\n",
+ serverIndex, response);
+
+ // Stop NTRIP Server operations
+ ntripServerShutdown(serverIndex);
+ }
+
+ systemPrintf("NTRIP Server %d connected to %s:%d %s\r\n", serverIndex,
+ settings.ntripServer_CasterHost[serverIndex],
+ settings.ntripServer_CasterPort[serverIndex],
+ settings.ntripServer_MountPoint[serverIndex]);
+
+ // Connection is now open, start the RTCM correction data timer
+ ntripServer->timer = millis();
+
+ // We don't use a task because we use I2C hardware (and don't have a semphore).
+ online.ntripServer[serverIndex] = true;
+ ntripServer->startTime = millis();
+ ntripServerSetState(ntripServer, NTRIP_SERVER_CASTING);
+ }
+
+ // Look for '401 Unauthorized'
+ else if (strstr(response, "401") != nullptr)
+ {
+ systemPrintf(
+ "NTRIP Caster %d responded with unauthorized error: %s. Are you sure your caster credentials are correct?\r\n",
+ serverIndex, response);
+
+ // Give up - Shutdown NTRIP server, no further retries
+ ntripServerShutdown(serverIndex);
+ }
+
+ // Other errors returned by the caster
+ else
+ {
+ systemPrintf("NTRIP Server %d connected but caster responded with problem: %s\r\n", serverIndex, response);
+
+ // Check for connection limit
+ if (ntripServerConnectLimitReached(serverIndex))
+ systemPrintf("NTRIP Server %d retry limit reached; do you have your caster address and port correct?\r\n", serverIndex);
+ }
+ }
+ break;
+
+ // NTRIP server authorized to send RTCM correction data to NTRIP caster
+ case NTRIP_SERVER_CASTING:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_NTRIP_SERVER + serverIndex))
+ // Failed to connect to to the network, attempt to restart the network
+ ntripServerStop(serverIndex, true); // Note: was ntripServerRestart(serverIndex);
+
+ // Check for a broken connection
+ else if (!ntripServer->networkClient->connected())
+ {
+ // Broken connection, retry the NTRIP connection
+ systemPrintf("Connection to NTRIP Caster %d was lost\r\n", serverIndex);
+ ntripServerRestart(serverIndex);
+ }
+ else if ((millis() - ntripServer->timer) > (15 * 1000))
+ {
+ // GNSS stopped sending RTCM correction data
+ systemPrintf("NTRIP Server %d breaking connection to caster due to lack of RTCM data!\r\n", serverIndex);
+ ntripServerRestart(serverIndex);
+ }
+ else
+ {
+ // Handle other types of NTRIP connection failures to prevent
+ // hammering the NTRIP caster with rapid connection attempts.
+ // A fast reconnect is reasonable after a long NTRIP caster
+ // connection. However increasing backoff delays should be
+ // added when the NTRIP caster fails after a short connection
+ // interval.
+ if (((millis() - ntripServer->startTime) > NTRIP_SERVER_CONNECTION_TIME)
+ && (ntripServer->connectionAttempts || ntripServer->connectionAttemptTimeout))
+ {
+ // After a long connection period, reset the attempt counter
+ ntripServer->connectionAttempts = 0;
+ ntripServer->connectionAttemptTimeout = 0;
+ if (settings.debugNtripServerState)
+ systemPrintf("NTRIP Server %d resetting connection attempt counter and timeout\r\n", serverIndex);
+ }
+
+ // All is well
+ cyclePositionLEDs();
+ }
+ break;
+ }
+
+ // Periodically display the state
+ if (PERIODIC_DISPLAY(PD_NTRIP_SERVER_STATE))
+ {
+ systemPrintf("NTRIP Server %d state: %s\r\n", serverIndex, ntripServerStateName[ntripServer->state]);
+ if (serverIndex == (NTRIP_SERVER_MAX - 1))
+ PERIODIC_CLEAR(PD_NTRIP_SERVER_STATE); // Clear the periodic display only on the last server
+ }
+}
+
+// Update the NTRIP server state machine
+void ntripServerUpdate()
+{
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ ntripServerUpdate(serverIndex);
+}
+
+// Verify the NTRIP server tables
+void ntripServerValidateTables()
+{
+ if (ntripServerStateNameEntries != NTRIP_SERVER_STATE_MAX)
+ reportFatalError("Fix ntripServerStateNameEntries to match NTRIPServerState");
+ if (NETWORK_USER_MAX > (sizeof(NETWORK_USER) * 8))
+ reportFatalError("Increase the NETWORK_USER type");
+}
+
+#endif // COMPILE_NETWORK
diff --git a/Firmware/RTK_Surveyor/OtaClient.ino b/Firmware/RTK_Surveyor/OtaClient.ino
new file mode 100644
index 000000000..eaaf1b02f
--- /dev/null
+++ b/Firmware/RTK_Surveyor/OtaClient.ino
@@ -0,0 +1,868 @@
+/*------------------------------------------------------------------------------
+OtaClient.ino
+
+ The Over-The-Air (OTA) client sits on top of the network layer and requests
+ a JSON file from the GitHub server that describes the version and URL of
+ the released firmware. If the released firmware is more recent then the OTA
+ client downloads and flashes the released firmware.
+
+ RTK
+ Device
+ ^
+ | OTA client
+ V
+ GitHub
+
+------------------------------------------------------------------------------*/
+
+#if COMPILE_NETWORK
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+#define HTTPS_TRANSPORT (OTA_USE_SSL ? "https://" : "http://")
+
+#define OTA_JSON_FILE_URL \
+ "/sparkfun/SparkFun_RTK_Firmware_Binaries/main/RTK-Firmware.json"
+#define OTA_NO_PROGRESS_TIMEOUT (3 * 60 * 1000) // 3 minutes
+#define OTA_SERVER "raw.githubusercontent.com"
+#define OTA_SERVER_PORT 443
+#define OTA_USE_SSL 1
+
+enum OtaState
+{
+ OTA_STATE_OFF = 0,
+ OTA_STATE_START_NETWORK,
+ OTA_STATE_WAIT_FOR_NETWORK,
+ OTA_STATE_JSON_FILE_REQUEST,
+ OTA_STATE_JSON_FILE_PARSE_HTTP_STATUS,
+ OTA_STATE_JSON_FILE_PARSE_LENGTH,
+ OTA_STATE_JSON_FILE_SKIP_HEADERS,
+ OTA_STATE_JSON_FILE_READ_DATA,
+ OTA_STATE_PARSE_JSON_DATA,
+ OTA_STATE_BIN_FILE_REQUEST,
+ OTA_STATE_BIN_FILE_PARSE_HTTP_STATUS,
+ OTA_STATE_BIN_FILE_PARSE_LENGTH,
+ OTA_STATE_BIN_FILE_SKIP_HEADERS,
+ OTA_STATE_BIN_FILE_READ_DATA,
+ // Insert new states before this line
+ OTA_STATE_MAX
+};
+
+const char * const otaStateNames[] =
+{
+ "OTA_STATE_OFF",
+ "OTA_STATE_START_NETWORK",
+ "OTA_STATE_WAIT_FOR_NETWORK",
+ "OTA_STATE_JSON_FILE_REQUEST",
+ "OTA_STATE_JSON_FILE_PARSE_HTTP_STATUS",
+ "OTA_STATE_JSON_FILE_PARSE_LENGTH",
+ "OTA_STATE_JSON_FILE_SKIP_HEADERS",
+ "OTA_STATE_JSON_FILE_READ_DATA",
+ "OTA_STATE_PARSE_JSON_DATA",
+ "OTA_STATE_BIN_FILE_REQUEST",
+ "OTA_STATE_BIN_FILE_PARSE_HTTP_STATUS",
+ "OTA_STATE_BIN_FILE_PARSE_LENGTH",
+ "OTA_STATE_BIN_FILE_SKIP_HEADERS",
+ "OTA_STATE_BIN_FILE_READ_DATA"
+};
+const int otaStateEntries = sizeof(otaStateNames) / sizeof(otaStateNames[0]);
+
+const RtkMode_t otaClientMode = RTK_MODE_BASE_FIXED
+ | RTK_MODE_BASE_SURVEY_IN
+ | RTK_MODE_BUBBLE_LEVEL
+ | RTK_MODE_NTP
+ | RTK_MODE_ROVER;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+static byte otaBluetoothState = BT_OFF;
+static char otaBuffer[1379];
+static int otaBufferData;
+static NetworkClient * otaClient;
+static int otaFileBytes;
+static int otaFileSize;
+static String otaJsonFileData;
+static String otaReleasedFirmwareURL;
+static uint8_t otaState;
+static uint32_t otaTimer;
+
+//----------------------------------------
+// Over-The-Air (OTA) firmware update support routines
+//----------------------------------------
+
+// Get the OTA state name
+const char * otaGetStateName(uint8_t state, char * string)
+{
+ if (state < OTA_STATE_MAX)
+ return otaStateNames[state];
+ sprintf(string, "Unknown state (%d)", state);
+ return string;
+}
+
+// Get the file length from the HTTP header
+int otaParseFileLength()
+{
+ int fileLength;
+
+ // Parse the file length from the HTTP header
+ otaBufferData = 0;
+ if (sscanf(otaBuffer, "Content-Length: %d", &fileLength) == 1)
+ return fileLength;
+ return -1;
+}
+
+// Get the server file status from the HTTP header
+int otaParseJsonStatus()
+{
+ int status;
+
+ // Parse the status from the HTTP header
+ if (sscanf(otaBuffer, "HTTP/1.1 %d", &status) == 1)
+ return status;
+ return -1;
+}
+
+// Read data from the JSON or firmware file
+void otaReadFileData(int bufferLength)
+{
+ int bytesToRead;
+
+ // Determine how much data is available
+ otaBufferData = 0;
+ bytesToRead = otaClient->available();
+ if (bytesToRead)
+ {
+ // Determine the number of bytes to read
+ if (bytesToRead > bufferLength)
+ bytesToRead = bufferLength;
+
+ // Read in the file data
+ otaBufferData = otaClient->read((uint8_t *)otaBuffer, bytesToRead);
+ }
+}
+
+// Read a line from the HTTP header
+int otaReadHeaderLine()
+{
+ int bytesToRead;
+ String otaReleasedFirmwareVersion;
+ int status;
+
+ // Determine if the network is shutting down
+ if (networkIsShuttingDown(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network is shutting down!");
+ otaStop();
+ return -1;
+ }
+
+ // Determine if the network is connected to the media
+ if (!networkUserConnected(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network has failed!");
+ otaStop();
+ return -1;
+ }
+
+ // Verify the connection to the HTTP server
+ if (!otaClient->connected())
+ {
+ systemPrintln("OTA: HTTP connection broken!");
+ otaStop();
+ return -1;
+ }
+
+ // Read in the released firmware data
+ while (otaClient->available())
+ {
+ otaBuffer[otaBufferData] = otaClient->read();
+
+ // Drop the carriage return
+ if (otaBuffer[otaBufferData] != '\r')
+ {
+ // Build the header line
+ if (otaBuffer[otaBufferData] != '\n')
+ otaBufferData += 1;
+ else
+ {
+ // Zero-terminate the header line
+ otaBuffer[otaBufferData] = 0;
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+// Set the next OTA state
+void otaSetState(uint8_t newState)
+{
+ char string1[40];
+ char string2[40];
+ const char * arrow;
+ const char * asterisk;
+ const char * initialState;
+ const char * endingState;
+ bool pd;
+
+ // Display the state transition
+ pd = PERIODIC_DISPLAY(PD_OTA_CLIENT_STATE);
+ if ((settings.debugFirmwareUpdate) || pd)
+ {
+ arrow = "";
+ asterisk = "";
+ initialState = "";
+ if (newState == otaState)
+ asterisk = "*";
+ else
+ {
+ initialState = otaGetStateName(otaState, string1);
+ arrow = " --> ";
+ }
+ }
+
+ // Set the new state
+ otaState = newState;
+ if ((settings.debugFirmwareUpdate) || pd)
+ {
+ // Display the new firmware update state
+ PERIODIC_CLEAR(PD_OTA_CLIENT_STATE);
+ endingState = otaGetStateName(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());
+ }
+ }
+
+ // Display the starting percentage
+ if (otaState == OTA_STATE_BIN_FILE_READ_DATA)
+ otaDisplayPercentage(otaFileBytes, otaFileSize, pd);
+
+ // Validate the firmware update state
+ if (newState >= OTA_STATE_MAX)
+ reportFatalError("Invalid OTA state");
+}
+
+// Stop the OTA firmware update
+void otaStop()
+{
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("otaStop called");
+
+ if (otaState != OTA_STATE_OFF)
+ {
+ // Stop WiFi
+ systemPrintln("OTA stopping WiFi");
+ online.otaFirmwareUpdate = false;
+
+ // Stop writing to flash
+ if (Update.isRunning())
+ Update.abort();
+
+ // Close the SSL connection
+ if (otaClient)
+ {
+ delete otaClient;
+ otaClient = nullptr;
+ }
+
+ // Close the network connection
+ if (networkGetUserNetwork(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ networkUserClose(NETWORK_USER_OTA_FIRMWARE_UPDATE);
+
+ // Stop the firmware update
+ otaBufferData = 0;
+ otaJsonFileData = String("");
+ otaSetState(OTA_STATE_OFF);
+ otaTimer = millis();
+
+ // Restart bluetooth if necessary
+ if (otaBluetoothState == BT_CONNECTED)
+ {
+ otaBluetoothState = BT_OFF;
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("Firmware update restarting Bluetooth");
+ bluetoothStart(); // Restart BT according to settings
+ }
+ }
+};
+
+int otaWriteDataToFlash(int bytesToWrite)
+{
+ int bytesWritten;
+
+ bytesWritten = 0;
+ if (bytesToWrite)
+ {
+ // Write the data to flash
+ bytesWritten = Update.write((uint8_t *)otaBuffer, bytesToWrite);
+ if (bytesWritten)
+ {
+ otaFileBytes += bytesWritten;
+ if (bytesWritten != bytesToWrite)
+ {
+ // Only a portion of the data was written, move the rest of
+ // the data to the beginning of the buffer
+ memcpy(otaBuffer, &otaBuffer[bytesWritten], bytesToWrite - bytesWritten);
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Wrote only %d of %d bytes to flash\r\n", bytesWritten, otaBufferData);
+ }
+
+ // Display the percentage written
+ otaPullCallback(otaFileBytes, otaFileSize);
+ }
+ }
+
+ // Return the number of bytes to write
+ return bytesToWrite - bytesWritten;
+}
+
+//----------------------------------------
+// Over-The-Air (OTA) firmware update state machine
+//----------------------------------------
+
+// Perform the over-the-air (OTA) firmware updates
+void otaClientUpdate()
+{
+ int bytesWritten;
+ int32_t checkIntervalMillis;
+ NETWORK_DATA * network;
+ String otaReleasedFirmwareVersion;
+ int status;
+
+ // Perform the firmware update
+ if (!inMainMenu)
+ {
+ // Shutdown the OTA client when the mode or setting changes
+ DMW_st(otaSetState, otaState);
+ if (NEQ_RTK_MODE(otaClientMode) || (!settings.enableAutoFirmwareUpdate))
+ {
+ if (otaState > OTA_STATE_OFF)
+ {
+ otaStop();
+
+ // Due to the interruption, enable a fast retry
+ otaTimer = millis() - checkIntervalMillis + OTA_NO_PROGRESS_TIMEOUT;
+ }
+ }
+
+ // Walk the state machine to do the firmware update
+ switch (otaState)
+ {
+ // Handle invalid OTA states
+ default: {
+ systemPrintf("ERROR: Unknown OTA state (%d)\r\n", otaState);
+ otaStop();
+ break;
+ }
+
+ // Over-the-air firmware updates are not active
+ case OTA_STATE_OFF: {
+ // Determine if the user enabled automatic firmware updates
+ if (EQ_RTK_MODE(otaClientMode) && settings.enableAutoFirmwareUpdate)
+ {
+ // Wait until it is time to check for a firmware update
+ checkIntervalMillis = settings.autoFirmwareCheckMinutes * 60 * 1000;
+ if ((int32_t)(millis() - otaTimer) >= checkIntervalMillis)
+ {
+ otaTimer = millis();
+ online.otaFirmwareUpdate = true;
+ otaSetState(OTA_STATE_START_NETWORK);
+ }
+ }
+ break;
+ }
+
+ // Start the network
+ case OTA_STATE_START_NETWORK: {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA starting network");
+ if (!networkUserOpen(NETWORK_USER_OTA_FIRMWARE_UPDATE, NETWORK_TYPE_ACTIVE))
+ {
+ systemPrintln("OTA: Firmware update failed, unable to start network");
+ otaStop();
+ }
+ else
+ otaSetState(OTA_STATE_WAIT_FOR_NETWORK);
+ break;
+ }
+
+ // Wait for connection to the network
+ case OTA_STATE_WAIT_FOR_NETWORK: {
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network is shutting down!");
+ otaStop();
+ }
+
+ // Determine if the network is connected to the media
+ else if (networkUserConnected(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA connected to network");
+
+ // Allocate the OTA firmware update client
+ otaClient = networkClient(NETWORK_USER_OTA_FIRMWARE_UPDATE, OTA_USE_SSL);
+ if (!otaClient)
+ {
+ systemPrintln("ERROR: Failed to allocate OTA client!");
+ otaStop();
+ }
+ else
+ {
+ // Stop Bluetooth
+ otaBluetoothState = bluetoothGetState();
+ if (otaBluetoothState != BT_OFF)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA stopping Bluetooth");
+ bluetoothStop();
+ }
+
+ // Connect to GitHub
+ otaSetState(OTA_STATE_JSON_FILE_REQUEST);
+ }
+ }
+ break;
+ }
+
+ // Issue the HTTP request to get the JSON file
+ case OTA_STATE_JSON_FILE_REQUEST: {
+ // Determine if the network is shutting down
+ if (networkIsShuttingDown(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network is shutting down!");
+ otaStop();
+ }
+
+ // Determine if the network is connected to the media
+ else if (!networkUserConnected(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network has failed!");
+ otaStop();
+ }
+
+ // Attempt to connect to the server using HTTPS
+ else if (!otaClient->connect(OTA_SERVER, OTA_SERVER_PORT))
+ {
+ // if you didn't get a connection to the server:
+ systemPrintln("OTA: Connection failed");
+ otaStop();
+ }
+ else
+ {
+ systemPrintln("OTA: Requesting JSON file");
+
+ // Make the HTTP request:
+ otaClient->print("GET ");
+ otaClient->print(OTA_FIRMWARE_JSON_URL);
+ otaClient->println(" HTTP/1.1");
+ otaClient->println("User-Agent: RTK OTA Client");
+ otaClient->print("Host: ");
+ otaClient->println(OTA_SERVER);
+ otaClient->println("Connection: close");
+ otaClient->println();
+ otaBufferData = 0;
+ otaSetState(OTA_STATE_JSON_FILE_PARSE_HTTP_STATUS);
+ }
+ break;
+ }
+
+ // Locate the HTTP status header
+ case OTA_STATE_JSON_FILE_PARSE_HTTP_STATUS: {
+ status = otaReadHeaderLine();
+ if (status)
+ break;
+
+ // Verify that the server found the file
+ status = otaParseJsonStatus();
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Server file status: %d\r\n", status);
+ if (status != 200)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA: Server failed to locate the JSON file");
+ otaStop();
+ }
+ else
+ {
+ otaBufferData = 0;
+ otaSetState(OTA_STATE_JSON_FILE_PARSE_LENGTH);
+ }
+ break;
+ }
+
+ // Locate the file length header
+ case OTA_STATE_JSON_FILE_PARSE_LENGTH: {
+ status = otaReadHeaderLine();
+ if (status)
+ break;
+
+ // Verify the header line length
+ if (!otaBufferData)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA: JSON file length not found");
+ otaStop();
+ break;
+ }
+
+ // Get the file length
+ otaFileSize = otaParseFileLength();
+ if (otaFileSize >= 0)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: JSON file length %d bytes\r\n", otaFileSize);
+ otaBufferData = 0;
+ otaFileBytes = 0;
+ otaSetState(OTA_STATE_JSON_FILE_SKIP_HEADERS);
+ }
+ break;
+ }
+
+ // Skip over the rest of the HTTP headers
+ case OTA_STATE_JSON_FILE_SKIP_HEADERS: {
+ status = otaReadHeaderLine();
+ if (status)
+ break;
+
+ // Determine if this is the separater between the HTTP headers
+ // and the file data
+ if (!otaBufferData)
+ otaSetState(OTA_STATE_JSON_FILE_READ_DATA);
+ otaBufferData = 0;
+ break;
+ }
+
+ // Receive the JSON data from the HTTP server
+ case OTA_STATE_JSON_FILE_READ_DATA: {
+ // Determine if the network is shutting down
+ if (networkIsShuttingDown(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintf("OTA: Network is shutting down after %d bytes!\r\n", otaFileBytes);
+ otaStop();
+ }
+
+ // Determine if the network is connected to the media
+ else if (!networkUserConnected(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintf("OTA: Network has failed after %d bytes!\r\n", otaFileBytes);
+ otaStop();
+ }
+
+ // Verify the connection to the HTTP server
+ else if (!otaClient->connected())
+ {
+ systemPrintf("OTA: HTTP connection broken after %d bytes!\r\n", otaFileBytes);
+ otaStop();
+ }
+ else
+ {
+ // Read data from the JSON file
+ otaReadFileData(sizeof(otaBuffer) - 1);
+ if (otaBufferData)
+ {
+ // Zero terminate the file data in the buffer
+ otaBuffer[otaBufferData] = 0;
+
+ // Append the JSON file data to the string
+ otaJsonFileData += String(&otaBuffer[0]);
+ otaBufferData = 0;
+ }
+
+ // Done if not at the end-of-file
+ if (otaJsonFileData.length() != otaFileSize)
+ break;
+
+ // Reached end-of-file
+ // Parse the JSON file
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: JSON data: %s\r\n", otaJsonFileData.c_str());
+ otaSetState(OTA_STATE_PARSE_JSON_DATA);
+ }
+ break;
+ }
+
+ // Parse the JSON data and determine if new firmware is available
+ case OTA_STATE_PARSE_JSON_DATA: {
+ // Locate the fields in the JSON file
+ DynamicJsonDocument doc(1000);
+ if (deserializeJson(doc, otaJsonFileData.c_str()) != DeserializationError::Ok)
+ {
+ systemPrintln("OTA: Failed to parse the JSON file data");
+ otaStop();
+ }
+ else
+ {
+ char versionString[9];
+
+ // Get the current version
+ getFirmwareVersion(versionString, sizeof(versionString), false);
+
+ // Step through the configurations looking for a match
+ for (auto config : doc["Configurations"].as())
+ {
+ // Get the latest released version
+ otaReleasedFirmwareVersion = config["Version"].isNull() ? "" : (const char *)config["Version"];
+ otaReleasedFirmwareURL = config["URL"].isNull() ? "" : (const char *)config["URL"];
+ if ((tolower(versionString[0]) != 'd')
+ && (FIRMWARE_VERSION_MAJOR != 99)
+ && (String(&versionString[1]) >= otaReleasedFirmwareVersion))
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Current firmware %s is beyond released %s, no change necessary.\r\n",
+ versionString, otaReleasedFirmwareVersion.c_str());
+ otaStop();
+ }
+ else
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Firmware URL %s\r\n", otaReleasedFirmwareURL.c_str());
+ if ((strncasecmp(HTTPS_TRANSPORT,
+ otaReleasedFirmwareURL.c_str(),
+ strlen(HTTPS_TRANSPORT)) != 0)
+ || (strncasecmp(OTA_SERVER,
+ &otaReleasedFirmwareURL.c_str()[strlen(HTTPS_TRANSPORT)],
+ strlen(OTA_SERVER)) != 0))
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA: Invalid firmware URL");
+ otaStop();
+ }
+ else
+ {
+ // Break the connection with the HTTP server
+ otaClient->stop();
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Upgrading firmware from %s to %s\r\n",
+ versionString, otaReleasedFirmwareVersion.c_str());
+ otaReleasedFirmwareURL.remove(0, strlen(HTTPS_TRANSPORT) + strlen(OTA_SERVER));
+ otaSetState(OTA_STATE_BIN_FILE_REQUEST);
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ // Issue the HTTP request to get the released firmware file
+ case OTA_STATE_BIN_FILE_REQUEST: {
+ // Determine if the network is shutting down
+ if (networkIsShuttingDown(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network is shutting down!");
+ otaStop();
+ }
+
+ // Determine if the network is connected to the media
+ else if (!networkUserConnected(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network has failed!");
+ otaStop();
+ }
+
+ // Attempt to connect to the server using HTTPS
+ else if (!otaClient->connect(OTA_SERVER, OTA_SERVER_PORT))
+ {
+ // if you didn't get a connection to the server:
+ systemPrintln("OTA: Connection failed");
+ otaStop();
+ }
+ else
+ {
+ systemPrintln("OTA: Requesting BIN file");
+
+ // Make the HTTP request:
+ otaClient->print("GET ");
+ otaClient->print(otaReleasedFirmwareURL.c_str());
+ otaClient->println(" HTTP/1.1");
+ otaClient->println("User-Agent: RTK OTA Client");
+ otaClient->print("Host: ");
+ otaClient->println(OTA_SERVER);
+ otaClient->println("Connection: close");
+ otaClient->println();
+ otaBufferData = 0;
+ otaSetState(OTA_STATE_BIN_FILE_PARSE_HTTP_STATUS);
+ }
+ break;
+ }
+
+ // Locate the HTTP status header
+ case OTA_STATE_BIN_FILE_PARSE_HTTP_STATUS: {
+ status = otaReadHeaderLine();
+ if (status)
+ break;
+
+ // Verify that the server found the file
+ status = otaParseJsonStatus();
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Server file status: %d\r\n", status);
+ if (status != 200)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA: Server failed to locate the JSON file");
+ otaStop();
+ }
+ else
+ {
+ otaBufferData = 0;
+ otaSetState(OTA_STATE_BIN_FILE_PARSE_LENGTH);
+ }
+ break;
+ }
+
+ // Locate the file length header
+ case OTA_STATE_BIN_FILE_PARSE_LENGTH: {
+ status = otaReadHeaderLine();
+ if (status)
+ break;
+
+ // Verify the header line length
+ if (!otaBufferData)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintln("OTA: BIN file length not found");
+ otaStop();
+ break;
+ }
+
+ // Get the file length
+ otaFileSize = otaParseFileLength();
+ if (otaFileSize >= 0)
+ {
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: BIN file length %d bytes\r\n", otaFileSize);
+ otaBufferData = 0;
+ otaFileBytes = 0;
+ otaSetState(OTA_STATE_BIN_FILE_SKIP_HEADERS);
+ }
+ break;
+ }
+
+ // Skip over the rest of the HTTP headers
+ case OTA_STATE_BIN_FILE_SKIP_HEADERS: {
+ status = otaReadHeaderLine();
+ if (status)
+ break;
+
+ // Determine if this is the separater between the HTTP headers
+ // and the file data
+ if (!otaBufferData)
+ {
+ // Start the firmware update process
+ if (!Update.begin(UPDATE_SIZE_UNKNOWN))
+ otaStop();
+ else
+ {
+ otaTimer = millis();
+ otaSetState(OTA_STATE_BIN_FILE_READ_DATA);
+ }
+ }
+ otaBufferData = 0;
+ break;
+ }
+
+ // Receive the bin file from the HTTP server
+ case OTA_STATE_BIN_FILE_READ_DATA: {
+ do
+ {
+ bytesWritten = 0;
+
+ // Determine if the network is shutting down
+ if (networkIsShuttingDown(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network is shutting down!");
+ otaStop();
+ }
+
+ // Determine if the network is connected to the media
+ else if (!networkUserConnected(NETWORK_USER_OTA_FIRMWARE_UPDATE))
+ {
+ systemPrintln("OTA: Network has failed!");
+ otaStop();
+ }
+
+ // Verify the connection to the HTTP server
+ else if (!otaClient->connected())
+ {
+ systemPrintf("OTA: HTTP connection broken after %d bytes!\r\n", otaFileBytes);
+ otaStop();
+ }
+
+ // Determine if progress is being made
+ else if ((millis() - otaTimer) >= OTA_NO_PROGRESS_TIMEOUT)
+ {
+ systemPrintln("OTA: No progress being made, link broken!");
+ otaStop();
+ checkIntervalMillis = settings.autoFirmwareCheckMinutes * 60 * 1000;
+
+ // Delay for OTA_NO_PROGRESS_TIMEOUT
+ otaTimer = millis() - checkIntervalMillis + OTA_NO_PROGRESS_TIMEOUT;
+ }
+
+ // Read data and write it to the flash
+ else
+ {
+ // Read data from the binary file
+ if (!otaBufferData)
+ otaReadFileData(sizeof(otaBuffer));
+
+ // Write the data to the flash
+ if (otaBufferData)
+ {
+ bytesWritten = otaBufferData;
+ otaBufferData = otaWriteDataToFlash(otaBufferData);
+ bytesWritten -= otaBufferData;
+ otaTimer = millis();
+
+ // Check for end-of-file
+ if (otaFileBytes == otaFileSize)
+ {
+ // The end-of-file was reached
+ if (settings.debugFirmwareUpdate)
+ systemPrintf("OTA: Downloaded %d bytes\r\n", otaFileBytes);
+ Update.end(true);
+
+ // Reset the system
+ systemPrintln("OTA: Starting the new firmware");
+ delay(1000);
+ ESP.restart();
+ break;
+ }
+ }
+ }
+ } while (bytesWritten);
+ break;
+ }
+ }
+
+ // Periodically display the PVT client state
+ if (PERIODIC_DISPLAY(PD_OTA_CLIENT_STATE))
+ otaSetState(otaState);
+ }
+}
+
+// Verify the firmware update tables
+void otaVerifyTables()
+{
+ // Verify the table lengths
+ if (otaStateEntries != OTA_STATE_MAX)
+ reportFatalError("Fix otaStateNames table to match OtaState");
+}
+
+#endif // COMPILE_NETWORK
diff --git a/Firmware/RTK_Surveyor/Parse_NMEA.ino b/Firmware/RTK_Surveyor/Parse_NMEA.ino
new file mode 100644
index 000000000..a05dd0c4d
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Parse_NMEA.ino
@@ -0,0 +1,116 @@
+/*------------------------------------------------------------------------------
+Parse_NMEA.ino
+
+ NMEA message parsing support routines
+------------------------------------------------------------------------------*/
+
+//
+// NMEA Message
+//
+// +----------+---------+--------+---------+----------+----------+
+// | Preamble | Name | Comma | Data | Asterisk | Checksum |
+// | 8 bits | n bytes | 8 bits | n bytes | 8 bits | 2 bytes |
+// | $ | | , | | | |
+// +----------+---------+--------+---------+----------+----------+
+// | |
+// |<-------- Checksum -------->|
+//
+
+// Check for the preamble
+uint8_t nmeaPreamble(PARSE_STATE *parse, uint8_t data)
+{
+ if (data == '$')
+ {
+ parse->crc = 0;
+ parse->computeCrc = false;
+ parse->nmeaMessageNameLength = 0;
+ parse->state = nmeaFindFirstComma;
+ return SENTENCE_TYPE_NMEA;
+ }
+ return SENTENCE_TYPE_NONE;
+}
+
+// Read the message name
+uint8_t nmeaFindFirstComma(PARSE_STATE *parse, uint8_t data)
+{
+ parse->crc ^= data;
+ if ((data != ',') || (parse->nmeaMessageNameLength == 0))
+ {
+ if ((data < 'A') || (data > 'Z'))
+ {
+ parse->length = 0;
+ parse->buffer[parse->length++] = data;
+ return gpsMessageParserFirstByte(parse, data);
+ }
+
+ // Save the message name
+ parse->nmeaMessageName[parse->nmeaMessageNameLength++] = data;
+ }
+ else
+ {
+ // Zero terminate the message name
+ parse->nmeaMessageName[parse->nmeaMessageNameLength++] = 0;
+ parse->state = nmeaFindAsterisk;
+ }
+ return SENTENCE_TYPE_NMEA;
+}
+
+// Read the message data
+uint8_t nmeaFindAsterisk(PARSE_STATE *parse, uint8_t data)
+{
+ if (data != '*')
+ parse->crc ^= data;
+ else
+ parse->state = nmeaChecksumByte1;
+ return SENTENCE_TYPE_NMEA;
+}
+
+// Read the first checksum byte
+uint8_t nmeaChecksumByte1(PARSE_STATE *parse, uint8_t data)
+{
+ parse->state = nmeaChecksumByte2;
+ return SENTENCE_TYPE_NMEA;
+}
+
+// Read the second checksum byte
+uint8_t nmeaChecksumByte2(PARSE_STATE *parse, uint8_t data)
+{
+ parse->nmeaLength = parse->length;
+ parse->state = nmeaLineTermination;
+ return SENTENCE_TYPE_NMEA;
+}
+
+// Read the line termination
+uint8_t nmeaLineTermination(PARSE_STATE *parse, uint8_t data)
+{
+ int checksum;
+
+ // Process the line termination
+ if ((data != '\r') && (data != '\n'))
+ {
+ // Don't include this character in the buffer
+ parse->length--;
+
+ // Convert the checksum characters into binary
+ checksum = AsciiToNibble(parse->buffer[parse->nmeaLength - 2]) << 4;
+ checksum |= AsciiToNibble(parse->buffer[parse->nmeaLength - 1]);
+
+ // Validate the checksum
+ if (checksum == parse->crc)
+ parse->crc = 0;
+ if (settings.enablePrintBadMessages && parse->crc && (!inMainMenu))
+ printNmeaChecksumError(parse);
+
+ // Process this message if CRC is valid
+ if (parse->crc == 0)
+ parse->eomCallback(parse, SENTENCE_TYPE_NMEA);
+ else
+ failedParserMessages_NMEA++;
+
+ // Add this character to the beginning of the buffer
+ parse->length = 0;
+ parse->buffer[parse->length++] = data;
+ return gpsMessageParserFirstByte(parse, data);
+ }
+ return SENTENCE_TYPE_NMEA;
+}
diff --git a/Firmware/RTK_Surveyor/Parse_RTCM.ino b/Firmware/RTK_Surveyor/Parse_RTCM.ino
new file mode 100644
index 000000000..e3402c1dd
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Parse_RTCM.ino
@@ -0,0 +1,134 @@
+/*------------------------------------------------------------------------------
+Parse_RTCM.ino
+
+ RTCM message parsing support routines
+------------------------------------------------------------------------------*/
+
+//
+// RTCM Standard 10403.2 - Chapter 4, Transport Layer
+//
+// |<------------- 3 bytes ------------>|<----- length ----->|<- 3 bytes ->|
+// | | | |
+// +----------+--------+----------------+---------+----------+-------------+
+// | Preamble | Fill | Message Length | Message | Fill | CRC-24Q |
+// | 8 bits | 6 bits | 10 bits | n-bits | 0-7 bits | 24 bits |
+// | 0xd3 | 000000 | (in bytes) | | zeros | |
+// +----------+--------+----------------+---------+----------+-------------+
+// | |
+// |<------------------------ CRC -------------------------->|
+//
+
+// Check for the preamble
+uint8_t rtcmPreamble(PARSE_STATE *parse, uint8_t data)
+{
+ if (data == 0xd3)
+ {
+ // Start the CRC with this byte
+ parse->crc = 0;
+ parse->crc = COMPUTE_CRC24Q(parse, data);
+ parse->computeCrc = true;
+
+ // Get the message length
+ parse->state = rtcmReadLength1;
+ return SENTENCE_TYPE_RTCM;
+ }
+ return SENTENCE_TYPE_NONE;
+}
+
+// Read the upper two bits of the length
+uint8_t rtcmReadLength1(PARSE_STATE *parse, uint8_t data)
+{
+ // Verify the length byte - check the 6 MS bits are all zero
+ if (data & (~3))
+ {
+ // Invalid length, place this byte at the beginning of the buffer
+ parse->length = 0;
+ parse->buffer[parse->length++] = data;
+ parse->computeCrc = false;
+
+ // Start searching for a preamble byte
+ return gpsMessageParserFirstByte(parse, data);
+ }
+
+ // Save the upper 2 bits of the length
+ parse->bytesRemaining = data << 8;
+ parse->state = rtcmReadLength2;
+ return SENTENCE_TYPE_RTCM;
+}
+
+// Read the lower 8 bits of the length
+uint8_t rtcmReadLength2(PARSE_STATE *parse, uint8_t data)
+{
+ parse->bytesRemaining |= data;
+ parse->state = rtcmReadMessage1;
+ return SENTENCE_TYPE_RTCM;
+}
+
+// Read the upper 8 bits of the message number
+uint8_t rtcmReadMessage1(PARSE_STATE *parse, uint8_t data)
+{
+ parse->message = data << 4;
+ parse->bytesRemaining -= 1;
+ parse->state = rtcmReadMessage2;
+ return SENTENCE_TYPE_RTCM;
+}
+
+// Read the lower 4 bits of the message number
+uint8_t rtcmReadMessage2(PARSE_STATE *parse, uint8_t data)
+{
+ parse->message |= data >> 4;
+ parse->bytesRemaining -= 1;
+ parse->state = rtcmReadData;
+ return SENTENCE_TYPE_RTCM;
+}
+
+// Read the rest of the message
+uint8_t rtcmReadData(PARSE_STATE *parse, uint8_t data)
+{
+ // Account for this data byte
+ parse->bytesRemaining -= 1;
+
+ // Wait until all the data is received
+ if (parse->bytesRemaining <= 0)
+ {
+ parse->rtcmCrc = parse->crc & 0x00ffffff;
+ parse->bytesRemaining = 3;
+ parse->state = rtcmReadCrc;
+ }
+ return SENTENCE_TYPE_RTCM;
+}
+
+// Read the CRC
+uint8_t rtcmReadCrc(PARSE_STATE *parse, uint8_t data)
+{
+ // Account for this data byte
+ parse->bytesRemaining -= 1;
+
+ // Wait until all the data is received
+ if (parse->bytesRemaining > 0)
+ return SENTENCE_TYPE_RTCM;
+
+ // Update the maximum message length
+ if (parse->length > parse->maxLength)
+ {
+ parse->maxLength = parse->length;
+ printRtcmMaxLength(parse);
+ }
+
+ // Display the RTCM messages with bad CRC
+ parse->crc &= 0x00ffffff;
+ if (settings.enablePrintBadMessages && parse->crc && (!inMainMenu))
+ printRtcmChecksumError(parse);
+
+ // Process the message if CRC is valid
+ if (parse->crc == 0)
+ parse->eomCallback(parse, SENTENCE_TYPE_RTCM);
+ else
+ failedParserMessages_RTCM++;
+
+ // Search for another preamble byte
+ parse->length = 0;
+ parse->computeCrc = false;
+ parse->state = gpsMessageParserFirstByte;
+ return SENTENCE_TYPE_NONE;
+}
diff --git a/Firmware/RTK_Surveyor/Parse_UBLOX.ino b/Firmware/RTK_Surveyor/Parse_UBLOX.ino
new file mode 100644
index 000000000..3d613acd8
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Parse_UBLOX.ino
@@ -0,0 +1,155 @@
+/*------------------------------------------------------------------------------
+Parse_UBLOX.ino
+
+ u-blox message parsing support routines
+------------------------------------------------------------------------------*/
+
+//
+// U-BLOX Message
+//
+// |<-- Preamble --->|
+// | |
+// +--------+--------+---------+--------+---------+---------+--------+--------+
+// | SYNC | SYNC | Class | ID | Length | Payload | CK_A | CK_B |
+// | 8 bits | 8 bits | 8 bits | 8 bits | 2 bytes | n bytes | 8 bits | 8 bits |
+// | 0xb5 | 0x62 | | | | | | |
+// +--------+--------+---------+--------+---------+---------+--------+--------+
+// | |
+// |<------------- Checksum ------------->|
+//
+// 8-Bit Fletcher Algorithm, which is used in the TCP standard (RFC 1145)
+// http://www.ietf.org/rfc/rfc1145.txt
+// Checksum calculation
+// Initialization: CK_A = CK_B = 0
+// CK_A += data
+// CK_B += CK_A
+//
+
+// Check for the preamble
+uint8_t ubloxPreamble(PARSE_STATE *parse, uint8_t data)
+{
+ if (data == 0xb5)
+ {
+ parse->state = ubloxSync2;
+ return SENTENCE_TYPE_UBX;
+ }
+ return SENTENCE_TYPE_NONE;
+}
+
+// Read the second sync byte
+uint8_t ubloxSync2(PARSE_STATE *parse, uint8_t data)
+{
+ // Verify the sync 2 byte
+ if (data != 0x62)
+ {
+ // Display the invalid data
+ if (settings.enablePrintBadMessages && (!inMainMenu))
+ printUbloxInvalidData(parse);
+
+ // Invalid sync 2 byte, place this byte at the beginning of the buffer
+ parse->length = 0;
+ parse->buffer[parse->length++] = data;
+
+ // Start searching for a preamble byte
+ return gpsMessageParserFirstByte(parse, data);
+ }
+
+ parse->state = ubloxClass;
+ return SENTENCE_TYPE_UBX;
+}
+
+// Read the class byte
+uint8_t ubloxClass(PARSE_STATE *parse, uint8_t data)
+{
+ // Start the checksum calculation
+ parse->ck_a = data;
+ parse->ck_b = data;
+
+ // Save the class as the upper 8-bits of the message
+ parse->message = ((uint16_t)data) << 8;
+ parse->state = ubloxId;
+ return SENTENCE_TYPE_UBX;
+}
+
+// Read the ID byte
+uint8_t ubloxId(PARSE_STATE *parse, uint8_t data)
+{
+ // Calculate the checksum
+ parse->ck_a += data;
+ parse->ck_b += parse->ck_a;
+
+ // Save the ID as the lower 8-bits of the message
+ parse->message |= data;
+ parse->state = ubloxLength1;
+ return SENTENCE_TYPE_UBX;
+}
+
+// Read the first length byte
+uint8_t ubloxLength1(PARSE_STATE *parse, uint8_t data)
+{
+ // Calculate the checksum
+ parse->ck_a += data;
+ parse->ck_b += parse->ck_a;
+
+ // Save the first length byte
+ parse->bytesRemaining = data;
+ parse->state = ubloxLength2;
+ return SENTENCE_TYPE_UBX;
+}
+
+// Read the second length byte
+uint8_t ubloxLength2(PARSE_STATE *parse, uint8_t data)
+{
+ // Calculate the checksum
+ parse->ck_a += data;
+ parse->ck_b += parse->ck_a;
+
+ // Save the second length byte
+ parse->bytesRemaining |= ((uint16_t)data) << 8;
+ parse->state = ubloxPayload;
+ return SENTENCE_TYPE_UBX;
+}
+
+// Read the payload
+uint8_t ubloxPayload(PARSE_STATE *parse, uint8_t data)
+{
+ // Compute the checksum over the payload
+ if (parse->bytesRemaining--)
+ {
+ // Calculate the checksum
+ parse->ck_a += data;
+ parse->ck_b += parse->ck_a;
+ return SENTENCE_TYPE_UBX;
+ }
+ return ubloxCkA(parse, data);
+}
+
+// Read the CK_A byte
+uint8_t ubloxCkA(PARSE_STATE *parse, uint8_t data)
+{
+ parse->state = ubloxCkB;
+ return SENTENCE_TYPE_UBX;
+}
+
+// Read the CK_B byte
+uint8_t ubloxCkB(PARSE_STATE *parse, uint8_t data)
+{
+ bool badChecksum;
+
+ // Validate the checksum
+ badChecksum =
+ ((parse->buffer[parse->length - 2] != parse->ck_a) || (parse->buffer[parse->length - 1] != parse->ck_b));
+ if (settings.enablePrintBadMessages && badChecksum && (!inMainMenu))
+ printUbloxChecksumError(parse);
+
+ // Process this message if checksum is valid
+ if (badChecksum == false)
+ parse->eomCallback(parse, SENTENCE_TYPE_UBX);
+ else
+ failedParserMessages_UBX++;
+
+ // Search for the next preamble byte
+ parse->length = 0;
+ parse->state = gpsMessageParserFirstByte;
+ return SENTENCE_TYPE_NONE;
+}
diff --git a/Firmware/RTK_Surveyor/Patch/Server.h b/Firmware/RTK_Surveyor/Patch/Server.h
new file mode 100644
index 000000000..fc482ecc3
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Patch/Server.h
@@ -0,0 +1,32 @@
+/*
+ Server.h - Base class that provides Server
+ Copyright (c) 2011 Adrian McEwen. All right reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef server_h
+#define server_h
+
+#include "Print.h"
+
+class Server: public Print
+{
+public:
+ //virtual void begin(uint16_t port=0) =0;
+ void begin() {};
+};
+
+#endif
diff --git a/Firmware/RTK_Surveyor/Patch/WiFiGeneric.cpp b/Firmware/RTK_Surveyor/Patch/WiFiGeneric.cpp
new file mode 100644
index 000000000..9104eab68
--- /dev/null
+++ b/Firmware/RTK_Surveyor/Patch/WiFiGeneric.cpp
@@ -0,0 +1,1484 @@
+/*
+ ESP8266WiFiGeneric.cpp - WiFi library for esp8266
+
+ Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ Reworked on 28 Dec 2015 by Markus Sattler
+
+ */
+
+#include "WiFi.h"
+#include "WiFiGeneric.h"
+
+extern "C" {
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include "lwip/ip_addr.h"
+#include "lwip/opt.h"
+#include "lwip/err.h"
+#include "lwip/dns.h"
+#include "dhcpserver/dhcpserver_options.h"
+
+} //extern "C"
+
+#include "esp32-hal.h"
+#include
+#include "sdkconfig.h"
+
+#define _byte_swap32(num) (((num>>24)&0xff) | ((num<<8)&0xff0000) | ((num>>8)&0xff00) | ((num<<24)&0xff000000))
+ESP_EVENT_DEFINE_BASE(ARDUINO_EVENTS);
+/*
+ * Private (exposable) methods
+ * */
+static esp_netif_t* esp_netifs[ESP_IF_MAX] = {nullptr, nullptr, nullptr};
+esp_interface_t get_esp_netif_interface(esp_netif_t* esp_netif){
+ for(int i=0; i(local_ip);
+ info.gw.addr = static_cast(gateway);
+ info.netmask.addr = static_cast(subnet);
+
+ log_v("Configuring %s static IP: " IPSTR ", MASK: " IPSTR ", GW: " IPSTR,
+ interface == ESP_IF_WIFI_STA ? "Station" :
+ interface == ESP_IF_WIFI_AP ? "SoftAP" : "Ethernet",
+ IP2STR(&info.ip), IP2STR(&info.netmask), IP2STR(&info.gw));
+
+ esp_err_t err = ESP_OK;
+ if(interface != ESP_IF_WIFI_AP){
+ err = esp_netif_dhcpc_get_status(esp_netif, &status);
+ if(err){
+ log_e("DHCPC Get Status Failed! 0x%04x", err);
+ return err;
+ }
+ err = esp_netif_dhcpc_stop(esp_netif);
+ if(err && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED){
+ log_e("DHCPC Stop Failed! 0x%04x", err);
+ return err;
+ }
+ err = esp_netif_set_ip_info(esp_netif, &info);
+ if(err){
+ log_e("Netif Set IP Failed! 0x%04x", err);
+ return err;
+ }
+ if(info.ip.addr == 0){
+ err = esp_netif_dhcpc_start(esp_netif);
+ if(err){
+ log_e("DHCPC Start Failed! 0x%04x", err);
+ return err;
+ }
+ }
+ } else {
+ err = esp_netif_dhcps_get_status(esp_netif, &status);
+ if(err){
+ log_e("DHCPS Get Status Failed! 0x%04x", err);
+ return err;
+ }
+ err = esp_netif_dhcps_stop(esp_netif);
+ if(err && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED){
+ log_e("DHCPS Stop Failed! 0x%04x", err);
+ return err;
+ }
+ err = esp_netif_set_ip_info(esp_netif, &info);
+ if(err){
+ log_e("Netif Set IP Failed! 0x%04x", err);
+ return err;
+ }
+
+ dhcps_lease_t lease;
+ lease.enable = true;
+ uint8_t CIDR = WiFiGenericClass::calculateSubnetCIDR(subnet);
+ log_v("SoftAP: %s | Gateway: %s | DHCP Start: %s | Netmask: %s", local_ip.toString().c_str(), gateway.toString().c_str(), dhcp_lease_start.toString().c_str(), subnet.toString().c_str());
+ // netmask must have room for at least 12 IP addresses (AP + GW + 10 DHCP Leasing addresses)
+ // netmask also must be limited to the last 8 bits of IPv4, otherwise this function won't work
+ // IDF NETIF checks netmask for the 3rd byte: https://github.com/espressif/esp-idf/blob/master/components/esp_netif/lwip/esp_netif_lwip.c#L1857-L1862
+ if (CIDR > 28 || CIDR < 24) {
+ log_e("Bad netmask. It must be from /24 to /28 (255.255.255. 0<->240)");
+ return ESP_FAIL; // ESP_FAIL if initializing failed
+ }
+ // The code below is ready for any netmask, not limited to 255.255.255.0
+ uint32_t netmask = _byte_swap32(info.netmask.addr);
+ uint32_t ap_ipaddr = _byte_swap32(info.ip.addr);
+ uint32_t dhcp_ipaddr = _byte_swap32(static_cast(dhcp_lease_start));
+ dhcp_ipaddr = dhcp_ipaddr == 0 ? ap_ipaddr + 1 : dhcp_ipaddr;
+ uint32_t leaseStartMax = ~netmask - 10;
+ // there will be 10 addresses for DHCP to lease
+ lease.start_ip.addr = dhcp_ipaddr;
+ lease.end_ip.addr = lease.start_ip.addr + 10;
+ // Check if local_ip is in the same subnet as the dhcp leasing range initial address
+ if ((ap_ipaddr & netmask) != (dhcp_ipaddr & netmask)) {
+ log_e("The AP IP address (%s) and the DHCP start address (%s) must be in the same subnet",
+ local_ip.toString().c_str(), IPAddress(_byte_swap32(dhcp_ipaddr)).toString().c_str());
+ return ESP_FAIL; // ESP_FAIL if initializing failed
+ }
+ // prevents DHCP lease range to overflow subnet range
+ if ((dhcp_ipaddr & ~netmask) >= leaseStartMax) {
+ // make first DHCP lease addr stay in the begining of the netmask range
+ lease.start_ip.addr = (dhcp_ipaddr & netmask) + 1;
+ lease.end_ip.addr = lease.start_ip.addr + 10;
+ log_w("DHCP Lease out of range - Changing DHCP leasing start to %s", IPAddress(_byte_swap32(lease.start_ip.addr)).toString().c_str());
+ }
+ // Check if local_ip is within DHCP range
+ if (ap_ipaddr >= lease.start_ip.addr && ap_ipaddr <= lease.end_ip.addr) {
+ log_e("The AP IP address (%s) can't be within the DHCP range (%s -- %s)",
+ local_ip.toString().c_str(), IPAddress(_byte_swap32(lease.start_ip.addr)).toString().c_str(), IPAddress(_byte_swap32(lease.end_ip.addr)).toString().c_str());
+ return ESP_FAIL; // ESP_FAIL if initializing failed
+ }
+ // Check if gateway is within DHCP range
+ uint32_t gw_ipaddr = _byte_swap32(info.gw.addr);
+ bool gw_in_same_subnet = (gw_ipaddr & netmask) == (ap_ipaddr & netmask);
+ if (gw_in_same_subnet && gw_ipaddr >= lease.start_ip.addr && gw_ipaddr <= lease.end_ip.addr) {
+ log_e("The GatewayP address (%s) can't be within the DHCP range (%s -- %s)",
+ gateway.toString().c_str(), IPAddress(_byte_swap32(lease.start_ip.addr)).toString().c_str(), IPAddress(_byte_swap32(lease.end_ip.addr)).toString().c_str());
+ return ESP_FAIL; // ESP_FAIL if initializing failed
+ }
+ // all done, just revert back byte order of DHCP lease range
+ lease.start_ip.addr = _byte_swap32(lease.start_ip.addr);
+ lease.end_ip.addr = _byte_swap32(lease.end_ip.addr);
+ log_v("DHCP Server Range: %s to %s", IPAddress(lease.start_ip.addr).toString().c_str(), IPAddress(lease.end_ip.addr).toString().c_str());
+ err = tcpip_adapter_dhcps_option(
+ (tcpip_adapter_dhcp_option_mode_t)TCPIP_ADAPTER_OP_SET,
+ (tcpip_adapter_dhcp_option_id_t)ESP_NETIF_SUBNET_MASK,
+ (void*)&info.netmask.addr, sizeof(info.netmask.addr)
+ );
+ if(err){
+ log_e("DHCPS Set Netmask Failed! 0x%04x", err);
+ return err;
+ }
+ err = tcpip_adapter_dhcps_option(
+ (tcpip_adapter_dhcp_option_mode_t)TCPIP_ADAPTER_OP_SET,
+ (tcpip_adapter_dhcp_option_id_t)REQUESTED_IP_ADDRESS,
+ (void*)&lease, sizeof(dhcps_lease_t)
+ );
+ if(err){
+ log_e("DHCPS Set Lease Failed! 0x%04x", err);
+ return err;
+ }
+ err = esp_netif_dhcps_start(esp_netif);
+ if(err){
+ log_e("DHCPS Start Failed! 0x%04x", err);
+ return err;
+ }
+ }
+ return err;
+}
+
+esp_err_t set_esp_interface_dns(esp_interface_t interface, IPAddress main_dns=IPAddress(), IPAddress backup_dns=IPAddress(), IPAddress fallback_dns=IPAddress()){
+ esp_netif_t *esp_netif = esp_netifs[interface];
+ esp_netif_dns_info_t dns;
+ dns.ip.type = ESP_IPADDR_TYPE_V4;
+ dns.ip.u_addr.ip4.addr = static_cast(main_dns);
+ if(dns.ip.u_addr.ip4.addr && esp_netif_set_dns_info(esp_netif, ESP_NETIF_DNS_MAIN, &dns) != ESP_OK){
+ log_e("Set Main DNS Failed!");
+ return ESP_FAIL;
+ }
+ if(interface != ESP_IF_WIFI_AP){
+ dns.ip.u_addr.ip4.addr = static_cast(backup_dns);
+ if(dns.ip.u_addr.ip4.addr && esp_netif_set_dns_info(esp_netif, ESP_NETIF_DNS_BACKUP, &dns) != ESP_OK){
+ log_e("Set Backup DNS Failed!");
+ return ESP_FAIL;
+ }
+ dns.ip.u_addr.ip4.addr = static_cast(fallback_dns);
+ if(dns.ip.u_addr.ip4.addr && esp_netif_set_dns_info(esp_netif, ESP_NETIF_DNS_FALLBACK, &dns) != ESP_OK){
+ log_e("Set Fallback DNS Failed!");
+ return ESP_FAIL;
+ }
+ }
+ return ESP_OK;
+}
+
+#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+static const char * auth_mode_str(int authmode)
+{
+ switch (authmode) {
+ case WIFI_AUTH_OPEN:
+ return ("OPEN");
+ break;
+ case WIFI_AUTH_WEP:
+ return ("WEP");
+ break;
+ case WIFI_AUTH_WPA_PSK:
+ return ("WPA_PSK");
+ break;
+ case WIFI_AUTH_WPA2_PSK:
+ return ("WPA2_PSK");
+ break;
+ case WIFI_AUTH_WPA_WPA2_PSK:
+ return ("WPA_WPA2_PSK");
+ break;
+ case WIFI_AUTH_WPA2_ENTERPRISE:
+ return ("WPA2_ENTERPRISE");
+ break;
+ case WIFI_AUTH_WPA3_PSK:
+ return ("WPA3_PSK");
+ break;
+ case WIFI_AUTH_WPA2_WPA3_PSK:
+ return ("WPA2_WPA3_PSK");
+ break;
+ case WIFI_AUTH_WAPI_PSK:
+ return ("WPAPI_PSK");
+ break;
+ default:
+ break;
+ }
+ return ("UNKNOWN");
+}
+#endif
+
+static char default_hostname[32] = {0,};
+static const char * get_esp_netif_hostname(){
+ if(default_hostname[0] == 0){
+ uint8_t eth_mac[6];
+ esp_wifi_get_mac((wifi_interface_t)WIFI_IF_STA, eth_mac);
+ snprintf(default_hostname, 32, "%s%02X%02X%02X", CONFIG_IDF_TARGET "-", eth_mac[3], eth_mac[4], eth_mac[5]);
+ }
+ return (const char *)default_hostname;
+}
+static void set_esp_netif_hostname(const char * name){
+ if(name){
+ snprintf(default_hostname, 32, "%s", name);
+ }
+}
+
+static xQueueHandle _arduino_event_queue;
+static TaskHandle_t _arduino_event_task_handle = nullptr;
+static EventGroupHandle_t _arduino_event_group = nullptr;
+
+static void _arduino_event_task(void * arg){
+ arduino_event_t *data = nullptr;
+ for (;;) {
+ if(xQueueReceive(_arduino_event_queue, &data, portMAX_DELAY) == pdTRUE){
+ WiFiGenericClass::_eventCallback(data);
+ free(data);
+ data = nullptr;
+ }
+ }
+ vTaskDelete(nullptr);
+ _arduino_event_task_handle = nullptr;
+}
+
+esp_err_t postArduinoEvent(arduino_event_t *data)
+{
+ if(data == nullptr){
+ return ESP_FAIL;
+ }
+ arduino_event_t * event = (arduino_event_t*)malloc(sizeof(arduino_event_t));
+ if(event == nullptr){
+ log_e("Arduino Event Malloc Failed!");
+ return ESP_FAIL;
+ }
+ memcpy(event, data, sizeof(arduino_event_t));
+ if (xQueueSend(_arduino_event_queue, &event, portMAX_DELAY) != pdPASS) {
+ log_e("Arduino Event Send Failed!");
+ return ESP_FAIL;
+ }
+ return ESP_OK;
+}
+
+static void _arduino_event_cb(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
+ arduino_event_t arduino_event;
+ arduino_event.event_id = ARDUINO_EVENT_MAX;
+
+ /*
+ * STA
+ * */
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
+ log_v("STA Started");
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_START;
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) {
+ log_v("STA Stopped");
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_STOP;
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_sta_authmode_change_t * event = (wifi_event_sta_authmode_change_t*)event_data;
+ log_v("STA Auth Mode Changed: From: %s, To: %s", auth_mode_str(event->old_mode), auth_mode_str(event->new_mode));
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE;
+ memcpy(&arduino_event.event_info.wifi_sta_authmode_change, event_data, sizeof(wifi_event_sta_authmode_change_t));
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_sta_connected_t * event = (wifi_event_sta_connected_t*)event_data;
+ log_v("STA Connected: SSID: %s, BSSID: " MACSTR ", Channel: %u, Auth: %s", event->ssid, MAC2STR(event->bssid), event->channel, auth_mode_str(event->authmode));
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_CONNECTED;
+ memcpy(&arduino_event.event_info.wifi_sta_connected, event_data, sizeof(wifi_event_sta_connected_t));
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_sta_disconnected_t * event = (wifi_event_sta_disconnected_t*)event_data;
+ log_v("STA Disconnected: SSID: %s, BSSID: " MACSTR ", Reason: %u", event->ssid, MAC2STR(event->bssid), event->reason);
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_DISCONNECTED;
+ memcpy(&arduino_event.event_info.wifi_sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t));
+ } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
+ log_v("STA Got %sIP:" IPSTR, event->ip_changed?"New ":"Same ", IP2STR(&event->ip_info.ip));
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_GOT_IP;
+ memcpy(&arduino_event.event_info.got_ip, event_data, sizeof(ip_event_got_ip_t));
+ } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) {
+ log_v("STA IP Lost");
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_LOST_IP;
+
+ /*
+ * SCAN
+ * */
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_sta_scan_done_t * event = (wifi_event_sta_scan_done_t*)event_data;
+ log_v("SCAN Done: ID: %u, Status: %u, Results: %u", event->scan_id, event->status, event->number);
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_SCAN_DONE;
+ memcpy(&arduino_event.event_info.wifi_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t));
+
+ /*
+ * AP
+ * */
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) {
+ log_v("AP Started");
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_START;
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) {
+ log_v("AP Stopped");
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_STOP;
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_PROBEREQRECVED) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_ap_probe_req_rx_t * event = (wifi_event_ap_probe_req_rx_t*)event_data;
+ log_v("AP Probe Request: RSSI: %d, MAC: " MACSTR, event->rssi, MAC2STR(event->mac));
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED;
+ memcpy(&arduino_event.event_info.wifi_ap_probereqrecved, event_data, sizeof(wifi_event_ap_probe_req_rx_t));
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
+ log_v("AP Station Connected: MAC: " MACSTR ", AID: %d", MAC2STR(event->mac), event->aid);
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_STACONNECTED;
+ memcpy(&arduino_event.event_info.wifi_ap_staconnected, event_data, sizeof(wifi_event_ap_staconnected_t));
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
+ log_v("AP Station Disconnected: MAC: " MACSTR ", AID: %d", MAC2STR(event->mac), event->aid);
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_STADISCONNECTED;
+ memcpy(&arduino_event.event_info.wifi_ap_stadisconnected, event_data, sizeof(wifi_event_ap_stadisconnected_t));
+ } else if (event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ ip_event_ap_staipassigned_t * event = (ip_event_ap_staipassigned_t*)event_data;
+ log_v("AP Station IP Assigned:" IPSTR, IP2STR(&event->ip));
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED;
+ memcpy(&arduino_event.event_info.wifi_ap_staipassigned, event_data, sizeof(ip_event_ap_staipassigned_t));
+
+ /*
+ * ETH
+ * */
+ } else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_CONNECTED) {
+ log_v("Ethernet Link Up");
+ arduino_event.event_id = ARDUINO_EVENT_ETH_CONNECTED;
+ memcpy(&arduino_event.event_info.eth_connected, event_data, sizeof(esp_eth_handle_t));
+ } else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_DISCONNECTED) {
+ log_v("Ethernet Link Down");
+ arduino_event.event_id = ARDUINO_EVENT_ETH_DISCONNECTED;
+ } else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_START) {
+ log_v("Ethernet Started");
+ arduino_event.event_id = ARDUINO_EVENT_ETH_START;
+ } else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_STOP) {
+ log_v("Ethernet Stopped");
+ arduino_event.event_id = ARDUINO_EVENT_ETH_STOP;
+ } else if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
+ log_v("Ethernet got %sip:" IPSTR, event->ip_changed?"new":"", IP2STR(&event->ip_info.ip));
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_ETH_GOT_IP;
+ memcpy(&arduino_event.event_info.got_ip, event_data, sizeof(ip_event_got_ip_t));
+
+ /*
+ * IPv6
+ * */
+ } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) {
+ ip_event_got_ip6_t * event = (ip_event_got_ip6_t*)event_data;
+ esp_interface_t iface = get_esp_netif_interface(event->esp_netif);
+ log_v("IF[%d] Got IPv6: IP Index: %d, Zone: %d, " IPV6STR, iface, event->ip_index, event->ip6_info.ip.zone, IPV62STR(event->ip6_info.ip));
+ memcpy(&arduino_event.event_info.got_ip6, event_data, sizeof(ip_event_got_ip6_t));
+ if(iface == ESP_IF_WIFI_STA){
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_GOT_IP6;
+ } else if(iface == ESP_IF_WIFI_AP){
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_AP_GOT_IP6;
+ } else if(iface == ESP_IF_ETH){
+ arduino_event.event_id = ARDUINO_EVENT_ETH_GOT_IP6;
+ }
+
+ /*
+ * WPS
+ * */
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_SUCCESS) {
+ arduino_event.event_id = ARDUINO_EVENT_WPS_ER_SUCCESS;
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_FAILED) {
+ arduino_event.event_id = ARDUINO_EVENT_WPS_ER_FAILED;
+ memcpy(&arduino_event.event_info.wps_fail_reason, event_data, sizeof(wifi_event_sta_wps_fail_reason_t));
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_TIMEOUT) {
+ arduino_event.event_id = ARDUINO_EVENT_WPS_ER_TIMEOUT;
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_PIN) {
+ arduino_event.event_id = ARDUINO_EVENT_WPS_ER_PIN;
+ memcpy(&arduino_event.event_info.wps_er_pin, event_data, sizeof(wifi_event_sta_wps_er_pin_t));
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_PBC_OVERLAP) {
+ arduino_event.event_id = ARDUINO_EVENT_WPS_ER_PBC_OVERLAP;
+
+ /*
+ * FTM
+ * */
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_FTM_REPORT) {
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_FTM_REPORT;
+ memcpy(&arduino_event.event_info.wifi_ftm_report, event_data, sizeof(wifi_event_ftm_report_t));
+
+
+ /*
+ * SMART CONFIG
+ * */
+ } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
+ log_v("SC Scan Done");
+ arduino_event.event_id = ARDUINO_EVENT_SC_SCAN_DONE;
+ } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
+ log_v("SC Found Channel");
+ arduino_event.event_id = ARDUINO_EVENT_SC_FOUND_CHANNEL;
+ } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR
+ smartconfig_event_got_ssid_pswd_t *event = (smartconfig_event_got_ssid_pswd_t *)event_data;
+ log_v("SC: SSID: %s, Password: %s", (const char *)event->ssid, (const char *)event->password);
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_SC_GOT_SSID_PSWD;
+ memcpy(&arduino_event.event_info.sc_got_ssid_pswd, event_data, sizeof(smartconfig_event_got_ssid_pswd_t));
+
+ } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
+ log_v("SC Send Ack Done");
+ arduino_event.event_id = ARDUINO_EVENT_SC_SEND_ACK_DONE;
+
+ /*
+ * Provisioning
+ * */
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_INIT) {
+ log_v("Provisioning Initialized!");
+ arduino_event.event_id = ARDUINO_EVENT_PROV_INIT;
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_DEINIT) {
+ log_v("Provisioning Uninitialized!");
+ arduino_event.event_id = ARDUINO_EVENT_PROV_DEINIT;
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_START) {
+ log_v("Provisioning Start!");
+ arduino_event.event_id = ARDUINO_EVENT_PROV_START;
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_END) {
+ log_v("Provisioning End!");
+ wifi_prov_mgr_deinit();
+ arduino_event.event_id = ARDUINO_EVENT_PROV_END;
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_CRED_RECV) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
+ wifi_sta_config_t *event = (wifi_sta_config_t *)event_data;
+ log_v("Provisioned Credentials: SSID: %s, Password: %s", (const char *) event->ssid, (const char *) event->password);
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_PROV_CRED_RECV;
+ memcpy(&arduino_event.event_info.prov_cred_recv, event_data, sizeof(wifi_sta_config_t));
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_CRED_FAIL) {
+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR
+ wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
+ log_e("Provisioning Failed: Reason : %s", (*reason == WIFI_PROV_STA_AUTH_ERROR)?"Authentication Failed":"AP Not Found");
+ #endif
+ arduino_event.event_id = ARDUINO_EVENT_PROV_CRED_FAIL;
+ memcpy(&arduino_event.event_info.prov_fail_reason, event_data, sizeof(wifi_prov_sta_fail_reason_t));
+ } else if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_CRED_SUCCESS) {
+ log_v("Provisioning Success!");
+ arduino_event.event_id = ARDUINO_EVENT_PROV_CRED_SUCCESS;
+ }
+
+ if(arduino_event.event_id < ARDUINO_EVENT_MAX){
+ postArduinoEvent(&arduino_event);
+ }
+}
+
+static bool _start_network_event_task(){
+ if(!_arduino_event_group){
+ _arduino_event_group = xEventGroupCreate();
+ if(!_arduino_event_group){
+ log_e("Network Event Group Create Failed!");
+ return false;
+ }
+ xEventGroupSetBits(_arduino_event_group, WIFI_DNS_IDLE_BIT);
+ }
+ if(!_arduino_event_queue){
+ _arduino_event_queue = xQueueCreate(32, sizeof(arduino_event_t*));
+ if(!_arduino_event_queue){
+ log_e("Network Event Queue Create Failed!");
+ return false;
+ }
+ }
+
+ esp_err_t err = esp_event_loop_create_default();
+ if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
+ log_e("esp_event_loop_create_default failed!");
+ return err;
+ }
+
+ if(!_arduino_event_task_handle){
+ xTaskCreateUniversal(_arduino_event_task, "arduino_events", 4096, nullptr, ESP_TASKD_EVENT_PRIO - 1, &_arduino_event_task_handle, ARDUINO_EVENT_RUNNING_CORE);
+ if(!_arduino_event_task_handle){
+ log_e("Network Event Task Start Failed!");
+ return false;
+ }
+ }
+
+ if(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, nullptr, nullptr)){
+ log_e("event_handler_instance_register for WIFI_EVENT Failed!");
+ return false;
+ }
+
+ if(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, nullptr, nullptr)){
+ log_e("event_handler_instance_register for IP_EVENT Failed!");
+ return false;
+ }
+
+ if(esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, nullptr, nullptr)){
+ log_e("event_handler_instance_register for SC_EVENT Failed!");
+ return false;
+ }
+
+ if(esp_event_handler_instance_register(ETH_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, nullptr, nullptr)){
+ log_e("event_handler_instance_register for ETH_EVENT Failed!");
+ return false;
+ }
+
+ if(esp_event_handler_instance_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, nullptr, nullptr)){
+ log_e("event_handler_instance_register for WIFI_PROV_EVENT Failed!");
+ return false;
+ }
+
+ return true;
+}
+
+bool tcpipInit(){
+ static bool initialized = false;
+ if(!initialized){
+ initialized = true;
+#if CONFIG_IDF_TARGET_ESP32
+ uint8_t mac[8];
+ if(esp_efuse_mac_get_default(mac) == ESP_OK){
+ esp_base_mac_addr_set(mac);
+ }
+#endif
+ initialized = esp_netif_init() == ESP_OK;
+ if(initialized){
+ initialized = _start_network_event_task();
+ } else {
+ log_e("esp_netif_init failed!");
+ }
+ }
+ return initialized;
+}
+
+/*
+ * WiFi INIT
+ * */
+
+static bool lowLevelInitDone = false;
+bool WiFiGenericClass::_wifiUseStaticBuffers = false;
+
+bool WiFiGenericClass::useStaticBuffers(){
+ return _wifiUseStaticBuffers;
+}
+
+void WiFiGenericClass::useStaticBuffers(bool bufferMode){
+ if (lowLevelInitDone) {
+ log_w("WiFi already started. Call WiFi.mode(WIFI_MODE_NULL) before setting Static Buffer Mode.");
+ }
+ _wifiUseStaticBuffers = bufferMode;
+}
+
+// Temporary fix to ensure that CDC+JTAG stay on on ESP32-C3
+#if CONFIG_IDF_TARGET_ESP32C3
+extern "C" void phy_bbpll_en_usb(bool en);
+#endif
+
+bool wifiLowLevelInit(bool persistent){
+ if(!lowLevelInitDone){
+ lowLevelInitDone = true;
+ if(!tcpipInit()){
+ lowLevelInitDone = false;
+ return lowLevelInitDone;
+ }
+ if(esp_netifs[ESP_IF_WIFI_AP] == nullptr){
+ esp_netifs[ESP_IF_WIFI_AP] = esp_netif_create_default_wifi_ap();
+ }
+ if(esp_netifs[ESP_IF_WIFI_STA] == nullptr){
+ esp_netifs[ESP_IF_WIFI_STA] = esp_netif_create_default_wifi_sta();
+ }
+
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+
+ if(!WiFiGenericClass::useStaticBuffers()) {
+ cfg.static_tx_buf_num = 0;
+ cfg.dynamic_tx_buf_num = 32;
+ cfg.tx_buf_type = 1;
+ cfg.cache_tx_buf_num = 4; // can't be zero!
+ cfg.static_rx_buf_num = 4;
+ cfg.dynamic_rx_buf_num = 32;
+ }
+
+ esp_err_t err = esp_wifi_init(&cfg);
+ if(err){
+ log_e("esp_wifi_init %d", err);
+ lowLevelInitDone = false;
+ return lowLevelInitDone;
+ }
+// Temporary fix to ensure that CDC+JTAG stay on on ESP32-C3
+#if CONFIG_IDF_TARGET_ESP32C3
+ phy_bbpll_en_usb(true);
+#endif
+ if(!persistent){
+ lowLevelInitDone = esp_wifi_set_storage(WIFI_STORAGE_RAM) == ESP_OK;
+ }
+ if(lowLevelInitDone){
+ arduino_event_t arduino_event;
+ arduino_event.event_id = ARDUINO_EVENT_WIFI_READY;
+ postArduinoEvent(&arduino_event);
+ }
+ }
+ return lowLevelInitDone;
+}
+
+static bool wifiLowLevelDeinit(){
+ if(lowLevelInitDone){
+ lowLevelInitDone = !(esp_wifi_deinit() == ESP_OK);
+ }
+ return !lowLevelInitDone;
+}
+
+static bool _esp_wifi_started = false;
+
+static bool espWiFiStart(){
+ if(_esp_wifi_started){
+ return true;
+ }
+ _esp_wifi_started = true;
+ esp_err_t err = esp_wifi_start();
+ if (err != ESP_OK) {
+ _esp_wifi_started = false;
+ log_e("esp_wifi_start %d", err);
+ return _esp_wifi_started;
+ }
+ return _esp_wifi_started;
+}
+
+static bool espWiFiStop(){
+ esp_err_t err;
+ if(!_esp_wifi_started){
+ return true;
+ }
+ _esp_wifi_started = false;
+ err = esp_wifi_stop();
+ if(err){
+ log_e("Could not stop WiFi! %d", err);
+ _esp_wifi_started = true;
+ return false;
+ }
+ return wifiLowLevelDeinit();
+}
+
+// -----------------------------------------------------------------------------------------------------------------------
+// ------------------------------------------------- Generic WiFi function -----------------------------------------------
+// -----------------------------------------------------------------------------------------------------------------------
+
+typedef struct WiFiEventCbList {
+ static wifi_event_id_t current_id;
+ wifi_event_id_t id;
+ WiFiEventCb cb;
+ WiFiEventFuncCb fcb;
+ WiFiEventSysCb scb;
+ arduino_event_id_t event;
+
+ WiFiEventCbList() : id(current_id++), cb(nullptr), fcb(nullptr), scb(nullptr), event(ARDUINO_EVENT_WIFI_READY) {}
+} WiFiEventCbList_t;
+wifi_event_id_t WiFiEventCbList::current_id = 1;
+
+
+// arduino dont like std::vectors move static here
+static std::vector cbEventList;
+
+bool WiFiGenericClass::_persistent = true;
+bool WiFiGenericClass::_long_range = false;
+wifi_mode_t WiFiGenericClass::_forceSleepLastMode = WIFI_MODE_NULL;
+#if CONFIG_IDF_TARGET_ESP32S2
+wifi_ps_type_t WiFiGenericClass::_sleepEnabled = WIFI_PS_NONE;
+#else
+wifi_ps_type_t WiFiGenericClass::_sleepEnabled = WIFI_PS_MIN_MODEM;
+#endif
+
+WiFiGenericClass::WiFiGenericClass()
+{
+}
+
+const char * WiFiGenericClass::getHostname()
+{
+ return get_esp_netif_hostname();
+}
+
+bool WiFiGenericClass::setHostname(const char * hostname)
+{
+ set_esp_netif_hostname(hostname);
+ return true;
+}
+
+int WiFiGenericClass::setStatusBits(int bits){
+ if(!_arduino_event_group){
+ return 0;
+ }
+ return xEventGroupSetBits(_arduino_event_group, bits);
+}
+
+int WiFiGenericClass::clearStatusBits(int bits){
+ if(!_arduino_event_group){
+ return 0;
+ }
+ return xEventGroupClearBits(_arduino_event_group, bits);
+}
+
+int WiFiGenericClass::getStatusBits(){
+ if(!_arduino_event_group){
+ return 0;
+ }
+ return xEventGroupGetBits(_arduino_event_group);
+}
+
+int WiFiGenericClass::waitStatusBits(int bits, uint32_t timeout_ms){
+ if(!_arduino_event_group){
+ return 0;
+ }
+ return xEventGroupWaitBits(
+ _arduino_event_group, // The event group being tested.
+ bits, // The bits within the event group to wait for.
+ pdFALSE, // BIT_0 and BIT_4 should be cleared before returning.
+ pdTRUE, // Don't wait for both bits, either bit will do.
+ timeout_ms / portTICK_PERIOD_MS ) & bits; // Wait a maximum of 100ms for either bit to be set.
+}
+
+/**
+ * set callback function
+ * @param cbEvent WiFiEventCb
+ * @param event optional filter (WIFI_EVENT_MAX is all events)
+ */
+wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventCb cbEvent, arduino_event_id_t event)
+{
+ if(!cbEvent) {
+ return 0;
+ }
+ WiFiEventCbList_t newEventHandler;
+ newEventHandler.cb = cbEvent;
+ newEventHandler.fcb = nullptr;
+ newEventHandler.scb = nullptr;
+ newEventHandler.event = event;
+ cbEventList.push_back(newEventHandler);
+ return newEventHandler.id;
+}
+
+wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventFuncCb cbEvent, arduino_event_id_t event)
+{
+ if(!cbEvent) {
+ return 0;
+ }
+ WiFiEventCbList_t newEventHandler;
+ newEventHandler.cb = nullptr;
+ newEventHandler.fcb = cbEvent;
+ newEventHandler.scb = nullptr;
+ newEventHandler.event = event;
+ cbEventList.push_back(newEventHandler);
+ return newEventHandler.id;
+}
+
+wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventSysCb cbEvent, arduino_event_id_t event)
+{
+ if(!cbEvent) {
+ return 0;
+ }
+ WiFiEventCbList_t newEventHandler;
+ newEventHandler.cb = nullptr;
+ newEventHandler.fcb = nullptr;
+ newEventHandler.scb = cbEvent;
+ newEventHandler.event = event;
+ cbEventList.push_back(newEventHandler);
+ return newEventHandler.id;
+}
+
+/**
+ * removes a callback form event handler
+ * @param cbEvent WiFiEventCb
+ * @param event optional filter (WIFI_EVENT_MAX is all events)
+ */
+void WiFiGenericClass::removeEvent(WiFiEventCb cbEvent, arduino_event_id_t event)
+{
+ if(!cbEvent) {
+ return;
+ }
+
+ for(uint32_t i = 0; i < cbEventList.size(); i++) {
+ WiFiEventCbList_t entry = cbEventList[i];
+ if(entry.cb == cbEvent && entry.event == event) {
+ cbEventList.erase(cbEventList.begin() + i);
+ }
+ }
+}
+
+void WiFiGenericClass::removeEvent(WiFiEventSysCb cbEvent, arduino_event_id_t event)
+{
+ if(!cbEvent) {
+ return;
+ }
+
+ for(uint32_t i = 0; i < cbEventList.size(); i++) {
+ WiFiEventCbList_t entry = cbEventList[i];
+ if(entry.scb == cbEvent && entry.event == event) {
+ cbEventList.erase(cbEventList.begin() + i);
+ }
+ }
+}
+
+void WiFiGenericClass::removeEvent(wifi_event_id_t id)
+{
+ for(uint32_t i = 0; i < cbEventList.size(); i++) {
+ WiFiEventCbList_t entry = cbEventList[i];
+ if(entry.id == id) {
+ cbEventList.erase(cbEventList.begin() + i);
+ }
+ }
+}
+
+/**
+ * callback for WiFi events
+ * @param arg
+ */
+#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
+const char * arduino_event_names[] = {
+ "WIFI_READY",
+ "SCAN_DONE",
+ "STA_START", "STA_STOP", "STA_CONNECTED", "STA_DISCONNECTED", "STA_AUTHMODE_CHANGE", "STA_GOT_IP", "STA_GOT_IP6", "STA_LOST_IP",
+ "AP_START", "AP_STOP", "AP_STACONNECTED", "AP_STADISCONNECTED", "AP_STAIPASSIGNED", "AP_PROBEREQRECVED", "AP_GOT_IP6",
+ "FTM_REPORT",
+ "ETH_START", "ETH_STOP", "ETH_CONNECTED", "ETH_DISCONNECTED", "ETH_GOT_IP", "ETH_GOT_IP6",
+ "WPS_ER_SUCCESS", "WPS_ER_FAILED", "WPS_ER_TIMEOUT", "WPS_ER_PIN", "WPS_ER_PBC_OVERLAP",
+ "SC_SCAN_DONE", "SC_FOUND_CHANNEL", "SC_GOT_SSID_PSWD", "SC_SEND_ACK_DONE",
+ "PROV_INIT", "PROV_DEINIT", "PROV_START", "PROV_END", "PROV_CRED_RECV", "PROV_CRED_FAIL", "PROV_CRED_SUCCESS"
+};
+#endif
+#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_WARN
+const char * system_event_reasons[] = { "UNSPECIFIED", "AUTH_EXPIRE", "AUTH_LEAVE", "ASSOC_EXPIRE", "ASSOC_TOOMANY", "NOT_AUTHED", "NOT_ASSOCED", "ASSOC_LEAVE", "ASSOC_NOT_AUTHED", "DISASSOC_PWRCAP_BAD", "DISASSOC_SUPCHAN_BAD", "UNSPECIFIED", "IE_INVALID", "MIC_FAILURE", "4WAY_HANDSHAKE_TIMEOUT", "GROUP_KEY_UPDATE_TIMEOUT", "IE_IN_4WAY_DIFFERS", "GROUP_CIPHER_INVALID", "PAIRWISE_CIPHER_INVALID", "AKMP_INVALID", "UNSUPP_RSN_IE_VERSION", "INVALID_RSN_IE_CAP", "802_1X_AUTH_FAILED", "CIPHER_SUITE_REJECTED", "BEACON_TIMEOUT", "NO_AP_FOUND", "AUTH_FAIL", "ASSOC_FAIL", "HANDSHAKE_TIMEOUT", "CONNECTION_FAIL" };
+#define reason2str(r) ((r>176)?system_event_reasons[r-176]:system_event_reasons[r-1])
+#endif
+esp_err_t WiFiGenericClass::_eventCallback(arduino_event_t *event)
+{
+ static bool first_connect = true;
+
+ if(event->event_id < ARDUINO_EVENT_MAX) {
+ log_d("Arduino Event: %d - %s", event->event_id, arduino_event_names[event->event_id]);
+ }
+ if(event->event_id == ARDUINO_EVENT_WIFI_SCAN_DONE) {
+ WiFiScanClass::_scanDone();
+
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_START) {
+ WiFiSTAClass::_setStatus(WL_DISCONNECTED);
+ setStatusBits(STA_STARTED_BIT);
+ if(esp_wifi_set_ps(_sleepEnabled) != ESP_OK){
+ log_e("esp_wifi_set_ps failed");
+ }
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_STOP) {
+ WiFiSTAClass::_setStatus(WL_NO_SHIELD);
+ clearStatusBits(STA_STARTED_BIT | STA_CONNECTED_BIT | STA_HAS_IP_BIT | STA_HAS_IP6_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_CONNECTED) {
+ WiFiSTAClass::_setStatus(WL_IDLE_STATUS);
+ setStatusBits(STA_CONNECTED_BIT);
+
+ //esp_netif_create_ip6_linklocal(esp_netifs[ESP_IF_WIFI_STA]);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
+ uint8_t reason = event->event_info.wifi_sta_disconnected.reason;
+ log_w("Reason: %u - %s", reason, reason2str(reason));
+ if(reason == WIFI_REASON_NO_AP_FOUND) {
+ WiFiSTAClass::_setStatus(WL_NO_SSID_AVAIL);
+ } else if((reason == WIFI_REASON_AUTH_FAIL) && !first_connect){
+ WiFiSTAClass::_setStatus(WL_CONNECT_FAILED);
+ } else if(reason == WIFI_REASON_BEACON_TIMEOUT || reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
+ WiFiSTAClass::_setStatus(WL_CONNECTION_LOST);
+ } else if(reason == WIFI_REASON_AUTH_EXPIRE) {
+
+ } else {
+ WiFiSTAClass::_setStatus(WL_DISCONNECTED);
+ }
+ clearStatusBits(STA_CONNECTED_BIT | STA_HAS_IP_BIT | STA_HAS_IP6_BIT);
+ if(first_connect && ((reason == WIFI_REASON_AUTH_EXPIRE) ||
+ (reason >= WIFI_REASON_BEACON_TIMEOUT)))
+ {
+ log_d("WiFi Reconnect Running");
+ WiFi.disconnect();
+ WiFi.begin();
+ first_connect = false;
+ }
+ else if(WiFi.getAutoReconnect()){
+ if((reason == WIFI_REASON_AUTH_EXPIRE) ||
+ (reason >= WIFI_REASON_BEACON_TIMEOUT && reason != WIFI_REASON_AUTH_FAIL))
+ {
+ log_d("WiFi AutoReconnect Running");
+ WiFi.disconnect();
+ WiFi.begin();
+ }
+ }
+ else if (reason == WIFI_REASON_ASSOC_FAIL){
+ WiFiSTAClass::_setStatus(WL_CONNECT_FAILED);
+ }
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
+#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
+ uint8_t * ip = (uint8_t *)&(event->event_info.got_ip.ip_info.ip.addr);
+ uint8_t * mask = (uint8_t *)&(event->event_info.got_ip.ip_info.netmask.addr);
+ uint8_t * gw = (uint8_t *)&(event->event_info.got_ip.ip_info.gw.addr);
+ log_d("STA IP: %u.%u.%u.%u, MASK: %u.%u.%u.%u, GW: %u.%u.%u.%u",
+ ip[0], ip[1], ip[2], ip[3],
+ mask[0], mask[1], mask[2], mask[3],
+ gw[0], gw[1], gw[2], gw[3]);
+#endif
+ WiFiSTAClass::_setStatus(WL_CONNECTED);
+ setStatusBits(STA_HAS_IP_BIT | STA_CONNECTED_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_LOST_IP) {
+ WiFiSTAClass::_setStatus(WL_IDLE_STATUS);
+ clearStatusBits(STA_HAS_IP_BIT);
+
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_AP_START) {
+ setStatusBits(AP_STARTED_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_AP_STOP) {
+ clearStatusBits(AP_STARTED_BIT | AP_HAS_CLIENT_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_AP_STACONNECTED) {
+ setStatusBits(AP_HAS_CLIENT_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_AP_STADISCONNECTED) {
+ wifi_sta_list_t clients;
+ if(esp_wifi_ap_get_sta_list(&clients) != ESP_OK || !clients.num){
+ clearStatusBits(AP_HAS_CLIENT_BIT);
+ }
+
+ } else if(event->event_id == ARDUINO_EVENT_ETH_START) {
+ setStatusBits(ETH_STARTED_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_ETH_STOP) {
+ clearStatusBits(ETH_STARTED_BIT | ETH_CONNECTED_BIT | ETH_HAS_IP_BIT | ETH_HAS_IP6_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_ETH_CONNECTED) {
+ setStatusBits(ETH_CONNECTED_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_ETH_DISCONNECTED) {
+ clearStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP_BIT | ETH_HAS_IP6_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_ETH_GOT_IP) {
+#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
+ uint8_t * ip = (uint8_t *)&(event->event_info.got_ip.ip_info.ip.addr);
+ uint8_t * mask = (uint8_t *)&(event->event_info.got_ip.ip_info.netmask.addr);
+ uint8_t * gw = (uint8_t *)&(event->event_info.got_ip.ip_info.gw.addr);
+ log_d("ETH IP: %u.%u.%u.%u, MASK: %u.%u.%u.%u, GW: %u.%u.%u.%u",
+ ip[0], ip[1], ip[2], ip[3],
+ mask[0], mask[1], mask[2], mask[3],
+ gw[0], gw[1], gw[2], gw[3]);
+#endif
+ setStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP_BIT);
+
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_GOT_IP6) {
+ setStatusBits(STA_CONNECTED_BIT | STA_HAS_IP6_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_WIFI_AP_GOT_IP6) {
+ setStatusBits(AP_HAS_IP6_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_ETH_GOT_IP6) {
+ setStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP6_BIT);
+ } else if(event->event_id == ARDUINO_EVENT_SC_GOT_SSID_PSWD) {
+ WiFi.begin(
+ (const char *)event->event_info.sc_got_ssid_pswd.ssid,
+ (const char *)event->event_info.sc_got_ssid_pswd.password,
+ 0,
+ ((event->event_info.sc_got_ssid_pswd.bssid_set == true)?event->event_info.sc_got_ssid_pswd.bssid:nullptr)
+ );
+ } else if(event->event_id == ARDUINO_EVENT_SC_SEND_ACK_DONE) {
+ esp_smartconfig_stop();
+ WiFiSTAClass::_smartConfigDone = true;
+ }
+
+ for(uint32_t i = 0; i < cbEventList.size(); i++) {
+ WiFiEventCbList_t entry = cbEventList[i];
+ if(entry.cb || entry.fcb || entry.scb) {
+ if(entry.event == (arduino_event_id_t) event->event_id || entry.event == ARDUINO_EVENT_MAX) {
+ if(entry.cb) {
+ entry.cb((arduino_event_id_t) event->event_id);
+ } else if(entry.fcb) {
+ entry.fcb((arduino_event_id_t) event->event_id, (arduino_event_info_t) event->event_info);
+ } else {
+ entry.scb(event);
+ }
+ }
+ }
+ }
+ return ESP_OK;
+}
+
+/**
+ * Return the current channel associated with the network
+ * @return channel (1-13)
+ */
+int32_t WiFiGenericClass::channel(void)
+{
+ uint8_t primaryChan = 0;
+ wifi_second_chan_t secondChan = WIFI_SECOND_CHAN_NONE;
+ if(!lowLevelInitDone){
+ return primaryChan;
+ }
+ esp_wifi_get_channel(&primaryChan, &secondChan);
+ return primaryChan;
+}
+
+
+/**
+ * store WiFi config in SDK flash area
+ * @param persistent
+ */
+void WiFiGenericClass::persistent(bool persistent)
+{
+ _persistent = persistent;
+}
+
+
+/**
+ * enable WiFi long range mode
+ * @param enable
+ */
+void WiFiGenericClass::enableLongRange(bool enable)
+{
+ _long_range = enable;
+}
+
+
+/**
+ * set new mode
+ * @param m WiFiMode_t
+ */
+bool WiFiGenericClass::mode(wifi_mode_t m)
+{
+ wifi_mode_t cm = getMode();
+ if(cm == m) {
+ return true;
+ }
+ if(!cm && m){
+ if(!wifiLowLevelInit(_persistent)){
+ return false;
+ }
+ } else if(cm && !m){
+ return espWiFiStop();
+ }
+
+ esp_err_t err;
+ if(m & WIFI_MODE_STA){
+ err = set_esp_interface_hostname(ESP_IF_WIFI_STA, get_esp_netif_hostname());
+ if(err){
+ log_e("Could not set hostname! %d", err);
+ return false;
+ }
+ }
+ err = esp_wifi_set_mode(m);
+ if(err){
+ log_e("Could not set mode! %d", err);
+ return false;
+ }
+ if(_long_range){
+ if(m & WIFI_MODE_STA){
+ err = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR);
+ if(err != ESP_OK){
+ log_e("Could not enable long range on STA! %d", err);
+ return false;
+ }
+ }
+ if(m & WIFI_MODE_AP){
+ err = esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_LR);
+ if(err != ESP_OK){
+ log_e("Could not enable long range on AP! %d", err);
+ return false;
+ }
+ }
+ }
+ if(!espWiFiStart()){
+ return false;
+ }
+
+ #ifdef BOARD_HAS_DUAL_ANTENNA
+ if(!setDualAntennaConfig(ANT1, ANT2, WIFI_RX_ANT_AUTO, WIFI_TX_ANT_AUTO)){
+ log_e("Dual Antenna Config failed!");
+ return false;
+ }
+ #endif
+
+ return true;
+}
+
+/**
+ * get WiFi mode
+ * @return WiFiMode
+ */
+wifi_mode_t WiFiGenericClass::getMode()
+{
+ if(!lowLevelInitDone || !_esp_wifi_started){
+ return WIFI_MODE_NULL;
+ }
+ wifi_mode_t mode;
+ if(esp_wifi_get_mode(&mode) != ESP_OK){
+ log_w("WiFi not started");
+ return WIFI_MODE_NULL;
+ }
+ return mode;
+}
+
+/**
+ * control STA mode
+ * @param enable bool
+ * @return ok
+ */
+bool WiFiGenericClass::enableSTA(bool enable)
+{
+
+ wifi_mode_t currentMode = getMode();
+ bool isEnabled = ((currentMode & WIFI_MODE_STA) != 0);
+
+ if(isEnabled != enable) {
+ if(enable) {
+ return mode((wifi_mode_t)(currentMode | WIFI_MODE_STA));
+ }
+ return mode((wifi_mode_t)(currentMode & (~WIFI_MODE_STA)));
+ }
+ return true;
+}
+
+/**
+ * control AP mode
+ * @param enable bool
+ * @return ok
+ */
+bool WiFiGenericClass::enableAP(bool enable)
+{
+
+ wifi_mode_t currentMode = getMode();
+ bool isEnabled = ((currentMode & WIFI_MODE_AP) != 0);
+
+ if(isEnabled != enable) {
+ if(enable) {
+ return mode((wifi_mode_t)(currentMode | WIFI_MODE_AP));
+ }
+ return mode((wifi_mode_t)(currentMode & (~WIFI_MODE_AP)));
+ }
+ return true;
+}
+
+/**
+ * control modem sleep when only in STA mode
+ * @param enable bool
+ * @return ok
+ */
+bool WiFiGenericClass::setSleep(bool enabled){
+ return setSleep(enabled?WIFI_PS_MIN_MODEM:WIFI_PS_NONE);
+}
+
+/**
+ * control modem sleep when only in STA mode
+ * @param mode wifi_ps_type_t
+ * @return ok
+ */
+bool WiFiGenericClass::setSleep(wifi_ps_type_t sleepType)
+{
+ if(sleepType != _sleepEnabled){
+ _sleepEnabled = sleepType;
+ if((getMode() & WIFI_MODE_STA) != 0){
+ if(esp_wifi_set_ps(_sleepEnabled) != ESP_OK){
+ log_e("esp_wifi_set_ps failed!");
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * get modem sleep enabled
+ * @return true if modem sleep is enabled
+ */
+wifi_ps_type_t WiFiGenericClass::getSleep()
+{
+ return _sleepEnabled;
+}
+
+/**
+ * control wifi tx power
+ * @param power enum maximum wifi tx power
+ * @return ok
+ */
+bool WiFiGenericClass::setTxPower(wifi_power_t power){
+ if((getStatusBits() & (STA_STARTED_BIT | AP_STARTED_BIT)) == 0){
+ log_w("Neither AP or STA has been started");
+ return false;
+ }
+ return esp_wifi_set_max_tx_power(power) == ESP_OK;
+}
+
+wifi_power_t WiFiGenericClass::getTxPower(){
+ int8_t power;
+ if((getStatusBits() & (STA_STARTED_BIT | AP_STARTED_BIT)) == 0){
+ log_w("Neither AP or STA has been started");
+ return WIFI_POWER_19_5dBm;
+ }
+ if(esp_wifi_get_max_tx_power(&power)){
+ return WIFI_POWER_19_5dBm;
+ }
+ return (wifi_power_t)power;
+}
+
+/**
+ * Initiate FTM Session.
+ * @param frm_count Number of FTM frames requested in terms of 4 or 8 bursts (allowed values - 0(No pref), 16, 24, 32, 64)
+ * @param burst_period Requested time period between consecutive FTM bursts in 100's of milliseconds (allowed values - 0(No pref), 2 - 255)
+ * @param channel Primary channel of the FTM Responder
+ * @param mac MAC address of the FTM Responder
+ * @return true on success
+ */
+bool WiFiGenericClass::initiateFTM(uint8_t frm_count, uint16_t burst_period, uint8_t channel, const uint8_t * mac) {
+ wifi_ftm_initiator_cfg_t ftmi_cfg = {
+ .resp_mac = {0,0,0,0,0,0},
+ .channel = channel,
+ .frm_count = frm_count,
+ .burst_period = burst_period,
+ };
+ if(mac != nullptr){
+ memcpy(ftmi_cfg.resp_mac, mac, 6);
+ }
+ // Request FTM session with the Responder
+ if (ESP_OK != esp_wifi_ftm_initiate_session(&ftmi_cfg)) {
+ log_e("Failed to initiate FTM session");
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Configure Dual antenna.
+ * @param gpio_ant1 Configure the GPIO number for the antenna 1 connected to the RF switch (default GPIO2 on ESP32-WROOM-DA)
+ * @param gpio_ant2 Configure the GPIO number for the antenna 2 connected to the RF switch (default GPIO25 on ESP32-WROOM-DA)
+ * @param rx_mode Set the RX antenna mode. See wifi_rx_ant_t for the options.
+ * @param tx_mode Set the TX antenna mode. See wifi_tx_ant_t for the options.
+ * @return true on success
+ */
+bool WiFiGenericClass::setDualAntennaConfig(uint8_t gpio_ant1, uint8_t gpio_ant2, wifi_rx_ant_t rx_mode, wifi_tx_ant_t tx_mode) {
+
+ wifi_ant_gpio_config_t wifi_ant_io;
+
+ if (ESP_OK != esp_wifi_get_ant_gpio(&wifi_ant_io)) {
+ log_e("Failed to get antenna configuration");
+ return false;
+ }
+
+ wifi_ant_io.gpio_cfg[0].gpio_num = gpio_ant1;
+ wifi_ant_io.gpio_cfg[0].gpio_select = 1;
+ wifi_ant_io.gpio_cfg[1].gpio_num = gpio_ant2;
+ wifi_ant_io.gpio_cfg[1].gpio_select = 1;
+
+ if (ESP_OK != esp_wifi_set_ant_gpio(&wifi_ant_io)) {
+ log_e("Failed to set antenna GPIO configuration");
+ return false;
+ }
+
+ // Set antenna default configuration
+ wifi_ant_config_t ant_config = {
+ .rx_ant_mode = WIFI_ANT_MODE_AUTO,
+ .rx_ant_default = WIFI_ANT_MAX, // Ignored in AUTO mode
+ .tx_ant_mode = WIFI_ANT_MODE_AUTO,
+ .enabled_ant0 = 1,
+ .enabled_ant1 = 2,
+ };
+
+ switch (rx_mode)
+ {
+ case WIFI_RX_ANT0:
+ ant_config.rx_ant_mode = WIFI_ANT_MODE_ANT0;
+ break;
+ case WIFI_RX_ANT1:
+ ant_config.rx_ant_mode = WIFI_ANT_MODE_ANT1;
+ break;
+ case WIFI_RX_ANT_AUTO:
+ log_i("TX Antenna will be automatically selected");
+ ant_config.rx_ant_default = WIFI_ANT_ANT0;
+ ant_config.rx_ant_mode = WIFI_ANT_MODE_AUTO;
+ // Force TX for AUTO if RX is AUTO
+ ant_config.tx_ant_mode = WIFI_ANT_MODE_AUTO;
+ goto set_ant;
+ break;
+ default:
+ log_e("Invalid default antenna! Falling back to AUTO");
+ ant_config.rx_ant_mode = WIFI_ANT_MODE_AUTO;
+ break;
+ }
+
+ switch (tx_mode)
+ {
+ case WIFI_TX_ANT0:
+ ant_config.tx_ant_mode = WIFI_ANT_MODE_ANT0;
+ break;
+ case WIFI_TX_ANT1:
+ ant_config.tx_ant_mode = WIFI_ANT_MODE_ANT1;
+ break;
+ case WIFI_TX_ANT_AUTO:
+ log_i("RX Antenna will be automatically selected");
+ ant_config.rx_ant_default = WIFI_ANT_ANT0;
+ ant_config.tx_ant_mode = WIFI_ANT_MODE_AUTO;
+ // Force RX for AUTO if RX is AUTO
+ ant_config.rx_ant_mode = WIFI_ANT_MODE_AUTO;
+ break;
+ default:
+ log_e("Invalid default antenna! Falling back to AUTO");
+ ant_config.rx_ant_default = WIFI_ANT_ANT0;
+ ant_config.tx_ant_mode = WIFI_ANT_MODE_AUTO;
+ break;
+ }
+
+set_ant:
+ if (ESP_OK != esp_wifi_set_ant(&ant_config)) {
+ log_e("Failed to set antenna configuration");
+ return false;
+ }
+
+ return true;
+}
+
+// -----------------------------------------------------------------------------------------------------------------------
+// ------------------------------------------------ Generic Network function ---------------------------------------------
+// -----------------------------------------------------------------------------------------------------------------------
+
+/**
+ * DNS callback
+ * @param name
+ * @param ipaddr
+ * @param callback_arg
+ */
+static void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
+{
+ if(ipaddr) {
+ (*reinterpret_cast(callback_arg)) = ipaddr->u_addr.ip4.addr;
+ }
+ xEventGroupSetBits(_arduino_event_group, WIFI_DNS_DONE_BIT);
+}
+
+/**
+ * Resolve the given hostname to an IP address.
+ * @param aHostname Name to be resolved
+ * @param aResult IPAddress structure to store the returned IP address
+ * @return 1 if aIPAddrString was successfully converted to an IP address,
+ * else error code
+ */
+int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult)
+{
+ ip_addr_t addr;
+ aResult = static_cast(0);
+ waitStatusBits(WIFI_DNS_IDLE_BIT, 16000);
+ clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT);
+ err_t err = dns_gethostbyname(aHostname, &addr, &wifi_dns_found_callback, &aResult);
+ if(err == ERR_OK && addr.u_addr.ip4.addr) {
+ aResult = addr.u_addr.ip4.addr;
+ } else if(err == ERR_INPROGRESS) {
+ waitStatusBits(WIFI_DNS_DONE_BIT, 15000); //real internal timeout in lwip library is 14[s]
+ clearStatusBits(WIFI_DNS_DONE_BIT);
+ }
+ setStatusBits(WIFI_DNS_IDLE_BIT);
+ if((uint32_t)aResult == 0){
+ log_e("DNS Failed for %s", aHostname);
+ }
+ return (uint32_t)aResult != 0;
+}
+
+IPAddress WiFiGenericClass::calculateNetworkID(IPAddress ip, IPAddress subnet) {
+ IPAddress networkID;
+
+ for (size_t i = 0; i < 4; i++)
+ networkID[i] = subnet[i] & ip[i];
+
+ return networkID;
+}
+
+IPAddress WiFiGenericClass::calculateBroadcast(IPAddress ip, IPAddress subnet) {
+ IPAddress broadcastIp;
+
+ for (int i = 0; i < 4; i++)
+ broadcastIp[i] = ~subnet[i] | ip[i];
+
+ return broadcastIp;
+}
+
+uint8_t WiFiGenericClass::calculateSubnetCIDR(IPAddress subnetMask) {
+ uint8_t CIDR = 0;
+
+ for (uint8_t i = 0; i < 4; i++) {
+ if (subnetMask[i] == 0x80) // 128
+ CIDR += 1;
+ else if (subnetMask[i] == 0xC0) // 192
+ CIDR += 2;
+ else if (subnetMask[i] == 0xE0) // 224
+ CIDR += 3;
+ else if (subnetMask[i] == 0xF0) // 242
+ CIDR += 4;
+ else if (subnetMask[i] == 0xF8) // 248
+ CIDR += 5;
+ else if (subnetMask[i] == 0xFC) // 252
+ CIDR += 6;
+ else if (subnetMask[i] == 0xFE) // 254
+ CIDR += 7;
+ else if (subnetMask[i] == 0xFF) // 255
+ CIDR += 8;
+ }
+
+ return CIDR;
+}
diff --git a/Firmware/RTK_Surveyor/PvtClient.ino b/Firmware/RTK_Surveyor/PvtClient.ino
new file mode 100644
index 000000000..287a94060
--- /dev/null
+++ b/Firmware/RTK_Surveyor/PvtClient.ino
@@ -0,0 +1,534 @@
+/*
+PvtClient.ino
+
+ The (position, velocity and time) client sits on top of the network layer and
+ sends position data to one
+ or more computers or cell phones for display.
+
+ Satellite ... Satellite
+ | | |
+ | | |
+ | V |
+ | RTK |
+ '------> Base <------'
+ Station
+ |
+ | NTRIP Server sends correction data
+ V
+ NTRIP Caster
+ |
+ | NTRIP Client receives correction data
+ |
+ V
+ Bluetooth RTK Network: PVT Client
+ .---------------- Rover ----------------------------------.
+ | | |
+ | | Network: PVT Server |
+ | PVT data | Position, velocity & time data | PVT data
+ | V |
+ | Computer or |
+ '------------> Cell Phone <-------------------------------'
+ for display
+
+ PVT Client Testing:
+
+ Using Ethernet on RTK Reference Station and specifying a PVT Client host:
+
+ 1. Network failure - Disconnect Ethernet cable at RTK Reference Station,
+ expecting retry PVT client connection after network restarts
+
+ 2. Internet link failure - Disconnect Ethernet cable between Ethernet
+ switch and the firewall to simulate an internet failure, expecting
+ retry PVT client connection after delay
+
+ 3. Internet outage - Disconnect Ethernet cable between Ethernet
+ switch and the firewall to simulate an internet failure, expecting
+ PVT client retry interval to increase from 5 seconds to 5 minutes
+ and 20 seconds, and the PVT client to reconnect following the outage.
+
+ Using WiFi on RTK Express or RTK Reference Station, no PVT Client host
+ specified, running Vespucci on the cell phone:
+
+ Vespucci Setup:
+ * Click on the gear icon
+ * Scroll down and click on Advanced preferences
+ * Click on Location settings
+ * Click on GPS/GNSS source
+ * Set to NMEA from TCP server
+ * Click on NMEA network source
+ * Set IP address to 127.0.0.1:1958
+ * Uncheck Leave GPS/GNSS turned off
+ * Check Fallback to network location
+ * Click on Stale location after
+ * Set the value 5 seconds
+ * Exit the menus
+
+ 1. Verify connection to the Vespucci application on the cell phone
+ (gateway IP address).
+
+ 2. Vespucci not running: Stop the Vespucci application, expecting PVT
+ client retry interval to increase from 5 seconds to 5 minutes and
+ 20 seconds. The PVT client connects once the Vespucci application
+ is restarted on the phone.
+
+ Test Setups:
+
+ RTK Express RTK Reference Station
+ ^ ^ ^
+ WiFi | WiFi | | Ethernet cable
+ v v v
+ WiFi Access Point <-----------> Ethernet Switch
+ Ethernet ^
+ Cable | Ethernet cable
+ v
+ Internet Firewall
+ ^
+ | Ethernet cable
+ v
+ Modem
+ ^
+ |
+ v
+ Internet
+ ^
+ |
+ v
+ NMEA Server
+ NTRIP Caster
+
+
+ RTK Express RTK Reference Station
+ ^ ^
+ WiFi | WiFi |
+ \ /
+ \ /
+ v v
+ Cell Phone (NMEA Server)
+ ^
+ |
+ v
+ NTRIP Caster
+
+ Possible NTRIP Casters
+
+ * https://emlid.com/ntrip-caster/
+ * http://rtk2go.com/
+ * private SNIP NTRIP caster
+*/
+
+#if COMPILE_NETWORK
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+#define PVT_MAX_CONNECTIONS 6
+#define PVT_DELAY_BETWEEN_CONNECTIONS (5 * 1000)
+
+// Define the PVT client states
+enum PvtClientStates
+{
+ PVT_CLIENT_STATE_OFF = 0,
+ PVT_CLIENT_STATE_NETWORK_STARTED,
+ PVT_CLIENT_STATE_CLIENT_STARTING,
+ PVT_CLIENT_STATE_CONNECTED,
+ // Insert new states here
+ PVT_CLIENT_STATE_MAX // Last entry in the state list
+};
+
+const char * const pvtClientStateName[] =
+{
+ "PVT_CLIENT_STATE_OFF",
+ "PVT_CLIENT_STATE_NETWORK_STARTED",
+ "PVT_CLIENT_STATE_CLIENT_STARTING",
+ "PVT_CLIENT_STATE_CONNECTED"
+};
+
+const int pvtClientStateNameEntries = sizeof(pvtClientStateName) / sizeof(pvtClientStateName[0]);
+
+const RtkMode_t pvtClientMode = RTK_MODE_BASE_FIXED
+ | RTK_MODE_BASE_SURVEY_IN
+ | RTK_MODE_ROVER;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+static NetworkClient * pvtClient;
+static IPAddress pvtClientIpAddress;
+static uint8_t pvtClientState;
+static volatile RING_BUFFER_OFFSET pvtClientTail;
+static volatile bool pvtClientWriteError;
+
+//----------------------------------------
+// PVT Client handleGnssDataTask Support Routines
+//----------------------------------------
+
+// Send PVT data to the NMEA server
+int32_t pvtClientSendData(uint16_t dataHead)
+{
+ bool connected;
+ int32_t bytesToSend;
+ int32_t bytesSent;
+
+ // Determine if a client is connected
+ bytesToSend = 0;
+ connected = settings.enablePvtClient && online.pvtClient;
+
+ // Determine if the client is connected
+ if ((!connected) || (!online.pvtClient))
+ pvtClientTail = dataHead;
+ else
+ {
+ // Determine the amount of data in the buffer
+ bytesToSend = dataHead - pvtClientTail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (bytesToSend > 0)
+ {
+ // Reduce bytes to send if we have more to send then the end of the buffer
+ // We'll wrap next loop
+ if ((pvtClientTail + bytesToSend) > settings.gnssHandlerBufferSize)
+ bytesToSend = settings.gnssHandlerBufferSize - pvtClientTail;
+
+ // Send the data to the NMEA server
+ bytesSent = pvtClient->write(&ringBuffer[pvtClientTail], bytesToSend);
+ if (bytesSent >= 0)
+ {
+ if ((settings.debugPvtClient || PERIODIC_DISPLAY(PD_PVT_CLIENT_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_CLIENT_DATA);
+ systemPrintf("PVT client sent %d bytes, %d remaining\r\n", bytesSent, bytesToSend - bytesSent);
+ }
+
+ // Assume all data was sent, wrap the buffer pointer
+ pvtClientTail += bytesSent;
+ if (pvtClientTail >= settings.gnssHandlerBufferSize)
+ pvtClientTail -= settings.gnssHandlerBufferSize;
+
+ // Update space available for use in UART task
+ bytesToSend = dataHead - pvtClientTail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ }
+
+ // Failed to write the data
+ else
+ {
+ // Done with this client connection
+ if (!inMainMenu)
+ systemPrintf("PVT client breaking connection with %s:%d\r\n",
+ pvtClientIpAddress.toString().c_str(), settings.pvtClientPort);
+
+ pvtClientWriteError = true;
+ bytesToSend = 0;
+ }
+ }
+ }
+
+ // Return the amount of space that WiFi is using in the buffer
+ return bytesToSend;
+}
+
+// Update the state of the PVT client state machine
+void pvtClientSetState(uint8_t newState)
+{
+ if ((settings.debugPvtClient || PERIODIC_DISPLAY(PD_PVT_CLIENT_STATE)) && (!inMainMenu))
+ {
+ if (pvtClientState == newState)
+ systemPrint("*");
+ else
+ systemPrintf("%s --> ", pvtClientStateName[pvtClientState]);
+ }
+ pvtClientState = newState;
+ if ((settings.debugPvtClient || PERIODIC_DISPLAY(PD_PVT_CLIENT_STATE)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_CLIENT_STATE);
+ if (newState >= PVT_CLIENT_STATE_MAX)
+ {
+ systemPrintf("Unknown PVT Client state: %d\r\n", pvtClientState);
+ reportFatalError("Unknown PVT Client state");
+ }
+ else
+ systemPrintln(pvtClientStateName[pvtClientState]);
+ }
+}
+
+// Remove previous messages from the ring buffer
+void discardPvtClientBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail)
+{
+ if (previousTail < newTail)
+ {
+ // No buffer wrap occurred
+ if ((pvtClientTail >= previousTail) && (pvtClientTail < newTail))
+ pvtClientTail = newTail;
+ }
+ else
+ {
+ // Buffer wrap occurred
+ if ((pvtClientTail >= previousTail) || (pvtClientTail < newTail))
+ pvtClientTail = newTail;
+ }
+}
+
+//----------------------------------------
+// PVT Client Routines
+//----------------------------------------
+
+// Start the PVT client
+bool pvtClientStart()
+{
+ NetworkClient * client;
+
+ // Allocate the PVT client
+ client = new NetworkClient(NETWORK_USER_PVT_CLIENT);
+ if (client)
+ {
+ // Get the host name
+ char hostname[sizeof(settings.pvtClientHost)];
+ strcpy(hostname, settings.pvtClientHost);
+ if (!strlen(hostname))
+ {
+ // No host was specified, assume we are using WiFi and using a phone
+ // running the application and a WiFi hot spot. The IP address of
+ // the phone is provided to the RTK during the DHCP handshake as the
+ // gateway IP address.
+
+ // Attempt the PVT client connection
+ pvtClientIpAddress = wifiGetGatewayIpAddress();
+ sprintf(hostname, "%s", pvtClientIpAddress.toString().c_str());
+ }
+
+ // Display the PVT client connection attempt
+ if (settings.debugPvtClient)
+ systemPrintf("PVT client connecting to %s:%d\r\n", hostname, settings.pvtClientPort);
+
+ // Attempt the PVT client connection
+ if (client->connect(hostname, settings.pvtClientPort))
+ {
+ // Get the client IP address
+ pvtClientIpAddress = client->remoteIP();
+
+ // Display the PVT client connection
+ systemPrintf("PVT client connected to %s:%d\r\n",
+ pvtClientIpAddress.toString().c_str(), settings.pvtClientPort);
+
+ // The PVT client is connected
+ pvtClient = client;
+ pvtClientWriteError = false;
+ online.pvtClient = true;
+ return true;
+ }
+ else
+ {
+ // Release any allocated resources
+ client->stop();
+ delete client;
+ }
+ }
+ return false;
+}
+
+// Stop the PVT client
+void pvtClientStop()
+{
+ NetworkClient * client;
+ IPAddress ipAddress;
+
+ client = pvtClient;
+ if (client)
+ {
+ // Delay to allow the UART task to finish with the pvtClient
+ online.pvtClient = false;
+ delay(5);
+
+ // Done with the PVT client connection
+ ipAddress = pvtClientIpAddress;
+ pvtClientIpAddress = IPAddress((uint32_t)0);
+ pvtClient->stop();
+ delete pvtClient;
+ pvtClient = nullptr;
+
+ // Notify the user of the PVT client shutdown
+ if (!inMainMenu)
+ systemPrintf("PVT client disconnected from %s:%d\r\n",
+ ipAddress.toString().c_str(), settings.pvtClientPort);
+ }
+
+ // Done with the network
+ if (pvtClientState != PVT_CLIENT_STATE_OFF)
+ networkUserClose(NETWORK_USER_PVT_CLIENT);
+
+ // Initialize the PVT client
+ pvtClientWriteError = false;
+ if (settings.debugPvtClient)
+ systemPrintln("PVT client offline");
+ pvtClientSetState(PVT_CLIENT_STATE_OFF);
+}
+
+// Update the PVT client state
+void pvtClientUpdate()
+{
+ static uint8_t connectionAttempt;
+ static uint32_t connectionDelay;
+ uint32_t days;
+ byte hours;
+ uint64_t milliseconds;
+ byte minutes;
+ byte seconds;
+ static uint32_t timer;
+
+ // Shutdown the PVT client when the mode or setting changes
+ DMW_st(pvtClientSetState, pvtClientState);
+ if (NEQ_RTK_MODE(pvtClientMode) || (!settings.enablePvtClient))
+ {
+ if (pvtClientState > PVT_CLIENT_STATE_OFF)
+ pvtClientStop();
+ }
+
+ /*
+ PVT Client state machine
+
+ .---------------->PVT_CLIENT_STATE_OFF
+ | |
+ | pvtClientStop | settings.enablePvtClient
+ | |
+ | V
+ +<----------PVT_CLIENT_STATE_NETWORK_STARTED
+ ^ |
+ | | networkUserConnected
+ | |
+ | V
+ +<----------PVT_CLIENT_STATE_CLIENT_STARTING
+ ^ |
+ | | pvtClientStart = true
+ | |
+ | V
+ '--------------PVT_CLIENT_STATE_CONNECTED
+ */
+
+ switch (pvtClientState)
+ {
+ default:
+ systemPrintf("PVT client state: %d\r\n", pvtClientState);
+ reportFatalError("Invalid PVT client state");
+ break;
+
+ // Wait until the PVT client is enabled
+ case PVT_CLIENT_STATE_OFF:
+ // Determine if the PVT client should be running
+ if (EQ_RTK_MODE(pvtClientMode) && settings.enablePvtClient)
+ {
+ if (networkUserOpen(NETWORK_USER_PVT_CLIENT, NETWORK_TYPE_ACTIVE))
+ {
+ timer = 0;
+ pvtClientSetState(PVT_CLIENT_STATE_NETWORK_STARTED);
+ }
+ }
+ break;
+
+ // Wait until the network is connected
+ case PVT_CLIENT_STATE_NETWORK_STARTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_PVT_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ pvtClientStop();
+
+ // Determine if WiFi is required
+ else if ((!strlen(settings.pvtClientHost)) && (networkGetType(NETWORK_TYPE_ACTIVE) != NETWORK_TYPE_WIFI))
+ {
+ // Wrong network type, WiFi is required but another network is being used
+ if ((millis() - timer) >= (15 * 1000))
+ {
+ timer = millis();
+ systemPrintln("PVT Client must connect via WiFi when no host is specified");
+ }
+ }
+
+ // Wait for the network to connect to the media
+ else if (networkUserConnected(NETWORK_USER_PVT_CLIENT))
+ {
+ // The network type and host provide a valid configuration
+ timer = millis();
+ pvtClientSetState(PVT_CLIENT_STATE_CLIENT_STARTING);
+ }
+ break;
+
+ // Attempt the connection ot the PVT server
+ case PVT_CLIENT_STATE_CLIENT_STARTING:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_PVT_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ pvtClientStop();
+
+ // Delay before connecting to the network
+ else if ((millis() - timer) >= connectionDelay)
+ {
+ timer = millis();
+
+ // Start the PVT client
+ if (!pvtClientStart())
+ {
+ // Connection failure
+ if (settings.debugPvtClient)
+ systemPrintln("PVT Client connection failed");
+ connectionDelay = PVT_DELAY_BETWEEN_CONNECTIONS << connectionAttempt;
+ if (connectionAttempt < PVT_MAX_CONNECTIONS)
+ connectionAttempt += 1;
+
+ // Display the uptime
+ milliseconds = connectionDelay;
+ days = milliseconds / MILLISECONDS_IN_A_DAY;
+ milliseconds %= MILLISECONDS_IN_A_DAY;
+ hours = milliseconds / MILLISECONDS_IN_AN_HOUR;
+ milliseconds %= MILLISECONDS_IN_AN_HOUR;
+ minutes = milliseconds / MILLISECONDS_IN_A_MINUTE;
+ milliseconds %= MILLISECONDS_IN_A_MINUTE;
+ seconds = milliseconds / MILLISECONDS_IN_A_SECOND;
+ milliseconds %= MILLISECONDS_IN_A_SECOND;
+ if (settings.debugPvtClient)
+ systemPrintf("PVT Client delaying %d %02d:%02d:%02d.%03lld\r\n",
+ days, hours, minutes, seconds, milliseconds);
+ }
+ else
+ {
+ // Successful connection
+ connectionAttempt = 0;
+ pvtClientSetState(PVT_CLIENT_STATE_CONNECTED);
+ }
+ }
+ break;
+
+ // Wait for the PVT client to shutdown or a PVT client link failure
+ case PVT_CLIENT_STATE_CONNECTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_PVT_CLIENT))
+ // Failed to connect to to the network, attempt to restart the network
+ pvtClientStop();
+
+ // Determine if the PVT client link is broken
+ else if ((!*pvtClient) || (!pvtClient->connected()) || pvtClientWriteError)
+ // Stop the PVT client
+ pvtClientStop();
+ break;
+ }
+
+ // Periodically display the PVT client state
+ if (PERIODIC_DISPLAY(PD_PVT_CLIENT_STATE))
+ pvtClientSetState(pvtClientState);
+}
+
+// Verify the PVT client tables
+void pvtClientValidateTables()
+{
+ if (pvtClientStateNameEntries != PVT_CLIENT_STATE_MAX)
+ reportFatalError("Fix pvtClientStateNameEntries to match PvtClientStates");
+}
+
+// Zero the PVT client tail
+void pvtClientZeroTail()
+{
+ pvtClientTail = 0;
+}
+
+#endif // COMPILE_NETWORK
diff --git a/Firmware/RTK_Surveyor/PvtServer.ino b/Firmware/RTK_Surveyor/PvtServer.ino
new file mode 100644
index 000000000..ed91cd710
--- /dev/null
+++ b/Firmware/RTK_Surveyor/PvtServer.ino
@@ -0,0 +1,540 @@
+/*
+pvtServer.ino
+
+ The PVT (position, velocity and time) server sits on top of the network layer
+ and sends position data to one or more computers or cell phones for display.
+
+ Satellite ... Satellite
+ | | |
+ | | |
+ | V |
+ | RTK |
+ '------> Base <------'
+ Station
+ |
+ | NTRIP Server sends correction data
+ V
+ NTRIP Caster
+ |
+ | NTRIP Client receives correction data
+ |
+ V
+ Bluetooth RTK Network: PVT Client
+ .---------------- Rover ----------------------------------.
+ | | |
+ | | Network: PVT Server |
+ | PVT data | Position, velocity & time data | PVT data
+ | V |
+ | Computer or |
+ '------------> Cell Phone <-------------------------------'
+ for display
+
+*/
+
+#ifdef COMPILE_WIFI
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+#define PVT_SERVER_MAX_CLIENTS 4
+#define PVT_SERVER_CLIENT_DATA_TIMEOUT (15 * 1000)
+
+// Define the PVT server states
+enum PvtServerStates
+{
+ PVT_SERVER_STATE_OFF = 0,
+ PVT_SERVER_STATE_NETWORK_STARTED,
+ PVT_SERVER_STATE_RUNNING,
+ // Insert new states here
+ PVT_SERVER_STATE_MAX // Last entry in the state list
+};
+
+const char * const pvtServerStateName[] =
+{
+ "PVT_SERVER_STATE_OFF",
+ "PVT_SERVER_STATE_NETWORK_STARTED",
+ "PVT_SERVER_STATE_RUNNING",
+};
+
+const int pvtServerStateNameEntries = sizeof(pvtServerStateName) / sizeof(pvtServerStateName[0]);
+
+const RtkMode_t pvtServerMode = RTK_MODE_BASE_FIXED
+ | RTK_MODE_BASE_SURVEY_IN
+ | RTK_MODE_ROVER;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+// PVT server
+static WiFiServer * pvtServer = nullptr;
+static uint8_t pvtServerState;
+static uint32_t pvtServerTimer;
+
+// PVT server clients
+static volatile uint8_t pvtServerClientConnected;
+static volatile uint8_t pvtServerClientDataSent;
+static volatile uint8_t pvtServerClientWriteError;
+static NetworkClient * pvtServerClient[PVT_SERVER_MAX_CLIENTS];
+static IPAddress pvtServerClientIpAddress[PVT_SERVER_MAX_CLIENTS];
+static volatile RING_BUFFER_OFFSET pvtServerClientTails[PVT_SERVER_MAX_CLIENTS];
+
+//----------------------------------------
+// PVT Server handleGnssDataTask Support Routines
+//----------------------------------------
+
+// Send data to the PVT clients
+int32_t pvtServerClientSendData(int index, uint8_t *data, uint16_t length)
+{
+
+ length = pvtServerClient[index]->write(data, length);
+ if (length >= 0)
+ {
+ // Update the data sent flag when data successfully sent
+ if (length > 0)
+ pvtServerClientDataSent |= 1 << index;
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_CLIENT_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_CLIENT_DATA);
+ systemPrintf("PVT server wrote %d bytes to %d.%d.%d.%d\r\n",
+ length,
+ pvtServerClientIpAddress[index][0],
+ pvtServerClientIpAddress[index][1],
+ pvtServerClientIpAddress[index][2],
+ pvtServerClientIpAddress[index][3]);
+ }
+ }
+
+ // Failed to write the data
+ else
+ {
+ // Done with this client connection
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_CLIENT_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_CLIENT_DATA);
+ systemPrintf("PVT server breaking connection %d with client %d.%d.%d.%d\r\n",
+ index,
+ pvtServerClientIpAddress[index][0],
+ pvtServerClientIpAddress[index][1],
+ pvtServerClientIpAddress[index][2],
+ pvtServerClientIpAddress[index][3]);
+ }
+
+ pvtServerClient[index]->stop();
+ pvtServerClientConnected &= ~(1 << index);
+ pvtServerClientWriteError |= 1 << index;
+ length = 0;
+ }
+ return length;
+}
+
+// Send PVT data to the clients
+int32_t pvtServerSendData(uint16_t dataHead)
+{
+ int32_t usedSpace = 0;
+
+ int32_t bytesToSend;
+ int index;
+ uint16_t tail;
+
+ // Update each of the clients
+ for (index = 0; index < PVT_SERVER_MAX_CLIENTS; index++)
+ {
+ tail = pvtServerClientTails[index];
+
+ // Determine if the client is connected
+ if (!(pvtServerClientConnected & (1 << index)))
+ tail = dataHead;
+ else
+ {
+ // Determine the amount of PVT data in the buffer
+ bytesToSend = dataHead - tail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (bytesToSend > 0)
+ {
+ // Reduce bytes to send if we have more to send then the end of the buffer
+ // We'll wrap next loop
+ if ((tail + bytesToSend) > settings.gnssHandlerBufferSize)
+ bytesToSend = settings.gnssHandlerBufferSize - tail;
+
+ // Send the data to the PVT server clients
+ bytesToSend = pvtServerClientSendData(index, &ringBuffer[tail], bytesToSend);
+
+ // Assume all data was sent, wrap the buffer pointer
+ tail += bytesToSend;
+ if (tail >= settings.gnssHandlerBufferSize)
+ tail -= settings.gnssHandlerBufferSize;
+
+ // Update space available for use in UART task
+ bytesToSend = dataHead - tail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (usedSpace < bytesToSend)
+ usedSpace = bytesToSend;
+ }
+ }
+ pvtServerClientTails[index] = tail;
+ }
+
+ // Return the amount of space that PVT server client is using in the buffer
+ return usedSpace;
+}
+
+// Remove previous messages from the ring buffer
+void discardPvtServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail)
+{
+ int index;
+ uint16_t tail;
+
+ // Update each of the clients
+ for (index = 0; index < PVT_SERVER_MAX_CLIENTS; index++)
+ {
+ tail = pvtServerClientTails[index];
+ if (previousTail < newTail)
+ {
+ // No buffer wrap occurred
+ if ((tail >= previousTail) && (tail < newTail))
+ pvtServerClientTails[index] = newTail;
+ }
+ else
+ {
+ // Buffer wrap occurred
+ if ((tail >= previousTail) || (tail < newTail))
+ pvtServerClientTails[index] = newTail;
+ }
+ }
+}
+
+//----------------------------------------
+// PVT Server Routines
+//----------------------------------------
+
+// Update the state of the PVT server state machine
+void pvtServerSetState(uint8_t newState)
+{
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_STATE)) && (!inMainMenu))
+ {
+ if (pvtServerState == newState)
+ systemPrint("*");
+ else
+ systemPrintf("%s --> ", pvtServerStateName[pvtServerState]);
+ }
+ pvtServerState = newState;
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_STATE)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_STATE);
+ if (newState >= PVT_SERVER_STATE_MAX)
+ {
+ systemPrintf("Unknown PVT Server state: %d\r\n", pvtServerState);
+ reportFatalError("Unknown PVT Server state");
+ }
+ else
+ systemPrintln(pvtServerStateName[pvtServerState]);
+ }
+}
+
+// Start the PVT server
+bool pvtServerStart()
+{
+ IPAddress localIp;
+
+ if (settings.debugPvtServer && (!inMainMenu))
+ systemPrintln("PVT server starting the server");
+
+ // Start the PVT server
+ pvtServer = new WiFiServer(settings.pvtServerPort);
+ if (!pvtServer)
+ return false;
+
+ pvtServer->begin();
+ online.pvtServer = true;
+ localIp = wifiGetIpAddress();
+ systemPrintf("PVT server online, IP address %d.%d.%d.%d:%d\r\n",
+ localIp[0], localIp[1], localIp[2], localIp[3],
+ settings.pvtServerPort);
+ return true;
+}
+
+// Stop the PVT server
+void pvtServerStop()
+{
+ int index;
+
+ // Notify the rest of the system that the PVT server is shutting down
+ if (online.pvtServer)
+ {
+ // Notify the UART2 tasks of the PVT server shutdown
+ online.pvtServer = false;
+ delay(5);
+ }
+
+ // Determine if PVT server clients are active
+ if (pvtServerClientConnected)
+ {
+ // Shutdown the PVT server client links
+ for (index = 0; index < PVT_SERVER_MAX_CLIENTS; index++)
+ pvtServerStopClient(index);
+ }
+
+ // Shutdown the PVT server
+ if (pvtServer != nullptr)
+ {
+ // Stop the PVT server
+ if (settings.debugPvtServer && (!inMainMenu))
+ systemPrintln("PVT server stopping");
+ pvtServer->stop();
+ delete pvtServer;
+ pvtServer = nullptr;
+ }
+
+ // Stop using the network
+ if (pvtServerState != PVT_SERVER_STATE_OFF)
+ {
+ networkUserClose(NETWORK_USER_PVT_SERVER);
+
+ // The PVT server is now off
+ pvtServerSetState(PVT_SERVER_STATE_OFF);
+ pvtServerTimer = millis();
+ }
+}
+
+// Stop the PVT server client
+void pvtServerStopClient(int index)
+{
+ bool connected;
+ bool dataSent;
+
+ // Done with this client connection
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_DATA);
+
+ // Determine the shutdown reason
+ connected = pvtServerClient[index]->connected()
+ && (!(pvtServerClientWriteError & (1 << index)));
+ dataSent = ((millis() - pvtServerTimer) < PVT_SERVER_CLIENT_DATA_TIMEOUT)
+ || (pvtServerClientDataSent & (1 << index));
+ if (!dataSent)
+ systemPrintf("PVT Server: No data sent over %d seconds\r\n",
+ PVT_SERVER_CLIENT_DATA_TIMEOUT / 1000);
+ if (!connected)
+ systemPrintf("PVT Server: Link to client broken\r\n");
+ systemPrintf("PVT server client %d disconnected from %d.%d.%d.%d\r\n",
+ index,
+ pvtServerClientIpAddress[index][0],
+ pvtServerClientIpAddress[index][1],
+ pvtServerClientIpAddress[index][2],
+ pvtServerClientIpAddress[index][3]);
+ }
+
+ // Shutdown the PVT server client link
+ pvtServerClient[index]->stop();
+ pvtServerClientConnected &= ~(1 << index);
+ pvtServerClientWriteError &= ~(1 << index);
+}
+
+// Update the PVT server state
+void pvtServerUpdate()
+{
+ bool connected;
+ bool dataSent;
+ int index;
+ IPAddress ipAddress;
+
+ // Shutdown the PVT server when the mode or setting changes
+ DMW_st(pvtServerSetState, pvtServerState);
+ if (NEQ_RTK_MODE(pvtServerMode) || (!settings.enablePvtServer))
+ {
+ if (pvtServerState > PVT_SERVER_STATE_OFF)
+ pvtServerStop();
+ }
+
+ /*
+ PVT Server state machine
+
+ .---------------->PVT_SERVER_STATE_OFF
+ | |
+ | pvtServerStop | settings.enablePvtServer
+ | |
+ | V
+ +<----------PVT_SERVER_STATE_NETWORK_STARTED
+ ^ |
+ | | networkUserConnected
+ | |
+ | V
+ +<--------------PVT_SERVER_STATE_RUNNING
+ ^ |
+ | | network failure
+ | |
+ | V
+ '-----------PVT_SERVER_STATE_WAIT_NO_CLIENTS
+ */
+
+ switch (pvtServerState)
+ {
+ default:
+ break;
+
+ // Wait until the PVT server is enabled
+ case PVT_SERVER_STATE_OFF:
+ // Determine if the PVT server should be running
+ if (EQ_RTK_MODE(pvtServerMode) && settings.enablePvtServer && (!wifiIsConnected()))
+ {
+ if (networkUserOpen(NETWORK_USER_PVT_SERVER, NETWORK_TYPE_ACTIVE))
+ {
+ if (settings.debugPvtServer && (!inMainMenu))
+ systemPrintln("PVT server starting the network");
+ pvtServerSetState(PVT_SERVER_STATE_NETWORK_STARTED);
+ }
+ }
+ break;
+
+ // Wait until the network is connected
+ case PVT_SERVER_STATE_NETWORK_STARTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_PVT_SERVER))
+ // Failed to connect to to the network, attempt to restart the network
+ pvtServerStop();
+
+ // Wait for the network to connect to the media
+ else if (networkUserConnected(NETWORK_USER_PVT_SERVER))
+ {
+ // Delay before starting the PVT server
+ if ((millis() - pvtServerTimer) >= (1 * 1000))
+ {
+ // The network type and host provide a valid configuration
+ pvtServerTimer = millis();
+
+ // Start the PVT server
+ if (pvtServerStart())
+ pvtServerSetState(PVT_SERVER_STATE_RUNNING);
+ }
+ }
+ break;
+
+ // Handle client connections and link failures
+ case PVT_SERVER_STATE_RUNNING:
+ // Determine if the network has failed
+ if ((!settings.enablePvtServer) || networkIsShuttingDown(NETWORK_USER_PVT_SERVER))
+ {
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_DATA);
+ systemPrintln("PVT server initiating shutdown");
+ }
+
+ // Network connection failed, attempt to restart the network
+ pvtServerStop();
+ break;
+ }
+
+ // Walk the list of PVT server clients
+ for (index = 0; index < PVT_SERVER_MAX_CLIENTS; index++)
+ {
+ // Determine if the client data structure is in use
+ if (pvtServerClientConnected & (1 << index))
+ {
+ // Data structure in use
+ // Check for a working PVT server client connection
+ connected = pvtServerClient[index]->connected();
+ dataSent = ((millis() - pvtServerTimer) < PVT_SERVER_CLIENT_DATA_TIMEOUT)
+ || (pvtServerClientDataSent & (1 << index));
+ if (connected && dataSent)
+ {
+ // Display this client connection
+ if (PERIODIC_DISPLAY(PD_PVT_SERVER_DATA) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_DATA);
+ systemPrintf("PVT server client %d connected to %d.%d.%d.%d\r\n",
+ index,
+ pvtServerClientIpAddress[index][0],
+ pvtServerClientIpAddress[index][1],
+ pvtServerClientIpAddress[index][2],
+ pvtServerClientIpAddress[index][3]);
+ }
+ }
+
+ // Shutdown the PVT server client link
+ else
+ pvtServerStopClient(index);
+ }
+ }
+
+ // Walk the list of PVT server clients
+ for (index = 0; index < PVT_SERVER_MAX_CLIENTS; index++)
+ {
+ // Determine if the client data structure is in use
+ if (!(pvtServerClientConnected & (1 << index)))
+ {
+ WiFiClient client;
+
+ // Data structure not in use
+ // Check for another PVT server client
+ client = pvtServer->available();
+
+ // Done if no PVT server client found
+ if (!client)
+ break;
+
+ // Start processing the new PVT server client connection
+ pvtServerClient[index] = new NetworkWiFiClient(client);
+ pvtServerClientIpAddress[index] = pvtServerClient[index]->remoteIP();
+ pvtServerClientConnected |= 1 << index;
+ pvtServerClientDataSent |= 1 << index;
+ if ((settings.debugPvtServer || PERIODIC_DISPLAY(PD_PVT_SERVER_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_SERVER_DATA);
+ systemPrintf("PVT server client %d connected to %d.%d.%d.%d\r\n",
+ index,
+ pvtServerClientIpAddress[index][0],
+ pvtServerClientIpAddress[index][1],
+ pvtServerClientIpAddress[index][2],
+ pvtServerClientIpAddress[index][3]);
+ }
+ }
+ }
+
+ // Check for data moving across the connections
+ if ((millis() - pvtServerTimer) >= PVT_SERVER_CLIENT_DATA_TIMEOUT)
+ {
+ // Restart the data verification
+ pvtServerTimer = millis();
+ pvtServerClientDataSent = 0;
+ }
+ break;
+ }
+
+ // Periodically display the PVT state
+ if (PERIODIC_DISPLAY(PD_PVT_SERVER_STATE) && (!inMainMenu))
+ pvtServerSetState(pvtServerState);
+}
+
+// Verify the PVT server tables
+void pvtServerValidateTables()
+{
+ char line[128];
+
+ // Verify PVT_SERVER_MAX_CLIENTS
+ if ((sizeof(pvtServerClientConnected) * 8) < PVT_SERVER_MAX_CLIENTS)
+ {
+ snprintf(line, sizeof(line),
+ "Please set PVT_SERVER_MAX_CLIENTS <= %d or increase size of pvtServerClientConnected",
+ sizeof(pvtServerClientConnected) * 8);
+ reportFatalError(line);
+ }
+ if (pvtServerStateNameEntries != PVT_SERVER_STATE_MAX)
+ reportFatalError("Fix pvtServerStateNameEntries to match PvtServerStates");
+}
+
+// Zero the PVT server client tails
+void pvtServerZeroTail()
+{
+ int index;
+
+ for (index = 0; index < PVT_SERVER_MAX_CLIENTS; index++)
+ pvtServerClientTails[index] = 0;
+}
+
+#endif // COMPILE_WIFI
diff --git a/Firmware/RTK_Surveyor/PvtUdpServer.ino b/Firmware/RTK_Surveyor/PvtUdpServer.ino
new file mode 100644
index 000000000..36637b0e4
--- /dev/null
+++ b/Firmware/RTK_Surveyor/PvtUdpServer.ino
@@ -0,0 +1,383 @@
+/*
+pvtUdpServer.ino
+
+ The PVT (position, velocity and time) server sits on top of the network layer
+ and sends position data to one or more computers or cell phones for display.
+
+ Satellite ... Satellite
+ | | |
+ | | |
+ | V |
+ | RTK |
+ '------> Base <------'
+ Station
+ |
+ | NTRIP Server sends correction data
+ V
+ NTRIP Caster
+ |
+ | NTRIP Client receives correction data
+ |
+ V
+ Bluetooth RTK Network: PVT Client
+ .---------------- Rover ----------------------------------.
+ | | |
+ | | Network: PVT Server |
+ | PVT data | Position, velocity & time data | PVT data
+ | V |
+ | Computer or |
+ '------------> Cell Phone <-------------------------------'
+ for display
+
+ PVT UDP Server Testing:
+
+ RTK Express(+) with WiFi enabled, PvtUdpServer enabled, PvtUdpPort setup,
+ Smartphone with QField:
+
+ Network Setup:
+ Connect the Smartphone and the RTK Express to the same Network (e.g. the Smartphones HotSpot).
+
+ QField Setup:
+ * Open a project
+ * Open the left menu
+ * Click on the gear icon
+ * Click on Settings
+ * Click on Positioning
+ * Add a new Positioning device
+ * Set Connection type to UDP (NMEA)
+ * Set the Adress to
+ * Set the Port to the value of the specified PvtUdpPort (default 10110)
+ * Optional: give it a name (e.g. RTK Express UDP)
+ * Click on the Checkmark
+ * Make sure the new device is set as the Postioning device in use
+ * Exit the menus
+
+ 1. Long press on the location icon in the lower right corner
+
+ 2. Enable Show Position Information
+
+ 3. Verify that the displayed coordinates, fix tpe etc. are valid
+*/
+
+#if COMPILE_NETWORK
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+// Define the PVT server states
+enum PvtUdpServerStates
+{
+ PVT_UDP_SERVER_STATE_OFF = 0,
+ PVT_UDP_SERVER_STATE_NETWORK_STARTED,
+ PVT_UDP_SERVER_STATE_RUNNING,
+ // Insert new states here
+ PVT_UDP_SERVER_STATE_MAX // Last entry in the state list
+};
+
+const char * const pvtUdpServerStateName[] =
+{
+ "PVT_UDP_SERVER_STATE_OFF",
+ "PVT_UDP_SERVER_STATE_NETWORK_STARTED",
+ "PVT_UDP_SERVER_STATE_RUNNING",
+};
+
+const int pvtUdpServerStateNameEntries = sizeof(pvtUdpServerStateName) / sizeof(pvtUdpServerStateName[0]);
+
+const RtkMode_t pvtUdpServerMode = RTK_MODE_BASE_FIXED
+ | RTK_MODE_BASE_SURVEY_IN
+ | RTK_MODE_ROVER;
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+// PVT UDP server
+static NetworkUDP *pvtUdpServer = nullptr;
+static uint8_t pvtUdpServerState;
+static uint32_t pvtUdpServerTimer;
+static volatile RING_BUFFER_OFFSET pvtUdpServerTail;
+//----------------------------------------
+// PVT UDP Server handleGnssDataTask Support Routines
+//----------------------------------------
+
+// Send data as broadcast
+int32_t pvtUdpServerSendDataBroadcast(uint8_t *data, uint16_t length)
+{
+ if (!length)
+ return 0;
+
+ // Send the data as broadcast
+ if (settings.enablePvtUdpServer && online.pvtUdpServer && wifiIsConnected())
+ {
+ pvtUdpServer->beginPacket(WiFi.broadcastIP(), settings.pvtUdpServerPort);
+ pvtUdpServer->write(data, length);
+ if(pvtUdpServer->endPacket()){
+ if ((settings.debugPvtUdpServer || PERIODIC_DISPLAY(PD_PVT_UDP_SERVER_BROADCAST_DATA)) && (!inMainMenu))
+ {
+ systemPrintf("PVT UDP Server wrote %d bytes as broadcast on port %d\r\n", length, settings.pvtUdpServerPort);
+ PERIODIC_CLEAR(PD_PVT_UDP_SERVER_BROADCAST_DATA);
+ }
+ }
+ // Failed to write the data
+ else if ((settings.debugPvtUdpServer || PERIODIC_DISPLAY(PD_PVT_UDP_SERVER_BROADCAST_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_UDP_SERVER_BROADCAST_DATA);
+ systemPrintf("PVT UDP Server failed to write %d bytes as broadcast\r\n", length);
+ length = 0;
+ }
+ }
+ return length;
+}
+
+// Send PVT data as broadcast
+int32_t pvtUdpServerSendData(uint16_t dataHead)
+{
+ int32_t usedSpace = 0;
+
+ int32_t bytesToSend;
+
+ uint16_t tail;
+
+ tail = pvtUdpServerTail;
+
+ // Determine the amount of PVT data in the buffer
+ bytesToSend = dataHead - tail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (bytesToSend > 0)
+ {
+ // Reduce bytes to send if we have more to send then the end of the buffer
+ // We'll wrap next loop
+ if ((tail + bytesToSend) > settings.gnssHandlerBufferSize)
+ bytesToSend = settings.gnssHandlerBufferSize - tail;
+
+ // Send the data
+ bytesToSend = pvtUdpServerSendDataBroadcast(&ringBuffer[tail], bytesToSend);
+
+ // Assume all data was sent, wrap the buffer pointer
+ tail += bytesToSend;
+ if (tail >= settings.gnssHandlerBufferSize)
+ tail -= settings.gnssHandlerBufferSize;
+
+ // Update space available for use in UART task
+ bytesToSend = dataHead - tail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (usedSpace < bytesToSend)
+ usedSpace = bytesToSend;
+ }
+
+ pvtUdpServerTail = tail;
+
+ // Return the amount of space that PVT server client is using in the buffer
+ return usedSpace;
+}
+
+// Remove previous messages from the ring buffer
+void discardPvtUdpServerBytes(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail)
+{
+ int index;
+ uint16_t tail;
+
+ tail = pvtUdpServerTail;
+ if (previousTail < newTail)
+ {
+ // No buffer wrap occurred
+ if ((tail >= previousTail) && (tail < newTail))
+ pvtUdpServerTail = newTail;
+ }
+ else
+ {
+ // Buffer wrap occurred
+ if ((tail >= previousTail) || (tail < newTail))
+ pvtUdpServerTail = newTail;
+ }
+}
+
+//----------------------------------------
+// PVT Server Routines
+//----------------------------------------
+
+// Update the state of the PVT server state machine
+void pvtUdpServerSetState(uint8_t newState)
+{
+ if ((settings.debugPvtUdpServer || PERIODIC_DISPLAY(PD_PVT_UDP_SERVER_STATE)) && (!inMainMenu))
+ {
+ if (pvtUdpServerState == newState)
+ systemPrint("*");
+ else
+ systemPrintf("%s --> ", pvtUdpServerStateName[pvtUdpServerState]);
+ }
+ pvtUdpServerState = newState;
+ if ((settings.debugPvtUdpServer || PERIODIC_DISPLAY(PD_PVT_UDP_SERVER_STATE)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_UDP_SERVER_STATE);
+ if (newState >= PVT_UDP_SERVER_STATE_MAX)
+ {
+ systemPrintf("Unknown PVT UDP Server state: %d\r\n", pvtUdpServerState);
+ reportFatalError("Unknown PVT UDP Server state");
+ }
+ else
+ systemPrintln(pvtUdpServerStateName[pvtUdpServerState]);
+ }
+}
+
+// Start the PVT server
+bool pvtUdpServerStart()
+{
+ IPAddress localIp;
+
+ if (settings.debugPvtUdpServer && (!inMainMenu))
+ systemPrintln("PVT UDP server starting");
+
+ // Start the PVT server
+ pvtUdpServer = new NetworkUDP(NETWORK_USER_PVT_UDP_SERVER);
+ if (!pvtUdpServer)
+ return false;
+
+ pvtUdpServer->begin(settings.pvtUdpServerPort);
+ online.pvtUdpServer = true;
+ systemPrintf("PVT UDP server online, broadcasting to port %d\r\n", settings.pvtUdpServerPort);
+ return true;
+}
+
+// Stop the PVT UDP server
+void pvtUdpServerStop()
+{
+ int index;
+
+ // Notify the rest of the system that the PVT server is shutting down
+ if (online.pvtUdpServer)
+ {
+ // Notify the UART2 tasks of the PVT server shutdown
+ online.pvtUdpServer = false;
+ delay(5);
+ }
+
+ // Shutdown the PVT server
+ if (pvtUdpServer != nullptr)
+ {
+ // Stop the PVT server
+ if (settings.debugPvtUdpServer && (!inMainMenu))
+ systemPrintln("PVT UDP server stopping");
+ pvtUdpServer->stop();
+ delete pvtUdpServer;
+ pvtUdpServer = nullptr;
+ }
+
+ // Stop using the network
+ if (pvtUdpServerState != PVT_UDP_SERVER_STATE_OFF)
+ {
+ networkUserClose(NETWORK_USER_PVT_UDP_SERVER);
+
+ // The PVT server is now off
+ pvtUdpServerSetState(PVT_UDP_SERVER_STATE_OFF);
+ pvtUdpServerTimer = millis();
+ }
+}
+
+// Update the PVT server state
+void pvtUdpServerUpdate()
+{
+ bool connected;
+ bool dataSent;
+ int index;
+ IPAddress ipAddress;
+
+ // Shutdown the PVT UDP server when the mode or setting changes
+ DMW_st(pvtUdpServerSetState, pvtUdpServerState);
+ if (NEQ_RTK_MODE(pvtUdpServerMode) || (!settings.enablePvtUdpServer))
+ {
+ if (pvtUdpServerState > PVT_UDP_SERVER_STATE_OFF)
+ pvtUdpServerStop();
+ }
+
+ /*
+ PVT UDP Server state machine
+
+ .--------------->PVT_UDP_SERVER_STATE_OFF
+ | |
+ | pvtUdpServerStop | settings.enablePvtUdpServer
+ | |
+ | V
+ +<---------PVT_UDP_SERVER_STATE_NETWORK_STARTED
+ ^ |
+ | | networkUserConnected
+ | |
+ | V
+ '--------------PVT_UDP_SERVER_STATE_RUNNING
+ */
+
+ switch (pvtUdpServerState)
+ {
+ default:
+ break;
+
+ // Wait until the PVT server is enabled
+ case PVT_UDP_SERVER_STATE_OFF:
+ // Determine if the PVT server should be running
+ if (EQ_RTK_MODE(pvtUdpServerMode) && settings.enablePvtUdpServer && (!wifiIsConnected()))
+ {
+ if (networkUserOpen(NETWORK_USER_PVT_UDP_SERVER, NETWORK_TYPE_ACTIVE))
+ {
+ if (settings.debugPvtUdpServer && (!inMainMenu))
+ systemPrintln("PVT UDP server starting the network");
+ pvtUdpServerSetState(PVT_UDP_SERVER_STATE_NETWORK_STARTED);
+ }
+ }
+ break;
+
+ // Wait until the network is connected
+ case PVT_UDP_SERVER_STATE_NETWORK_STARTED:
+ // Determine if the network has failed
+ if (networkIsShuttingDown(NETWORK_USER_PVT_UDP_SERVER))
+ // Failed to connect to to the network, attempt to restart the network
+ pvtUdpServerStop();
+
+ // Wait for the network to connect to the media
+ else if (networkUserConnected(NETWORK_USER_PVT_UDP_SERVER))
+ {
+ // Delay before starting the PVT server
+ if ((millis() - pvtUdpServerTimer) >= (1 * 1000))
+ {
+ // The network type and host provide a valid configuration
+ pvtUdpServerTimer = millis();
+
+ // Start the PVT UDP server
+ if (pvtUdpServerStart())
+ pvtUdpServerSetState(PVT_UDP_SERVER_STATE_RUNNING);
+ }
+ }
+ break;
+
+ // Handle client connections and link failures
+ case PVT_UDP_SERVER_STATE_RUNNING:
+ // Determine if the network has failed
+ if ((!settings.enablePvtUdpServer) || networkIsShuttingDown(NETWORK_USER_PVT_UDP_SERVER))
+ {
+ if ((settings.debugPvtUdpServer || PERIODIC_DISPLAY(PD_PVT_UDP_SERVER_DATA)) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_PVT_UDP_SERVER_DATA);
+ systemPrintln("PVT server initiating shutdown");
+ }
+
+ // Network connection failed, attempt to restart the network
+ pvtUdpServerStop();
+ break;
+ }
+ break;
+ }
+
+ // Periodically display the PVT state
+ if (PERIODIC_DISPLAY(PD_PVT_UDP_SERVER_STATE) && (!inMainMenu))
+ pvtUdpServerSetState(pvtUdpServerState);
+}
+
+// Zero the PVT server client tails
+void pvtUdpServerZeroTail()
+{
+ pvtUdpServerTail = 0;
+}
+
+#endif // COMPILE_NETWORK
diff --git a/Firmware/RTK_Surveyor/RTK_Surveyor.ino b/Firmware/RTK_Surveyor/RTK_Surveyor.ino
index a4e92779d..ff33c7ffe 100644
--- a/Firmware/RTK_Surveyor/RTK_Surveyor.ino
+++ b/Firmware/RTK_Surveyor/RTK_Surveyor.ino
@@ -3,470 +3,1464 @@
SparkFun Electronics
Nathan Seidle
- This firmware runs the core of the SparkFun RTK Surveyor product. It runs on an ESP32
+ This firmware runs the core of the SparkFun RTK products. It runs on an ESP32
and communicates with the ZED-F9P.
- Select the ESP32 Dev Module from the boards list. This maps the same pins to the ESP32-WROOM module.
- Select 'Minimal SPIFFS (1.9MB App)' from the partition list. This will enable SD firmware updates.
+ Compiled with Arduino v1.8.15 with ESP32 core v2.0.2.
+
+ For compilation instructions see https://docs.sparkfun.com/SparkFun_RTK_Firmware/firmware_update/#compiling-source
Special thanks to Avinab Malla for guidance on getting xTasks implemented.
The RTK Surveyor implements classic Bluetooth SPP to transfer data from the
ZED-F9P to the phone and receive any RTCM from the phone and feed it back
- to the ZED-F9P to achieve RTK: F9PSerialWriteTask(), F9PSerialReadTask().
-
- A settings file is accessed on microSD if available otherwise settings are pulled from
- ESP32's emulated EEPROM.
-
- As of v1.2, the heap is approximately 94072 during Rover Fix, 142260 during WiFi Casting. This is
- important to maintain as unit will begin to have stability issues at ~30k.
-
- The main loop handles lower priority updates such as:
- Fuel gauge checking and power LED color update
- Setup switch monitoring (module configure between Rover and Base)
- Text menu interactions
-
- Main Menu (Display MAC address / broadcast name):
- (Done) GNSS - Configure measurement rate, SBAS
- (Done) Log - Control messages logged to SD
- (Done) Broadcast - Control messages sent over BT SPP
- (Done) Base - Enter fixed coordinates, survey-in settings, WiFi/Caster settings,
- (Done) Ports - Configure Radio and Data port baud rates
- (Done) Test menu
- (Done) Firmware upgrade menu
- Enable various debug outputs sent over BT
+ to the ZED-F9P to achieve RTK: btReadTask(), gnssReadTask().
+ Settings are loaded from microSD if available otherwise settings are pulled from ESP32's file system LittleFS.
*/
-const int FIRMWARE_VERSION_MAJOR = 1;
-const int FIRMWARE_VERSION_MINOR = 4;
-
-//Define the RTK board identifier:
+#define COMPILE_ETHERNET // Comment out to remove Ethernet (W5500) support
+#define COMPILE_WIFI // Comment out to remove WiFi functionality
+
+#ifdef COMPILE_WIFI
+#define COMPILE_AP // Requires WiFi. Comment out to remove Access Point functionality
+#define COMPILE_ESPNOW // Requires WiFi. Comment out to remove ESP-Now functionality.
+#endif // COMPILE_WIFI
+
+#define COMPILE_BT // Comment out to remove Bluetooth functionality
+#define COMPILE_L_BAND // Comment out to remove L-Band functionality
+#define COMPILE_SD_MMC // Comment out to remove REFERENCE_STATION microSD SD_MMC support
+// #define REF_STN_GNSS_DEBUG //Uncomment this line to output GNSS library debug messages on serialGNSS. Ref Stn only.
+// Needs ENABLE_DEVELOPER
+
+#if defined(COMPILE_WIFI) || defined(COMPILE_ETHERNET)
+#define COMPILE_NETWORK true
+#else // COMPILE_WIFI || COMPILE_ETHERNET
+#define COMPILE_NETWORK false
+#endif // COMPILE_WIFI || COMPILE_ETHERNET
+
+// Always define ENABLE_DEVELOPER to enable its use in conditional statements
+#ifndef ENABLE_DEVELOPER
+#define ENABLE_DEVELOPER \
+ true // This enable specials developer modes (don't check power button at startup). Passed in from compiler flags.
+#endif // ENABLE_DEVELOPER
+
+// This is passed in from compiler extra flags
+#ifndef POINTPERFECT_TOKEN
+#define FIRMWARE_VERSION_MAJOR 99
+#define FIRMWARE_VERSION_MINOR 99
+#endif // POINTPERFECT_TOKEN
+
+// Define the RTK board identifier:
// This is an int which is unique to this variant of the RTK Surveyor hardware which allows us
-// to make sure that the settings in EEPROM are correct for this version of the RTK
+// to make sure that the settings stored in flash (LittleFS) are correct for this version of the RTK
// (sizeOfSettings is not necessarily unique and we want to avoid problems when swapping from one variant to another)
// It is the sum of:
// the major firmware version * 0x10
// the minor firmware version
#define RTK_IDENTIFIER (FIRMWARE_VERSION_MAJOR * 0x10 + FIRMWARE_VERSION_MINOR)
+#define NTRIP_SERVER_MAX 2
+
+#ifdef COMPILE_ETHERNET
+#include // http://librarymanager/All#Arduino_Ethernet
+#include "SparkFun_WebServer_ESP32_W5500.h" //http://librarymanager/All#SparkFun_WebServer_ESP32_W5500 v1.5.5
+#endif // COMPILE_ETHERNET
+
+#ifdef COMPILE_WIFI
+#include "ESP32OTAPull.h" //http://librarymanager/All#ESP-OTA-Pull Used for getting
+#include "esp_wifi.h" //Needed for esp_wifi_set_protocol()
+#include //Built-in.
+#include //Built-in.
+#include //Built-in. Needed for ThingStream API for ZTP
+#include //http://librarymanager/All#PubSubClient_MQTT_Lightweight by Nick O'Leary v2.8.0 Used for MQTT obtaining of keys
+#include //Built-in.
+#include //Built-in.
+#include //Built-in.
+#endif // COMPILE_WIFI
+
+#if COMPILE_NETWORK
+#include // http://librarymanager/All#SSLClientESP32
+#include "X509_Certificate_Bundle.h" // Root certificates
+#endif // COMPILE_NETWORK
+
#include "settings.h"
-//Hardware connections
+#define MAX_CPU_CORES 2
+#define IDLE_COUNT_PER_SECOND 515400 //Found by empirical sketch
+#define IDLE_TIME_DISPLAY_SECONDS 5
+#define MAX_IDLE_TIME_COUNT (IDLE_TIME_DISPLAY_SECONDS * IDLE_COUNT_PER_SECOND)
+#define MILLISECONDS_IN_A_SECOND 1000
+#define MILLISECONDS_IN_A_MINUTE (60 * MILLISECONDS_IN_A_SECOND)
+#define MILLISECONDS_IN_AN_HOUR (60 * MILLISECONDS_IN_A_MINUTE)
+#define MILLISECONDS_IN_A_DAY (24 * MILLISECONDS_IN_AN_HOUR)
+
+#define SECONDS_IN_A_MINUTE 60
+#define SECONDS_IN_AN_HOUR (60 * SECONDS_IN_A_MINUTE)
+#define SECONDS_IN_A_DAY (24 * SECONDS_IN_AN_HOUR)
+
+// Hardware connections
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//These pins are set in beginBoard()
-int pin_batteryLevelLED_Red;
-int pin_batteryLevelLED_Green;
-int pin_positionAccuracyLED_1cm;
-int pin_positionAccuracyLED_10cm;
-int pin_positionAccuracyLED_100cm;
-int pin_baseStatusLED;
-int pin_bluetoothStatusLED;
-int pin_baseSwitch;
-int pin_microSD_CS;
-int pin_zed_tx_ready;
-int pin_zed_reset;
-int pin_batteryLevel_alert;
-
-int pin_muxA;
-int pin_muxB;
-int pin_powerSenseAndControl;
-int pin_setupButton;
-int pin_powerFastOff;
-int pin_dac26;
-int pin_adc39;
-int pin_peripheralPowerControl;
+// These pins are set in beginBoard()
+int pin_batteryLevelLED_Red = -1;
+int pin_batteryLevelLED_Green = -1;
+int pin_positionAccuracyLED_1cm = -1;
+int pin_positionAccuracyLED_10cm = -1;
+int pin_positionAccuracyLED_100cm = -1;
+int pin_baseStatusLED = -1;
+int pin_bluetoothStatusLED = -1;
+int pin_microSD_CS = -1;
+int pin_zed_tx_ready = -1;
+int pin_zed_reset = -1;
+int pin_batteryLevel_alert = -1;
+
+int pin_muxA = -1;
+int pin_muxB = -1;
+int pin_powerSenseAndControl = -1;
+int pin_setupButton = -1;
+int pin_powerFastOff = -1;
+int pin_dac26 = -1;
+int pin_adc39 = -1;
+int pin_peripheralPowerControl = -1;
+
+int pin_radio_rx = -1;
+int pin_radio_tx = -1;
+int pin_radio_rst = -1;
+int pin_radio_pwr = -1;
+int pin_radio_cts = -1;
+int pin_radio_rts = -1;
+
+int pin_Ethernet_CS = -1;
+int pin_Ethernet_Interrupt = -1;
+int pin_GNSS_CS = -1;
+int pin_GNSS_TimePulse = -1;
+int pin_microSD_CardDetect = -1;
+
+int pin_PICO = 23;
+int pin_POCI = 19;
+int pin_SCK = 18;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//I2C for GNSS, battery gauge, display, accelerometer
+// I2C for GNSS, battery gauge, display, accelerometer
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-//EEPROM for storing settings
+// LittleFS for storing settings for different user profiles
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-#include
-#define EEPROM_SIZE 4096 //ESP32 emulates EEPROM in non-volatile storage (external flash IC). Max is 508k.
+#include
+
+#define MAX_PROFILE_COUNT 8
+uint8_t activeProfiles = 0; // Bit vector indicating which profiles are active
+uint8_t displayProfile; // Range: 0 - (MAX_PROFILE_COUNT - 1)
+uint8_t profileNumber = MAX_PROFILE_COUNT; // profileNumber gets set once at boot to save loading time
+char profileNames[MAX_PROFILE_COUNT][50]; // Populated based on names found in LittleFS and SD
+char settingsFileName[60]; // Contains the %s_Settings_%d.txt with current profile number set
+
+char stationCoordinateECEFFileName[60]; // Contains the /StationCoordinates-ECEF_%d.csv with current profile number set
+char stationCoordinateGeodeticFileName[60]; // Contains the /StationCoordinates-Geodetic_%d.csv with current profile
+ // number set
+const int COMMON_COORDINATES_MAX_STATIONS = 50; // Record upto 50 ECEF and Geodetic commonly used stations
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-//Handy library for setting ESP32 system time to GNSS time
+// Handy library for setting ESP32 system time to GNSS time
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-#include //http://librarymanager/All#ESP32Time
+#include //http://librarymanager/All#ESP32Time by FBiego v2.0.0
ESP32Time rtc;
+unsigned long syncRTCInterval = 1000; // To begin, sync RTC every second. Interval can be increased once sync'd.
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-//microSD Interface
+// microSD Interface
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#include
-#include "SdFat.h"
-SdFat sd;
-SPIClass spi = SPIClass(VSPI); //We need to pass the class into SD.begin so we can set the SPI freq in beginSD()
+#include "SdFat.h" //http://librarymanager/All#sdfat_exfat by Bill Greiman. Currently uses v2.1.1
+SdFat *sd;
+
+#include "FileSdFatMMC.h" //Hybrid SdFat and SD_MMC file access
-char platformFilePrefix[40] = "SFE_Surveyor"; //Sets the prefix for logs and settings files
+#define platformFilePrefix platformFilePrefixTable[productVariant] // Sets the prefix for logs and settings files
-SdFile ubxFile; //File that all gnss ubx messages setences are written to
-unsigned long lastUBXLogSyncTime = 0; //Used to record to SD every half second
-int startLogTime_minutes = 0; //Mark when we start logging so we can stop logging after maxLogTime_minutes
+FileSdFatMMC *ubxFile; // File that all GNSS ubx messages sentences are written to
+unsigned long lastUBXLogSyncTime = 0; // Used to record to SD every half second
+int startLogTime_minutes = 0; // Mark when we start any logging so we can stop logging after maxLogTime_minutes
+int startCurrentLogTime_minutes =
+ 0; // Mark when we start this specific log file so we can close it after x minutes and start a new one
-//System crashes if two tasks access a file at the same time
-//So we use a semaphore to see if file system is available
-SemaphoreHandle_t xFATSemaphore;
+// System crashes if two tasks access a file at the same time
+// So we use a semaphore to see if file system is available
+SemaphoreHandle_t sdCardSemaphore;
+TickType_t loggingSemaphoreWait_ms = 10 / portTICK_PERIOD_MS;
const TickType_t fatSemaphore_shortWait_ms = 10 / portTICK_PERIOD_MS;
const TickType_t fatSemaphore_longWait_ms = 200 / portTICK_PERIOD_MS;
+
+// Display used/free space in menu and config page
+uint64_t sdCardSize = 0;
+uint64_t sdFreeSpace = 0;
+bool outOfSDSpace = false;
+const uint32_t sdMinAvailableSpace = 10000000; // Minimum available bytes before SD is marked as out of space
+
+// Controls Logging Icon type
+typedef enum LoggingType
+{
+ LOGGING_UNKNOWN = 0,
+ LOGGING_STANDARD,
+ LOGGING_PPP,
+ LOGGING_CUSTOM
+} LoggingType;
+LoggingType loggingType = LOGGING_UNKNOWN;
+
+FileSdFatMMC *managerTempFile; // File used for uploading or downloading in file manager section of AP config
+bool managerFileOpen = false;
+
+TaskHandle_t sdSizeCheckTaskHandle = nullptr; // Store handles so that we can kill the task once size is found
+const uint8_t sdSizeCheckTaskPriority = 0; // 3 being the highest, and 0 being the lowest
+const int sdSizeCheckStackSize = 3000;
+bool sdSizeCheckTaskComplete = false;
+
+char logFileName[sizeof("SFE_Reference_Station_230101_120101.ubx_plusExtraSpace")] = {0};
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-//Connection settings to NTRIP Caster
-//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// Over-the-Air (OTA) update support
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-#define COMPILE_WIFI 1 //Comment out to remove all WiFi functionality
+#include //http://librarymanager/All#Arduino_JSON_messagepack v6.19.4
+
+#include "esp_ota_ops.h" //Needed for partition counting and updateFromSD
+
+#define NETWORK_STOP(type) \
+ { \
+ if (settings.debugNetworkLayer) \
+ systemPrintf("networkStop called by %s %d\r\n", __FILE__, __LINE__); \
+ networkStop(type); \
+ }
#ifdef COMPILE_WIFI
-#include
-#include "esp_wifi.h" //Needed for init/deinit of resources to free up RAM
-WiFiClient caster;
-#endif
-const char * ntrip_server_name = "SparkFun_RTK_Surveyor";
+#define WIFI_STOP() \
+{ \
+ if (settings.debugWifiState) \
+ systemPrintf("wifiStop called by %s %d\r\n", __FILE__, __LINE__); \
+ wifiStop(); \
+}
-unsigned long lastServerSent_ms = 0; //Time of last data pushed to caster
-unsigned long lastServerReport_ms = 0; //Time of last report of caster bytes sent
-int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster
+#endif // COMPILE_WIFI
-uint32_t casterBytesSent = 0; //Just a running total
-uint32_t casterResponseWaitStartTime = 0; //Used to detect if caster service times out
-//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#define OTA_FIRMWARE_JSON_URL \
+ "https://raw.githubusercontent.com/sparkfun/SparkFun_RTK_Firmware_Binaries/main/RTK-Firmware.json"
+#define OTA_RC_FIRMWARE_JSON_URL \
+ "https://raw.githubusercontent.com/sparkfun/SparkFun_RTK_Firmware_Binaries/main/RTK-RC-Firmware.json"
+bool apConfigFirmwareUpdateInProcess = false; // Goes true once WiFi is connected and OTA pull begins
+unsigned int binBytesSent = 0; // Tracks firmware bytes sent over WiFi OTA update via AP config.
-//GNSS configuration
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// Connection settings to NTRIP Caster
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-#include //http://librarymanager/All#SparkFun_u-blox_GNSS
+#include "base64.h" //Built-in. Needed for NTRIP Client credential encoding.
-//Note: There are two prevalent versions of the ZED-F9P: v1.12 (part# -01B) and v1.13 (-02B).
-//v1.13 causes the RTK LED to not function if SBAS is enabled. To avoid this, we
-//disable SBAS by default.
+bool enableRCFirmware = false; // Goes true from AP config page
+bool currentlyParsingData = false; // Goes true when we hit 750ms timeout with new data
-char zedFirmwareVersion[20]; //The string looks like 'FWVER=HPG 1.12'. Output to debug menu and settings file.
+// Give up connecting after this number of attempts
+// Connection attempts are throttled to increase the time between attempts
+int wifiMaxConnectionAttempts = 500;
+int wifiOriginalMaxConnectionAttempts = wifiMaxConnectionAttempts; // Modified during L-Band WiFi connect attempt
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-// Extend the class for getModuleInfo. Used to diplay ZED-F9P firmware version in debug menu.
-class SFE_UBLOX_GNSS_ADD : public SFE_UBLOX_GNSS
+// GNSS configuration
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#include //http://librarymanager/All#SparkFun_u-blox_GNSS_v3 v3.0.5
+
+char zedFirmwareVersion[20]; // The string looks like 'HPG 1.12'. Output to system status menu and settings file.
+char neoFirmwareVersion[20]; // Output to system status menu.
+uint8_t zedFirmwareVersionInt = 0; // Controls which features (constellations) can be configured (v1.12 doesn't support
+ // SBAS). Note: will fail above 2.55!
+uint8_t zedModuleType = PLATFORM_F9P; // Controls which messages are supported and configured
+char zedUniqueId[11] = {'0', '0', '0', '0', '0', '0',
+ '0', '0', '0', '0', 0}; // Output to system status menu and log file.
+
+// Use Michael's lock/unlock methods to prevent the UART2 task from calling checkUblox during a sendCommand and
+// waitForResponse. Also prevents pushRawData from being called too.
+class SFE_UBLOX_GNSS_SUPER_DERIVED : public SFE_UBLOX_GNSS_SUPER
{
public:
- boolean getModuleInfo(uint16_t maxWait = 1100); //Queries module, texts
+ // SemaphoreHandle_t gnssSemaphore = nullptr;
- struct minfoStructure // Structure to hold the module info (uses 341 bytes of RAM)
+ // Revert to a simple bool lock. The Mutex was causing occasional panics caused by
+ // vTaskPriorityDisinheritAfterTimeout in lock() (I think possibly / probably caused by the GNSS not being pinned to
+ // one core?
+ bool iAmLocked = false;
+
+ bool createLock(void)
+ {
+ // if (gnssSemaphore == nullptr)
+ // gnssSemaphore = xSemaphoreCreateMutex();
+ // return gnssSemaphore;
+
+ return true;
+ }
+ bool lock(void)
+ {
+ // return (xSemaphoreTake(gnssSemaphore, 2100) == pdPASS);
+
+ if (!iAmLocked)
+ {
+ iAmLocked = true;
+ return true;
+ }
+
+ unsigned long startTime = millis();
+ while (((millis() - startTime) < 2100) && (iAmLocked))
+ delay(1); // Yield
+
+ if (!iAmLocked)
+ {
+ iAmLocked = true;
+ return true;
+ }
+
+ return false;
+ }
+ void unlock(void)
{
- char swVersion[30];
- char hwVersion[10];
- uint8_t extensionNo = 0;
- char extension[10][30];
- } minfo;
+ // xSemaphoreGive(gnssSemaphore);
+
+ iAmLocked = false;
+ }
+ void deleteLock(void)
+ {
+ // vSemaphoreDelete(gnssSemaphore);
+ // gnssSemaphore = nullptr;
+ }
};
-SFE_UBLOX_GNSS_ADD i2cGNSS;
+SFE_UBLOX_GNSS_SUPER_DERIVED theGNSS;
+
+#ifdef COMPILE_L_BAND
+static SFE_UBLOX_GNSS_SUPER i2cLBand; // NEO-D9S
+
+void checkRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct);
+#endif
+
+volatile struct timeval
+ gnssSyncTv; // This holds the time the RTC was sync'd to GNSS time via Time Pulse interrupt - used by NTP
+struct timeval previousGnssSyncTv; // This holds the time of the previous RTC sync
+
+// These globals are updated regularly via the storePVTdata callback
+unsigned long pvtArrivalMillis = 0;
+bool pvtUpdated = false;
+double latitude;
+double longitude;
+float altitude;
+float horizontalAccuracy;
+bool validDate;
+bool validTime;
+bool confirmedDate;
+bool confirmedTime;
+bool fullyResolved;
+uint32_t tAcc;
+uint8_t gnssDay;
+uint8_t gnssMonth;
+uint16_t gnssYear;
+uint8_t gnssHour;
+uint8_t gnssMinute;
+uint8_t gnssSecond;
+int32_t gnssNano;
+uint16_t mseconds;
+uint8_t numSV;
+uint8_t fixType;
+uint8_t carrSoln;
+
+unsigned long timTpArrivalMillis = 0;
+bool timTpUpdated = false;
+uint32_t timTpEpoch;
+uint32_t timTpMicros;
+
+uint8_t aStatus = SFE_UBLOX_ANTENNA_STATUS_DONTKNOW;
+
+unsigned long lastARPLog = 0; // Time of the last ARP log event
+bool newARPAvailable = false;
+int64_t ARPECEFX = 0; // ARP ECEF is 38-bit signed
+int64_t ARPECEFY = 0;
+int64_t ARPECEFZ = 0;
+uint16_t ARPECEFH = 0;
+
+const byte haeNumberOfDecimals = 8; // Used for printing and transmitting lat/lon
+bool lBandCommunicationEnabled = false;
+bool lBandForceGetKeys = false; //Used to allow key update from display
+unsigned long rtcmLastPacketReceived = 0; //Monitors the last time we received RTCM. Proctors PMP vs RTCM prioritization.
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// GPS parse table
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// Define the parsers that get included
+#define PARSE_NMEA_MESSAGES
+#define PARSE_RTCM_MESSAGES
+#define PARSE_UBLOX_MESSAGES
+
+// Build the GPS_PARSE_TABLE macro
+#include "GpsMessageParser.h" // Include the parser
+
+// Create the GPS message parse table instance
+GPS_PARSE_TABLE;
-//Used for config ZED for things not supported in library: getPortSettings, getSerialRate, getNMEASettings, getRTCMSettings
-//This array holds the payload data bytes. Global so that we can use between config functions.
-#define MAX_PAYLOAD_SIZE 384 // Override MAX_PAYLOAD_SIZE for getModuleInfo which can return up to 348 bytes
-uint8_t settingPayload[MAX_PAYLOAD_SIZE];
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//Battery fuel gauge and PWM LEDs
+// Battery fuel gauge and PWM LEDs
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-#include // Click here to get the library: http://librarymanager/All#SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library
+#include //Click here to get the library: http://librarymanager/All#SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library
SFE_MAX1704X lipo(MAX1704X_MAX17048);
-// setting PWM properties
-const int freq = 5000;
+// RTK Surveyor LED PWM properties
+const int pwmFreq = 5000;
const int ledRedChannel = 0;
const int ledGreenChannel = 1;
-const int resolution = 8;
+const int ledBTChannel = 2;
+const int pwmResolution = 8;
-int battLevel = 0; //SOC measured from fuel gauge, in %. Used in multiple places (display, serial debug, log)
+int pwmFadeAmount = 10;
+int btFadeLevel = 0;
+
+int battLevel = 0; // SOC measured from fuel gauge, in %. Used in multiple places (display, serial debug, log)
float battVoltage = 0.0;
float battChangeRate = 0.0;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//Hardware serial and BT buffers
+// Hardware serial and BT buffers
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//We use a local copy of the BluetoothSerial library so that we can increase the RX buffer. See issue: https://github.com/sparkfun/SparkFun_RTK_Surveyor/issues/18
+#ifdef COMPILE_BT
+// See bluetoothSelect.h for implemenation
+#include "bluetoothSelect.h"
+#endif // COMPILE_BT
-#define COMPILE_BT 1 //Comment out to disable all Bluetooth
+#define platformPrefix platformPrefixTable[productVariant] // Sets the prefix for broadcast names
-#ifdef COMPILE_BT
-#include "src/BluetoothSerial/BluetoothSerial.h"
-BluetoothSerial SerialBT;
-#include "esp_bt.h" //Core access is needed for BT stop. See customBTstop() for more info.
-#include "esp_gap_bt_api.h" //Needed for setting of pin. See issue: https://github.com/sparkfun/SparkFun_RTK_Surveyor/issues/5
-#endif
+#include //Required for uart_set_rx_full_threshold() on cores > 2)
+#define AVERAGE_SENTENCE_LENGTH_IN_BYTES 32
+RING_BUFFER_OFFSET * rbOffsetArray;
+uint16_t rbOffsetEntries;
-#define SERIAL_SIZE_RX 4096 //Reduced from 16384 to make room for WiFi/NTRIP server capabilities
-uint8_t rBuffer[SERIAL_SIZE_RX]; //Buffer for reading from F9P to SPP
-uint8_t wBuffer[SERIAL_SIZE_RX]; //Buffer for writing from incoming SPP to F9P
-TaskHandle_t F9PSerialReadTaskHandle = NULL; //Store handles so that we can kill them if user goes into WiFi NTRIP Server mode
-TaskHandle_t F9PSerialWriteTaskHandle = NULL; //Store handles so that we can kill them if user goes into WiFi NTRIP Server mode
+uint8_t *ringBuffer; // Buffer for reading from F9P. At 230400bps, 23040 bytes/s. If SD blocks for 250ms, we need 23040
+ // * 0.25 = 5760 bytes worst case.
+TaskHandle_t gnssReadTaskHandle =
+ nullptr; // Store handles so that we can kill them if user goes into WiFi NTRIP Server mode
+const int gnssReadTaskStackSize = 2500;
-TaskHandle_t startUART2TaskHandle = NULL; //Dummy task to start UART2 on core 0.
-bool uart2Started = false;
+TaskHandle_t handleGnssDataTaskHandle = nullptr;
+const int handleGnssDataTaskStackSize = 3000;
-//Reduced stack size from 10,000 to 2,000 to make room for WiFi/NTRIP server capabilities
-const int readTaskStackSize = 2000;
-const int writeTaskStackSize = 2000;
+TaskHandle_t pinUART2TaskHandle = nullptr; // Dummy task to start hardware on an assigned core
+volatile bool uart2pinned = false; // This variable is touched by core 0 but checked by core 1. Must be volatile.
-char incomingBTTest = 0; //Stores incoming text over BT when in test mode
-//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+TaskHandle_t pinI2CTaskHandle = nullptr; // Dummy task to start hardware on an assigned core
+volatile bool i2cPinned = false; // This variable is touched by core 0 but checked by core 1. Must be volatile.
+
+TaskHandle_t pinBluetoothTaskHandle = nullptr; // Dummy task to start hardware on an assigned core
+volatile bool bluetoothPinned = false; // This variable is touched by core 0 but checked by core 1. Must be volatile.
+
+volatile static int combinedSpaceRemaining = 0; // Overrun indicator
+volatile static long fileSize = 0; // Updated with each write
+int bufferOverruns = 0; // Running count of possible data losses since power-on
+
+bool zedUartPassed = false; // Goes true during testing if ESP can communicate with ZED over UART
+const uint8_t btEscapeCharacter = '+';
+const uint8_t btMaxEscapeCharacters = 3; // Number of characters needed to enter command mode over B
-//External Display
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-#include //Click here to get the library: http://librarymanager/All#SparkFun_Micro_OLED
-#include "icons.h"
-#define PIN_RESET 9
-#define DC_JUMPER 1
-MicroOLED oled(PIN_RESET, DC_JUMPER);
+// External Display
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#include //http://librarymanager/All#SparkFun_Qwiic_Graphic_OLED
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//Firmware binaries loaded from SD
+// Firmware binaries loaded from SD
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include
int binCount = 0;
-char binFileNames[10][50];
-const char* forceFirmwareFileName = "RTK_Surveyor_Firmware_Force.bin"; //File that will be loaded at startup regardless of user input
+const int maxBinFiles = 10;
+char binFileNames[maxBinFiles][50];
+const char *forceFirmwareFileName =
+ "RTK_Surveyor_Firmware_Force.bin"; // File that will be loaded at startup regardless of user input
+int binBytesLastUpdate = 0; // Allows websocket notification to be sent every 100k bytes
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//Low frequency tasks
+// Low frequency tasks
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include
Ticker btLEDTask;
-float btLEDTaskPace = 0.5; //Seconds
-
-//Ticker battCheckTask;
-//float battCheckTaskPace = 2.0; //Seconds
+float btLEDTaskPace2Hz = 0.5;
+float btLEDTaskPace33Hz = 0.03;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//Accelerometer for bubble leveling
+// Accelerometer for bubble leveling
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "SparkFun_LIS2DH12.h" //Click here to get the library: http://librarymanager/All#SparkFun_LIS2DH12
SPARKFUN_LIS2DH12 accel;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-//Global variables
+// Buttons - Interrupt driven and debounce
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-uint8_t unitMACAddress[6]; //Use MAC address in BT broadcast and display
-char deviceName[20]; //The serial string that is broadcast. Ex: 'Surveyor Base-BC61'
-const byte menuTimeout = 15; //Menus will exit/timeout after this number of seconds
-bool inTestMode = false; //Used to re-route BT traffic while in test sub menu
-int systemTime_minutes = 0; //Used to test if logging is less than max minutes
-uint32_t powerPressedStartTime = 0; //Times how long user has been holding power button, used for power down
-uint8_t debounceDelay = 20; //ms to delay between button reads
+#include //http://librarymanager/All#JC_Button v2.1.2
+Button *setupBtn = nullptr; // We can't instantiate the buttons here because we don't yet know what pin numbers to use
+Button *powerBtn = nullptr;
+
+TaskHandle_t ButtonCheckTaskHandle = nullptr;
+const uint8_t ButtonCheckTaskPriority = 1; // 3 being the highest, and 0 being the lowest
+const int buttonTaskStackSize = 2000;
+
+const int shutDownButtonTime = 2000; // ms press and hold before shutdown
+unsigned long lastRockerSwitchChange = 0; // If quick toggle is detected (less than 500ms), enter WiFi AP Config mode
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+// Webserver for serving config page from ESP32 as Acess Point
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#ifdef COMPILE_WIFI
+#ifdef COMPILE_AP
+
+#include "ESPAsyncWebServer.h" //Get from: https://github.com/me-no-dev/ESPAsyncWebServer v1.2.3
+#include "form.h"
+
+AsyncWebServer *webserver = nullptr;
+AsyncWebSocket *websocket = nullptr;
+
+char *settingsCSV = nullptr; // Push large array onto heap
+
+#endif // COMPILE_AP
+#endif // COMPILE_WIFI
+
+// Because the incoming string is longer than max len, there are multiple callbacks so we
+// use a global to combine the incoming
+#define AP_CONFIG_SETTING_SIZE 5000
+char *incomingSettings = nullptr;
+int incomingSettingsSpot = 0;
+unsigned long timeSinceLastIncomingSetting = 0;
+unsigned long lastDynamicDataUpdate = 0;
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// PointPerfect Corrections
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#if __has_include("tokens.h")
+#include "tokens.h"
+#endif // __has_include("tokens.h")
+
+float lBandEBNO = 0.0; // Used on system status menu
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// ESP NOW for multipoint wireless broadcasting over 2.4GHz
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+#ifdef COMPILE_ESPNOW
+
+#include
+
+uint8_t espnowOutgoing[250]; // ESP NOW has max of 250 characters
+unsigned long espnowLastAdd; // Tracks how long since last byte was added to the outgoing buffer
+uint8_t espnowOutgoingSpot = 0; // ESP Now has max of 250 characters
+uint16_t espnowBytesSent = 0; // May be more than 255
+uint8_t receivedMAC[6]; // Holds the broadcast MAC during pairing
+
+int packetRSSI = 0;
+unsigned long lastEspnowRssiUpdate = 0;
+
+#endif // COMPILE_ESPNOW
+
+int espnowRSSI = 0;
+const uint8_t ESPNOW_MAX_PEERS = 5; // Maximum of 5 rovers
+
+// Ethernet
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#ifdef COMPILE_ETHERNET
+IPAddress ethernetIPAddress;
+IPAddress ethernetDNS;
+IPAddress ethernetGateway;
+IPAddress ethernetSubnetMask;
+
+class derivedEthernetUDP : public EthernetUDP
+{
+ public:
+ uint8_t getSockIndex()
+ {
+ return sockindex; // sockindex is protected in EthernetUDP. A derived class can access it.
+ }
+};
+volatile struct timeval ethernetNtpTv; // This will hold the time the Ethernet NTP packet arrived
+bool ntpLogIncreasing;
+
+#endif // COMPILE_ETHERNET
+
+unsigned long lastEthernetCheck = 0; // Prevents cable checking from continually happening
+//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Global variables
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+#define lbandMACAddress btMACAddress
+uint8_t wifiMACAddress[6]; // Display this address in the system menu
+uint8_t btMACAddress[6]; // Display this address when Bluetooth is enabled, otherwise display wifiMACAddress
+uint8_t ethernetMACAddress[6]; // Display this address when Ethernet is enabled, otherwise display wifiMACAddress
+char deviceName[70]; // The serial string that is broadcast. Ex: 'Surveyor Base-BC61'
+const uint16_t menuTimeout = 60 * 10; // Menus will exit/timeout after this number of seconds
+int systemTime_minutes = 0; // Used to test if logging is less than max minutes
+uint32_t powerPressedStartTime = 0; // Times how long user has been holding power button, used for power down
+bool inMainMenu = false; // Set true when in the serial config menu system.
+bool btPrintEcho = false; // Set true when in the serial config menu system via Bluetooth.
+bool btPrintEchoExit = false; // When true, exit all config menus.
uint32_t lastBattUpdate = 0;
uint32_t lastDisplayUpdate = 0;
+bool forceDisplayUpdate = false; // Goes true when setup is pressed, causes display to refresh real time
uint32_t lastSystemStateUpdate = 0;
+bool forceSystemStateUpdate = false; // Set true to avoid update wait
uint32_t lastAccuracyLEDUpdate = 0;
-uint32_t lastBaseLEDupdate = 0; //Controls the blinking of the Base LED
-
-uint32_t lastFileReport = 0; //When logging, print file record stats every few seconds
-long lastStackReport = 0; //Controls the report rate of stack highwater mark within a task
-uint32_t lastHeapReport = 0; //Report heap every 1s if option enabled
-uint32_t lastTaskHeapReport = 0; //Report task heap every 1s if option enabled
-uint32_t lastCasterLEDupdate = 0; //Controls the cycling of position LEDs during casting
-
-uint32_t lastSatelliteDishIconUpdate = 0;
-bool satelliteDishIconDisplayed = false; //Toggles as lastSatelliteDishIconUpdate goes above 1000ms
-uint32_t lastCrosshairIconUpdate = 0;
-bool crosshairIconDisplayed = false; //Toggles as lastCrosshairIconUpdate goes above 1000ms
+uint32_t lastBaseLEDupdate = 0; // Controls the blinking of the Base LED
+
+uint32_t lastFileReport = 0; // When logging, print file record stats every few seconds
+long lastStackReport = 0; // Controls the report rate of stack highwater mark within a task
+uint32_t lastHeapReport = 0; // Report heap every 1s if option enabled
+uint32_t lastTaskHeapReport = 0; // Report task heap every 1s if option enabled
+uint32_t lastCasterLEDupdate = 0; // Controls the cycling of position LEDs during casting
+uint32_t lastRTCAttempt = 0; // Wait 1000ms between checking GNSS for current date/time
+uint32_t lastRTCSync = 0; // Time in millis when the RTC was last sync'd
+bool rtcSyncd = false; // Set to true when the RTC has been sync'd via TP pulse
+uint32_t lastPrintPosition = 0; // For periodic display of the position
+uint32_t lastPrintState = 0; // For periodic display of the RTK state (solution)
+
uint32_t lastBaseIconUpdate = 0;
-bool baseIconDisplayed = false; //Toggles as lastBaseIconUpdate goes above 1000ms
-uint32_t lastWifiIconUpdate = 0;
-bool wifiIconDisplayed = false; //Toggles as lastWifiIconUpdate goes above 1000ms
-uint32_t lastLoggingIconUpdate = 0;
-int loggingIconDisplayed = 0; //Increases every 500ms while logging
+bool baseIconDisplayed = false; // Toggles as lastBaseIconUpdate goes above 1000ms
+uint8_t loggingIconDisplayed = 0; // Increases every 500ms while logging
+uint8_t espnowIconDisplayed = 0; // Increases every 500ms while transmitting
uint64_t lastLogSize = 0;
-bool logIncreasing = false; //Goes true when log file is greater than lastLogSize
-bool reuseLastLog = false; //Goes true if we have a reset due to software (rather than POR)
+bool logIncreasing = false; // Goes true when log file is greater than lastLogSize or logPosition changes
+bool reuseLastLog = false; // Goes true if we have a reset due to software (rather than POR)
-uint32_t lastRTCMPacketSent = 0; //Used to count RTCM packets sent during base mode
-uint32_t rtcmPacketsSent = 0; //Used to count RTCM packets sent via processRTCM()
+uint16_t rtcmPacketsSent = 0; // Used to count RTCM packets sent via processRTCM()
+uint32_t rtcmBytesSent = 0;
+uint32_t rtcmLastReceived = 0;
-uint32_t maxSurveyInWait_s = 60L * 15L; //Re-start survey-in after X seconds
+uint32_t maxSurveyInWait_s = 60L * 15L; // Re-start survey-in after X seconds
-uint32_t totalWriteTime = 0; //Used to calculate overall write speed using SdFat library
+uint16_t svinObservationTime = 0; // Use globals so we don't have to request these values multiple times (slow response)
+float svinMeanAccuracy = 0;
-bool setupByPowerButton = false; //We can change setup via tapping power button
+uint32_t lastSetupMenuChange = 0; // Auto-selects the setup menu option after 1500ms
+uint32_t lastTestMenuChange = 0; // Avoids exiting the test menu for at least 1 second
+
+bool firstRoverStart = false; // Used to detect if user is toggling power button at POR to enter test menu
+
+bool newEventToRecord = false; // Goes true when INT pin goes high
+uint32_t triggerCount = 0; // Global copy - TM2 event counter
+uint32_t triggerTowMsR = 0; // Global copy - Time Of Week of rising edge (ms)
+uint32_t triggerTowSubMsR = 0; // Global copy - Millisecond fraction of Time Of Week of rising edge in nanoseconds
+uint32_t triggerAccEst = 0; // Global copy - Accuracy estimate in nanoseconds
+
+bool firstPowerOn = true; // After boot, apply new settings to ZED if user switches between base or rover
+unsigned long splashStart = 0; // Controls how long the splash is displayed for. Currently min of 2s.
+bool restartBase = false; // If user modifies any NTRIP Server settings, we need to restart the base
+bool restartRover = false; // If user modifies any NTRIP Client settings, we need to restart the rover
+
+unsigned long startTime = 0; // Used for checking longest running functions
+bool lbandCorrectionsReceived = false; // Used to display L-Band SIV icon when corrections are successfully decrypted
+unsigned long lastLBandDecryption = 0; // Timestamp of last successfully decrypted PMP message
+volatile bool mqttMessageReceived = false; // Goes true when the subscribed MQTT channel reports back
+uint8_t leapSeconds = 0; // Gets set if GNSS is online
+unsigned long rtcWaitTime = 0; // At poweron, we give the RTC a few seconds to update during PointPerfect Key checking
+
+TaskHandle_t idleTaskHandle[MAX_CPU_CORES];
+uint32_t max_idle_count = MAX_IDLE_TIME_COUNT;
+
+bool firstRadioSpotBlink = false; // Controls when the shared icon space is toggled
+unsigned long firstRadioSpotTimer = 0;
+bool secondRadioSpotBlink = false; // Controls when the shared icon space is toggled
+unsigned long secondRadioSpotTimer = 0;
+bool thirdRadioSpotBlink = false; // Controls when the shared icon space is toggled
+unsigned long thirdRadioSpotTimer = 0;
+
+bool bluetoothIncomingRTCM = false;
+bool bluetoothOutgoingRTCM = false;
+bool netIncomingRTCM = false;
+bool netOutgoingRTCM = false;
+bool espnowIncomingRTCM = false;
+bool espnowOutgoingRTCM = false;
+
+static RtcmTransportState rtcmParsingState = RTCM_TRANSPORT_STATE_WAIT_FOR_PREAMBLE_D3;
+uint16_t failedParserMessages_UBX = 0;
+uint16_t failedParserMessages_RTCM = 0;
+uint16_t failedParserMessages_NMEA = 0;
+
+unsigned long btLastByteReceived = 0; // Track when last BT transmission was received.
+const long btMinEscapeTime = 2000; // Bluetooth serial traffic must stop this amount before an escape char is recognized
+uint8_t btEscapeCharsReceived = 0; // Used to enter command mode
+
+bool externalPowerConnected = false; // Goes true when a high voltage is seen on power control pin
+
+// configureViaEthernet:
+// Set to true if configureViaEthernet.txt exists in LittleFS.
+// Causes setup and loop to skip any code which would cause SPI or interrupts to be initialized.
+// This is to allow SparkFun_WebServer_ESP32_W5500 to have _exclusive_ access to WiFi, SPI and Interrupts.
+bool configureViaEthernet = false;
+
+unsigned long lbandTimeFloatStarted = 0; // Monitors the ZED during L-Band reception if a fix takes too long
+int lbandRestarts = 0;
+unsigned long lbandTimeToFix = 0;
+unsigned long lbandLastReport = 0;
+
+volatile PeriodicDisplay_t periodicDisplay;
+
+unsigned long shutdownNoChargeTimer = 0;
+
+RtkMode_t rtkMode; // Mode of operation
+
+bool sdCardForcedOffline = false; //Goes true if a isPresent() test passes, but then sdFat fails to mount SD card.
+//See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/758
+
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+#define DEAD_MAN_WALKING_ENABLED 0
+
+#if DEAD_MAN_WALKING_ENABLED
+
+// Developer subsitutions enabled by changing DEAD_MAN_WALKING_ENABLED
+// from 0 to 1
+volatile bool deadManWalking;
+#define DMW_if if (deadManWalking)
+#define DMW_c(string) DMW_if systemPrintf("%s called\r\n", string);
+#define DMW_ds(routine, dataStructure) DMW_if routine(dataStructure, dataStructure->state);
+#define DMW_m(string) DMW_if systemPrintln(string);
+#define DMW_r(string) DMW_if systemPrintf("%s returning\r\n",string);
+#define DMW_rs(string, status) DMW_if systemPrintf("%s returning %d\r\n",string, (int32_t)status);
+#define DMW_st(routine, state) DMW_if routine(state);
+
+#define START_DEAD_MAN_WALKING \
+{ \
+ deadManWalking = true; \
+ \
+ /* Output as much as possible to identify the location of the failure */ \
+ settings.printDebugMessages = true; \
+ settings.enableI2Cdebug = true; \
+ settings.enableHeapReport = true; \
+ settings.enableTaskReports = true; \
+ settings.enablePrintState = true; \
+ settings.enablePrintPosition = true; \
+ settings.enablePrintIdleTime = true; \
+ settings.enablePrintBatteryMessages = true; \
+ settings.enablePrintRoverAccuracy = true; \
+ settings.enablePrintBadMessages = true; \
+ settings.enablePrintLogFileMessages = true; \
+ settings.enablePrintLogFileStatus = true; \
+ settings.enablePrintRingBufferOffsets = true; \
+ settings.enablePrintStates = true; \
+ settings.enablePrintDuplicateStates = true; \
+ settings.enablePrintRtcSync = true; \
+ settings.enablePrintBufferOverrun = true; \
+ settings.enablePrintSDBuffers = true; \
+ settings.periodicDisplay = (PeriodicDisplay_t)-1; \
+ settings.enablePrintEthernetDiag = true; \
+ settings.debugWifiState = true; \
+ settings.debugNetworkLayer = true; \
+ settings.printNetworkStatus = true; \
+ settings.debugNtripClientRtcm = true; \
+ settings.debugNtripClientState = true; \
+ settings.debugNtripServerRtcm = true; \
+ settings.debugNtripServerState = true; \
+ settings.debugPvtClient = true; \
+ settings.debugPvtServer = true; \
+ settings.debugPvtUdpServer = true; \
+}
+
+#else // 0
+
+// Production substitutions
+#define deadManWalking 0
+#define DMW_if if (0)
+#define DMW_c(string)
+#define DMW_ds(routine, dataStructure)
+#define DMW_m(string)
+#define DMW_r(string)
+#define DMW_rs(string, status)
+#define DMW_st(routine, state)
+
+#endif // 0
+
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+/*
+ +---------------------------------------+ +----------+
+ | ESP32 | | GNSS | Antenna
+ +-------------+ | | | | |
+ | Phone | | .-----------. .--------. |27 42| | |
+ | RTCM |--->|-->| |--------->| |-->|----->|TXD, MISO | |
+ | | | | Bluetooth | | UART 2 | | | UART1 | |
+ | NMEA + RTCM |<---|<--| |<-------+-| |<--|<-----|RXD, MOSI |<----'
+ +-------------+ | '-----------' | '--------' |28 43| |
+ | | | | |
+ .---------+ | | | | |
+ / uSD Card | | | | | |
+ / | | .----. V | | |
+ | Log File |<---|<--| |<--------------+ | | |47
+ | | | | | | | | D_SEL |<---- N/C (1)
+ | Profile # |<-->|<->| SD |<--> Profile | | | 0 = SPI |
+ | | | | | | | | 1 = I2C |
+ | Settings |<-->|<->| |<--> Settings | | | UART1 |
+ | | | '----' | | | |
+ +------------+ | | | | |
+ | .--------. | | | |
+ | | |<----------' | | |
+ | | USB | | | |
+ USB UART <--->|<->| Serial |<-- Debug Output | | |
+ (Config ESP32) | | | | | |
+ | | |<-- Serial Config | | UART 2 |<--> Radio
+ | '--------' | | | Connector
+ | | | | (Correction
+ | .------. | | | Data)
+ Browser <--->|<->| |<---> WiFi Config | | |
+ | | | | | |
+ +--------------+ | | | | | USB |<--> USB UART
+ | |<--|<--| WiFi |<---- NMEA + RTCM <-. | | | (Config UBLOX)
+ | NTRIP Caster | | | | | | | |
+ | |-->|-->| |-----------. | |6 46| |
+ +--------------+ | | | | | .----|<-----|TXREADY |
+ | '------' | | v | | |
+ | | .-----. | | |
+ | '----->| | |33 44| |
+ | | |<->|<---->|SDA, CS_N |
+ | Commands -------->| I2C | | | I2C |
+ | | |-->|----->|SCL, CLK |
+ | Status <--------| | |36 45| |
+ | '-----' | +----------+
+ | |
+ +---------------------------------------+
+ 26| |24 A B
+ | | 0 0 = X0, Y0
+ V V 0 1 = X1, Y1
+ +-------+ 1 0 = X2, Y2
+ | B A | 1 1 = X3, Y3
+ | |
+ | X0|<--- GNSS UART1 TXD
+ | |
+ | X1|<--- GNSS PPS STAT
+ 3 <---|X |
+ | X2|<--- SCL
+ | |
+ | X3|<--- DAC2
+ Data Port | |
+ | Y0|----> ZED UART1 RXD
+ | |
+ | Y1|<--> ZED EXT INT
+ 2 <-->|Y |
+ | Y2|---> SDA
+ | |
+ | Y3|---> ADC39
+ | |
+ | MUX |
+ +-------+
+*/
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// Initialize any globals that can't easily be given default values
+
+void initializeGlobals()
+{
+ gnssSyncTv.tv_sec = 0;
+ gnssSyncTv.tv_usec = 0;
+ previousGnssSyncTv.tv_sec = 0;
+ previousGnssSyncTv.tv_usec = 0;
+}
-uint16_t svinObservationTime = 0; //Use globals so we don't have to request these values multiple times (slow response)
-float svinMeanAccuracy = 0;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
void setup()
{
- Serial.begin(115200); //UART0 for programming and debugging
+ initializeGlobals(); // Initialize any global variables that can't be given default values
- Wire.begin(); //Start I2C on core 1
- Wire.setClock(400000);
+ Serial.begin(115200); // UART0 for programming and debugging
- beginBoard(); //Determine what hardware platform we are running on
+ DMW_c("verifyTables");
+ verifyTables (); // Verify the consistency of the internal tables
- beginDisplay(); //Check if an external Qwiic OLED is attached
+ DMW_c("identifyBoard");
+ identifyBoard(); // Determine what hardware platform we are running on
- beginLEDs(); //LED and PWM setup
+ DMW_c("initializePowerPins");
+ initializePowerPins(); // Initialize any essential power pins - e.g. enable power for the Display
- //Start EEPROM and SD for settings, and display for output
- //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
- beginEEPROM();
+ DMW_c("beginMux");
+ beginMux(); // Must come before I2C activity to avoid external devices from corrupting the bus. See issue #474:
+ // https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/474
- //eepromErase();
+ DMW_c("beginI2C");
+ beginI2C();
- beginSD(); //Test if SD is present
- if (online.microSD == true)
- {
- Serial.println(F("microSD online"));
- scanForFirmware(); //See if SD card contains new firmware that should be loaded at startup
- }
+ DMW_c("beginDisplay");
+ beginDisplay(); // Start display to be able to display any errors
- loadSettings(); //Attempt to load settings after SD is started so we can read the settings file if available
- //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ DMW_c("findSpiffsPartition");
+ if (!findSpiffsPartition())
+ {
+ printPartitionTable(); // Print the partition tables
+ reportFatalError("spiffs partition not found!");
+ }
+
+ DMW_c("beginFS");
+ beginFS(); // Start LittleFS file system for settings
+
+ DMW_c("checkConfigureViaEthernet");
+ configureViaEthernet =
+ checkConfigureViaEthernet(); // Check if going into dedicated configureViaEthernet (STATE_CONFIG_VIA_ETH) mode
+
+ DMW_c("beginGNSS");
+ beginGNSS(); // Connect to GNSS to get module type
+
+ DMW_c("beginBoard");
+ beginBoard(); // Now finish setting up the board and check the on button
+
+ DMW_c("displaySplash");
+ displaySplash(); // Display the RTK product name and firmware version
+
+ DMW_c("beginLEDs");
+ beginLEDs(); // LED and PWM setup
+
+ DMW_c("beginSD");
+ beginSD(); // Test if SD is present
+
+ DMW_c("loadSettings");
+ loadSettings(); // Attempt to load settings after SD is started so we can read the settings file if available
+
+ DMW_c("beginIdleTasks");
+ beginIdleTasks(); // Enable processor load calculations
+
+ DMW_c("beginUART2");
+ beginUART2(); // Start UART2 on core 0, used to receive serial from ZED and pass out over SPP
+
+ DMW_c("beginFuelGauge");
+ beginFuelGauge(); // Configure battery fuel guage monitor
- beginUART2(); //Start UART2 on core 0, used to receive serial from ZED and pass out over SPP
+ DMW_c("configureGNSS");
+ configureGNSS(); // Configure ZED module
- beginFuelGauge(); //Configure battery fuel guage monitor
- checkBatteryLevels(); //Force display so you see battery level immediately at power on
+ DMW_c("ethernetBegin");
+ ethernetBegin(); // Start-up the Ethernet connection
- beginGNSS(); //Connect and configure ZED-F9P
+ DMW_c("beginAccelerometer");
+ beginAccelerometer();
- beginAccelerometer();
+ DMW_c("beginLBand");
+ beginLBand(); // Begin L-Band
- beginSystemState(); //Determine initial system state
+ DMW_c("beginExternalTriggers");
+ beginExternalTriggers(); // Configure the time pulse output and TM2 input
- Serial.flush(); //Complete any previous prints
+ DMW_c("beginInterrupts");
+ beginInterrupts(); // Begin the TP and W5500 interrupts
- danceLEDs(); //Turn on LEDs like a car dashboard
+ DMW_c("beginSystemState");
+ beginSystemState(); // Determine initial system state. Start task for button monitoring.
+
+ DMW_c("updateRTC");
+ updateRTC(); // The GNSS likely has time/date. Update ESP32 RTC to match. Needed for PointPerfect key expiration.
+
+ Serial.flush(); // Complete any previous prints
+
+ log_d("Boot time: %d", millis());
+
+ DMW_c("danceLEDs");
+ danceLEDs(); // Turn on LEDs like a car dashboard
}
void loop()
{
- i2cGNSS.checkUblox(); //Regularly poll to get latest data and any RTCM
+ static uint32_t lastPeriodicDisplay;
+
+ // Determine which items are periodically displayed
+ if ((millis() - lastPeriodicDisplay) >= settings.periodicDisplayInterval)
+ {
+ lastPeriodicDisplay = millis();
+ periodicDisplay = settings.periodicDisplay;
+
+ // Reboot the system after a specified timeout
+ if (((lastPeriodicDisplay / 1000) > settings.rebootSeconds) && (!inMainMenu))
+ ESP.restart();
+ }
+ if (deadManWalking)
+ periodicDisplay = (PeriodicDisplay_t)-1;
+
+ if (online.gnss == true)
+ {
+ DMW_c("theGNSS.checkUblox");
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ DMW_c("theGNSS.checkCallbacks");
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, eventTriggerReceived
+ }
+
+ DMW_c("updateSystemState");
+ updateSystemState();
- checkButtons(); //Change system state as needed
+ DMW_c("updateBattery");
+ updateBattery();
- updateSystemState();
+ DMW_c("updateDisplay");
+ updateDisplay();
- updateBattLEDs();
+ DMW_c("updateRTC");
+ updateRTC(); // Set system time to GNSS once we have fix
- updateDisplay();
+ DMW_c("updateSD");
+ updateSD(); // Check if SD needs to be started or is at max capacity
- updateRTC(); //Set system time to GNSS once we have fix
+ DMW_c("updateLogs");
+ updateLogs(); // Record any new data. Create or close files as needed.
- updateLogs(); //Record any new data. Create or close files as needed.
+ DMW_c("reportHeap");
+ reportHeap(); // If debug enabled, report free heap
- reportHeap(); //If debug enabled, report free heap
+ DMW_c("updateSerial");
+ updateSerial(); // Menu system via ESP32 USB connection
- //Menu system via ESP32 USB connection
- if (Serial.available()) menuMain(); //Present user menu
+ DMW_c("networkUpdate");
+ networkUpdate(); // Maintain the network connections
- //Convert current system time to minutes. This is used in F9PSerialReadTask()/updateLogs() to see if we are within max log window.
- systemTime_minutes = millis() / 1000L / 60;
+ DMW_c("updateLBand");
+ updateLBand(); // Check if we've recently received PointPerfect corrections or not
- delay(10); //A small delay prevents panic if no other I2C or functions are called
+ DMW_c("updateRadio");
+ updateRadio(); // Check if we need to finish sending any RTCM over link radio
+
+ DMW_c("printPosition");
+ printPosition(); // Periodically print GNSS coordinates if enabled
+
+ DMW_c("printRTKState");
+ printRTKState(); // Periodically print RTK state (solution) if enabled
+
+ // A small delay prevents panic if no other I2C or functions are called
+ delay(10);
}
-//Create or close files as needed (startup or as user changes settings)
-//Push new data to log as needed
-void updateLogs()
+// Monitor if SD card is online or not
+// Attempt to remount SD card if card is offline but present
+// Capture card size when mounted
+void updateSD()
{
- if (online.logging == false && settings.enableLogging == true)
- {
- beginLogging();
- }
- else if (online.logging == true && settings.enableLogging == false)
- {
- //Close down file
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ if (online.microSD == false)
{
- ubxFile.sync();
- ubxFile.close();
- online.logging = false;
- //xSemaphoreGive(xFATSemaphore); //Do not release semaphore
+ // Are we offline because we are out of space?
+ if (outOfSDSpace == true)
+ {
+ if (sdPresent() == false) // Poll card to see if user has removed card
+ outOfSDSpace = false;
+ }
+ else if (sdPresent() == true) // Poll card to see if a card is inserted
+ {
+ beginSD(); // Attempt to start SD
+ if(online.microSD == true)
+ systemPrintln("SD inserted");
+ }
}
- }
- //Report file sizes to show recording is working
- if (online.logging == true)
- {
- if (millis() - lastFileReport > 5000)
+ if (online.logging == true && sdCardSize > 0 &&
+ sdFreeSpace < sdMinAvailableSpace) // Stop logging if we are below the min
{
- long fileSize = 0;
+ log_d("Logging stopped. SD full.");
+ outOfSDSpace = true;
+ endSD(false, true); //(alreadyHaveSemaphore, releaseSemaphore) Close down file.
+ return;
+ }
- //Attempt to access file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile() and F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_shortWait_ms) == pdPASS)
- {
- fileSize = ubxFile.fileSize();
+ if (online.microSD && sdCardSize == 0)
+ beginSDSizeCheckTask(); // Start task to determine SD card size
- xSemaphoreGive(xFATSemaphore);
- }
+ if (sdSizeCheckTaskComplete == true)
+ deleteSDSizeCheckTask();
- if (fileSize > 0)
- {
- lastFileReport = millis();
- Serial.printf("UBX file size: %ld", fileSize);
+ // Check if SD card is still present
+ if (productVariant == REFERENCE_STATION)
+ {
+ if (sdPresent() == false)
+ endSD(false, true); //(alreadyHaveSemaphore, releaseSemaphore) Close down SD.
+ }
+}
- if ((systemTime_minutes - startLogTime_minutes) < settings.maxLogTime_minutes)
+// Create or close files as needed (startup or as user changes settings)
+// Push new data to log as needed
+void updateLogs()
+{
+ // Convert current system time to minutes. This is used in F9PSerialReadTask()/updateLogs() to see if we are within
+ // 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)
+ return;
+
+ if (online.microSD == false)
+ return; // We can't log if there is no SD
+
+ if (outOfSDSpace == true)
+ return; // We can't log if we are out of SD space
+
+ if (online.logging == false && settings.enableLogging == true)
+ {
+ beginLogging();
+
+ setLoggingType(); // Determine if we are standard, PPP, or custom. Changes logging icon accordingly.
+ }
+ else if (online.logging == true && settings.enableLogging == false)
+ {
+ // Close down file
+ endSD(false, true);
+ }
+ 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();
+ }
+
+ if (online.logging == true)
+ {
+ // Record any pending trigger events
+ if (newEventToRecord == true)
{
- //Calculate generation and write speeds every 5 seconds
- uint32_t delta = fileSize - lastLogSize;
- Serial.printf(" - Generation rate: %0.1fkB/s", delta / 5.0 / 1000.0);
- Serial.printf(" - Write speed: %0.1fkB/s", delta / (totalWriteTime / 1000000.0) / 1000.0);
+ systemPrintln("Recording event");
+
+ // Record trigger count with Time Of Week of rising edge (ms), Millisecond fraction of Time Of Week of
+ // rising edge (ns), and accuracy estimate (ns)
+ char eventData[82]; // Max NMEA sentence length is 82
+ snprintf(eventData, sizeof(eventData), "%d,%d,%d,%d", triggerCount, triggerTowMsR, triggerTowSubMsR,
+ triggerAccEst);
+
+ char nmeaMessage[82]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_EVENT, nmeaMessage, sizeof(nmeaMessage),
+ eventData); // textID, buffer, sizeOfBuffer, text
+
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_shortWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_EVENT);
+
+ ubxFile->println(nmeaMessage);
+
+ xSemaphoreGive(sdCardSemaphore);
+ newEventToRecord = false;
+ }
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+
+ // While a retry does occur during the next loop, it is possible to loose
+ // trigger events if they occur too rapidly or if the log file is closed
+ // before the trigger event is written!
+ log_w("sdCardSemaphore failed to yield, held by %s, RTK_Surveyor.ino line %d", semaphoreHolder,
+ __LINE__);
+ }
}
- else
+
+ // Record the Antenna Reference Position - if available
+ if (newARPAvailable == true && settings.enableARPLogging &&
+ ((millis() - lastARPLog) > (settings.ARPLoggingInterval_s * 1000)))
{
- Serial.printf(" reached max log time %d", settings.maxLogTime_minutes);
+ systemPrintln("Recording Antenna Reference Position");
+
+ lastARPLog = millis();
+ newARPAvailable = false;
+
+ double x = ARPECEFX;
+ x /= 10000.0; // Convert to m
+ double y = ARPECEFY;
+ y /= 10000.0; // Convert to m
+ double z = ARPECEFZ;
+ z /= 10000.0; // Convert to m
+ double h = ARPECEFH;
+ h /= 10000.0; // Convert to m
+ char ARPData[82]; // Max NMEA sentence length is 82
+ snprintf(ARPData, sizeof(ARPData), "%.4f,%.4f,%.4f,%.4f", x, y, z, h);
+
+ char nmeaMessage[82]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_ARP_ECEF_XYZH, nmeaMessage, sizeof(nmeaMessage),
+ ARPData); // textID, buffer, sizeOfBuffer, text
+
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_shortWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_EVENT);
+
+ ubxFile->println(nmeaMessage);
+
+ xSemaphoreGive(sdCardSemaphore);
+ newEventToRecord = false;
+ }
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+ log_w("sdCardSemaphore failed to yield, held by %s, RTK_Surveyor.ino line %d", semaphoreHolder,
+ __LINE__);
+ }
}
- Serial.println();
-
- totalWriteTime = 0; //Reset write time every 5s
-
- if (fileSize > lastLogSize)
+ // Report file sizes to show recording is working
+ if ((millis() - lastFileReport) > 5000)
{
- lastLogSize = fileSize;
- logIncreasing = true;
+ if (fileSize > 0)
+ {
+ lastFileReport = millis();
+ if (settings.enablePrintLogFileStatus)
+ {
+ systemPrintf("Log file size: %ld", fileSize);
+
+ 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);
+ }
+ else
+ {
+ systemPrintf(" reached max log time %d", settings.maxLogTime_minutes);
+ }
+
+ systemPrintln();
+ }
+
+ if (fileSize > lastLogSize)
+ {
+ lastLogSize = fileSize;
+ logIncreasing = true;
+ }
+ else
+ {
+ log_d("No increase in file size");
+ logIncreasing = false;
+
+ endSD(false, true); // alreadyHaveSemaphore, releaseSemaphore
+ }
+ }
}
- else
- logIncreasing = false;
- }
}
- }
}
-//Once we have a fix, sync system clock to GNSS
-//All SD writes will use the system date/time
+// Once we have a fix, sync system clock to GNSS
+// All SD writes will use the system date/time
void updateRTC()
{
- if (online.rtc == false)
- {
- if (online.gnss == true)
+ if (online.rtc == false) // Only do this if the rtc has not been sync'd previously
+ {
+ if (online.gnss == true) // Only do this if the GNSS is online
+ {
+ if (millis() - lastRTCAttempt > syncRTCInterval) // Only attempt this once per second
+ {
+ lastRTCAttempt = millis();
+
+ // theGNSS.checkUblox and theGNSS.checkCallbacks are called in the loop but updateRTC
+ // can also be called duing begin. To be safe, check for fresh PVT data here.
+ theGNSS.checkUblox(); // Poll to get latest data
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, storePVTdata
+
+ bool timeValid = false;
+ if (validTime == true &&
+ validDate == true) // Will pass if ZED's RTC is reporting (regardless of GNSS fix)
+ timeValid = true;
+ if (confirmedTime == true && confirmedDate == true) // Requires GNSS fix
+ timeValid = true;
+ if (timeValid &&
+ (millis() - pvtArrivalMillis > 999)) // If the GNSS time is over a second old, don't use it
+ timeValid = false;
+
+ if (timeValid == true)
+ {
+ // To perform the time zone adjustment correctly, it's easiest if we convert the GNSS time and date
+ // into Unix epoch first and then apply the timeZone offset
+ uint32_t epochSecs;
+ uint32_t epochMicros;
+ convertGnssTimeToEpoch(&epochSecs, &epochMicros);
+ epochSecs += settings.timeZoneSeconds;
+ epochSecs += settings.timeZoneMinutes * 60;
+ epochSecs += settings.timeZoneHours * 60 * 60;
+
+ // Set the internal system time
+ rtc.setTime(epochSecs, epochMicros);
+
+ online.rtc = true;
+ lastRTCSync = millis();
+
+ systemPrint("System time set to: ");
+ systemPrintln(rtc.getDateTime(true));
+
+ recordSystemSettingsToFileSD(
+ settingsFileName); // This will re-record the setting file with current date/time.
+ }
+ else
+ {
+ systemPrintln("No GNSS date/time available for system RTC.");
+ } // End timeValid
+ } // End lastRTCAttempt
+ } // End online.gnss
+ } // End online.rtc
+
+ // Print TP time sync information here. Trying to do it in the ISR would be a bad idea....
+ if (settings.enablePrintRtcSync == true)
+ {
+ if ((previousGnssSyncTv.tv_sec != gnssSyncTv.tv_sec) || (previousGnssSyncTv.tv_usec != gnssSyncTv.tv_usec))
+ {
+ time_t nowtime;
+ struct tm *nowtm;
+ char tmbuf[64], buf[64];
+
+ nowtime = gnssSyncTv.tv_sec;
+ nowtm = localtime(&nowtime);
+ strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
+ systemPrintf("RTC resync took place at: %s.%03d\r\n", tmbuf, gnssSyncTv.tv_usec / 1000);
+
+ previousGnssSyncTv.tv_sec = gnssSyncTv.tv_sec;
+ previousGnssSyncTv.tv_usec = gnssSyncTv.tv_usec;
+ }
+ }
+}
+
+// Called from main loop
+// Control incoming/outgoing RTCM data from:
+// External radio - this is normally a serial telemetry radio hung off the RADIO port
+// Internal ESP NOW radio - Use the ESP32 to directly transmit/receive RTCM over 2.4GHz (no WiFi needed)
+void updateRadio()
+{
+ // If we have not gotten new RTCM bytes for a period of time, assume end of frame
+ if (millis() - rtcmLastReceived > 50 && rtcmBytesSent > 0)
{
- if (i2cGNSS.getConfirmedDate() == true && i2cGNSS.getConfirmedTime() == true)
- {
- //Set the internal system time
- //This is normally set with WiFi NTP but we will rarely have WiFi
- rtc.setTime(i2cGNSS.getSecond(), i2cGNSS.getMinute(), i2cGNSS.getHour(), i2cGNSS.getDay(), i2cGNSS.getMonth(), i2cGNSS.getYear()); // 17th Jan 2021 15:24:30
+ rtcmBytesSent = 0;
+ rtcmPacketsSent++; // If not checking RTCM CRC, count based on timeout
+ }
- online.rtc = true;
+#ifdef COMPILE_ESPNOW
+ if (settings.radioType == RADIO_ESPNOW)
+ {
+ if (espnowState == ESPNOW_PAIRED)
+ {
+ // 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 (settings.espnowBroadcast == false)
+ esp_now_send(0, (uint8_t *)&espnowOutgoing, espnowOutgoingSpot); // Send partial packet to all peers
+ else
+ {
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ esp_now_send(broadcastMac, (uint8_t *)&espnowOutgoing,
+ espnowOutgoingSpot); // Send packet via broadcast
+ }
+
+ if (!inMainMenu)
+ log_d("ESPNOW transmitted %d RTCM bytes", espnowBytesSent + espnowOutgoingSpot);
+ espnowBytesSent = 0;
+ espnowOutgoingSpot = 0; // Reset
+ }
+
+ // If we don't receive an ESP NOW packet after some time, set RSSI to very negative
+ // This removes the ESPNOW icon from the display when the link goes down
+ if (millis() - lastEspnowRssiUpdate > 5000 && espnowRSSI > -255)
+ espnowRSSI = -255;
+ }
+ }
+#endif // COMPILE_ESPNOW
+}
+
+// Record who is holding the semaphore
+volatile SemaphoreFunction semaphoreFunction = FUNCTION_NOT_SET;
- Serial.print(F("System time set to: "));
- Serial.println(rtc.getTime("%B %d %Y %H:%M:%S")); //From ESP32Time library example
+void markSemaphore(SemaphoreFunction functionNumber)
+{
+ semaphoreFunction = functionNumber;
+}
- recordSystemSettingsToFile(); //This will re-record the setting file with current date/time.
- }
+// Resolves the holder to a printable string
+void getSemaphoreFunction(char *functionName)
+{
+ switch (semaphoreFunction)
+ {
+ default:
+ strcpy(functionName, "Unknown");
+ break;
+
+ case FUNCTION_SYNC:
+ strcpy(functionName, "Sync");
+ break;
+ case FUNCTION_WRITESD:
+ strcpy(functionName, "Write");
+ break;
+ case FUNCTION_FILESIZE:
+ strcpy(functionName, "FileSize");
+ break;
+ case FUNCTION_EVENT:
+ strcpy(functionName, "Event");
+ break;
+ case FUNCTION_BEGINSD:
+ strcpy(functionName, "BeginSD");
+ break;
+ case FUNCTION_RECORDSETTINGS:
+ strcpy(functionName, "Record Settings");
+ break;
+ case FUNCTION_LOADSETTINGS:
+ strcpy(functionName, "Load Settings");
+ break;
+ case FUNCTION_MARKEVENT:
+ strcpy(functionName, "Mark Event");
+ break;
+ case FUNCTION_GETLINE:
+ strcpy(functionName, "Get line");
+ break;
+ case FUNCTION_REMOVEFILE:
+ strcpy(functionName, "Remove file");
+ break;
+ case FUNCTION_RECORDLINE:
+ strcpy(functionName, "Record Line");
+ break;
+ case FUNCTION_CREATEFILE:
+ strcpy(functionName, "Create File");
+ break;
+ case FUNCTION_ENDLOGGING:
+ strcpy(functionName, "End Logging");
+ break;
+ case FUNCTION_FINDLOG:
+ strcpy(functionName, "Find Log");
+ break;
+ case FUNCTION_LOGTEST:
+ strcpy(functionName, "Log Test");
+ break;
+ case FUNCTION_FILELIST:
+ strcpy(functionName, "File List");
+ break;
+ case FUNCTION_FILEMANAGER_OPEN1:
+ strcpy(functionName, "FileManager Open1");
+ break;
+ case FUNCTION_FILEMANAGER_OPEN2:
+ strcpy(functionName, "FileManager Open2");
+ break;
+ case FUNCTION_FILEMANAGER_OPEN3:
+ strcpy(functionName, "FileManager Open3");
+ break;
+ case FUNCTION_FILEMANAGER_UPLOAD1:
+ strcpy(functionName, "FileManager Upload1");
+ break;
+ case FUNCTION_FILEMANAGER_UPLOAD2:
+ strcpy(functionName, "FileManager Upload2");
+ break;
+ case FUNCTION_FILEMANAGER_UPLOAD3:
+ strcpy(functionName, "FileManager Upload3");
+ break;
+ case FUNCTION_SDSIZECHECK:
+ strcpy(functionName, "SD Size Check");
+ break;
+ case FUNCTION_LOG_CLOSURE:
+ strcpy(functionName, "Log Closure");
+ break;
+ case FUNCTION_NTPEVENT:
+ strcpy(functionName, "NTP Event");
+ break;
}
- }
}
diff --git a/Firmware/RTK_Surveyor/Rover.ino b/Firmware/RTK_Surveyor/Rover.ino
index 1599daea0..29b6053f9 100644
--- a/Firmware/RTK_Surveyor/Rover.ino
+++ b/Firmware/RTK_Surveyor/Rover.ino
@@ -1,271 +1,321 @@
-
-//Configure specific aspects of the receiver for rover mode
+// Configure specific aspects of the receiver for rover mode
bool configureUbloxModuleRover()
{
- bool response = true;
- int maxWait = 2000;
-
- response = i2cGNSS.disableSurveyMode(maxWait); //Disable survey
- if (response == false)
- Serial.println(F("Disable Survey failed"));
-
- // Set dynamic model
- if (i2cGNSS.getDynamicModel(maxWait) != settings.dynamicModel)
- {
- response = i2cGNSS.setDynamicModel((dynModel)settings.dynamicModel, maxWait);
- if (response == false)
- Serial.println(F("setDynamicModel failed"));
- }
-
- //Disable RTCM sentences on I2C, USB, and UART2
- response = true; //Reset
- response &= disableRTCMSentences(COM_PORT_I2C);
- response &= disableRTCMSentences(COM_PORT_UART2);
- response &= disableRTCMSentences(COM_PORT_USB);
-
- //Re-enable any RTCM msgs on UART1 the user has set within settings
- response &= configureGNSSMessageRates(COM_PORT_UART1, ubxMessages); //Make sure the appropriate messages are enabled
-
- if (response == false)
- Serial.println(F("Disable RTCM failed"));
-
- response = setNMEASettings(); //Enable high precision NMEA and extended sentences
- if (response == false)
- Serial.println(F("setNMEASettings failed"));
-
- response = true; //Reset
-
- //The last thing we do is set output rate.
- response = true; //Reset
- if (i2cGNSS.getMeasurementRate() != settings.measurementRate)
- {
- response &= i2cGNSS.setMeasurementRate(settings.measurementRate);
- }
- if (i2cGNSS.getNavigationRate() != settings.navigationRate)
- {
- response &= i2cGNSS.setNavigationRate(settings.navigationRate);
- }
- if (response == false)
- Serial.println(F("Set Nav Rate failed"));
-
- return (response);
-}
+ if (online.gnss == false)
+ {
+ log_d("GNSS not online");
+ return (false);
+ }
-//The u-blox library doesn't directly support NMEA configuration so let's do it manually
-bool setNMEASettings()
-{
- uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data bytes
- ubxPacket customCfg = {0, 0, 0, 0, 0, customPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
-
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_NMEA; // This is the message ID
- customCfg.len = 0; // Setting the len (length) to zero let's us poll the current settings
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
-
- uint16_t maxWait = 1250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100)
-
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("NMEA setting failed"));
- return (false);
- }
-
- customPayload[3] |= (1 << 3); //Set the highPrec flag
-
- customPayload[8] = 1; //Enable extended satellite numbering
-
- // Now we write the custom packet back again to change the setting
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_SENT) // This time we are only expecting an ACK
- {
- Serial.println(F("NMEA setting failed"));
- return (false);
- }
- return (true);
-}
+ // If our settings haven't changed, and this is first config since power on, trust ZED's settings
+ if (settings.updateZEDSettings == false && firstPowerOn == true)
+ {
+ firstPowerOn = false; // Next time user switches modes, new settings will be applied
+ log_d("Skipping ZED Rover configuration");
+ return (true);
+ }
-//Returns true if constellation is enabled
-bool getConstellation(uint8_t constellation)
-{
- uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data bytes
- ubxPacket customCfg = {0, 0, 0, 0, 0, customPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
+ firstPowerOn = false; // If we switch between rover/base in the future, force config of module.
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_GNSS; // This is the message ID
- customCfg.len = 0; // Setting the len (length) to zero lets us poll the current settings
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, storePVTdata
- uint16_t maxWait = 1250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100)
+ bool success = false;
+ int tryNo = -1;
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("Get Constellation failed"));
- return (false);
- }
+ // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS
+ // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI
+ // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being
+ // processed.
+ while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success)
+ {
+ bool response = true;
- if (customPayload[8 + 8 * constellation] & (1 << 0)) return true; //Check if bit 0 is set
- return false;
-}
+ // Set output rate
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_MEAS, settings.measurementRate);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_NAV, settings.navigationRate);
-//The u-blox library doesn't directly support constellation control so let's do it manually
-//Also allows the enable/disable of any constellation (BeiDou, Galileo, etc)
-bool setConstellation(uint8_t constellation, bool enable)
-{
- uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data bytes
- ubxPacket customCfg = {0, 0, 0, 0, 0, customPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
-
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_GNSS; // This is the message ID
- customCfg.len = 0; // Setting the len (length) to zero lets us poll the current settings
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
-
- uint16_t maxWait = 1250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100)
-
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("Set Constellation failed"));
- return (false);
- }
-
- if (enable)
- {
- if (constellation == SFE_UBLOX_GNSS_ID_GPS || constellation == SFE_UBLOX_GNSS_ID_QZSS)
- {
- //QZSS must follow GPS
- customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_GPS) + 4] |= (1 << 0); //Set the enable bit
- customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_QZSS) + 4] |= (1 << 0); //Set the enable bit
- }
- else
- {
- customPayload[locateGNSSID(customPayload, constellation) + 4] |= (1 << 0); //Set the enable bit
- }
+ // Survey mode is only available on ZED-F9P modules
+ if (commandSupported(UBLOX_CFG_TMODE_MODE) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode
- //Set sigCfgMask as well
- if (constellation == SFE_UBLOX_GNSS_ID_GPS || constellation == SFE_UBLOX_GNSS_ID_QZSS)
- {
- customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_GPS) + 6] |= 0x11; //Enable GPS L1C/A, and L2C
+ response &=
+ theGNSS.addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); // Set dynamic model
- //QZSS must follow GPS
- customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_QZSS) + 6] = 0x11; //Enable QZSS L1C/A, and L2C - Follow u-center
- //customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_QZSS) + 6] = 0x15; //Enable QZSS L1C/A, L1S, and L2C
- }
- else if (constellation == SFE_UBLOX_GNSS_ID_SBAS)
- {
- customPayload[locateGNSSID(customPayload, constellation) + 6] |= 0x01; //Enable SBAS L1C/A
- }
- else if (constellation == SFE_UBLOX_GNSS_ID_GALILEO)
- {
- customPayload[locateGNSSID(customPayload, constellation) + 6] |= 0x21; //Enable Galileo E1/E5b
- }
- else if (constellation == SFE_UBLOX_GNSS_ID_BEIDOU)
- {
- customPayload[locateGNSSID(customPayload, constellation) + 6] |= 0x11; //Enable BeiDou B1I/B2I
- }
- else if (constellation == SFE_UBLOX_GNSS_ID_GLONASS)
- {
- customPayload[locateGNSSID(customPayload, constellation) + 6] |= 0x11; //Enable GLONASS L1 and L2
- }
- }
- else //Disable
- {
- //QZSS must follow GPS
- if (constellation == SFE_UBLOX_GNSS_ID_GPS || constellation == SFE_UBLOX_GNSS_ID_QZSS)
- {
- customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_GPS) + 4] &= ~(1 << 0); //Clear the enable bit
+ // RTCM is only available on ZED-F9P modules
+ //
+ // For most RTK products, the GNSS is interfaced via both I2C and UART1. Configuration and PVT/HPPOS messages
+ // are configured over I2C. Any messages that need to be logged are output on UART1, and received by this code
+ // using serialGNSS. So in Rover mode, we want to disable any RTCM messages on I2C (and USB and UART2).
+ //
+ // But, on the Reference Station, the GNSS is interfaced via SPI. It has no access to I2C and UART1. So for that
+ // product - in Rover mode - we want to leave any RTCM messages enabled on SPI so they can be logged if desired.
+
+ // Find first RTCM record in ubxMessage array
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+
+ if (zedModuleType == PLATFORM_F9P)
+ {
+ if (USE_I2C_GNSS)
+ {
+ // Set RTCM messages to user's settings
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ response &= theGNSS.addCfgValset(
+ ubxMessages[firstRTCMRecord + x].msgConfigKey - 1,
+ settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 - 1 = I2C
+ }
+ else
+ {
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ response &= theGNSS.addCfgValset(
+ ubxMessages[firstRTCMRecord + x].msgConfigKey + 3,
+ settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 3 = SPI
+ }
+
+ // Set RTCM messages to user's settings
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ {
+ response &=
+ theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 1,
+ settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 1 = UART2
+ response &=
+ theGNSS.addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 2,
+ settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 2 = USB
+ }
+ }
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NMEA_MAINTALKERID,
+ 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NMEA_HIGHPREC, 1); // Enable high precision NMEA
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NMEA_SVNUMBERING, 1); // Enable extended satellite numbering
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation
+
+ response &= theGNSS.sendCfgValset(); // Closing
- customPayload[locateGNSSID(customPayload, SFE_UBLOX_GNSS_ID_QZSS) + 4] &= ~(1 << 0); //Clear the enable bit
+ if (response)
+ success = true;
}
- else
+
+ if (!success)
+ log_d("Rover config failed 1");
+
+ if (zedModuleType == PLATFORM_F9R)
{
- customPayload[locateGNSSID(customPayload, constellation) + 4] &= ~(1 << 0); //Clear the enable bit
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+
+ response &=
+ theGNSS.addCfgValset(UBLOX_CFG_SFCORE_USE_SF, settings.enableSensorFusion); // Enable/disable sensor fusion
+ response &=
+ theGNSS.addCfgValset(UBLOX_CFG_SFIMU_AUTO_MNTALG_ENA,
+ settings.autoIMUmountAlignment); // Enable/disable Automatic IMU-mount Alignment
+
+ if (zedFirmwareVersionInt >= 121)
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SFIMU_IMU_MNTALG_YAW, settings.imuYaw);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SFIMU_IMU_MNTALG_PITCH, settings.imuPitch);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SFIMU_IMU_MNTALG_ROLL, settings.imuRoll);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SFODO_DIS_AUTODIRPINPOL, settings.sfDisableWheelDirection);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SFODO_COMBINE_TICKS, settings.sfCombineWheelTicks);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_NAV_PRIO, settings.rateNavPrio);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SFODO_USE_SPEED, settings.sfUseSpeed);
+ }
+
+ response &= theGNSS.sendCfgValset(); // Closing - 28 keys
+
+ if (response == false)
+ {
+ log_d("Rover config failed 2");
+ success = false;
+ }
}
- }
+ if (!success)
+ systemPrintln("Rover config fail");
- // Now we write the custom packet back again to change the setting
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_SENT) // This time we are only expecting an ACK
- {
- Serial.println(F("Constellation setting failed"));
- return (false);
- }
+ return (success);
+}
- return (true);
+// Turn on the three accuracy LEDs depending on our current HPA (horizontal positional accuracy)
+void updateAccuracyLEDs()
+{
+ // Update the horizontal accuracy LEDs only every second or so
+ if (millis() - lastAccuracyLEDUpdate > 2000)
+ {
+ lastAccuracyLEDUpdate = millis();
+
+ if (online.gnss == true)
+ {
+ if (horizontalAccuracy > 0)
+ {
+ if (settings.enablePrintRoverAccuracy)
+ {
+ systemPrint("Rover Accuracy (m): ");
+ systemPrint(horizontalAccuracy, 4); // Print the accuracy with 4 decimal places
+ systemPrint(", SIV: ");
+ systemPrint(numSV);
+ systemPrintln();
+ }
+
+ if (productVariant == RTK_SURVEYOR)
+ {
+ if (horizontalAccuracy <= 0.02)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
+ }
+ else if (horizontalAccuracy <= 0.100)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
+ }
+ else if (horizontalAccuracy <= 1.0000)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
+ }
+ else if (horizontalAccuracy > 1.0)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ }
+ }
+ }
+ else if (settings.enablePrintRoverAccuracy)
+ {
+ systemPrint("Rover Accuracy: ");
+ systemPrint(horizontalAccuracy);
+ systemPrint(" ");
+ systemPrint("No lock. SIV: ");
+ systemPrint(numSV);
+ systemPrintln();
+ }
+ } // End GNSS online checking
+ } // Check every 2000ms
}
-//Given a payload, return the location of a given constellation
-//This is needed because IMES is not currently returned in the query packet
-//so QZSS and GLONAS are offset by -8 bytes.
-uint8_t locateGNSSID(uint8_t *customPayload, uint8_t constellation)
+// These are the callbacks that get regularly called, globals are updated
+void storePVTdata(UBX_NAV_PVT_data_t *ubxDataStruct)
{
- for (int x = 0 ; x < 7 ; x++) //Assume max of 7 constellations
- {
- if (customPayload[4 + 8 * x] == constellation) //Test gnssid
- return (4 + x * 8);
- }
-
- Serial.print(F("locateGNSSID failed: "));
- Serial.println(constellation);
- return (0);
+ altitude = ubxDataStruct->height / 1000.0;
+
+ gnssDay = ubxDataStruct->day;
+ gnssMonth = ubxDataStruct->month;
+ gnssYear = ubxDataStruct->year;
+
+ gnssHour = ubxDataStruct->hour;
+ gnssMinute = ubxDataStruct->min;
+ gnssSecond = ubxDataStruct->sec;
+ gnssNano = ubxDataStruct->nano;
+ mseconds = ceil((ubxDataStruct->iTOW % 1000) / 10.0); // Limit to first two digits
+
+ numSV = ubxDataStruct->numSV;
+ fixType = ubxDataStruct->fixType;
+ carrSoln = ubxDataStruct->flags.bits.carrSoln;
+
+ validDate = ubxDataStruct->valid.bits.validDate;
+ validTime = ubxDataStruct->valid.bits.validTime;
+ fullyResolved = ubxDataStruct->valid.bits.fullyResolved;
+ tAcc = ubxDataStruct->tAcc;
+ confirmedDate = ubxDataStruct->flags2.bits.confirmedDate;
+ confirmedTime = ubxDataStruct->flags2.bits.confirmedTime;
+
+ pvtArrivalMillis = millis();
+ pvtUpdated = true;
}
-//Turn on the three accuracy LEDs depending on our current HPA (horizontal positional accuracy)
-void updateAccuracyLEDs()
+void storeHPdata(UBX_NAV_HPPOSLLH_data_t *ubxDataStruct)
{
- //Update the horizontal accuracy LEDs only every second or so
- if (millis() - lastAccuracyLEDUpdate > 2000)
- {
- lastAccuracyLEDUpdate = millis();
+ horizontalAccuracy = ((float)ubxDataStruct->hAcc) / 10000.0; // Convert hAcc from mm*0.1 to m
- uint32_t accuracy = i2cGNSS.getHorizontalAccuracy(250);
+ latitude = ((double)ubxDataStruct->lat) / 10000000.0;
+ latitude += ((double)ubxDataStruct->latHp) / 1000000000.0;
+ longitude = ((double)ubxDataStruct->lon) / 10000000.0;
+ longitude += ((double)ubxDataStruct->lonHp) / 1000000000.0;
+}
- if (accuracy > 0)
- {
- // Convert the horizontal accuracy (mm * 10^-1) to a float
- float f_accuracy = accuracy;
- f_accuracy = f_accuracy / 10000.0; // Convert from mm * 10^-1 to m
+void storeTIMTPdata(UBX_TIM_TP_data_t *ubxDataStruct)
+{
+ uint32_t tow = ubxDataStruct->week - SFE_UBLOX_JAN_1ST_2020_WEEK; // Calculate the number of weeks since Jan 1st
+ // 2020
+ tow *= SFE_UBLOX_SECS_PER_WEEK; // Convert weeks to seconds
+ tow += SFE_UBLOX_EPOCH_WEEK_2086; // Add the TOW for Jan 1st 2020
+ tow += ubxDataStruct->towMS / 1000; // Add the TOW for the next TP
- Serial.print(F("Rover Accuracy (m): "));
- Serial.print(f_accuracy, 4); // Print the accuracy with 4 decimal places
- Serial.println();
+ uint32_t us = ubxDataStruct->towMS % 1000; // Extract the milliseconds
+ us *= 1000; // Convert to microseconds
- if (productVariant == RTK_SURVEYOR)
- {
- if (f_accuracy <= 0.02)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- }
- else if (f_accuracy <= 0.100)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- }
- else if (f_accuracy <= 1.0000)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- }
- else if (f_accuracy > 1.0)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
- }
+ double subMS = ubxDataStruct->towSubMS; // Get towSubMS (ms * 2^-32)
+ subMS *= pow(2.0, -32.0); // Convert to milliseconds
+ subMS *= 1000; // Convert to microseconds
+
+ us += (uint32_t)subMS; // Add subMS
+
+ timTpEpoch = tow;
+ timTpMicros = us;
+ timTpArrivalMillis = millis();
+ timTpUpdated = true;
+}
+
+void storeMONHWdata(UBX_MON_HW_data_t *ubxDataStruct)
+{
+ aStatus = ubxDataStruct->aStatus;
+}
+
+void storeRTCM1005data(RTCM_1005_data_t *rtcmData1005)
+{
+ ARPECEFX = rtcmData1005->AntennaReferencePointECEFX;
+ ARPECEFY = rtcmData1005->AntennaReferencePointECEFY;
+ ARPECEFZ = rtcmData1005->AntennaReferencePointECEFZ;
+ ARPECEFH = 0;
+ newARPAvailable = true;
+}
+
+void storeRTCM1006data(RTCM_1006_data_t *rtcmData1006)
+{
+ ARPECEFX = rtcmData1006->AntennaReferencePointECEFX;
+ ARPECEFY = rtcmData1006->AntennaReferencePointECEFY;
+ ARPECEFZ = rtcmData1006->AntennaReferencePointECEFZ;
+ ARPECEFH = rtcmData1006->AntennaHeight;
+ newARPAvailable = true;
+}
+
+// Helper method to convert GNSS time and date into Unix Epoch
+void convertGnssTimeToEpoch(uint32_t *epochSecs, uint32_t *epochMicros)
+{
+ uint32_t t = SFE_UBLOX_DAYS_FROM_1970_TO_2020; // Jan 1st 2020 as days from Jan 1st 1970
+ t += (uint32_t)SFE_UBLOX_DAYS_SINCE_2020[gnssYear - 2020]; // Add on the number of days since 2020
+ t += (uint32_t)
+ SFE_UBLOX_DAYS_SINCE_MONTH[gnssYear % 4 == 0 ? 0 : 1][gnssMonth - 1]; // Add on the number of days since Jan 1st
+ t += (uint32_t)gnssDay - 1; // Add on the number of days since the 1st of the month
+ t *= 24; // Convert to hours
+ t += (uint32_t)gnssHour; // Add on the hour
+ t *= 60; // Convert to minutes
+ t += (uint32_t)gnssMinute; // Add on the minute
+ t *= 60; // Convert to seconds
+ t += (uint32_t)gnssSecond; // Add on the second
+
+ int32_t us = gnssNano / 1000; // Convert nanos to micros
+ uint32_t micro;
+ // Adjust t if nano is negative
+ if (us < 0)
+ {
+ micro = (uint32_t)(us + 1000000); // Make nano +ve
+ t--; // Decrement t by 1 second
}
else
{
- Serial.print(F("Rover Accuracy: "));
- Serial.print(accuracy);
- Serial.print(" ");
- Serial.print(F("No lock. SIV: "));
- Serial.print(i2cGNSS.getSIV());
- Serial.println();
+ micro = us;
}
- }
+
+ *epochSecs = t;
+ *epochMicros = micro;
}
diff --git a/Firmware/RTK_Surveyor/SD.ino b/Firmware/RTK_Surveyor/SD.ino
new file mode 100644
index 000000000..efb7bda48
--- /dev/null
+++ b/Firmware/RTK_Surveyor/SD.ino
@@ -0,0 +1,169 @@
+/*
+ These are low level functions to aid in detecting whether a card is present or not.
+ Because of ESP32 v2 core, SdFat can only operate using Shared SPI. This makes the sd->begin test take over 1s
+ which causes the RTK product to boot slowly. To circumvent this, we will ping the SD card directly to see if it
+ responds. Failures take 2ms, successes take 1ms.
+
+ From Prototype puzzle:
+ https://github.com/sparkfunX/ThePrototype/blob/master/Firmware/TestSketches/sdLocker/sdLocker.ino License: Public
+ domain. This code is based on Karl Lunt's work: https://www.seanet.com/~karllunt/sdlocker2.html
+*/
+
+// Define commands for the SD card
+#define SD_GO_IDLE (0x40 + 0) // CMD0 - go to idle state
+#define SD_INIT (0x40 + 1) // CMD1 - start initialization
+#define SD_SEND_IF_COND (0x40 + 8) // CMD8 - send interface (conditional), works for SDHC only
+#define SD_SEND_STATUS (0x40 + 13) // CMD13 - send card status
+#define SD_SET_BLK_LEN (0x40 + 16) // CMD16 - set length of block in bytes
+#define SD_LOCK_UNLOCK (0x40 + 42) // CMD42 - lock/unlock card
+#define CMD55 (0x40 + 55) // multi-byte preface command
+#define SD_READ_OCR (0x40 + 58) // read OCR
+#define SD_ADV_INIT (0xc0 + 41) // ACMD41, for SDHC cards - advanced start initialization
+
+// Define options for accessing the SD card's PWD (CMD42)
+#define MASK_ERASE 0x08 // erase the entire card
+#define MASK_LOCK_UNLOCK 0x04 // lock or unlock the card with password
+#define MASK_CLR_PWD 0x02 // clear password
+#define MASK_SET_PWD 0x01 // set password
+
+// Define bit masks for fields in the lock/unlock command (CMD42) data structure
+#define SET_PWD_MASK (1 << 0)
+#define CLR_PWD_MASK (1 << 1)
+#define LOCK_UNLOCK_MASK (1 << 2)
+#define ERASE_MASK (1 << 3)
+
+// Begin initialization by sending CMD0 and waiting until SD card
+// responds with In Idle Mode (0x01). If the response is not 0x01
+// within a reasonable amount of time, there is no SD card on the bus.
+// Returns false if not card is detected
+// Returns true if a card responds
+// This test takes approximately 13ms to complete
+bool sdPresent(void)
+{
+ if (productVariant == REFERENCE_STATION)
+ {
+ if (pin_microSD_CardDetect > 0)
+ {
+ pinMode(pin_microSD_CardDetect, INPUT); // Internal pullups not supported on input only pins
+ if (digitalRead(pin_microSD_CardDetect) == LOW)
+ return (true); // Card low - SD in place
+ return (false); // Card detect high - No SD
+ }
+ }
+ else if (USE_SPI_MICROSD)
+ {
+ byte response = 0;
+
+ SPI.begin();
+ SPI.setClockDivider(SPI_CLOCK_DIV2);
+ SPI.setDataMode(SPI_MODE0);
+ SPI.setBitOrder(MSBFIRST);
+ pinMode(pin_microSD_CS, OUTPUT);
+
+ // Sending clocks while card power stabilizes...
+ deselectCard(); // always make sure
+ for (byte i = 0; i < 30; i++) // send several clocks while card power stabilizes
+ xchg(0xff);
+
+ // Sending CMD0 - GO IDLE...
+ for (byte i = 0; i < 0x10; i++) // Attempt to go idle
+ {
+ response = sdSendCommand(SD_GO_IDLE, 0); // send CMD0 - go to idle state
+ if (response == 1)
+ break;
+ }
+ if (response != 1)
+ return (false); // Card failed to respond to idle
+ }
+
+ return (true);
+}
+
+/*
+ sdSendCommand send raw command to SD card, return response
+
+ This routine accepts a single SD command and a 4-byte argument. It sends
+ the command plus argument, adding the appropriate CRC. It then returns
+ the one-byte response from the SD card.
+
+ For advanced commands (those with a command byte having bit 7 set), this
+ routine automatically sends the required preface command (CMD55) before
+ sending the requested command.
+
+ Upon exit, this routine returns the response byte from the SD card.
+ Possible responses are:
+ 0xff No response from card; card might actually be missing
+ 0x01 SD card returned 0x01, which is OK for most commands
+ 0x?? other responses are command-specific
+*/
+byte sdSendCommand(byte command, unsigned long arg)
+{
+ byte response;
+
+ if (command & 0x80) // special case, ACMD(n) is sent as CMD55 and CMDn
+ {
+ command &= 0x7f; // strip high bit for later
+ response = sdSendCommand(CMD55, 0); // send first part (recursion)
+ if (response > 1)
+ return (response);
+ }
+
+ deselectCard();
+ xchg(0xFF);
+ selectCard(); // enable CS
+ xchg(0xFF);
+
+ xchg(command | 0x40); // command always has bit 6 set!
+ xchg((byte)(arg >> 24)); // send data, starting with top byte
+ xchg((byte)(arg >> 16));
+ xchg((byte)(arg >> 8));
+ xchg((byte)(arg & 0xFF));
+
+ byte crc = 0x01; // good for most cases
+ if (command == SD_GO_IDLE)
+ crc = 0x95; // this will be good enough for most commands
+ if (command == SD_SEND_IF_COND)
+ crc = 0x87; // special case, have to use different CRC
+ xchg(crc); // send final byte
+
+ for (int i = 0; i < 30; i++) // loop until timeout or response
+ {
+ response = xchg(0xFF);
+ if ((response & 0x80) == 0)
+ break; // high bit cleared means we got a response
+ }
+
+ /*
+ We have issued the command but the SD card is still selected. We
+ only deselectCard the card if the command we just sent is NOT a command
+ that requires additional data exchange, such as reading or writing
+ a block.
+ */
+ if ((command != SD_READ_OCR) && (command != SD_SEND_STATUS) && (command != SD_SEND_IF_COND) &&
+ (command != SD_LOCK_UNLOCK))
+ {
+ deselectCard(); // all done
+ xchg(0xFF); // close with eight more clocks
+ }
+
+ return (response); // let the caller sort it out
+}
+
+// Select (enable) the SD card
+void selectCard(void)
+{
+ digitalWrite(pin_microSD_CS, LOW);
+}
+
+// Deselect (disable) the SD card
+void deselectCard(void)
+{
+ digitalWrite(pin_microSD_CS, HIGH);
+}
+
+// Exchange a byte of data with the SD card via host's SPI bus
+byte xchg(byte val)
+{
+ byte receivedVal = SPI.transfer(val);
+ return receivedVal;
+}
diff --git a/Firmware/RTK_Surveyor/States.ino b/Firmware/RTK_Surveyor/States.ino
index b95e55c2b..30657156d 100644
--- a/Firmware/RTK_Surveyor/States.ino
+++ b/Firmware/RTK_Surveyor/States.ino
@@ -1,610 +1,1365 @@
/*
This is the main state machine for the device. It's big but controls each step of the system.
- See system state chart document for a visual representation of how states can change to/from.
+ See system state diagram for a visual representation of how states can change to/from.
+ Statemachine diagram:
+ https://lucid.app/lucidchart/53519501-9fa5-4352-aa40-673f88ca0c9b/edit?invitationId=inv_ebd4b988-513d-4169-93fd-c291851108f8
*/
-//Given the current state, see if conditions have moved us to a new state
-//A user pressing the setup button (change between rover/base) is handled by checkpin_setupButton()
+static uint32_t lastStateTime = 0;
+
+// Given the current state, see if conditions have moved us to a new state
+// A user pressing the setup button (change between rover/base) is handled by checkpin_setupButton()
void updateSystemState()
{
- if (millis() - lastSystemStateUpdate > 500)
- {
- lastSystemStateUpdate = millis();
-
- //Move between states as needed
- switch (systemState)
+ if (millis() - lastSystemStateUpdate > 500 || forceSystemStateUpdate == true)
{
- case (STATE_ROVER_NOT_STARTED):
+ lastSystemStateUpdate = millis();
+ forceSystemStateUpdate = false;
+
+ // Check to see if any external sources need to change state
+ if (newSystemStateRequested == true)
{
- //Configure for rover mode
- displayRoverStart(0);
+ newSystemStateRequested = false;
+ if (systemState != requestedSystemState)
+ {
+ changeState(requestedSystemState);
+ lastStateTime = millis();
+ }
+ }
- //If we are survey'd in, but switch is rover then disable survey
- if (configureUbloxModuleRover() == false)
- {
- Serial.println(F("Rover config failed"));
- displayRoverFail(1000);
- return;
- }
+ if (settings.enablePrintStates && ((millis() - lastStateTime) > 15000))
+ {
+ changeState(systemState);
+ lastStateTime = millis();
+ }
- stopWiFi(); //Turn off WiFi and release all resources
- startBluetooth(); //Turn on Bluetooth with 'Rover' name
+ // Move between states as needed
+ DMW_st(changeState, systemState);
+ switch (systemState)
+ {
+ /*
+ .-----------------------------------.
+ | STATE_ROVER_NOT_STARTED |
+ | Text: 'Rover' and 'Rover Started' |
+ '-----------------------------------'
+ |
+ |
+ |
+ V
+ .-----------------------------------.
+ | STATE_ROVER_NO_FIX |
+ | SIV Icon Blink |
+ | "HPA: >30m" |
+ | "SIV: 0" |
+ '-----------------------------------'
+ |
+ | GPS Lock
+ | 3D, 3D+DR
+ V
+ .-----------------------------------.
+ | STATE_ROVER_FIX | Carrier
+ | SIV Icon Solid | Solution = 2
+ .-------->| "HPA: .513" |---------.
+ | | "SIV: 30" | |
+ | '-----------------------------------' |
+ | | |
+ | | Carrier Solution = 1 |
+ | V |
+ | .-----------------------------------. |
+ | | STATE_ROVER_RTK_FLOAT | |
+ | No RTK | Double Crosshair Blinking | |
+ +<--------| "*HPA: .080" | |
+ ^ | "SIV: 30" | |
+ | '-----------------------------------' |
+ | ^ | |
+ | | | Carrier |
+ | | | Solution = 2 |
+ | | V |
+ | Carrier | +<-------------------'
+ | Solution = 1 | |
+ | | V
+ | .-----------------------------------.
+ | | STATE_ROVER_RTK_FIX |
+ | No RTK | Double Crosshair Solid |
+ '---------| "*HPA: .014" |
+ | "SIV: 30" |
+ '-----------------------------------'
+
+ */
+ case (STATE_ROVER_NOT_STARTED): {
+ RTK_MODE(RTK_MODE_ROVER);
+ if (online.gnss == false)
+ {
+ firstRoverStart = false; // If GNSS is offline, we still need to allow button use
+ return;
+ }
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, LOW);
+ if (productVariant == RTK_SURVEYOR)
+ {
+ digitalWrite(pin_baseStatusLED, LOW);
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ ledcWrite(ledBTChannel, 0); // Turn off BT LED
+ }
- settings.lastState = STATE_ROVER_NOT_STARTED;
- recordSystemSettings();
+ if (productVariant == REFERENCE_STATION)
+ {
+ digitalWrite(pin_baseStatusLED, LOW);
+ }
- displayRoverSuccess(500);
+ // Configure for rover mode
+ displayRoverStart(0);
- changeState(STATE_ROVER_NO_FIX);
+ // If we are survey'd in, but switch is rover then disable survey
+ if (configureUbloxModuleRover() == false)
+ {
+ systemPrintln("Rover config failed");
+ displayRoverFail(1000);
+ return;
+ }
+
+ setMuxport(settings.dataPortChannel); // Return mux to original channel
+
+ NETWORK_STOP(NETWORK_TYPE_WIFI);
+ WIFI_STOP(); // Stop WiFi, ntripClient will start as needed.
+ bluetoothStart(); // Turn on Bluetooth with 'Rover' name
+ radioStart(); // Start internal radio if enabled, otherwise disable
+
+ if (!tasksStartUART2()) // Start monitoring the UART1 from ZED for NMEA and UBX data (enables logging)
+ displayRoverFail(1000);
+ else
+ {
+ settings.updateZEDSettings = false; // On the next boot, no need to update the ZED on this profile
+ settings.lastState = STATE_ROVER_NOT_STARTED;
+ recordSystemSettings(); // Record this state for next POR
+
+ displayRoverSuccess(500);
+
+ changeState(STATE_ROVER_NO_FIX);
+
+ firstRoverStart = false; // Do not allow entry into test menu again
+ }
}
break;
- case (STATE_ROVER_NO_FIX):
- {
- if (i2cGNSS.getFixType() == 3) //3D
- changeState(STATE_ROVER_FIX);
+ case (STATE_ROVER_NO_FIX): {
+ if (fixType == 3 || fixType == 4) // 3D, 3D+DR
+ changeState(STATE_ROVER_FIX);
}
break;
- case (STATE_ROVER_FIX):
- {
- updateAccuracyLEDs();
+ case (STATE_ROVER_FIX): {
+ updateAccuracyLEDs();
- byte rtkType = i2cGNSS.getCarrierSolutionType();
- if (rtkType == 1) //RTK Float
- changeState(STATE_ROVER_RTK_FLOAT);
- else if (rtkType == 2) //RTK Fix
- changeState(STATE_ROVER_RTK_FIX);
+ if (carrSoln == 1) // RTK Float
+ {
+ lbandTimeFloatStarted =
+ millis(); // Restart timer for L-Band. Don't immediately reset ZED to achieve fix.
+ changeState(STATE_ROVER_RTK_FLOAT);
+ }
+ else if (carrSoln == 2) // RTK Fix
+ changeState(STATE_ROVER_RTK_FIX);
}
break;
- case (STATE_ROVER_RTK_FLOAT):
- {
- updateAccuracyLEDs();
+ case (STATE_ROVER_RTK_FLOAT): {
+ updateAccuracyLEDs();
- byte rtkType = i2cGNSS.getCarrierSolutionType();
- if (rtkType == 0) //No RTK
- changeState(STATE_ROVER_FIX);
- if (rtkType == 2) //RTK Fix
- changeState(STATE_ROVER_RTK_FIX);
+ if (carrSoln == 0) // No RTK
+ changeState(STATE_ROVER_FIX);
+ if (carrSoln == 2) // RTK Fix
+ changeState(STATE_ROVER_RTK_FIX);
}
break;
- case (STATE_ROVER_RTK_FIX):
- {
- updateAccuracyLEDs();
+ case (STATE_ROVER_RTK_FIX): {
+ updateAccuracyLEDs();
- byte rtkType = i2cGNSS.getCarrierSolutionType();
- if (rtkType == 0) //No RTK
- changeState(STATE_ROVER_FIX);
- if (rtkType == 1) //RTK Float
- changeState(STATE_ROVER_RTK_FLOAT);
+ if (carrSoln == 0) // No RTK
+ changeState(STATE_ROVER_FIX);
+ if (carrSoln == 1) // RTK Float
+ {
+ lbandTimeFloatStarted =
+ millis(); // Restart timer for L-Band. Don't immediately reset ZED to achieve fix.
+ changeState(STATE_ROVER_RTK_FLOAT);
+ }
}
break;
- case (STATE_BASE_NOT_STARTED):
- {
- //Turn off base LED until we successfully enter temp/fix state
- if (productVariant == RTK_SURVEYOR)
- {
- digitalWrite(pin_baseStatusLED, LOW);
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
-
- displayBaseStart(0); //Show 'Base'
-
- //Restart Bluetooth with 'Base' name
- //We start BT regardless of Ntrip Server in case user wants to transmit survey-in stats over BT
- stopWiFi();
- startBluetooth();
-
- if (configureUbloxModuleBase() == true)
- {
- settings.lastState = STATE_BASE_NOT_STARTED; //Record this state for next POR
- recordSystemSettings();
+ /*
+ .-----------------------------------.
+ startBase() | STATE_BASE_NOT_STARTED |
+ .------------| Text: 'Base' |
+ | = false '-----------------------------------'
+ | |
+ | Stop WiFi, | startBase() = true
+ | Stop | Stop WiFi
+ | Bluetooth | Start Bluetooth
+ | V
+ | .-----------------------------------.
+ | | STATE_BASE_TEMP_SETTLE |
+ | | Temp Base Icon. Blinking HPA. |
+ | | "HPA: 7.15" |
+ | | "SIV: 5" |
+ | '-----------------------------------'
+ V |
+ STATE_BASE_FIXED_NOT_STARTED | horizontalAccuracy > 0.0
+ (next diagram) | && horizontalAccuracy
+ | < settings.surveyInStartingAccuracy
+ | && surveyInStart() == true
+ V
+ .-----------------------------------.
+ | STATE_BASE_TEMP_SURVEY_STARTED | svinObservationTime >
+ | Temp Base Icon blinking | maxSurveyInWait_s
+ | "Mean: 0.089" |--------------.
+ | "Time: 36" | |
+ '-----------------------------------' |
+ | |
+ | getSurveyInValid() |
+ | = true V
+ | STATE_ROVER_NOT_STARTED
+ V (Previous diagram)
+ .-----------------------------------.
+ | STATE_BASE_TEMP_TRANSMITTING |
+ | Temp Base Icon solid |
+ | "Xmitting" |
+ | "RTCM: 2145" |
+ '-----------------------------------'
+
+ */
+
+ case (STATE_BASE_NOT_STARTED): {
+ RTK_MODE(RTK_MODE_BASE_SURVEY_IN);
+ firstRoverStart = false; // If base is starting, no test menu, normal button use.
+
+ if (online.gnss == false)
+ return;
+
+ // Turn off base LED until we successfully enter temp/fix state
+ if (productVariant == RTK_SURVEYOR)
+ {
+ digitalWrite(pin_baseStatusLED, LOW);
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ ledcWrite(ledBTChannel, 0); // Turn off BT LED
+ }
- displayBaseSuccess(500); //Show 'Base Started'
+ if (productVariant == REFERENCE_STATION)
+ digitalWrite(pin_baseStatusLED, LOW);
- if (settings.fixedBase == false)
+ displayBaseStart(0); // Show 'Base'
+
+ // Allow WiFi to continue running if NTRIP Client is needed for assisted survey in
+ if (wifiIsNeeded() == false)
{
- changeState(STATE_BASE_TEMP_SETTLE);
+ NETWORK_STOP(NETWORK_TYPE_WIFI);
+ WIFI_STOP();
}
- else if (settings.fixedBase == true)
+
+ bluetoothStop();
+ bluetoothStart(); // Restart Bluetooth with 'Base' identifier
+
+ // Start monitoring the UART1 from ZED for NMEA and UBX data (enables logging)
+ if (tasksStartUART2() && configureUbloxModuleBase())
{
- changeState(STATE_BASE_FIXED_NOT_STARTED);
+ settings.updateZEDSettings = false; // On the next boot, no need to update the ZED on this profile
+ settings.lastState = STATE_BASE_NOT_STARTED; // Record this state for next POR
+ recordSystemSettings(); // Record this state for next POR
+
+ displayBaseSuccess(500); // Show 'Base Started'
+
+ if (settings.fixedBase == false)
+ changeState(STATE_BASE_TEMP_SETTLE);
+ else if (settings.fixedBase == true)
+ changeState(STATE_BASE_FIXED_NOT_STARTED);
+ }
+ else
+ {
+ displayBaseFail(1000);
}
- }
- else
- {
- displayBaseFail(1000);
- }
}
break;
- //Wait for horz acc of 5m or less before starting survey in
- case (STATE_BASE_TEMP_SETTLE):
- {
- //Blink base LED slowly while we wait for first fix
- if (millis() - lastBaseLEDupdate > 1000)
- {
- lastBaseLEDupdate = millis();
+ // Wait for horz acc of 5m or less before starting survey in
+ case (STATE_BASE_TEMP_SETTLE): {
+ // Blink base LED slowly while we wait for first fix
+ if (millis() - lastBaseLEDupdate > 1000)
+ {
+ lastBaseLEDupdate = millis();
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED));
- }
+ if ((productVariant == RTK_SURVEYOR) || (productVariant == REFERENCE_STATION))
+ digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED));
+ }
- //Check for <1m horz accuracy before starting surveyIn
- uint32_t accuracy = i2cGNSS.getHorizontalAccuracy();
+ // Check for <1m horz accuracy before starting surveyIn
+ systemPrintf("Waiting for Horz Accuracy < %0.2f meters: %0.2f, SIV: %d\r\n",
+ settings.surveyInStartingAccuracy, horizontalAccuracy, numSV);
- float f_accuracy = accuracy;
- f_accuracy = f_accuracy / 10000.0; // Convert the horizontal accuracy (mm * 10^-1) to a float
+ if (horizontalAccuracy > 0.0 && horizontalAccuracy < settings.surveyInStartingAccuracy)
+ {
+ displaySurveyStart(0); // Show 'Survey'
- Serial.printf("Waiting for Horz Accuracy < %0.2f meters: %0.2f\n\r", settings.surveyInStartingAccuracy, f_accuracy);
+ if (surveyInStart() == true) // Begin survey
+ {
+ displaySurveyStarted(500); // Show 'Survey Started'
- if (f_accuracy > 0.0 && f_accuracy < settings.surveyInStartingAccuracy)
- {
- displaySurveyStart(0); //Show 'Survey'
+ changeState(STATE_BASE_TEMP_SURVEY_STARTED);
+ }
+ }
+ }
+ break;
- if (beginSurveyIn() == true) //Begin survey
+ // Check survey status until it completes or 15 minutes elapses and we go back to rover
+ case (STATE_BASE_TEMP_SURVEY_STARTED): {
+ // Blink base LED quickly during survey in
+ if (millis() - lastBaseLEDupdate > 500)
{
- displaySurveyStarted(500); //Show 'Survey Started'
+ lastBaseLEDupdate = millis();
- changeState(STATE_BASE_TEMP_SURVEY_STARTED);
+ if ((productVariant == RTK_SURVEYOR) || (productVariant == REFERENCE_STATION))
+ digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED));
+ }
+
+ // Get the data once to avoid duplicate slow responses
+ svinObservationTime = theGNSS.getSurveyInObservationTime(50);
+ svinMeanAccuracy = theGNSS.getSurveyInMeanAccuracy(50);
+
+ if (theGNSS.getSurveyInValid(50) == true) // Survey in complete
+ {
+ systemPrintf("Observation Time: %d\r\n", svinObservationTime);
+ systemPrintln("Base survey complete! RTCM now broadcasting.");
+
+ if ((productVariant == RTK_SURVEYOR) || (productVariant == REFERENCE_STATION))
+ digitalWrite(pin_baseStatusLED, HIGH); // Indicate survey complete
+
+ // Start the NTRIP server if requested
+ RTK_MODE(RTK_MODE_BASE_FIXED);
+
+ radioStart(); // Start internal radio if enabled, otherwise disable
+
+ rtcmPacketsSent = 0; // Reset any previous number
+ changeState(STATE_BASE_TEMP_TRANSMITTING);
+ }
+ else
+ {
+ systemPrint("Time elapsed: ");
+ systemPrint(svinObservationTime);
+ systemPrint(" Accuracy: ");
+ systemPrint(svinMeanAccuracy, 3);
+ systemPrint(" SIV: ");
+ systemPrint(numSV);
+ systemPrintln();
+
+ if (svinObservationTime > maxSurveyInWait_s)
+ {
+ systemPrintf("Survey-In took more than %d minutes. Returning to rover mode.\r\n",
+ maxSurveyInWait_s / 60);
+
+ if (surveyInReset() == false)
+ {
+ systemPrintln("Survey reset failed - attempt 1/3");
+ if (surveyInReset() == false)
+ {
+ systemPrintln("Survey reset failed - attempt 2/3");
+ if (surveyInReset() == false)
+ {
+ systemPrintln("Survey reset failed - attempt 3/3");
+ }
+ }
+ }
+
+ changeState(STATE_ROVER_NOT_STARTED);
+ }
}
- }
}
break;
- //Check survey status until it completes or 15 minutes elapses and we go back to rover
- case (STATE_BASE_TEMP_SURVEY_STARTED):
- {
- //Blink base LED quickly during survey in
- if (millis() - lastBaseLEDupdate > 500)
- {
- lastBaseLEDupdate = millis();
+ // Leave base temp transmitting over external radio, or WiFi/NTRIP, or ESP NOW
+ case (STATE_BASE_TEMP_TRANSMITTING): {
+ }
+ break;
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED));
- }
+ /*
+ .-----------------------------------.
+ startBase() | STATE_BASE_FIXED_NOT_STARTED |
+ = false | Text: "Base Started" |
+ .-------------| |
+ | '-----------------------------------'
+ V |
+ STATE_ROVER_NOT_STARTED | startBase() = true
+ (Rover diagram) V
+ .-----------------------------------.
+ | STATE_BASE_FIXED_TRANSMITTING |
+ | Castle Base Icon solid |
+ | "Xmitting" |
+ | "RTCM: 0" |
+ '-----------------------------------'
+
+ */
+
+ // User has set switch to base with fixed option enabled. Let's configure and try to get there.
+ // If fixed base fails, we'll handle it here
+ case (STATE_BASE_FIXED_NOT_STARTED): {
+ RTK_MODE(RTK_MODE_BASE_FIXED);
+ bool response = startFixedBase();
+ if (response == true)
+ {
+ if ((productVariant == RTK_SURVEYOR) || (productVariant == REFERENCE_STATION))
+ digitalWrite(pin_baseStatusLED, HIGH); // Turn on base LED
+
+ radioStart(); // Start internal radio if enabled, otherwise disable
+
+ changeState(STATE_BASE_FIXED_TRANSMITTING);
+ }
+ else
+ {
+ systemPrintln("Fixed base start failed");
+ displayBaseFail(1000);
+
+ changeState(STATE_ROVER_NOT_STARTED); // Return to rover mode to avoid being in fixed base mode
+ }
+ }
+ break;
+
+ // Leave base fixed transmitting if user has enabled WiFi/NTRIP
+ case (STATE_BASE_FIXED_TRANSMITTING): {
+ }
+ break;
+
+ case (STATE_BUBBLE_LEVEL): {
+ // Do nothing - display only
+ }
+ break;
- //Get the data once to avoid duplicate slow responses
- svinObservationTime = i2cGNSS.getSurveyInObservationTime(100);
- svinMeanAccuracy = i2cGNSS.getSurveyInMeanAccuracy(100);
+ case (STATE_PROFILE): {
+ // Do nothing - display only
+ }
+ break;
- if (i2cGNSS.getSurveyInValid(100) == true) //Survey in complete
- {
- Serial.printf("obs time: %d\n\r", svinObservationTime);
- Serial.println(F("Base survey complete! RTCM now broadcasting."));
+ case (STATE_MARK_EVENT): {
+ bool logged = false;
+ bool marked = false;
+ // Gain access to the SPI controller for the microSD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_MARKEVENT);
+
+ // Record this user event to the log
+ if (online.logging == true)
+ {
+ char nmeaMessage[82]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_WAYPOINT, nmeaMessage, sizeof(nmeaMessage),
+ (char *)"CustomEvent"); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+ logged = true;
+ }
+
+ // Record this point to the marks file
+ if (settings.enableMarksFile)
+ {
+ // Get the marks file name
+ char fileName[32];
+ bool fileOpen = false;
+ char markBuffer[100];
+ bool sdCardWasOnline;
+ int year;
+ int month;
+ int day;
+
+ // Get the date
+ year = rtc.getYear();
+ month = rtc.getMonth() + 1;
+ day = rtc.getDay();
+
+ // Build the file name
+ snprintf(fileName, sizeof(fileName), "/Marks_%04d_%02d_%02d.csv", year, month, day);
+
+ // Try to gain access the SD card
+ sdCardWasOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ if (online.microSD == true)
+ {
+ // Check if the marks file already exists
+ bool marksFileExists = false;
+ if (USE_SPI_MICROSD)
+ {
+ marksFileExists = sd->exists(fileName);
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ marksFileExists = SD_MMC.exists(fileName);
+ }
+#endif // COMPILE_SD_MMC
+
+ // Open the marks file
+ FileSdFatMMC marksFile;
+
+ if (marksFileExists)
+ {
+ if (marksFile && marksFile.open(fileName, O_APPEND | O_WRITE))
+ {
+ fileOpen = true;
+ marksFile.updateFileCreateTimestamp();
+ }
+ }
+ else
+ {
+ if (marksFile && marksFile.open(fileName, O_CREAT | O_WRITE))
+ {
+ fileOpen = true;
+ marksFile.updateFileAccessTimestamp();
+
+ // Add the column headers
+ // YYYYMMDDHHMMSS, Lat: xxxx, Long: xxxx, Alt: xxxx, SIV: xx, HPA: xxxx, Batt: xxx
+ // 1 2 3 4 5 6 7 8 9
+ // 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ strcpy(markBuffer, "Date, Time, Latitude, Longitude, Altitude Meters, SIV, HPA Meters, "
+ "Battery Level, Voltage\n");
+ marksFile.write((const uint8_t *)markBuffer, strlen(markBuffer));
+ }
+ }
+
+ if (fileOpen)
+ {
+ // Create the mark text
+ // 1 2 3 4 5 6 7 8
+ // 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+ // YYYY-MM-DD, HH:MM:SS, ---Latitude---, --Longitude---, --Alt--,SIV, --HPA---,Level,Volts\n
+ if (horizontalAccuracy >= 100.)
+ snprintf(
+ markBuffer, sizeof(markBuffer),
+ "%04d-%02d-%02d, %02d:%02d:%02d, %14.9f, %14.9f, %7.1f, %2d, %8.0f, %3d%%, %4.2f\n",
+ year, month, day, rtc.getHour(true), rtc.getMinute(), rtc.getSecond(), latitude,
+ longitude, altitude, numSV, horizontalAccuracy, battLevel, battVoltage);
+ else if (horizontalAccuracy >= 10.)
+ snprintf(
+ markBuffer, sizeof(markBuffer),
+ "%04d-%02d-%02d, %02d:%02d:%02d, %14.9f, %14.9f, %7.1f, %2d, %8.1f, %3d%%, %4.2f\n",
+ year, month, day, rtc.getHour(true), rtc.getMinute(), rtc.getSecond(), latitude,
+ longitude, altitude, numSV, horizontalAccuracy, battLevel, battVoltage);
+ else if (horizontalAccuracy >= 1.)
+ snprintf(
+ markBuffer, sizeof(markBuffer),
+ "%04d-%02d-%02d, %02d:%02d:%02d, %14.9f, %14.9f, %7.1f, %2d, %8.2f, %3d%%, %4.2f\n",
+ year, month, day, rtc.getHour(true), rtc.getMinute(), rtc.getSecond(), latitude,
+ longitude, altitude, numSV, horizontalAccuracy, battLevel, battVoltage);
+ else
+ snprintf(
+ markBuffer, sizeof(markBuffer),
+ "%04d-%02d-%02d, %02d:%02d:%02d, %14.9f, %14.9f, %7.1f, %2d, %8.3f, %3d%%, %4.2f\n",
+ year, month, day, rtc.getHour(true), rtc.getMinute(), rtc.getSecond(), latitude,
+ longitude, altitude, numSV, horizontalAccuracy, battLevel, battVoltage);
+
+ // Write the mark to the file
+ marksFile.write((const uint8_t *)markBuffer, strlen(markBuffer));
+
+ // Update the file to create time & date
+ marksFile.updateFileCreateTimestamp();
+
+ // Close the mark file
+ marksFile.close();
+
+ marked = true;
+ }
+
+ // Dismount the SD card
+ if (!sdCardWasOnline)
+ endSD(true, false);
+ }
+ }
+
+ // Done with the SPI controller
+ xSemaphoreGive(sdCardSemaphore);
+
+ // Record this event to the log
+ if ((online.logging == true) && (settings.enableMarksFile))
+ {
+ if (logged && marked)
+ displayEventMarked(500); // Show 'Event Marked'
+ else if (marked)
+ displayNoLogging(500); // Show 'No Logging'
+ else if (logged)
+ displayNotMarked(500); // Show 'Not Marked'
+ else
+ displayMarkFailure(500); // Show 'Mark Failure'
+ }
+ else if (settings.enableMarksFile)
+ {
+ if (marked)
+ displayMarked(500); // Show 'Marked'
+ else
+ displayNotMarked(500); // Show 'Not Marked'
+ }
+ else if (logged)
+ displayEventMarked(500); // Show 'Event Marked'
+ else
+ displayNoLogging(500); // Show 'No Logging'
+
+ // Return to the previous state
+ changeState(lastSystemState);
+ } // End sdCardSemaphore
+ else
+ {
+ // Enable retry by not changing states
+ log_d("sdCardSemaphore failed to yield in STATE_MARK_EVENT");
+ }
+ }
+ break;
+
+ case (STATE_DISPLAY_SETUP): {
+ if (millis() - lastSetupMenuChange > 1500)
+ {
+ forceSystemStateUpdate = true; // Immediately go to this new state
+ changeState(setupState); // Change to last setup state
+ if (setupState == STATE_BUBBLE_LEVEL)
+ RTK_MODE(RTK_MODE_BUBBLE_LEVEL);
+ }
+ }
+ break;
+
+ case (STATE_WIFI_CONFIG_NOT_STARTED): {
if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, HIGH); //Indicate survey complete
+ {
+ // Start BT LED Fade to indicate start of WiFi
+ btLEDTask.detach(); // Increase BT LED blinker task rate
+ btLEDTask.attach(btLEDTaskPace33Hz, updateBTled); // Rate in seconds, callback
+
+ digitalWrite(pin_baseStatusLED, LOW);
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ }
- rtcmPacketsSent = 0; //Reset any previous number
- changeState(STATE_BASE_TEMP_TRANSMITTING);
- }
- else
- {
- Serial.print(F("Time elapsed: "));
- Serial.print(svinObservationTime);
- Serial.print(F(" Accuracy: "));
- Serial.print(svinMeanAccuracy);
- Serial.print(F(" SIV: "));
- Serial.print(i2cGNSS.getSIV());
- Serial.println();
+ if (productVariant == REFERENCE_STATION)
+ digitalWrite(pin_baseStatusLED, LOW);
- if (svinObservationTime > maxSurveyInWait_s)
+ displayWiFiConfigNotStarted(); // Display immediately during SD cluster pause
+
+ bluetoothStop();
+ espnowStop();
+
+ tasksStopUART2(); // Delete F9 serial tasks if running
+ if (!startWebServer()) // Start in AP mode and show config html page
+ changeState(STATE_ROVER_NOT_STARTED);
+ else
{
- Serial.printf("Survey-In took more than %d minutes. Returning to rover mode.\n\r", maxSurveyInWait_s / 60);
+ RTK_MODE(RTK_MODE_WIFI_CONFIG);
+ changeState(STATE_WIFI_CONFIG);
+ }
+ }
+ break;
- resetSurvey();
+ case (STATE_WIFI_CONFIG): {
+ 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.updateZEDSettings =
+ true; // When this profile is loaded next, force system to update ZED 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
+ }
+ }
- changeState(STATE_ROVER_NOT_STARTED);
+#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);
+ websocket->textAll(settingsCSV);
+ }
}
- }
+#endif // COMPILE_AP
+#endif // COMPILE_WIFI
}
break;
- //Leave base temp transmitting if user has enabled WiFi/NTRIP
- case (STATE_BASE_TEMP_TRANSMITTING):
- {
- if (settings.enableNtripServer == true)
- {
- //Turn off Bluetooth and turn on WiFi
- endBluetooth();
- startWiFi();
+ // Setup device for testing
+ case (STATE_TEST): {
+ // Debounce entry into test menu
+ if (millis() - lastTestMenuChange > 500)
+ {
+ tasksStopUART2(); // Stop absoring ZED serial via task
+ zedUartPassed = false;
+
+ // Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted
+ // even if there is no GPS fix. We use it to test serial output.
+ theGNSS.newCfgValset(); // Create a new Configuration Item VALSET message
+ theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART2, 1); // Enable message 1230 every second
+ theGNSS.sendCfgValset(); // Send the VALSET
- changeState(STATE_BASE_TEMP_WIFI_STARTED);
- }
+ RTK_MODE(RTK_MODE_TESTING);
+ changeState(STATE_TESTING);
+ }
}
break;
- //Check to see if we have connected over WiFi
- case (STATE_BASE_TEMP_WIFI_STARTED):
- {
-#ifdef COMPILE_WIFI
- byte wifiStatus = WiFi.status();
- if (wifiStatus == WL_CONNECTED)
- {
- radioState = WIFI_CONNECTED;
-
- changeState(STATE_BASE_TEMP_WIFI_CONNECTED);
- }
- else
- {
- Serial.print(F("WiFi Status: "));
- switch (wifiStatus) {
- case WL_NO_SSID_AVAIL:
- Serial.printf("SSID '%s' not detected\n\r", settings.wifiSSID);
- break;
- case WL_NO_SHIELD: Serial.println(F("WL_NO_SHIELD")); break;
- case WL_IDLE_STATUS: Serial.println(F("WL_IDLE_STATUS")); break;
- case WL_SCAN_COMPLETED: Serial.println(F("WL_SCAN_COMPLETED")); break;
- case WL_CONNECTED: Serial.println(F("WL_CONNECTED")); break;
- case WL_CONNECT_FAILED: Serial.println(F("WL_CONNECT_FAILED")); break;
- case WL_CONNECTION_LOST: Serial.println(F("WL_CONNECTION_LOST")); break;
- case WL_DISCONNECTED: Serial.println(F("WL_DISCONNECTED")); break;
- }
- delay(1000);
- }
-#endif
- }
- break;
-
- case (STATE_BASE_TEMP_WIFI_CONNECTED):
- {
- if (productVariant == RTK_SURVEYOR)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
-
- if (settings.enableNtripServer == true)
- {
- //Open connection to caster service
-#ifdef COMPILE_WIFI
- if (caster.connect(settings.casterHost, settings.casterPort) == true) //Attempt connection
+ // Display testing screen - do nothing
+ case (STATE_TESTING): {
+ // Exit via button press task
+ }
+ break;
+
+#ifdef COMPILE_L_BAND
+ case (STATE_KEYS_STARTED): {
+ if (rtcWaitTime == 0)
+ rtcWaitTime = millis();
+
+ // We want an immediate change from this state
+ forceSystemStateUpdate = true; // Immediately go to this new state
+
+ // If user has turned off PointPerfect, skip everything
+ if (settings.enablePointPerfectCorrections == false)
{
- changeState(STATE_BASE_TEMP_CASTER_STARTED);
+ changeState(settings.lastState); // Go to either rover or base
+ }
- Serial.printf("Connected to %s:%d\n\r", settings.casterHost, settings.casterPort);
+ // If there is no WiFi setup, and no keys, skip everything
+ else if (wifiNetworkCount() == 0 && strlen(settings.pointPerfectCurrentKey) == 0)
+ {
+ displayNoSSIDs(2000);
+ changeState(settings.lastState); // Go to either rover or base
+ }
- const int SERVER_BUFFER_SIZE = 512;
- char serverBuffer[SERVER_BUFFER_SIZE];
+ // If we don't have keys, begin zero touch provisioning
+ else if (strlen(settings.pointPerfectCurrentKey) == 0 || strlen(settings.pointPerfectNextKey) == 0)
+ {
+ log_d("L_Band Keys starting WiFi");
+
+ // Temporarily limit WiFi connection attempts
+ wifiOriginalMaxConnectionAttempts = wifiMaxConnectionAttempts;
+ wifiMaxConnectionAttempts = 0; // Override setting during key retrieval. Give up after single failure.
- snprintf(serverBuffer, SERVER_BUFFER_SIZE, "SOURCE %s /%s\r\nSource-Agent: NTRIP %s/%s\r\n\r\n",
- settings.mountPointPW, settings.mountPoint, ntrip_server_name, "App Version 1.0");
+ wifiStart();
+ changeState(STATE_KEYS_PROVISION_WIFI_STARTED);
+ }
- //Serial.printf("Sending credentials:\n%s\n\r", serverBuffer);
- caster.write(serverBuffer, strlen(serverBuffer));
+ // Determine if we have valid date/time RTC from last boot
+ else if (online.rtc == false)
+ {
+ if (millis() - rtcWaitTime > 2000)
+ {
+ // If RTC is not available, we will assume we need keys
+ changeState(STATE_KEYS_NEEDED);
+ }
+ }
- casterResponseWaitStartTime = millis();
+ else
+ {
+ // Determine days until next key expires
+ int daysRemaining =
+ daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1);
+ log_d("Days until keys expire: %d", daysRemaining);
+
+ if (checkCertificates() && (daysRemaining > 28 && daysRemaining <= 56))
+ changeState(STATE_KEYS_DAYS_REMAINING);
+ else
+ changeState(STATE_KEYS_NEEDED);
}
-#endif
- }
}
break;
- //Wait for response for caster service and make sure it's valid
- case (STATE_BASE_TEMP_CASTER_STARTED):
- {
-#ifdef COMPILE_WIFI
- //Check if caster service responded
- if (caster.available() == 0)
- {
- if (millis() - casterResponseWaitStartTime > 5000)
+ case (STATE_KEYS_NEEDED): {
+ forceSystemStateUpdate = true; // immediately go to this new state
+
+ if (online.rtc == false)
{
- Serial.println(F("Caster failed to respond. Do you have your caster address and port correct?"));
- caster.stop();
+ log_d("Keys Needed. RTC offline. Starting WiFi");
+
+ // Temporarily limit WiFi connection attempts
+ wifiOriginalMaxConnectionAttempts = wifiMaxConnectionAttempts;
+ wifiMaxConnectionAttempts = 0; // Override setting during key retrieval. Give up after single failure.
- changeState(STATE_BASE_TEMP_WIFI_CONNECTED); //Return to previous state
+ wifiStart();
+ changeState(STATE_KEYS_WIFI_STARTED); // If we can't check the RTC, continue
}
- }
- else
- {
- //Check reply
- bool connectionSuccess = false;
- char response[512];
- int responseSpot = 0;
- while (caster.available())
+
+ // When did we last try to get keys? Attempt every 24 hours
+ else if (rtc.getEpoch() - settings.lastKeyAttempt > (60 * 60 * 24))
{
- response[responseSpot++] = caster.read();
- if (strstr(response, "200") != NULL) //Look for 'ICY 200 OK'
- connectionSuccess = true;
- if (responseSpot == 512 - 1) break;
+ settings.lastKeyAttempt = rtc.getEpoch(); // Mark it
+ recordSystemSettings(); // Record these settings to unit
+
+ log_d("Keys Needed. Starting WiFi");
+
+ // Temporarily limit WiFi connection attempts
+ wifiOriginalMaxConnectionAttempts = wifiMaxConnectionAttempts;
+ wifiMaxConnectionAttempts = 0; // Override setting during key retrieval. Give up after single failure.
+
+ wifiStart(); // Starts WiFi state machine
+ changeState(STATE_KEYS_WIFI_STARTED);
}
- response[responseSpot] = '\0';
- //Serial.printf("Caster responded with: %s\n\r", response);
- if (connectionSuccess == false)
+ // Added to display WiFi error if user selects GetKeys from the display
+ // Normally, this would be caught during STATE_KEYS_STARTED
+ else if (wifiNetworkCount() == 0)
{
- Serial.printf("Caster responded with bad news: %s. Are you sure your caster credentials are correct?\n\r", response);
- changeState(STATE_BASE_TEMP_WIFI_CONNECTED); //Return to previous state
+ displayNoSSIDs(1000);
+ changeState(
+ STATE_KEYS_DAYS_REMAINING); // We have valid keys, we've already tried today. No need to try again.
}
- else
+
+ // Added to allow user to select GetKeys from the display
+ // This forces a key update
+ else if (lBandForceGetKeys == true)
{
- //We're connected!
- //Serial.println(F("Connected to caster"));
+ lBandForceGetKeys = false;
- //Reset flags
- lastServerReport_ms = millis();
- lastServerSent_ms = millis();
- casterBytesSent = 0;
+ log_d("Force key update. Starting WiFi");
- rtcmPacketsSent = 0; //Reset any previous number
+ // Temporarily limit WiFi connection attempts
+ wifiOriginalMaxConnectionAttempts = wifiMaxConnectionAttempts;
+ wifiMaxConnectionAttempts = 0; // Override setting during key retrieval. Give up after single failure.
- changeState(STATE_BASE_TEMP_CASTER_CONNECTED);
+ wifiStart(); // Starts WiFi state machine
+
+ changeState(STATE_KEYS_WIFI_STARTED);
+ }
+
+ else
+ {
+ log_d("Already tried to obtain keys for today");
+ changeState(
+ STATE_KEYS_DAYS_REMAINING); // We have valid keys, we've already tried today. No need to try again.
}
- }
-#endif
}
break;
- //Monitor connected state
- case (STATE_BASE_TEMP_CASTER_CONNECTED):
- {
- cyclePositionLEDs();
+ case (STATE_KEYS_WIFI_STARTED): {
+ if (wifiIsConnected())
+ changeState(STATE_KEYS_WIFI_CONNECTED);
+ else
+ {
+ wifiShutdown(); // Turn off WiFi
-#ifdef COMPILE_WIFI
- if (caster.connected() == false)
- {
- Serial.println(F("Caster no longer connected. Reconnecting..."));
- changeState(STATE_BASE_TEMP_WIFI_CONNECTED); //Return to 2 earlier states to try to reconnect
- }
-#endif
+ wifiMaxConnectionAttempts =
+ wifiOriginalMaxConnectionAttempts; // Override setting to 2 attemps during keys
+ changeState(STATE_KEYS_WIFI_TIMEOUT);
+ }
}
break;
- //User has set switch to base with fixed option enabled. Let's configure and try to get there.
- //If fixed base fails, we'll handle it here
- case (STATE_BASE_FIXED_NOT_STARTED):
- {
- bool response = startFixedBase();
- if (response == true)
- {
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, HIGH); //Turn on base LED
+ case (STATE_KEYS_WIFI_CONNECTED): {
- changeState(STATE_BASE_FIXED_TRANSMITTING);
- }
- else
- {
- Serial.println(F("Fixed base start failed"));
- displayBaseFail(1000);
+ // Check that the certs are valid
+ if (checkCertificates() == true)
+ {
+ // Update the keys
+ if (pointperfectUpdateKeys() == true) // Connect to ThingStream MQTT and get PointPerfect key UBX packet
+ displayKeysUpdated();
+ }
+ else
+ {
+ // Erase keys
+ erasePointperfectCredentials();
- changeState(STATE_ROVER_NOT_STARTED); //Return to rover mode to avoid being in fixed base mode
- }
+ // Provision device
+ if (pointperfectProvisionDevice() == true) // Connect to ThingStream API and get keys
+ displayKeysUpdated();
+ }
+
+ wifiShutdown(); // Turn off WiFi
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(STATE_KEYS_DAYS_REMAINING);
}
break;
- //Leave base fixed transmitting if user has enabled WiFi/NTRIP
- case (STATE_BASE_FIXED_TRANSMITTING):
- {
- if (settings.enableNtripServer == true)
- {
- //Turn off Bluetooth and turn on WiFi
- endBluetooth();
- startWiFi();
+ case (STATE_KEYS_DAYS_REMAINING): {
+ if (online.rtc == true)
+ {
+ if (settings.pointPerfectNextKeyStart > 0)
+ {
+ int daysRemaining =
+ daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1);
+ systemPrintf("Days until PointPerfect keys expire: %d\r\n", daysRemaining);
+ if (daysRemaining >= 0)
+ {
+ paintKeyDaysRemaining(daysRemaining, 2000);
+ }
+ else
+ {
+ paintKeysExpired();
+ }
+ }
+ }
+ paintLBandConfigure();
+
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(STATE_KEYS_LBAND_CONFIGURE);
+ }
+ break;
+
+ case (STATE_KEYS_LBAND_CONFIGURE): {
+ // Be sure we ignore any external RTCM sources
+ theGNSS.setUART2Input(COM_TYPE_UBX); // Set the UART2 to input UBX (no RTCM)
- rtcmPacketsSent = 0; //Reset any previous number
+ pointperfectApplyKeys(); // Send current keys, if available, to ZED-F9P
- changeState(STATE_BASE_FIXED_WIFI_STARTED);
- }
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(settings.lastState); // Go to either rover or base
}
break;
- //Check to see if we have connected over WiFi
- case (STATE_BASE_FIXED_WIFI_STARTED):
- {
-#ifdef COMPILE_WIFI
- byte wifiStatus = WiFi.status();
- if (wifiStatus == WL_CONNECTED)
- {
- radioState = WIFI_CONNECTED;
-
- changeState(STATE_BASE_FIXED_WIFI_CONNECTED);
- }
- else
- {
- Serial.print(F("WiFi Status: "));
- switch (wifiStatus) {
- case WL_NO_SSID_AVAIL:
- Serial.printf("SSID '%s' not detected\n\r", settings.wifiSSID);
- break;
- case WL_NO_SHIELD: Serial.println(F("WL_NO_SHIELD")); break;
- case WL_IDLE_STATUS: Serial.println(F("WL_IDLE_STATUS")); break;
- case WL_SCAN_COMPLETED: Serial.println(F("WL_SCAN_COMPLETED")); break;
- case WL_CONNECTED: Serial.println(F("WL_CONNECTED")); break;
- case WL_CONNECT_FAILED: Serial.println(F("WL_CONNECT_FAILED")); break;
- case WL_CONNECTION_LOST: Serial.println(F("WL_CONNECTION_LOST")); break;
- case WL_DISCONNECTED: Serial.println(F("WL_DISCONNECTED")); break;
- }
- delay(1000);
- }
-#endif
- }
- break;
-
- case (STATE_BASE_FIXED_WIFI_CONNECTED):
- {
- if (productVariant == RTK_SURVEYOR)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
- if (settings.enableNtripServer == true)
- {
-#ifdef COMPILE_WIFI
- //Open connection to caster service
- if (caster.connect(settings.casterHost, settings.casterPort) == true) //Attempt connection
+ case (STATE_KEYS_WIFI_TIMEOUT): {
+ paintKeyWiFiFail(2000);
+
+ forceSystemStateUpdate = true; // Imediately go to this new state
+
+ if (online.rtc == true)
+ {
+ int daysRemaining =
+ daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1);
+
+ if (daysRemaining >= 0)
+ {
+ changeState(STATE_KEYS_DAYS_REMAINING);
+ }
+ else
+ {
+ paintKeysExpired();
+ changeState(STATE_KEYS_LBAND_ENCRYPTED);
+ }
+ }
+ else
{
- changeState(STATE_BASE_FIXED_CASTER_STARTED);
+ // No WiFi. No RTC. We don't know if the keys we have are expired. Attempt to use them.
+ changeState(STATE_KEYS_LBAND_CONFIGURE);
+ }
+ }
+ break;
- Serial.printf("Connected to %s:%d\n\r", settings.casterHost, settings.casterPort);
+ case (STATE_KEYS_LBAND_ENCRYPTED): {
+ // Since L-Band is not available, be sure RTCM can be provided over UART2
+ theGNSS.setUART2Input(COM_TYPE_RTCM3); // Set the UART2 to input RTCM
- const int SERVER_BUFFER_SIZE = 512;
- char serverBuffer[SERVER_BUFFER_SIZE];
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(settings.lastState); // Go to either rover or base
+ }
+ break;
- snprintf(serverBuffer, SERVER_BUFFER_SIZE, "SOURCE %s /%s\r\nSource-Agent: NTRIP %s/%s\r\n\r\n",
- settings.mountPointPW, settings.mountPoint, ntrip_server_name, "App Version 1.0");
+ case (STATE_KEYS_PROVISION_WIFI_STARTED): {
+ if (wifiIsConnected())
+ changeState(STATE_KEYS_PROVISION_WIFI_CONNECTED);
+ else
+ {
+ wifiShutdown(); // Turn off WiFi
+ changeState(STATE_KEYS_WIFI_TIMEOUT);
+ }
+ }
+ break;
- //Serial.printf("Sending credentials:\n%s\n\r", serverBuffer);
- caster.write(serverBuffer, strlen(serverBuffer));
+ case (STATE_KEYS_PROVISION_WIFI_CONNECTED): {
+ forceSystemStateUpdate = true; // Imediately go to this new state
- casterResponseWaitStartTime = millis();
+ if (pointperfectProvisionDevice() == true)
+ {
+ displayKeysUpdated();
+ changeState(STATE_KEYS_DAYS_REMAINING);
}
-#endif
- }
+ else
+ {
+ paintKeyProvisionFail(10000); // Device not whitelisted. Show device ID.
+ changeState(STATE_KEYS_LBAND_ENCRYPTED);
+ }
+ wifiShutdown(); // Turn off WiFi
}
break;
+#endif // COMPILE_L_BAND
- //Wait for response for caster service and make sure it's valid
- case (STATE_BASE_FIXED_CASTER_STARTED):
- {
-#ifdef COMPILE_WIFI
- //Check if caster service responded
- if (caster.available() < 10)
- {
- if (millis() - casterResponseWaitStartTime > 5000)
+ case (STATE_ESPNOW_PAIRING_NOT_STARTED): {
+#ifdef COMPILE_ESPNOW
+ paintEspNowPairing();
+
+ // Start ESP-Now if needed, put ESP-Now into broadcast state
+ espnowBeginPairing();
+
+ changeState(STATE_ESPNOW_PAIRING);
+#else // COMPILE_ESPNOW
+ changeState(STATE_ROVER_NOT_STARTED);
+#endif // COMPILE_ESPNOW
+ }
+ break;
+
+ case (STATE_ESPNOW_PAIRING): {
+ if (espnowIsPaired() == true)
{
- Serial.println(F("Caster failed to respond. Do you have your caster address and port correct?"));
- caster.stop();
- delay(10); //Yield to RTOS
+ paintEspNowPaired();
- changeState(STATE_BASE_FIXED_WIFI_CONNECTED); //Return to previous state
+ // Return to the previous state
+ changeState(lastSystemState);
}
- }
- else
- {
- //Check reply
- bool connectionSuccess = false;
- char response[512];
- int responseSpot = 0;
- while (caster.available())
+ else
{
- response[responseSpot++] = caster.read();
- if (strstr(response, "200") != NULL) //Look for 'ICY 200 OK'
- connectionSuccess = true;
- if (responseSpot == 512 - 1) break;
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ espnowSendPairMessage(broadcastMac); // Send unit's MAC address over broadcast, no ack, no encryption
}
- response[responseSpot] = '\0';
- //Serial.printf("Caster responded with: %s\n\r", response);
+ }
+ break;
+
+#ifdef COMPILE_ETHERNET
+ case (STATE_NTPSERVER_NOT_STARTED): {
+ RTK_MODE(RTK_MODE_NTP);
+ firstRoverStart = false; // If NTP is starting, no test menu, normal button use.
- if (connectionSuccess == false)
+ if (online.gnss == false)
+ return;
+
+ displayNtpStart(500); // Show 'NTP'
+
+ // Start monitoring the UART1 from ZED for NMEA and UBX data (enables logging)
+ if (tasksStartUART2() && configureUbloxModuleNTP())
{
- Serial.printf("Caster responded with bad news: %s. Are you sure your caster credentials are correct?", response);
- changeState(STATE_BASE_FIXED_WIFI_CONNECTED); //Return to previous state
+ settings.updateZEDSettings = false; // On the next boot, no need to update the ZED on this profile
+ settings.lastState = STATE_NTPSERVER_NOT_STARTED; // Record this state for next POR
+ recordSystemSettings();
+
+ if (online.NTPServer)
+ {
+ if (settings.debugNtp)
+ systemPrintln("NTP Server started");
+ displayNtpStarted(500); // Show 'NTP Started'
+ changeState(STATE_NTPSERVER_NO_SYNC);
+ }
+ else
+ {
+ if (settings.debugNtp)
+ systemPrintln("NTP Server waiting for Ethernet");
+ displayNtpNotReady(1000); // Show 'Ethernet Not Ready'
+ changeState(STATE_NTPSERVER_NO_SYNC);
+ }
}
else
{
- //We're connected!
- //Serial.println(F("Connected to caster"));
+ if (settings.debugNtp)
+ systemPrintln("NTP Server ZED configuration failed");
+ displayNTPFail(1000); // Show 'NTP Failed'
+ // Do we stay in STATE_NTPSERVER_NOT_STARTED? Or should we reset?
+ }
+ }
+ break;
+
+ case (STATE_NTPSERVER_NO_SYNC): {
+ if (rtcSyncd)
+ {
+ if (settings.debugNtp)
+ systemPrintln("NTP Server RTC synchronized");
+ changeState(STATE_NTPSERVER_SYNC);
+ }
+ }
+ break;
+
+ case (STATE_NTPSERVER_SYNC): {
+ // Do nothing - display only
+ }
+ break;
+
+ case (STATE_CONFIG_VIA_ETH_NOT_STARTED): {
+ displayConfigViaEthNotStarted(1500);
+
+ settings.updateZEDSettings = false; // On the next boot, no need to update the ZED 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
- //Reset flags
- lastServerReport_ms = millis();
- lastServerSent_ms = millis();
- casterBytesSent = 0;
+ ESP.restart(); // Restart to go into the dedicated configure-via-ethernet mode
+ }
+ break;
- rtcmPacketsSent = 0; //Reset any previous number
+ 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 we continue, startEthernerWebServerESP32W5500 will fail as it won't have exclusive access to SPI and
+ // ints).
+ if (!configureViaEthernet)
+ {
+ displayConfigViaEthNotStarted(1500);
+ settings.lastState = STATE_CONFIG_VIA_ETH_STARTED; // Re-record this state for POR
+ recordSystemSettings();
- changeState(STATE_BASE_FIXED_CASTER_CONNECTED);
+ 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
}
- }
-#endif
+
+ displayConfigViaEthStarted(1500);
+
+ bluetoothStop(); // Should be redundant - but just in case
+ espnowStop(); // Should be redundant - but just in case
+ tasksStopUART2(); // Delete F9 serial tasks if running
+
+ ethernetWebServerStartESP32W5500(); // Start Ethernet in dedicated configure-via-ethernet mode
+
+ if (!startWebServer(false, settings.httpPort)) // Start the async web server
+ changeState(STATE_ROVER_NOT_STARTED);
+ else
+ changeState(STATE_CONFIG_VIA_ETH);
}
break;
- //Monitor connected state
- case (STATE_BASE_FIXED_CASTER_CONNECTED):
- {
- cyclePositionLEDs();
+ 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.updateZEDSettings =
+ true; // When this profile is loaded next, force system to update ZED 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
- if (caster.connected() == false)
- {
- changeState(STATE_BASE_FIXED_WIFI_CONNECTED);
- }
-#endif
+#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);
+ websocket->textAll(settingsCSV);
+ }
+ }
+#endif // COMPILE_AP
+#endif // COMPILE_WIFI
+ }
+ break;
+
+ case (STATE_CONFIG_VIA_ETH_RESTART_BASE): {
+ displayConfigViaEthNotStarted(1000);
+
+ ethernetWebServerStopESP32W5500();
+
+ settings.updateZEDSettings = false; // On the next boot, no need to update the ZED 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): {
+ forceDisplayUpdate = true;
+ powerDown(true);
+ }
+ break;
+
+ default: {
+ systemPrintf("Unknown state: %d\r\n", systemState);
+ }
+ break;
+ }
}
- }
}
-//Change states and print the new state
-void changeState(SystemState newState)
+// System state changes may only occur within main state machine
+// To allow state changes from external sources (ie, Button Tasks) requests can be made
+// Requests are handled at the start of updateSystemState()
+void requestChangeState(SystemState requestedState)
{
- systemState = newState;
+ newSystemStateRequested = true;
+ requestedSystemState = requestedState;
+ log_d("Requested System State: %d", requestedSystemState);
+}
- //Debug print
- switch (systemState)
- {
+// Print the current state
+const char *getState(SystemState state, char *buffer)
+{
+ switch (state)
+ {
case (STATE_ROVER_NOT_STARTED):
- Serial.println(F("State: Rover - Not Started"));
- break;
+ return "STATE_ROVER_NOT_STARTED";
case (STATE_ROVER_NO_FIX):
- Serial.println(F("State: Rover - No Fix"));
- break;
+ return "STATE_ROVER_NO_FIX";
case (STATE_ROVER_FIX):
- Serial.println(F("State: Rover - Fix"));
- break;
+ return "STATE_ROVER_FIX";
case (STATE_ROVER_RTK_FLOAT):
- Serial.println(F("State: Rover - RTK Float"));
- break;
+ return "STATE_ROVER_RTK_FLOAT";
case (STATE_ROVER_RTK_FIX):
- Serial.println(F("State: Rover - RTK Fix"));
- break;
+ return "STATE_ROVER_RTK_FIX";
case (STATE_BASE_NOT_STARTED):
- Serial.println(F("State: Base - Not Started"));
- break;
+ return "STATE_BASE_NOT_STARTED";
case (STATE_BASE_TEMP_SETTLE):
- Serial.println(F("State: Base-Temp - Settle"));
- break;
+ return "STATE_BASE_TEMP_SETTLE";
case (STATE_BASE_TEMP_SURVEY_STARTED):
- Serial.println(F("State: Base-Temp - Survey Started"));
- break;
+ return "STATE_BASE_TEMP_SURVEY_STARTED";
case (STATE_BASE_TEMP_TRANSMITTING):
- Serial.println(F("State: Base-Temp - Transmitting"));
- break;
- case (STATE_BASE_TEMP_WIFI_STARTED):
- Serial.println(F("State: Base-Temp - WiFi Started"));
- break;
- case (STATE_BASE_TEMP_WIFI_CONNECTED):
- Serial.println(F("State: Base-Temp - WiFi Connected"));
- break;
- case (STATE_BASE_TEMP_CASTER_STARTED):
- Serial.println(F("State: Base-Temp - Caster Started"));
- break;
- case (STATE_BASE_TEMP_CASTER_CONNECTED):
- Serial.println(F("State: Base-Temp - Caster Connected"));
- break;
+ return "STATE_BASE_TEMP_TRANSMITTING";
case (STATE_BASE_FIXED_NOT_STARTED):
- Serial.println(F("State: Base-Fixed - Not Started"));
- break;
+ return "STATE_BASE_FIXED_NOT_STARTED";
case (STATE_BASE_FIXED_TRANSMITTING):
- Serial.println(F("State: Base-Fixed - Transmitting"));
- break;
- case (STATE_BASE_FIXED_WIFI_STARTED):
- Serial.println(F("State: Base-Fixed - WiFi Started"));
- break;
- case (STATE_BASE_FIXED_WIFI_CONNECTED):
- Serial.println(F("State: Base-Fixed - WiFi Connected"));
- break;
- case (STATE_BASE_FIXED_CASTER_STARTED):
- Serial.println(F("State: Base-Fixed - Caster Started"));
- break;
- case (STATE_BASE_FIXED_CASTER_CONNECTED):
- Serial.println(F("State: Base-Fixed - Caster Connected"));
- break;
- default:
- Serial.printf("State Unknown: %d\n\r", systemState);
- break;
- }
+ return "STATE_BASE_FIXED_TRANSMITTING";
+ case (STATE_BUBBLE_LEVEL):
+ return "STATE_BUBBLE_LEVEL";
+ case (STATE_MARK_EVENT):
+ return "STATE_MARK_EVENT";
+ 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_TEST):
+ return "STATE_TEST";
+ case (STATE_TESTING):
+ return "STATE_TESTING";
+ case (STATE_PROFILE):
+ return "STATE_PROFILE";
+#ifdef COMPILE_L_BAND
+ case (STATE_KEYS_STARTED):
+ return "STATE_KEYS_STARTED";
+ case (STATE_KEYS_NEEDED):
+ return "STATE_KEYS_NEEDED";
+ case (STATE_KEYS_WIFI_STARTED):
+ return "STATE_KEYS_WIFI_STARTED";
+ case (STATE_KEYS_WIFI_CONNECTED):
+ return "STATE_KEYS_WIFI_CONNECTED";
+ case (STATE_KEYS_WIFI_TIMEOUT):
+ return "STATE_KEYS_WIFI_TIMEOUT";
+ case (STATE_KEYS_EXPIRED):
+ return "STATE_KEYS_EXPIRED";
+ case (STATE_KEYS_DAYS_REMAINING):
+ return "STATE_KEYS_DAYS_REMAINING";
+ case (STATE_KEYS_LBAND_CONFIGURE):
+ return "STATE_KEYS_LBAND_CONFIGURE";
+ case (STATE_KEYS_LBAND_ENCRYPTED):
+ return "STATE_KEYS_LBAND_ENCRYPTED";
+ case (STATE_KEYS_PROVISION_WIFI_STARTED):
+ return "STATE_KEYS_PROVISION_WIFI_STARTED";
+ case (STATE_KEYS_PROVISION_WIFI_CONNECTED):
+ return "STATE_KEYS_PROVISION_WIFI_CONNECTED";
+#endif // COMPILE_L_BAND
+
+ case (STATE_ESPNOW_PAIRING_NOT_STARTED):
+ return "STATE_ESPNOW_PAIRING_NOT_STARTED";
+ case (STATE_ESPNOW_PAIRING):
+ return "STATE_ESPNOW_PAIRING";
+
+ case (STATE_NTPSERVER_NOT_STARTED):
+ return "STATE_NTPSERVER_NOT_STARTED";
+ case (STATE_NTPSERVER_NO_SYNC):
+ return "STATE_NTPSERVER_NO_SYNC";
+ 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):
+ return "STATE_NOT_SET";
+ }
+
+ // Handle the unknown case
+ sprintf(buffer, "Unknown: %d", state);
+ return buffer;
+}
+
+// Change states and print the new state
+void changeState(SystemState newState)
+{
+ char string1[30];
+ char string2[30];
+ const char *arrow;
+ const char *asterisk;
+ const char *initialState;
+ const char *endingState;
+
+ // Log the heap size at the state change
+ reportHeapNow(false);
+
+ // Debug print of new state, add leading asterisk for repeated states
+ if ((!settings.enablePrintDuplicateStates) && (newState == systemState))
+ return;
+
+ if (settings.enablePrintStates)
+ {
+ arrow = "";
+ asterisk = "";
+ initialState = "";
+ if (newState == systemState)
+ asterisk = "*";
+ else
+ {
+ initialState = getState(systemState, string1);
+ arrow = " --> ";
+ }
+ }
+
+ // Set the new state
+ systemState = newState;
+ if (settings.enablePrintStates)
+ {
+ endingState = getState(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());
+ }
+ }
}
diff --git a/Firmware/RTK_Surveyor/System.ino b/Firmware/RTK_Surveyor/System.ino
index 2d253fb0b..dd0031a40 100644
--- a/Firmware/RTK_Surveyor/System.ino
+++ b/Firmware/RTK_Surveyor/System.ino
@@ -1,793 +1,1208 @@
-//Get MAC, start radio
-//Tack device's MAC address to end of friendly broadcast name
-//This allows multiple units to be on at same time
-bool startBluetooth()
+// Setup the u-blox module for any setup (base or rover)
+// 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.
+bool configureUbloxModule()
{
-#ifdef COMPILE_BT
+ if (online.gnss == false)
+ return (false);
- //Get unit MAC address
- esp_read_mac(unitMACAddress, ESP_MAC_WIFI_STA);
- unitMACAddress[5] += 2; //Convert MAC address to Bluetooth MAC (add 2): https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system.html#mac-address
+ bool response = true;
- char stateName[10];
- if (buttonPreviousState == BUTTON_ROVER)
- strcpy(stateName, "Rover");
- else
- strcpy(stateName, "Base");
+ // Turn on/off debug messages
+ if (settings.enableI2Cdebug)
+ {
+#if defined(REF_STN_GNSS_DEBUG)
+ if (ENABLE_DEVELOPER && productVariant == REFERENCE_STATION)
+ theGNSS.enableDebugging(serialGNSS); // Output all debug messages over serialGNSS
+ else
+#endif // REF_STN_GNSS_DEBUG
+ theGNSS.enableDebugging(Serial, true); // Enable only the critical debug messages over Serial
+ }
+ else
+ theGNSS.disableDebugging();
- sprintf(deviceName, "%s %s-%02X%02X", platformPrefix, stateName, unitMACAddress[4], unitMACAddress[5]); //Base mode
+ // Wait for initial report from module
+ int maxWait = 2000;
+ startTime = millis();
+ while (pvtUpdated == false)
+ {
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, eventTriggerReceived
+ delay(10);
+ if ((millis() - startTime) > maxWait)
+ {
+ log_d("PVT Update failed");
+ break;
+ }
+ }
- if (SerialBT.begin(deviceName, false, settings.sppRxQueueSize, settings.sppTxQueueSize) == false) //localName, isMaster, rxBufferSize, txBufferSize
- {
- Serial.println(F("An error occurred initializing Bluetooth"));
- radioState = RADIO_OFF;
+ // The first thing we do is go to 1Hz to lighten any I2C traffic from a previous configuration
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_MEAS, 1000);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_NAV, 1);
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_bluetoothStatusLED, LOW);
- return (false);
- }
-
- //Set PIN to 1234 so we can connect to older BT devices, but not require a PIN for modern device pairing
- //See issue: https://github.com/sparkfun/SparkFun_RTK_Surveyor/issues/5
- //https://github.com/espressif/esp-idf/issues/1541
- //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
- esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
-
- esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE; //Requires pin 1234 on old BT dongle, No prompt on new BT dongle
- //esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_OUT; //Works but prompts for either pin (old) or 'Does this 6 pin appear on the device?' (new)
-
- esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
-
- esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
- esp_bt_pin_code_t pin_code;
- pin_code[0] = '1';
- pin_code[1] = '2';
- pin_code[2] = '3';
- pin_code[3] = '4';
- esp_bt_gap_set_pin(pin_type, 4, pin_code);
- //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
- SerialBT.register_callback(btCallback); //Controls BT Status LED on Surveyor
- SerialBT.setTimeout(250);
-
- Serial.print(F("Bluetooth broadcasting as: "));
- Serial.println(deviceName);
-
- radioState = BT_ON_NOCONNECTION;
-
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_bluetoothStatusLED, HIGH);
-
- //Start the tasks for handling incoming and outgoing BT bytes to/from ZED-F9P
- if (F9PSerialReadTaskHandle == NULL)
- xTaskCreate(
- F9PSerialReadTask,
- "F9Read", //Just for humans
- readTaskStackSize, //Stack Size
- NULL, //Task input parameter
- 1, //Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
- &F9PSerialReadTaskHandle); //Task handle
-
- if (F9PSerialWriteTaskHandle == NULL)
- xTaskCreate(
- F9PSerialWriteTask,
- "F9Write", //Just for humans
- writeTaskStackSize, //Stack Size
- NULL, //Task input parameter
- 0, //Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
- &F9PSerialWriteTaskHandle); //Task handle
-
- //Start task for controlling Bluetooth pair LED
- if (productVariant == RTK_SURVEYOR)
- btLEDTask.attach(btLEDTaskPace, updateBTled); //Rate in seconds, callback
-#endif
-
- return (true);
+ if (commandSupported(UBLOX_CFG_TMODE_MODE) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode
+
+ // UART1 will primarily be used to pass NMEA and UBX from ZED to ESP32 (eventually to cell phone)
+ // but the phone can also provide RTCM data and a user may want to configure the ZED over Bluetooth.
+ // So let's be sure to enable UBX+NMEA+RTCM on the input
+ if (USE_I2C_GNSS)
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0);
+
+ response &= theGNSS.addCfgValset(
+ UBLOX_CFG_UART1_BAUDRATE, settings.dataPortBaud); // Defaults to 230400 to maximize message output support
+ response &= theGNSS.addCfgValset(
+ UBLOX_CFG_UART2_BAUDRATE,
+ settings.radioPortBaud); // Defaults to 57600 to match SiK telemetry radio firmware default
+
+ // Disable SPI port - This is just to remove some overhead by ZED
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIOUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIOUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_SPIOUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIOUTPROT_RTCM3X, 0);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_SPIINPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_SPARTN, 0);
+ }
+ else // SPI GNSS
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIOUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIOUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_SPIOUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIOUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_SPIINPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SPIINPROT_SPARTN, 0);
+
+ // Disable I2C and UART1 ports - This is just to remove some overhead by ZED
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2COUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2COUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_I2COUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2COUTPROT_RTCM3X, 0);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_I2CINPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_SPARTN, 0);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 0);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0);
+ }
+
+ // Set the UART2 to only do RTCM (in case this device goes into base mode)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_UBX, settings.enableUART2UBXIn);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0);
+
+ // We don't want NMEA over I2C, but we will want to deliver RTCM, and UBX+RTCM is not an option
+ if (USE_I2C_GNSS)
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2COUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2COUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_I2COUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2COUTPROT_RTCM3X, 1);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_RTCM3X, 1);
+
+ if (commandSupported(UBLOX_CFG_I2CINPROT_SPARTN) == true)
+ {
+ // We push NEO-D9S correction data over the I2C interface via the PMP message. This uses the UBX protocol.
+ // SPARTN is not needed on I2C
+ response &= theGNSS.addCfgValset(UBLOX_CFG_I2CINPROT_SPARTN, 0);
+ }
+ }
+
+ if (settings.enableZedUsb == true)
+ {
+ // The USB port on the ZED may be used for RTCM to/from the computer (as an NTRIP caster or client)
+ // So let's be sure all protocols are on for the USB port
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_USBOUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_USBINPROT_SPARTN) == true)
+ {
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/713
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_SPARTN, 1);
+ }
+ }
+ else
+ {
+ //Disable all protocols over USB
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_USBOUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_RTCM3X, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_USBINPROT_SPARTN) == true)
+ {
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/713
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_SPARTN, 0);
+ }
+ }
+
+ if (commandSupported(UBLOX_CFG_NAVSPG_INFIL_MINCNO) == true)
+ {
+ if (zedModuleType == PLATFORM_F9R)
+ response &= theGNSS.addCfgValset(
+ UBLOX_CFG_NAVSPG_INFIL_MINCNO,
+ settings.minCNO_F9R); // Set minimum satellite signal level for navigation - default 20
+ else
+ response &= theGNSS.addCfgValset(
+ UBLOX_CFG_NAVSPG_INFIL_MINCNO,
+ settings.minCNO_F9P); // Set minimum satellite signal level for navigation - default 6
+ }
+
+ if (commandSupported(UBLOX_CFG_NAV2_OUT_ENABLED) == true)
+ {
+ // Count NAV2 messages and enable NAV2 as needed.
+ if (getNAV2MessageCount() > 0)
+ {
+ response &= theGNSS.addCfgValset(
+ UBLOX_CFG_NAV2_OUT_ENABLED,
+ 1); // Enable NAV2 messages. This has the side effect of causing RTCM to generate twice as fast.
+ }
+ else
+ response &= theGNSS.addCfgValset(UBLOX_CFG_NAV2_OUT_ENABLED, 0); // Disable NAV2 messages
+ }
+
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Module failed config block 0");
+ response = true; // Reset
+
+ // Enable the constellations the user has set
+ response &= setConstellations(true); // 19 messages. Send newCfg or sendCfg with value set
+ if (response == false)
+ systemPrintln("Module failed config block 1");
+ 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");
+ response = true; // Reset
+
+ // Disable NMEA messages on all but UART1
+ response &= theGNSS.newCfgValset();
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_I2C, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_I2C, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C, 0);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART2, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART2, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART2, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART2, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART2, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART2, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART2, 0);
+
+ if (USE_I2C_GNSS) // Don't disable NMEA on SPI if the GNSS is SPI!
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_SPI, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_SPI, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_SPI, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_SPI, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_SPI, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_SPI, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_SPI, 0);
+ }
+
+ if (USE_SPI_GNSS) // If the GNSS is SPI, _do_ disable NMEA on UART1
+ {
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART1, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART1, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART1, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART1, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART1, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART1, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART1, 0);
+ }
+
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Module failed config block 3");
+
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ response &= theGNSS.setAutoESFSTATUS(
+ true, false); // Tell the GPS to "send" each ESF Status, but do not update stale data when accessed
+ }
+
+ return (response);
}
-//This function stops BT so that it can be restarted later
-//It also releases as much system resources as possible so that WiFi/caster is more stable
-void endBluetooth()
+// Turn on indicator LEDs to verify LED function and indicate setup sucess
+void danceLEDs()
{
- //Delete tasks if running
- if (F9PSerialReadTaskHandle != NULL)
- {
- vTaskDelete(F9PSerialReadTaskHandle);
- F9PSerialReadTaskHandle = NULL;
- }
- if (F9PSerialWriteTaskHandle != NULL)
- {
- vTaskDelete(F9PSerialWriteTaskHandle);
- F9PSerialWriteTaskHandle = NULL;
- }
-
-#ifdef COMPILE_BT
- SerialBT.flush(); //Complete any transfers
- SerialBT.disconnect(); //Drop any clients
- SerialBT.end(); //SerialBT.end() will release significant RAM (~100k!) but a SerialBT.start will crash.
-#endif
-
- //The following code releases the BT hardware so that it can be restarted with a SerialBT.begin
- customBTstop();
- Serial.println(F("Bluetooth turned off"));
-
- radioState = RADIO_OFF;
-}
+ if (productVariant == RTK_SURVEYOR)
+ {
+ for (int x = 0; x < 2; x++)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
+ digitalWrite(pin_baseStatusLED, HIGH);
+ digitalWrite(pin_bluetoothStatusLED, HIGH);
+ delay(100);
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ digitalWrite(pin_baseStatusLED, LOW);
+ digitalWrite(pin_bluetoothStatusLED, LOW);
+ delay(100);
+ }
-//Starting and restarting BT is a problem. See issue: https://github.com/espressif/arduino-esp32/issues/2718
-//To work around the bug without modifying the core we create our own btStop() function with
-//the patch from github
-bool customBTstop() {
-#ifdef COMPILE_BT
- if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
- return true;
- }
- if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
- if (esp_bt_controller_disable()) {
- log_e("BT Disable failed");
- return false;
- }
- while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED);
- }
- if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED)
- {
- log_i("inited");
- if (esp_bt_controller_deinit())
- {
- log_e("BT deint failed");
- return false;
- }
- while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED)
- ;
- return true;
- }
- log_e("BT Stop failed");
-#endif
- return false;
+ digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
+ digitalWrite(pin_baseStatusLED, HIGH);
+ digitalWrite(pin_bluetoothStatusLED, HIGH);
+
+ delay(250);
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ delay(250);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ delay(250);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+
+ delay(250);
+ digitalWrite(pin_baseStatusLED, LOW);
+ delay(250);
+ digitalWrite(pin_bluetoothStatusLED, LOW);
+ }
+ else
+ {
+ // Units can boot under 1s. Keep splash screen up for at least 2s.
+ while ((millis() - splashStart) < 2000)
+ delay(1);
+ }
}
-//Start WiFi assuming it was previously fully released
-//See WiFiBluetoothSwitch sketch for more info
-void startWiFi()
+// Update Battery level LEDs every 5s
+void updateBattery()
{
-#ifdef COMPILE_WIFI
- wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
- esp_wifi_init(&wifi_init_config); //Restart WiFi resources
-
- Serial.printf("Connecting to local WiFi: %s\n\r", settings.wifiSSID);
- WiFi.begin(settings.wifiSSID, settings.wifiPW);
-#endif
+ if (millis() - lastBattUpdate > 5000)
+ {
+ lastBattUpdate = millis();
- radioState = WIFI_ON_NOCONNECTION;
+ checkBatteryLevels();
+ }
}
-//Stop WiFi and release all resources
-//See WiFiBluetoothSwitch sketch for more info
-void stopWiFi()
+// When called, checks level of battery and updates the LED brightnesses
+// And outputs a serial message to USB
+void checkBatteryLevels()
{
-#ifdef COMPILE_WIFI
- caster.stop();
- WiFi.mode(WIFI_OFF);
- esp_wifi_deinit(); //Free all resources
-#endif
+ if (online.battery == true)
+ {
+ battLevel = lipo.getSOC();
+ battVoltage = lipo.getVoltage();
+ battChangeRate = lipo.getChangeRate();
+ }
+ else
+ {
+ // False numbers but above system cut-off level
+ battLevel = 10;
+ battVoltage = 3.7;
+ battChangeRate = 0;
+ }
- Serial.println("WiFi Stopped");
+ if (battChangeRate >= -0.01)
+ externalPowerConnected = true;
+ else
+ externalPowerConnected = false;
- radioState = RADIO_OFF;
+ if (settings.enablePrintBatteryMessages)
+ {
+ char tempStr[25];
+ if (externalPowerConnected)
+ snprintf(tempStr, sizeof(tempStr), "C");
+ else
+ snprintf(tempStr, sizeof(tempStr), "Disc");
+
+ systemPrintf("Batt (%d%%): Voltage: %0.02fV", battLevel, battVoltage);
+
+ systemPrintf(" %sharging: %0.02f%%/hr ", tempStr, battChangeRate);
+
+ if (battLevel < 10)
+ snprintf(tempStr, sizeof(tempStr), "Red");
+ else if (battLevel < 50)
+ snprintf(tempStr, sizeof(tempStr), "Yellow");
+ else if (battLevel <= 110)
+ snprintf(tempStr, sizeof(tempStr), "Green");
+ else
+ snprintf(tempStr, sizeof(tempStr), "No batt");
+
+ systemPrintf("%s\r\n", tempStr);
+ }
+
+ // Check if we need to shutdown due to no charging
+ if (settings.shutdownNoChargeTimeout_s > 0)
+ {
+ if (externalPowerConnected == false)
+ {
+ int secondsSinceLastCharger = (millis() - shutdownNoChargeTimer) / 1000;
+ if (secondsSinceLastCharger > settings.shutdownNoChargeTimeout_s)
+ powerDown(true);
+ }
+ else
+ {
+ shutdownNoChargeTimer = millis(); // Reset timer because power is attached
+ }
+ }
+
+ if (productVariant == RTK_SURVEYOR)
+ {
+ if (battLevel < 10)
+ {
+ ledcWrite(ledRedChannel, 255);
+ ledcWrite(ledGreenChannel, 0);
+ }
+ else if (battLevel < 50)
+ {
+ ledcWrite(ledRedChannel, 128);
+ ledcWrite(ledGreenChannel, 128);
+ }
+ else if (battLevel <= 110)
+ {
+ ledcWrite(ledRedChannel, 0);
+ ledcWrite(ledGreenChannel, 255);
+ }
+ else
+ {
+ ledcWrite(ledRedChannel, 10);
+ ledcWrite(ledGreenChannel, 0);
+ }
+ }
}
-//Setup the u-blox module for any setup (base or rover)
-//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.
-bool configureUbloxModule()
+// Ping an I2C device and see if it responds
+bool isConnected(uint8_t deviceAddress)
{
- boolean response = true;
- int maxWait = 2000;
-
- i2cGNSS.checkUblox(); //Regularly poll to get latest data and any RTCM
-
- //The first thing we do is go to 1Hz to lighten any I2C traffic from a previous configuration
- if (i2cGNSS.getNavigationFrequency(maxWait) != 1)
- response &= i2cGNSS.setNavigationFrequency(1, maxWait);
- if (response == false)
- Serial.println(F("Set rate failed"));
-
- response = i2cGNSS.disableSurveyMode(maxWait); //Disable survey
- if (response == false)
- Serial.println(F("Disable Survey failed"));
-
-#define OUTPUT_SETTING 14
-#define INPUT_SETTING 12
-
- //UART1 will primarily be used to pass NMEA and UBX from ZED to ESP32 (eventually to cell phone)
- //but the phone can also provide RTCM data and a user may want to configure the ZED over Bluetooth.
- //So let's be sure to enable UBX+NMEA+RTCM on the input
- getPortSettings(COM_PORT_UART1); //Load the settingPayload with this port's settings
- if (settingPayload[OUTPUT_SETTING] != (COM_TYPE_NMEA | COM_TYPE_UBX | COM_TYPE_RTCM3) || settingPayload[INPUT_SETTING] != (COM_TYPE_NMEA | COM_TYPE_UBX | COM_TYPE_RTCM3))
- {
- response &= i2cGNSS.setPortOutput(COM_PORT_UART1, COM_TYPE_NMEA | COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the UART1 to output UBX+NMEA+RTCM
- response &= i2cGNSS.setPortInput(COM_PORT_UART1, COM_TYPE_NMEA | COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the UART1 to input UBX+NMEA+RTCM
- }
-
- //Disable SPI port - This is just to remove some overhead by ZED
- getPortSettings(COM_PORT_SPI); //Load the settingPayload with this port's settings
- if (settingPayload[OUTPUT_SETTING] != 0 || settingPayload[INPUT_SETTING] != 0)
- {
- response &= i2cGNSS.setPortOutput(COM_PORT_SPI, 0); //Disable all protocols
- response &= i2cGNSS.setPortInput(COM_PORT_SPI, 0); //Disable all protocols
- }
-
- getPortSettings(COM_PORT_UART2); //Load the settingPayload with this port's settings
- if (settingPayload[OUTPUT_SETTING] != COM_TYPE_RTCM3 || settingPayload[INPUT_SETTING] != COM_TYPE_RTCM3)
- {
- response &= i2cGNSS.setPortOutput(COM_PORT_UART2, COM_TYPE_RTCM3); //Set the UART2 to output RTCM (in case this device goes into base mode)
- response &= i2cGNSS.setPortInput(COM_PORT_UART2, COM_TYPE_RTCM3); //Set the UART2 to input RTCM
- }
-
- //Turn on RTCM over I2C port so that we can harvest RTCM over I2C and send out over WiFi
- //This is easier than parsing over UART because the library handles the frame detection
- getPortSettings(COM_PORT_I2C); //Load the settingPayload with this port's settings
- if (settingPayload[OUTPUT_SETTING] != (COM_TYPE_UBX | COM_TYPE_RTCM3) || settingPayload[INPUT_SETTING] != COM_TYPE_UBX)
- {
- response &= i2cGNSS.setPortOutput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the I2C port to output UBX (config), NMEA (logging), and RTCM3 (casting)
- response &= i2cGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX); //Set the I2C port to input UBX only
- }
-
- //The USB port on the ZED may be used for RTCM to/from the computer (as an NTRIP caster or client)
- //So let's be sure all protocols are on for the USB port
- getPortSettings(COM_PORT_USB); //Load the settingPayload with this port's settings
- if (settingPayload[OUTPUT_SETTING] != (COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3) || settingPayload[INPUT_SETTING] != (COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3))
- {
- response &= i2cGNSS.setPortOutput(COM_PORT_USB, (COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3)); //Set the USB port to everything
- response &= i2cGNSS.setPortInput(COM_PORT_USB, (COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3)); //Set the USB port to everything
- }
-
- response &= configureConstellations(); //Enable the constellations the user has set
-
- response &= configureGNSSMessageRates(COM_PORT_UART1, ubxMessages); //Make sure the appropriate messages are enabled
-
- response &= i2cGNSS.setAutoPVT(true, false); //Tell the GPS to "send" each solution, but do not update stale data when accessed
- response &= i2cGNSS.setAutoHPPOSLLH(true, false); //Tell the GPS to "send" each high res solution, but do not update stale data when accessed
-
- if (getSerialRate(COM_PORT_UART1) != settings.dataPortBaud)
- {
- Serial.println(F("Updating UART1 rate"));
- i2cGNSS.setSerialRate(settings.dataPortBaud, COM_PORT_UART1); //Set UART1 to 115200
- }
- if (getSerialRate(COM_PORT_UART2) != settings.radioPortBaud)
- {
- Serial.println(F("Updating UART2 rate"));
- i2cGNSS.setSerialRate(settings.radioPortBaud, COM_PORT_UART2); //Set UART2 to 57600 to match SiK telemetry radio firmware default
- }
-
- if (response == false)
- {
- Serial.println(F("Module failed initial config."));
- }
-
- response &= i2cGNSS.saveConfiguration(); //Save the current settings to flash and BBR
- if (response == false)
- Serial.println(F("Module failed to save."));
-
- //Turn on/off debug messages
- if (settings.enableI2Cdebug)
- i2cGNSS.enableDebugging(Serial, true); //Enable only the critical debug messages over Serial
- else
- i2cGNSS.disableDebugging();
-
- return (response);
+ Wire.beginTransmission(deviceAddress);
+ if (Wire.endTransmission() == 0)
+ return true;
+ return false;
}
+// Create a test file in file structure to make sure we can
+bool createTestFile()
+{
+ FileSdFatMMC testFile;
+
+ // TODO: double-check that SdFat tollerates preceding slashes
+ char testFileName[40] = "/testfile.txt";
+
+ // Attempt to write to the file system
+ if ((!testFile) || (testFile.open(testFileName, O_CREAT | O_APPEND | O_WRITE) != true))
+ {
+ systemPrintln("createTestFile: failed to create (open) test file");
+ return (false);
+ }
+
+ testFile.println("Testing...");
+ // File successfully created
+ testFile.close();
-//Disable all the NMEA sentences on a given com port
-bool disableNMEASentences(uint8_t portType)
+ if (USE_SPI_MICROSD)
+ {
+ if (sd->exists(testFileName))
+ sd->remove(testFileName);
+ return (!sd->exists(testFileName));
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (SD_MMC.exists(testFileName))
+ SD_MMC.remove(testFileName);
+ return (!SD_MMC.exists(testFileName));
+ }
+#endif // COMPILE_SD_MMC
+
+ return (false);
+}
+
+// If debug option is on, print available heap
+void reportHeapNow(bool alwaysPrint)
{
- bool response = true;
- if (getNMEASettings(UBX_NMEA_GGA, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_GGA, portType);
- if (getNMEASettings(UBX_NMEA_GSA, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_GSA, portType);
- if (getNMEASettings(UBX_NMEA_GSV, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_GSV, portType);
- if (getNMEASettings(UBX_NMEA_RMC, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_RMC, portType);
- if (getNMEASettings(UBX_NMEA_GST, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_GST, portType);
- if (getNMEASettings(UBX_NMEA_GLL, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_GLL, portType);
- if (getNMEASettings(UBX_NMEA_VTG, portType) != 0)
- response &= i2cGNSS.disableNMEAMessage(UBX_NMEA_VTG, portType);
-
- return (response);
+ if (alwaysPrint || (settings.enableHeapReport == true))
+ {
+ lastHeapReport = millis();
+ systemPrintf("FreeHeap: %d / HeapLowestPoint: %d / LargestBlock: %d\r\n", ESP.getFreeHeap(),
+ xPortGetMinimumEverFreeHeapSize(), heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
+ }
}
-//Enable RTCM sentences for a given com port
-//This function is needed when switching from rover to base
-//We over-ride user settings in base mode
-bool enableRTCMSentences(uint8_t portType)
+// If debug option is on, print available heap
+void reportHeap()
{
- bool response = true;
- int maxWait = 1000; //When I2C traffic is large during logging, give extra time
-
- if (getRTCMSettings(UBX_RTCM_1005, portType) != 1)
- response &= i2cGNSS.enableRTCMmessage(UBX_RTCM_1005, portType, 1, maxWait); //Enable message 1005 to output through UART2, message every second
- if (getRTCMSettings(UBX_RTCM_1074, portType) != 1)
- response &= i2cGNSS.enableRTCMmessage(UBX_RTCM_1074, portType, 1, maxWait);
- if (getRTCMSettings(UBX_RTCM_1084, portType) != 1)
- response &= i2cGNSS.enableRTCMmessage(UBX_RTCM_1084, portType, 1, maxWait);
- if (getRTCMSettings(UBX_RTCM_1094, portType) != 1)
- response &= i2cGNSS.enableRTCMmessage(UBX_RTCM_1094, portType, 1, maxWait);
- if (getRTCMSettings(UBX_RTCM_1124, portType) != 1)
- response &= i2cGNSS.enableRTCMmessage(UBX_RTCM_1124, portType, 1, maxWait);
- if (getRTCMSettings(UBX_RTCM_1230, portType) != 10)
- response &= i2cGNSS.enableRTCMmessage(UBX_RTCM_1230, portType, 10, maxWait); //Enable message every 10 seconds
-
- return (response);
+ if (settings.enableHeapReport == true)
+ {
+ if (millis() - lastHeapReport > 1000)
+ {
+ reportHeapNow(false);
+ }
+ }
}
-//Disable RTCM sentences for a given com port
-//This function is needed when switching from base to rover
-//It's used for turning off RTCM on USB, UART2, and I2C ports.
-//UART1 should be re-enabled with user settings using the configureGNSSMessageRates() function
-bool disableRTCMSentences(uint8_t portType)
+// Based on current LED state, blink upwards fashion
+// Used to indicate casting
+void cyclePositionLEDs()
{
- bool response = true;
- int maxWait = 1000; //When I2C traffic is large during logging, give extra time
-
- if (getRTCMSettings(UBX_RTCM_1005, portType) != 0)
- response &= i2cGNSS.disableRTCMmessage(UBX_RTCM_1005, portType, maxWait);
- if (getRTCMSettings(UBX_RTCM_1074, portType) != 0)
- response &= i2cGNSS.disableRTCMmessage(UBX_RTCM_1074, portType, maxWait);
- if (getRTCMSettings(UBX_RTCM_1084, portType) != 0)
- response &= i2cGNSS.disableRTCMmessage(UBX_RTCM_1084, portType, maxWait);
- if (getRTCMSettings(UBX_RTCM_1094, portType) != 0)
- response &= i2cGNSS.disableRTCMmessage(UBX_RTCM_1094, portType, maxWait);
- if (getRTCMSettings(UBX_RTCM_1124, portType) != 0)
- response &= i2cGNSS.disableRTCMmessage(UBX_RTCM_1124, portType, maxWait);
- if (getRTCMSettings(UBX_RTCM_1230, portType) != 0)
- response &= i2cGNSS.disableRTCMmessage(UBX_RTCM_1230, portType, maxWait);
- return (response);
+ if (productVariant == RTK_SURVEYOR)
+ {
+ // Cycle position LEDs to indicate casting
+ if (millis() - lastCasterLEDupdate > 500)
+ {
+ lastCasterLEDupdate = millis();
+ if (digitalRead(pin_positionAccuracyLED_100cm) == HIGH)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ }
+ else if (digitalRead(pin_positionAccuracyLED_10cm) == HIGH)
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, LOW);
+ }
+ else // Catch all
+ {
+ digitalWrite(pin_positionAccuracyLED_1cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_10cm, LOW);
+ digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
+ }
+ }
+ }
+}
+
+// Determine MUX pins for this platform and set MUX to ADC/DAC to avoid I2C bus failure
+void beginMux()
+{
+ if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS_PLUS)
+ {
+ pin_muxA = 2;
+ pin_muxB = 4;
+ }
+ else if (productVariant == RTK_FACET || productVariant == RTK_FACET_LBAND)
+ {
+ pin_muxA = 2;
+ pin_muxB = 0;
+ }
+
+ setMuxport(MUX_ADC_DAC); // Set mux to user's choice: NMEA, I2C, PPS, or DAC
}
-//Given a portID, load the settings associated
-bool getPortSettings(uint8_t portID)
+// Set the port of the 1:4 dual channel analog mux
+// This allows NMEA, I2C, PPS/Event, and ADC/DAC to be routed through data port via software select
+void setMuxport(int channelNumber)
{
- ubxPacket customCfg = {0, 0, 0, 0, 0, settingPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
+ if (pin_muxA >= 0 && pin_muxB >= 0)
+ {
+ pinMode(pin_muxA, OUTPUT);
+ pinMode(pin_muxB, OUTPUT);
+
+ if (channelNumber > 3)
+ return; // Error check
+
+ switch (channelNumber)
+ {
+ case 0:
+ digitalWrite(pin_muxA, LOW);
+ digitalWrite(pin_muxB, LOW);
+ break;
+ case 1:
+ digitalWrite(pin_muxA, HIGH);
+ digitalWrite(pin_muxB, LOW);
+ break;
+ case 2:
+ digitalWrite(pin_muxA, LOW);
+ digitalWrite(pin_muxB, HIGH);
+ break;
+ case 3:
+ digitalWrite(pin_muxA, HIGH);
+ digitalWrite(pin_muxB, HIGH);
+ break;
+ }
+ }
+}
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_PRT; // This is the message ID
- customCfg.len = 1;
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
+// Create $GNTXT, type message complete with CRC
+// https://www.nmea.org/Assets/20160520%20txt%20amendment.pdf
+// Used for recording system events (boot reason, event triggers, etc) inside the log
+void createNMEASentence(customNmeaType_e textID, char *nmeaMessage, size_t sizeOfNmeaMessage, char *textMessage)
+{
+ // Currently we don't have messages longer than 82 char max so we hardcode the sentence numbers
+ const uint8_t totalNumberOfSentences = 1;
+ const uint8_t sentenceNumber = 1;
- uint16_t maxWait = 250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100)
+ char nmeaTxt[200]; // Max NMEA sentence length is 82
+ snprintf(nmeaTxt, sizeof(nmeaTxt), "$GNTXT,%02d,%02d,%02d,%s*", totalNumberOfSentences, sentenceNumber, textID,
+ textMessage);
- settingPayload[0] = portID; //Request the caller's portID from GPS module
+ // From: http://engineeringnotes.blogspot.com/2015/02/generate-crc-for-nmea-strings-arduino.html
+ byte CRC = 0; // XOR chars between '$' and '*'
+ for (byte x = 1; x < strlen(nmeaTxt) - 1; x++)
+ CRC = CRC ^ nmeaTxt[x];
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("getPortSettings failed"));
- return (false);
- }
+ snprintf(nmeaMessage, sizeOfNmeaMessage, "%s%02X", nmeaTxt, CRC);
+}
- return (true);
+// Reset settings struct to default initializers
+void settingsToDefaults()
+{
+ static const Settings defaultSettings;
+ memcpy(&settings, &defaultSettings, sizeof(defaultSettings));
}
-//Given a portID and a NMEA message type, load the settings associated
-uint8_t getNMEASettings(uint8_t msgID, uint8_t portID)
+// Given a spot in the ubxMsg array, return true if this message is supported on this platform and firmware version
+bool messageSupported(int messageNumber)
{
- ubxPacket customCfg = {0, 0, 0, 0, 0, settingPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
+ bool messageSupported = false;
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_MSG; // This is the message ID
- customCfg.len = 2;
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
+ if ((zedModuleType == PLATFORM_F9P) &&
+ (zedFirmwareVersionInt >= ubxMessages[messageNumber].f9pFirmwareVersionSupported))
+ messageSupported = true;
+ else if ((zedModuleType == PLATFORM_F9R) &&
+ (zedFirmwareVersionInt >= ubxMessages[messageNumber].f9rFirmwareVersionSupported))
+ messageSupported = true;
- uint16_t maxWait = 250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100)
+ return (messageSupported);
+}
+// Given a command key, return true if that key is supported on this platform and firmware version
+bool commandSupported(const uint32_t key)
+{
+ bool commandSupported = false;
- settingPayload[0] = UBX_CLASS_NMEA;
- settingPayload[1] = msgID;
+ // Locate this key in the known key array
+ int commandNumber = 0;
+ for (; commandNumber < MAX_UBX_CMD; commandNumber++)
+ {
+ if (ubxCommands[commandNumber].cmdKey == key)
+ break;
+ }
+ if (commandNumber == MAX_UBX_CMD)
+ {
+ systemPrintf("commandSupported: Unknown command key 0x%02X\r\n", key);
+ commandSupported = false;
+ }
+ else
+ {
+ if ((zedModuleType == PLATFORM_F9P) &&
+ (zedFirmwareVersionInt >= ubxCommands[commandNumber].f9pFirmwareVersionSupported))
+ commandSupported = true;
+ else if ((zedModuleType == PLATFORM_F9R) &&
+ (zedFirmwareVersionInt >= ubxCommands[commandNumber].f9rFirmwareVersionSupported))
+ commandSupported = true;
+ }
+ return (commandSupported);
+}
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("getNMEASettings failed"));
- return (false);
- }
+// Enable all the valid messages for this platform
+// There are many messages so split into batches. VALSET is limited to 64 max per batch
+// Uses dummy newCfg and sendCfg values to be sure we open/close a complete set
+bool setMessages(int maxRetries)
+{
+ uint32_t spiOffset =
+ 0; // Set to 3 if using SPI to convert UART1 keys to SPI. This is brittle and non-perfect, but works.
+ if (USE_SPI_GNSS)
+ spiOffset = 3;
+
+ bool success = false;
+ int tryNo = -1;
+
+ // Try up to maxRetries times to configure the messages
+ // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI
+ // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being
+ // processed.
+ while ((++tryNo < maxRetries) && !success)
+ {
+ bool response = true;
+ int messageNumber = 0;
+
+ while (messageNumber < MAX_UBX_MSG)
+ {
+ response &= theGNSS.newCfgValset();
+
+ do
+ {
+ if (messageSupported(messageNumber) == true)
+ {
+ uint8_t rate = settings.ubxMessageRates[messageNumber];
+
+ // If the GNSS is SPI, we need to make sure that NAV_PVT, NAV_HPPOSLLH and ESF_STATUS remained
+ // enabled (but not enabled for logging)
+ if (USE_SPI_GNSS)
+ {
+ if (ubxMessages[messageNumber].msgClass == UBX_CLASS_NAV)
+ if ((ubxMessages[messageNumber].msgID == UBX_NAV_PVT) ||
+ (ubxMessages[messageNumber].msgID == UBX_NAV_HPPOSLLH))
+ if (rate == 0)
+ rate = 1;
+ if (ubxMessages[messageNumber].msgClass == UBX_CLASS_ESF)
+ if (ubxMessages[messageNumber].msgID == UBX_ESF_STATUS)
+ if (zedModuleType == PLATFORM_F9R)
+ if (rate == 0)
+ rate = 1;
+ if (ubxMessages[messageNumber].msgClass == UBX_CLASS_TIM)
+ {
+ if (ubxMessages[messageNumber].msgID == UBX_TIM_TM2)
+ if (rate == 0)
+ rate = 1;
+ if (ubxMessages[messageNumber].msgID == UBX_TIM_TP)
+ if (HAS_GNSS_TP_INT)
+ if (rate == 0)
+ rate = 1;
+ }
+ if (ubxMessages[messageNumber].msgClass == UBX_CLASS_RXM)
+ if (ubxMessages[messageNumber].msgID == UBX_RXM_COR)
+ if (rate == 0)
+ rate = 1;
+ if (ubxMessages[messageNumber].msgClass == UBX_CLASS_NMEA)
+ if (ubxMessages[messageNumber].msgID == UBX_NMEA_GGA)
+ if (rate == 0)
+ rate = 1;
+ if (ubxMessages[messageNumber].msgClass == UBX_CLASS_MON)
+ if (ubxMessages[messageNumber].msgID == UBX_MON_HW)
+ if (rate == 0)
+ rate = 1;
+ }
+
+ response &= theGNSS.addCfgValset(ubxMessages[messageNumber].msgConfigKey + spiOffset, rate);
+ }
+ messageNumber++;
+ } while (((messageNumber % 43) < 42) &&
+ (messageNumber < MAX_UBX_MSG)); // Limit 1st batch to 42. Batches after that will be (up to) 43 in
+ // size. It's a HHGTTG thing.
+
+ if (theGNSS.sendCfgValset() == false)
+ {
+ log_d("sendCfg failed at messageNumber %d %s. Try %d of %d.", messageNumber - 1,
+ (messageNumber - 1) < MAX_UBX_MSG ? ubxMessages[messageNumber - 1].msgTextName : "", tryNo + 1,
+ maxRetries);
+ response &= false; // If any one of the Valset fails, report failure overall
+ }
+ }
+
+ // For SPI GNSS products, we need to add each message to the GNSS Library logging buffer
+ // to mimic UART1
+ if (USE_SPI_GNSS)
+ {
+ uint32_t logRTCMMessages = 0;
+ uint32_t logNMEAMessages = 0;
+
+ for (messageNumber = 0; messageNumber < MAX_UBX_MSG; messageNumber++)
+ {
+ if (ubxMessages[messageNumber].msgClass == UBX_RTCM_MSB) // RTCM messages
+ {
+ if (messageSupported(messageNumber) == true)
+ logRTCMMessages |= ubxMessages[messageNumber].filterMask;
+ }
+ else if (ubxMessages[messageNumber].msgClass == UBX_CLASS_NMEA) // NMEA messages
+ {
+ if (messageSupported(messageNumber) == true)
+ logNMEAMessages |= ubxMessages[messageNumber].filterMask;
+ }
+ else // UBX messages
+ {
+ if (messageSupported(messageNumber) == true)
+ theGNSS.enableUBXlogging(ubxMessages[messageNumber].msgClass, ubxMessages[messageNumber].msgID,
+ settings.ubxMessageRates[messageNumber] > 0);
+ }
+ }
+
+ theGNSS.setRTCMLoggingMask(logRTCMMessages);
+ theGNSS.setNMEALoggingMask(logNMEAMessages);
+ }
+
+ if (response)
+ success = true;
+ }
- return (settingPayload[2 + portID]); //Return just the byte associated with this portID
+ return (success);
}
-//Given a portID and a RTCM message type, load the settings associated
-uint8_t getRTCMSettings(uint8_t msgID, uint8_t portID)
+// Enable all the valid messages for this platform over the USB port
+// Add 2 to every UART1 key. This is brittle and non-perfect, but works.
+bool setMessagesUSB(int maxRetries)
{
- ubxPacket customCfg = {0, 0, 0, 0, 0, settingPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
+ bool success = false;
+ int tryNo = -1;
+
+ // Try up to maxRetries times to configure the messages
+ // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI
+ // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being
+ // processed.
+ while ((++tryNo < maxRetries) && !success)
+ {
+ bool response = true;
+ int messageNumber = 0;
+
+ while (messageNumber < MAX_UBX_MSG)
+ {
+ response &= theGNSS.newCfgValset();
+
+ do
+ {
+ if (messageSupported(messageNumber) == true)
+ response &= theGNSS.addCfgValset(ubxMessages[messageNumber].msgConfigKey + 2,
+ settings.ubxMessageRates[messageNumber]);
+ messageNumber++;
+ } while (((messageNumber % 43) < 42) &&
+ (messageNumber < MAX_UBX_MSG)); // Limit 1st batch to 42. Batches after that will be (up to) 43 in
+ // size. It's a HHGTTG thing.
+
+ response &= theGNSS.sendCfgValset();
+ }
+
+ if (response)
+ success = true;
+ }
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_MSG; // This is the message ID
- customCfg.len = 2;
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
+ return (success);
+}
- uint16_t maxWait = 1250; // Wait for up to 1250ms (Serial may need a lot longer e.g. 1100)
+// Enable all the valid constellations and bands for this platform
+// Band support varies between platforms and firmware versions
+// We open/close a complete set if sendCompleteBatch = true
+// 19 messages
+bool setConstellations(bool sendCompleteBatch)
+{
+ bool response = true;
- settingPayload[0] = UBX_RTCM_MSB;
- settingPayload[1] = msgID;
+ if (sendCompleteBatch)
+ response &= theGNSS.newCfgValset();
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("getRTCMSettings failed"));
- return (false);
- }
+ bool enableMe = settings.ubxConstellations[0].enabled;
+ response &= theGNSS.addCfgValset(settings.ubxConstellations[0].configKey, enableMe); // GPS
- return (settingPayload[2 + portID]); //Return just the byte associated with this portID
-}
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_GPS_L1CA_ENA, settings.ubxConstellations[0].enabled);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_GPS_L2C_ENA, settings.ubxConstellations[0].enabled);
-//Given a portID and a NMEA message type, load the settings associated
-uint32_t getSerialRate(uint8_t portID)
-{
- ubxPacket customCfg = {0, 0, 0, 0, 0, settingPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
+ // v1.12 ZED-F9P firmware does not allow for SBAS control
+ // Also, if we can't identify the version (99), skip SBAS enable
+ if ((zedModuleType == PLATFORM_F9P) && ((zedFirmwareVersionInt == 112) || (zedFirmwareVersionInt == 99)))
+ {
+ // Skip
+ }
+ else
+ {
+ response &= theGNSS.addCfgValset(settings.ubxConstellations[1].configKey,
+ settings.ubxConstellations[1].enabled); // SBAS
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_SBAS_L1CA_ENA, settings.ubxConstellations[1].enabled);
+ }
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_PRT; // This is the message ID
- customCfg.len = 1;
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
+ response &=
+ theGNSS.addCfgValset(settings.ubxConstellations[2].configKey, settings.ubxConstellations[2].enabled); // GAL
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_GAL_E1_ENA, settings.ubxConstellations[2].enabled);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_GAL_E5B_ENA, settings.ubxConstellations[2].enabled);
- uint16_t maxWait = 250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100)
+ response &=
+ theGNSS.addCfgValset(settings.ubxConstellations[3].configKey, settings.ubxConstellations[3].enabled); // BDS
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_BDS_B1_ENA, settings.ubxConstellations[3].enabled);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_BDS_B2_ENA, settings.ubxConstellations[3].enabled);
- settingPayload[0] = portID;
+ response &=
+ theGNSS.addCfgValset(settings.ubxConstellations[4].configKey, settings.ubxConstellations[4].enabled); // QZSS
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1CA_ENA, settings.ubxConstellations[4].enabled);
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.println(F("getSerialRate failed"));
- return (false);
- }
+ // UBLOX_CFG_SIGNAL_QZSS_L1S_ENA not supported on F9R in v1.21 and below
+ if (zedModuleType == PLATFORM_F9P)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1S_ENA, settings.ubxConstellations[4].enabled);
+ else if ((zedModuleType == PLATFORM_F9R) && (zedFirmwareVersionInt > 121))
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1S_ENA, settings.ubxConstellations[4].enabled);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L2C_ENA, settings.ubxConstellations[4].enabled);
+
+ response &=
+ theGNSS.addCfgValset(settings.ubxConstellations[5].configKey, settings.ubxConstellations[5].enabled); // GLO
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_GLO_L1_ENA, settings.ubxConstellations[5].enabled);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_SIGNAL_GLO_L2_ENA, settings.ubxConstellations[5].enabled);
- return (((uint32_t)settingPayload[10] << 16) | ((uint32_t)settingPayload[9] << 8) | settingPayload[8]);
+ if (sendCompleteBatch)
+ response &= theGNSS.sendCfgValset();
+
+ return (response);
}
-//Freeze displaying a given error code
-void blinkError(t_errorNumber errorNumber)
+// Periodically print position if enabled
+void printPosition()
{
- while (1)
- {
- for (int x = 0 ; x < errorNumber ; x++)
+ // Periodically print the position
+ if (settings.enablePrintPosition && ((millis() - lastPrintPosition) > 15000))
{
- if (productVariant == RTK_SURVEYOR)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- digitalWrite(pin_baseStatusLED, HIGH);
- digitalWrite(pin_bluetoothStatusLED, HIGH);
- delay(200);
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- digitalWrite(pin_baseStatusLED, LOW);
- digitalWrite(pin_bluetoothStatusLED, LOW);
- delay(200);
- }
+ printCurrentConditions();
+ lastPrintPosition = millis();
}
-
- delay(2000);
- }
}
-//Turn on indicator LEDs to verify LED function and indicate setup sucess
-void danceLEDs()
+// Periodically print RTK state if enabled
+void printRTKState()
{
- if (productVariant == RTK_SURVEYOR)
- {
- for (int x = 0 ; x < 2 ; x++)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- digitalWrite(pin_baseStatusLED, HIGH);
- digitalWrite(pin_bluetoothStatusLED, HIGH);
- delay(100);
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- digitalWrite(pin_baseStatusLED, LOW);
- digitalWrite(pin_bluetoothStatusLED, LOW);
- delay(100);
- }
-
- digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- digitalWrite(pin_baseStatusLED, HIGH);
- digitalWrite(pin_bluetoothStatusLED, HIGH);
-
- delay(250);
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- delay(250);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- delay(250);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
-
- delay(250);
- digitalWrite(pin_baseStatusLED, LOW);
- delay(250);
- digitalWrite(pin_bluetoothStatusLED, LOW);
- }
+ // Periodically print the RTK state
+ if (settings.enablePrintState && ((millis() - lastPrintState) > 15000))
+ {
+ printCurrentRTKState();
+ lastPrintState = millis();
+ }
}
-//Call back for when BT connection event happens (connected/disconnect)
-//Used for updating the radioState state machine
-#ifdef COMPILE_BT
-void btCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
- if (event == ESP_SPP_SRV_OPEN_EVT) {
- Serial.println(F("Client Connected"));
- radioState = BT_CONNECTED;
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_bluetoothStatusLED, HIGH);
- }
+// Given a user's string, try to identify the type and return the coordinate in DD.ddddddddd format
+CoordinateInputType coordinateIdentifyInputType(const char *userEntryOriginal, double *coordinate)
+{
+ char userEntry[50];
+ strncpy(userEntry, userEntryOriginal,
+ sizeof(userEntry) - 1); // strtok modifies the message so make copy into userEntry
- if (event == ESP_SPP_CLOSE_EVT ) {
- Serial.println(F("Client disconnected"));
- radioState = BT_ON_NOCONNECTION;
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_bluetoothStatusLED, LOW);
- }
-}
-#endif
+ *coordinate = 0.0; // Clear what is given to us
-//Update Battery level LEDs every 5s
-void updateBattLEDs()
-{
- if (millis() - lastBattUpdate > 5000)
- {
- lastBattUpdate = millis();
+ CoordinateInputType coordinateInputType = COORDINATE_INPUT_TYPE_INVALID_UNKNOWN;
- checkBatteryLevels();
- }
-}
+ int dashCount = 0;
+ int spaceCount = 0;
+ int decimalCount = 0;
+ int lengthOfLeadingNumber = 0;
-//When called, checks level of battery and updates the LED brightnesses
-//And outputs a serial message to USB
-void checkBatteryLevels()
-{
- battLevel = lipo.getSOC();
- battVoltage = lipo.getVoltage();
- battChangeRate = lipo.getChangeRate();
+ // Scan entry for invalid chars
+ // A valid entry has only numbers, -, ' ', and .
+ for (int x = 0; x < strlen(userEntry); x++)
+ {
+ if (isdigit(userEntry[x])) // All good
+ {
+ if (decimalCount == 0)
+ lengthOfLeadingNumber++;
+ }
+ else if (userEntry[x] == '-')
+ dashCount++; // All good
+ else if (userEntry[x] == ' ')
+ spaceCount++; // All good
+ else if (userEntry[x] == '.')
+ decimalCount++; // All good
+ else
+ return (COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); // String contains invalid character
+ }
- Serial.printf("Batt (%d%%): Voltage: %0.02fV", battLevel, battVoltage);
+ // Seven possible entry types
+ // DD.ddddddddd
+ // DDMM.mmmmmmm
+ // DD MM.mmmmmmm
+ // DD-MM.mmmmmmm
+ // DDMMSS.ssssss
+ // DD MM SS.ssssss
+ // DD-MM-SS.ssssss
+ // DDMMSS
+ // DD MM SS
+ // DD-MM-SS
+
+ if (decimalCount > 1)
+ return (COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); // 40.09.033 is not valid.
+ if (spaceCount > 2)
+ return (COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); // Only 0, 1, or 2 allowed. 40 05 25.2049 is valid.
+ if (dashCount > 3)
+ return (COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); // Only 0, 1, 2, or 3 allowed. -105-11-05.1629 is valid.
+ if (lengthOfLeadingNumber > 7)
+ return (COORDINATE_INPUT_TYPE_INVALID_UNKNOWN); // Only 7 or fewer. -1051105.188992 (DDDMMSS or DDMMSS) is valid
+
+ bool negativeSign = false;
+ if (userEntry[0] == '-')
+ {
+ userEntry[0] = ' ';
+ negativeSign = true;
+ dashCount--; // Use dashCount as the internal dashes only, not the leading negative sign
+ }
+
+ if (spaceCount == 0 && dashCount == 0 &&
+ (lengthOfLeadingNumber == 7 || lengthOfLeadingNumber == 6)) // DDMMSS.ssssss or DDMMSS
+ {
+ coordinateInputType = COORDINATE_INPUT_TYPE_DDMMSS;
- char tempStr[25];
- if (battChangeRate > 0)
- sprintf(tempStr, "C");
- else
- sprintf(tempStr, "Disc");
- Serial.printf(" %sharging: %0.02f%%/hr ", tempStr, battChangeRate);
+ long intPortion = atoi(userEntry); // Get DDDMMSS
+ long decimal = intPortion / 10000L; // Get DDD
+ intPortion -= (decimal * 10000L);
+ long minutes = intPortion / 100L; // Get MM
- if (battLevel < 10)
- sprintf(tempStr, "Red");
- else if (battLevel < 50)
- sprintf(tempStr, "Yellow");
- else if (battLevel >= 50)
- sprintf(tempStr, "Green");
- else
- sprintf(tempStr, "No batt");
+ // Find '.'
+ char *decimalPtr = strchr(userEntry, '.');
+ if (decimalPtr == nullptr)
+ coordinateInputType = COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL;
- Serial.printf("%s\n\r", tempStr);
+ double seconds = atof(userEntry); // Get DDDMMSS.ssssss
+ seconds -= (decimal * 10000); // Remove DDD
+ seconds -= (minutes * 100); // Remove MM
+ *coordinate = decimal + (minutes / (double)60) + (seconds / (double)3600);
- if (productVariant == RTK_SURVEYOR)
- {
- if (battLevel < 10)
+ if (negativeSign)
+ *coordinate *= -1;
+ }
+ else if (spaceCount == 0 && dashCount == 0 &&
+ (lengthOfLeadingNumber == 5 || lengthOfLeadingNumber == 4)) // DDMM.mmmmmmm
{
- ledcWrite(ledRedChannel, 255);
- ledcWrite(ledGreenChannel, 0);
+ coordinateInputType = COORDINATE_INPUT_TYPE_DDMM;
+
+ long intPortion = atoi(userEntry); // Get DDDMM
+ long decimal = intPortion / 100L; // Get DDD
+ intPortion -= (decimal * 100L);
+ double minutes = atof(userEntry); // Get DDDMM.mmmmmmm
+ minutes -= (decimal * 100L); // Remove DDD
+ *coordinate = decimal + (minutes / (double)60);
+ if (negativeSign)
+ *coordinate *= -1;
}
- else if (battLevel < 50)
+ else if (dashCount == 1) // DD-MM.mmmmmmm
{
- ledcWrite(ledRedChannel, 128);
- ledcWrite(ledGreenChannel, 128);
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD_MM_DASH;
+
+ char *token = strtok(userEntry, "-"); // Modifies the given array
+ // We trust that token points at something because the dashCount is > 0
+ int decimal = atoi(token); // Get DD
+ token = strtok(nullptr, "-");
+ double minutes = atof(token); // Get MM.mmmmmmm
+ *coordinate = decimal + (minutes / 60.0);
+ if (negativeSign)
+ *coordinate *= -1;
}
- else if (battLevel >= 50)
+ else if (dashCount == 2) // DD-MM-SS.ssss or DD-MM-SS
{
- ledcWrite(ledRedChannel, 0);
- ledcWrite(ledGreenChannel, 255);
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD_MM_SS_DASH;
+
+ char *token = strtok(userEntry, "-"); // Modifies the given array
+ // We trust that token points at something because the spaceCount is > 0
+ int decimal = atoi(token); // Get DD
+ token = strtok(nullptr, "-");
+ int minutes = atoi(token); // Get MM
+ token = strtok(nullptr, "-");
+
+ // Find '.'
+ char *decimalPtr = strchr(token, '.'); // Use token, not userEntry, as the dashes are now NULL
+ if (decimalPtr == nullptr)
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL;
+
+ double seconds = atof(token); // Get SS.ssssss
+ *coordinate = decimal + (minutes / (double)60) + (seconds / (double)3600);
+ if (negativeSign)
+ *coordinate *= -1;
}
- else
+ else if (spaceCount == 0) // DD.dddddd
{
- ledcWrite(ledRedChannel, 10);
- ledcWrite(ledGreenChannel, 0);
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD;
+ sscanf(userEntry, "%lf", coordinate); // Load float from userEntry into coordinate
+ if (negativeSign)
+ *coordinate *= -1;
}
- }
-}
+ else if (spaceCount == 1) // DD MM.mmmmmmm
+ {
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD_MM;
+
+ char *token = strtok(userEntry, " "); // Modifies the given array
+ // We trust that token points at something because the spaceCount is > 0
+ int decimal = atoi(token); // Get DD
+ token = strtok(nullptr, " ");
+ double minutes = atof(token); // Get MM.mmmmmmm
+ *coordinate = decimal + (minutes / 60.0);
+ if (negativeSign)
+ *coordinate *= -1;
+ }
+ else if (spaceCount == 2) // DD MM SS.ssssss or DD MM SS
+ {
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD_MM_SS;
-//Ping an I2C device and see if it responds
-bool isConnected(uint8_t deviceAddress)
-{
- Wire.beginTransmission(deviceAddress);
- if (Wire.endTransmission() == 0)
- return true;
- return false;
-}
+ char *token = strtok(userEntry, " "); // Modifies the given array
+ // We trust that token points at something because the spaceCount is > 0
+ int decimal = atoi(token); // Get DD
+ token = strtok(nullptr, " ");
+ int minutes = atoi(token); // Get MM
+ token = strtok(nullptr, " ");
-//Given text, a position, and kerning, print text to display
-//This is helpful for squishing or stretching a string to appropriately fill the display
-void printTextwithKerning(char *newText, uint8_t xPos, uint8_t yPos, uint8_t kerning)
-{
- for (int x = 0 ; x < strlen(newText) ; x++)
- {
- oled.setCursor(xPos, yPos);
- oled.print(newText[x]);
- xPos += kerning;
- }
-}
-//Create a test file in file structure to make sure we can
-bool createTestFile()
-{
- SdFile testFile;
- char testFileName[40] = "testfile.txt";
+ // Find '.'
+ char *decimalPtr = strchr(token, '.');
+ if (decimalPtr == nullptr)
+ coordinateInputType = COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL;
- //Attempt to write to file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile() and F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_shortWait_ms) == pdPASS)
- {
- if (testFile.open(testFileName, O_CREAT | O_APPEND | O_WRITE) == true)
- {
- testFile.close();
+ double seconds = atof(token); // Get SS.ssssss
- if (sd.exists(testFileName))
- sd.remove(testFileName);
- xSemaphoreGive(xFATSemaphore);
- return (true);
+ *coordinate = decimal + (minutes / (double)60) + (seconds / (double)3600);
+ if (negativeSign)
+ *coordinate *= -1;
}
- xSemaphoreGive(xFATSemaphore);
- }
- return (false);
+ return (coordinateInputType);
}
-//If debug option is on, print available heap
-void reportHeap()
+// Given a coordinate and input type, output a string
+// So DD.ddddddddd can become 'DD MM SS.ssssss', etc
+void coordinateConvertInput(double coordinate, CoordinateInputType coordinateInputType, char *coordinateString,
+ int sizeOfCoordinateString)
{
- if (settings.enableHeapReport == true)
- {
- if (millis() - lastHeapReport > 1000)
+ if (coordinateInputType == COORDINATE_INPUT_TYPE_DD)
{
- lastHeapReport = millis();
- Serial.printf("FreeHeap: %d / HeapLowestPoint: %d / LargestBlock: %d\n\r", ESP.getFreeHeap(), xPortGetMinimumEverFreeHeapSize(), heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
+ snprintf(coordinateString, sizeOfCoordinateString, "%0.9f", coordinate);
}
- }
-}
-
-//Based on current LED state, blink upwards fashion
-//Used to indicate casting
-void cyclePositionLEDs()
-{
- if (productVariant == RTK_SURVEYOR)
- {
- //Cycle position LEDs to indicate casting
- if (millis() - lastCasterLEDupdate > 500)
- {
- lastCasterLEDupdate = millis();
- if (digitalRead(pin_positionAccuracyLED_100cm) == HIGH)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
- else if (digitalRead(pin_positionAccuracyLED_10cm) == HIGH)
- {
- digitalWrite(pin_positionAccuracyLED_1cm, HIGH);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, LOW);
- }
- else //Catch all
- {
- digitalWrite(pin_positionAccuracyLED_1cm, LOW);
- digitalWrite(pin_positionAccuracyLED_10cm, LOW);
- digitalWrite(pin_positionAccuracyLED_100cm, HIGH);
- }
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM || coordinateInputType == COORDINATE_INPUT_TYPE_DDMM ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_DASH ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SYMBOL)
+ {
+ int longitudeDegrees = (int)coordinate;
+ coordinate -= longitudeDegrees;
+ coordinate *= 60;
+ if (coordinate < 1)
+ coordinate *= -1;
+
+ if (coordinateInputType == COORDINATE_INPUT_TYPE_DDMM)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d%010.7f", longitudeDegrees, coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_DASH)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d-%010.7f", longitudeDegrees, coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SYMBOL)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d°%010.7f'", longitudeDegrees, coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d %010.7f", longitudeDegrees, coordinate);
+ }
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DDMMSS ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_DASH ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_SYMBOL ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL ||
+ coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL)
+ {
+ int longitudeDegrees = (int)coordinate;
+ coordinate -= longitudeDegrees;
+ coordinate *= 60;
+ if (coordinate < 1)
+ coordinate *= -1;
+
+ int longitudeMinutes = (int)coordinate;
+ coordinate -= longitudeMinutes;
+ coordinate *= 60;
+ if (coordinateInputType == COORDINATE_INPUT_TYPE_DDMMSS)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d%02d%09.6f", longitudeDegrees, longitudeMinutes,
+ coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_DASH)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d-%02d-%09.6f", longitudeDegrees, longitudeMinutes,
+ coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_SYMBOL)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d°%02d'%09.6f\"", longitudeDegrees, longitudeMinutes,
+ coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d %02d %09.6f", longitudeDegrees, longitudeMinutes,
+ coordinate);
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d%02d%02d", longitudeDegrees, longitudeMinutes,
+ (int)round(coordinate));
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d %02d %02d", longitudeDegrees, longitudeMinutes,
+ (int)round(coordinate));
+ else if (coordinateInputType == COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL)
+ snprintf(coordinateString, sizeOfCoordinateString, "%02d-%02d-%02d", longitudeDegrees, longitudeMinutes,
+ (int)round(coordinate));
+ }
+ else
+ {
+ log_d("Unknown coordinate input type");
}
- }
}
-
-//Set the port of the 1:4 dual channel analog mux
-//This allows NMEA, I2C, PPS/Event, and ADC/DAC to be routed through data port via software select
-void setMuxport(int channelNumber)
+// Given an input type, return a printable string
+const char *coordinatePrintableInputType(CoordinateInputType coordinateInputType)
{
- if (productVariant == RTK_EXPRESS)
- {
- pinMode(pin_muxA, OUTPUT);
- pinMode(pin_muxB, OUTPUT);
-
- if (channelNumber > 3) return; //Error check
-
- switch (channelNumber)
+ switch (coordinateInputType)
{
- case 0:
- digitalWrite(pin_muxA, LOW);
- digitalWrite(pin_muxB, LOW);
+ default:
+ return ("Unknown");
+ break;
+ case (COORDINATE_INPUT_TYPE_DD):
+ return ("DD.ddddddddd");
+ break;
+ case (COORDINATE_INPUT_TYPE_DDMM):
+ return ("DDMM.mmmmmmm");
break;
- case 1:
- digitalWrite(pin_muxA, HIGH);
- digitalWrite(pin_muxB, LOW);
+ case (COORDINATE_INPUT_TYPE_DD_MM):
+ return ("DD MM.mmmmmmm");
break;
- case 2:
- digitalWrite(pin_muxA, LOW);
- digitalWrite(pin_muxB, HIGH);
+ case (COORDINATE_INPUT_TYPE_DD_MM_DASH):
+ return ("DD-MM.mmmmmmm");
break;
- case 3:
- digitalWrite(pin_muxA, HIGH);
- digitalWrite(pin_muxB, HIGH);
+ case (COORDINATE_INPUT_TYPE_DD_MM_SYMBOL):
+ return ("DD°MM.mmmmmmm'");
+ break;
+ case (COORDINATE_INPUT_TYPE_DDMMSS):
+ return ("DDMMSS.ssssss");
+ break;
+ case (COORDINATE_INPUT_TYPE_DD_MM_SS):
+ return ("DD MM SS.ssssss");
+ break;
+ case (COORDINATE_INPUT_TYPE_DD_MM_SS_DASH):
+ return ("DD-MM-SS.ssssss");
+ break;
+ case (COORDINATE_INPUT_TYPE_DD_MM_SS_SYMBOL):
+ return ("DD°MM'SS.ssssss\"");
+ break;
+ case (COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL):
+ return ("DDMMSS");
+ break;
+ case (COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL):
+ return ("DD MM SS");
+ break;
+ case (COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL):
+ return ("DD-MM-SS");
break;
}
- }
+ return ("Unknown");
}
-boolean SFE_UBLOX_GNSS_ADD::getModuleInfo(uint16_t maxWait)
+// Print the error message every 15 seconds
+void reportFatalError(const char *errorMsg)
{
- i2cGNSS.minfo.hwVersion[0] = 0;
- i2cGNSS.minfo.swVersion[0] = 0;
- for (int i = 0; i < 10; i++)
- i2cGNSS.minfo.extension[i][0] = 0;
- i2cGNSS.minfo.extensionNo = 0;
-
- // Let's create our custom packet
- uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data bytes
-
- // The next line creates and initialises the packet information which wraps around the payload
- ubxPacket customCfg = {0, 0, 0, 0, 0, customPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
-
- customCfg.cls = UBX_CLASS_MON; // This is the message Class
- customCfg.id = UBX_MON_VER; // This is the message ID
- customCfg.len = 0; // Setting the len (length) to zero let's us poll the current settings
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
-
- // Now let's send the command. The module info is returned in customPayload
-
- if (sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED)
- return (false); //If command send fails then bail
-
- // Now let's extract the module info from customPayload
-
- uint16_t position = 0;
- for (int i = 0; i < 30; i++)
- {
- minfo.swVersion[i] = customPayload[position];
- position++;
- }
- for (int i = 0; i < 10; i++)
- {
- minfo.hwVersion[i] = customPayload[position];
- position++;
- }
-
- while (customCfg.len >= position + 30)
- {
- for (int i = 0; i < 30; i++)
- {
- minfo.extension[minfo.extensionNo][i] = customPayload[position];
- position++;
- }
- minfo.extensionNo++;
- if (minfo.extensionNo > 9)
- break;
- }
-
- return (true); //Success!
-}
-
-//Create $GNTXT, type message complete with CRC
-//https://www.nmea.org/Assets/20160520%20txt%20amendment.pdf
-//Used for reporting a system reboot inside the log
-void createNMEASentence(uint8_t sentenceNumber, uint8_t textID, char *nmeaMessage, char *textMessage)
-{
- char nmeaTxt[82]; //Max NMEA sentence length is 82
- sprintf(nmeaTxt, "$GNTXT,01,%02d,%02d,%s*", sentenceNumber, textID, textMessage);
-
- //From: http://engineeringnotes.blogspot.com/2015/02/generate-crc-for-nmea-strings-arduino.html
- byte CRC = 0; // XOR chars between '$' and '*'
- for (byte x = 1 ; x < strlen(nmeaTxt) - 1; x++)
- CRC = CRC ^ nmeaTxt[x];
-
- sprintf(nmeaMessage, "%s%02X", nmeaTxt, CRC);
+ while (1)
+ {
+ systemPrint("HALTED: ");
+ systemPrint(errorMsg);
+ systemPrintln();
+ sleep(15);
+ }
}
diff --git a/Firmware/RTK_Surveyor/Tasks.ino b/Firmware/RTK_Surveyor/Tasks.ino
index fc55a0247..ae80edfad 100644
--- a/Firmware/RTK_Surveyor/Tasks.ino
+++ b/Firmware/RTK_Surveyor/Tasks.ino
@@ -1,148 +1,1767 @@
-//High frequency tasks made by createTask()
-//And any low frequency tasks that are called by Ticker
-
-//If the phone has any new data (NTRIP RTCM, etc), read it in over Bluetooth and pass along to ZED
-//Task for writing to the GNSS receiver
-void F9PSerialWriteTask(void *e)
-{
- while (true)
- {
-#ifdef COMPILE_BT
- //Receive RTCM corrections or UBX config messages over bluetooth and pass along to ZED
- while (SerialBT.available())
- {
- taskYIELD();
- if (inTestMode == false)
- {
- //Pass bytes to GNSS receiver
- auto s = SerialBT.readBytes(wBuffer, SERIAL_SIZE_RX);
- serialGNSS.write(wBuffer, s);
+/*------------------------------------------------------------------------------
+Tasks.ino
+
+ This module implements the high frequency tasks made by xTaskCreate() and any
+ low frequency tasks that are called by Ticker.
+
+ GNSS
+ |
+ v
+ .--------+--------.
+ | |
+ v v
+ SPI or I2C
+ | |
+ | |
+ '------->+<-------'
+ |
+ | gnssReadTask
+ | gpsMessageParserFirstByte
+ | ...
+ | processUart1Message
+ |
+ v
+ Ring Buffer
+ |
+ | handleGnssDataTask
+ |
+ v
+ .---------------+-------+-------+---------------+
+ | | | |
+ | | | |
+ v v v v
+ Bluetooth PVT Client PVT Server SD Card
+
+------------------------------------------------------------------------------*/
+
+//----------------------------------------
+// Macros
+//----------------------------------------
+
+#define WRAP_OFFSET(offset, increment, arraySize) \
+ { \
+ offset += increment; \
+ if (offset >= arraySize) \
+ offset -= arraySize; \
+ }
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+enum RingBufferConsumers
+{
+ RBC_BLUETOOTH = 0,
+ RBC_PVT_CLIENT,
+ RBC_PVT_SERVER,
+ RBC_SD_CARD,
+ RBC_PVT_UDP_SERVER,
+ // Insert new consumers here
+ RBC_MAX
+};
+
+const char *const ringBufferConsumer[] = {
+ "Bluetooth", "PVT Client", "PVT Server", "SD Card", "PVT UDP Server",
+};
+
+const int ringBufferConsumerEntries = sizeof(ringBufferConsumer) / sizeof(ringBufferConsumer[0]);
+
+//----------------------------------------
+// Locals
+//----------------------------------------
+
+volatile static RING_BUFFER_OFFSET dataHead; // Head advances as data comes in from GNSS's UART
+volatile int32_t availableHandlerSpace; // settings.gnssHandlerBufferSize - usedSpace
+volatile const char *slowConsumer;
+
+// Buffer the incoming Bluetooth stream so that it can be passed in bulk over I2C
+uint8_t bluetoothOutgoingToZed[100];
+uint16_t bluetoothOutgoingToZedHead;
+unsigned long lastZedI2CSend; // Timestamp of the last time we sent RTCM ZED over I2C
+
+// Ring buffer tails
+static RING_BUFFER_OFFSET btRingBufferTail; // BT Tail advances as it is sent over BT
+static RING_BUFFER_OFFSET sdRingBufferTail; // SD Tail advances as it is recorded to SD
+
+// Ring buffer offsets
+static uint16_t rbOffsetHead;
+
+//----------------------------------------
+// Task routines
+//----------------------------------------
+
+// If the phone has any new data (NTRIP RTCM, etc), read it in over Bluetooth and pass along to ZED
+// Scan for escape characters to enter config menu
+void btReadTask(void *e)
+{
+ int rxBytes;
+
+ while (true)
+ {
+ // Display an alive message
+ if (PERIODIC_DISPLAY(PD_TASK_BLUETOOTH_READ))
+ {
+ PERIODIC_CLEAR(PD_TASK_BLUETOOTH_READ);
+ systemPrintln("btReadTask running");
+ }
+
+ // Receive RTCM corrections or UBX config messages over bluetooth and pass along to ZED
+ rxBytes = 0;
+ if (bluetoothGetState() == BT_CONNECTED)
+ {
+ while (btPrintEcho == false && bluetoothRxDataAvailable())
+ {
+ // Check stream for command characters
+ byte incoming = bluetoothRead();
+ rxBytes += 1;
+
+ if (incoming == btEscapeCharacter)
+ {
+ // Ignore escape characters received within 2 seconds of serial traffic
+ // Allow escape characters received within first 2 seconds of power on
+ if (millis() - btLastByteReceived > btMinEscapeTime || millis() < btMinEscapeTime)
+ {
+ btEscapeCharsReceived++;
+ if (btEscapeCharsReceived == btMaxEscapeCharacters)
+ {
+ printEndpoint = PRINT_ENDPOINT_ALL;
+ systemPrintln("Echoing all serial to BT device");
+ btPrintEcho = true;
+
+ btEscapeCharsReceived = 0;
+ btLastByteReceived = millis();
+ }
+ }
+ else
+ {
+ // Ignore this escape character, passing along to output
+ if (USE_I2C_GNSS)
+ {
+ // serialGNSS.write(incoming);
+ addToZedI2CBuffer(btEscapeCharacter);
+ }
+ else
+ theGNSS.pushRawData(&incoming, 1);
+ }
+ }
+ else // This is just a character in the stream, ignore
+ {
+ // Pass any escape characters that turned out to not be a complete escape sequence
+ while (btEscapeCharsReceived-- > 0)
+ {
+ if (USE_I2C_GNSS)
+ {
+ // serialGNSS.write(btEscapeCharacter);
+ addToZedI2CBuffer(btEscapeCharacter);
+ }
+ else
+ {
+ uint8_t escChar = btEscapeCharacter;
+ theGNSS.pushRawData(&escChar, 1);
+ }
+ }
+
+ // Pass byte to GNSS receiver or to system
+ // TODO - control if this RTCM source should be listened to or not
+ if (USE_I2C_GNSS)
+ {
+ // UART RX can be corrupted by UART TX
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/469
+ // serialGNSS.write(incoming);
+ addToZedI2CBuffer(incoming);
+ }
+ else
+ theGNSS.pushRawData(&incoming, 1);
+
+ btLastByteReceived = millis();
+ btEscapeCharsReceived = 0; // Update timeout check for escape char and partial frame
+
+ bluetoothIncomingRTCM = true;
+
+ // Record the arrival of RTCM from the Bluetooth connection (a phone or tablet is providing the RTCM
+ // via NTRIP). This resets the RTCM timeout used on the L-Band.
+ rtcmLastPacketReceived = millis();
+
+ } // End just a character in the stream
+
+ } // End btPrintEcho == false && bluetoothRxDataAvailable()
+
+ if (PERIODIC_DISPLAY(PD_BLUETOOTH_DATA_RX))
+ {
+ PERIODIC_CLEAR(PD_BLUETOOTH_DATA_RX);
+ systemPrintf("Bluetooth received %d bytes\r\n", rxBytes);
+ }
+ } // End bluetoothGetState() == BT_CONNECTED
+
+ if (bluetoothOutgoingToZedHead > 0 && ((millis() - lastZedI2CSend) > 100))
+ {
+ sendZedI2CBuffer(); // Send any outstanding RTCM
+ }
if (settings.enableTaskReports == true)
- Serial.printf("SerialWriteTask High watermark: %d\n\r", uxTaskGetStackHighWaterMark(NULL));
- }
- else
- {
- char incoming = SerialBT.read();
- Serial.printf("I heard: %c\n", incoming);
- incomingBTTest = incoming; //Displayed during system test
- }
+ systemPrintf("SerialWriteTask High watermark: %d\r\n", uxTaskGetStackHighWaterMark(nullptr));
+
+ feedWdt();
+ taskYIELD();
+ } // End while(true)
+}
+
+// Add byte to buffer that will be sent to ZED
+// We cannot write single characters to the ZED over I2C (as this will change the address pointer)
+void addToZedI2CBuffer(uint8_t incoming)
+{
+ bluetoothOutgoingToZed[bluetoothOutgoingToZedHead] = incoming;
+
+ bluetoothOutgoingToZedHead++;
+ if (bluetoothOutgoingToZedHead == sizeof(bluetoothOutgoingToZed))
+ {
+ sendZedI2CBuffer();
+ }
+}
+
+// Push the buffered data in bulk to the GNSS over I2C
+bool sendZedI2CBuffer()
+{
+ bool response = theGNSS.pushRawData(bluetoothOutgoingToZed, bluetoothOutgoingToZedHead);
+
+ if (response == true)
+ {
+ if (PERIODIC_DISPLAY(PD_ZED_DATA_TX))
+ {
+ PERIODIC_CLEAR(PD_ZED_DATA_TX);
+ systemPrintf("ZED TX: Sending %d bytes from I2C\r\n", bluetoothOutgoingToZedHead);
+ }
+ // log_d("Pushed %d bytes RTCM to ZED", bluetoothOutgoingToZedHead);
}
-#endif
- taskYIELD();
- }
+ // No matter the response, wrap the head and reset the timer
+ bluetoothOutgoingToZedHead = 0;
+ lastZedI2CSend = millis();
+ return (response);
+}
+
+// Normally a delay(1) will feed the WDT but if we don't want to wait that long, this feeds the WDT without delay
+void feedWdt()
+{
+ vTaskDelay(1);
}
-//If the ZED has any new NMEA data, pass it out over Bluetooth
-//Task for reading data from the GNSS receiver.
-void F9PSerialReadTask(void *e)
+//----------------------------------------------------------------------
+// The ESP32<->ZED-F9P serial connection is default 230,400bps to facilitate
+// 10Hz fix rate with PPP Logging Defaults (NMEAx5 + RXMx2) messages enabled.
+// ESP32 UART2 is begun with settings.uartReceiveBufferSize size buffer. The circular buffer
+// is 1024*6. At approximately 46.1K characters/second, a 6144 * 2
+// byte buffer should hold 267ms worth of serial data. Assuming SD writes are
+// 250ms worst case, we should record incoming all data. Bluetooth congestion
+// or conflicts with the SD card semaphore should clear within this time.
+//
+// Ring buffer empty when all the tails == dataHead
+//
+// +---------+
+// | |
+// | |
+// | |
+// | |
+// +---------+ <-- dataHead, btRingBufferTail, sdRingBufferTail, etc.
+//
+// Ring buffer contains data when any tail != dataHead
+//
+// +---------+
+// | |
+// | |
+// | yyyyyyy | <-- dataHead
+// | xxxxxxx | <-- btRingBufferTail (1 byte in buffer)
+// +---------+ <-- sdRingBufferTail (2 bytes in buffer)
+//
+// +---------+
+// | yyyyyyy | <-- btRingBufferTail (1 byte in buffer)
+// | xxxxxxx | <-- sdRingBufferTail (2 bytes in buffer)
+// | |
+// | |
+// +---------+ <-- dataHead
+//
+// Maximum ring buffer fill is settings.gnssHandlerBufferSize - 1
+//----------------------------------------------------------------------
+
+// Read bytes from ZED-F9P UART1 into ESP32 circular buffer
+// If data is coming in at 230,400bps = 23,040 bytes/s = one byte every 0.043ms
+// If SD blocks for 150ms (not extraordinary) that is 3,488 bytes that must be buffered
+// The ESP32 Arduino FIFO is ~120 bytes by default but overridden to 50 bytes (see pinUART2Task() and
+// uart_set_rx_full_threshold()). We use this task to harvest from FIFO into circular buffer during SD write blocking
+// time.
+void gnssReadTask(void *e)
{
- while (true)
- {
- while (serialGNSS.available())
+ static PARSE_STATE parse = {gpsMessageParserFirstByte, processUart1Message, "Log"};
+
+ uint8_t incomingData = 0;
+
+ while (true)
{
- auto s = serialGNSS.readBytes(rBuffer, SERIAL_SIZE_RX);
+ // Display an alive message
+ if (PERIODIC_DISPLAY(PD_TASK_GNSS_READ))
+ {
+ PERIODIC_CLEAR(PD_TASK_GNSS_READ);
+ systemPrintln("gnssReadTask running");
+ }
+
+ if (settings.enableTaskReports == true)
+ systemPrintf("SerialReadTask High watermark: %d\r\n", uxTaskGetStackHighWaterMark(nullptr));
- //If we are actively survey-in then do not pass NMEA data from ZED to phone
- if (systemState == STATE_BASE_TEMP_SETTLE || systemState == STATE_BASE_TEMP_SURVEY_STARTED)
- {
- //Do nothing
+ // Determine if serial data is available
+ if (USE_I2C_GNSS)
+ {
+ while (serialGNSS.available())
+ {
+ // Read the data from UART1
+ uint8_t incomingData[500];
+ int bytesIncoming = serialGNSS.read(incomingData, sizeof(incomingData));
+
+ for (int x = 0; x < bytesIncoming; x++)
+ {
+ // Save the data byte
+ parse.buffer[parse.length++] = incomingData[x];
+ parse.length %= PARSE_BUFFER_LENGTH;
+
+ // Compute the CRC value for the message
+ if (parse.computeCrc)
+ parse.crc = COMPUTE_CRC24Q(&parse, incomingData[x]);
+
+ // Update the parser state based on the incoming byte
+ parse.state(&parse, incomingData[x]);
+ }
+ }
+ }
+ else // SPI GNSS
+ {
+ theGNSS.checkUblox(); // Check for new data
+ while (theGNSS.fileBufferAvailable() > 0)
+ {
+ // Read the data from the logging buffer
+ theGNSS.extractFileBufferData(&incomingData,
+ 1); // TODO: make this more efficient by reading multiple bytes?
+
+ // Save the data byte
+ parse.buffer[parse.length++] = incomingData;
+ parse.length %= PARSE_BUFFER_LENGTH;
+
+ // Compute the CRC value for the message
+ if (parse.computeCrc)
+ parse.crc = COMPUTE_CRC24Q(&parse, incomingData);
+
+ // Update the parser state based on the incoming byte
+ parse.state(&parse, incomingData);
+ }
+ }
+
+ feedWdt();
taskYIELD();
- }
-#ifdef COMPILE_BT
- else if (SerialBT.connected())
- {
- if (SerialBT.isCongested() == false)
+ }
+}
+
+// Process a complete message incoming from parser
+// If we get a complete NMEA/UBX/RTCM message, pass on to SD/BT/PVT interfaces
+void processUart1Message(PARSE_STATE *parse, uint8_t type)
+{
+ int32_t bytesToCopy;
+ const char *consumer;
+ RING_BUFFER_OFFSET remainingBytes;
+ int32_t space;
+ int32_t use;
+
+ // Display the message
+ if ((settings.enablePrintLogFileMessages || PERIODIC_DISPLAY(PD_ZED_DATA_RX)) && (!parse->crc) && (!inMainMenu))
+ {
+ PERIODIC_CLEAR(PD_ZED_DATA_RX);
+ if (settings.enablePrintLogFileMessages)
{
- SerialBT.write(rBuffer, s); //Push new data to BT SPP
+ printTimeStamp();
+ systemPrint(" ");
}
- else if (settings.throttleDuringSPPCongestion == false)
+ else
+ systemPrint("ZED RX: ");
+ switch (type)
+ {
+ case SENTENCE_TYPE_NMEA:
+ systemPrintf("%s NMEA %s, %2d bytes\r\n", parse->parserName, parse->nmeaMessageName, parse->length);
+ break;
+
+ case SENTENCE_TYPE_RTCM:
+ systemPrintf("%s RTCM %d, %2d bytes\r\n", parse->parserName, parse->message, parse->length);
+ break;
+
+ case SENTENCE_TYPE_UBX:
+ systemPrintf("%s UBX %d.%d, %2d bytes\r\n", parse->parserName, parse->message >> 8, parse->message & 0xff,
+ parse->length);
+ break;
+ }
+ }
+
+ // Determine if this message will fit into the ring buffer
+ bytesToCopy = parse->length;
+ space = availableHandlerSpace;
+ use = settings.gnssHandlerBufferSize - space;
+ consumer = (char *)slowConsumer;
+ if ((bytesToCopy > space) && (!inMainMenu))
+ {
+ int32_t bufferedData;
+ int32_t bytesToDiscard;
+ int32_t discardedBytes;
+ int32_t listEnd;
+ int32_t messageLength;
+ int32_t offsetBytes;
+ int32_t previousTail;
+ int32_t rbOffsetTail;
+
+ // Determine the tail of the ring buffer
+ previousTail = dataHead + space + 1;
+ if (previousTail >= settings.gnssHandlerBufferSize)
+ previousTail -= settings.gnssHandlerBufferSize;
+
+ /* The rbOffsetArray holds the offsets into the ring buffer of the
+ * start of each of the parsed messages. A head (rbOffsetHead) and
+ * tail (rbOffsetTail) offsets are used for this array to insert and
+ * remove entries. Typically this task only manipulates the head as
+ * new messages are placed into the ring buffer. The handleGnssDataTask
+ * normally manipulates the tail as data is removed from the buffer.
+ * However this task will manipulate the tail under two conditions:
+ *
+ * 1. The ring buffer gets full and data must be discarded
+ *
+ * 2. The rbOffsetArray is too small to hold all of the message
+ * offsets for the data in the ring buffer. The array is full
+ * when (Head + 1) == Tail
+ *
+ * Notes:
+ * The rbOffsetArray is allocated along with the ring buffer in
+ * Begin.ino
+ *
+ * The first entry rbOffsetArray[0] is initialized to zero (0)
+ * in Begin.ino
+ *
+ * The array always has one entry in it containing the head offset
+ * which contains a valid offset into the ringBuffer, handled below
+ *
+ * The empty condition is Tail == Head
+ *
+ * The amount of data described by the rbOffsetArray is
+ * rbOffsetArray[Head] - rbOffsetArray[Tail]
+ *
+ * rbOffsetArray ringBuffer
+ * .-----------------. .-----------------.
+ * | | | |
+ * +-----------------+ | |
+ * Tail --> | Msg 1 Offset |---------->+-----------------+ <-- Tail n
+ * +-----------------+ | Msg 1 |
+ * | Msg 2 Offset |--------. | |
+ * +-----------------+ | | |
+ * | Msg 3 Offset |------. '->+-----------------+
+ * +-----------------+ | | Msg 2 |
+ * Head --> | Head Offset |--. | | |
+ * +-----------------+ | | | |
+ * | | | | | |
+ * +-----------------+ | | | |
+ * | | | '--->+-----------------+
+ * +-----------------+ | | Msg 3 |
+ * | | | | |
+ * +-----------------+ '------->+-----------------+ <-- dataHead
+ * | | | |
+ */
+
+ // Determine the index for the end of the circular ring buffer
+ // offset list
+ listEnd = rbOffsetHead;
+ WRAP_OFFSET(listEnd, 1, rbOffsetEntries);
+
+ // Update the tail, walk newest message to oldest message
+ rbOffsetTail = rbOffsetHead;
+ bufferedData = 0;
+ messageLength = 0;
+ while ((rbOffsetTail != listEnd) && (bufferedData < use))
+ {
+ // Determine the amount of data in the ring buffer up until
+ // either the tail or the end of the rbOffsetArray
+ //
+ // | |
+ // | | Valid, still in ring buffer
+ // | Newest |
+ // +-----------+ <-- rbOffsetHead
+ // | |
+ // | | free space
+ // | |
+ // rbOffsetTail --> +-----------+ <-- bufferedData
+ // | ring |
+ // | buffer | <-- used
+ // | data |
+ // +-----------+ Valid, still in ring buffer
+ // | |
+ //
+ messageLength = rbOffsetArray[rbOffsetTail];
+ WRAP_OFFSET(rbOffsetTail, rbOffsetEntries - 1, rbOffsetEntries);
+ messageLength -= rbOffsetArray[rbOffsetTail];
+ if (messageLength < 0)
+ messageLength += settings.gnssHandlerBufferSize;
+ bufferedData += messageLength;
+ }
+
+ // Account for any data in the ring buffer not described by the array
+ //
+ // | |
+ // +-----------+
+ // | Oldest |
+ // | |
+ // | ring |
+ // | buffer | <-- used
+ // | data |
+ // +-----------+ Valid, still in ring buffer
+ // | |
+ // rbOffsetTail --> +-----------+ <-- bufferedData
+ // | |
+ // | Newest |
+ // +-----------+ <-- rbOffsetHead
+ // | |
+ //
+ discardedBytes = 0;
+ if (bufferedData < use)
+ discardedBytes = use - bufferedData;
+
+ // Writing to the SD card, the network or Bluetooth, a partial
+ // message may be written leaving the tail pointer mid-message
+ //
+ // | |
+ // rbOffsetTail --> +-----------+
+ // | Oldest |
+ // | |
+ // | ring |
+ // | buffer | <-- used
+ // | data | Valid, still in ring buffer
+ // +-----------+ <--
+ // | |
+ // +-----------+
+ // | |
+ // | Newest |
+ // +-----------+ <-- rbOffsetHead
+ // | |
+ //
+ else if (bufferedData > use)
{
- SerialBT.write(rBuffer, s); //Push new data to SPP regardless of congestion
+ // Remove the remaining portion of the oldest entry in the array
+ discardedBytes = messageLength + use - bufferedData;
+ WRAP_OFFSET(rbOffsetTail, 1, rbOffsetEntries);
}
+
+ // rbOffsetTail now points to the beginning of a message in the
+ // ring buffer
+ // Determine the amount of data to discard
+ bytesToDiscard = discardedBytes;
+ if (bytesToDiscard < bytesToCopy)
+ bytesToDiscard = bytesToCopy;
+ if (bytesToDiscard < AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD)
+ bytesToDiscard = AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD;
+
+ // Walk the ring buffer messages from oldest to newest
+ while ((discardedBytes < bytesToDiscard) && (rbOffsetTail != rbOffsetHead))
+ {
+ // Determine the length of the oldest message
+ WRAP_OFFSET(rbOffsetTail, 1, rbOffsetEntries);
+ discardedBytes = rbOffsetArray[rbOffsetTail] - previousTail;
+ if (discardedBytes < 0)
+ discardedBytes += settings.gnssHandlerBufferSize;
+ }
+
+ // Discard the oldest data from the ring buffer
+ if (consumer)
+ systemPrintf("Ring buffer full: discarding %d bytes, %s is slow\r\n", discardedBytes, consumer);
else
+ systemPrintf("Ring buffer full: discarding %d bytes\r\n", discardedBytes);
+ updateRingBufferTails(previousTail, rbOffsetArray[rbOffsetTail]);
+ availableHandlerSpace += discardedBytes;
+ }
+
+ // Add another message to the ring buffer
+ // Account for this message
+ availableHandlerSpace -= bytesToCopy;
+
+ // Fill the buffer to the end and then start at the beginning
+ if ((dataHead + bytesToCopy) > settings.gnssHandlerBufferSize)
+ bytesToCopy = settings.gnssHandlerBufferSize - dataHead;
+
+ // Display the dataHead offset
+ if (settings.enablePrintRingBufferOffsets && (!inMainMenu))
+ systemPrintf("DH: %4d --> ", dataHead);
+
+ // Copy the data into the ring buffer
+ memcpy(&ringBuffer[dataHead], parse->buffer, bytesToCopy);
+ dataHead += bytesToCopy;
+ if (dataHead >= settings.gnssHandlerBufferSize)
+ dataHead -= settings.gnssHandlerBufferSize;
+
+ // Determine the remaining bytes
+ remainingBytes = parse->length - bytesToCopy;
+ if (remainingBytes)
+ {
+ // Copy the remaining bytes into the beginning of the ring buffer
+ memcpy(ringBuffer, &parse->buffer[bytesToCopy], remainingBytes);
+ dataHead += remainingBytes;
+ if (dataHead >= settings.gnssHandlerBufferSize)
+ dataHead -= settings.gnssHandlerBufferSize;
+ }
+
+ // Add the head offset to the offset array
+ WRAP_OFFSET(rbOffsetHead, 1, rbOffsetEntries);
+ rbOffsetArray[rbOffsetHead] = dataHead;
+
+ // Display the dataHead offset
+ if (settings.enablePrintRingBufferOffsets && (!inMainMenu))
+ systemPrintf("%4d\r\n", dataHead);
+}
+
+// Remove previous messages from the ring buffer
+void updateRingBufferTails(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail)
+{
+ // Trim any long or medium tails
+ discardRingBufferBytes(&btRingBufferTail, previousTail, newTail);
+ discardRingBufferBytes(&sdRingBufferTail, previousTail, newTail);
+ discardPvtClientBytes(previousTail, newTail);
+ discardPvtServerBytes(previousTail, newTail);
+ discardPvtUdpServerBytes(previousTail, newTail);
+}
+
+// Remove previous messages from the ring buffer
+void discardRingBufferBytes(RING_BUFFER_OFFSET *tail, RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET newTail)
+{
+ // The longest tail is being trimmed. Medium length tails may contain
+ // some data within the region begin trimmed. The shortest tails will
+ // be trimmed.
+ //
+ // Devices that get their tails trimmed, may output a partial message
+ // prior to the buffer trimming. After the trimming, the tail of the
+ // ring buffer points to the beginning of a new message.
+ //
+ // previousTail newTail
+ // | |
+ // Before trimming v Discarded v After trimming
+ // ----+----------------- ... -----+-- .. ---+-----------+------
+ // | Partial message | | |
+ // ----+----------------- ... -----+-- .. ---+-----------+------
+ // ^ ^ ^
+ // | | |
+ // long tail ----' '--- medium tail '-- short tail
+ //
+ // Determine if the trimmed data wraps the end of the buffer
+ if (previousTail < newTail)
+ {
+ // No buffer wrap occurred
+ // Only discard the data from long and medium tails
+ if ((*tail >= previousTail) && (*tail < newTail))
+ *tail = newTail;
+ }
+ else
+ {
+ // Buffer wrap occurred
+ if ((*tail >= previousTail) || (*tail < newTail))
+ *tail = newTail;
+ }
+}
+
+// If new data is in the ringBuffer, dole it out to appropriate interface
+// Send data out Bluetooth, record to SD, or send to network clients
+// Each device (Bluetooth, SD and network client) gets its own tail. If the
+// device is running too slowly then data for that device is dropped.
+// The usedSpace variable tracks the total space in use in the buffer.
+void handleGnssDataTask(void *e)
+{
+ int32_t bytesToSend;
+ bool connected;
+ uint32_t deltaMillis;
+ int32_t freeSpace;
+ uint16_t listEnd;
+ static uint32_t maxMillis[RBC_MAX];
+ uint32_t startMillis;
+ int32_t usedSpace;
+
+ // Initialize the tails
+ btRingBufferTail = 0;
+ pvtClientZeroTail();
+ pvtServerZeroTail();
+ pvtUdpServerZeroTail();
+ sdRingBufferTail = 0;
+
+ while (true)
+ {
+ // Display an alive message
+ if (PERIODIC_DISPLAY(PD_TASK_HANDLE_GNSS_DATA))
+ {
+ PERIODIC_CLEAR(PD_TASK_HANDLE_GNSS_DATA);
+ systemPrintln("handleGnssDataTask running");
+ }
+
+ usedSpace = 0;
+
+ //----------------------------------------------------------------------
+ // Send data over Bluetooth
+ //----------------------------------------------------------------------
+
+ startMillis = millis();
+
+ // Determine BT connection state
+ bool connected = (bluetoothGetState() == BT_CONNECTED) && (systemState != STATE_BASE_TEMP_SETTLE) &&
+ (systemState != STATE_BASE_TEMP_SURVEY_STARTED);
+ if (!connected)
+ // Discard the data
+ btRingBufferTail = dataHead;
+ else
+ {
+ // Determine the amount of Bluetooth data in the buffer
+ bytesToSend = dataHead - btRingBufferTail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (bytesToSend > 0)
+ {
+ // Reduce bytes to send if we have more to send then the end of
+ // the buffer, we'll wrap next loop
+ if ((btRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize)
+ bytesToSend = settings.gnssHandlerBufferSize - btRingBufferTail;
+
+ // If we are in the config menu, suppress data flowing from ZED to cell phone
+ if (btPrintEcho == false)
+ // Push new data to BT SPP
+ bytesToSend = bluetoothWrite(&ringBuffer[btRingBufferTail], bytesToSend);
+
+ // Account for the data that was sent
+ if (bytesToSend > 0)
+ {
+ // If we are in base mode, assume part of the outgoing data is RTCM
+ if (systemState >= STATE_BASE_NOT_STARTED && systemState <= STATE_BASE_FIXED_TRANSMITTING)
+ bluetoothOutgoingRTCM = true;
+
+ // Account for the sent or dropped data
+ btRingBufferTail += bytesToSend;
+ if (btRingBufferTail >= settings.gnssHandlerBufferSize)
+ btRingBufferTail -= settings.gnssHandlerBufferSize;
+
+ // Remember the maximum transfer time
+ deltaMillis = millis() - startMillis;
+ if (maxMillis[RBC_BLUETOOTH] < deltaMillis)
+ maxMillis[RBC_BLUETOOTH] = deltaMillis;
+
+ // Display the data movement
+ if (PERIODIC_DISPLAY(PD_BLUETOOTH_DATA_TX))
+ {
+ PERIODIC_CLEAR(PD_BLUETOOTH_DATA_TX);
+ systemPrintf("Bluetooth: %d bytes written\r\n", bytesToSend);
+ }
+ }
+ else
+ log_w("BT failed to send");
+
+ // Determine the amount of data that remains in the buffer
+ bytesToSend = dataHead - btRingBufferTail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (usedSpace < bytesToSend)
+ {
+ usedSpace = bytesToSend;
+ slowConsumer = "Bluetooth";
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Send data to the network clients
+ //----------------------------------------------------------------------
+
+ startMillis = millis();
+
+ // Update space available for use in UART task
+ bytesToSend = pvtClientSendData(dataHead);
+ if (usedSpace < bytesToSend)
{
- //Don't push data to BT SPP if there is congestion to prevent heap hits.
- log_d("Dropped SPP Bytes: %d", s);
+ usedSpace = bytesToSend;
+ slowConsumer = "PVT client";
}
- }
-#endif
- if (settings.enableTaskReports == true)
- Serial.printf("SerialReadTask High watermark: %d\n\r", uxTaskGetStackHighWaterMark(NULL));
+ // Remember the maximum transfer time
+ deltaMillis = millis() - startMillis;
+ if (maxMillis[RBC_PVT_CLIENT] < deltaMillis)
+ maxMillis[RBC_PVT_CLIENT] = deltaMillis;
+
+ startMillis = millis();
- //If user wants to log, record to SD
- if (online.logging == true)
- {
- //Check if we are inside the max time window for logging
- if ((systemTime_minutes - startLogTime_minutes) < settings.maxLogTime_minutes)
+ // Update space available for use in UART task
+ bytesToSend = pvtServerSendData(dataHead);
+ if (usedSpace < bytesToSend)
{
- //Attempt to write to file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_shortWait_ms) == pdPASS)
- {
- ubxFile.write(rBuffer, s);
+ usedSpace = bytesToSend;
+ slowConsumer = "PVT server";
+ }
+
+ // Remember the maximum transfer time
+ deltaMillis = millis() - startMillis;
+ if (maxMillis[RBC_PVT_SERVER] < deltaMillis)
+ maxMillis[RBC_PVT_SERVER] = deltaMillis;
+
+ startMillis = millis();
+
+ // Update space available for use in UART task
+ bytesToSend = pvtUdpServerSendData(dataHead);
+ if (usedSpace < bytesToSend)
+ {
+ usedSpace = bytesToSend;
+ slowConsumer = "PVT UDP server";
+ }
- //Force file sync every 5000ms
- if (millis() - lastUBXLogSyncTime > 5000)
+ // Remember the maximum transfer time
+ deltaMillis = millis() - startMillis;
+ if (maxMillis[RBC_PVT_UDP_SERVER] < deltaMillis)
+ maxMillis[RBC_PVT_UDP_SERVER] = deltaMillis;
+
+ //----------------------------------------------------------------------
+ // Log data to the SD card
+ //----------------------------------------------------------------------
+
+ // Determine if the SD card is enabled for logging
+ connected = online.logging && ((systemTime_minutes - startLogTime_minutes) < settings.maxLogTime_minutes);
+
+ // If user wants to log, record to SD
+ if (!connected)
+ // Discard the data
+ sdRingBufferTail = dataHead;
+ else
+ {
+ // Determine the amount of microSD card logging data in the buffer
+ bytesToSend = dataHead - sdRingBufferTail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (bytesToSend > 0)
{
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED)); //Blink LED to indicate logging activity
+ // Attempt to gain access to the SD card, avoids collisions with file
+ // writing from other functions like recordSystemSettingsToFile()
+ if (xSemaphoreTake(sdCardSemaphore, loggingSemaphoreWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_WRITESD);
+
+ // Reduce bytes to record if we have more then the end of the buffer
+ if ((sdRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize)
+ bytesToSend = settings.gnssHandlerBufferSize - sdRingBufferTail;
+
+ if (settings.enablePrintSDBuffers && (!inMainMenu))
+ {
+ int bufferAvailable;
+ if (USE_I2C_GNSS)
+ bufferAvailable = serialGNSS.available();
+ else
+ {
+ theGNSS.checkUblox();
+ bufferAvailable = theGNSS.fileBufferAvailable();
+ }
+ int availableUARTSpace;
+ if (USE_I2C_GNSS)
+ availableUARTSpace = settings.uartReceiveBufferSize - bufferAvailable;
+ else
+ // Use gnssHandlerBufferSize for now. TODO: work out if the SPI GNSS needs its own buffer
+ // size setting
+ availableUARTSpace = settings.gnssHandlerBufferSize - bufferAvailable;
+ systemPrintf("SD Incoming Serial: %04d\tToRead: %04d\tMovedToBuffer: %04d\tavailableUARTSpace: "
+ "%04d\tavailableHandlerSpace: %04d\tToRecord: %04d\tRecorded: %04d\tBO: %d\r\n",
+ bufferAvailable, 0, 0, availableUARTSpace, availableHandlerSpace, bytesToSend, 0,
+ bufferOverruns);
+ }
+
+ // Write the data to the file
+ long startTime = millis();
+ startMillis = millis();
+
+ bytesToSend = ubxFile->write(&ringBuffer[sdRingBufferTail], bytesToSend);
+ if (PERIODIC_DISPLAY(PD_SD_LOG_WRITE) && (bytesToSend > 0))
+ {
+ PERIODIC_CLEAR(PD_SD_LOG_WRITE);
+ systemPrintf("SD %d bytes written to log file\r\n", bytesToSend);
+ }
+
+ static unsigned long lastFlush = 0;
+ if (USE_MMC_MICROSD)
+ {
+ if (millis() > (lastFlush + 250)) // Flush every 250ms, not every write
+ {
+ ubxFile->flush();
+ lastFlush += 250;
+ }
+ }
+ fileSize = ubxFile->fileSize(); // Update file size
- long startWriteTime = micros();
- taskYIELD();
- ubxFile.sync();
- taskYIELD();
- long stopWriteTime = micros();
- totalWriteTime += stopWriteTime - startWriteTime; //Used to calculate overall write speed
+ sdFreeSpace -= bytesToSend; // Update remaining space on SD
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED)); //Return LED to previous state
+ // Force file sync every 60s
+ if (millis() - lastUBXLogSyncTime > 60000)
+ {
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_baseStatusLED,
+ !digitalRead(pin_baseStatusLED)); // Blink LED to indicate logging activity
- updateDataFileAccess(&ubxFile); // Update the file access time & date
+ ubxFile->sync();
+ ubxFile->updateFileAccessTimestamp(); // Update the file access time & date
- lastUBXLogSyncTime = millis();
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_baseStatusLED,
+ !digitalRead(pin_baseStatusLED)); // Return LED to previous state
+
+ lastUBXLogSyncTime = millis();
+ }
+
+ // Remember the maximum transfer time
+ deltaMillis = millis() - startMillis;
+ if (maxMillis[RBC_SD_CARD] < deltaMillis)
+ maxMillis[RBC_SD_CARD] = deltaMillis;
+ long endTime = millis();
+
+ if (settings.enablePrintBufferOverrun)
+ {
+ 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);
+ }
+
+ xSemaphoreGive(sdCardSemaphore);
+
+ // Account for the sent data or dropped
+ if (bytesToSend > 0)
+ {
+ sdRingBufferTail += bytesToSend;
+ if (sdRingBufferTail >= settings.gnssHandlerBufferSize)
+ sdRingBufferTail -= settings.gnssHandlerBufferSize;
+ }
+ } // End sdCardSemaphore
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+ log_w("sdCardSemaphore failed to yield for SD write, held by %s, Tasks.ino line %d",
+ semaphoreHolder, __LINE__);
+
+ delay(1); // Needed to prevent WDT resets during long Record Settings locks
+ taskYIELD();
+ }
+
+ // Update space available for use in UART task
+ bytesToSend = dataHead - sdRingBufferTail;
+ if (bytesToSend < 0)
+ bytesToSend += settings.gnssHandlerBufferSize;
+ if (usedSpace < bytesToSend)
+ {
+ usedSpace = bytesToSend;
+ slowConsumer = "SD card";
+ }
+ } // bytesToSend
+ } // End connected
+
+ //----------------------------------------------------------------------
+ // Update the available space in the ring buffer
+ //----------------------------------------------------------------------
+
+ freeSpace = settings.gnssHandlerBufferSize - usedSpace;
+
+ // Don't fill the last byte to prevent buffer overflow
+ if (freeSpace)
+ freeSpace -= 1;
+ availableHandlerSpace = freeSpace;
+
+ //----------------------------------------------------------------------
+ // Display the millisecond values for the different ring buffer consumers
+ //----------------------------------------------------------------------
+
+ if (PERIODIC_DISPLAY(PD_RING_BUFFER_MILLIS))
+ {
+ int milliseconds;
+ int seconds;
+
+ PERIODIC_CLEAR(PD_RING_BUFFER_MILLIS);
+ for (int index = 0; index < RBC_MAX; index++)
+ {
+ milliseconds = maxMillis[index];
+ if (milliseconds > 1)
+ {
+ seconds = milliseconds / MILLISECONDS_IN_A_SECOND;
+ milliseconds %= MILLISECONDS_IN_A_SECOND;
+ systemPrintf("%s: %d:%03d Sec\r\n", ringBufferConsumer[index], seconds, milliseconds);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Let other tasks run, prevent watch dog timer (WDT) resets
+ //----------------------------------------------------------------------
+
+ delay(1);
+ taskYIELD();
+ }
+}
+
+// Control BT status LED according to bluetoothGetState()
+void updateBTled()
+{
+ if (productVariant == RTK_SURVEYOR)
+ {
+ // Blink on/off while we wait for BT connection
+ if (bluetoothGetState() == BT_NOTCONNECTED)
+ {
+ if (btFadeLevel == 0)
+ btFadeLevel = 255;
+ else
+ btFadeLevel = 0;
+ ledcWrite(ledBTChannel, btFadeLevel);
+ }
+
+ // Solid LED if BT Connected
+ else if (bluetoothGetState() == BT_CONNECTED)
+ ledcWrite(ledBTChannel, 255);
+
+ // Pulse LED while no BT and we wait for WiFi connection
+ else if (wifiState == WIFI_STATE_CONNECTING || wifiState == WIFI_STATE_CONNECTED)
+ {
+ // Fade in/out the BT LED during WiFi AP mode
+ btFadeLevel += pwmFadeAmount;
+ if (btFadeLevel <= 0 || btFadeLevel >= 255)
+ pwmFadeAmount *= -1;
+
+ if (btFadeLevel > 255)
+ btFadeLevel = 255;
+ if (btFadeLevel < 0)
+ btFadeLevel = 0;
+
+ ledcWrite(ledBTChannel, btFadeLevel);
+ }
+ else
+ ledcWrite(ledBTChannel, 0);
+ }
+}
+
+// For RTK Express and RTK Facet, monitor momentary buttons
+void ButtonCheckTask(void *e)
+{
+ uint8_t index;
+
+ if (setupBtn != nullptr)
+ setupBtn->begin();
+ if (powerBtn != nullptr)
+ powerBtn->begin();
+
+ while (true)
+ {
+ // Display an alive message
+ if (PERIODIC_DISPLAY(PD_TASK_BUTTON_CHECK))
+ {
+ PERIODIC_CLEAR(PD_TASK_BUTTON_CHECK);
+ systemPrintln("ButtonCheckTask running");
+ }
+
+ /* RTK Surveyor
+
+ .----------------------------.
+ | |
+ V |
+ .------------------. |
+ | Power On | |
+ '------------------' |
+ | |
+ | Setup button = 0 |
+ V |
+ .------------------. |
+ .------>| Rover Mode | |
+ | '------------------' |
+ | | |
+ | | Setup button = 1 |
+ | V |
+ | .------------------. |
+ '-------| Base Mode | |
+ Setup button = 0 '------------------' |
+ after long time | | |
+ | | Setup button = 0 |
+ Setup button = 0 | | after short time |
+ after short time | | (< 500 mSec) |
+ (< 500 mSec) | | |
+ STATE_ROVER_NOT_STARTED | | |
+ V V |
+ .------------------. .------------------. |
+ | Test Mode | | WiFi Config Mode |----------'
+ '------------------' '------------------'
+
+ */
+
+ if (productVariant == RTK_SURVEYOR)
+ {
+ if (setupBtn &&
+ (settings.disableSetupButton == false)) // Allow check of the setup button if not overridden by settings
+ {
+ setupBtn->read();
+
+ // When switch is set to '1' = BASE, pin will be shorted to ground
+ if (setupBtn->isPressed()) // Switch is set to base mode
+ {
+ if (buttonPreviousState == BUTTON_ROVER)
+ {
+ lastRockerSwitchChange = millis(); // Record for WiFi AP access
+ buttonPreviousState = BUTTON_BASE;
+ requestChangeState(STATE_BASE_NOT_STARTED);
+ }
+ }
+ else if (setupBtn->wasReleased()) // Switch is set to Rover
+ {
+ if (buttonPreviousState == BUTTON_BASE)
+ {
+ buttonPreviousState = BUTTON_ROVER;
+
+ // If quick toggle is detected (less than 500ms), enter WiFi AP Config mode
+ if (millis() - lastRockerSwitchChange < 500)
+ {
+ if (systemState == STATE_ROVER_NOT_STARTED &&
+ online.display == true) // Catch during Power On
+ requestChangeState(STATE_TEST); // If RTK Surveyor, with display attached, during Rover
+ // not started, then enter test mode
+ else
+ requestChangeState(STATE_WIFI_CONFIG_NOT_STARTED);
+ }
+ else
+ {
+ requestChangeState(STATE_ROVER_NOT_STARTED);
+ }
+ }
+ }
+ }
+ }
+ else if (productVariant == RTK_EXPRESS ||
+ productVariant == RTK_EXPRESS_PLUS) // Express: Check both of the momentary switches
+ {
+ if (setupBtn != nullptr)
+ setupBtn->read();
+ if (powerBtn != nullptr)
+ powerBtn->read();
+
+ if (systemState == STATE_SHUTDOWN)
+ {
+ // Ignore button presses while shutting down
+ }
+ else if (powerBtn != nullptr && powerBtn->pressedFor(shutDownButtonTime))
+ {
+ forceSystemStateUpdate = true;
+ requestChangeState(STATE_SHUTDOWN);
+
+ if (inMainMenu)
+ powerDown(true); // State machine is not updated while in menu system so go straight to power down
+ // as needed
+ }
+ else if ((setupBtn != nullptr && setupBtn->pressedFor(500)) &&
+ (powerBtn != nullptr && powerBtn->pressedFor(500)))
+ {
+ forceSystemStateUpdate = true;
+ requestChangeState(STATE_TEST);
+ lastTestMenuChange = millis(); // Avoid exiting test menu for 1s
+ }
+ else if (setupBtn != nullptr && setupBtn->wasReleased())
+ {
+ if (settings.disableSetupButton ==
+ false) // Allow check of the setup button if not overridden by settings
+ {
+ switch (systemState)
+ {
+ // If we are in any running state, change to STATE_DISPLAY_SETUP
+ case STATE_ROVER_NOT_STARTED:
+ case STATE_ROVER_NO_FIX:
+ case STATE_ROVER_FIX:
+ case STATE_ROVER_RTK_FLOAT:
+ case STATE_ROVER_RTK_FIX:
+ case STATE_BASE_NOT_STARTED:
+ case STATE_BASE_TEMP_SETTLE:
+ case STATE_BASE_TEMP_SURVEY_STARTED:
+ case STATE_BASE_TEMP_TRANSMITTING:
+ case STATE_BASE_FIXED_NOT_STARTED:
+ case STATE_BASE_FIXED_TRANSMITTING:
+ case STATE_BUBBLE_LEVEL:
+ case STATE_WIFI_CONFIG_NOT_STARTED:
+ case STATE_WIFI_CONFIG:
+ case STATE_ESPNOW_PAIRING_NOT_STARTED:
+ case STATE_ESPNOW_PAIRING:
+ lastSystemState =
+ systemState; // Remember this state to return after we mark an event or ESP-Now pair
+ requestChangeState(STATE_DISPLAY_SETUP);
+ setupState = STATE_MARK_EVENT;
+ lastSetupMenuChange = millis();
+ break;
+
+ case STATE_MARK_EVENT:
+ // If the user presses the setup button during a mark event, do nothing
+ // Allow system to return to lastSystemState
+ break;
+
+ case STATE_PROFILE:
+ // If the user presses the setup button during a profile change, do nothing
+ // Allow system to return to lastSystemState
+ break;
+
+ case STATE_TEST:
+ // Do nothing. User is releasing the setup button.
+ break;
+
+ case STATE_TESTING:
+ // If we are in testing, return to Rover Not Started
+ requestChangeState(STATE_ROVER_NOT_STARTED);
+ break;
+
+ case STATE_DISPLAY_SETUP:
+ // If we are displaying the setup menu, cycle through possible system states
+ // Exit display setup and enter new system state after ~1500ms in updateSystemState()
+ lastSetupMenuChange = millis();
+
+ forceDisplayUpdate = true; // User is interacting so repaint display quickly
+
+ switch (setupState)
+ {
+ case STATE_MARK_EVENT:
+ setupState = STATE_ROVER_NOT_STARTED;
+ break;
+ case STATE_ROVER_NOT_STARTED:
+ // If F9R, skip base state
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ // If accel offline, skip bubble
+ if (online.accelerometer == true)
+ setupState = STATE_BUBBLE_LEVEL;
+ else
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ }
+ else
+ setupState = STATE_BASE_NOT_STARTED;
+ break;
+ case STATE_BASE_NOT_STARTED:
+ // If accel offline, skip bubble
+ if (online.accelerometer == true)
+ setupState = STATE_BUBBLE_LEVEL;
+ else
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ break;
+ case STATE_BUBBLE_LEVEL:
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ break;
+ case STATE_WIFI_CONFIG_NOT_STARTED:
+ setupState = STATE_ESPNOW_PAIRING_NOT_STARTED;
+ break;
+ case STATE_ESPNOW_PAIRING_NOT_STARTED:
+ // If only one active profile do not show any profiles
+ index = getProfileNumberFromUnit(0);
+ displayProfile = getProfileNumberFromUnit(1);
+ setupState = (index >= displayProfile) ? STATE_MARK_EVENT : STATE_PROFILE;
+ displayProfile = 0;
+ break;
+ case STATE_PROFILE:
+ // Done when no more active profiles
+ displayProfile++;
+ if (!getProfileNumberFromUnit(displayProfile))
+ setupState = STATE_MARK_EVENT;
+ break;
+ default:
+ systemPrintf("ButtonCheckTask unknown setup state: %d\r\n", setupState);
+ setupState = STATE_MARK_EVENT;
+ break;
+ }
+ break;
+
+ default:
+ systemPrintf("ButtonCheckTask unknown system state: %d\r\n", systemState);
+ requestChangeState(STATE_ROVER_NOT_STARTED);
+ break;
+ }
+ } // End disabdisableSetupButton check
+ }
+ } // End Platform = RTK Express
+ else if (productVariant == RTK_FACET || productVariant == RTK_FACET_LBAND ||
+ productVariant == RTK_FACET_LBAND_DIRECT) // Check one momentary button
+ {
+ if (powerBtn != nullptr)
+ powerBtn->read();
+
+ if (systemState == STATE_SHUTDOWN)
+ {
+ // Ignore button presses while shutting down
+ }
+ else if (powerBtn != nullptr && powerBtn->pressedFor(shutDownButtonTime))
+ {
+ forceSystemStateUpdate = true;
+ requestChangeState(STATE_SHUTDOWN);
+
+ if (inMainMenu)
+ powerDown(true); // State machine is not updated while in menu system so go straight to power down
+ // as needed
+ }
+ else if (powerBtn != nullptr &&
+ (systemState == STATE_ROVER_NOT_STARTED || systemState == STATE_KEYS_STARTED) &&
+ firstRoverStart == true && powerBtn->pressedFor(500))
+ {
+ forceSystemStateUpdate = true;
+ requestChangeState(STATE_TEST);
+ lastTestMenuChange = millis(); // Avoid exiting test menu for 1s
+ }
+ else if (powerBtn != nullptr && powerBtn->wasReleased() && firstRoverStart == false)
+ {
+ if (settings.disableSetupButton ==
+ false) // Allow check of the setup button if not overridden by settings
+ {
+ switch (systemState)
+ {
+ // If we are in any running state, change to STATE_DISPLAY_SETUP
+ case STATE_ROVER_NOT_STARTED:
+ case STATE_ROVER_NO_FIX:
+ case STATE_ROVER_FIX:
+ case STATE_ROVER_RTK_FLOAT:
+ case STATE_ROVER_RTK_FIX:
+ case STATE_BASE_NOT_STARTED:
+ case STATE_BASE_TEMP_SETTLE:
+ case STATE_BASE_TEMP_SURVEY_STARTED:
+ case STATE_BASE_TEMP_TRANSMITTING:
+ case STATE_BASE_FIXED_NOT_STARTED:
+ case STATE_BASE_FIXED_TRANSMITTING:
+ case STATE_BUBBLE_LEVEL:
+ case STATE_WIFI_CONFIG_NOT_STARTED:
+ case STATE_WIFI_CONFIG:
+ case STATE_ESPNOW_PAIRING_NOT_STARTED:
+ case STATE_ESPNOW_PAIRING:
+ lastSystemState =
+ systemState; // Remember this state to return after we mark an event or ESP-Now pair
+ requestChangeState(STATE_DISPLAY_SETUP);
+ setupState = STATE_MARK_EVENT;
+ lastSetupMenuChange = millis();
+ break;
+
+ case STATE_MARK_EVENT:
+ // If the user presses the setup button during a mark event, do nothing
+ // Allow system to return to lastSystemState
+ break;
+
+ case STATE_PROFILE:
+ // If the user presses the setup button during a profile change, do nothing
+ // Allow system to return to lastSystemState
+ break;
+
+ case STATE_TEST:
+ // Do nothing. User is releasing the setup button.
+ break;
+
+ case STATE_TESTING:
+ // If we are in testing, return to Rover Not Started
+ requestChangeState(STATE_ROVER_NOT_STARTED);
+ break;
+
+ case STATE_DISPLAY_SETUP:
+ // If we are displaying the setup menu, cycle through possible system states
+ // Exit display setup and enter new system state after ~1500ms in updateSystemState()
+ lastSetupMenuChange = millis();
+
+ forceDisplayUpdate = true; // User is interacting so repaint display quickly
+
+ switch (setupState)
+ {
+ case STATE_MARK_EVENT:
+ setupState = STATE_ROVER_NOT_STARTED;
+ break;
+ case STATE_ROVER_NOT_STARTED:
+ // If F9R, skip base state
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ // If accel offline, skip bubble
+ if (online.accelerometer == true)
+ setupState = STATE_BUBBLE_LEVEL;
+ else
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ }
+ else
+ setupState = STATE_BASE_NOT_STARTED;
+ break;
+ case STATE_BASE_NOT_STARTED:
+ // If accel offline, skip bubble
+ if (online.accelerometer == true)
+ setupState = STATE_BUBBLE_LEVEL;
+ else
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ break;
+ case STATE_BUBBLE_LEVEL:
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ break;
+ case STATE_WIFI_CONFIG_NOT_STARTED:
+ if (productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ lBandForceGetKeys = true;
+ setupState = STATE_KEYS_NEEDED;
+ }
+ else
+ setupState = STATE_ESPNOW_PAIRING_NOT_STARTED;
+ break;
+
+ case STATE_KEYS_NEEDED:
+ lBandForceGetKeys = false; // User has scrolled past the GetKeys option
+ setupState = STATE_ESPNOW_PAIRING_NOT_STARTED;
+ break;
+
+ case STATE_ESPNOW_PAIRING_NOT_STARTED:
+ // If only one active profile do not show any profiles
+ index = getProfileNumberFromUnit(0);
+ displayProfile = getProfileNumberFromUnit(1);
+ setupState = (index >= displayProfile) ? STATE_MARK_EVENT : STATE_PROFILE;
+ displayProfile = 0;
+ break;
+ case STATE_PROFILE:
+ // Done when no more active profiles
+ displayProfile++;
+ if (!getProfileNumberFromUnit(displayProfile))
+ setupState = STATE_MARK_EVENT;
+ break;
+ default:
+ systemPrintf("ButtonCheckTask unknown setup state: %d\r\n", setupState);
+ setupState = STATE_MARK_EVENT;
+ break;
+ }
+ break;
+
+ default:
+ systemPrintf("ButtonCheckTask unknown system state: %d\r\n", systemState);
+ requestChangeState(STATE_ROVER_NOT_STARTED);
+ break;
+ }
+ } // End disableSetupButton check
+ }
+ } // End Platform = RTK Facet
+ else if (productVariant == REFERENCE_STATION) // Check one momentary button
+ {
+ if (setupBtn != nullptr)
+ setupBtn->read();
+
+ if (systemState == STATE_SHUTDOWN)
+ {
+ // Ignore button presses while shutting down
+ }
+ else if (setupBtn != nullptr && setupBtn->pressedFor(shutDownButtonTime))
+ {
+ forceSystemStateUpdate = true;
+ requestChangeState(STATE_SHUTDOWN);
+
+ if (inMainMenu)
+ powerDown(true); // State machine is not updated while in menu system so go straight to power down
+ // as needed
+ }
+ else if (setupBtn != nullptr && systemState == STATE_BASE_NOT_STARTED && firstRoverStart == true &&
+ setupBtn->pressedFor(500))
+ {
+ forceSystemStateUpdate = true;
+ requestChangeState(STATE_TEST);
+ lastTestMenuChange = millis(); // Avoid exiting test menu for 1s
}
+ else if (setupBtn != nullptr && setupBtn->wasReleased() && firstRoverStart == false)
+ {
+ if (settings.disableSetupButton ==
+ false) // Allow check of the setup button if not overridden by settings
+ {
+
+ switch (systemState)
+ {
+ // If we are in any running state, change to STATE_DISPLAY_SETUP
+ case STATE_BASE_NOT_STARTED:
+ case STATE_BASE_TEMP_SETTLE:
+ case STATE_BASE_TEMP_SURVEY_STARTED:
+ case STATE_BASE_TEMP_TRANSMITTING:
+ case STATE_BASE_FIXED_NOT_STARTED:
+ case STATE_BASE_FIXED_TRANSMITTING:
+ case STATE_ROVER_NOT_STARTED:
+ case STATE_ROVER_NO_FIX:
+ case STATE_ROVER_FIX:
+ case STATE_ROVER_RTK_FLOAT:
+ case STATE_ROVER_RTK_FIX:
+ case STATE_NTPSERVER_NOT_STARTED:
+ case STATE_NTPSERVER_NO_SYNC:
+ case STATE_NTPSERVER_SYNC:
+ case STATE_WIFI_CONFIG_NOT_STARTED:
+ case STATE_WIFI_CONFIG:
+ case STATE_CONFIG_VIA_ETH_NOT_STARTED:
+ case STATE_ESPNOW_PAIRING_NOT_STARTED:
+ case STATE_ESPNOW_PAIRING:
+ lastSystemState = systemState; // Remember this state to return after ESP-Now pair
+ requestChangeState(STATE_DISPLAY_SETUP);
+ setupState = STATE_BASE_NOT_STARTED;
+ lastSetupMenuChange = millis();
+ break;
+
+ case STATE_CONFIG_VIA_ETH_STARTED:
+ case STATE_CONFIG_VIA_ETH:
+ // If the user presses the button during configure-via-ethernet, then do a complete restart into
+ // Base mode
+ requestChangeState(STATE_CONFIG_VIA_ETH_RESTART_BASE);
+ break;
+
+ case STATE_PROFILE:
+ // If the user presses the setup button during a profile change, do nothing
+ // Allow system to return to lastSystemState
+ break;
+
+ case STATE_TEST:
+ // Do nothing. User is releasing the setup button.
+ break;
- xSemaphoreGive(xFATSemaphore);
- } //End xFATSemaphore
- else
- {
- log_d("F9SerialRead: Semaphore failed to yield");
- }
- } //End maxLogTime
- } //End logging
+ case STATE_TESTING:
+ // If we are in testing, return to Base Not Started
+ requestChangeState(STATE_BASE_NOT_STARTED);
+ break;
- taskYIELD();
+ case STATE_DISPLAY_SETUP:
+ // If we are displaying the setup menu, cycle through possible system states
+ // Exit display setup and enter new system state after ~1500ms in updateSystemState()
+ lastSetupMenuChange = millis();
- } //End Serial.available()
+ forceDisplayUpdate = true; // User is interacting so repaint display quickly
- taskYIELD();
- }
+ switch (setupState)
+ {
+ case STATE_BASE_NOT_STARTED:
+ setupState = STATE_ROVER_NOT_STARTED;
+ break;
+ case STATE_ROVER_NOT_STARTED:
+ setupState = STATE_NTPSERVER_NOT_STARTED;
+ break;
+ case STATE_NTPSERVER_NOT_STARTED:
+ setupState = STATE_CONFIG_VIA_ETH_NOT_STARTED;
+ break;
+ case STATE_CONFIG_VIA_ETH_NOT_STARTED:
+ setupState = STATE_WIFI_CONFIG_NOT_STARTED;
+ break;
+ case STATE_WIFI_CONFIG_NOT_STARTED:
+ setupState = STATE_ESPNOW_PAIRING_NOT_STARTED;
+ break;
+ case STATE_ESPNOW_PAIRING_NOT_STARTED:
+ // If only one active profile do not show any profiles
+ index = getProfileNumberFromUnit(0);
+ displayProfile = getProfileNumberFromUnit(1);
+ setupState = (index >= displayProfile) ? STATE_BASE_NOT_STARTED : STATE_PROFILE;
+ displayProfile = 0;
+ break;
+ case STATE_PROFILE:
+ // Done when no more active profiles
+ displayProfile++;
+ if (!getProfileNumberFromUnit(displayProfile))
+ setupState = STATE_BASE_NOT_STARTED;
+ break;
+ case STATE_MARK_EVENT: // Skip the warning message if setupState is still in the default Mark
+ // Event state
+ setupState = STATE_BASE_NOT_STARTED;
+ break;
+ default:
+ systemPrintf("ButtonCheckTask unknown setup state: %d\r\n", setupState);
+ setupState = STATE_BASE_NOT_STARTED;
+ break;
+ }
+ break;
+
+ default:
+ systemPrintf("ButtonCheckTask unknown system state: %d\r\n", systemState);
+ requestChangeState(STATE_BASE_NOT_STARTED);
+ break;
+ }
+ } // End disableSetupButton check
+ }
+ } // End Platform = REFERENCE_STATION
+
+ delay(1); // Poor man's way of feeding WDT. Required to prevent Priority 1 tasks from causing WDT reset
+ taskYIELD();
+ }
}
-//Assign UART2 interrupts to the current core. See: https://github.com/espressif/arduino-esp32/issues/3386
-void startUART2Task( void *pvParameters )
+void idleTask(void *e)
{
- serialGNSS.begin(settings.dataPortBaud); //UART2 on pins 16/17 for SPP. The ZED-F9P will be configured to output NMEA over its UART1 at the same rate.
- serialGNSS.setRxBufferSize(SERIAL_SIZE_RX);
- serialGNSS.setTimeout(50);
+ int cpu = xPortGetCoreID();
+ uint32_t idleCount = 0;
+ uint32_t lastDisplayIdleTime = 0;
+ uint32_t lastStackPrintTime = 0;
- uart2Started = true;
+ while (1)
+ {
+ // Increment a count during the idle time
+ idleCount++;
+
+ // Determine if it is time to print the CPU idle times
+ if ((millis() - lastDisplayIdleTime) >= (IDLE_TIME_DISPLAY_SECONDS * 1000) && !inMainMenu)
+ {
+ lastDisplayIdleTime = millis();
+
+ // Get the idle time
+ if (idleCount > max_idle_count)
+ max_idle_count = idleCount;
+
+ // Display the idle times
+ if (settings.enablePrintIdleTime)
+ {
+ systemPrintf("CPU %d idle time: %d%% (%d/%d)\r\n", cpu, idleCount * 100 / max_idle_count, idleCount,
+ max_idle_count);
- vTaskDelete( NULL ); //Delete task once it has run once
+ // Print the task count
+ if (cpu)
+ systemPrintf("%d Tasks\r\n", uxTaskGetNumberOfTasks());
+ }
+
+ // Restart the idle count for the next display time
+ idleCount = 0;
+ }
+
+ // Display the high water mark if requested
+ if ((settings.enableTaskReports == true) &&
+ ((millis() - lastStackPrintTime) >= (IDLE_TIME_DISPLAY_SECONDS * 1000)))
+ {
+ lastStackPrintTime = millis();
+ systemPrintf("idleTask %d High watermark: %d\r\n", xPortGetCoreID(), uxTaskGetStackHighWaterMark(nullptr));
+ }
+
+ // The idle task should NOT delay or yield
+ }
}
-//Control BT status LED according to bluetoothState
-void updateBTled()
+// Serial Read/Write tasks for the F9P must be started after BT is up and running otherwise SerialBT->available will
+// cause reboot
+bool tasksStartUART2()
{
- if (productVariant == RTK_SURVEYOR)
- {
- if (radioState == BT_ON_NOCONNECTION)
- digitalWrite(pin_bluetoothStatusLED, !digitalRead(pin_bluetoothStatusLED));
- else if (radioState == BT_CONNECTED)
- digitalWrite(pin_bluetoothStatusLED, HIGH);
- else
- digitalWrite(pin_bluetoothStatusLED, LOW);
- }
+ // Verify that the ring buffer was successfully allocated
+ if (!ringBuffer)
+ {
+ systemPrintln("ERROR: Ring buffer allocation failure!");
+ systemPrintln("Decrease GNSS handler (ring) buffer size");
+ displayNoRingBuffer(5000);
+ return false;
+ }
+
+ availableHandlerSpace = settings.gnssHandlerBufferSize;
+
+ // Reads data from ZED and stores data into circular buffer
+ if (gnssReadTaskHandle == nullptr)
+ xTaskCreatePinnedToCore(gnssReadTask, // Function to call
+ "gnssRead", // Just for humans
+ gnssReadTaskStackSize, // Stack Size
+ nullptr, // Task input parameter
+ settings.gnssReadTaskPriority, // Priority
+ &gnssReadTaskHandle, // Task handle
+ settings.gnssReadTaskCore); // Core where task should run, 0=core, 1=Arduino
+
+ // Reads data from circular buffer and sends data to SD, SPP, or network clients
+ if (handleGnssDataTaskHandle == nullptr)
+ xTaskCreatePinnedToCore(handleGnssDataTask, // Function to call
+ "handleGNSSData", // Just for humans
+ handleGnssDataTaskStackSize, // Stack Size
+ nullptr, // Task input parameter
+ settings.handleGnssDataTaskPriority, // Priority
+ &handleGnssDataTaskHandle, // Task handle
+ settings.handleGnssDataTaskCore); // Core where task should run, 0=core, 1=Arduino
+
+ // Reads data from BT and sends to ZED
+ if (btReadTaskHandle == nullptr)
+ xTaskCreatePinnedToCore(btReadTask, // Function to call
+ "btRead", // Just for humans
+ btReadTaskStackSize, // Stack Size
+ nullptr, // Task input parameter
+ settings.btReadTaskPriority, // Priority
+ &btReadTaskHandle, // Task handle
+ settings.btReadTaskCore); // Core where task should run, 0=core, 1=Arduino
+ return true;
+}
+
+// Stop tasks - useful when running firmware update or WiFi AP is running
+void tasksStopUART2()
+{
+ // Delete tasks if running
+ if (gnssReadTaskHandle != nullptr)
+ {
+ vTaskDelete(gnssReadTaskHandle);
+ gnssReadTaskHandle = nullptr;
+ }
+ if (handleGnssDataTaskHandle != nullptr)
+ {
+ vTaskDelete(handleGnssDataTaskHandle);
+ handleGnssDataTaskHandle = nullptr;
+ }
+ if (btReadTaskHandle != nullptr)
+ {
+ vTaskDelete(btReadTaskHandle);
+ btReadTaskHandle = nullptr;
+ }
+
+ // Give the other CPU time to finish
+ // Eliminates CPU bus hang condition
+ delay(100);
+}
+
+// Checking the number of available clusters on the SD card can take multiple seconds
+// Rather than blocking the system, we run a background task
+// Once the size check is complete, the task is removed
+void sdSizeCheckTask(void *e)
+{
+ while (true)
+ {
+ // Display an alive message
+ if (PERIODIC_DISPLAY(PD_TASK_SD_SIZE_CHECK))
+ {
+ PERIODIC_CLEAR(PD_TASK_SD_SIZE_CHECK);
+ systemPrintln("sdSizeCheckTask running");
+ }
+
+ if (online.microSD && sdCardSize == 0)
+ {
+ // Attempt to gain access to the SD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_SDSIZECHECK);
+
+ if (USE_SPI_MICROSD)
+ {
+ csd_t csd;
+ sd->card()->readCSD(&csd); // Card Specific Data
+ sdCardSize = (uint64_t)512 * sd->card()->sectorCount();
+
+ sd->volumeBegin();
+
+ // Find available cluster/space
+ sdFreeSpace = sd->vol()->freeClusterCount(); // This takes a few seconds to complete
+ sdFreeSpace *= sd->vol()->sectorsPerCluster();
+ sdFreeSpace *= 512L; // Bytes per sector
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ sdCardSize = SD_MMC.cardSize();
+ sdFreeSpace = SD_MMC.totalBytes() - SD_MMC.usedBytes();
+ }
+#endif // COMPILE_SD_MMC
+
+ xSemaphoreGive(sdCardSemaphore);
+
+ // uint64_t sdUsedSpace = sdCardSize - sdFreeSpace; //Don't think of it as used, think of it as unusable
+
+ String cardSize;
+ stringHumanReadableSize(cardSize, sdCardSize);
+ String freeSpace;
+ stringHumanReadableSize(freeSpace, sdFreeSpace);
+ systemPrintf("SD card size: %s / Free space: %s\r\n", cardSize, freeSpace);
+
+ outOfSDSpace = false;
+
+ sdSizeCheckTaskComplete = true;
+ }
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+ log_d("sdCardSemaphore failed to yield, held by %s, Tasks.ino line %d\r\n", semaphoreHolder, __LINE__);
+ }
+ }
+
+ delay(1);
+ taskYIELD(); // Let other tasks run
+ }
+}
+
+// Validate the task table lengths
+void tasksValidateTables()
+{
+ if (ringBufferConsumerEntries != RBC_MAX)
+ reportFatalError("Fix ringBufferConsumer table to match RingBufferConsumers");
}
diff --git a/Firmware/RTK_Surveyor/W5500.ino b/Firmware/RTK_Surveyor/W5500.ino
new file mode 100644
index 000000000..092cdc327
--- /dev/null
+++ b/Firmware/RTK_Surveyor/W5500.ino
@@ -0,0 +1,160 @@
+#ifdef COMPILE_ETHERNET
+
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// Extra code for the W5500 (vs. w5100.h)
+
+const uint16_t w5500RTR = 0x0019; // Retry Count Register - Common Register Block
+const uint16_t w5500SIR = 0x0017; // Socket Interrupt Register - Common Register Block
+const uint16_t w5500SIMR = 0x0018; // Socket Interrupt Mask Register - Common Register Block
+const uint16_t w5500SnIR = 0x0002; // Socket n Interrupt Register - Socket Register Block
+const uint16_t w5500SnIMR = 0x002C; // Socket n Interrupt Mask Register - Socket Register Block
+
+const uint8_t w5500CommonRegister = 0x00 << 3; // Block Select
+const uint8_t w5500Socket0Register = 0x01 << 3; // Block Select
+const uint8_t w5500Socket1Register = 0x05 << 3; // Block Select
+const uint8_t w5500Socket2Register = 0x09 << 3; // Block Select
+const uint8_t w5500Socket3Register = 0x0D << 3; // Block Select
+const uint8_t w5500Socket4Register = 0x11 << 3; // Block Select
+const uint8_t w5500Socket5Register = 0x15 << 3; // Block Select
+const uint8_t w5500Socket6Register = 0x19 << 3; // Block Select
+const uint8_t w5500Socket7Register = 0x1D << 3; // Block Select
+const uint8_t w5500RegisterWrite = 0x01 << 2; // Read/Write bit
+const uint8_t w5500VDM = 0x00 << 0; // Variable Data Length Mode
+const uint8_t w5500FDM1 = 0x01 << 0; // Fixed Data Length Mode 1 Byte
+const uint8_t w5500FDM2 = 0x02 << 0; // Fixed Data Length Mode 2 Byte
+const uint8_t w5500FDM4 = 0x03 << 0; // Fixed Data Length Mode 4 Byte
+
+const uint8_t w5500SocketRegisters[] = {w5500Socket0Register, w5500Socket1Register, w5500Socket2Register,
+ w5500Socket3Register, w5500Socket4Register, w5500Socket5Register,
+ w5500Socket6Register, w5500Socket7Register};
+
+const uint8_t w5500SIR_ClearAll = 0xFF;
+const uint8_t w5500SIMR_EnableAll = 0xFF;
+
+const uint8_t w5500SnIR_ClearAll = 0xFF;
+
+const uint8_t w5500SnIMR_CON = 0x01 << 0;
+const uint8_t w5500SnIMR_DISCON = 0x01 << 1;
+const uint8_t w5500SnIMR_RECV = 0x01 << 2;
+const uint8_t w5500SnIMR_TIMEOUT = 0x01 << 3;
+const uint8_t w5500SnIMR_SENDOK = 0x01 << 4;
+
+void w5500write(SPIClass &spiPort, const int cs, uint16_t address, uint8_t control, uint8_t *data, uint8_t len)
+{
+ // Apply settings
+ spiPort.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
+
+ // Signal communication start
+ digitalWrite(cs, LOW);
+
+ spiPort.transfer(address >> 8); // Address Phase
+ spiPort.transfer(address & 0xFF);
+
+ spiPort.transfer(control | w5500RegisterWrite | w5500VDM); // Control Phase
+
+ for (uint8_t i = 0; i < len; i++)
+ {
+ spiPort.transfer(*data++); // Data Phase
+ }
+
+ // End communication
+ digitalWrite(cs, HIGH);
+ spiPort.endTransaction();
+}
+
+void w5500read(SPIClass &spiPort, const int cs, uint16_t address, uint8_t control, uint8_t *data, uint8_t len)
+{
+ // Apply settings
+ spiPort.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
+
+ // Signal communication start
+ digitalWrite(cs, LOW);
+
+ spiPort.transfer(address >> 8); // Address Phase
+ spiPort.transfer(address & 0xFF);
+
+ spiPort.transfer(control | w5500VDM); // Control Phase
+
+ for (uint8_t i = 0; i < len; i++)
+ {
+ *data++ = spiPort.transfer(0x00); // Data Phase
+ }
+
+ // End communication
+ digitalWrite(cs, HIGH);
+ spiPort.endTransaction();
+}
+
+void w5500ClearSocketInterrupts()
+{
+ // Clear all W5500 socket interrupts.
+ for (uint8_t i = 0; i < (sizeof(w5500SocketRegisters) / sizeof(uint8_t)); i++)
+ {
+ w5500write(SPI, pin_Ethernet_CS, w5500SnIR, w5500SocketRegisters[i], (uint8_t *)&w5500SnIR_ClearAll, 1);
+ }
+ w5500write(SPI, pin_Ethernet_CS, w5500SIR, w5500CommonRegister, (uint8_t *)&w5500SIR_ClearAll, 1);
+}
+
+void w5500ClearSocketInterrupt(uint8_t sockIndex)
+{
+ // Clear the interrupt for sockIndex only
+
+ // Sn_IR indicates the status of Socket Interrupt such as establishment, termination,
+ // receiving data, timeout). When an interrupt occurs and the corresponding bit of
+ // Sn_IMR is ‘1’, the corresponding bit of Sn_IR becomes ‘1’.
+ // In order to clear the Sn_IR bit, the host should write the bit to ‘1’.
+ w5500write(SPI, pin_Ethernet_CS, w5500SnIR, w5500SocketRegisters[sockIndex], (uint8_t *)&w5500SnIR_ClearAll, 1);
+
+ // SIR indicates the interrupt status of Socket. Each bit of SIR be still ‘1’ until Sn_IR is
+ // cleared by the host. If Sn_IR is not equal to ‘0x00’, the n-th bit of SIR is ‘1’ and INTn
+ // PIN is asserted until SIR is ‘0x00’.
+ uint8_t SIR = 1 << sockIndex;
+ w5500write(SPI, pin_Ethernet_CS, w5500SIR, w5500CommonRegister, &SIR, 1);
+}
+
+bool w5500CheckSocketInterrupt(uint8_t sockIndex)
+{
+ // Check the interrupt for sockIndex only
+ uint8_t S_INT = 1 << sockIndex;
+ uint8_t SIR;
+ w5500read(SPI, pin_Ethernet_CS, w5500SIR, w5500CommonRegister, &SIR, 1);
+ return ((S_INT & SIR) > 0);
+}
+
+void w5500EnableSocketInterrupts()
+{
+ // Enable the RECV interrupt on all eight sockets
+ for (uint8_t i = 0; i < (sizeof(w5500SocketRegisters) / sizeof(uint8_t)); i++)
+ {
+ w5500write(SPI, pin_Ethernet_CS, w5500SnIMR, w5500SocketRegisters[i], (uint8_t *)&w5500SnIMR_RECV, 1);
+ }
+
+ w5500write(SPI, pin_Ethernet_CS, w5500SIMR, w5500CommonRegister, (uint8_t *)&w5500SIMR_EnableAll,
+ 1); // Enable the socket interrupt on all eight sockets
+}
+
+void w5500EnableSocketInterrupt(uint8_t sockIndex)
+{
+ w5500write(SPI, pin_Ethernet_CS, w5500SnIMR, w5500SocketRegisters[sockIndex], (uint8_t *)&w5500SnIMR_RECV,
+ 1); // Enable the RECV interrupt for sockIndex only
+
+ // Read-Modify-Write
+ uint8_t SIMR;
+ w5500read(SPI, pin_Ethernet_CS, w5500SIMR, w5500CommonRegister, &SIMR, 1);
+ SIMR |= 1 << sockIndex;
+ w5500write(SPI, pin_Ethernet_CS, w5500SIMR, w5500CommonRegister, &SIMR, 1); // Enable the socket interrupt
+}
+
+void w5500DisableSocketInterrupt(uint8_t sockIndex)
+{
+ w5500write(SPI, pin_Ethernet_CS, w5500SnIMR, w5500SocketRegisters[sockIndex], (uint8_t *)&w5500SnIMR_RECV,
+ 1); // Enable the RECV interrupt for sockIndex only
+
+ // Read-Modify-Write
+ uint8_t SIMR;
+ w5500read(SPI, pin_Ethernet_CS, w5500SIMR, w5500CommonRegister, &SIMR, 1);
+ SIMR &= ~(1 << sockIndex);
+ w5500write(SPI, pin_Ethernet_CS, w5500SIMR, w5500CommonRegister, &SIMR, 1); // Disable the socket interrupt
+}
+
+#endif // COMPILE_ETHERNET
diff --git a/Firmware/RTK_Surveyor/WiFi.ino b/Firmware/RTK_Surveyor/WiFi.ino
new file mode 100644
index 000000000..e9101f451
--- /dev/null
+++ b/Firmware/RTK_Surveyor/WiFi.ino
@@ -0,0 +1,681 @@
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ 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 States:
+
+ WIFI_STATE_OFF<-------------------.
+ | |
+ wifiStart() | |
+ | | WL_CONNECT_FAILED (Bad password)
+ | | WL_NO_SSID_AVAIL (Out of range)
+ v Fail |
+ WIFI_STATE_CONNECTING------------->+
+ | ^ ^
+ wifiConnect() | | | wifiShutdown()
+ | | WL_CONNECTION_LOST |
+ | | WL_DISCONNECTED |
+ v | |
+ WIFI_STATE_CONNECTED -------------'
+ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+
+//----------------------------------------
+// Globals
+//----------------------------------------
+
+int wifiConnectionAttempts; // Count the number of connection attempts between restarts
+
+#ifdef COMPILE_WIFI
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+//----------------------------------------
+// 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;
+
+// WiFi Timer usage:
+// * Measure interval to display IP address
+static unsigned long wifiDisplayTimer;
+
+// Last time the WiFi state was displayed
+static uint32_t lastWifiState;
+
+// DNS server for Captive Portal
+static DNSServer dnsServer;
+
+//----------------------------------------
+// WiFi Routines
+//----------------------------------------
+
+// Set WiFi credentials
+// Enable TCP connections
+void menuWiFi()
+{
+ bool restartWiFi = false; // Restart WiFi if user changes anything
+
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: WiFi Networks");
+
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ systemPrintf("%d) SSID %d: %s\r\n", (x * 2) + 1, x + 1, settings.wifiNetworks[x].ssid);
+ systemPrintf("%d) Password %d: %s\r\n", (x * 2) + 2, x + 1, settings.wifiNetworks[x].password);
+ }
+
+ systemPrint("a) Configure device via WiFi Access Point or connect to WiFi: ");
+ systemPrintf("%s\r\n", settings.wifiConfigOverAP ? "AP" : "WiFi");
+
+ systemPrint("c) Captive Portal: ");
+ systemPrintf("%s\r\n", settings.enableCaptivePortal ? "Enabled" : "Disabled");
+
+ systemPrint("m) MDNS: ");
+ systemPrintf("%s\r\n", settings.mdnsEnable ? "Enabled" : "Disabled");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming >= 1 && incoming <= MAX_WIFI_NETWORKS * 2)
+ {
+ int arraySlot = ((incoming - 1) / 2); // Adjust incoming to array starting at 0
+
+ if (incoming % 2 == 1)
+ {
+ systemPrintf("Enter SSID network %d: ", arraySlot + 1);
+ getString(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);
+ getString(settings.wifiNetworks[arraySlot].password, sizeof(settings.wifiNetworks[arraySlot].password));
+ restartWiFi = true; // If we are modifying the SSID table, force restart of WiFi
+ }
+ }
+ else if (incoming == 'a')
+ {
+ settings.wifiConfigOverAP ^= 1;
+ restartWiFi = true;
+ }
+ else if (incoming == 'c')
+ {
+ settings.enableCaptivePortal ^= 1;
+ }
+ else if (incoming == 'm')
+ {
+ settings.mdnsEnable ^= 1;
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ // Erase passwords from empty SSID entries
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ if (strlen(settings.wifiNetworks[x].ssid) == 0)
+ strcpy(settings.wifiNetworks[x].password, "");
+ }
+
+ // Restart WiFi if anything changes
+ if (restartWiFi == true)
+ {
+ // 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
+ if (wifiIsConnected())
+ {
+ log_d("Menu caused restarting of WiFi");
+ WIFI_STOP();
+ wifiStart();
+ wifiConnectionAttempts = 0; // Reset the timeout
+ }
+ }
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Display the WiFi IP address
+void wifiDisplayIpAddress()
+{
+ systemPrint("WiFi IP address: ");
+ systemPrint(WiFi.localIP());
+ systemPrintf(" RSSI: %d\r\n", WiFi.RSSI());
+
+ wifiDisplayTimer = millis();
+}
+
+// Get the WiFi adapter status
+byte wifiGetStatus()
+{
+ return WiFi.status();
+}
+
+// Update the state of the WiFi state machine
+void wifiSetState(byte newState)
+{
+ if ((settings.debugWifiState || PERIODIC_DISPLAY(PD_WIFI_STATE)) && (wifiState == newState))
+ systemPrint("*");
+ wifiState = newState;
+
+ if (settings.debugWifiState || PERIODIC_DISPLAY(PD_WIFI_STATE))
+ {
+ PERIODIC_CLEAR(PD_WIFI_STATE);
+ switch (newState)
+ {
+ default:
+ systemPrintf("Unknown WiFi state: %d\r\n", newState);
+ break;
+ case WIFI_STATE_OFF:
+ systemPrintln("WIFI_STATE_OFF");
+ break;
+ case WIFI_STATE_START:
+ systemPrintln("WIFI_STATE_START");
+ break;
+ case WIFI_STATE_CONNECTING:
+ systemPrintln("WIFI_STATE_CONNECTING");
+ break;
+ case WIFI_STATE_CONNECTED:
+ systemPrintln("WIFI_STATE_CONNECTED");
+ break;
+ }
+ }
+}
+
+//----------------------------------------
+// WiFi Config Support Routines - compiled out
+//----------------------------------------
+
+// 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()
+{
+ return(wifiStartAP(false)); //Don't force AP mode
+}
+
+bool wifiStartAP(bool forceAP)
+{
+ if (settings.wifiConfigOverAP == true || forceAP)
+ {
+ // Stop any current WiFi activity
+ WIFI_STOP();
+
+ // Start in AP mode
+ WiFi.mode(WIFI_AP);
+
+ // 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
+ log_d("WiFi protocols set");
+
+ 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);
+ if (WiFi.softAP("RTK Config") == false) // Must be short enough to fit OLED Width
+ {
+ systemPrintln("WiFi AP failed to start");
+ return (false);
+ }
+ systemPrint("WiFi AP Started with IP: ");
+ systemPrintln(WiFi.softAPIP());
+
+ // Start DNS Server
+ if (dnsServer.start(53, "*", WiFi.softAPIP()) == false)
+ {
+ systemPrintln("WiFi DNS Server failed to start");
+ return (false);
+ }
+ else
+ {
+ log_d("DNS Server started");
+ }
+ }
+ else
+ {
+ // Start webserver on local WiFi instead of AP
+
+ // Attempt to connect to local WiFi with increasing timeouts
+ int timeout = 0;
+ int x = 0;
+ const int maxTries = 2;
+ for (; x < maxTries; x++)
+ {
+ timeout += 5000;
+ if (wifiConnect(timeout) == true) // Attempt to connect to any SSID on settings list
+ {
+ wifiPrintNetworkInfo();
+ 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
+ }
+ }
+
+ return (true);
+}
+
+//----------------------------------------
+// WiFi Routines
+//----------------------------------------
+
+// Advance the WiFi state from off to connected
+// Throttle connection attempts as needed
+void wifiUpdate()
+{
+ // Skip if in configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ // log_d("configureViaEthernet: skipping wifiUpdate");
+ return;
+ }
+
+ // Periodically display the WiFi state
+ if (settings.debugWifiState && ((millis() - lastWifiState) > 15000))
+ {
+ wifiSetState(wifiState);
+ lastWifiState = millis();
+ }
+
+ DMW_st(wifiSetState, wifiState);
+ switch (wifiState)
+ {
+ default:
+ systemPrintf("Unknown wifiState: %d\r\n", wifiState);
+ break;
+
+ case WIFI_STATE_OFF:
+ // Any service that needs WiFi will call wifiStart()
+ break;
+
+ case WIFI_STATE_CONNECTING:
+ // Pause until connection timeout has passed
+ if (millis() - wifiLastConnectionAttempt > wifiConnectionAttemptTimeout)
+ {
+ wifiLastConnectionAttempt = millis();
+
+ if (wifiConnect(10000) == true) // Attempt to connect to any SSID on settings list
+ {
+ if (espnowState > ESPNOW_OFF)
+ espnowStart();
+
+ wifiSetState(WIFI_STATE_CONNECTED);
+ }
+ else
+ {
+ // We failed to connect
+ if (wifiConnectLimitReached() == false) // Increases wifiConnectionAttemptTimeout
+ {
+ if (wifiConnectionAttemptTimeout / 1000 < 120)
+ systemPrintf("Next WiFi attempt in %d seconds.\r\n", wifiConnectionAttemptTimeout / 1000);
+ else
+ systemPrintf("Next WiFi attempt in %d minutes.\r\n", wifiConnectionAttemptTimeout / 1000 / 60);
+ }
+ else
+ {
+ systemPrintln("WiFi connection failed. Giving up.");
+ displayNoWiFi(2000);
+ WIFI_STOP(); // Move back to WIFI_STATE_OFF
+ }
+ }
+ }
+
+ break;
+
+ case WIFI_STATE_CONNECTED:
+ // Verify link is still up
+ if (wifiIsConnected() == false)
+ {
+ systemPrintln("WiFi link lost");
+ wifiConnectionAttempts = 0; // Reset the timeout
+ wifiSetState(WIFI_STATE_CONNECTING);
+ }
+
+ // If WiFi is connected, and no services require WiFi, shut it off
+ else if (wifiIsNeeded() == false)
+ WIFI_STOP();
+
+ break;
+ }
+
+ // Process DNS when we are in AP mode for captive portal
+ if (WiFi.getMode() == WIFI_AP && settings.enableCaptivePortal)
+ {
+ dnsServer.processNextRequest();
+ }
+}
+
+// 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
+void wifiStart()
+{
+ if (wifiNetworkCount() == 0)
+ {
+ systemPrintln("Error: Please enter at least one SSID before using WiFi");
+ displayNoSSIDs(2000);
+ WIFI_STOP();
+ return;
+ }
+
+ if (wifiIsConnected() == true)
+ return; // We don't need to do anything
+
+ if (wifiState > WIFI_STATE_OFF)
+ return; // We're in the midst of connecting
+
+ log_d("Starting WiFi");
+
+ wifiSetState(WIFI_STATE_CONNECTING); // This starts the state machine running
+
+ // Display the heap state
+ reportHeapNow(settings.debugWifiState);
+}
+
+// Stop WiFi and release all resources
+void wifiStop()
+{
+ // Stop the web server
+ stopWebServer();
+
+ // Stop the multicast domain name server
+ if (settings.mdnsEnable == true)
+ MDNS.end();
+
+ // Stop the DNS server if we were using the captive portal
+ if (WiFi.getMode() == WIFI_AP && settings.enableCaptivePortal)
+ dnsServer.stop();
+
+ // Stop the other network clients and then WiFi
+ NETWORK_STOP(NETWORK_TYPE_WIFI);
+}
+
+// Stop WiFi and release all resources
+// If ESP NOW is active, leave WiFi on enough for ESP NOW
+void wifiShutdown()
+{
+ wifiSetState(WIFI_STATE_OFF);
+
+ wifiConnectionAttempts = 0; // Reset the timeout
+
+ // If ESP-Now is active, change protocol to only Long Range and re-start WiFi
+ if (espnowState > ESPNOW_OFF)
+ {
+ if (WiFi.getMode() != WIFI_STA)
+ WiFi.mode(WIFI_STA);
+
+ // Enable long range, PHY rate of ESP32 will be 512Kbps or 256Kbps
+ // esp_wifi_set_protocol requires WiFi to be started
+ esp_err_t response = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR);
+ if (response != ESP_OK)
+ systemPrintf("wifiShutdown: Error setting ESP-Now lone protocol: %s\r\n", esp_err_to_name(response));
+ else
+ log_d("WiFi disabled, ESP-Now left in place");
+ }
+ else
+ {
+ WiFi.mode(WIFI_OFF);
+ log_d("WiFi Stopped");
+ }
+
+ // Display the heap state
+ reportHeapNow(settings.debugWifiState);
+}
+
+bool wifiIsConnected()
+{
+ return (wifiGetStatus() == WL_CONNECTED);
+}
+
+// Attempts a connection to all provided SSIDs
+// Returns true if successful
+// Gives up if no SSID detected or connection times out
+bool wifiConnect(unsigned long timeout)
+{
+ if (wifiIsConnected())
+ return (true); // Nothing to do
+
+ displayWiFiConnect();
+
+ // 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));
+ }
+ }
+
+ WiFiMulti wifiMulti;
+
+ // Load SSIDs
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ if (strlen(settings.wifiNetworks[x].ssid) > 0)
+ wifiMulti.addAP(settings.wifiNetworks[x].ssid, settings.wifiNetworks[x].password);
+ }
+
+ systemPrint("Connecting WiFi... ");
+
+ int wifiResponse = wifiMulti.run(timeout);
+ if (wifiResponse == WL_CONNECTED)
+ {
+ if (settings.enablePvtClient == true || settings.enablePvtServer == true || settings.enablePvtUdpServer == true)
+ {
+ if (settings.mdnsEnable == true)
+ {
+ if (MDNS.begin("rtk") == false) // This should make the module findable from 'rtk.local' in browser
+ systemPrintln("Error setting up MDNS responder!");
+ else
+ MDNS.addService("http", "tcp", settings.httpPort); // Add service to MDNS
+ }
+ }
+
+ systemPrintln();
+ return true;
+ }
+ else if (wifiResponse == WL_DISCONNECTED)
+ systemPrint("No friendly WiFi networks detected.\r\n");
+ else
+ systemPrintf("WiFi failed to connect: error #%d.\r\n", wifiResponse);
+
+ return false;
+}
+
+// Based on the current settings and system states, determine if we need WiFi on or not
+// This function does not start WiFi. Any service that needs it should call wifiStart().
+// This function is used to turn WiFi off if nothing needs it.
+bool wifiIsNeeded()
+{
+ if (settings.enablePvtClient == true)
+ return true;
+ if (settings.enablePvtServer == true)
+ return true;
+ if (settings.enablePvtUdpServer == true)
+ return true;
+ if (settings.enableAutoFirmwareUpdate)
+ return true;
+
+ // Handle WiFi within systemStates
+ if (systemState <= STATE_ROVER_RTK_FIX && settings.enableNtripClient == true)
+ return true;
+
+ if (systemState >= STATE_BASE_NOT_STARTED && systemState <= STATE_BASE_FIXED_TRANSMITTING &&
+ settings.enableNtripServer == true)
+ return true;
+
+ // If the user has enabled NTRIP Client for an Assisted Survey-In, and Survey-In is running, keep WiFi on.
+ if (systemState >= STATE_BASE_NOT_STARTED && systemState <= STATE_BASE_TEMP_SURVEY_STARTED &&
+ settings.enableNtripClient == true && settings.fixedBase == false)
+ return true;
+
+ // If WiFi is on while we are in the following states, allow WiFi to continue to operate
+ if (systemState >= STATE_BUBBLE_LEVEL && systemState <= STATE_PROFILE)
+ {
+ // Keep WiFi on if user presses setup button, enters bubble level, is in AP config mode, etc
+ return true;
+ }
+
+ if (systemState == STATE_KEYS_WIFI_STARTED || systemState == STATE_KEYS_WIFI_CONNECTED)
+ return true;
+ if (systemState == STATE_KEYS_PROVISION_WIFI_STARTED || systemState == STATE_KEYS_PROVISION_WIFI_CONNECTED)
+ return true;
+
+ return false;
+}
+
+// Counts the number of entered SSIDs
+int wifiNetworkCount()
+{
+ // Count SSIDs
+ int networkCount = 0;
+ for (int x = 0; x < MAX_WIFI_NETWORKS; x++)
+ {
+ if (strlen(settings.wifiNetworks[x].ssid) > 0)
+ networkCount++;
+ }
+ return networkCount;
+}
+
+// Determine if another connection is possible or if the limit has been reached
+bool wifiConnectLimitReached()
+{
+ // Retry the connection a few times
+ bool limitReached = false;
+ if (wifiConnectionAttempts++ >= wifiMaxConnectionAttempts)
+ limitReached = true;
+
+ wifiConnectionAttemptsTotal++;
+
+ if (limitReached == false)
+ {
+ wifiConnectionAttemptTimeout =
+ wifiConnectionAttempts * 15 * 1000L; // Wait 15, 30, 45, etc seconds between attempts
+
+ reportHeapNow(settings.debugWifiState);
+ }
+ else
+ {
+ // No more connection attempts
+ systemPrintln("WiFi connection attempts exceeded!");
+ }
+ return limitReached;
+}
+
+void wifiPrintNetworkInfo()
+{
+ systemPrintln("\nNetwork Configuration:");
+ systemPrintln("----------------------");
+ systemPrint(" SSID: ");
+ systemPrintln(WiFi.SSID());
+ systemPrint(" WiFi Status: ");
+ systemPrintln(WiFi.status());
+ systemPrint("WiFi Strength: ");
+ systemPrint(WiFi.RSSI());
+ systemPrintln(" dBm");
+ systemPrint(" MAC: ");
+ systemPrintln(WiFi.macAddress());
+ systemPrint(" IP: ");
+ systemPrintln(WiFi.localIP());
+ systemPrint(" Subnet: ");
+ systemPrintln(WiFi.subnetMask());
+ systemPrint(" Gateway: ");
+ systemPrintln(WiFi.gatewayIP());
+ systemPrint(" DNS 1: ");
+ systemPrintln(WiFi.dnsIP(0));
+ systemPrint(" DNS 2: ");
+ systemPrintln(WiFi.dnsIP(1));
+ systemPrint(" DNS 3: ");
+ systemPrintln(WiFi.dnsIP(2));
+ systemPrintln();
+}
+
+// Returns true if unit is in config mode
+// Used to disallow services (NTRIP, TCP, etc) from updating
+bool wifiInConfigMode()
+{
+ if (systemState >= STATE_WIFI_CONFIG_NOT_STARTED && systemState <= STATE_WIFI_CONFIG)
+ return true;
+ return false;
+}
+
+IPAddress wifiGetGatewayIpAddress()
+{
+ return WiFi.gatewayIP();
+}
+
+IPAddress wifiGetIpAddress()
+{
+ return WiFi.localIP();
+}
+
+int wifiGetRssi()
+{
+ return WiFi.RSSI();
+}
+
+#endif // COMPILE_WIFI
diff --git a/Firmware/RTK_Surveyor/X509_Certificate_Bundle.h b/Firmware/RTK_Surveyor/X509_Certificate_Bundle.h
new file mode 100644
index 000000000..d64efab17
--- /dev/null
+++ b/Firmware/RTK_Surveyor/X509_Certificate_Bundle.h
@@ -0,0 +1,1056 @@
+const uint8_t x509CertificateBundle[] =
+{
+ 0x00, 0x29, 0x00, 0x3b, 0x01, 0x26, 0x30, 0x39, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, // 16
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, // 32
+ 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, // 48
+ 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, // 64
+ 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, // 80
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, // 96
+ 0x01, 0x00, 0xb2, 0x78, 0x80, 0x71, 0xca, 0x78, 0xd5, 0xe3, 0x71, 0xaf, 0x47, 0x80, 0x50, 0x74, // 112
+ 0x7d, 0x6e, 0xd8, 0xd7, 0x88, 0x76, 0xf4, 0x99, 0x68, 0xf7, 0x58, 0x21, 0x60, 0xf9, 0x74, 0x84, // 128
+ 0x01, 0x2f, 0xac, 0x02, 0x2d, 0x86, 0xd3, 0xa0, 0x43, 0x7a, 0x4e, 0xb2, 0xa4, 0xd0, 0x36, 0xba, // 144
+ 0x01, 0xbe, 0x8d, 0xdb, 0x48, 0xc8, 0x07, 0x17, 0x36, 0x4c, 0xf4, 0xee, 0x88, 0x23, 0xc7, 0x3e, // 160
+ 0xeb, 0x37, 0xf5, 0xb5, 0x19, 0xf8, 0x49, 0x68, 0xb0, 0xde, 0xd7, 0xb9, 0x76, 0x38, 0x1d, 0x61, // 176
+ 0x9e, 0xa4, 0xfe, 0x82, 0x36, 0xa5, 0xe5, 0x4a, 0x56, 0xe4, 0x45, 0xe1, 0xf9, 0xfd, 0xb4, 0x16, // 192
+ 0xfa, 0x74, 0xda, 0x9c, 0x9b, 0x35, 0x39, 0x2f, 0xfa, 0xb0, 0x20, 0x50, 0x06, 0x6c, 0x7a, 0xd0, // 208
+ 0x80, 0xb2, 0xa6, 0xf9, 0xaf, 0xec, 0x47, 0x19, 0x8f, 0x50, 0x38, 0x07, 0xdc, 0xa2, 0x87, 0x39, // 224
+ 0x58, 0xf8, 0xba, 0xd5, 0xa9, 0xf9, 0x48, 0x67, 0x30, 0x96, 0xee, 0x94, 0x78, 0x5e, 0x6f, 0x89, // 240
+ 0xa3, 0x51, 0xc0, 0x30, 0x86, 0x66, 0xa1, 0x45, 0x66, 0xba, 0x54, 0xeb, 0xa3, 0xc3, 0x91, 0xf9, // 256
+ 0x48, 0xdc, 0xff, 0xd1, 0xe8, 0x30, 0x2d, 0x7d, 0x2d, 0x74, 0x70, 0x35, 0xd7, 0x88, 0x24, 0xf7, // 272
+ 0x9e, 0xc4, 0x59, 0x6e, 0xbb, 0x73, 0x87, 0x17, 0xf2, 0x32, 0x46, 0x28, 0xb8, 0x43, 0xfa, 0xb7, // 288
+ 0x1d, 0xaa, 0xca, 0xb4, 0xf2, 0x9f, 0x24, 0x0e, 0x2d, 0x4b, 0xf7, 0x71, 0x5c, 0x5e, 0x69, 0xff, // 304
+ 0xea, 0x95, 0x02, 0xcb, 0x38, 0x8a, 0xae, 0x50, 0x38, 0x6f, 0xdb, 0xfb, 0x2d, 0x62, 0x1b, 0xc5, // 320
+ 0xc7, 0x1e, 0x54, 0xe1, 0x77, 0xe0, 0x67, 0xc8, 0x0f, 0x9c, 0x87, 0x23, 0xd6, 0x3f, 0x40, 0x20, // 336
+ 0x7f, 0x20, 0x80, 0xc4, 0x80, 0x4c, 0x3e, 0x3b, 0x24, 0x26, 0x8e, 0x04, 0xae, 0x6c, 0x9a, 0xc8, // 352
+ 0xaa, 0x0d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x3b, 0x02, 0x26, 0x30, 0x39, 0x31, 0x0b, 0x30, // 368
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, // 384
+ 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, // 400
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x20, 0x52, 0x6f, 0x6f, // 416
+ 0x74, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, // 432
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, // 448
+ 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xad, 0x96, 0x9f, 0x2d, 0x9c, 0x4a, 0x4c, 0x4a, 0x81, // 464
+ 0x79, 0x51, 0x99, 0xec, 0x8a, 0xcb, 0x6b, 0x60, 0x51, 0x13, 0xbc, 0x4d, 0x6d, 0x06, 0xfc, 0xb0, // 480
+ 0x08, 0x8d, 0xdd, 0x19, 0x10, 0x6a, 0xc7, 0x26, 0x0c, 0x35, 0xd8, 0xc0, 0x6f, 0x20, 0x84, 0xe9, // 496
+ 0x94, 0xb1, 0x9b, 0x85, 0x03, 0xc3, 0x5b, 0xdb, 0x4a, 0xe8, 0xc8, 0xf8, 0x90, 0x76, 0xd9, 0x5b, // 512
+ 0x4f, 0xe3, 0x4c, 0xe8, 0x06, 0x36, 0x4d, 0xcc, 0x9a, 0xac, 0x3d, 0x0c, 0x90, 0x2b, 0x92, 0xd4, // 528
+ 0x06, 0x19, 0x60, 0xac, 0x37, 0x44, 0x79, 0x85, 0x81, 0x82, 0xad, 0x5a, 0x37, 0xe0, 0x0d, 0xcc, // 544
+ 0x9d, 0xa6, 0x4c, 0x52, 0x76, 0xea, 0x43, 0x9d, 0xb7, 0x04, 0xd1, 0x50, 0xf6, 0x55, 0xe0, 0xd5, // 560
+ 0xd2, 0xa6, 0x49, 0x85, 0xe9, 0x37, 0xe9, 0xca, 0x7e, 0xae, 0x5c, 0x95, 0x4d, 0x48, 0x9a, 0x3f, // 576
+ 0xae, 0x20, 0x5a, 0x6d, 0x88, 0x95, 0xd9, 0x34, 0xb8, 0x52, 0x1a, 0x43, 0x90, 0xb0, 0xbf, 0x6c, // 592
+ 0x05, 0xb9, 0xb6, 0x78, 0xb7, 0xea, 0xd0, 0xe4, 0x3a, 0x3c, 0x12, 0x53, 0x62, 0xff, 0x4a, 0xf2, // 608
+ 0x7b, 0xbe, 0x35, 0x05, 0xa9, 0x12, 0x34, 0xe3, 0xf3, 0x64, 0x74, 0x62, 0x2c, 0x3d, 0x00, 0x49, // 624
+ 0x5a, 0x28, 0xfe, 0x32, 0x44, 0xbb, 0x87, 0xdd, 0x65, 0x27, 0x02, 0x71, 0x3b, 0xda, 0x4a, 0xf7, // 640
+ 0x1f, 0xda, 0xcd, 0xf7, 0x21, 0x55, 0x90, 0x4f, 0x0f, 0xec, 0xae, 0x82, 0xe1, 0x9f, 0x6b, 0xd9, // 656
+ 0x45, 0xd3, 0xbb, 0xf0, 0x5f, 0x87, 0xed, 0x3c, 0x2c, 0x39, 0x86, 0xda, 0x3f, 0xde, 0xec, 0x72, // 672
+ 0x55, 0xeb, 0x79, 0xa3, 0xad, 0xdb, 0xdd, 0x7c, 0xb0, 0xba, 0x1c, 0xce, 0xfc, 0xde, 0x4f, 0x35, // 688
+ 0x76, 0xcf, 0x0f, 0xf8, 0x78, 0x1f, 0x6a, 0x36, 0x51, 0x46, 0x27, 0x61, 0x5b, 0xe9, 0x9e, 0xcf, // 704
+ 0xf0, 0xa2, 0x55, 0x7d, 0x7c, 0x25, 0x8a, 0x6f, 0x2f, 0xb4, 0xc5, 0xcf, 0x84, 0x2e, 0x2b, 0xfd, // 720
+ 0x0d, 0x51, 0x10, 0x6c, 0xfb, 0x5f, 0x1b, 0xbc, 0x1b, 0x7e, 0xc5, 0xae, 0x3b, 0x98, 0x01, 0x31, // 736
+ 0x92, 0xff, 0x0b, 0x57, 0xf4, 0x9a, 0xb2, 0xb9, 0x57, 0xe9, 0xab, 0xef, 0x0d, 0x76, 0xd1, 0xf0, // 752
+ 0xee, 0xf4, 0xce, 0x86, 0xa7, 0xe0, 0x6e, 0xe9, 0xb4, 0x69, 0xa1, 0xdf, 0x69, 0xf6, 0x33, 0xc6, // 768
+ 0x69, 0x2e, 0x97, 0x13, 0x9e, 0xa5, 0x87, 0xb0, 0x57, 0x10, 0x81, 0x37, 0xc9, 0x53, 0xb3, 0xbb, // 784
+ 0x7f, 0xf6, 0x92, 0xd1, 0x9c, 0xd0, 0x18, 0xf4, 0x92, 0x6e, 0xda, 0x83, 0x4f, 0xa6, 0x63, 0x99, // 800
+ 0x4c, 0xa5, 0xfb, 0x5e, 0xef, 0x21, 0x64, 0x7a, 0x20, 0x5f, 0x6c, 0x64, 0x85, 0x15, 0xcb, 0x37, // 816
+ 0xe9, 0x62, 0x0c, 0x0b, 0x2a, 0x16, 0xdc, 0x01, 0x2e, 0x32, 0xda, 0x3e, 0x4b, 0xf5, 0x9e, 0x3a, // 832
+ 0xf6, 0x17, 0x40, 0x94, 0xef, 0x9e, 0x91, 0x08, 0x86, 0xfa, 0xbe, 0x63, 0xa8, 0x5a, 0x33, 0xec, // 848
+ 0xcb, 0x74, 0x43, 0x95, 0xf9, 0x6c, 0x69, 0x52, 0x36, 0xc7, 0x29, 0x6f, 0xfc, 0x55, 0x03, 0x5c, // 864
+ 0x1f, 0xfb, 0x9f, 0xbd, 0x47, 0xeb, 0xe7, 0x49, 0x47, 0x95, 0x0b, 0x4e, 0x89, 0x22, 0x09, 0x49, // 880
+ 0xe0, 0xf5, 0x61, 0x1e, 0xf1, 0xbf, 0x2e, 0x8a, 0x72, 0x6e, 0x80, 0x59, 0xff, 0x57, 0x3a, 0xf9, // 896
+ 0x75, 0x32, 0xa3, 0x4e, 0x5f, 0xec, 0xed, 0x28, 0x62, 0xd9, 0x4d, 0x73, 0xf2, 0xcc, 0x81, 0x17, // 912
+ 0x60, 0xed, 0xcd, 0xeb, 0xdc, 0xdb, 0xa7, 0xca, 0xc5, 0x7e, 0x02, 0xbd, 0xf2, 0x54, 0x08, 0x54, // 928
+ 0xfd, 0xb4, 0x2d, 0x09, 0x2c, 0x17, 0x54, 0x4a, 0x98, 0xd1, 0x54, 0xe1, 0x51, 0x67, 0x08, 0xd2, // 944
+ 0xed, 0x6e, 0x7e, 0x6f, 0x3f, 0xd2, 0x2d, 0x81, 0x59, 0x29, 0x66, 0xcb, 0x90, 0x39, 0x95, 0x11, // 960
+ 0x1e, 0x74, 0x27, 0xfe, 0xdd, 0xeb, 0xaf, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x3b, 0x00, 0x5b, // 976
+ 0x30, 0x39, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, // 992
+ 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, // 1008
+ 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, // 1024
+ 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x33, 0x30, 0x59, 0x30, 0x13, 0x06, // 1040
+ 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, // 1056
+ 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x29, 0x97, 0xa7, 0xc6, 0x41, 0x7f, 0xc0, 0x0d, 0x9b, 0xe8, // 1072
+ 0x01, 0x1b, 0x56, 0xc6, 0xf2, 0x52, 0xa5, 0xba, 0x2d, 0xb2, 0x12, 0xe8, 0xd2, 0x2e, 0xd7, 0xfa, // 1088
+ 0xc9, 0xc5, 0xd8, 0xaa, 0x6d, 0x1f, 0x73, 0x81, 0x3b, 0x3b, 0x98, 0x6b, 0x39, 0x7c, 0x33, 0xa5, // 1104
+ 0xc5, 0x4e, 0x86, 0x8e, 0x80, 0x17, 0x68, 0x62, 0x45, 0x57, 0x7d, 0x44, 0x58, 0x1d, 0xb3, 0x37, // 1120
+ 0xe5, 0x67, 0x08, 0xeb, 0x66, 0xde, 0x00, 0x3b, 0x00, 0x78, 0x30, 0x39, 0x31, 0x0b, 0x30, 0x09, // 1136
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, // 1152
+ 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, // 1168
+ 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, // 1184
+ 0x20, 0x43, 0x41, 0x20, 0x34, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, // 1200
+ 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xd2, 0xab, 0x8a, // 1216
+ 0x37, 0x4f, 0xa3, 0x53, 0x0d, 0xfe, 0xc1, 0x8a, 0x7b, 0x4b, 0xa8, 0x7b, 0x46, 0x4b, 0x63, 0xb0, // 1232
+ 0x62, 0xf6, 0x2d, 0x1b, 0xdb, 0x08, 0x71, 0x21, 0xd2, 0x00, 0xe8, 0x63, 0xbd, 0x9a, 0x27, 0xfb, // 1248
+ 0xf0, 0x39, 0x6e, 0x5d, 0xea, 0x3d, 0xa5, 0xc9, 0x81, 0xaa, 0xa3, 0x5b, 0x20, 0x98, 0x45, 0x5d, // 1264
+ 0x16, 0xdb, 0xfd, 0xe8, 0x10, 0x6d, 0xe3, 0x9c, 0xe0, 0xe3, 0xbd, 0x5f, 0x84, 0x62, 0xf3, 0x70, // 1280
+ 0x64, 0x33, 0xa0, 0xcb, 0x24, 0x2f, 0x70, 0xba, 0x88, 0xa1, 0x2a, 0xa0, 0x75, 0xf8, 0x81, 0xae, // 1296
+ 0x62, 0x06, 0xc4, 0x81, 0xdb, 0x39, 0x6e, 0x29, 0xb0, 0x1e, 0xfa, 0x2e, 0x5c, 0x00, 0x41, 0x01, // 1312
+ 0x26, 0x30, 0x3f, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1b, 0x44, 0x69, // 1328
+ 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, // 1344
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x2e, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, // 1360
+ 0x04, 0x03, 0x13, 0x0e, 0x44, 0x53, 0x54, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, // 1376
+ 0x58, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, // 1392
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, // 1408
+ 0x01, 0x01, 0x00, 0xdf, 0xaf, 0xe9, 0x97, 0x50, 0x08, 0x83, 0x57, 0xb4, 0xcc, 0x62, 0x65, 0xf6, // 1424
+ 0x90, 0x82, 0xec, 0xc7, 0xd3, 0x2c, 0x6b, 0x30, 0xca, 0x5b, 0xec, 0xd9, 0xc3, 0x7d, 0xc7, 0x40, // 1440
+ 0xc1, 0x18, 0x14, 0x8b, 0xe0, 0xe8, 0x33, 0x76, 0x49, 0x2a, 0xe3, 0x3f, 0x21, 0x49, 0x93, 0xac, // 1456
+ 0x4e, 0x0e, 0xaf, 0x3e, 0x48, 0xcb, 0x65, 0xee, 0xfc, 0xd3, 0x21, 0x0f, 0x65, 0xd2, 0x2a, 0xd9, // 1472
+ 0x32, 0x8f, 0x8c, 0xe5, 0xf7, 0x77, 0xb0, 0x12, 0x7b, 0xb5, 0x95, 0xc0, 0x89, 0xa3, 0xa9, 0xba, // 1488
+ 0xed, 0x73, 0x2e, 0x7a, 0x0c, 0x06, 0x32, 0x83, 0xa2, 0x7e, 0x8a, 0x14, 0x30, 0xcd, 0x11, 0xa0, // 1504
+ 0xe1, 0x2a, 0x38, 0xb9, 0x79, 0x0a, 0x31, 0xfd, 0x50, 0xbd, 0x80, 0x65, 0xdf, 0xb7, 0x51, 0x63, // 1520
+ 0x83, 0xc8, 0xe2, 0x88, 0x61, 0xea, 0x4b, 0x61, 0x81, 0xec, 0x52, 0x6b, 0xb9, 0xa2, 0xe2, 0x4b, // 1536
+ 0x1a, 0x28, 0x9f, 0x48, 0xa3, 0x9e, 0x0c, 0xda, 0x09, 0x8e, 0x3e, 0x17, 0x2e, 0x1e, 0xdd, 0x20, // 1552
+ 0xdf, 0x5b, 0xc6, 0x2a, 0x8a, 0xab, 0x2e, 0xbd, 0x70, 0xad, 0xc5, 0x0b, 0x1a, 0x25, 0x90, 0x74, // 1568
+ 0x72, 0xc5, 0x7b, 0x6a, 0xab, 0x34, 0xd6, 0x30, 0x89, 0xff, 0xe5, 0x68, 0x13, 0x7b, 0x54, 0x0b, // 1584
+ 0xc8, 0xd6, 0xae, 0xec, 0x5a, 0x9c, 0x92, 0x1e, 0x3d, 0x64, 0xb3, 0x8c, 0xc6, 0xdf, 0xbf, 0xc9, // 1600
+ 0x41, 0x70, 0xec, 0x16, 0x72, 0xd5, 0x26, 0xec, 0x38, 0x55, 0x39, 0x43, 0xd0, 0xfc, 0xfd, 0x18, // 1616
+ 0x5c, 0x40, 0xf1, 0x97, 0xeb, 0xd5, 0x9a, 0x9b, 0x8d, 0x1d, 0xba, 0xda, 0x25, 0xb9, 0xc6, 0xd8, // 1632
+ 0xdf, 0xc1, 0x15, 0x02, 0x3a, 0xab, 0xda, 0x6e, 0xf1, 0x3e, 0x2e, 0xf5, 0x5c, 0x08, 0x9c, 0x3c, // 1648
+ 0xd6, 0x83, 0x69, 0xe4, 0x10, 0x9b, 0x19, 0x2a, 0xb6, 0x29, 0x57, 0xe3, 0xe5, 0x3d, 0x9b, 0x9f, // 1664
+ 0xf0, 0x02, 0x5d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x48, 0x00, 0x78, 0x30, 0x46, 0x31, 0x0b, // 1680
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, 0x06, // 1696
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, // 1712
+ 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, // 1728
+ 0x13, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, // 1744
+ 0x20, 0x45, 0x34, 0x36, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, // 1760
+ 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0x9c, 0x0e, 0xb1, 0xcf, // 1776
+ 0xb7, 0xe8, 0x9e, 0x52, 0x77, 0x75, 0x34, 0xfa, 0xa5, 0x46, 0xa7, 0xad, 0x32, 0x19, 0x32, 0xb4, // 1792
+ 0x07, 0xa9, 0x27, 0xca, 0x94, 0xbb, 0x0c, 0xd2, 0x0a, 0x10, 0xc7, 0xda, 0x89, 0xb0, 0x97, 0x0c, // 1808
+ 0x70, 0x13, 0x09, 0x01, 0x8e, 0xd8, 0xea, 0x47, 0xea, 0xbe, 0xb2, 0x80, 0x2b, 0xcd, 0xfc, 0x28, // 1824
+ 0x0d, 0xdb, 0xac, 0xbc, 0xa4, 0x86, 0x37, 0xed, 0x70, 0x08, 0x00, 0x75, 0xea, 0x93, 0x0b, 0x7b, // 1840
+ 0x2e, 0x52, 0x9c, 0x23, 0x68, 0x23, 0x06, 0x43, 0xec, 0x92, 0x2f, 0x53, 0x84, 0xdb, 0xfb, 0x47, // 1856
+ 0x14, 0x07, 0xe8, 0x5f, 0x94, 0x67, 0x5d, 0xc9, 0x7a, 0x81, 0x3c, 0x20, 0x00, 0x48, 0x02, 0x26, // 1872
+ 0x30, 0x46, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, // 1888
+ 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, // 1904
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, // 1920
+ 0x55, 0x04, 0x03, 0x13, 0x13, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, // 1936
+ 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x52, 0x34, 0x36, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, // 1952
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, // 1968
+ 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xac, 0xac, 0x74, 0x32, 0xe8, 0xb3, 0x65, // 1984
+ 0xe5, 0xba, 0xed, 0x43, 0x26, 0x1d, 0xa6, 0x89, 0x0d, 0x45, 0xba, 0x29, 0x88, 0xb2, 0xa4, 0x1d, // 2000
+ 0x63, 0xdd, 0xd3, 0xc1, 0x2c, 0x09, 0x57, 0x89, 0x39, 0xa1, 0x55, 0xe9, 0x67, 0x34, 0x77, 0x0c, // 2016
+ 0x6e, 0xe4, 0x55, 0x1d, 0x52, 0x25, 0xd2, 0x13, 0x6b, 0x5e, 0xe1, 0x1d, 0xa9, 0xb7, 0x7d, 0x89, // 2032
+ 0x32, 0x5f, 0x0d, 0x9e, 0x9f, 0x2c, 0x7a, 0x63, 0x60, 0x40, 0x1f, 0xa6, 0xb0, 0xb6, 0x78, 0x8f, // 2048
+ 0x99, 0x54, 0x96, 0x08, 0x58, 0xae, 0xe4, 0x06, 0xbc, 0x62, 0x05, 0x02, 0x16, 0xbf, 0xaf, 0xa8, // 2064
+ 0x23, 0x03, 0xb6, 0x94, 0x0f, 0xbc, 0x6e, 0x6c, 0xc2, 0xcb, 0xd5, 0xa6, 0xbb, 0x0c, 0xe9, 0xf6, // 2080
+ 0xc1, 0x02, 0xfb, 0x21, 0xde, 0x66, 0xdd, 0x17, 0xab, 0x74, 0x42, 0xef, 0xf0, 0x74, 0x2f, 0x25, // 2096
+ 0xf4, 0xea, 0x6b, 0x55, 0x5b, 0x90, 0xdb, 0x9d, 0xdf, 0x5e, 0x87, 0x0a, 0x40, 0xfb, 0xad, 0x19, // 2112
+ 0x6b, 0xfb, 0xf7, 0xca, 0x60, 0x88, 0xde, 0xda, 0xc1, 0x8f, 0xd6, 0xae, 0xd5, 0x7f, 0xd4, 0x3c, // 2128
+ 0x83, 0xee, 0xd7, 0x16, 0x4c, 0x83, 0x45, 0x33, 0x6b, 0x27, 0xd0, 0x86, 0xd0, 0x1c, 0x2d, 0x6b, // 2144
+ 0xf3, 0xab, 0x7d, 0xf1, 0x85, 0xa9, 0xf5, 0x28, 0xd2, 0xad, 0xef, 0xf3, 0x84, 0x4b, 0x1c, 0x87, // 2160
+ 0xfc, 0x13, 0xa3, 0x3a, 0x72, 0xa2, 0x5a, 0x11, 0x2b, 0xd6, 0x27, 0x71, 0x27, 0xed, 0x81, 0x2d, // 2176
+ 0x6d, 0x66, 0x81, 0x92, 0x87, 0xb4, 0x1b, 0x58, 0x7a, 0xcc, 0x3f, 0x0a, 0xfa, 0x46, 0x4f, 0x4d, // 2192
+ 0x78, 0x5c, 0xf8, 0x2b, 0x48, 0xe3, 0x04, 0x84, 0xcb, 0x5d, 0xf6, 0xb4, 0x6a, 0xb3, 0x65, 0xfc, // 2208
+ 0x42, 0x9e, 0x51, 0x26, 0x23, 0x20, 0xcb, 0x3d, 0x14, 0xf9, 0x81, 0xed, 0x65, 0x16, 0x00, 0x4f, // 2224
+ 0x1a, 0x64, 0x97, 0x66, 0x08, 0xcf, 0x8c, 0x7b, 0xe3, 0x2b, 0xc0, 0x9d, 0xf9, 0x14, 0xf2, 0x1b, // 2240
+ 0xf1, 0x56, 0x6a, 0x16, 0xbf, 0x2c, 0x85, 0x85, 0xcd, 0x78, 0x38, 0x9a, 0xeb, 0x42, 0x6a, 0x02, // 2256
+ 0x34, 0x18, 0x83, 0x17, 0x4e, 0x94, 0x56, 0xf8, 0xb6, 0x82, 0xb5, 0xf3, 0x96, 0xdd, 0x3d, 0xf3, // 2272
+ 0xbe, 0x7f, 0x20, 0x77, 0x3e, 0x7b, 0x19, 0x23, 0x6b, 0x2c, 0xd4, 0x72, 0x73, 0x43, 0x57, 0x7d, // 2288
+ 0xe0, 0xf8, 0xd7, 0x69, 0x4f, 0x17, 0x36, 0x04, 0xf9, 0xc0, 0x90, 0x60, 0x37, 0x45, 0xde, 0xe6, // 2304
+ 0x0c, 0xd8, 0x74, 0x8d, 0xae, 0x9c, 0xa2, 0x6d, 0x74, 0x5d, 0x42, 0xbe, 0x06, 0xf5, 0xd9, 0x64, // 2320
+ 0x6e, 0x02, 0x10, 0xac, 0x89, 0xb0, 0x4c, 0x3b, 0x07, 0x4d, 0x40, 0x7e, 0x24, 0xc5, 0x8a, 0x98, // 2336
+ 0x82, 0x79, 0x8e, 0xa4, 0xa7, 0x82, 0x20, 0x8d, 0x23, 0xfa, 0x27, 0x71, 0xc9, 0xdf, 0xc6, 0x41, // 2352
+ 0x74, 0xa0, 0x4d, 0xf6, 0x91, 0x16, 0xdc, 0x46, 0x8c, 0x5f, 0x29, 0x63, 0x31, 0x59, 0x71, 0x0c, // 2368
+ 0xd8, 0x6f, 0xc2, 0xb6, 0x32, 0x7d, 0xfb, 0xe6, 0x5d, 0x53, 0xa6, 0x7e, 0x15, 0xfc, 0xbb, 0x75, // 2384
+ 0x7c, 0x5d, 0xec, 0xf8, 0xf6, 0x17, 0x1c, 0xec, 0xc7, 0x6b, 0x19, 0xcb, 0xf3, 0x7b, 0xf0, 0x2b, // 2400
+ 0x07, 0xa5, 0xd9, 0x6c, 0x79, 0x54, 0x76, 0x6c, 0x9d, 0x1c, 0xa6, 0x6e, 0x0e, 0xe9, 0x79, 0x0c, // 2416
+ 0xa8, 0x23, 0x6a, 0xa3, 0xdf, 0x1b, 0x30, 0x31, 0x9f, 0xb1, 0x54, 0x7b, 0xfe, 0x6a, 0xcb, 0x66, // 2432
+ 0xaa, 0xdc, 0x65, 0xd0, 0xa2, 0x9e, 0x4a, 0x9a, 0x07, 0x21, 0x6b, 0x81, 0x8f, 0xdb, 0xc4, 0x59, // 2448
+ 0xfa, 0xde, 0x22, 0xc0, 0x04, 0x9c, 0xe3, 0xaa, 0x5b, 0x36, 0x93, 0xe8, 0x3d, 0xbd, 0x7a, 0xa1, // 2464
+ 0x9d, 0x0b, 0x76, 0xb1, 0x0b, 0xc7, 0x9d, 0xfd, 0xcf, 0x98, 0xa8, 0x06, 0xc2, 0xf8, 0x2a, 0xa3, // 2480
+ 0xa1, 0x83, 0xa0, 0xb7, 0x25, 0x72, 0xa5, 0x02, 0xe3, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x49, // 2496
+ 0x02, 0x26, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, // 2512
+ 0x53, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x19, 0x47, 0x6f, 0x6f, 0x67, // 2528
+ 0x6c, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, // 2544
+ 0x73, 0x20, 0x4c, 0x4c, 0x43, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0b, // 2560
+ 0x47, 0x54, 0x53, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x52, 0x31, 0x30, 0x82, 0x02, 0x22, 0x30, // 2576
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, // 2592
+ 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xb6, 0x11, 0x02, 0x8b, // 2608
+ 0x1e, 0xe3, 0xa1, 0x77, 0x9b, 0x3b, 0xdc, 0xbf, 0x94, 0x3e, 0xb7, 0x95, 0xa7, 0x40, 0x3c, 0xa1, // 2624
+ 0xfd, 0x82, 0xf9, 0x7d, 0x32, 0x06, 0x82, 0x71, 0xf6, 0xf6, 0x8c, 0x7f, 0xfb, 0xe8, 0xdb, 0xbc, // 2640
+ 0x6a, 0x2e, 0x97, 0x97, 0xa3, 0x8c, 0x4b, 0xf9, 0x2b, 0xf6, 0xb1, 0xf9, 0xce, 0x84, 0x1d, 0xb1, // 2656
+ 0xf9, 0xc5, 0x97, 0xde, 0xef, 0xb9, 0xf2, 0xa3, 0xe9, 0xbc, 0x12, 0x89, 0x5e, 0xa7, 0xaa, 0x52, // 2672
+ 0xab, 0xf8, 0x23, 0x27, 0xcb, 0xa4, 0xb1, 0x9c, 0x63, 0xdb, 0xd7, 0x99, 0x7e, 0xf0, 0x0a, 0x5e, // 2688
+ 0xeb, 0x68, 0xa6, 0xf4, 0xc6, 0x5a, 0x47, 0x0d, 0x4d, 0x10, 0x33, 0xe3, 0x4e, 0xb1, 0x13, 0xa3, // 2704
+ 0xc8, 0x18, 0x6c, 0x4b, 0xec, 0xfc, 0x09, 0x90, 0xdf, 0x9d, 0x64, 0x29, 0x25, 0x23, 0x07, 0xa1, // 2720
+ 0xb4, 0xd2, 0x3d, 0x2e, 0x60, 0xe0, 0xcf, 0xd2, 0x09, 0x87, 0xbb, 0xcd, 0x48, 0xf0, 0x4d, 0xc2, // 2736
+ 0xc2, 0x7a, 0x88, 0x8a, 0xbb, 0xba, 0xcf, 0x59, 0x19, 0xd6, 0xaf, 0x8f, 0xb0, 0x07, 0xb0, 0x9e, // 2752
+ 0x31, 0xf1, 0x82, 0xc1, 0xc0, 0xdf, 0x2e, 0xa6, 0x6d, 0x6c, 0x19, 0x0e, 0xb5, 0xd8, 0x7e, 0x26, // 2768
+ 0x1a, 0x45, 0x03, 0x3d, 0xb0, 0x79, 0xa4, 0x94, 0x28, 0xad, 0x0f, 0x7f, 0x26, 0xe5, 0xa8, 0x08, // 2784
+ 0xfe, 0x96, 0xe8, 0x3c, 0x68, 0x94, 0x53, 0xee, 0x83, 0x3a, 0x88, 0x2b, 0x15, 0x96, 0x09, 0xb2, // 2800
+ 0xe0, 0x7a, 0x8c, 0x2e, 0x75, 0xd6, 0x9c, 0xeb, 0xa7, 0x56, 0x64, 0x8f, 0x96, 0x4f, 0x68, 0xae, // 2816
+ 0x3d, 0x97, 0xc2, 0x84, 0x8f, 0xc0, 0xbc, 0x40, 0xc0, 0x0b, 0x5c, 0xbd, 0xf6, 0x87, 0xb3, 0x35, // 2832
+ 0x6c, 0xac, 0x18, 0x50, 0x7f, 0x84, 0xe0, 0x4c, 0xcd, 0x92, 0xd3, 0x20, 0xe9, 0x33, 0xbc, 0x52, // 2848
+ 0x99, 0xaf, 0x32, 0xb5, 0x29, 0xb3, 0x25, 0x2a, 0xb4, 0x48, 0xf9, 0x72, 0xe1, 0xca, 0x64, 0xf7, // 2864
+ 0xe6, 0x82, 0x10, 0x8d, 0xe8, 0x9d, 0xc2, 0x8a, 0x88, 0xfa, 0x38, 0x66, 0x8a, 0xfc, 0x63, 0xf9, // 2880
+ 0x01, 0xf9, 0x78, 0xfd, 0x7b, 0x5c, 0x77, 0xfa, 0x76, 0x87, 0xfa, 0xec, 0xdf, 0xb1, 0x0e, 0x79, // 2896
+ 0x95, 0x57, 0xb4, 0xbd, 0x26, 0xef, 0xd6, 0x01, 0xd1, 0xeb, 0x16, 0x0a, 0xbb, 0x8e, 0x0b, 0xb5, // 2912
+ 0xc5, 0xc5, 0x8a, 0x55, 0xab, 0xd3, 0xac, 0xea, 0x91, 0x4b, 0x29, 0xcc, 0x19, 0xa4, 0x32, 0x25, // 2928
+ 0x4e, 0x2a, 0xf1, 0x65, 0x44, 0xd0, 0x02, 0xce, 0xaa, 0xce, 0x49, 0xb4, 0xea, 0x9f, 0x7c, 0x83, // 2944
+ 0xb0, 0x40, 0x7b, 0xe7, 0x43, 0xab, 0xa7, 0x6c, 0xa3, 0x8f, 0x7d, 0x89, 0x81, 0xfa, 0x4c, 0xa5, // 2960
+ 0xff, 0xd5, 0x8e, 0xc3, 0xce, 0x4b, 0xe0, 0xb5, 0xd8, 0xb3, 0x8e, 0x45, 0xcf, 0x76, 0xc0, 0xed, // 2976
+ 0x40, 0x2b, 0xfd, 0x53, 0x0f, 0xb0, 0xa7, 0xd5, 0x3b, 0x0d, 0xb1, 0x8a, 0xa2, 0x03, 0xde, 0x31, // 2992
+ 0xad, 0xcc, 0x77, 0xea, 0x6f, 0x7b, 0x3e, 0xd6, 0xdf, 0x91, 0x22, 0x12, 0xe6, 0xbe, 0xfa, 0xd8, // 3008
+ 0x32, 0xfc, 0x10, 0x63, 0x14, 0x51, 0x72, 0xde, 0x5d, 0xd6, 0x16, 0x93, 0xbd, 0x29, 0x68, 0x33, // 3024
+ 0xef, 0x3a, 0x66, 0xec, 0x07, 0x8a, 0x26, 0xdf, 0x13, 0xd7, 0x57, 0x65, 0x78, 0x27, 0xde, 0x5e, // 3040
+ 0x49, 0x14, 0x00, 0xa2, 0x00, 0x7f, 0x9a, 0xa8, 0x21, 0xb6, 0xa9, 0xb1, 0x95, 0xb0, 0xa5, 0xb9, // 3056
+ 0x0d, 0x16, 0x11, 0xda, 0xc7, 0x6c, 0x48, 0x3c, 0x40, 0xe0, 0x7e, 0x0d, 0x5a, 0xcd, 0x56, 0x3c, // 3072
+ 0xd1, 0x97, 0x05, 0xb9, 0xcb, 0x4b, 0xed, 0x39, 0x4b, 0x9c, 0xc4, 0x3f, 0xd2, 0x55, 0x13, 0x6e, // 3088
+ 0x24, 0xb0, 0xd6, 0x71, 0xfa, 0xf4, 0xc1, 0xba, 0xcc, 0xed, 0x1b, 0xf5, 0xfe, 0x81, 0x41, 0xd8, // 3104
+ 0x00, 0x98, 0x3d, 0x3a, 0xc8, 0xae, 0x7a, 0x98, 0x37, 0x18, 0x05, 0x95, 0x02, 0x03, 0x01, 0x00, // 3120
+ 0x01, 0x00, 0x49, 0x02, 0x26, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, // 3136
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x19, 0x47, // 3152
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, // 3168
+ 0x69, 0x63, 0x65, 0x73, 0x20, 0x4c, 0x4c, 0x43, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, // 3184
+ 0x03, 0x13, 0x0b, 0x47, 0x54, 0x53, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x52, 0x32, 0x30, 0x82, // 3200
+ 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, // 3216
+ 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xce, // 3232
+ 0xde, 0xfd, 0xa6, 0xfb, 0xec, 0xec, 0x14, 0x34, 0x3c, 0x07, 0x06, 0x5a, 0x6c, 0x59, 0xf7, 0x19, // 3248
+ 0x35, 0xdd, 0xf7, 0xc1, 0x9d, 0x55, 0xaa, 0xd3, 0xcd, 0x3b, 0xa4, 0x93, 0x72, 0xef, 0x0a, 0xfa, // 3264
+ 0x6d, 0x9d, 0xf6, 0xf0, 0x85, 0x80, 0x5b, 0xa1, 0x48, 0x52, 0x9f, 0x39, 0xc5, 0xb7, 0xee, 0x28, // 3280
+ 0xac, 0xef, 0xcb, 0x76, 0x68, 0x14, 0xb9, 0xdf, 0xad, 0x01, 0x6c, 0x99, 0x1f, 0xc4, 0x22, 0x1d, // 3296
+ 0x9f, 0xfe, 0x72, 0x77, 0xe0, 0x2c, 0x5b, 0xaf, 0xe4, 0x04, 0xbf, 0x4f, 0x72, 0xa0, 0x1a, 0x34, // 3312
+ 0x98, 0xe8, 0x39, 0x68, 0xec, 0x95, 0x25, 0x7b, 0x76, 0xa1, 0xe6, 0x69, 0xb9, 0x85, 0x19, 0xbd, // 3328
+ 0x89, 0x8c, 0xfe, 0xad, 0xed, 0x36, 0xea, 0x73, 0xbc, 0xff, 0x83, 0xe2, 0xcb, 0x7d, 0xc1, 0xd2, // 3344
+ 0xce, 0x4a, 0xb3, 0x8d, 0x05, 0x9e, 0x8b, 0x49, 0x93, 0xdf, 0xc1, 0x5b, 0xd0, 0x6e, 0x5e, 0xf0, // 3360
+ 0x2e, 0x30, 0x2e, 0x82, 0xfc, 0xfa, 0xbc, 0xb4, 0x17, 0x0a, 0x48, 0xe5, 0x88, 0x9b, 0xc5, 0x9b, // 3376
+ 0x6b, 0xde, 0xb0, 0xca, 0xb4, 0x03, 0xf0, 0xda, 0xf4, 0x90, 0xb8, 0x65, 0x64, 0xf7, 0x5c, 0x4c, // 3392
+ 0xad, 0xe8, 0x7e, 0x66, 0x5e, 0x99, 0xd7, 0xb8, 0xc2, 0x3e, 0xc8, 0xd0, 0x13, 0x9d, 0xad, 0xee, // 3408
+ 0xe4, 0x45, 0x7b, 0x89, 0x55, 0xf7, 0x8a, 0x1f, 0x62, 0x52, 0x84, 0x12, 0xb3, 0xc2, 0x40, 0x97, // 3424
+ 0xe3, 0x8a, 0x1f, 0x47, 0x91, 0xa6, 0x74, 0x5a, 0xd2, 0xf8, 0xb1, 0x63, 0x28, 0x10, 0xb8, 0xb3, // 3440
+ 0x09, 0xb8, 0x56, 0x77, 0x40, 0xa2, 0x26, 0x98, 0x79, 0xc6, 0xfe, 0xdf, 0x25, 0xee, 0x3e, 0xe5, // 3456
+ 0xa0, 0x7f, 0xd4, 0x61, 0x0f, 0x51, 0x4b, 0x3c, 0x3f, 0x8c, 0xda, 0xe1, 0x70, 0x74, 0xd8, 0xc2, // 3472
+ 0x68, 0xa1, 0xf9, 0xc1, 0x0c, 0xe9, 0xa1, 0xe2, 0x7f, 0xbb, 0x55, 0x3c, 0x76, 0x06, 0xee, 0x6a, // 3488
+ 0x4e, 0xcc, 0x92, 0x88, 0x30, 0x4d, 0x9a, 0xbd, 0x4f, 0x0b, 0x48, 0x9a, 0x84, 0xb5, 0x98, 0xa3, // 3504
+ 0xd5, 0xfb, 0x73, 0xc1, 0x57, 0x61, 0xdd, 0x28, 0x56, 0x75, 0x13, 0xae, 0x87, 0x8e, 0xe7, 0x0c, // 3520
+ 0x51, 0x09, 0x10, 0x75, 0x88, 0x4c, 0xbc, 0x8d, 0xf9, 0x7b, 0x3c, 0xd4, 0x22, 0x48, 0x1f, 0x2a, // 3536
+ 0xdc, 0xeb, 0x6b, 0xbb, 0x44, 0xb1, 0xcb, 0x33, 0x71, 0x32, 0x46, 0xaf, 0xad, 0x4a, 0xf1, 0x8c, // 3552
+ 0xe8, 0x74, 0x3a, 0xac, 0xe7, 0x1a, 0x22, 0x73, 0x80, 0xd2, 0x30, 0xf7, 0x25, 0x42, 0xc7, 0x22, // 3568
+ 0x3b, 0x3b, 0x12, 0xad, 0x96, 0x2e, 0xc6, 0xc3, 0x76, 0x07, 0xaa, 0x20, 0xb7, 0x35, 0x49, 0x57, // 3584
+ 0xe9, 0x92, 0x49, 0xe8, 0x76, 0x16, 0x72, 0x31, 0x67, 0x2b, 0x96, 0x7e, 0x8a, 0xa3, 0xc7, 0x94, // 3600
+ 0x56, 0x22, 0xbf, 0x6a, 0x4b, 0x7e, 0x01, 0x21, 0xb2, 0x23, 0x32, 0xdf, 0xe4, 0x9a, 0x44, 0x6d, // 3616
+ 0x59, 0x5b, 0x5d, 0xf5, 0x00, 0xa0, 0x1c, 0x9b, 0xc6, 0x78, 0x97, 0x8d, 0x90, 0xff, 0x9b, 0xc8, // 3632
+ 0xaa, 0xb4, 0xaf, 0x11, 0x51, 0x39, 0x5e, 0xd9, 0xfb, 0x67, 0xad, 0xd5, 0x5b, 0x11, 0x9d, 0x32, // 3648
+ 0x9a, 0x1b, 0xbd, 0xd5, 0xba, 0x5b, 0xa5, 0xc9, 0xcb, 0x25, 0x69, 0x53, 0x55, 0x27, 0x5c, 0xe0, // 3664
+ 0xca, 0x36, 0xcb, 0x88, 0x61, 0xfb, 0x1e, 0xb7, 0xd0, 0xcb, 0xee, 0x16, 0xfb, 0xd3, 0xa6, 0x4c, // 3680
+ 0xde, 0x92, 0xa5, 0xd4, 0xe2, 0xdf, 0xf5, 0x06, 0x54, 0xde, 0x2e, 0x9d, 0x4b, 0xb4, 0x93, 0x30, // 3696
+ 0xaa, 0x81, 0xce, 0xdd, 0x1a, 0xdc, 0x51, 0x73, 0x0d, 0x4f, 0x70, 0xe9, 0xe5, 0xb6, 0x16, 0x21, // 3712
+ 0x19, 0x79, 0xb2, 0xe6, 0x89, 0x0b, 0x75, 0x64, 0xca, 0xd5, 0xab, 0xbc, 0x09, 0xc1, 0x18, 0xa1, // 3728
+ 0xff, 0xd4, 0x54, 0xa1, 0x85, 0x3c, 0xfd, 0x14, 0x24, 0x03, 0xb2, 0x87, 0xd3, 0xa4, 0xb7, 0x02, // 3744
+ 0x03, 0x01, 0x00, 0x01, 0x00, 0x49, 0x00, 0x78, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, // 3760
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0a, // 3776
+ 0x13, 0x19, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, // 3792
+ 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x4c, 0x4c, 0x43, 0x31, 0x14, 0x30, 0x12, 0x06, // 3808
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x0b, 0x47, 0x54, 0x53, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x52, // 3824
+ 0x33, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, // 3840
+ 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0x1f, 0x4f, 0x33, 0x87, 0x33, 0x29, 0x8a, // 3856
+ 0xa1, 0x84, 0xde, 0xcb, 0xc7, 0x21, 0x58, 0x41, 0x89, 0xea, 0x56, 0x9d, 0x2b, 0x4b, 0x85, 0xc6, // 3872
+ 0x1d, 0x4c, 0x27, 0xbc, 0x7f, 0x26, 0x51, 0x72, 0x6f, 0xe2, 0x9f, 0xd6, 0xa3, 0xca, 0xcc, 0x45, // 3888
+ 0x14, 0x46, 0x8b, 0xad, 0xef, 0x7e, 0x86, 0x8c, 0xec, 0xb1, 0x7e, 0x2f, 0xff, 0xa9, 0x71, 0x9d, // 3904
+ 0x18, 0x84, 0x45, 0x04, 0x41, 0x55, 0x6e, 0x2b, 0xea, 0x26, 0x7f, 0xbb, 0x90, 0x01, 0xe3, 0x4b, // 3920
+ 0x19, 0xba, 0xe4, 0x54, 0x96, 0x45, 0x09, 0xb1, 0xd5, 0x6c, 0x91, 0x44, 0xad, 0x84, 0x13, 0x8e, // 3936
+ 0x9a, 0x8c, 0x0d, 0x80, 0x0c, 0x32, 0xf6, 0xe0, 0x27, 0x00, 0x49, 0x00, 0x78, 0x30, 0x47, 0x31, // 3952
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x22, 0x30, 0x20, // 3968
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x19, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x72, // 3984
+ 0x75, 0x73, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x4c, 0x4c, 0x43, // 4000
+ 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0b, 0x47, 0x54, 0x53, 0x20, 0x52, // 4016
+ 0x6f, 0x6f, 0x74, 0x20, 0x52, 0x34, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, // 4032
+ 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xf3, 0x74, // 4048
+ 0x73, 0xa7, 0x68, 0x8b, 0x60, 0xae, 0x43, 0xb8, 0x35, 0xc5, 0x81, 0x30, 0x7b, 0x4b, 0x49, 0x9d, // 4064
+ 0xfb, 0xc1, 0x61, 0xce, 0xe6, 0xde, 0x46, 0xbd, 0x6b, 0xd5, 0x61, 0x18, 0x35, 0xae, 0x40, 0xdd, // 4080
+ 0x73, 0xf7, 0x89, 0x91, 0x30, 0x5a, 0xeb, 0x3c, 0xee, 0x85, 0x7c, 0xa2, 0x40, 0x76, 0x3b, 0xa9, // 4096
+ 0xc6, 0xb8, 0x47, 0xd8, 0x2a, 0xe7, 0x92, 0x91, 0x6a, 0x73, 0xe9, 0xb1, 0x72, 0x39, 0x9f, 0x29, // 4112
+ 0x9f, 0xa2, 0x98, 0xd3, 0x5f, 0x5e, 0x58, 0x86, 0x65, 0x0f, 0xa1, 0x84, 0x65, 0x06, 0xd1, 0xdc, // 4128
+ 0x8b, 0xc9, 0xc7, 0x73, 0xc8, 0x8c, 0x6a, 0x2f, 0xe5, 0xc4, 0xab, 0xd1, 0x1d, 0x8a, 0x00, 0x4c, // 4144
+ 0x02, 0x26, 0x30, 0x4a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, // 4160
+ 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x49, 0x64, 0x65, 0x6e, // 4176
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, // 4192
+ 0x49, 0x64, 0x65, 0x6e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x72, // 4208
+ 0x63, 0x69, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31, 0x30, 0x82, // 4224
+ 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, // 4240
+ 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xa7, // 4256
+ 0x50, 0x19, 0xde, 0x3f, 0x99, 0x3d, 0xd4, 0x33, 0x46, 0xf1, 0x6f, 0x51, 0x61, 0x82, 0xb2, 0xa9, // 4272
+ 0x4f, 0x8f, 0x67, 0x89, 0x5d, 0x84, 0xd9, 0x53, 0xdd, 0x0c, 0x28, 0xd9, 0xd7, 0xf0, 0xff, 0xae, // 4288
+ 0x95, 0x43, 0x72, 0x99, 0xf9, 0xb5, 0x5d, 0x7c, 0x8a, 0xc1, 0x42, 0xe1, 0x31, 0x50, 0x74, 0xd1, // 4304
+ 0x81, 0x0d, 0x7c, 0xcd, 0x9b, 0x21, 0xab, 0x43, 0xe2, 0xac, 0xad, 0x5e, 0x86, 0x6e, 0xf3, 0x09, // 4320
+ 0x8a, 0x1f, 0x5a, 0x32, 0xbd, 0xa2, 0xeb, 0x94, 0xf9, 0xe8, 0x5c, 0x0a, 0xec, 0xff, 0x98, 0xd2, // 4336
+ 0xaf, 0x71, 0xb3, 0xb4, 0x53, 0x9f, 0x4e, 0x87, 0xef, 0x92, 0xbc, 0xbd, 0xec, 0x4f, 0x32, 0x30, // 4352
+ 0x88, 0x4b, 0x17, 0x5e, 0x57, 0xc4, 0x53, 0xc2, 0xf6, 0x02, 0x97, 0x8d, 0xd9, 0x62, 0x2b, 0xbf, // 4368
+ 0x24, 0x1f, 0x62, 0x8d, 0xdf, 0xc3, 0xb8, 0x29, 0x4b, 0x49, 0x78, 0x3c, 0x93, 0x60, 0x88, 0x22, // 4384
+ 0xfc, 0x99, 0xda, 0x36, 0xc8, 0xc2, 0xa2, 0xd4, 0x2c, 0x54, 0x00, 0x67, 0x35, 0x6e, 0x73, 0xbf, // 4400
+ 0x02, 0x58, 0xf0, 0xa4, 0xdd, 0xe5, 0xb0, 0xa2, 0x26, 0x7a, 0xca, 0xe0, 0x36, 0xa5, 0x19, 0x16, // 4416
+ 0xf5, 0xfd, 0xb7, 0xef, 0xae, 0x3f, 0x40, 0xf5, 0x6d, 0x5a, 0x04, 0xfd, 0xce, 0x34, 0xca, 0x24, // 4432
+ 0xdc, 0x74, 0x23, 0x1b, 0x5d, 0x33, 0x13, 0x12, 0x5d, 0xc4, 0x01, 0x25, 0xf6, 0x30, 0xdd, 0x02, // 4448
+ 0x5d, 0x9f, 0xe0, 0xd5, 0x47, 0xbd, 0xb4, 0xeb, 0x1b, 0xa1, 0xbb, 0x49, 0x49, 0xd8, 0x9f, 0x5b, // 4464
+ 0x02, 0xf3, 0x8a, 0xe4, 0x24, 0x90, 0xe4, 0x62, 0x4f, 0x4f, 0xc1, 0xaf, 0x8b, 0x0e, 0x74, 0x17, // 4480
+ 0xa8, 0xd1, 0x72, 0x88, 0x6a, 0x7a, 0x01, 0x49, 0xcc, 0xb4, 0x46, 0x79, 0xc6, 0x17, 0xb1, 0xda, // 4496
+ 0x98, 0x1e, 0x07, 0x59, 0xfa, 0x75, 0x21, 0x85, 0x65, 0xdd, 0x90, 0x56, 0xce, 0xfb, 0xab, 0xa5, // 4512
+ 0x60, 0x9d, 0xc4, 0x9d, 0xf9, 0x52, 0xb0, 0x8b, 0xbd, 0x87, 0xf9, 0x8f, 0x2b, 0x23, 0x0a, 0x23, // 4528
+ 0x76, 0x3b, 0xf7, 0x33, 0xe1, 0xc9, 0x00, 0xf3, 0x69, 0xf9, 0x4b, 0xa2, 0xe0, 0x4e, 0xbc, 0x7e, // 4544
+ 0x93, 0x39, 0x84, 0x07, 0xf7, 0x44, 0x70, 0x7e, 0xfe, 0x07, 0x5a, 0xe5, 0xb1, 0xac, 0xd1, 0x18, // 4560
+ 0xcc, 0xf2, 0x35, 0xe5, 0x49, 0x49, 0x08, 0xca, 0x56, 0xc9, 0x3d, 0xfb, 0x0f, 0x18, 0x7d, 0x8b, // 4576
+ 0x3b, 0xc1, 0x13, 0xc2, 0x4d, 0x8f, 0xc9, 0x4f, 0x0e, 0x37, 0xe9, 0x1f, 0xa1, 0x0e, 0x6a, 0xdf, // 4592
+ 0x62, 0x2e, 0xcb, 0x35, 0x06, 0x51, 0x79, 0x2c, 0xc8, 0x25, 0x38, 0xf4, 0xfa, 0x4b, 0xa7, 0x89, // 4608
+ 0x5c, 0x9c, 0xd2, 0xe3, 0x0d, 0x39, 0x86, 0x4a, 0x74, 0x7c, 0xd5, 0x59, 0x87, 0xc2, 0x3f, 0x4e, // 4624
+ 0x0c, 0x5c, 0x52, 0xf4, 0x3d, 0xf7, 0x52, 0x82, 0xf1, 0xea, 0xa3, 0xac, 0xfd, 0x49, 0x34, 0x1a, // 4640
+ 0x28, 0xf3, 0x41, 0x88, 0x3a, 0x13, 0xee, 0xe8, 0xde, 0xff, 0x99, 0x1d, 0x5f, 0xba, 0xcb, 0xe8, // 4656
+ 0x1e, 0xf2, 0xb9, 0x50, 0x60, 0xc0, 0x31, 0xd3, 0x73, 0xe5, 0xef, 0xbe, 0xa0, 0xed, 0x33, 0x0b, // 4672
+ 0x74, 0xbe, 0x20, 0x20, 0xc4, 0x67, 0x6c, 0xf0, 0x08, 0x03, 0x7a, 0x55, 0x80, 0x7f, 0x46, 0x4e, // 4688
+ 0x96, 0xa7, 0xf4, 0x1e, 0x3e, 0xe1, 0xf6, 0xd8, 0x09, 0xe1, 0x33, 0x64, 0x2b, 0x63, 0xd7, 0x32, // 4704
+ 0x5e, 0x9f, 0xf9, 0xc0, 0x7b, 0x0f, 0x78, 0x6f, 0x97, 0xbc, 0x93, 0x9a, 0xf9, 0x9c, 0x12, 0x90, // 4720
+ 0x78, 0x7a, 0x80, 0x87, 0x15, 0xd7, 0x72, 0x74, 0x9c, 0x55, 0x74, 0x78, 0xb1, 0xba, 0xe1, 0x6e, // 4736
+ 0x70, 0x04, 0xba, 0x4f, 0xa0, 0xba, 0x68, 0xc3, 0x7b, 0xff, 0x31, 0xf0, 0x73, 0x3d, 0x3d, 0x94, // 4752
+ 0x2a, 0xb1, 0x0b, 0x41, 0x0e, 0xa0, 0xfe, 0x4d, 0x88, 0x65, 0x6b, 0x79, 0x33, 0xb4, 0xd7, 0x02, // 4768
+ 0x03, 0x01, 0x00, 0x01, 0x00, 0x4e, 0x01, 0x26, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, // 4784
+ 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, // 4800
+ 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x52, 0x33, 0x31, 0x13, 0x30, 0x11, // 4816
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, // 4832
+ 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, // 4848
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, // 4864
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, // 4880
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcc, 0x25, 0x76, 0x90, 0x79, 0x06, 0x78, 0x22, 0x16, // 4896
+ 0xf5, 0xc0, 0x83, 0xb6, 0x84, 0xca, 0x28, 0x9e, 0xfd, 0x05, 0x76, 0x11, 0xc5, 0xad, 0x88, 0x72, // 4912
+ 0xfc, 0x46, 0x02, 0x43, 0xc7, 0xb2, 0x8a, 0x9d, 0x04, 0x5f, 0x24, 0xcb, 0x2e, 0x4b, 0xe1, 0x60, // 4928
+ 0x82, 0x46, 0xe1, 0x52, 0xab, 0x0c, 0x81, 0x47, 0x70, 0x6c, 0xdd, 0x64, 0xd1, 0xeb, 0xf5, 0x2c, // 4944
+ 0xa3, 0x0f, 0x82, 0x3d, 0x0c, 0x2b, 0xae, 0x97, 0xd7, 0xb6, 0x14, 0x86, 0x10, 0x79, 0xbb, 0x3b, // 4960
+ 0x13, 0x80, 0x77, 0x8c, 0x08, 0xe1, 0x49, 0xd2, 0x6a, 0x62, 0x2f, 0x1f, 0x5e, 0xfa, 0x96, 0x68, // 4976
+ 0xdf, 0x89, 0x27, 0x95, 0x38, 0x9f, 0x06, 0xd7, 0x3e, 0xc9, 0xcb, 0x26, 0x59, 0x0d, 0x73, 0xde, // 4992
+ 0xb0, 0xc8, 0xe9, 0x26, 0x0e, 0x83, 0x15, 0xc6, 0xef, 0x5b, 0x8b, 0xd2, 0x04, 0x60, 0xca, 0x49, // 5008
+ 0xa6, 0x28, 0xf6, 0x69, 0x3b, 0xf6, 0xcb, 0xc8, 0x28, 0x91, 0xe5, 0x9d, 0x8a, 0x61, 0x57, 0x37, // 5024
+ 0xac, 0x74, 0x14, 0xdc, 0x74, 0xe0, 0x3a, 0xee, 0x72, 0x2f, 0x2e, 0x9c, 0xfb, 0xd0, 0xbb, 0xbf, // 5040
+ 0xf5, 0x3d, 0x00, 0xe1, 0x06, 0x33, 0xe8, 0x82, 0x2b, 0xae, 0x53, 0xa6, 0x3a, 0x16, 0x73, 0x8c, // 5056
+ 0xdd, 0x41, 0x0e, 0x20, 0x3a, 0xc0, 0xb4, 0xa7, 0xa1, 0xe9, 0xb2, 0x4f, 0x90, 0x2e, 0x32, 0x60, // 5072
+ 0xe9, 0x57, 0xcb, 0xb9, 0x04, 0x92, 0x68, 0x68, 0xe5, 0x38, 0x26, 0x60, 0x75, 0xb2, 0x9f, 0x77, // 5088
+ 0xff, 0x91, 0x14, 0xef, 0xae, 0x20, 0x49, 0xfc, 0xad, 0x40, 0x15, 0x48, 0xd1, 0x02, 0x31, 0x61, // 5104
+ 0x19, 0x5e, 0xb8, 0x97, 0xef, 0xad, 0x77, 0xb7, 0x64, 0x9a, 0x7a, 0xbf, 0x5f, 0xc1, 0x13, 0xef, // 5120
+ 0x9b, 0x62, 0xfb, 0x0d, 0x6c, 0xe0, 0x54, 0x69, 0x16, 0xa9, 0x03, 0xda, 0x6e, 0xe9, 0x83, 0x93, // 5136
+ 0x71, 0x76, 0xc6, 0x69, 0x85, 0x82, 0x17, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x4e, 0x02, 0x26, // 5152
+ 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, // 5168
+ 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, // 5184
+ 0x2d, 0x20, 0x52, 0x36, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47, // 5200
+ 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, // 5216
+ 0x04, 0x03, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x30, 0x82, // 5232
+ 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, // 5248
+ 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0x95, // 5264
+ 0x07, 0xe8, 0x73, 0xca, 0x66, 0xf9, 0xec, 0x14, 0xca, 0x7b, 0x3c, 0xf7, 0x0d, 0x08, 0xf1, 0xb4, // 5280
+ 0x45, 0x0b, 0x2c, 0x82, 0xb4, 0x48, 0xc6, 0xeb, 0x5b, 0x3c, 0xae, 0x83, 0xb8, 0x41, 0x92, 0x33, // 5296
+ 0x14, 0xa4, 0x6f, 0x7f, 0xe9, 0x2a, 0xcc, 0xc6, 0xb0, 0x88, 0x6b, 0xc5, 0xb6, 0x89, 0xd1, 0xc6, // 5312
+ 0xb2, 0xff, 0x14, 0xce, 0x51, 0x14, 0x21, 0xec, 0x4a, 0xdd, 0x1b, 0x5a, 0xc6, 0xd6, 0x87, 0xee, // 5328
+ 0x4d, 0x3a, 0x15, 0x06, 0xed, 0x64, 0x66, 0x0b, 0x92, 0x80, 0xca, 0x44, 0xde, 0x73, 0x94, 0x4e, // 5344
+ 0xf3, 0xa7, 0x89, 0x7f, 0x4f, 0x78, 0x63, 0x08, 0xc8, 0x12, 0x50, 0x6d, 0x42, 0x66, 0x2f, 0x4d, // 5360
+ 0xb9, 0x79, 0x28, 0x4d, 0x52, 0x1a, 0x8a, 0x1a, 0x80, 0xb7, 0x19, 0x81, 0x0e, 0x7e, 0xc4, 0x8a, // 5376
+ 0xbc, 0x64, 0x4c, 0x21, 0x1c, 0x43, 0x68, 0xd7, 0x3d, 0x3c, 0x8a, 0xc5, 0xb2, 0x66, 0xd5, 0x90, // 5392
+ 0x9a, 0xb7, 0x31, 0x06, 0xc5, 0xbe, 0xe2, 0x6d, 0x32, 0x06, 0xa6, 0x1e, 0xf9, 0xb9, 0xeb, 0xaa, // 5408
+ 0xa3, 0xb8, 0xbf, 0xbe, 0x82, 0x63, 0x50, 0xd0, 0xf0, 0x18, 0x89, 0xdf, 0xe4, 0x0f, 0x79, 0xf5, // 5424
+ 0xea, 0xa2, 0x1f, 0x2a, 0xd2, 0x70, 0x2e, 0x7b, 0xe7, 0xbc, 0x93, 0xbb, 0x6d, 0x53, 0xe2, 0x48, // 5440
+ 0x7c, 0x8c, 0x10, 0x07, 0x38, 0xff, 0x66, 0xb2, 0x77, 0x61, 0x7e, 0xe0, 0xea, 0x8c, 0x3c, 0xaa, // 5456
+ 0xb4, 0xa4, 0xf6, 0xf3, 0x95, 0x4a, 0x12, 0x07, 0x6d, 0xfd, 0x8c, 0xb2, 0x89, 0xcf, 0xd0, 0xa0, // 5472
+ 0x61, 0x77, 0xc8, 0x58, 0x74, 0xb0, 0xd4, 0x23, 0x3a, 0xf7, 0x5d, 0x3a, 0xca, 0xa2, 0xdb, 0x9d, // 5488
+ 0x09, 0xde, 0x5d, 0x44, 0x2d, 0x90, 0xf1, 0x81, 0xcd, 0x57, 0x92, 0xfa, 0x7e, 0xbc, 0x50, 0x04, // 5504
+ 0x63, 0x34, 0xdf, 0x6b, 0x93, 0x18, 0xbe, 0x6b, 0x36, 0xb2, 0x39, 0xe4, 0xac, 0x24, 0x36, 0xb7, // 5520
+ 0xf0, 0xef, 0xb6, 0x1c, 0x13, 0x57, 0x93, 0xb6, 0xde, 0xb2, 0xf8, 0xe2, 0x85, 0xb7, 0x73, 0xa2, // 5536
+ 0xb8, 0x35, 0xaa, 0x45, 0xf2, 0xe0, 0x9d, 0x36, 0xa1, 0x6f, 0x54, 0x8a, 0xf1, 0x72, 0x56, 0x6e, // 5552
+ 0x2e, 0x88, 0xc5, 0x51, 0x42, 0x44, 0x15, 0x94, 0xee, 0xa3, 0xc5, 0x38, 0x96, 0x9b, 0x4e, 0x4e, // 5568
+ 0x5a, 0x0b, 0x47, 0xf3, 0x06, 0x36, 0x49, 0x77, 0x30, 0xbc, 0x71, 0x37, 0xe5, 0xa6, 0xec, 0x21, // 5584
+ 0x08, 0x75, 0xfc, 0xe6, 0x61, 0x16, 0x3f, 0x77, 0xd5, 0xd9, 0x91, 0x97, 0x84, 0x0a, 0x6c, 0xd4, // 5600
+ 0x02, 0x4d, 0x74, 0xc0, 0x14, 0xed, 0xfd, 0x39, 0xfb, 0x83, 0xf2, 0x5e, 0x14, 0xa1, 0x04, 0xb0, // 5616
+ 0x0b, 0xe9, 0xfe, 0xee, 0x8f, 0xe1, 0x6e, 0x0b, 0xb2, 0x08, 0xb3, 0x61, 0x66, 0x09, 0x6a, 0xb1, // 5632
+ 0x06, 0x3a, 0x65, 0x96, 0x59, 0xc0, 0xf0, 0x35, 0xfd, 0xc9, 0xda, 0x28, 0x8d, 0x1a, 0x11, 0x87, // 5648
+ 0x70, 0x81, 0x0a, 0xa8, 0x9a, 0x75, 0x1d, 0x9e, 0x3a, 0x86, 0x05, 0x00, 0x9e, 0xdb, 0x80, 0xd6, // 5664
+ 0x25, 0xf9, 0xdc, 0x05, 0x9e, 0x27, 0x59, 0x4c, 0x76, 0x39, 0x5b, 0xea, 0xf9, 0xa5, 0xa1, 0xd8, // 5680
+ 0x83, 0x0f, 0xd1, 0xff, 0xdf, 0x30, 0x11, 0xf9, 0x85, 0xcf, 0x33, 0x48, 0xf5, 0xca, 0x6d, 0x64, // 5696
+ 0x14, 0x2c, 0x7a, 0x58, 0x4f, 0xd3, 0x4b, 0x08, 0x49, 0xc5, 0x95, 0x64, 0x1a, 0x63, 0x0e, 0x79, // 5712
+ 0x3d, 0xf5, 0xb3, 0x8c, 0xca, 0x58, 0xad, 0x9c, 0x42, 0x45, 0x79, 0x6e, 0x0e, 0x87, 0x19, 0x5c, // 5728
+ 0x54, 0xb1, 0x65, 0xb6, 0xbf, 0x8c, 0x9b, 0xdc, 0x13, 0xe9, 0x0d, 0x6f, 0xb8, 0x2e, 0xdc, 0x67, // 5744
+ 0x6e, 0xc9, 0x8b, 0x11, 0xb5, 0x84, 0x14, 0x8a, 0x00, 0x19, 0x70, 0x83, 0x79, 0x91, 0x97, 0x91, // 5760
+ 0xd4, 0x1a, 0x27, 0xbf, 0x37, 0x1e, 0x32, 0x07, 0xd8, 0x14, 0x63, 0x3c, 0x28, 0x4c, 0xaf, 0x02, // 5776
+ 0x03, 0x01, 0x00, 0x01, 0x00, 0x4f, 0x02, 0x26, 0x30, 0x4d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, // 5792
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, // 5808
+ 0x13, 0x09, 0x49, 0x64, 0x65, 0x6e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x2a, 0x30, 0x28, 0x06, // 5824
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x49, 0x64, 0x65, 0x6e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, // 5840
+ 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x53, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x52, 0x6f, // 5856
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, // 5872
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, // 5888
+ 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xb6, 0x22, 0x94, 0xfc, 0xa4, 0x48, 0xaf, 0xe8, // 5904
+ 0x47, 0x6b, 0x0a, 0xfb, 0x27, 0x76, 0xe4, 0xf2, 0x3f, 0x8a, 0x3b, 0x7a, 0x4a, 0x2c, 0x31, 0x2a, // 5920
+ 0x8c, 0x8d, 0xb0, 0xa9, 0xc3, 0x31, 0x6b, 0xa8, 0x77, 0x76, 0x84, 0x26, 0xb6, 0xac, 0x81, 0x42, // 5936
+ 0x0d, 0x08, 0xeb, 0x55, 0x58, 0xbb, 0x7a, 0xf8, 0xbc, 0x65, 0x7d, 0xf2, 0xa0, 0x6d, 0x8b, 0xa8, // 5952
+ 0x47, 0xe9, 0x62, 0x76, 0x1e, 0x11, 0xee, 0x08, 0x14, 0xd1, 0xb2, 0x44, 0x16, 0xf4, 0xea, 0xd0, // 5968
+ 0xfa, 0x1e, 0x2f, 0x5e, 0xdb, 0xcb, 0x73, 0x41, 0xae, 0xbc, 0x00, 0xb0, 0x4a, 0x2b, 0x40, 0xb2, // 5984
+ 0xac, 0xe1, 0x3b, 0x4b, 0xc2, 0x2d, 0x9d, 0xe4, 0xa1, 0x9b, 0xec, 0x1a, 0x3a, 0x1e, 0xf0, 0x08, // 6000
+ 0xb3, 0xd0, 0xe4, 0x24, 0x35, 0x07, 0x9f, 0x9c, 0xb4, 0xc9, 0x52, 0x6d, 0xdb, 0x07, 0xca, 0x8f, // 6016
+ 0xb5, 0x5b, 0xf0, 0x83, 0xf3, 0x4f, 0xc7, 0x2d, 0xa5, 0xc8, 0xad, 0xcb, 0x95, 0x20, 0xa4, 0x31, // 6032
+ 0x28, 0x57, 0x58, 0x5a, 0xe4, 0x8d, 0x1b, 0x9a, 0xab, 0x9e, 0x0d, 0x0c, 0xf2, 0x0a, 0x33, 0x39, // 6048
+ 0x22, 0x39, 0x0a, 0x97, 0x2e, 0xf3, 0x53, 0x77, 0xb9, 0x44, 0x45, 0xfd, 0x84, 0xcb, 0x36, 0x20, // 6064
+ 0x81, 0x59, 0x2d, 0x9a, 0x6f, 0x6d, 0x48, 0x48, 0x61, 0xca, 0x4c, 0xdf, 0x53, 0xd1, 0xaf, 0x52, // 6080
+ 0xbc, 0x44, 0x9f, 0xab, 0x2f, 0x6b, 0x83, 0x72, 0xef, 0x75, 0x80, 0xda, 0x06, 0x33, 0x1b, 0x5d, // 6096
+ 0xc8, 0xda, 0x63, 0xc6, 0x4d, 0xcd, 0xac, 0x66, 0x31, 0xcd, 0xd1, 0xde, 0x3e, 0x87, 0x10, 0x36, // 6112
+ 0xe1, 0xb9, 0xa4, 0x7a, 0xef, 0x60, 0x50, 0xb2, 0xcb, 0xca, 0xa6, 0x56, 0xe0, 0x37, 0xaf, 0xab, // 6128
+ 0x34, 0x13, 0x39, 0x25, 0xe8, 0x39, 0x66, 0xe4, 0x98, 0x7a, 0xaa, 0x12, 0x98, 0x9c, 0x59, 0x66, // 6144
+ 0x86, 0x3e, 0xad, 0xf1, 0xb0, 0xca, 0x3e, 0x06, 0x0f, 0x7b, 0xf0, 0x11, 0x4b, 0x37, 0xa0, 0x44, // 6160
+ 0x6d, 0x7b, 0xcb, 0xa8, 0x8c, 0x71, 0xf4, 0xd5, 0xb5, 0x91, 0x36, 0xcc, 0xf0, 0x15, 0xc6, 0x2b, // 6176
+ 0xde, 0x51, 0x17, 0xb1, 0x97, 0x4c, 0x50, 0x3d, 0xb1, 0x95, 0x59, 0x7c, 0x05, 0x7d, 0x2d, 0x21, // 6192
+ 0xd5, 0x00, 0xbf, 0x01, 0x67, 0xa2, 0x5e, 0x7b, 0xa6, 0x5c, 0xf2, 0xf7, 0x22, 0xf1, 0x90, 0x0d, // 6208
+ 0x93, 0xdb, 0xaa, 0x44, 0x51, 0x66, 0xcc, 0x7d, 0x76, 0x03, 0xeb, 0x6a, 0xa8, 0x2a, 0x38, 0x19, // 6224
+ 0x97, 0x76, 0x0d, 0x6b, 0x8a, 0x61, 0xf9, 0xbc, 0xf6, 0xee, 0x76, 0xfd, 0x70, 0x2b, 0xdd, 0x29, // 6240
+ 0x3c, 0xf8, 0x0a, 0x1e, 0x5b, 0x42, 0x1c, 0x8b, 0x56, 0x2f, 0x55, 0x1b, 0x1c, 0xa1, 0x2e, 0xb5, // 6256
+ 0xc7, 0x16, 0xe6, 0xf8, 0xaa, 0x3c, 0x92, 0x8e, 0x69, 0xb6, 0x01, 0xc1, 0xb5, 0x86, 0x9d, 0x89, // 6272
+ 0x0f, 0x0b, 0x38, 0x94, 0x54, 0xe8, 0xea, 0xdc, 0x9e, 0x3d, 0x25, 0xbc, 0x53, 0x26, 0xed, 0xd5, // 6288
+ 0xab, 0x39, 0xaa, 0xc5, 0x40, 0x4c, 0x54, 0xab, 0xb2, 0xb4, 0xd9, 0xd9, 0xf8, 0xd7, 0x72, 0xdb, // 6304
+ 0x1c, 0xbc, 0x6d, 0xbd, 0x65, 0x5f, 0xef, 0x88, 0x35, 0x2a, 0x66, 0x2f, 0xee, 0xf6, 0xb3, 0x65, // 6320
+ 0xf0, 0x33, 0x8d, 0x7c, 0x98, 0x41, 0x69, 0x46, 0x0f, 0x43, 0x1c, 0x69, 0xfa, 0x9b, 0xb5, 0xd0, // 6336
+ 0x61, 0x6a, 0xcd, 0xca, 0x4b, 0xd9, 0x4c, 0x90, 0x46, 0xab, 0x15, 0x59, 0xa1, 0x47, 0x54, 0x29, // 6352
+ 0x2e, 0x83, 0x28, 0x5f, 0x1c, 0xc2, 0xa2, 0xab, 0x72, 0x17, 0x00, 0x06, 0x8e, 0x45, 0xec, 0x8b, // 6368
+ 0xe2, 0x33, 0x3d, 0x7f, 0xda, 0x19, 0x44, 0xe4, 0x62, 0x72, 0xc3, 0xdf, 0x22, 0xc6, 0xf2, 0x56, // 6384
+ 0xd4, 0xdd, 0x5f, 0x95, 0x72, 0xed, 0x6d, 0x5f, 0xf7, 0x48, 0x03, 0x5b, 0xfd, 0xc5, 0x2a, 0xa0, // 6400
+ 0xf6, 0x73, 0x23, 0x84, 0x10, 0x1b, 0x01, 0xe7, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x4f, 0x02, // 6416
+ 0x26, 0x30, 0x4d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, // 6432
+ 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x44, 0x69, 0x67, 0x69, 0x43, // 6448
+ 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, // 6464
+ 0x04, 0x03, 0x13, 0x1c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x54, 0x4c, 0x53, // 6480
+ 0x20, 0x52, 0x53, 0x41, 0x34, 0x30, 0x39, 0x36, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x47, 0x35, // 6496
+ 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, // 6512
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, // 6528
+ 0x00, 0xb3, 0xd0, 0xf4, 0xc9, 0x79, 0x11, 0x9d, 0xfd, 0xfc, 0x66, 0x81, 0xe7, 0xcc, 0xd5, 0xe4, // 6544
+ 0xbc, 0xec, 0x81, 0x3e, 0x6a, 0x35, 0x8e, 0x2e, 0xb7, 0xe7, 0xde, 0xaf, 0xf9, 0x07, 0x4d, 0xcf, // 6560
+ 0x30, 0x9d, 0xea, 0x09, 0x0b, 0x99, 0xbd, 0x6c, 0x57, 0xda, 0x18, 0x4a, 0xb8, 0x78, 0xac, 0x3a, // 6576
+ 0x39, 0xa8, 0xa6, 0x48, 0xac, 0x2e, 0x72, 0xe5, 0xbd, 0xeb, 0xf1, 0x1a, 0xcd, 0xe7, 0xa4, 0x03, // 6592
+ 0xa9, 0x3f, 0x11, 0xb4, 0xd8, 0x2f, 0x89, 0x16, 0xfb, 0x94, 0x01, 0x3d, 0xbb, 0x2f, 0xf8, 0x13, // 6608
+ 0x05, 0xa1, 0x78, 0x1c, 0x8e, 0x28, 0xe0, 0x45, 0xe0, 0x83, 0xf4, 0x59, 0x1b, 0x95, 0xb3, 0xae, // 6624
+ 0x7e, 0x03, 0x45, 0xe5, 0xbe, 0xc2, 0x42, 0xfe, 0xee, 0xf2, 0x3c, 0xb6, 0x85, 0x13, 0x98, 0x32, // 6640
+ 0x9d, 0x16, 0xa8, 0x29, 0xc2, 0x0b, 0x1c, 0x38, 0xdc, 0x9f, 0x31, 0x77, 0x5c, 0xbf, 0x27, 0xa3, // 6656
+ 0xfc, 0x27, 0xac, 0xb7, 0x2b, 0xbd, 0x74, 0x9b, 0x17, 0x2d, 0xf2, 0x81, 0xda, 0x5d, 0xb0, 0xe1, // 6672
+ 0x23, 0x17, 0x3e, 0x88, 0x4a, 0x12, 0x23, 0xd0, 0xea, 0xcf, 0x9d, 0xde, 0x03, 0x17, 0xb1, 0x42, // 6688
+ 0x4a, 0xa0, 0x16, 0x4c, 0xa4, 0x6d, 0x93, 0xe9, 0x3f, 0x3a, 0xee, 0x3a, 0x7c, 0x9d, 0x58, 0x9d, // 6704
+ 0xf4, 0x4e, 0x8f, 0xfc, 0x3b, 0x23, 0xc8, 0x6d, 0xb8, 0xe2, 0x05, 0xda, 0xcc, 0xeb, 0xec, 0xc3, // 6720
+ 0x31, 0xf4, 0xd7, 0xa7, 0x29, 0x54, 0x80, 0xcf, 0x44, 0x5b, 0x4c, 0x6f, 0x30, 0x9e, 0xf3, 0xcc, // 6736
+ 0xdd, 0x1f, 0x94, 0x43, 0x9d, 0x4d, 0x7f, 0x70, 0x70, 0x0d, 0xd4, 0x3a, 0xd1, 0x37, 0xf0, 0x6c, // 6752
+ 0x9d, 0x9b, 0xc0, 0x14, 0x93, 0x58, 0xef, 0xcd, 0x41, 0x38, 0x75, 0xbc, 0x13, 0x03, 0x95, 0x7c, // 6768
+ 0x7f, 0xe3, 0x5c, 0xe9, 0xd5, 0x0d, 0xd5, 0xe2, 0x7c, 0x10, 0x62, 0xaa, 0x6b, 0xf0, 0x3d, 0x76, // 6784
+ 0xf3, 0x3f, 0xa3, 0xe8, 0xb0, 0xc1, 0xfd, 0xef, 0xaa, 0x57, 0x4d, 0xac, 0x86, 0xa7, 0x18, 0xb4, // 6800
+ 0x29, 0xc1, 0x2c, 0x0e, 0xbf, 0x64, 0xbe, 0x29, 0x8c, 0xd8, 0x02, 0x2d, 0xcd, 0x5c, 0x2f, 0xf2, // 6816
+ 0x7f, 0xef, 0x15, 0xf4, 0x0c, 0x15, 0xac, 0x0a, 0xb0, 0xf1, 0xd3, 0x0d, 0x4f, 0x6a, 0x4d, 0x77, // 6832
+ 0x97, 0x01, 0xa0, 0xf1, 0x66, 0xb7, 0xb7, 0xce, 0xef, 0xce, 0xec, 0xec, 0xa5, 0x75, 0xca, 0xac, // 6848
+ 0xe3, 0xe1, 0x63, 0xf7, 0xb8, 0xa1, 0x04, 0xc8, 0xbc, 0x7b, 0x3f, 0x5d, 0x2d, 0x16, 0x22, 0x56, // 6864
+ 0xed, 0x48, 0x49, 0xfe, 0xa7, 0x2f, 0x79, 0x30, 0x25, 0x9b, 0xba, 0x6b, 0x2d, 0x3f, 0x9d, 0x3b, // 6880
+ 0xc4, 0x17, 0xe7, 0x1d, 0x2e, 0xfb, 0xf2, 0xcf, 0xa6, 0xfc, 0xe3, 0x14, 0x2c, 0x96, 0x98, 0x21, // 6896
+ 0x8c, 0xb4, 0x91, 0xe9, 0x19, 0x60, 0x83, 0xf2, 0x30, 0x2b, 0x06, 0x73, 0x50, 0xd5, 0x98, 0x3b, // 6912
+ 0x06, 0xe9, 0xc7, 0x8a, 0x0c, 0x60, 0x8c, 0x28, 0xf8, 0x52, 0x9b, 0x6e, 0xe1, 0xf6, 0x4d, 0xbb, // 6928
+ 0x06, 0x24, 0x9b, 0xd7, 0x2b, 0x26, 0x3f, 0xfd, 0x2a, 0x2f, 0x71, 0xf5, 0xd6, 0x24, 0xbe, 0x7f, // 6944
+ 0x31, 0x9e, 0x0f, 0x6d, 0xe8, 0x8f, 0x4f, 0x4d, 0xa3, 0x3f, 0xff, 0x35, 0xea, 0xdf, 0x49, 0x5e, // 6960
+ 0x41, 0x8f, 0x86, 0xf9, 0xf1, 0x77, 0x79, 0x4b, 0x1b, 0xb4, 0xa3, 0x5e, 0x2f, 0xfb, 0x46, 0x02, // 6976
+ 0xd0, 0x66, 0x13, 0x5e, 0x5e, 0x85, 0x4f, 0xce, 0xd8, 0x70, 0x88, 0x7b, 0xce, 0x01, 0xb5, 0x96, // 6992
+ 0x97, 0xd7, 0xcd, 0x7d, 0xfd, 0x82, 0xf8, 0xc2, 0x24, 0xc1, 0xca, 0x01, 0x39, 0x4f, 0x8d, 0xa2, // 7008
+ 0xc1, 0x14, 0x40, 0x1f, 0x9c, 0x66, 0xd5, 0x0c, 0x09, 0x46, 0xd6, 0xf2, 0xd0, 0xd1, 0x48, 0x76, // 7024
+ 0x56, 0x3a, 0x43, 0xcb, 0xb6, 0x0a, 0x11, 0x39, 0xba, 0x8c, 0x13, 0x6c, 0x06, 0xb5, 0x9e, 0xcf, // 7040
+ 0xeb, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x50, 0x00, 0x78, 0x30, 0x4e, 0x31, 0x0b, 0x30, 0x09, // 7056
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, // 7072
+ 0x04, 0x0a, 0x13, 0x0e, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e, // 7088
+ 0x63, 0x2e, 0x31, 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1d, 0x44, 0x69, 0x67, // 7104
+ 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x54, 0x4c, 0x53, 0x20, 0x45, 0x43, 0x43, 0x20, 0x50, 0x33, // 7120
+ 0x38, 0x34, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x47, 0x35, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, // 7136
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, // 7152
+ 0x00, 0x04, 0xc1, 0x44, 0xa1, 0xcf, 0x11, 0x97, 0x50, 0x9a, 0xde, 0x23, 0x82, 0x35, 0x07, 0xcd, // 7168
+ 0xd0, 0xcb, 0x18, 0x9d, 0xd2, 0xf1, 0x7f, 0x77, 0x35, 0x4f, 0x3b, 0xdd, 0x94, 0x72, 0x52, 0xed, // 7184
+ 0xc2, 0x3b, 0xf8, 0xec, 0xfa, 0x7b, 0x6b, 0x58, 0x20, 0xec, 0x99, 0xae, 0xc9, 0xfc, 0x68, 0xb3, // 7200
+ 0x75, 0xb9, 0xdb, 0x09, 0xec, 0xc8, 0x13, 0xf5, 0x4e, 0xc6, 0x0a, 0x1d, 0x66, 0x30, 0x4c, 0xbb, // 7216
+ 0x1f, 0x47, 0x0a, 0x3c, 0x61, 0x10, 0x42, 0x29, 0x7c, 0xa5, 0x08, 0x0e, 0xe0, 0x22, 0xe9, 0xd3, // 7232
+ 0x35, 0x68, 0xce, 0x9b, 0x63, 0x9f, 0x84, 0xb5, 0x99, 0x4d, 0x58, 0xa0, 0x8e, 0xf5, 0x54, 0xe7, // 7248
+ 0x95, 0xc9, 0x00, 0x51, 0x02, 0x26, 0x30, 0x4f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, // 7264
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x20, // 7280
+ 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, // 7296
+ 0x79, 0x20, 0x52, 0x65, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, // 7312
+ 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0c, 0x49, 0x53, 0x52, 0x47, 0x20, // 7328
+ 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x58, 0x31, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, // 7344
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, // 7360
+ 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xad, 0xe8, 0x24, 0x73, 0xf4, 0x14, 0x37, 0xf3, // 7376
+ 0x9b, 0x9e, 0x2b, 0x57, 0x28, 0x1c, 0x87, 0xbe, 0xdc, 0xb7, 0xdf, 0x38, 0x90, 0x8c, 0x6e, 0x3c, // 7392
+ 0xe6, 0x57, 0xa0, 0x78, 0xf7, 0x75, 0xc2, 0xa2, 0xfe, 0xf5, 0x6a, 0x6e, 0xf6, 0x00, 0x4f, 0x28, // 7408
+ 0xdb, 0xde, 0x68, 0x86, 0x6c, 0x44, 0x93, 0xb6, 0xb1, 0x63, 0xfd, 0x14, 0x12, 0x6b, 0xbf, 0x1f, // 7424
+ 0xd2, 0xea, 0x31, 0x9b, 0x21, 0x7e, 0xd1, 0x33, 0x3c, 0xba, 0x48, 0xf5, 0xdd, 0x79, 0xdf, 0xb3, // 7440
+ 0xb8, 0xff, 0x12, 0xf1, 0x21, 0x9a, 0x4b, 0xc1, 0x8a, 0x86, 0x71, 0x69, 0x4a, 0x66, 0x66, 0x6c, // 7456
+ 0x8f, 0x7e, 0x3c, 0x70, 0xbf, 0xad, 0x29, 0x22, 0x06, 0xf3, 0xe4, 0xc0, 0xe6, 0x80, 0xae, 0xe2, // 7472
+ 0x4b, 0x8f, 0xb7, 0x99, 0x7e, 0x94, 0x03, 0x9f, 0xd3, 0x47, 0x97, 0x7c, 0x99, 0x48, 0x23, 0x53, // 7488
+ 0xe8, 0x38, 0xae, 0x4f, 0x0a, 0x6f, 0x83, 0x2e, 0xd1, 0x49, 0x57, 0x8c, 0x80, 0x74, 0xb6, 0xda, // 7504
+ 0x2f, 0xd0, 0x38, 0x8d, 0x7b, 0x03, 0x70, 0x21, 0x1b, 0x75, 0xf2, 0x30, 0x3c, 0xfa, 0x8f, 0xae, // 7520
+ 0xdd, 0xda, 0x63, 0xab, 0xeb, 0x16, 0x4f, 0xc2, 0x8e, 0x11, 0x4b, 0x7e, 0xcf, 0x0b, 0xe8, 0xff, // 7536
+ 0xb5, 0x77, 0x2e, 0xf4, 0xb2, 0x7b, 0x4a, 0xe0, 0x4c, 0x12, 0x25, 0x0c, 0x70, 0x8d, 0x03, 0x29, // 7552
+ 0xa0, 0xe1, 0x53, 0x24, 0xec, 0x13, 0xd9, 0xee, 0x19, 0xbf, 0x10, 0xb3, 0x4a, 0x8c, 0x3f, 0x89, // 7568
+ 0xa3, 0x61, 0x51, 0xde, 0xac, 0x87, 0x07, 0x94, 0xf4, 0x63, 0x71, 0xec, 0x2e, 0xe2, 0x6f, 0x5b, // 7584
+ 0x98, 0x81, 0xe1, 0x89, 0x5c, 0x34, 0x79, 0x6c, 0x76, 0xef, 0x3b, 0x90, 0x62, 0x79, 0xe6, 0xdb, // 7600
+ 0xa4, 0x9a, 0x2f, 0x26, 0xc5, 0xd0, 0x10, 0xe1, 0x0e, 0xde, 0xd9, 0x10, 0x8e, 0x16, 0xfb, 0xb7, // 7616
+ 0xf7, 0xa8, 0xf7, 0xc7, 0xe5, 0x02, 0x07, 0x98, 0x8f, 0x36, 0x08, 0x95, 0xe7, 0xe2, 0x37, 0x96, // 7632
+ 0x0d, 0x36, 0x75, 0x9e, 0xfb, 0x0e, 0x72, 0xb1, 0x1d, 0x9b, 0xbc, 0x03, 0xf9, 0x49, 0x05, 0xd8, // 7648
+ 0x81, 0xdd, 0x05, 0xb4, 0x2a, 0xd6, 0x41, 0xe9, 0xac, 0x01, 0x76, 0x95, 0x0a, 0x0f, 0xd8, 0xdf, // 7664
+ 0xd5, 0xbd, 0x12, 0x1f, 0x35, 0x2f, 0x28, 0x17, 0x6c, 0xd2, 0x98, 0xc1, 0xa8, 0x09, 0x64, 0x77, // 7680
+ 0x6e, 0x47, 0x37, 0xba, 0xce, 0xac, 0x59, 0x5e, 0x68, 0x9d, 0x7f, 0x72, 0xd6, 0x89, 0xc5, 0x06, // 7696
+ 0x41, 0x29, 0x3e, 0x59, 0x3e, 0xdd, 0x26, 0xf5, 0x24, 0xc9, 0x11, 0xa7, 0x5a, 0xa3, 0x4c, 0x40, // 7712
+ 0x1f, 0x46, 0xa1, 0x99, 0xb5, 0xa7, 0x3a, 0x51, 0x6e, 0x86, 0x3b, 0x9e, 0x7d, 0x72, 0xa7, 0x12, // 7728
+ 0x05, 0x78, 0x59, 0xed, 0x3e, 0x51, 0x78, 0x15, 0x0b, 0x03, 0x8f, 0x8d, 0xd0, 0x2f, 0x05, 0xb2, // 7744
+ 0x3e, 0x7b, 0x4a, 0x1c, 0x4b, 0x73, 0x05, 0x12, 0xfc, 0xc6, 0xea, 0xe0, 0x50, 0x13, 0x7c, 0x43, // 7760
+ 0x93, 0x74, 0xb3, 0xca, 0x74, 0xe7, 0x8e, 0x1f, 0x01, 0x08, 0xd0, 0x30, 0xd4, 0x5b, 0x71, 0x36, // 7776
+ 0xb4, 0x07, 0xba, 0xc1, 0x30, 0x30, 0x5c, 0x48, 0xb7, 0x82, 0x3b, 0x98, 0xa6, 0x7d, 0x60, 0x8a, // 7792
+ 0xa2, 0xa3, 0x29, 0x82, 0xcc, 0xba, 0xbd, 0x83, 0x04, 0x1b, 0xa2, 0x83, 0x03, 0x41, 0xa1, 0xd6, // 7808
+ 0x05, 0xf1, 0x1b, 0xc2, 0xb6, 0xf0, 0xa8, 0x7c, 0x86, 0x3b, 0x46, 0xa8, 0x48, 0x2a, 0x88, 0xdc, // 7824
+ 0x76, 0x9a, 0x76, 0xbf, 0x1f, 0x6a, 0xa5, 0x3d, 0x19, 0x8f, 0xeb, 0x38, 0xf3, 0x64, 0xde, 0xc8, // 7840
+ 0x2b, 0x0d, 0x0a, 0x28, 0xff, 0xf7, 0xdb, 0xe2, 0x15, 0x42, 0xd4, 0x22, 0xd0, 0x27, 0x5d, 0xe1, // 7856
+ 0x79, 0xfe, 0x18, 0xe7, 0x70, 0x88, 0xad, 0x4e, 0xe6, 0xd9, 0x8b, 0x3a, 0xc6, 0xdd, 0x27, 0x51, // 7872
+ 0x6e, 0xff, 0xbc, 0x64, 0xf5, 0x33, 0x43, 0x4f, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x51, 0x00, // 7888
+ 0x78, 0x30, 0x4f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, // 7904
+ 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, // 7920
+ 0x6e, 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x20, 0x52, 0x65, 0x73, // 7936
+ 0x65, 0x61, 0x72, 0x63, 0x68, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x15, 0x30, 0x13, 0x06, // 7952
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x0c, 0x49, 0x53, 0x52, 0x47, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, // 7968
+ 0x58, 0x32, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, // 7984
+ 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xcd, 0x9b, 0xd5, 0x9f, 0x80, 0x83, // 8000
+ 0x0a, 0xec, 0x09, 0x4a, 0xf3, 0x16, 0x4a, 0x3e, 0x5c, 0xcf, 0x77, 0xac, 0xde, 0x67, 0x05, 0x0d, // 8016
+ 0x1d, 0x07, 0xb6, 0xdc, 0x16, 0xfb, 0x5a, 0x8b, 0x14, 0xdb, 0xe2, 0x71, 0x60, 0xc4, 0xba, 0x45, // 8032
+ 0x95, 0x11, 0x89, 0x8e, 0xea, 0x06, 0xdf, 0xf7, 0x2a, 0x16, 0x1c, 0xa4, 0xb9, 0xc5, 0xc5, 0x32, // 8048
+ 0xe0, 0x03, 0xe0, 0x1e, 0x82, 0x18, 0x38, 0x8b, 0xd7, 0x45, 0xd8, 0x0a, 0x6a, 0x6e, 0xe6, 0x00, // 8064
+ 0x77, 0xfb, 0x02, 0x51, 0x7d, 0x22, 0xd8, 0x0a, 0x6e, 0x9a, 0x5b, 0x77, 0xdf, 0xf0, 0xfa, 0x41, // 8080
+ 0xec, 0x39, 0xdc, 0x75, 0xca, 0x68, 0x07, 0x0c, 0x1f, 0xea, 0x00, 0x52, 0x00, 0x5b, 0x30, 0x50, // 8096
+ 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1b, 0x47, 0x6c, 0x6f, 0x62, 0x61, // 8112
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x45, 0x43, 0x43, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, // 8128
+ 0x41, 0x20, 0x2d, 0x20, 0x52, 0x34, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, // 8144
+ 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, // 8160
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, // 8176
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, // 8192
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xb8, 0xc6, 0x79, 0xd3, 0x8f, // 8208
+ 0x6c, 0x25, 0x0e, 0x9f, 0x2e, 0x39, 0x19, 0x1c, 0x03, 0xa4, 0xae, 0x9a, 0xe5, 0x39, 0x07, 0x09, // 8224
+ 0x16, 0xca, 0x63, 0xb1, 0xb9, 0x86, 0xf8, 0x8a, 0x57, 0xc1, 0x57, 0xce, 0x42, 0xfa, 0x73, 0xa1, // 8240
+ 0xf7, 0x65, 0x42, 0xff, 0x1e, 0xc1, 0x00, 0xb2, 0x6e, 0x73, 0x0e, 0xff, 0xc7, 0x21, 0xe5, 0x18, // 8256
+ 0xa4, 0xaa, 0xd9, 0x71, 0x3f, 0xa8, 0xd4, 0xb9, 0xce, 0x8c, 0x1d, 0x00, 0x52, 0x00, 0x78, 0x30, // 8272
+ 0x50, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1b, 0x47, 0x6c, 0x6f, 0x62, // 8288
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x45, 0x43, 0x43, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, // 8304
+ 0x43, 0x41, 0x20, 0x2d, 0x20, 0x52, 0x35, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, // 8320
+ 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, // 8336
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, // 8352
+ 0x6e, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, // 8368
+ 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0x47, 0x45, 0x0e, 0x96, 0xfb, 0x7d, 0x5d, // 8384
+ 0xbf, 0xe9, 0x39, 0xd1, 0x21, 0xf8, 0x9f, 0x0b, 0xb6, 0xd5, 0x7b, 0x1e, 0x92, 0x3a, 0x48, 0x59, // 8400
+ 0x1c, 0xf0, 0x62, 0x31, 0x2d, 0xc0, 0x7a, 0x28, 0xfe, 0x1a, 0xa7, 0x5c, 0xb3, 0xb6, 0xcc, 0x97, // 8416
+ 0xe7, 0x45, 0xd4, 0x58, 0xfa, 0xd1, 0x77, 0x6d, 0x43, 0xa2, 0xc0, 0x87, 0x65, 0x34, 0x0a, 0x1f, // 8432
+ 0x7a, 0xdd, 0xeb, 0x3c, 0x33, 0xa1, 0xc5, 0x9d, 0x4d, 0xa4, 0x6f, 0x41, 0x95, 0x38, 0x7f, 0xc9, // 8448
+ 0x1e, 0x84, 0xeb, 0xd1, 0x9e, 0x49, 0x92, 0x87, 0x94, 0x87, 0x0c, 0x3a, 0x85, 0x4a, 0x66, 0x9f, // 8464
+ 0x9d, 0x59, 0x93, 0x4d, 0x97, 0x61, 0x06, 0x86, 0x4a, 0x00, 0x59, 0x01, 0x26, 0x30, 0x57, 0x31, // 8480
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, // 8496
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, // 8512
+ 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, // 8528
+ 0x13, 0x07, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, // 8544
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, // 8560
+ 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, // 8576
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, // 8592
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0x0e, 0xe6, 0x99, 0x8d, 0xce, 0xa3, 0xe3, 0x4f, // 8608
+ 0x8a, 0x7e, 0xfb, 0xf1, 0x8b, 0x83, 0x25, 0x6b, 0xea, 0x48, 0x1f, 0xf1, 0x2a, 0xb0, 0xb9, 0x95, // 8624
+ 0x11, 0x04, 0xbd, 0xf0, 0x63, 0xd1, 0xe2, 0x67, 0x66, 0xcf, 0x1c, 0xdd, 0xcf, 0x1b, 0x48, 0x2b, // 8640
+ 0xee, 0x8d, 0x89, 0x8e, 0x9a, 0xaf, 0x29, 0x80, 0x65, 0xab, 0xe9, 0xc7, 0x2d, 0x12, 0xcb, 0xab, // 8656
+ 0x1c, 0x4c, 0x70, 0x07, 0xa1, 0x3d, 0x0a, 0x30, 0xcd, 0x15, 0x8d, 0x4f, 0xf8, 0xdd, 0xd4, 0x8c, // 8672
+ 0x50, 0x15, 0x1c, 0xef, 0x50, 0xee, 0xc4, 0x2e, 0xf7, 0xfc, 0xe9, 0x52, 0xf2, 0x91, 0x7d, 0xe0, // 8688
+ 0x6d, 0xd5, 0x35, 0x30, 0x8e, 0x5e, 0x43, 0x73, 0xf2, 0x41, 0xe9, 0xd5, 0x6a, 0xe3, 0xb2, 0x89, // 8704
+ 0x3a, 0x56, 0x39, 0x38, 0x6f, 0x06, 0x3c, 0x88, 0x69, 0x5b, 0x2a, 0x4d, 0xc5, 0xa7, 0x54, 0xb8, // 8720
+ 0x6c, 0x89, 0xcc, 0x9b, 0xf9, 0x3c, 0xca, 0xe5, 0xfd, 0x89, 0xf5, 0x12, 0x3c, 0x92, 0x78, 0x96, // 8736
+ 0xd6, 0xdc, 0x74, 0x6e, 0x93, 0x44, 0x61, 0xd1, 0x8d, 0xc7, 0x46, 0xb2, 0x75, 0x0e, 0x86, 0xe8, // 8752
+ 0x19, 0x8a, 0xd5, 0x6d, 0x6c, 0xd5, 0x78, 0x16, 0x95, 0xa2, 0xe9, 0xc8, 0x0a, 0x38, 0xeb, 0xf2, // 8768
+ 0x24, 0x13, 0x4f, 0x73, 0x54, 0x93, 0x13, 0x85, 0x3a, 0x1b, 0xbc, 0x1e, 0x34, 0xb5, 0x8b, 0x05, // 8784
+ 0x8c, 0xb9, 0x77, 0x8b, 0xb1, 0xdb, 0x1f, 0x20, 0x91, 0xab, 0x09, 0x53, 0x6e, 0x90, 0xce, 0x7b, // 8800
+ 0x37, 0x74, 0xb9, 0x70, 0x47, 0x91, 0x22, 0x51, 0x63, 0x16, 0x79, 0xae, 0xb1, 0xae, 0x41, 0x26, // 8816
+ 0x08, 0xc8, 0x19, 0x2b, 0xd1, 0x46, 0xaa, 0x48, 0xd6, 0x64, 0x2a, 0xd7, 0x83, 0x34, 0xff, 0x2c, // 8832
+ 0x2a, 0xc1, 0x6c, 0x19, 0x43, 0x4a, 0x07, 0x85, 0xe7, 0xd3, 0x7c, 0xf6, 0x21, 0x68, 0xef, 0xea, // 8848
+ 0xf2, 0x52, 0x9f, 0x7f, 0x93, 0x90, 0xcf, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x5c, 0x01, 0x26, // 8864
+ 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, // 8880
+ 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, // 8896
+ 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, // 8912
+ 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, // 8928
+ 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, // 8944
+ 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x82, 0x01, 0x22, // 8960
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, // 8976
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04, 0xbb, // 8992
+ 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79, 0xd4, 0x29, 0xe2, 0xe1, 0xe8, // 9008
+ 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e, 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, // 9024
+ 0x09, 0x05, 0x6d, 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12, 0xeb, // 9040
+ 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88, 0x77, 0xd3, 0x1c, 0x8f, 0xc7, // 9056
+ 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7, 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, // 9072
+ 0x8d, 0x2d, 0xe5, 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab, 0x25, // 9088
+ 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5, 0xf7, 0xf9, 0x52, 0x13, 0x2f, // 9104
+ 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f, 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, // 9120
+ 0x33, 0x7a, 0x77, 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0, 0xc2, // 9136
+ 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd, 0x07, 0x59, 0x02, 0xd4, 0x59, // 9152
+ 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0, 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, // 9168
+ 0xea, 0xeb, 0xd4, 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9, 0x39, // 9184
+ 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c, 0xb9, 0x3d, 0xe5, 0xc9, 0x23, // 9200
+ 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c, 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, // 9216
+ 0x86, 0x3a, 0x6b, 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76, 0xbf, // 9232
+ 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27, 0x1a, 0x39, 0x02, 0x03, 0x01, // 9248
+ 0x00, 0x01, 0x00, 0x63, 0x01, 0x26, 0x30, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, // 9264
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, // 9280
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, // 9296
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, // 9312
+ 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, // 9328
+ 0x13, 0x17, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, // 9344
+ 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, // 9360
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, // 9376
+ 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe2, 0x3b, 0xe1, 0x11, 0x72, 0xde, // 9392
+ 0xa8, 0xa4, 0xd3, 0xa3, 0x57, 0xaa, 0x50, 0xa2, 0x8f, 0x0b, 0x77, 0x90, 0xc9, 0xa2, 0xa5, 0xee, // 9408
+ 0x12, 0xce, 0x96, 0x5b, 0x01, 0x09, 0x20, 0xcc, 0x01, 0x93, 0xa7, 0x4e, 0x30, 0xb7, 0x53, 0xf7, // 9424
+ 0x43, 0xc4, 0x69, 0x00, 0x57, 0x9d, 0xe2, 0x8d, 0x22, 0xdd, 0x87, 0x06, 0x40, 0x00, 0x81, 0x09, // 9440
+ 0xce, 0xce, 0x1b, 0x83, 0xbf, 0xdf, 0xcd, 0x3b, 0x71, 0x46, 0xe2, 0xd6, 0x66, 0xc7, 0x05, 0xb3, // 9456
+ 0x76, 0x27, 0x16, 0x8f, 0x7b, 0x9e, 0x1e, 0x95, 0x7d, 0xee, 0xb7, 0x48, 0xa3, 0x08, 0xda, 0xd6, // 9472
+ 0xaf, 0x7a, 0x0c, 0x39, 0x06, 0x65, 0x7f, 0x4a, 0x5d, 0x1f, 0xbc, 0x17, 0xf8, 0xab, 0xbe, 0xee, // 9488
+ 0x28, 0xd7, 0x74, 0x7f, 0x7a, 0x78, 0x99, 0x59, 0x85, 0x68, 0x6e, 0x5c, 0x23, 0x32, 0x4b, 0xbf, // 9504
+ 0x4e, 0xc0, 0xe8, 0x5a, 0x6d, 0xe3, 0x70, 0xbf, 0x77, 0x10, 0xbf, 0xfc, 0x01, 0xf6, 0x85, 0xd9, // 9520
+ 0xa8, 0x44, 0x10, 0x58, 0x32, 0xa9, 0x75, 0x18, 0xd5, 0xd1, 0xa2, 0xbe, 0x47, 0xe2, 0x27, 0x6a, // 9536
+ 0xf4, 0x9a, 0x33, 0xf8, 0x49, 0x08, 0x60, 0x8b, 0xd4, 0x5f, 0xb4, 0x3a, 0x84, 0xbf, 0xa1, 0xaa, // 9552
+ 0x4a, 0x4c, 0x7d, 0x3e, 0xcf, 0x4f, 0x5f, 0x6c, 0x76, 0x5e, 0xa0, 0x4b, 0x37, 0x91, 0x9e, 0xdc, // 9568
+ 0x22, 0xe6, 0x6d, 0xce, 0x14, 0x1a, 0x8e, 0x6a, 0xcb, 0xfe, 0xcd, 0xb3, 0x14, 0x64, 0x17, 0xc7, // 9584
+ 0x5b, 0x29, 0x9e, 0x32, 0xbf, 0xf2, 0xee, 0xfa, 0xd3, 0x0b, 0x42, 0xd4, 0xab, 0xb7, 0x41, 0x32, // 9600
+ 0xda, 0x0c, 0xd4, 0xef, 0xf8, 0x81, 0xd5, 0xbb, 0x8d, 0x58, 0x3f, 0xb5, 0x1b, 0xe8, 0x49, 0x28, // 9616
+ 0xa2, 0x70, 0xda, 0x31, 0x04, 0xdd, 0xf7, 0xb2, 0x16, 0xf2, 0x4c, 0x0a, 0x4e, 0x07, 0xa8, 0xed, // 9632
+ 0x4a, 0x3d, 0x5e, 0xb5, 0x7f, 0xa3, 0x90, 0xc3, 0xaf, 0x27, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, // 9648
+ 0x63, 0x01, 0x26, 0x30, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, // 9664
+ 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, // 9680
+ 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, // 9696
+ 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, // 9712
+ 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x44, // 9728
+ 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, // 9744
+ 0x6f, 0x6f, 0x74, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, // 9760
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, // 9776
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbb, 0x37, 0xcd, 0x34, 0xdc, 0x7b, 0x6b, 0xc9, 0xb2, // 9792
+ 0x68, 0x90, 0xad, 0x4a, 0x75, 0xff, 0x46, 0xba, 0x21, 0x0a, 0x08, 0x8d, 0xf5, 0x19, 0x54, 0xc9, // 9808
+ 0xfb, 0x88, 0xdb, 0xf3, 0xae, 0xf2, 0x3a, 0x89, 0x91, 0x3c, 0x7a, 0xe6, 0xab, 0x06, 0x1a, 0x6b, // 9824
+ 0xcf, 0xac, 0x2d, 0xe8, 0x5e, 0x09, 0x24, 0x44, 0xba, 0x62, 0x9a, 0x7e, 0xd6, 0xa3, 0xa8, 0x7e, // 9840
+ 0xe0, 0x54, 0x75, 0x20, 0x05, 0xac, 0x50, 0xb7, 0x9c, 0x63, 0x1a, 0x6c, 0x30, 0xdc, 0xda, 0x1f, // 9856
+ 0x19, 0xb1, 0xd7, 0x1e, 0xde, 0xfd, 0xd7, 0xe0, 0xcb, 0x94, 0x83, 0x37, 0xae, 0xec, 0x1f, 0x43, // 9872
+ 0x4e, 0xdd, 0x7b, 0x2c, 0xd2, 0xbd, 0x2e, 0xa5, 0x2f, 0xe4, 0xa9, 0xb8, 0xad, 0x3a, 0xd4, 0x99, // 9888
+ 0xa4, 0xb6, 0x25, 0xe9, 0x9b, 0x6b, 0x00, 0x60, 0x92, 0x60, 0xff, 0x4f, 0x21, 0x49, 0x18, 0xf7, // 9904
+ 0x67, 0x90, 0xab, 0x61, 0x06, 0x9c, 0x8f, 0xf2, 0xba, 0xe9, 0xb4, 0xe9, 0x92, 0x32, 0x6b, 0xb5, // 9920
+ 0xf3, 0x57, 0xe8, 0x5d, 0x1b, 0xcd, 0x8c, 0x1d, 0xab, 0x95, 0x04, 0x95, 0x49, 0xf3, 0x35, 0x2d, // 9936
+ 0x96, 0xe3, 0x49, 0x6d, 0xdd, 0x77, 0xe3, 0xfb, 0x49, 0x4b, 0xb4, 0xac, 0x55, 0x07, 0xa9, 0x8f, // 9952
+ 0x95, 0xb3, 0xb4, 0x23, 0xbb, 0x4c, 0x6d, 0x45, 0xf0, 0xf6, 0xa9, 0xb2, 0x95, 0x30, 0xb4, 0xfd, // 9968
+ 0x4c, 0x55, 0x8c, 0x27, 0x4a, 0x57, 0x14, 0x7c, 0x82, 0x9d, 0xcd, 0x73, 0x92, 0xd3, 0x16, 0x4a, // 9984
+ 0x06, 0x0c, 0x8c, 0x50, 0xd1, 0x8f, 0x1e, 0x09, 0xbe, 0x17, 0xa1, 0xe6, 0x21, 0xca, 0xfd, 0x83, // 10000
+ 0xe5, 0x10, 0xbc, 0x83, 0xa5, 0x0a, 0xc4, 0x67, 0x28, 0xf6, 0x73, 0x14, 0x14, 0x3d, 0x46, 0x76, // 10016
+ 0xc3, 0x87, 0x14, 0x89, 0x21, 0x34, 0x4d, 0xaf, 0x0f, 0x45, 0x0c, 0xa6, 0x49, 0xa1, 0xba, 0xbb, // 10032
+ 0x9c, 0xc5, 0xb1, 0x33, 0x83, 0x29, 0x85, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x63, 0x00, 0x78, // 10048
+ 0x30, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, // 10064
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, // 10080
+ 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, // 10096
+ 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, // 10112
+ 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x44, 0x69, 0x67, 0x69, // 10128
+ 0x43, 0x65, 0x72, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, // 10144
+ 0x20, 0x47, 0x33, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 10160
+ 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xdd, 0xa7, 0xd9, 0xbb, 0x8a, // 10176
+ 0xb8, 0x0b, 0xfb, 0x0b, 0x7f, 0x21, 0xd2, 0xf0, 0xbe, 0xbe, 0x73, 0xf3, 0x33, 0x5d, 0x1a, 0xbc, // 10192
+ 0x34, 0xea, 0xde, 0xc6, 0x9b, 0xbc, 0xd0, 0x95, 0xf6, 0xf0, 0xcc, 0xd0, 0x0b, 0xba, 0x61, 0x5b, // 10208
+ 0x51, 0x46, 0x7e, 0x9e, 0x2d, 0x9f, 0xee, 0x8e, 0x63, 0x0c, 0x17, 0xec, 0x07, 0x70, 0xf5, 0xcf, // 10224
+ 0x84, 0x2e, 0x40, 0x83, 0x9c, 0xe8, 0x3f, 0x41, 0x6d, 0x3b, 0xad, 0xd3, 0xa4, 0x14, 0x59, 0x36, // 10240
+ 0x78, 0x9d, 0x03, 0x43, 0xee, 0x10, 0x13, 0x6c, 0x72, 0xde, 0xae, 0x88, 0xa7, 0xa1, 0x6b, 0xb5, // 10256
+ 0x43, 0xce, 0x67, 0xdc, 0x23, 0xff, 0x03, 0x1c, 0xa3, 0xe2, 0x3e, 0x00, 0x64, 0x02, 0x26, 0x30, // 10272
+ 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, // 10288
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, // 10304
+ 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, // 10320
+ 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, // 10336
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x44, 0x69, 0x67, 0x69, 0x43, // 10352
+ 0x65, 0x72, 0x74, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x52, 0x6f, 0x6f, 0x74, // 10368
+ 0x20, 0x47, 0x34, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, // 10384
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, // 10400
+ 0x82, 0x02, 0x01, 0x00, 0xbf, 0xe6, 0x90, 0x73, 0x68, 0xde, 0xbb, 0xe4, 0x5d, 0x4a, 0x3c, 0x30, // 10416
+ 0x22, 0x30, 0x69, 0x33, 0xec, 0xc2, 0xa7, 0x25, 0x2e, 0xc9, 0x21, 0x3d, 0xf2, 0x8a, 0xd8, 0x59, // 10432
+ 0xc2, 0xe1, 0x29, 0xa7, 0x3d, 0x58, 0xab, 0x76, 0x9a, 0xcd, 0xae, 0x7b, 0x1b, 0x84, 0x0d, 0xc4, // 10448
+ 0x30, 0x1f, 0xf3, 0x1b, 0xa4, 0x38, 0x16, 0xeb, 0x56, 0xc6, 0x97, 0x6d, 0x1d, 0xab, 0xb2, 0x79, // 10464
+ 0xf2, 0xca, 0x11, 0xd2, 0xe4, 0x5f, 0xd6, 0x05, 0x3c, 0x52, 0x0f, 0x52, 0x1f, 0xc6, 0x9e, 0x15, // 10480
+ 0xa5, 0x7e, 0xbe, 0x9f, 0xa9, 0x57, 0x16, 0x59, 0x55, 0x72, 0xaf, 0x68, 0x93, 0x70, 0xc2, 0xb2, // 10496
+ 0xba, 0x75, 0x99, 0x6a, 0x73, 0x32, 0x94, 0xd1, 0x10, 0x44, 0x10, 0x2e, 0xdf, 0x82, 0xf3, 0x07, // 10512
+ 0x84, 0xe6, 0x74, 0x3b, 0x6d, 0x71, 0xe2, 0x2d, 0x0c, 0x1b, 0xee, 0x20, 0xd5, 0xc9, 0x20, 0x1d, // 10528
+ 0x63, 0x29, 0x2d, 0xce, 0xec, 0x5e, 0x4e, 0xc8, 0x93, 0xf8, 0x21, 0x61, 0x9b, 0x34, 0xeb, 0x05, // 10544
+ 0xc6, 0x5e, 0xec, 0x5b, 0x1a, 0xbc, 0xeb, 0xc9, 0xcf, 0xcd, 0xac, 0x34, 0x40, 0x5f, 0xb1, 0x7a, // 10560
+ 0x66, 0xee, 0x77, 0xc8, 0x48, 0xa8, 0x66, 0x57, 0x57, 0x9f, 0x54, 0x58, 0x8e, 0x0c, 0x2b, 0xb7, // 10576
+ 0x4f, 0xa7, 0x30, 0xd9, 0x56, 0xee, 0xca, 0x7b, 0x5d, 0xe3, 0xad, 0xc9, 0x4f, 0x5e, 0xe5, 0x35, // 10592
+ 0xe7, 0x31, 0xcb, 0xda, 0x93, 0x5e, 0xdc, 0x8e, 0x8f, 0x80, 0xda, 0xb6, 0x91, 0x98, 0x40, 0x90, // 10608
+ 0x79, 0xc3, 0x78, 0xc7, 0xb6, 0xb1, 0xc4, 0xb5, 0x6a, 0x18, 0x38, 0x03, 0x10, 0x8d, 0xd8, 0xd4, // 10624
+ 0x37, 0xa4, 0x2e, 0x05, 0x7d, 0x88, 0xf5, 0x82, 0x3e, 0x10, 0x91, 0x70, 0xab, 0x55, 0x82, 0x41, // 10640
+ 0x32, 0xd7, 0xdb, 0x04, 0x73, 0x2a, 0x6e, 0x91, 0x01, 0x7c, 0x21, 0x4c, 0xd4, 0xbc, 0xae, 0x1b, // 10656
+ 0x03, 0x75, 0x5d, 0x78, 0x66, 0xd9, 0x3a, 0x31, 0x44, 0x9a, 0x33, 0x40, 0xbf, 0x08, 0xd7, 0x5a, // 10672
+ 0x49, 0xa4, 0xc2, 0xe6, 0xa9, 0xa0, 0x67, 0xdd, 0xa4, 0x27, 0xbc, 0xa1, 0x4f, 0x39, 0xb5, 0x11, // 10688
+ 0x58, 0x17, 0xf7, 0x24, 0x5c, 0x46, 0x8f, 0x64, 0xf7, 0xc1, 0x69, 0x88, 0x76, 0x98, 0x76, 0x3d, // 10704
+ 0x59, 0x5d, 0x42, 0x76, 0x87, 0x89, 0x97, 0x69, 0x7a, 0x48, 0xf0, 0xe0, 0xa2, 0x12, 0x1b, 0x66, // 10720
+ 0x9a, 0x74, 0xca, 0xde, 0x4b, 0x1e, 0xe7, 0x0e, 0x63, 0xae, 0xe6, 0xd4, 0xef, 0x92, 0x92, 0x3a, // 10736
+ 0x9e, 0x3d, 0xdc, 0x00, 0xe4, 0x45, 0x25, 0x89, 0xb6, 0x9a, 0x44, 0x19, 0x2b, 0x7e, 0xc0, 0x94, // 10752
+ 0xb4, 0xd2, 0x61, 0x6d, 0xeb, 0x33, 0xd9, 0xc5, 0xdf, 0x4b, 0x04, 0x00, 0xcc, 0x7d, 0x1c, 0x95, // 10768
+ 0xc3, 0x8f, 0xf7, 0x21, 0xb2, 0xb2, 0x11, 0xb7, 0xbb, 0x7f, 0xf2, 0xd5, 0x8c, 0x70, 0x2c, 0x41, // 10784
+ 0x60, 0xaa, 0xb1, 0x63, 0x18, 0x44, 0x95, 0x1a, 0x76, 0x62, 0x7e, 0xf6, 0x80, 0xb0, 0xfb, 0xe8, // 10800
+ 0x64, 0xa6, 0x33, 0xd1, 0x89, 0x07, 0xe1, 0xbd, 0xb7, 0xe6, 0x43, 0xa4, 0x18, 0xb8, 0xa6, 0x77, // 10816
+ 0x01, 0xe1, 0x0f, 0x94, 0x0c, 0x21, 0x1d, 0xb2, 0x54, 0x29, 0x25, 0x89, 0x6c, 0xe5, 0x0e, 0x52, // 10832
+ 0x51, 0x47, 0x74, 0xbe, 0x26, 0xac, 0xb6, 0x41, 0x75, 0xde, 0x7a, 0xac, 0x5f, 0x8d, 0x3f, 0xc9, // 10848
+ 0xbc, 0xd3, 0x41, 0x11, 0x12, 0x5b, 0xe5, 0x10, 0x50, 0xeb, 0x31, 0xc5, 0xca, 0x72, 0x16, 0x22, // 10864
+ 0x09, 0xdf, 0x7c, 0x4c, 0x75, 0x3f, 0x63, 0xec, 0x21, 0x5f, 0xc4, 0x20, 0x51, 0x6b, 0x6f, 0xb1, // 10880
+ 0xab, 0x86, 0x8b, 0x4f, 0xc2, 0xd6, 0x45, 0x5f, 0x9d, 0x20, 0xfc, 0xa1, 0x1e, 0xc5, 0xc0, 0x8f, // 10896
+ 0xa2, 0xb1, 0x7e, 0x0a, 0x26, 0x99, 0xf5, 0xe4, 0x69, 0x2f, 0x98, 0x1d, 0x2d, 0xf5, 0xd9, 0xa9, // 10912
+ 0xb2, 0x1d, 0xe5, 0x1b, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x65, 0x01, 0x24, 0x30, 0x63, 0x31, // 10928
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, // 10944
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x54, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44, 0x61, // 10960
+ 0x64, 0x64, 0x79, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, // 10976
+ 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, // 10992
+ 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, // 11008
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, // 11024
+ 0x74, 0x79, 0x30, 0x82, 0x01, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, // 11040
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, 0x00, 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, // 11056
+ 0x01, 0x01, 0x00, 0xde, 0x9d, 0xd7, 0xea, 0x57, 0x18, 0x49, 0xa1, 0x5b, 0xeb, 0xd7, 0x5f, 0x48, // 11072
+ 0x86, 0xea, 0xbe, 0xdd, 0xff, 0xe4, 0xef, 0x67, 0x1c, 0xf4, 0x65, 0x68, 0xb3, 0x57, 0x71, 0xa0, // 11088
+ 0x5e, 0x77, 0xbb, 0xed, 0x9b, 0x49, 0xe9, 0x70, 0x80, 0x3d, 0x56, 0x18, 0x63, 0x08, 0x6f, 0xda, // 11104
+ 0xf2, 0xcc, 0xd0, 0x3f, 0x7f, 0x02, 0x54, 0x22, 0x54, 0x10, 0xd8, 0xb2, 0x81, 0xd4, 0xc0, 0x75, // 11120
+ 0x3d, 0x4b, 0x7f, 0xc7, 0x77, 0xc3, 0x3e, 0x78, 0xab, 0x1a, 0x03, 0xb5, 0x20, 0x6b, 0x2f, 0x6a, // 11136
+ 0x2b, 0xb1, 0xc5, 0x88, 0x7e, 0xc4, 0xbb, 0x1e, 0xb0, 0xc1, 0xd8, 0x45, 0x27, 0x6f, 0xaa, 0x37, // 11152
+ 0x58, 0xf7, 0x87, 0x26, 0xd7, 0xd8, 0x2d, 0xf6, 0xa9, 0x17, 0xb7, 0x1f, 0x72, 0x36, 0x4e, 0xa6, // 11168
+ 0x17, 0x3f, 0x65, 0x98, 0x92, 0xdb, 0x2a, 0x6e, 0x5d, 0xa2, 0xfe, 0x88, 0xe0, 0x0b, 0xde, 0x7f, // 11184
+ 0xe5, 0x8d, 0x15, 0xe1, 0xeb, 0xcb, 0x3a, 0xd5, 0xe2, 0x12, 0xa2, 0x13, 0x2d, 0xd8, 0x8e, 0xaf, // 11200
+ 0x5f, 0x12, 0x3d, 0xa0, 0x08, 0x05, 0x08, 0xb6, 0x5c, 0xa5, 0x65, 0x38, 0x04, 0x45, 0x99, 0x1e, // 11216
+ 0xa3, 0x60, 0x60, 0x74, 0xc5, 0x41, 0xa5, 0x72, 0x62, 0x1b, 0x62, 0xc5, 0x1f, 0x6f, 0x5f, 0x1a, // 11232
+ 0x42, 0xbe, 0x02, 0x51, 0x65, 0xa8, 0xae, 0x23, 0x18, 0x6a, 0xfc, 0x78, 0x03, 0xa9, 0x4d, 0x7f, // 11248
+ 0x80, 0xc3, 0xfa, 0xab, 0x5a, 0xfc, 0xa1, 0x40, 0xa4, 0xca, 0x19, 0x16, 0xfe, 0xb2, 0xc8, 0xef, // 11264
+ 0x5e, 0x73, 0x0d, 0xee, 0x77, 0xbd, 0x9a, 0xf6, 0x79, 0x98, 0xbc, 0xb1, 0x07, 0x67, 0xa2, 0x15, // 11280
+ 0x0d, 0xdd, 0xa0, 0x58, 0xc6, 0x44, 0x7b, 0x0a, 0x3e, 0x62, 0x28, 0x5f, 0xba, 0x41, 0x07, 0x53, // 11296
+ 0x58, 0xcf, 0x11, 0x7e, 0x38, 0x74, 0xc5, 0xf8, 0xff, 0xb5, 0x69, 0x90, 0x8f, 0x84, 0x74, 0xea, // 11312
+ 0x97, 0x1b, 0xaf, 0x02, 0x01, 0x03, 0x00, 0x67, 0x01, 0x26, 0x30, 0x65, 0x31, 0x0b, 0x30, 0x09, // 11328
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, // 11344
+ 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, // 11360
+ 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, // 11376
+ 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x24, 0x30, 0x22, 0x06, // 11392
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x1b, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x41, // 11408
+ 0x73, 0x73, 0x75, 0x72, 0x65, 0x64, 0x20, 0x49, 0x44, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, // 11424
+ 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, // 11440
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, // 11456
+ 0x01, 0x00, 0xad, 0x0e, 0x15, 0xce, 0xe4, 0x43, 0x80, 0x5c, 0xb1, 0x87, 0xf3, 0xb7, 0x60, 0xf9, // 11472
+ 0x71, 0x12, 0xa5, 0xae, 0xdc, 0x26, 0x94, 0x88, 0xaa, 0xf4, 0xce, 0xf5, 0x20, 0x39, 0x28, 0x58, // 11488
+ 0x60, 0x0c, 0xf8, 0x80, 0xda, 0xa9, 0x15, 0x95, 0x32, 0x61, 0x3c, 0xb5, 0xb1, 0x28, 0x84, 0x8a, // 11504
+ 0x8a, 0xdc, 0x9f, 0x0a, 0x0c, 0x83, 0x17, 0x7a, 0x8f, 0x90, 0xac, 0x8a, 0xe7, 0x79, 0x53, 0x5c, // 11520
+ 0x31, 0x84, 0x2a, 0xf6, 0x0f, 0x98, 0x32, 0x36, 0x76, 0xcc, 0xde, 0xdd, 0x3c, 0xa8, 0xa2, 0xef, // 11536
+ 0x6a, 0xfb, 0x21, 0xf2, 0x52, 0x61, 0xdf, 0x9f, 0x20, 0xd7, 0x1f, 0xe2, 0xb1, 0xd9, 0xfe, 0x18, // 11552
+ 0x64, 0xd2, 0x12, 0x5b, 0x5f, 0xf9, 0x58, 0x18, 0x35, 0xbc, 0x47, 0xcd, 0xa1, 0x36, 0xf9, 0x6b, // 11568
+ 0x7f, 0xd4, 0xb0, 0x38, 0x3e, 0xc1, 0x1b, 0xc3, 0x8c, 0x33, 0xd9, 0xd8, 0x2f, 0x18, 0xfe, 0x28, // 11584
+ 0x0f, 0xb3, 0xa7, 0x83, 0xd6, 0xc3, 0x6e, 0x44, 0xc0, 0x61, 0x35, 0x96, 0x16, 0xfe, 0x59, 0x9c, // 11600
+ 0x8b, 0x76, 0x6d, 0xd7, 0xf1, 0xa2, 0x4b, 0x0d, 0x2b, 0xff, 0x0b, 0x72, 0xda, 0x9e, 0x60, 0xd0, // 11616
+ 0x8e, 0x90, 0x35, 0xc6, 0x78, 0x55, 0x87, 0x20, 0xa1, 0xcf, 0xe5, 0x6d, 0x0a, 0xc8, 0x49, 0x7c, // 11632
+ 0x31, 0x98, 0x33, 0x6c, 0x22, 0xe9, 0x87, 0xd0, 0x32, 0x5a, 0xa2, 0xba, 0x13, 0x82, 0x11, 0xed, // 11648
+ 0x39, 0x17, 0x9d, 0x99, 0x3a, 0x72, 0xa1, 0xe6, 0xfa, 0xa4, 0xd9, 0xd5, 0x17, 0x31, 0x75, 0xae, // 11664
+ 0x85, 0x7d, 0x22, 0xae, 0x3f, 0x01, 0x46, 0x86, 0xf6, 0x28, 0x79, 0xc8, 0xb1, 0xda, 0xe4, 0x57, // 11680
+ 0x17, 0xc4, 0x7e, 0x1c, 0x0e, 0xb0, 0xb4, 0x92, 0xa6, 0x56, 0xb3, 0xbd, 0xb2, 0x97, 0xed, 0xaa, // 11696
+ 0xa7, 0xf0, 0xb7, 0xc5, 0xa8, 0x3f, 0x95, 0x16, 0xd0, 0xff, 0xa1, 0x96, 0xeb, 0x08, 0x5f, 0x18, // 11712
+ 0x77, 0x4f, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x67, 0x01, 0x26, 0x30, 0x65, 0x31, 0x0b, 0x30, // 11728
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, // 11744
+ 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, // 11760
+ 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, // 11776
+ 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x24, 0x30, 0x22, // 11792
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1b, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, // 11808
+ 0x41, 0x73, 0x73, 0x75, 0x72, 0x65, 0x64, 0x20, 0x49, 0x44, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, // 11824
+ 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, // 11840
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, // 11856
+ 0x01, 0x01, 0x00, 0xd9, 0xe7, 0x28, 0x2f, 0x52, 0x3f, 0x36, 0x72, 0x49, 0x88, 0x93, 0x34, 0xf3, // 11872
+ 0xf8, 0x6a, 0x1e, 0x31, 0x54, 0x80, 0x9f, 0xad, 0x54, 0x41, 0xb5, 0x47, 0xdf, 0x96, 0xa8, 0xd4, // 11888
+ 0xaf, 0x80, 0x2d, 0xb9, 0x0a, 0xcf, 0x75, 0xfd, 0x89, 0xa5, 0x7d, 0x24, 0xfa, 0xe3, 0x22, 0x0c, // 11904
+ 0x2b, 0xbc, 0x95, 0x17, 0x0b, 0x33, 0xbf, 0x19, 0x4d, 0x41, 0x06, 0x90, 0x00, 0xbd, 0x0c, 0x4d, // 11920
+ 0x10, 0xfe, 0x07, 0xb5, 0xe7, 0x1c, 0x6e, 0x22, 0x55, 0x31, 0x65, 0x97, 0xbd, 0xd3, 0x17, 0xd2, // 11936
+ 0x1e, 0x62, 0xf3, 0xdb, 0xea, 0x6c, 0x50, 0x8c, 0x3f, 0x84, 0x0c, 0x96, 0xcf, 0xb7, 0xcb, 0x03, // 11952
+ 0xe0, 0xca, 0x6d, 0xa1, 0x14, 0x4c, 0x1b, 0x89, 0xdd, 0xed, 0x00, 0xb0, 0x52, 0x7c, 0xaf, 0x91, // 11968
+ 0x6c, 0xb1, 0x38, 0x13, 0xd1, 0xe9, 0x12, 0x08, 0xc0, 0x00, 0xb0, 0x1c, 0x2b, 0x11, 0xda, 0x77, // 11984
+ 0x70, 0x36, 0x9b, 0xae, 0xce, 0x79, 0x87, 0xdc, 0x82, 0x70, 0xe6, 0x09, 0x74, 0x70, 0x55, 0x69, // 12000
+ 0xaf, 0xa3, 0x68, 0x9f, 0xbf, 0xdd, 0xb6, 0x79, 0xb3, 0xf2, 0x9d, 0x70, 0x29, 0x55, 0xf4, 0xab, // 12016
+ 0xff, 0x95, 0x61, 0xf3, 0xc9, 0x40, 0x6f, 0x1d, 0xd1, 0xbe, 0x93, 0xbb, 0xd3, 0x88, 0x2a, 0xbb, // 12032
+ 0x9d, 0xbf, 0x72, 0x5a, 0x56, 0x71, 0x3b, 0x3f, 0xd4, 0xf3, 0xd1, 0x0a, 0xfe, 0x28, 0xef, 0xa3, // 12048
+ 0xee, 0xd9, 0x99, 0xaf, 0x03, 0xd3, 0x8f, 0x60, 0xb7, 0xf2, 0x92, 0xa1, 0xb1, 0xbd, 0x89, 0x89, // 12064
+ 0x1f, 0x30, 0xcd, 0xc3, 0xa6, 0x2e, 0x62, 0x33, 0xae, 0x16, 0x02, 0x77, 0x44, 0x5a, 0xe7, 0x81, // 12080
+ 0x0a, 0x3c, 0xa7, 0x44, 0x2e, 0x79, 0xb8, 0x3f, 0x04, 0xbc, 0x5c, 0xa0, 0x87, 0xe1, 0x1b, 0xaf, // 12096
+ 0x51, 0x8e, 0xcd, 0xec, 0x2c, 0xfa, 0xf8, 0xfe, 0x6d, 0xf0, 0x3a, 0x7c, 0xaa, 0x8b, 0xe4, 0x67, // 12112
+ 0x95, 0x31, 0x8d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x67, 0x00, 0x78, 0x30, 0x65, 0x31, 0x0b, // 12128
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, // 12144
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, // 12160
+ 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, // 12176
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x24, 0x30, // 12192
+ 0x22, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1b, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, // 12208
+ 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x65, 0x64, 0x20, 0x49, 0x44, 0x20, 0x52, 0x6f, 0x6f, 0x74, // 12224
+ 0x20, 0x47, 0x33, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // 12240
+ 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0x19, 0xe7, 0xbc, 0xac, 0x44, // 12256
+ 0x65, 0xed, 0xcd, 0xb8, 0x3f, 0x58, 0xfb, 0x8d, 0xb1, 0x57, 0xa9, 0x44, 0x2d, 0x05, 0x15, 0xf2, // 12272
+ 0xef, 0x0b, 0xff, 0x10, 0x74, 0x9f, 0xb5, 0x62, 0x52, 0x5f, 0x66, 0x7e, 0x1f, 0xe5, 0xdc, 0x1b, // 12288
+ 0x45, 0x79, 0x0b, 0xcc, 0xc6, 0x53, 0x0a, 0x9d, 0x8d, 0x5d, 0x02, 0xd9, 0xa9, 0x59, 0xde, 0x02, // 12304
+ 0x5a, 0xf6, 0x95, 0x2a, 0x0e, 0x8d, 0x38, 0x4a, 0x8a, 0x49, 0xc6, 0xbc, 0xc6, 0x03, 0x38, 0x07, // 12320
+ 0x5f, 0x55, 0xda, 0x7e, 0x09, 0x6e, 0xe2, 0x7f, 0x5e, 0xd0, 0x45, 0x20, 0x0f, 0x59, 0x76, 0x10, // 12336
+ 0xd6, 0xa0, 0x24, 0xf0, 0x2d, 0xde, 0x36, 0xf2, 0x6c, 0x29, 0x39, 0x00, 0x6a, 0x01, 0x24, 0x30, // 12352
+ 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x25, // 12368
+ 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, // 12384
+ 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, // 12400
+ 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x29, // 12416
+ 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, // 12432
+ 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, // 12448
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x20, 0x30, 0x0d, 0x06, // 12464
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, // 12480
+ 0x00, 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0x32, 0xc8, 0xfe, 0xe9, 0x71, // 12496
+ 0xa6, 0x04, 0x85, 0xad, 0x0c, 0x11, 0x64, 0xdf, 0xce, 0x4d, 0xef, 0xc8, 0x03, 0x18, 0x87, 0x3f, // 12512
+ 0xa1, 0xab, 0xfb, 0x3c, 0xa6, 0x9f, 0xf0, 0xc3, 0xa1, 0xda, 0xd4, 0xd8, 0x6e, 0x2b, 0x53, 0x90, // 12528
+ 0xfb, 0x24, 0xa4, 0x3e, 0x84, 0xf0, 0x9e, 0xe8, 0x5f, 0xec, 0xe5, 0x27, 0x44, 0xf5, 0x28, 0xa6, // 12544
+ 0x3f, 0x7b, 0xde, 0xe0, 0x2a, 0xf0, 0xc8, 0xaf, 0x53, 0x2f, 0x9e, 0xca, 0x05, 0x01, 0x93, 0x1e, // 12560
+ 0x8f, 0x66, 0x1c, 0x39, 0xa7, 0x4d, 0xfa, 0x5a, 0xb6, 0x73, 0x04, 0x25, 0x66, 0xeb, 0x77, 0x7f, // 12576
+ 0xe7, 0x59, 0xc6, 0x4a, 0x99, 0x25, 0x14, 0x54, 0xeb, 0x26, 0xc7, 0xf3, 0x7f, 0x19, 0xd5, 0x30, // 12592
+ 0x70, 0x8f, 0xaf, 0xb0, 0x46, 0x2a, 0xff, 0xad, 0xeb, 0x29, 0xed, 0xd7, 0x9f, 0xaa, 0x04, 0x87, // 12608
+ 0xa3, 0xd4, 0xf9, 0x89, 0xa5, 0x34, 0x5f, 0xdb, 0x43, 0x91, 0x82, 0x36, 0xd9, 0x66, 0x3c, 0xb1, // 12624
+ 0xb8, 0xb9, 0x82, 0xfd, 0x9c, 0x3a, 0x3e, 0x10, 0xc8, 0x3b, 0xef, 0x06, 0x65, 0x66, 0x7a, 0x9b, // 12640
+ 0x19, 0x18, 0x3d, 0xff, 0x71, 0x51, 0x3c, 0x30, 0x2e, 0x5f, 0xbe, 0x3d, 0x77, 0x73, 0xb2, 0x5d, // 12656
+ 0x06, 0x6c, 0xc3, 0x23, 0x56, 0x9a, 0x2b, 0x85, 0x26, 0x92, 0x1c, 0xa7, 0x02, 0xb3, 0xe4, 0x3f, // 12672
+ 0x0d, 0xaf, 0x08, 0x79, 0x82, 0xb8, 0x36, 0x3d, 0xea, 0x9c, 0xd3, 0x35, 0xb3, 0xbc, 0x69, 0xca, // 12688
+ 0xf5, 0xcc, 0x9d, 0xe8, 0xfd, 0x64, 0x8d, 0x17, 0x80, 0x33, 0x6e, 0x5e, 0x4a, 0x5d, 0x99, 0xc9, // 12704
+ 0x1e, 0x87, 0xb4, 0x9d, 0x1a, 0xc0, 0xd5, 0x6e, 0x13, 0x35, 0x23, 0x5e, 0xdf, 0x9b, 0x5f, 0x3d, // 12720
+ 0xef, 0xd6, 0xf7, 0x76, 0xc2, 0xea, 0x3e, 0xbb, 0x78, 0x0d, 0x1c, 0x42, 0x67, 0x6b, 0x04, 0xd8, // 12736
+ 0xf8, 0xd6, 0xda, 0x6f, 0x8b, 0xf2, 0x44, 0xa0, 0x01, 0xab, 0x02, 0x01, 0x03, 0x00, 0x6e, 0x01, // 12752
+ 0x26, 0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, // 12768
+ 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, // 12784
+ 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, // 12800
+ 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, // 12816
+ 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67, // 12832
+ 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, // 12848
+ 0x61, 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, // 12864
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // 12880
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, // 12896
+ 0xc6, 0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d, 0x32, 0xa6, 0xdf, 0xe5, 0x81, // 12912
+ 0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71, 0x2a, 0xc3, 0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c, // 12928
+ 0xb0, 0x5f, 0x69, 0xa6, 0x40, 0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41, 0x59, // 12944
+ 0x3a, 0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a, 0xcc, 0x4d, 0x25, 0x82, 0xf7, // 12960
+ 0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3, 0x49, 0x6d, 0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6, // 12976
+ 0x5f, 0xc3, 0xa4, 0x84, 0xf8, 0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92, 0xe3, // 12992
+ 0x06, 0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb, 0x5a, 0x89, 0x15, 0x7e, 0x71, // 13008
+ 0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d, 0xbe, 0x3a, 0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d, // 13024
+ 0xb3, 0xc7, 0x24, 0x45, 0x9a, 0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13, 0xeb, // 13040
+ 0x51, 0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81, 0x41, 0x50, 0xa6, 0x79, 0x5c, // 13056
+ 0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e, 0xe6, 0x5d, 0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a, // 13072
+ 0x06, 0x16, 0xa4, 0x68, 0x73, 0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c, 0x55, // 13088
+ 0xdb, 0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11, 0xb4, 0x29, 0x80, 0x12, 0xf1, // 13104
+ 0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c, 0x27, 0x66, 0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a, // 13120
+ 0xa8, 0x38, 0xab, 0x0a, 0x64, 0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e, 0x87, // 13136
+ 0x5d, 0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b, 0x51, 0x38, 0xb8, 0x4b, 0xcb, // 13152
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x84, 0x01, 0x26, 0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, // 13168
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, // 13184
+ 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, // 13200
+ 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, // 13216
+ 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, // 13232
+ 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, // 13248
+ 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, // 13264
+ 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, // 13280
+ 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, // 13296
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, // 13312
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, // 13328
+ 0x8b, 0x8b, 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98, 0xd3, 0xa9, 0xbf, // 13344
+ 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88, 0xce, 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, // 13360
+ 0xf0, 0xa2, 0x29, 0x50, 0x99, 0xae, 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, // 13376
+ 0x42, 0x91, 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb, 0x59, 0x66, 0x97, // 13392
+ 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda, 0x37, 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, // 13408
+ 0x71, 0xb3, 0x52, 0xf0, 0x21, 0x4d, 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, // 13424
+ 0xf2, 0x83, 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae, 0x34, 0x91, 0xfe, // 13440
+ 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1, 0xf9, 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, // 13456
+ 0x0d, 0x8b, 0xdb, 0x72, 0x30, 0x6a, 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, // 13472
+ 0x04, 0x48, 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc, 0x39, 0xbd, 0x32, // 13488
+ 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37, 0x9c, 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, // 13504
+ 0x8b, 0xc6, 0xb8, 0x55, 0x43, 0x88, 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, // 13520
+ 0x00, 0xd4, 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3, 0x51, 0xc1, 0x0b, // 13536
+ 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05, 0x65, 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, // 13552
+ 0xad, 0xc6, 0xd4, 0x21, 0x19, 0xd2, 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, // 13568
+ 0x19, 0x12, 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26, 0xf7, 0x02, 0x03, // 13584
+ 0x01, 0x00, 0x01, 0x00, 0x86, 0x01, 0x26, 0x30, 0x81, 0x83, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, // 13600
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, // 13616
+ 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, // 13632
+ 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, // 13648
+ 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, // 13664
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, // 13680
+ 0x55, 0x04, 0x03, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x52, 0x6f, // 13696
+ 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, // 13712
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, // 13728
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, // 13744
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbf, 0x71, // 13760
+ 0x62, 0x08, 0xf1, 0xfa, 0x59, 0x34, 0xf7, 0x1b, 0xc9, 0x18, 0xa3, 0xf7, 0x80, 0x49, 0x58, 0xe9, // 13776
+ 0x22, 0x83, 0x13, 0xa6, 0xc5, 0x20, 0x43, 0x01, 0x3b, 0x84, 0xf1, 0xe6, 0x85, 0x49, 0x9f, 0x27, // 13792
+ 0xea, 0xf6, 0x84, 0x1b, 0x4e, 0xa0, 0xb4, 0xdb, 0x70, 0x98, 0xc7, 0x32, 0x01, 0xb1, 0x05, 0x3e, // 13808
+ 0x07, 0x4e, 0xee, 0xf4, 0xfa, 0x4f, 0x2f, 0x59, 0x30, 0x22, 0xe7, 0xab, 0x19, 0x56, 0x6b, 0xe2, // 13824
+ 0x80, 0x07, 0xfc, 0xf3, 0x16, 0x75, 0x80, 0x39, 0x51, 0x7b, 0xe5, 0xf9, 0x35, 0xb6, 0x74, 0x4e, // 13840
+ 0xa9, 0x8d, 0x82, 0x13, 0xe4, 0xb6, 0x3f, 0xa9, 0x03, 0x83, 0xfa, 0xa2, 0xbe, 0x8a, 0x15, 0x6a, // 13856
+ 0x7f, 0xde, 0x0b, 0xc3, 0xb6, 0x19, 0x14, 0x05, 0xca, 0xea, 0xc3, 0xa8, 0x04, 0x94, 0x3b, 0x46, // 13872
+ 0x7c, 0x32, 0x0d, 0xf3, 0x00, 0x66, 0x22, 0xc8, 0x8d, 0x69, 0x6d, 0x36, 0x8c, 0x11, 0x18, 0xb7, // 13888
+ 0xd3, 0xb2, 0x1c, 0x60, 0xb4, 0x38, 0xfa, 0x02, 0x8c, 0xce, 0xd3, 0xdd, 0x46, 0x07, 0xde, 0x0a, // 13904
+ 0x3e, 0xeb, 0x5d, 0x7c, 0xc8, 0x7c, 0xfb, 0xb0, 0x2b, 0x53, 0xa4, 0x92, 0x62, 0x69, 0x51, 0x25, // 13920
+ 0x05, 0x61, 0x1a, 0x44, 0x81, 0x8c, 0x2c, 0xa9, 0x43, 0x96, 0x23, 0xdf, 0xac, 0x3a, 0x81, 0x9a, // 13936
+ 0x0e, 0x29, 0xc5, 0x1c, 0xa9, 0xe9, 0x5d, 0x1e, 0xb6, 0x9e, 0x9e, 0x30, 0x0a, 0x39, 0xce, 0xf1, // 13952
+ 0x88, 0x80, 0xfb, 0x4b, 0x5d, 0xcc, 0x32, 0xec, 0x85, 0x62, 0x43, 0x25, 0x34, 0x02, 0x56, 0x27, // 13968
+ 0x01, 0x91, 0xb4, 0x3b, 0x70, 0x2a, 0x3f, 0x6e, 0xb1, 0xe8, 0x9c, 0x88, 0x01, 0x7d, 0x9f, 0xd4, // 13984
+ 0xf9, 0xdb, 0x53, 0x6d, 0x60, 0x9d, 0xbf, 0x2c, 0xe7, 0x58, 0xab, 0xb8, 0x5f, 0x46, 0xfc, 0xce, // 14000
+ 0xc4, 0x1b, 0x03, 0x3c, 0x09, 0xeb, 0x49, 0x31, 0x5c, 0x69, 0x46, 0xb3, 0xe0, 0x47, 0x02, 0x03, // 14016
+ 0x01, 0x00, 0x01, 0x00, 0x88, 0x00, 0x78, 0x30, 0x81, 0x85, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, // 14032
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, // 14048
+ 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, // 14064
+ 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, // 14080
+ 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, // 14096
+ 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, // 14112
+ 0x65, 0x64, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x43, 0x4f, 0x4d, // 14128
+ 0x4f, 0x44, 0x4f, 0x20, 0x45, 0x43, 0x43, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, // 14144
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, // 14160
+ 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, // 14176
+ 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0x03, 0x47, 0x7b, 0x2f, 0x75, 0xc9, 0x82, 0x15, 0x85, // 14192
+ 0xfb, 0x75, 0xe4, 0x91, 0x16, 0xd4, 0xab, 0x62, 0x99, 0xf5, 0x3e, 0x52, 0x0b, 0x06, 0xce, 0x41, // 14208
+ 0x00, 0x7f, 0x97, 0xe1, 0x0a, 0x24, 0x3c, 0x1d, 0x01, 0x04, 0xee, 0x3d, 0xd2, 0x8d, 0x09, 0x97, // 14224
+ 0x0c, 0xe0, 0x75, 0xe4, 0xfa, 0xfb, 0x77, 0x8a, 0x2a, 0xf5, 0x03, 0x60, 0x4b, 0x36, 0x8b, 0x16, // 14240
+ 0x23, 0x16, 0xad, 0x09, 0x71, 0xf4, 0x4a, 0xf4, 0x28, 0x50, 0xb4, 0xfe, 0x88, 0x1c, 0x6e, 0x3f, // 14256
+ 0x6c, 0x2f, 0x2f, 0x09, 0x59, 0x5b, 0xa5, 0x5b, 0x0b, 0x33, 0x99, 0xe2, 0xc3, 0x3d, 0x89, 0xf9, // 14272
+ 0x6a, 0x2c, 0xef, 0xb2, 0xd3, 0x06, 0xe9, 0x00, 0x88, 0x02, 0x26, 0x30, 0x81, 0x85, 0x31, 0x0b, // 14288
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, // 14304
+ 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, // 14320
+ 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, // 14336
+ 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, // 14352
+ 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, // 14368
+ 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, // 14384
+ 0x22, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x52, 0x53, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74, // 14400
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, // 14416
+ 0x69, 0x74, 0x79, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, // 14432
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, // 14448
+ 0x82, 0x02, 0x01, 0x00, 0x91, 0xe8, 0x54, 0x92, 0xd2, 0x0a, 0x56, 0xb1, 0xac, 0x0d, 0x24, 0xdd, // 14464
+ 0xc5, 0xcf, 0x44, 0x67, 0x74, 0x99, 0x2b, 0x37, 0xa3, 0x7d, 0x23, 0x70, 0x00, 0x71, 0xbc, 0x53, // 14480
+ 0xdf, 0xc4, 0xfa, 0x2a, 0x12, 0x8f, 0x4b, 0x7f, 0x10, 0x56, 0xbd, 0x9f, 0x70, 0x72, 0xb7, 0x61, // 14496
+ 0x7f, 0xc9, 0x4b, 0x0f, 0x17, 0xa7, 0x3d, 0xe3, 0xb0, 0x04, 0x61, 0xee, 0xff, 0x11, 0x97, 0xc7, // 14512
+ 0xf4, 0x86, 0x3e, 0x0a, 0xfa, 0x3e, 0x5c, 0xf9, 0x93, 0xe6, 0x34, 0x7a, 0xd9, 0x14, 0x6b, 0xe7, // 14528
+ 0x9c, 0xb3, 0x85, 0xa0, 0x82, 0x7a, 0x76, 0xaf, 0x71, 0x90, 0xd7, 0xec, 0xfd, 0x0d, 0xfa, 0x9c, // 14544
+ 0x6c, 0xfa, 0xdf, 0xb0, 0x82, 0xf4, 0x14, 0x7e, 0xf9, 0xbe, 0xc4, 0xa6, 0x2f, 0x4f, 0x7f, 0x99, // 14560
+ 0x7f, 0xb5, 0xfc, 0x67, 0x43, 0x72, 0xbd, 0x0c, 0x00, 0xd6, 0x89, 0xeb, 0x6b, 0x2c, 0xd3, 0xed, // 14576
+ 0x8f, 0x98, 0x1c, 0x14, 0xab, 0x7e, 0xe5, 0xe3, 0x6e, 0xfc, 0xd8, 0xa8, 0xe4, 0x92, 0x24, 0xda, // 14592
+ 0x43, 0x6b, 0x62, 0xb8, 0x55, 0xfd, 0xea, 0xc1, 0xbc, 0x6c, 0xb6, 0x8b, 0xf3, 0x0e, 0x8d, 0x9a, // 14608
+ 0xe4, 0x9b, 0x6c, 0x69, 0x99, 0xf8, 0x78, 0x48, 0x30, 0x45, 0xd5, 0xad, 0xe1, 0x0d, 0x3c, 0x45, // 14624
+ 0x60, 0xfc, 0x32, 0x96, 0x51, 0x27, 0xbc, 0x67, 0xc3, 0xca, 0x2e, 0xb6, 0x6b, 0xea, 0x46, 0xc7, // 14640
+ 0xc7, 0x20, 0xa0, 0xb1, 0x1f, 0x65, 0xde, 0x48, 0x08, 0xba, 0xa4, 0x4e, 0xa9, 0xf2, 0x83, 0x46, // 14656
+ 0x37, 0x84, 0xeb, 0xe8, 0xcc, 0x81, 0x48, 0x43, 0x67, 0x4e, 0x72, 0x2a, 0x9b, 0x5c, 0xbd, 0x4c, // 14672
+ 0x1b, 0x28, 0x8a, 0x5c, 0x22, 0x7b, 0xb4, 0xab, 0x98, 0xd9, 0xee, 0xe0, 0x51, 0x83, 0xc3, 0x09, // 14688
+ 0x46, 0x4e, 0x6d, 0x3e, 0x99, 0xfa, 0x95, 0x17, 0xda, 0x7c, 0x33, 0x57, 0x41, 0x3c, 0x8d, 0x51, // 14704
+ 0xed, 0x0b, 0xb6, 0x5c, 0xaf, 0x2c, 0x63, 0x1a, 0xdf, 0x57, 0xc8, 0x3f, 0xbc, 0xe9, 0x5d, 0xc4, // 14720
+ 0x9b, 0xaf, 0x45, 0x99, 0xe2, 0xa3, 0x5a, 0x24, 0xb4, 0xba, 0xa9, 0x56, 0x3d, 0xcf, 0x6f, 0xaa, // 14736
+ 0xff, 0x49, 0x58, 0xbe, 0xf0, 0xa8, 0xff, 0xf4, 0xb8, 0xad, 0xe9, 0x37, 0xfb, 0xba, 0xb8, 0xf4, // 14752
+ 0x0b, 0x3a, 0xf9, 0xe8, 0x43, 0x42, 0x1e, 0x89, 0xd8, 0x84, 0xcb, 0x13, 0xf1, 0xd9, 0xbb, 0xe1, // 14768
+ 0x89, 0x60, 0xb8, 0x8c, 0x28, 0x56, 0xac, 0x14, 0x1d, 0x9c, 0x0a, 0xe7, 0x71, 0xeb, 0xcf, 0x0e, // 14784
+ 0xdd, 0x3d, 0xa9, 0x96, 0xa1, 0x48, 0xbd, 0x3c, 0xf7, 0xaf, 0xb5, 0x0d, 0x22, 0x4c, 0xc0, 0x11, // 14800
+ 0x81, 0xec, 0x56, 0x3b, 0xf6, 0xd3, 0xa2, 0xe2, 0x5b, 0xb7, 0xb2, 0x04, 0x22, 0x52, 0x95, 0x80, // 14816
+ 0x93, 0x69, 0xe8, 0x8e, 0x4c, 0x65, 0xf1, 0x91, 0x03, 0x2d, 0x70, 0x74, 0x02, 0xea, 0x8b, 0x67, // 14832
+ 0x15, 0x29, 0x69, 0x52, 0x02, 0xbb, 0xd7, 0xdf, 0x50, 0x6a, 0x55, 0x46, 0xbf, 0xa0, 0xa3, 0x28, // 14848
+ 0x61, 0x7f, 0x70, 0xd0, 0xc3, 0xa2, 0xaa, 0x2c, 0x21, 0xaa, 0x47, 0xce, 0x28, 0x9c, 0x06, 0x45, // 14864
+ 0x76, 0xbf, 0x82, 0x18, 0x27, 0xb4, 0xd5, 0xae, 0xb4, 0xcb, 0x50, 0xe6, 0x6b, 0xf4, 0x4c, 0x86, // 14880
+ 0x71, 0x30, 0xe9, 0xa6, 0xdf, 0x16, 0x86, 0xe0, 0xd8, 0xff, 0x40, 0xdd, 0xfb, 0xd0, 0x42, 0x88, // 14896
+ 0x7f, 0xa3, 0x33, 0x3a, 0x2e, 0x5c, 0x1e, 0x41, 0x11, 0x81, 0x63, 0xce, 0x18, 0x71, 0x6b, 0x2b, // 14912
+ 0xec, 0xa6, 0x8a, 0xb7, 0x31, 0x5c, 0x3a, 0x6a, 0x47, 0xe0, 0xc3, 0x79, 0x59, 0xd6, 0x20, 0x1a, // 14928
+ 0xaf, 0xf2, 0x6a, 0x98, 0xaa, 0x72, 0xbc, 0x57, 0x4a, 0xd2, 0x4b, 0x9d, 0xbb, 0x10, 0xfc, 0xb0, // 14944
+ 0x4c, 0x41, 0xe5, 0xed, 0x1d, 0x3d, 0x5e, 0x28, 0x9d, 0x9c, 0xcc, 0xbf, 0xb3, 0x51, 0xda, 0xa7, // 14960
+ 0x47, 0xe5, 0x84, 0x53, 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x8b, 0x00, 0x78, 0x30, 0x81, 0x88, // 14976
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, // 14992
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4e, 0x65, 0x77, 0x20, 0x4a, 0x65, 0x72, 0x73, // 15008
+ 0x65, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b, 0x4a, 0x65, 0x72, // 15024
+ 0x73, 0x65, 0x79, 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, // 15040
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, // 15056
+ 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, // 15072
+ 0x03, 0x13, 0x25, 0x55, 0x53, 0x45, 0x52, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x43, 0x43, // 15088
+ 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, // 15104
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, // 15120
+ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, // 15136
+ 0x1a, 0xac, 0x54, 0x5a, 0xa9, 0xf9, 0x68, 0x23, 0xe7, 0x7a, 0xd5, 0x24, 0x6f, 0x53, 0xc6, 0x5a, // 15152
+ 0xd8, 0x4b, 0xab, 0xc6, 0xd5, 0xb6, 0xd1, 0xe6, 0x73, 0x71, 0xae, 0xdd, 0x9c, 0xd6, 0x0c, 0x61, // 15168
+ 0xfd, 0xdb, 0xa0, 0x89, 0x03, 0xb8, 0x05, 0x14, 0xec, 0x57, 0xce, 0xee, 0x5d, 0x3f, 0xe2, 0x21, // 15184
+ 0xb3, 0xce, 0xf7, 0xd4, 0x8a, 0x79, 0xe0, 0xa3, 0x83, 0x7e, 0x2d, 0x97, 0xd0, 0x61, 0xc4, 0xf1, // 15200
+ 0x99, 0xdc, 0x25, 0x91, 0x63, 0xab, 0x7f, 0x30, 0xa3, 0xb4, 0x70, 0xe2, 0xc7, 0xa1, 0x33, 0x9c, // 15216
+ 0xf3, 0xbf, 0x2e, 0x5c, 0x53, 0xb1, 0x5f, 0xb3, 0x7d, 0x32, 0x7f, 0x8a, 0x34, 0xe3, 0x79, 0x79, // 15232
+ 0x00, 0x8b, 0x02, 0x26, 0x30, 0x81, 0x88, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, // 15248
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4e, // 15264
+ 0x65, 0x77, 0x20, 0x4a, 0x65, 0x72, 0x73, 0x65, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, // 15280
+ 0x04, 0x07, 0x13, 0x0b, 0x4a, 0x65, 0x72, 0x73, 0x65, 0x79, 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, // 15296
+ 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, // 15312
+ 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, // 15328
+ 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x55, 0x53, 0x45, 0x52, 0x54, 0x72, // 15344
+ 0x75, 0x73, 0x74, 0x20, 0x52, 0x53, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, // 15360
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, // 15376
+ 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // 15392
+ 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, // 15408
+ 0x80, 0x12, 0x65, 0x17, 0x36, 0x0e, 0xc3, 0xdb, 0x08, 0xb3, 0xd0, 0xac, 0x57, 0x0d, 0x76, 0xed, // 15424
+ 0xcd, 0x27, 0xd3, 0x4c, 0xad, 0x50, 0x83, 0x61, 0xe2, 0xaa, 0x20, 0x4d, 0x09, 0x2d, 0x64, 0x09, // 15440
+ 0xdc, 0xce, 0x89, 0x9f, 0xcc, 0x3d, 0xa9, 0xec, 0xf6, 0xcf, 0xc1, 0xdc, 0xf1, 0xd3, 0xb1, 0xd6, // 15456
+ 0x7b, 0x37, 0x28, 0x11, 0x2b, 0x47, 0xda, 0x39, 0xc6, 0xbc, 0x3a, 0x19, 0xb4, 0x5f, 0xa6, 0xbd, // 15472
+ 0x7d, 0x9d, 0xa3, 0x63, 0x42, 0xb6, 0x76, 0xf2, 0xa9, 0x3b, 0x2b, 0x91, 0xf8, 0xe2, 0x6f, 0xd0, // 15488
+ 0xec, 0x16, 0x20, 0x90, 0x09, 0x3e, 0xe2, 0xe8, 0x74, 0xc9, 0x18, 0xb4, 0x91, 0xd4, 0x62, 0x64, // 15504
+ 0xdb, 0x7f, 0xa3, 0x06, 0xf1, 0x88, 0x18, 0x6a, 0x90, 0x22, 0x3c, 0xbc, 0xfe, 0x13, 0xf0, 0x87, // 15520
+ 0x14, 0x7b, 0xf6, 0xe4, 0x1f, 0x8e, 0xd4, 0xe4, 0x51, 0xc6, 0x11, 0x67, 0x46, 0x08, 0x51, 0xcb, // 15536
+ 0x86, 0x14, 0x54, 0x3f, 0xbc, 0x33, 0xfe, 0x7e, 0x6c, 0x9c, 0xff, 0x16, 0x9d, 0x18, 0xbd, 0x51, // 15552
+ 0x8e, 0x35, 0xa6, 0xa7, 0x66, 0xc8, 0x72, 0x67, 0xdb, 0x21, 0x66, 0xb1, 0xd4, 0x9b, 0x78, 0x03, // 15568
+ 0xc0, 0x50, 0x3a, 0xe8, 0xcc, 0xf0, 0xdc, 0xbc, 0x9e, 0x4c, 0xfe, 0xaf, 0x05, 0x96, 0x35, 0x1f, // 15584
+ 0x57, 0x5a, 0xb7, 0xff, 0xce, 0xf9, 0x3d, 0xb7, 0x2c, 0xb6, 0xf6, 0x54, 0xdd, 0xc8, 0xe7, 0x12, // 15600
+ 0x3a, 0x4d, 0xae, 0x4c, 0x8a, 0xb7, 0x5c, 0x9a, 0xb4, 0xb7, 0x20, 0x3d, 0xca, 0x7f, 0x22, 0x34, // 15616
+ 0xae, 0x7e, 0x3b, 0x68, 0x66, 0x01, 0x44, 0xe7, 0x01, 0x4e, 0x46, 0x53, 0x9b, 0x33, 0x60, 0xf7, // 15632
+ 0x94, 0xbe, 0x53, 0x37, 0x90, 0x73, 0x43, 0xf3, 0x32, 0xc3, 0x53, 0xef, 0xdb, 0xaa, 0xfe, 0x74, // 15648
+ 0x4e, 0x69, 0xc7, 0x6b, 0x8c, 0x60, 0x93, 0xde, 0xc4, 0xc7, 0x0c, 0xdf, 0xe1, 0x32, 0xae, 0xcc, // 15664
+ 0x93, 0x3b, 0x51, 0x78, 0x95, 0x67, 0x8b, 0xee, 0x3d, 0x56, 0xfe, 0x0c, 0xd0, 0x69, 0x0f, 0x1b, // 15680
+ 0x0f, 0xf3, 0x25, 0x26, 0x6b, 0x33, 0x6d, 0xf7, 0x6e, 0x47, 0xfa, 0x73, 0x43, 0xe5, 0x7e, 0x0e, // 15696
+ 0xa5, 0x66, 0xb1, 0x29, 0x7c, 0x32, 0x84, 0x63, 0x55, 0x89, 0xc4, 0x0d, 0xc1, 0x93, 0x54, 0x30, // 15712
+ 0x19, 0x13, 0xac, 0xd3, 0x7d, 0x37, 0xa7, 0xeb, 0x5d, 0x3a, 0x6c, 0x35, 0x5c, 0xdb, 0x41, 0xd7, // 15728
+ 0x12, 0xda, 0xa9, 0x49, 0x0b, 0xdf, 0xd8, 0x80, 0x8a, 0x09, 0x93, 0x62, 0x8e, 0xb5, 0x66, 0xcf, // 15744
+ 0x25, 0x88, 0xcd, 0x84, 0xb8, 0xb1, 0x3f, 0xa4, 0x39, 0x0f, 0xd9, 0x02, 0x9e, 0xeb, 0x12, 0x4c, // 15760
+ 0x95, 0x7c, 0xf3, 0x6b, 0x05, 0xa9, 0x5e, 0x16, 0x83, 0xcc, 0xb8, 0x67, 0xe2, 0xe8, 0x13, 0x9d, // 15776
+ 0xcc, 0x5b, 0x82, 0xd3, 0x4c, 0xb3, 0xed, 0x5b, 0xff, 0xde, 0xe5, 0x73, 0xac, 0x23, 0x3b, 0x2d, // 15792
+ 0x00, 0xbf, 0x35, 0x55, 0x74, 0x09, 0x49, 0xd8, 0x49, 0x58, 0x1a, 0x7f, 0x92, 0x36, 0xe6, 0x51, // 15808
+ 0x92, 0x0e, 0xf3, 0x26, 0x7d, 0x1c, 0x4d, 0x17, 0xbc, 0xc9, 0xec, 0x43, 0x26, 0xd0, 0xbf, 0x41, // 15824
+ 0x5f, 0x40, 0xa9, 0x44, 0x44, 0xf4, 0x99, 0xe7, 0x57, 0x87, 0x9e, 0x50, 0x1f, 0x57, 0x54, 0xa8, // 15840
+ 0x3e, 0xfd, 0x74, 0x63, 0x2f, 0xb1, 0x50, 0x65, 0x09, 0xe6, 0x58, 0x42, 0x2e, 0x43, 0x1a, 0x4c, // 15856
+ 0xb4, 0xf0, 0x25, 0x47, 0x59, 0xfa, 0x04, 0x1e, 0x93, 0xd4, 0x26, 0x46, 0x4a, 0x50, 0x81, 0xb2, // 15872
+ 0xde, 0xbe, 0x78, 0xb7, 0xfc, 0x67, 0x15, 0xe1, 0xc9, 0x57, 0x84, 0x1e, 0x0f, 0x63, 0xd6, 0xe9, // 15888
+ 0x62, 0xba, 0xd6, 0x5f, 0x55, 0x2e, 0xea, 0x5c, 0xc6, 0x28, 0x08, 0x04, 0x25, 0x39, 0xb8, 0x0e, // 15904
+ 0x2b, 0xa9, 0xf2, 0x4c, 0x97, 0x1c, 0x07, 0x3f, 0x0d, 0x52, 0xf5, 0xed, 0xef, 0x2f, 0x82, 0x0f, // 15920
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0x00, 0x92, 0x01, 0x26, 0x30, 0x81, 0x8f, 0x31, 0x0b, 0x30, 0x09, // 15936
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, // 15952
+ 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, // 15968
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, // 15984
+ 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, // 16000
+ 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, // 16016
+ 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, // 16032
+ 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x52, 0x6f, 0x6f, 0x74, // 16048
+ 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, // 16064
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, // 16080
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, // 16096
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0xed, 0xc1, 0x03, // 16112
+ 0xfc, 0xf6, 0x8f, 0xfc, 0x02, 0xb1, 0x6f, 0x5b, 0x9f, 0x48, 0xd9, 0x9d, 0x79, 0xe2, 0xa2, 0xb7, // 16128
+ 0x03, 0x61, 0x56, 0x18, 0xc3, 0x47, 0xb6, 0xd7, 0xca, 0x3d, 0x35, 0x2e, 0x89, 0x43, 0xf7, 0xa1, // 16144
+ 0x69, 0x9b, 0xde, 0x8a, 0x1a, 0xfd, 0x13, 0x20, 0x9c, 0xb4, 0x49, 0x77, 0x32, 0x29, 0x56, 0xfd, // 16160
+ 0xb9, 0xec, 0x8c, 0xdd, 0x22, 0xfa, 0x72, 0xdc, 0x27, 0x61, 0x97, 0xee, 0xf6, 0x5a, 0x84, 0xec, // 16176
+ 0x6e, 0x19, 0xb9, 0x89, 0x2c, 0xdc, 0x84, 0x5b, 0xd5, 0x74, 0xfb, 0x6b, 0x5f, 0xc5, 0x89, 0xa5, // 16192
+ 0x10, 0x52, 0x89, 0x46, 0x55, 0xf4, 0xb8, 0x75, 0x1c, 0xe6, 0x7f, 0xe4, 0x54, 0xae, 0x4b, 0xf8, // 16208
+ 0x55, 0x72, 0x57, 0x02, 0x19, 0xf8, 0x17, 0x71, 0x59, 0xeb, 0x1e, 0x28, 0x07, 0x74, 0xc5, 0x9d, // 16224
+ 0x48, 0xbe, 0x6c, 0xb4, 0xf4, 0xa4, 0xb0, 0xf3, 0x64, 0x37, 0x79, 0x92, 0xc0, 0xec, 0x46, 0x5e, // 16240
+ 0x7f, 0xe1, 0x6d, 0x53, 0x4c, 0x62, 0xaf, 0xcd, 0x1f, 0x0b, 0x63, 0xbb, 0x3a, 0x9d, 0xfb, 0xfc, // 16256
+ 0x79, 0x00, 0x98, 0x61, 0x74, 0xcf, 0x26, 0x82, 0x40, 0x63, 0xf3, 0xb2, 0x72, 0x6a, 0x19, 0x0d, // 16272
+ 0x99, 0xca, 0xd4, 0x0e, 0x75, 0xcc, 0x37, 0xfb, 0x8b, 0x89, 0xc1, 0x59, 0xf1, 0x62, 0x7f, 0x5f, // 16288
+ 0xb3, 0x5f, 0x65, 0x30, 0xf8, 0xa7, 0xb7, 0x4d, 0x76, 0x5a, 0x1e, 0x76, 0x5e, 0x34, 0xc0, 0xe8, // 16304
+ 0x96, 0x56, 0x99, 0x8a, 0xb3, 0xf0, 0x7f, 0xa4, 0xcd, 0xbd, 0xdc, 0x32, 0x31, 0x7c, 0x91, 0xcf, // 16320
+ 0xe0, 0x5f, 0x11, 0xf8, 0x6b, 0xaa, 0x49, 0x5c, 0xd1, 0x99, 0x94, 0xd1, 0xa2, 0xe3, 0x63, 0x5b, // 16336
+ 0x09, 0x76, 0xb5, 0x56, 0x62, 0xe1, 0x4b, 0x74, 0x1d, 0x96, 0xd4, 0x26, 0xd4, 0x08, 0x04, 0x59, // 16352
+ 0xd0, 0x98, 0x0e, 0x0e, 0xe6, 0xde, 0xfc, 0xc3, 0xec, 0x1f, 0x90, 0xf1, 0x02, 0x03, 0x01, 0x00, // 16368
+ 0x01, 0x00, 0x9b, 0x01, 0x26, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, // 16384
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, // 16400
+ 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, // 16416
+ 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, // 16432
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, // 16448
+ 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, // 16464
+ 0x6e, 0x63, 0x2e, 0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, // 16480
+ 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, // 16496
+ 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, // 16512
+ 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, // 16528
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, // 16544
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, // 16560
+ 0x00, 0xd5, 0x0c, 0x3a, 0xc4, 0x2a, 0xf9, 0x4e, 0xe2, 0xf5, 0xbe, 0x19, 0x97, 0x5f, 0x8e, 0x88, // 16576
+ 0x53, 0xb1, 0x1f, 0x3f, 0xcb, 0xcf, 0x9f, 0x20, 0x13, 0x6d, 0x29, 0x3a, 0xc8, 0x0f, 0x7d, 0x3c, // 16592
+ 0xf7, 0x6b, 0x76, 0x38, 0x63, 0xd9, 0x36, 0x60, 0xa8, 0x9b, 0x5e, 0x5c, 0x00, 0x80, 0xb2, 0x2f, // 16608
+ 0x59, 0x7f, 0xf6, 0x87, 0xf9, 0x25, 0x43, 0x86, 0xe7, 0x69, 0x1b, 0x52, 0x9a, 0x90, 0xe1, 0x71, // 16624
+ 0xe3, 0xd8, 0x2d, 0x0d, 0x4e, 0x6f, 0xf6, 0xc8, 0x49, 0xd9, 0xb6, 0xf3, 0x1a, 0x56, 0xae, 0x2b, // 16640
+ 0xb6, 0x74, 0x14, 0xeb, 0xcf, 0xfb, 0x26, 0xe3, 0x1a, 0xba, 0x1d, 0x96, 0x2e, 0x6a, 0x3b, 0x58, // 16656
+ 0x94, 0x89, 0x47, 0x56, 0xff, 0x25, 0xa0, 0x93, 0x70, 0x53, 0x83, 0xda, 0x84, 0x74, 0x14, 0xc3, // 16672
+ 0x67, 0x9e, 0x04, 0x68, 0x3a, 0xdf, 0x8e, 0x40, 0x5a, 0x1d, 0x4a, 0x4e, 0xcf, 0x43, 0x91, 0x3b, // 16688
+ 0xe7, 0x56, 0xd6, 0x00, 0x70, 0xcb, 0x52, 0xee, 0x7b, 0x7d, 0xae, 0x3a, 0xe7, 0xbc, 0x31, 0xf9, // 16704
+ 0x45, 0xf6, 0xc2, 0x60, 0xcf, 0x13, 0x59, 0x02, 0x2b, 0x80, 0xcc, 0x34, 0x47, 0xdf, 0xb9, 0xde, // 16720
+ 0x90, 0x65, 0x6d, 0x02, 0xcf, 0x2c, 0x91, 0xa6, 0xa6, 0xe7, 0xde, 0x85, 0x18, 0x49, 0x7c, 0x66, // 16736
+ 0x4e, 0xa3, 0x3a, 0x6d, 0xa9, 0xb5, 0xee, 0x34, 0x2e, 0xba, 0x0d, 0x03, 0xb8, 0x33, 0xdf, 0x47, // 16752
+ 0xeb, 0xb1, 0x6b, 0x8d, 0x25, 0xd9, 0x9b, 0xce, 0x81, 0xd1, 0x45, 0x46, 0x32, 0x96, 0x70, 0x87, // 16768
+ 0xde, 0x02, 0x0e, 0x49, 0x43, 0x85, 0xb6, 0x6c, 0x73, 0xbb, 0x64, 0xea, 0x61, 0x41, 0xac, 0xc9, // 16784
+ 0xd4, 0x54, 0xdf, 0x87, 0x2f, 0xc7, 0x22, 0xb2, 0x26, 0xcc, 0x9f, 0x59, 0x54, 0x68, 0x9f, 0xfc, // 16800
+ 0xbe, 0x2a, 0x2f, 0xc4, 0x55, 0x1c, 0x75, 0x40, 0x60, 0x17, 0x85, 0x02, 0x55, 0x39, 0x8b, 0x7f, // 16816
+ 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, // 16822
+};
+
diff --git a/Firmware/RTK_Surveyor/ZED.ino b/Firmware/RTK_Surveyor/ZED.ino
new file mode 100644
index 000000000..e2e2ae25a
--- /dev/null
+++ b/Firmware/RTK_Surveyor/ZED.ino
@@ -0,0 +1,109 @@
+// Enable data output from the NEO
+bool zedEnableLBandCommunication()
+{
+ bool response = true;
+
+#ifdef COMPILE_L_BAND
+
+ response &= theGNSS.setRXMCORcallbackPtr(&checkRXMCOR); // Enable callback to check if the PMP data is being decrypted successfully
+
+ if (productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ // Setup for ZED to NEO serial communication
+ response &= theGNSS.setVal32(UBLOX_CFG_UART2INPROT_UBX, true); // Configure ZED for UBX input on UART2
+
+ // Disable PMP callback over I2C. Enable UARTs.
+ response &= i2cLBand.setRXMPMPmessageCallbackPtr(nullptr); // Disable PMP callback to push raw PMP over I2C
+
+ response &= i2cLBand.newCfgValset();
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 0); // Disable UBX-RXM-PMP on NEO's I2C port
+
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 1); // Enable UBX output on NEO's UART2
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 1); // Output UBX-RXM-PMP on NEO's UART2
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2_BAUDRATE, settings.radioPortBaud); // Match baudrate with ZED
+ }
+ else if (productVariant == RTK_FACET_LBAND)
+ {
+ // Older versions of the Facet L-Band had solder jumpers that could be closed to directly connect the NEO
+ // to the ZED. If the user has explicitly disabled I2C corrections, enable a UART connection.
+ if (settings.useI2cForLbandCorrections == true)
+ {
+ response &= theGNSS.setVal32(UBLOX_CFG_UART2INPROT_UBX, settings.enableUART2UBXIn);
+
+ i2cLBand.setRXMPMPmessageCallbackPtr(&pushRXMPMP); // Enable PMP callback to push raw PMP over I2C
+
+ response &= i2cLBand.newCfgValset();
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 1); // Enable UBX-RXM-PMP on NEO's I2C port
+
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output on NEO's UART2
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 0); // Disable UBX-RXM-PMP on NEO's UART2
+ }
+ else // Setup ZED to NEO serial communication
+ {
+ response &= theGNSS.setVal32(UBLOX_CFG_UART2INPROT_UBX, true); // Configure ZED for UBX input on UART2
+
+ i2cLBand.setRXMPMPmessageCallbackPtr(nullptr); // Disable PMP callback to push raw PMP over I2C
+
+ response &= i2cLBand.newCfgValset();
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 0); // Disable UBX-RXM-PMP on NEO's I2C port
+
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 1); // Enable UBX output on UART2
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 1); // Output UBX-RXM-PMP on UART2
+ response &=
+ i2cLBand.addCfgValset(UBLOX_CFG_UART2_BAUDRATE, settings.radioPortBaud); // Match baudrate with ZED
+ }
+ }
+ else
+ {
+ systemPrintln("zedEnableLBandCorrections: Unknown platform");
+ return (false);
+ }
+
+ response &= i2cLBand.sendCfgValset();
+
+#endif
+
+ return (response);
+}
+
+// Disable data output from the NEO
+bool zedDisableLBandCommunication()
+{
+ bool response = true;
+
+#ifdef COMPILE_L_BAND
+ response &= i2cLBand.setRXMPMPmessageCallbackPtr(nullptr); // Disable PMP callback no matter the platform
+ response &= theGNSS.setRXMCORcallbackPtr(nullptr); // Disable callback to check if the PMP data is being decrypted successfully
+
+ if (productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ response &= i2cLBand.newCfgValset();
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output from NEO's UART2
+ }
+ else if (productVariant == RTK_FACET_LBAND)
+ {
+ // Older versions of the Facet L-Band had solder jumpers that could be closed to directly connect the NEO
+ // to the ZED. Check if the user has explicitly set I2C corrections.
+ if (settings.useI2cForLbandCorrections == true)
+ {
+ response &= i2cLBand.newCfgValset();
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 0); // Disable UBX-RXM-PMP from NEO's I2C port
+ }
+ else // Setup ZED to NEO serial communication
+ {
+ response &= i2cLBand.newCfgValset();
+ response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output from NEO's UART2
+ }
+ }
+ else
+ {
+ systemPrintln("zedEnableLBandCorrections: Unknown platform");
+ return (false);
+ }
+
+ response &= i2cLBand.sendCfgValset();
+
+#endif
+
+ return (response);
+}
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/bluetoothSelect.h b/Firmware/RTK_Surveyor/bluetoothSelect.h
new file mode 100644
index 000000000..11d1279a0
--- /dev/null
+++ b/Firmware/RTK_Surveyor/bluetoothSelect.h
@@ -0,0 +1,169 @@
+#ifdef COMPILE_BT
+
+//We use a local copy of the BluetoothSerial library so that we can increase the RX buffer. See issues:
+//https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/23
+//https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/469
+#include "src/BluetoothSerial/BluetoothSerial.h"
+
+#include //Click here to get the library: http://librarymanager/All#ESP32_BleSerial v1.0.4 by Avinab Malla
+
+class BTSerialInterface
+{
+ public:
+ virtual bool begin(String deviceName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize) = 0;
+ virtual void disconnect() = 0;
+ virtual void end() = 0;
+ virtual esp_err_t register_callback(esp_spp_cb_t *callback) = 0;
+ virtual void setTimeout(unsigned long timeout) = 0;
+
+ virtual int available() = 0;
+ virtual size_t readBytes(uint8_t *buffer, size_t bufferSize) = 0;
+ virtual int read() = 0;
+
+ // virtual bool isCongested() = 0;
+ virtual size_t write(const uint8_t *buffer, size_t size) = 0;
+ virtual size_t write(uint8_t value) = 0;
+ virtual void flush() = 0;
+};
+
+class BTClassicSerial : public virtual BTSerialInterface, public BluetoothSerial
+{
+ // Everything is already implemented in BluetoothSerial since the code was
+ // originally written using that class
+ public:
+ bool begin(String deviceName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize)
+ {
+ return BluetoothSerial::begin(deviceName, isMaster, rxQueueSize, txQueueSize);
+ }
+
+ void disconnect()
+ {
+ BluetoothSerial::disconnect();
+ }
+
+ void end()
+ {
+ BluetoothSerial::end();
+ }
+
+ esp_err_t register_callback(esp_spp_cb_t *callback)
+ {
+ return BluetoothSerial::register_callback(callback);
+ }
+
+ void setTimeout(unsigned long timeout)
+ {
+ BluetoothSerial::setTimeout(timeout);
+ }
+
+ int available()
+ {
+ return BluetoothSerial::available();
+ }
+
+ size_t readBytes(uint8_t *buffer, size_t bufferSize)
+ {
+ return BluetoothSerial::readBytes(buffer, bufferSize);
+ }
+
+ int read()
+ {
+ return BluetoothSerial::read();
+ }
+
+ size_t write(const uint8_t *buffer, size_t size)
+ {
+ return BluetoothSerial::write(buffer, size);
+ }
+
+ size_t write(uint8_t value)
+ {
+ return BluetoothSerial::write(value);
+ }
+
+ void flush()
+ {
+ BluetoothSerial::flush();
+ }
+};
+
+class BTLESerial : public virtual BTSerialInterface, public BleSerial
+{
+ public:
+ // Missing from BleSerial
+ bool begin(String deviceName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize)
+ {
+ BleSerial::begin(deviceName.c_str());
+ return true;
+ }
+
+ void disconnect()
+ {
+ Server->disconnect(Server->getConnId());
+ }
+
+ void end()
+ {
+ BleSerial::end();
+ }
+
+ esp_err_t register_callback(esp_spp_cb_t *callback)
+ {
+ connectionCallback = callback;
+ return ESP_OK;
+ }
+
+ void setTimeout(unsigned long timeout)
+ {
+ BleSerial::setTimeout(timeout);
+ }
+
+ int available()
+ {
+ return BleSerial::available();
+ }
+
+ size_t readBytes(uint8_t *buffer, size_t bufferSize)
+ {
+ return BleSerial::readBytes(buffer, bufferSize);
+ }
+
+ int read()
+ {
+ return BleSerial::read();
+ }
+
+ size_t write(const uint8_t *buffer, size_t size)
+ {
+ return BleSerial::write(buffer, size);
+ }
+
+ size_t write(uint8_t value)
+ {
+ return BleSerial::write(value);
+ }
+
+ void flush()
+ {
+ BleSerial::flush();
+ }
+
+ // override BLEServerCallbacks
+ void onConnect(BLEServer *pServer)
+ {
+ // bleConnected = true; Removed until PR is accepted
+ connectionCallback(ESP_SPP_SRV_OPEN_EVT, nullptr);
+ }
+
+ void onDisconnect(BLEServer *pServer)
+ {
+ // bleConnected = false; Removed until PR is accepted
+ connectionCallback(ESP_SPP_CLOSE_EVT, nullptr);
+ Server->startAdvertising();
+ }
+
+ private:
+ esp_spp_cb_t *connectionCallback;
+};
+
+#endif // COMPILE_BT
diff --git a/Firmware/RTK_Surveyor/crc24q.h b/Firmware/RTK_Surveyor/crc24q.h
new file mode 100644
index 000000000..793cf8c1d
--- /dev/null
+++ b/Firmware/RTK_Surveyor/crc24q.h
@@ -0,0 +1,99 @@
+/*
+ This is an implementation of the CRC-24Q cyclic redundancy checksum
+ used by Qualcomm, RTCM104V3, and PGP 6.5.1. According to the RTCM104V3
+ standard, it uses the error polynomial
+
+ x^24+ x^23+ x^18+ x^17+ x^14+ x^11+ x^10+ x^7+ x^6+ x^5+ x^4+ x^3+ x+1
+
+ This corresponds to a mask of 0x1864CFB. For a primer on CRC theory,
+ including detailed discussion of how and why the error polynomial is
+ expressed by this mask, see .
+
+ 1) It detects all single bit errors per 24-bit code word.
+ 2) It detects all double bit error combinations in a code word.
+ 3) It detects any odd number of errors.
+ 4) It detects any burst error for which the length of the burst is less than
+ or equal to 24 bits.
+ 5) It detects most large error bursts with length greater than 24 bits;
+ the odds of a false positive are at most 2^-23.
+
+ This hash should not be considered cryptographically secure, but it
+ is extremely good at detecting noise errors.
+
+ Note that this version has a seed of 0 wired in. The RTCM104V3 standard
+ requires this.
+
+ This file is Copyright 2008 by the GPSD project
+ SPDX-License-Identifier: BSD-2-clause
+*/
+
+//This file is originally from: https://gitlab.com/gpsd/gpsd/-/blob/master/gpsd/crc24q.c
+
+static const int unsigned crc24q[256] = {
+ 0x00000000u, 0x01864CFBu, 0x028AD50Du, 0x030C99F6u,
+ 0x0493E6E1u, 0x0515AA1Au, 0x061933ECu, 0x079F7F17u,
+ 0x08A18139u, 0x0927CDC2u, 0x0A2B5434u, 0x0BAD18CFu,
+ 0x0C3267D8u, 0x0DB42B23u, 0x0EB8B2D5u, 0x0F3EFE2Eu,
+ 0x10C54E89u, 0x11430272u, 0x124F9B84u, 0x13C9D77Fu,
+ 0x1456A868u, 0x15D0E493u, 0x16DC7D65u, 0x175A319Eu,
+ 0x1864CFB0u, 0x19E2834Bu, 0x1AEE1ABDu, 0x1B685646u,
+ 0x1CF72951u, 0x1D7165AAu, 0x1E7DFC5Cu, 0x1FFBB0A7u,
+ 0x200CD1E9u, 0x218A9D12u, 0x228604E4u, 0x2300481Fu,
+ 0x249F3708u, 0x25197BF3u, 0x2615E205u, 0x2793AEFEu,
+ 0x28AD50D0u, 0x292B1C2Bu, 0x2A2785DDu, 0x2BA1C926u,
+ 0x2C3EB631u, 0x2DB8FACAu, 0x2EB4633Cu, 0x2F322FC7u,
+ 0x30C99F60u, 0x314FD39Bu, 0x32434A6Du, 0x33C50696u,
+ 0x345A7981u, 0x35DC357Au, 0x36D0AC8Cu, 0x3756E077u,
+ 0x38681E59u, 0x39EE52A2u, 0x3AE2CB54u, 0x3B6487AFu,
+ 0x3CFBF8B8u, 0x3D7DB443u, 0x3E712DB5u, 0x3FF7614Eu,
+ 0x4019A3D2u, 0x419FEF29u, 0x429376DFu, 0x43153A24u,
+ 0x448A4533u, 0x450C09C8u, 0x4600903Eu, 0x4786DCC5u,
+ 0x48B822EBu, 0x493E6E10u, 0x4A32F7E6u, 0x4BB4BB1Du,
+ 0x4C2BC40Au, 0x4DAD88F1u, 0x4EA11107u, 0x4F275DFCu,
+ 0x50DCED5Bu, 0x515AA1A0u, 0x52563856u, 0x53D074ADu,
+ 0x544F0BBAu, 0x55C94741u, 0x56C5DEB7u, 0x5743924Cu,
+ 0x587D6C62u, 0x59FB2099u, 0x5AF7B96Fu, 0x5B71F594u,
+ 0x5CEE8A83u, 0x5D68C678u, 0x5E645F8Eu, 0x5FE21375u,
+ 0x6015723Bu, 0x61933EC0u, 0x629FA736u, 0x6319EBCDu,
+ 0x648694DAu, 0x6500D821u, 0x660C41D7u, 0x678A0D2Cu,
+ 0x68B4F302u, 0x6932BFF9u, 0x6A3E260Fu, 0x6BB86AF4u,
+ 0x6C2715E3u, 0x6DA15918u, 0x6EADC0EEu, 0x6F2B8C15u,
+ 0x70D03CB2u, 0x71567049u, 0x725AE9BFu, 0x73DCA544u,
+ 0x7443DA53u, 0x75C596A8u, 0x76C90F5Eu, 0x774F43A5u,
+ 0x7871BD8Bu, 0x79F7F170u, 0x7AFB6886u, 0x7B7D247Du,
+ 0x7CE25B6Au, 0x7D641791u, 0x7E688E67u, 0x7FEEC29Cu,
+ 0x803347A4u, 0x81B50B5Fu, 0x82B992A9u, 0x833FDE52u,
+ 0x84A0A145u, 0x8526EDBEu, 0x862A7448u, 0x87AC38B3u,
+ 0x8892C69Du, 0x89148A66u, 0x8A181390u, 0x8B9E5F6Bu,
+ 0x8C01207Cu, 0x8D876C87u, 0x8E8BF571u, 0x8F0DB98Au,
+ 0x90F6092Du, 0x917045D6u, 0x927CDC20u, 0x93FA90DBu,
+ 0x9465EFCCu, 0x95E3A337u, 0x96EF3AC1u, 0x9769763Au,
+ 0x98578814u, 0x99D1C4EFu, 0x9ADD5D19u, 0x9B5B11E2u,
+ 0x9CC46EF5u, 0x9D42220Eu, 0x9E4EBBF8u, 0x9FC8F703u,
+ 0xA03F964Du, 0xA1B9DAB6u, 0xA2B54340u, 0xA3330FBBu,
+ 0xA4AC70ACu, 0xA52A3C57u, 0xA626A5A1u, 0xA7A0E95Au,
+ 0xA89E1774u, 0xA9185B8Fu, 0xAA14C279u, 0xAB928E82u,
+ 0xAC0DF195u, 0xAD8BBD6Eu, 0xAE872498u, 0xAF016863u,
+ 0xB0FAD8C4u, 0xB17C943Fu, 0xB2700DC9u, 0xB3F64132u,
+ 0xB4693E25u, 0xB5EF72DEu, 0xB6E3EB28u, 0xB765A7D3u,
+ 0xB85B59FDu, 0xB9DD1506u, 0xBAD18CF0u, 0xBB57C00Bu,
+ 0xBCC8BF1Cu, 0xBD4EF3E7u, 0xBE426A11u, 0xBFC426EAu,
+ 0xC02AE476u, 0xC1ACA88Du, 0xC2A0317Bu, 0xC3267D80u,
+ 0xC4B90297u, 0xC53F4E6Cu, 0xC633D79Au, 0xC7B59B61u,
+ 0xC88B654Fu, 0xC90D29B4u, 0xCA01B042u, 0xCB87FCB9u,
+ 0xCC1883AEu, 0xCD9ECF55u, 0xCE9256A3u, 0xCF141A58u,
+ 0xD0EFAAFFu, 0xD169E604u, 0xD2657FF2u, 0xD3E33309u,
+ 0xD47C4C1Eu, 0xD5FA00E5u, 0xD6F69913u, 0xD770D5E8u,
+ 0xD84E2BC6u, 0xD9C8673Du, 0xDAC4FECBu, 0xDB42B230u,
+ 0xDCDDCD27u, 0xDD5B81DCu, 0xDE57182Au, 0xDFD154D1u,
+ 0xE026359Fu, 0xE1A07964u, 0xE2ACE092u, 0xE32AAC69u,
+ 0xE4B5D37Eu, 0xE5339F85u, 0xE63F0673u, 0xE7B94A88u,
+ 0xE887B4A6u, 0xE901F85Du, 0xEA0D61ABu, 0xEB8B2D50u,
+ 0xEC145247u, 0xED921EBCu, 0xEE9E874Au, 0xEF18CBB1u,
+ 0xF0E37B16u, 0xF16537EDu, 0xF269AE1Bu, 0xF3EFE2E0u,
+ 0xF4709DF7u, 0xF5F6D10Cu, 0xF6FA48FAu, 0xF77C0401u,
+ 0xF842FA2Fu, 0xF9C4B6D4u, 0xFAC82F22u, 0xFB4E63D9u,
+ 0xFCD11CCEu, 0xFD575035u, 0xFE5BC9C3u, 0xFFDD8538u,
+};
+
+#define COMPUTE_CRC24Q(parse, data) (((parse)->crc << 8) ^ crc24q[data ^ (((parse)->crc >> 16) & 0xff)])
diff --git a/Firmware/RTK_Surveyor/form.h b/Firmware/RTK_Surveyor/form.h
new file mode 100644
index 000000000..b49a81996
--- /dev/null
+++ b/Firmware/RTK_Surveyor/form.h
@@ -0,0 +1,8850 @@
+//This contains files neccessary to load the config page:
+//Files that do not change (boostrap.min.css, favicon) are gzip and stored in const array
+// * index.html
+// * favicon.ico
+
+// * /src/bootstrap.bundle.min.js - Needed for popper
+// * /src/bootstrap.min.css
+// * /src/bootstrap.min.js
+// * /src/jquery-3.6.0.min.js
+// * /src/main.js
+// * /src/rtk-setup.png
+// * /src/style.css
+
+// * /src/fonts/icomoon.eot
+// * /src/fonts/icomoon.svg
+// * /src/fonts/icomoon.ttf
+// * /src/fonts/icomoon.woof
+
+//To create uint8_t array from png/css/js etc. (will work on _any_ file, not just png's):
+// cd Firmware\Tools
+// python png_zipper.py ..\RTK_Surveyor\AP-Config\src\rtk-setup-wifi.png
+// The hex is saved in ..\RTK_Surveyor\AP-Config\src\rtk-setup-wifi.png.gzip_hex
+
+//To convert AP-Config\src\main.js to main_js[], run the Python main_js_zipper.py script in the Tools folder:
+// cd Firmware\Tools
+// python main_js_zipper.py
+
+static const uint8_t main_js[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x3B, 0xEE, 0x9C, 0x67, 0x02, 0xFF, 0x6D, 0x61, 0x69, 0x6E, 0x2E, 0x6A,
+ 0x73, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0xED, 0x7D, 0xEB, 0x7A, 0xDB, 0x38, 0x92, 0xE8, 0xFF,
+ 0x3C, 0x05, 0x5A, 0x67, 0x4E, 0x4B, 0x1A, 0xCB, 0xB2, 0x24, 0x5F, 0x12, 0xC7, 0xB1, 0x77, 0x7D,
+ 0x4B, 0xE2, 0x33, 0xB1, 0xE3, 0xCF, 0x4A, 0x3A, 0x9D, 0x64, 0x72, 0xBC, 0xB4, 0x08, 0xCB, 0x9C,
+ 0x48, 0xA4, 0x96, 0xA4, 0x62, 0x7B, 0x66, 0xF3, 0x4E, 0xFB, 0x0C, 0xFB, 0x64, 0xA7, 0x0A, 0x17,
+ 0x12, 0x00, 0xC1, 0x8B, 0x2E, 0x76, 0x32, 0x73, 0xDA, 0xDF, 0x4C, 0xDA, 0x26, 0x80, 0xAA, 0x42,
+ 0xA1, 0x50, 0x28, 0x14, 0x0A, 0x85, 0x6F, 0x4E, 0x48, 0x86, 0x4E, 0x4C, 0x6F, 0x9D, 0x7B, 0xB2,
+ 0x4B, 0xFE, 0xE3, 0x36, 0x7A, 0xBE, 0xB6, 0xF6, 0xA7, 0x7F, 0xDC, 0x7A, 0xBE, 0x1B, 0xDC, 0xB6,
+ 0x47, 0xC1, 0xC0, 0x89, 0xBD, 0xC0, 0x6F, 0xDF, 0x04, 0x51, 0xEC, 0x3B, 0x63, 0xFA, 0x7D, 0xED,
+ 0x36, 0xFA, 0x8F, 0x9D, 0x27, 0xDF, 0xA0, 0xD1, 0x2D, 0xBD, 0x8A, 0x82, 0xC1, 0x57, 0x1A, 0xEF,
+ 0x3C, 0x79, 0x22, 0xAA, 0x3B, 0xAE, 0x7B, 0xFC, 0x8D, 0xFA, 0xF1, 0x1B, 0x2F, 0x8A, 0xA9, 0x4F,
+ 0xC3, 0x46, 0x7D, 0x14, 0x38, 0x6E, 0xBD, 0x45, 0x02, 0xFF, 0x0D, 0xFC, 0xD2, 0x84, 0x9A, 0xD7,
+ 0x53, 0x7F, 0x80, 0x10, 0xC5, 0xA7, 0x06, 0xC5, 0xFA, 0x4D, 0xF2, 0x8F, 0x27, 0x04, 0x7E, 0x3C,
+ 0xDF, 0x8B, 0x3F, 0xD0, 0xAB, 0x3E, 0x03, 0xDB, 0x80, 0xEA, 0xDF, 0x95, 0x06, 0x46, 0xA1, 0x68,
+ 0x92, 0x50, 0x01, 0xC4, 0xFB, 0xF4, 0x96, 0xA4, 0x35, 0x44, 0xA7, 0x00, 0x8A, 0x56, 0xAF, 0x1D,
+ 0xF8, 0x63, 0x1A, 0x45, 0xCE, 0x90, 0x42, 0x8B, 0x04, 0x78, 0x63, 0x1C, 0x0D, 0x25, 0x48, 0xFC,
+ 0x99, 0x38, 0x61, 0x44, 0x4F, 0xFC, 0x41, 0x30, 0xF6, 0xFC, 0x21, 0x16, 0xB6, 0x5D, 0x27, 0x76,
+ 0x04, 0xAC, 0xEF, 0x3A, 0x61, 0x43, 0xDA, 0xA0, 0xB2, 0x6D, 0x48, 0xE3, 0x69, 0xE8, 0x13, 0x37,
+ 0x18, 0x4C, 0xC7, 0xD0, 0xB1, 0xF6, 0x90, 0xC6, 0xC7, 0x23, 0x8A, 0xBF, 0x1E, 0xDC, 0x9F, 0x40,
+ 0x6F, 0x79, 0x9F, 0x90, 0x7D, 0xD7, 0xDE, 0x1D, 0x75, 0xDF, 0x38, 0x48, 0x77, 0x67, 0x47, 0xF9,
+ 0x12, 0xF8, 0xC3, 0xF4, 0xD3, 0x64, 0xE4, 0xC4, 0xD7, 0x41, 0x38, 0x3E, 0x0F, 0x29, 0x94, 0xC2,
+ 0xF7, 0x5A, 0x7F, 0x1A, 0x7E, 0xA3, 0xF7, 0x41, 0x58, 0xE3, 0x15, 0x86, 0x34, 0x70, 0x69, 0xEC,
+ 0x0D, 0x38, 0xA0, 0x8D, 0x4E, 0xBB, 0xD3, 0x35, 0x0A, 0x80, 0xC0, 0x5D, 0xB2, 0xDA, 0xED, 0x6C,
+ 0xB6, 0xBB, 0xDB, 0x7A, 0xD1, 0xFE, 0x08, 0xDB, 0x74, 0x37, 0x3B, 0x9D, 0xB6, 0x68, 0x44, 0x07,
+ 0xF4, 0xFA, 0x77, 0x56, 0xBD, 0xF7, 0xAC, 0xD3, 0xEB, 0x6C, 0xB5, 0x37, 0xB7, 0x9E, 0xA5, 0x25,
+ 0x1F, 0xB1, 0x64, 0xE3, 0x69, 0x77, 0xEB, 0x59, 0x67, 0xA3, 0xBD, 0xD1, 0x59, 0x4F, 0x4B, 0x3E,
+ 0x31, 0xDC, 0xCF, 0xB6, 0xB6, 0xB6, 0x36, 0xDB, 0x1B, 0xCF, 0x36, 0x78, 0xC1, 0xC8, 0x89, 0xE2,
+ 0x97, 0xDE, 0x88, 0x9E, 0x81, 0xD8, 0x20, 0xE5, 0x35, 0xD9, 0x4B, 0xF8, 0x34, 0x1D, 0x5F, 0xD1,
+ 0x30, 0xED, 0xA6, 0xCF, 0xFE, 0x7E, 0x7B, 0x8D, 0xD5, 0xA3, 0x3E, 0x1D, 0xD1, 0x41, 0x4C, 0xDD,
+ 0xB4, 0x38, 0x12, 0x5F, 0x58, 0xB1, 0x02, 0x2A, 0xBA, 0x09, 0x40, 0xF0, 0x86, 0xF8, 0x19, 0x25,
+ 0x0E, 0xC7, 0xD3, 0x19, 0x45, 0x94, 0x17, 0x06, 0x57, 0xB1, 0xE3, 0xF9, 0xD4, 0x3D, 0xE5, 0x83,
+ 0x5D, 0xA9, 0xC2, 0x81, 0x13, 0x51, 0xBD, 0x92, 0x40, 0x21, 0xEA, 0x5C, 0xBC, 0x3B, 0x3C, 0xCD,
+ 0x02, 0xC2, 0x1E, 0xBD, 0x73, 0xAE, 0xE0, 0x1F, 0x7A, 0x17, 0x2B, 0xE4, 0x09, 0x31, 0x33, 0xBE,
+ 0x22, 0x5B, 0x04, 0xB8, 0x77, 0xF7, 0x13, 0x9A, 0x5F, 0x22, 0x88, 0xC1, 0x52, 0x56, 0x1C, 0xD2,
+ 0x41, 0x10, 0xBA, 0xD1, 0xF1, 0xE1, 0xF1, 0x4B, 0xF8, 0xFC, 0xF9, 0xCB, 0x8E, 0xFA, 0xF5, 0x95,
+ 0x18, 0x53, 0xA5, 0xE4, 0x7A, 0x3A, 0x1A, 0x9D, 0x03, 0xAC, 0xF7, 0x13, 0x10, 0x5D, 0xA5, 0x5B,
+ 0xA2, 0x59, 0x44, 0xE3, 0x77, 0xDE, 0x98, 0x06, 0xD3, 0x58, 0x32, 0xD9, 0x77, 0x8F, 0x40, 0xC6,
+ 0xB5, 0x8F, 0x83, 0x1B, 0x3A, 0xF8, 0x7A, 0x46, 0x6F, 0x5F, 0x7A, 0xE1, 0xF8, 0xD6, 0x09, 0xA9,
+ 0x56, 0x08, 0xB2, 0x6D, 0x2B, 0x7A, 0x32, 0x08, 0x7C, 0x60, 0xD1, 0x61, 0x00, 0x74, 0x79, 0x3E,
+ 0xA0, 0xC6, 0xCE, 0xE0, 0xB0, 0xF1, 0x19, 0x72, 0xF8, 0xF6, 0xED, 0xC5, 0xD1, 0xC9, 0xD9, 0xFE,
+ 0xBB, 0xE3, 0xCB, 0x93, 0xB3, 0xF3, 0xF7, 0xEF, 0x2E, 0xDF, 0x7D, 0x3C, 0x3F, 0xBE, 0x3C, 0x3A,
+ 0x7A, 0x4E, 0x3A, 0x2D, 0xB2, 0xB6, 0x76, 0x44, 0xAF, 0x9D, 0x29, 0x08, 0xE6, 0xD1, 0x51, 0xDB,
+ 0x95, 0x3F, 0x85, 0xED, 0x4E, 0x4F, 0x9F, 0x93, 0x2E, 0x6B, 0x09, 0xBF, 0xB6, 0xC7, 0xF8, 0x53,
+ 0x58, 0xFF, 0x12, 0x1B, 0xF4, 0x78, 0x03, 0x52, 0xB5, 0xC5, 0xE5, 0xD1, 0x7E, 0xFF, 0xF5, 0x73,
+ 0xB2, 0xCE, 0x9B, 0xAD, 0x56, 0x6E, 0xD6, 0xFF, 0x78, 0x7A, 0xF0, 0xF6, 0xCD, 0x73, 0xB2, 0xC1,
+ 0x1B, 0xFE, 0xCF, 0x7F, 0xCB, 0x96, 0xE3, 0x71, 0xBD, 0xA4, 0x57, 0xFD, 0xFE, 0x73, 0xB2, 0x99,
+ 0x90, 0x49, 0xFA, 0xFD, 0x76, 0xC4, 0x7E, 0xCA, 0x71, 0x42, 0xC3, 0xAD, 0xF9, 0x1A, 0x8A, 0x6E,
+ 0x3E, 0x4D, 0xBA, 0xB9, 0x3A, 0x53, 0x6B, 0xD9, 0xDB, 0x67, 0x69, 0x6F, 0xEB, 0x09, 0x80, 0x5A,
+ 0x69, 0x7F, 0x2F, 0xCF, 0xDE, 0x5E, 0x1E, 0x1D, 0x1F, 0x9E, 0x9C, 0xEE, 0x03, 0x8C, 0x6D, 0x39,
+ 0xA4, 0xFD, 0x3E, 0x59, 0x25, 0x67, 0x01, 0x71, 0xE9, 0xC0, 0x1B, 0x3B, 0xA3, 0x2A, 0x74, 0xA8,
+ 0x70, 0xBA, 0x1D, 0x95, 0x15, 0x33, 0x83, 0x42, 0x86, 0xE8, 0xF0, 0xBA, 0x2A, 0x73, 0xAA, 0xC2,
+ 0x3B, 0x39, 0xFB, 0x6D, 0xFF, 0xCD, 0xC9, 0xD1, 0xE5, 0xFB, 0xB3, 0xBF, 0x9C, 0xBD, 0xFD, 0x70,
+ 0x06, 0x60, 0x7A, 0x2D, 0xB9, 0x10, 0xC0, 0x74, 0xF9, 0x46, 0x43, 0x50, 0x6F, 0xE9, 0x8C, 0x41,
+ 0xD5, 0xD7, 0x16, 0xCA, 0x6F, 0x90, 0x7C, 0x3D, 0xF1, 0x27, 0xD3, 0x58, 0xE8, 0x0C, 0x63, 0x76,
+ 0xB5, 0xF3, 0xBA, 0xA1, 0xAE, 0xB8, 0x99, 0x35, 0x4D, 0x2E, 0x5A, 0x6B, 0x6B, 0x38, 0x65, 0x83,
+ 0x11, 0x85, 0xF5, 0x7E, 0xD8, 0xA8, 0x79, 0xA2, 0x8A, 0x54, 0x60, 0xCF, 0x49, 0x8D, 0xAC, 0x10,
+ 0xAC, 0x0F, 0xD0, 0xB0, 0x3E, 0x92, 0x85, 0xCB, 0x21, 0xD0, 0x81, 0x2B, 0x63, 0x34, 0x19, 0x79,
+ 0x71, 0xA3, 0xDE, 0xAA, 0x8B, 0xE5, 0x11, 0x16, 0x2C, 0xD2, 0x18, 0xC1, 0x8A, 0x7C, 0xC7, 0x54,
+ 0x38, 0xFC, 0xE7, 0x05, 0xAB, 0xDE, 0x1E, 0x51, 0x7F, 0x18, 0xDF, 0x00, 0xCF, 0xBA, 0xF8, 0x71,
+ 0x65, 0x97, 0xF4, 0xD4, 0x25, 0x17, 0xA1, 0x7A, 0xA8, 0xF5, 0xB1, 0xEE, 0xE7, 0xBB, 0x2F, 0x3B,
+ 0x5A, 0xC9, 0x37, 0x67, 0x94, 0x14, 0x01, 0x35, 0x5D, 0xA5, 0xD8, 0x24, 0xDF, 0xE5, 0x04, 0x03,
+ 0xAC, 0x15, 0x52, 0x6B, 0x61, 0x4B, 0xFE, 0x01, 0x7E, 0x91, 0x3D, 0xE0, 0xCD, 0xFA, 0x13, 0x18,
+ 0x38, 0x80, 0x0B, 0xDD, 0x1D, 0x3B, 0xBE, 0x1B, 0x25, 0x45, 0xDE, 0x35, 0x69, 0x78, 0x6E, 0x1B,
+ 0x18, 0x31, 0x9A, 0xBA, 0x34, 0x6A, 0xD4, 0x22, 0xF7, 0x34, 0x98, 0xFA, 0x30, 0x46, 0xB5, 0xA6,
+ 0x4A, 0x32, 0x07, 0xF3, 0x0E, 0x57, 0xFC, 0xC0, 0x5F, 0x0B, 0xAE, 0xAF, 0x49, 0xFF, 0x88, 0x80,
+ 0x2E, 0x74, 0xB4, 0x1A, 0x08, 0x8D, 0x91, 0x0F, 0xCA, 0x9C, 0xE9, 0xE0, 0x9A, 0x09, 0x03, 0x7F,
+ 0x6E, 0x3C, 0x97, 0x36, 0x6A, 0xB8, 0x96, 0x9C, 0x3A, 0x3E, 0x70, 0x3D, 0xAC, 0x35, 0x77, 0xB4,
+ 0x4A, 0xDF, 0xB5, 0xBF, 0x28, 0xC0, 0xD1, 0x20, 0xC7, 0xE1, 0xD4, 0x0E, 0x18, 0x97, 0xB0, 0x8A,
+ 0x80, 0xD3, 0xDF, 0x12, 0xF0, 0x38, 0x22, 0x00, 0x5D, 0x37, 0x44, 0x32, 0x78, 0x32, 0x76, 0x0A,
+ 0x50, 0xA5, 0x23, 0x49, 0x2C, 0xA2, 0xD8, 0x8B, 0x47, 0x6C, 0x5D, 0xBB, 0x78, 0xF7, 0x17, 0x36,
+ 0x2C, 0x46, 0x5B, 0x18, 0x33, 0xD2, 0x07, 0x2B, 0x6A, 0x52, 0xD3, 0x01, 0x64, 0x96, 0x33, 0xEC,
+ 0xB0, 0x32, 0x9C, 0x92, 0xD3, 0x26, 0x29, 0xAA, 0xCD, 0x94, 0xCF, 0x9E, 0x2B, 0x58, 0x6C, 0x0F,
+ 0x03, 0xFF, 0xDA, 0x1B, 0x9A, 0xDC, 0x49, 0xC7, 0x06, 0x56, 0xC8, 0x28, 0x08, 0xCB, 0x6A, 0x4D,
+ 0x26, 0x65, 0x35, 0x68, 0x7C, 0x43, 0x43, 0x9F, 0xC6, 0x65, 0xF5, 0xFC, 0xB8, 0x00, 0xD4, 0xDA,
+ 0x1A, 0xAF, 0xE4, 0x8C, 0x46, 0xC1, 0xED, 0x07, 0xEF, 0xA5, 0xF7, 0x16, 0x94, 0xC8, 0xB1, 0x84,
+ 0x3C, 0xF2, 0x80, 0xD5, 0xD0, 0x0C, 0xAA, 0xBD, 0x0C, 0xD0, 0x14, 0x00, 0xAB, 0x94, 0x12, 0x7A,
+ 0x37, 0x71, 0xFC, 0x08, 0x54, 0xC1, 0x6C, 0xD0, 0xFA, 0x14, 0xD8, 0x17, 0x16, 0x40, 0xCB, 0xA1,
+ 0x1F, 0x27, 0xEB, 0x79, 0x10, 0xC6, 0x87, 0x37, 0x8E, 0xEF, 0xD3, 0xD1, 0x51, 0x18, 0x4C, 0x60,
+ 0x93, 0xE0, 0x57, 0x94, 0x6B, 0xCB, 0x38, 0x1E, 0xDF, 0x4D, 0xC0, 0x6E, 0x89, 0x6A, 0xE4, 0xBF,
+ 0xFE, 0x8B, 0x58, 0x8A, 0x5F, 0x3A, 0x03, 0x1A, 0xFF, 0x31, 0xC6, 0x0F, 0x33, 0xC6, 0x0B, 0x8D,
+ 0x18, 0x39, 0x1F, 0x4D, 0xA3, 0x02, 0xAD, 0x57, 0x3C, 0x32, 0x7C, 0xF4, 0xFE, 0x18, 0x99, 0xAA,
+ 0xB3, 0x0F, 0xF6, 0xA3, 0xB5, 0xF1, 0xF4, 0x4E, 0x4C, 0xBB, 0x5E, 0xAD, 0x09, 0x0B, 0x19, 0x6C,
+ 0xC6, 0x5F, 0xBF, 0x3B, 0x7D, 0x83, 0x7A, 0xF7, 0xC3, 0x0D, 0xA5, 0xA3, 0xB5, 0x23, 0x2F, 0x24,
+ 0xC7, 0xB0, 0xD0, 0xBB, 0x34, 0xAC, 0xCD, 0x3B, 0x1F, 0xD9, 0x84, 0x23, 0x6F, 0x56, 0x0F, 0x60,
+ 0xF1, 0x2C, 0x9C, 0x94, 0xA2, 0x0E, 0x01, 0xA4, 0xB0, 0x91, 0x7B, 0xE0, 0x29, 0xCA, 0x21, 0xFD,
+ 0x31, 0x45, 0x2B, 0x0E, 0xE2, 0x05, 0xBD, 0xA6, 0x21, 0xF5, 0x07, 0x94, 0xF4, 0x63, 0xE6, 0xEB,
+ 0xF9, 0xE1, 0x1A, 0x94, 0xE3, 0x2A, 0x1F, 0x1E, 0x5E, 0xEF, 0x9F, 0x7C, 0x78, 0xAC, 0x26, 0x97,
+ 0x62, 0x79, 0xFE, 0x1D, 0xDD, 0x1E, 0x7C, 0x87, 0xFD, 0x1B, 0x0D, 0x11, 0xD0, 0x09, 0xD2, 0x9C,
+ 0xB5, 0x42, 0x4F, 0xA7, 0xB0, 0xE9, 0x06, 0x4B, 0x96, 0x12, 0xE7, 0x2A, 0xF8, 0x46, 0x49, 0xB6,
+ 0x21, 0x89, 0x69, 0x14, 0x9B, 0xAD, 0x02, 0xD7, 0xBB, 0xBE, 0x87, 0xCD, 0x7F, 0x1C, 0x83, 0xC5,
+ 0x1F, 0x11, 0x77, 0x4A, 0x49, 0x1C, 0x90, 0x6B, 0xD1, 0x92, 0x8C, 0xBC, 0xB1, 0xC7, 0xC5, 0x22,
+ 0xB2, 0xDA, 0xB4, 0x7B, 0xBB, 0xB0, 0x95, 0xE9, 0x5A, 0x25, 0x86, 0x39, 0x6D, 0x40, 0xE1, 0xA0,
+ 0x3A, 0x72, 0xEF, 0x7D, 0x67, 0xEC, 0x0D, 0x00, 0x1B, 0x1D, 0xD9, 0xC6, 0x09, 0x77, 0x0B, 0x3E,
+ 0xBD, 0x7D, 0x3B, 0x61, 0x1B, 0x15, 0xEE, 0xC9, 0xE3, 0x7F, 0x34, 0xEA, 0xA7, 0xC1, 0x2D, 0x0D,
+ 0xEB, 0x2D, 0x52, 0xEF, 0x76, 0xEB, 0x36, 0x29, 0x60, 0x78, 0xD0, 0xF5, 0xD8, 0x48, 0x20, 0xB4,
+ 0xC8, 0xD4, 0x77, 0x41, 0xC2, 0x7D, 0xEA, 0x5A, 0x5A, 0xE4, 0x21, 0x3A, 0x5E, 0xED, 0xC3, 0x66,
+ 0x2B, 0x16, 0xC8, 0x7A, 0x8B, 0x23, 0xB3, 0x8D, 0x32, 0xEC, 0x3A, 0xE2, 0x90, 0xB1, 0x7A, 0x88,
+ 0x1E, 0x52, 0x07, 0x1D, 0x5D, 0x57, 0xF7, 0x04, 0x8D, 0xE1, 0xA9, 0xEF, 0xC5, 0x25, 0xD2, 0x10,
+ 0xB9, 0x2F, 0x43, 0x4A, 0xFB, 0x13, 0x50, 0xAA, 0xB5, 0xA6, 0x86, 0x0B, 0xB4, 0xAF, 0x51, 0xB3,
+ 0xEF, 0xFD, 0xBD, 0xAC, 0xD2, 0x8D, 0x13, 0xBA, 0x38, 0xCC, 0x27, 0x47, 0x25, 0x15, 0xB3, 0xC2,
+ 0x54, 0xD2, 0xC0, 0x75, 0xEE, 0xA3, 0x0B, 0x3A, 0x76, 0x3C, 0x1F, 0xFA, 0x5A, 0x52, 0x77, 0x12,
+ 0x06, 0xB8, 0x3B, 0xE9, 0xA0, 0xA3, 0xB0, 0x5A, 0xD5, 0x6E, 0xF5, 0xAA, 0xBD, 0xEA, 0x55, 0xD7,
+ 0xAB, 0x57, 0xDD, 0xA8, 0x5E, 0x75, 0xB3, 0x7A, 0xD5, 0xAD, 0xEA, 0x55, 0x9F, 0x56, 0xA8, 0x1A,
+ 0x3A, 0xAE, 0x17, 0x9C, 0xEE, 0x1F, 0x96, 0x0D, 0x15, 0xFD, 0xE6, 0x0D, 0xE8, 0xC1, 0xBB, 0x52,
+ 0x21, 0x80, 0xDD, 0xB5, 0xF4, 0xE7, 0x96, 0xD4, 0xBC, 0x72, 0x62, 0x98, 0x46, 0xF7, 0xE7, 0x34,
+ 0x1C, 0x30, 0x0D, 0x9B, 0x54, 0x36, 0x15, 0x05, 0xE8, 0x06, 0xCF, 0xD5, 0xED, 0x13, 0x6D, 0xE7,
+ 0x58, 0xA6, 0x1D, 0xC3, 0xF8, 0x6B, 0x46, 0x2E, 0x2D, 0x38, 0xAC, 0xF5, 0xF2, 0xB1, 0xE6, 0xB7,
+ 0x7A, 0x3F, 0x19, 0x02, 0x57, 0x69, 0x6D, 0x01, 0x92, 0x07, 0xB8, 0x58, 0x85, 0xE3, 0x0B, 0x74,
+ 0xC0, 0x66, 0x89, 0x65, 0x7E, 0xD9, 0xC3, 0x60, 0x3C, 0x01, 0x8D, 0x48, 0x1B, 0xCD, 0x99, 0xC1,
+ 0xA2, 0x07, 0xF7, 0x82, 0x0E, 0xA8, 0x37, 0xB1, 0x00, 0xCF, 0xD6, 0xD1, 0x31, 0x94, 0xA0, 0x10,
+ 0xA2, 0xC7, 0x9D, 0xF7, 0x16, 0xE8, 0xD3, 0x10, 0xEC, 0x88, 0xF8, 0x5C, 0xAD, 0x65, 0xE3, 0xEC,
+ 0x9F, 0xD0, 0xC3, 0x34, 0x99, 0xC6, 0x9F, 0xF1, 0x3C, 0x69, 0x57, 0x00, 0xBD, 0x40, 0x59, 0xFD,
+ 0xF2, 0x19, 0x2A, 0x4F, 0xE9, 0x2E, 0x7A, 0x05, 0xAC, 0xD0, 0x56, 0x48, 0xED, 0x0B, 0xF0, 0x1E,
+ 0xDA, 0x4C, 0x1A, 0x75, 0xE6, 0x9B, 0xA6, 0x78, 0xBA, 0x84, 0x9E, 0x80, 0x19, 0x58, 0x25, 0x17,
+ 0xB7, 0xF7, 0x13, 0x3C, 0x9D, 0x92, 0xCC, 0xCE, 0x76, 0xC8, 0x5E, 0xAF, 0x31, 0x37, 0x26, 0xB4,
+ 0xAF, 0x70, 0x1B, 0x54, 0x8C, 0x87, 0xD7, 0x6A, 0x70, 0x67, 0x55, 0x55, 0x44, 0xCA, 0x31, 0x90,
+ 0x6D, 0x02, 0xA8, 0x67, 0x44, 0x36, 0x49, 0x5F, 0x68, 0x0E, 0x2A, 0x27, 0x4D, 0x05, 0xB8, 0xD9,
+ 0x6A, 0xFB, 0x60, 0xB8, 0xF7, 0x47, 0x05, 0xFD, 0xE6, 0xE7, 0x5C, 0x4B, 0xC7, 0xCD, 0x0E, 0xCB,
+ 0xB2, 0x58, 0xE5, 0x19, 0xDA, 0x83, 0xE0, 0xFB, 0x68, 0xC7, 0xF7, 0xF1, 0xA1, 0xF0, 0x7D, 0xB2,
+ 0xE3, 0xFB, 0xF4, 0x20, 0xF8, 0xA2, 0x89, 0x1F, 0xDC, 0x9E, 0x53, 0x0A, 0x9B, 0x87, 0xA9, 0xCD,
+ 0xC6, 0x4D, 0x6C, 0x4E, 0xD2, 0x69, 0x5A, 0x37, 0xBD, 0x13, 0x68, 0x0B, 0xEB, 0x5D, 0x64, 0xEE,
+ 0x78, 0x6B, 0xD5, 0x89, 0x10, 0x20, 0xEC, 0xAB, 0x88, 0x15, 0xFE, 0x0A, 0xEB, 0x26, 0xAA, 0xA6,
+ 0x17, 0x57, 0xE1, 0xDE, 0x0C, 0xA8, 0x22, 0x6E, 0x57, 0xE3, 0x79, 0x9E, 0x6D, 0x1D, 0x48, 0x0E,
+ 0xFB, 0xDA, 0x93, 0x69, 0x74, 0x33, 0xA3, 0x3E, 0x10, 0xB0, 0xE5, 0xA9, 0x60, 0x2E, 0x7C, 0x59,
+ 0x61, 0x1E, 0x1C, 0xD7, 0x63, 0x6E, 0x09, 0x98, 0xA0, 0x8D, 0x73, 0xDF, 0x99, 0x64, 0xE0, 0x7A,
+ 0xCC, 0xAD, 0xD6, 0xAC, 0x8A, 0x54, 0x8F, 0x58, 0x81, 0xE5, 0xB5, 0x17, 0x71, 0x48, 0x9C, 0x91,
+ 0x37, 0xF4, 0x77, 0xEB, 0x23, 0x7A, 0x1D, 0xD7, 0xF7, 0x4C, 0x77, 0xB1, 0xA5, 0x85, 0xBB, 0x87,
+ 0x2B, 0x8B, 0x46, 0x1F, 0x0E, 0xDB, 0x1A, 0x16, 0x54, 0x6D, 0x2D, 0xC7, 0xBA, 0x72, 0xA3, 0x17,
+ 0x6C, 0xB1, 0x23, 0xF1, 0xFD, 0x84, 0xEE, 0xF2, 0x35, 0xEB, 0x2A, 0xB8, 0xAB, 0x83, 0xBD, 0xB4,
+ 0x5B, 0xB7, 0x51, 0x53, 0x27, 0x6C, 0x59, 0xAC, 0x23, 0xB0, 0x93, 0xA3, 0x3A, 0x19, 0x40, 0x85,
+ 0x08, 0xFE, 0x84, 0x4D, 0xFB, 0x2A, 0x6B, 0xBD, 0xCA, 0xE1, 0x29, 0xFE, 0xFC, 0x43, 0xFC, 0x5C,
+ 0xDF, 0xAB, 0x48, 0xD2, 0x5A, 0x3C, 0x93, 0x94, 0xC2, 0x28, 0x43, 0xD3, 0xEC, 0x88, 0xE0, 0x71,
+ 0x31, 0xD2, 0x3D, 0xCB, 0x72, 0xF8, 0xFE, 0xE0, 0xF7, 0xCB, 0x2C, 0x24, 0xE5, 0xA8, 0x5C, 0x88,
+ 0x8C, 0xE7, 0xEE, 0xE4, 0xD5, 0xB8, 0xE0, 0xBE, 0xFF, 0x8C, 0xF2, 0x31, 0x80, 0xBC, 0x71, 0xAE,
+ 0xE8, 0x48, 0x1E, 0x9E, 0xE7, 0xD4, 0x3B, 0x12, 0x27, 0x58, 0x69, 0x2B, 0x79, 0x92, 0x75, 0x69,
+ 0x6E, 0xFD, 0xB0, 0x27, 0x4A, 0x2B, 0x79, 0x90, 0x05, 0x9B, 0xDF, 0x75, 0xDB, 0xD6, 0x57, 0x3D,
+ 0xFC, 0xE7, 0xC7, 0x75, 0x4A, 0xE3, 0xCF, 0xDD, 0x2F, 0xE8, 0x22, 0x40, 0x5E, 0x60, 0x38, 0xC1,
+ 0x65, 0xB7, 0xF3, 0x74, 0x03, 0x6A, 0xE0, 0xEF, 0x19, 0x40, 0x88, 0xD7, 0x8C, 0x17, 0xF8, 0x65,
+ 0x57, 0x85, 0x6D, 0x43, 0x2F, 0x27, 0xE1, 0xA9, 0x8D, 0x04, 0xFC, 0x6B, 0xC7, 0xDA, 0x44, 0x0D,
+ 0x57, 0x60, 0x92, 0x72, 0x13, 0x9A, 0xE2, 0x64, 0x98, 0x89, 0x46, 0x4B, 0x95, 0xF1, 0x7A, 0x87,
+ 0x51, 0xB0, 0x2F, 0xD9, 0x21, 0xA2, 0xF2, 0xB9, 0xC7, 0xF8, 0x90, 0xF0, 0xC0, 0xDA, 0x79, 0x0B,
+ 0xD3, 0x77, 0x77, 0xC9, 0x46, 0x5E, 0xAF, 0xE7, 0x25, 0xC4, 0xFA, 0x79, 0x3D, 0xA5, 0x6F, 0xA3,
+ 0xF3, 0xB4, 0x77, 0xD9, 0xAD, 0xC2, 0x0A, 0x68, 0x40, 0xC7, 0xE8, 0x7E, 0x61, 0xD1, 0x1B, 0xD0,
+ 0x85, 0x88, 0x52, 0x7F, 0x06, 0x86, 0x25, 0x9F, 0xA4, 0x2C, 0x22, 0x9C, 0x7A, 0xB3, 0xFD, 0xB7,
+ 0xC0, 0xF3, 0x1B, 0xF5, 0x7A, 0x33, 0x23, 0x3A, 0x58, 0xFE, 0xA4, 0x80, 0xA8, 0xCC, 0xA8, 0xBA,
+ 0xDE, 0x37, 0x4D, 0xA9, 0x0C, 0xC3, 0x60, 0x3A, 0x21, 0x61, 0x70, 0xCB, 0x75, 0xD2, 0x38, 0x1A,
+ 0x2A, 0x8C, 0x48, 0xB4, 0x92, 0x29, 0x09, 0x19, 0xB0, 0x23, 0xD6, 0x0F, 0x80, 0xC8, 0xD5, 0x9A,
+ 0xD9, 0x5E, 0xA2, 0x1C, 0x04, 0xA3, 0xD5, 0x68, 0xBC, 0xBA, 0x41, 0xF0, 0x97, 0x2D, 0xF6, 0x2F,
+ 0xA3, 0x82, 0x35, 0xAF, 0xEF, 0x19, 0x4D, 0x39, 0x73, 0xA0, 0xFD, 0xF3, 0x17, 0x6B, 0xAC, 0x46,
+ 0x29, 0x19, 0x4A, 0xEF, 0x34, 0x54, 0x1B, 0x75, 0x5D, 0x11, 0xF3, 0x88, 0x23, 0x43, 0xBD, 0x06,
+ 0x7E, 0x1C, 0x06, 0xA3, 0x7A, 0x09, 0x0E, 0xA9, 0xB9, 0x33, 0x5D, 0xE4, 0x9B, 0x17, 0xB5, 0x8C,
+ 0x69, 0xAB, 0x4A, 0xEC, 0x9B, 0x90, 0x1C, 0xB0, 0xC7, 0x61, 0x18, 0xA4, 0x74, 0x7A, 0xFE, 0xC8,
+ 0xF3, 0x29, 0xFF, 0x06, 0x0A, 0x7F, 0x52, 0x0A, 0x77, 0x0D, 0x18, 0xB2, 0xC7, 0xFF, 0x9D, 0x41,
+ 0xE9, 0xB3, 0x85, 0xC6, 0xF3, 0x87, 0x4A, 0x78, 0x8F, 0x65, 0xBF, 0x97, 0xAD, 0x34, 0xCB, 0x62,
+ 0xE0, 0xA7, 0xCD, 0x72, 0xB7, 0xEE, 0xD9, 0x3A, 0x33, 0xEF, 0x8B, 0x98, 0x5F, 0xB3, 0xB0, 0x1F,
+ 0xD9, 0x3A, 0xB3, 0x74, 0x23, 0x88, 0x1D, 0xD9, 0x2C, 0x6F, 0x7F, 0x97, 0xA9, 0x32, 0x63, 0x27,
+ 0x84, 0x37, 0xE5, 0x04, 0xE4, 0x33, 0xF5, 0xBF, 0xD8, 0xEC, 0x53, 0x6B, 0xC5, 0x76, 0x14, 0x0E,
+ 0x66, 0xF7, 0x51, 0x64, 0x62, 0x4E, 0x6C, 0xDE, 0x04, 0x5B, 0x60, 0xCA, 0x6C, 0xE6, 0x9E, 0x88,
+ 0x81, 0xB4, 0x19, 0x7C, 0x49, 0x74, 0xE4, 0x1C, 0x10, 0x03, 0xF4, 0x39, 0xDA, 0x41, 0xF2, 0xF0,
+ 0x4A, 0x03, 0xA6, 0xE2, 0x9D, 0x65, 0xC6, 0x14, 0x01, 0x13, 0x8D, 0x46, 0x64, 0x8D, 0x30, 0x17,
+ 0x1A, 0xB9, 0x9A, 0xC6, 0xB1, 0xEA, 0x00, 0xAF, 0x16, 0x7B, 0x11, 0x87, 0xF7, 0x96, 0x65, 0x4A,
+ 0xEC, 0x92, 0x84, 0xF3, 0x22, 0x09, 0x63, 0xD0, 0x34, 0x38, 0x19, 0x38, 0xF1, 0xE0, 0x86, 0x34,
+ 0x28, 0xCE, 0x73, 0xDB, 0x5A, 0xA7, 0xC5, 0xBB, 0x9C, 0x44, 0xD1, 0x94, 0x92, 0x5B, 0x0F, 0xD6,
+ 0xC6, 0x93, 0x23, 0x19, 0xFA, 0xD2, 0xAC, 0x7E, 0xDC, 0x50, 0x1C, 0x9A, 0x52, 0xB1, 0x17, 0x22,
+ 0xB6, 0xF0, 0xF1, 0xBA, 0xA1, 0x0C, 0xD9, 0xFE, 0x68, 0x04, 0xFB, 0x99, 0xE1, 0x74, 0x84, 0x01,
+ 0x44, 0x4C, 0xC7, 0xF3, 0xE1, 0xC3, 0x13, 0x47, 0xA6, 0x93, 0x8D, 0x91, 0x9B, 0xA1, 0x83, 0xAC,
+ 0xB9, 0xCD, 0xDE, 0x7C, 0xF8, 0x31, 0xFA, 0x6E, 0x0B, 0xCD, 0xEA, 0xCB, 0x73, 0x1A, 0xF4, 0x14,
+ 0x61, 0x34, 0x92, 0x30, 0x6F, 0xD9, 0xEE, 0x94, 0xBB, 0xCA, 0xF0, 0xD8, 0x77, 0x48, 0x85, 0xED,
+ 0x67, 0xEC, 0x84, 0xEB, 0xF5, 0x9D, 0xA4, 0x3A, 0xF3, 0x34, 0x0A, 0xF7, 0xDA, 0x29, 0x2C, 0xFA,
+ 0xD9, 0x9A, 0x82, 0x80, 0xA3, 0xC0, 0xAF, 0xC7, 0x64, 0xCA, 0xC3, 0x6E, 0x40, 0x60, 0x1C, 0x60,
+ 0xF7, 0x2D, 0xC5, 0x1D, 0x24, 0xF5, 0xBE, 0xC1, 0xE0, 0xDF, 0x3A, 0x91, 0xA2, 0x0B, 0x60, 0x04,
+ 0xAE, 0x83, 0x27, 0xD2, 0x7E, 0xD3, 0x83, 0x76, 0x54, 0x36, 0xE5, 0x46, 0xA7, 0xA6, 0xE3, 0xFA,
+ 0x32, 0x08, 0x07, 0x14, 0x86, 0x8C, 0x85, 0x4F, 0x0B, 0x02, 0xD2, 0xA1, 0x64, 0xA7, 0xDC, 0xD4,
+ 0x89, 0xA6, 0x21, 0x2B, 0xC7, 0xA5, 0xF6, 0xF5, 0xDF, 0xA1, 0x17, 0xAE, 0x17, 0x4D, 0x70, 0x68,
+ 0x58, 0x20, 0x3A, 0x9E, 0xD2, 0x90, 0xC3, 0x69, 0x14, 0x07, 0x63, 0xFE, 0x37, 0xEC, 0xC0, 0x90,
+ 0x3D, 0xF5, 0xA6, 0xA2, 0x7F, 0x87, 0x22, 0x02, 0x01, 0x35, 0x18, 0x0F, 0x15, 0x3A, 0xF1, 0x17,
+ 0x06, 0xF4, 0x12, 0xB5, 0xCD, 0xDC, 0x50, 0x98, 0xAE, 0x3A, 0x60, 0xA7, 0xAD, 0x6A, 0x9C, 0x1F,
+ 0xF7, 0x15, 0x2C, 0x17, 0xE6, 0x2B, 0x1A, 0xCC, 0x0D, 0x92, 0xFA, 0xB8, 0xB7, 0x7C, 0x13, 0x0C,
+ 0x87, 0xEC, 0x94, 0x67, 0x21, 0x28, 0x67, 0x71, 0xE8, 0x4D, 0xE4, 0x91, 0xEC, 0xE2, 0x90, 0xE4,
+ 0x71, 0xEC, 0x9C, 0x90, 0x8C, 0xD8, 0xA5, 0x05, 0x29, 0x3A, 0xBE, 0x83, 0x75, 0xD9, 0x77, 0x46,
+ 0xE7, 0x53, 0xA6, 0x62, 0x17, 0x82, 0x75, 0x0E, 0x3B, 0x81, 0xF8, 0x9C, 0x86, 0xD7, 0x74, 0x10,
+ 0x1F, 0x06, 0x21, 0x06, 0x54, 0xE0, 0xE9, 0xEC, 0xDC, 0x50, 0xD9, 0x12, 0xC7, 0x57, 0xF8, 0x39,
+ 0x21, 0x38, 0x7E, 0x4C, 0x7D, 0xDF, 0x49, 0x02, 0x09, 0x18, 0x85, 0xF3, 0x43, 0x9B, 0xC6, 0xC1,
+ 0xC9, 0xE9, 0xFB, 0x31, 0xFA, 0x00, 0xF7, 0xD1, 0xC1, 0x33, 0x5E, 0x5C, 0x22, 0xF6, 0x2F, 0xCE,
+ 0xE7, 0x11, 0xD2, 0x04, 0x12, 0xD7, 0x3D, 0x38, 0xFB, 0x30, 0xFA, 0x5F, 0x35, 0x0F, 0x79, 0x89,
+ 0x74, 0xA3, 0x99, 0xA5, 0xF1, 0x40, 0x48, 0xF4, 0x01, 0x2E, 0x48, 0x46, 0x09, 0x97, 0xD0, 0x4C,
+ 0xC9, 0xD4, 0x9D, 0x64, 0xBE, 0xB9, 0x37, 0x83, 0x89, 0x8C, 0x33, 0xC8, 0x62, 0x07, 0x33, 0x09,
+ 0xCD, 0x1A, 0x59, 0x00, 0x6B, 0xA3, 0x7A, 0x3B, 0x85, 0x85, 0x2C, 0xC0, 0x62, 0x23, 0x74, 0xAF,
+ 0x58, 0xD8, 0xA2, 0xF8, 0x1E, 0xD6, 0x14, 0x64, 0xC5, 0x88, 0xDD, 0xFB, 0xA9, 0xF9, 0x81, 0x4F,
+ 0x6B, 0xFA, 0xBD, 0x16, 0x16, 0x5F, 0x51, 0xDE, 0xF2, 0x6A, 0x14, 0x0C, 0xBE, 0xF2, 0xA6, 0x60,
+ 0x3E, 0x85, 0x14, 0xD5, 0xF9, 0x61, 0xFF, 0x37, 0x12, 0xF0, 0xA5, 0x42, 0x84, 0x16, 0xB0, 0xD0,
+ 0x5D, 0x05, 0xB6, 0xB8, 0x6D, 0x90, 0xDC, 0xE3, 0xE1, 0x57, 0x10, 0x58, 0x55, 0x6C, 0xAC, 0xB8,
+ 0x6E, 0xD6, 0xD6, 0x4E, 0xD2, 0x45, 0x3D, 0xA9, 0x3B, 0x18, 0x45, 0xE2, 0x62, 0x0D, 0x5E, 0x2D,
+ 0x48, 0xA2, 0x4B, 0xFF, 0x73, 0x0A, 0x06, 0x30, 0xBF, 0x41, 0x12, 0x84, 0x60, 0x18, 0x34, 0x6A,
+ 0x6D, 0x75, 0x6F, 0xD7, 0x22, 0xFC, 0x4F, 0xD7, 0x88, 0x45, 0xB4, 0x85, 0x2C, 0x2B, 0x18, 0x84,
+ 0xEF, 0x01, 0xBE, 0xAF, 0xAC, 0xA8, 0x8B, 0x98, 0x42, 0x30, 0xEC, 0xB6, 0x94, 0x06, 0x9F, 0xEF,
+ 0xBE, 0xB4, 0x45, 0xFC, 0x31, 0x3B, 0xCC, 0xD2, 0x4B, 0xB8, 0x55, 0xC1, 0x0A, 0x93, 0x21, 0xCB,
+ 0x58, 0x9F, 0x2D, 0x8B, 0xF1, 0x99, 0x78, 0x17, 0x70, 0xD1, 0x26, 0x63, 0xEE, 0xFE, 0x63, 0x7F,
+ 0xF0, 0xF2, 0x59, 0x99, 0x92, 0xFA, 0x13, 0x9F, 0xFB, 0x41, 0xDC, 0x68, 0x9B, 0x4E, 0xC5, 0xA6,
+ 0xE4, 0x17, 0x23, 0x25, 0xB1, 0x36, 0x1E, 0x97, 0x5B, 0xD2, 0xC8, 0xCC, 0xF2, 0xCB, 0x46, 0x87,
+ 0xEA, 0x38, 0xAF, 0x44, 0x87, 0xE6, 0x87, 0x07, 0x24, 0x18, 0x81, 0x5C, 0x6F, 0xD5, 0xE1, 0x5F,
+ 0x05, 0x14, 0xD0, 0x31, 0x1B, 0xFE, 0xC4, 0xB1, 0x3E, 0x13, 0x0D, 0x89, 0xBF, 0xDE, 0x46, 0x87,
+ 0x2C, 0xB4, 0xD3, 0x62, 0x98, 0x89, 0x3E, 0x2C, 0xF0, 0x43, 0x6E, 0x68, 0xA6, 0xA8, 0x32, 0x57,
+ 0xE1, 0x70, 0x16, 0x36, 0xB4, 0xF2, 0x27, 0xD2, 0xB7, 0xAB, 0x5C, 0x05, 0x82, 0xCE, 0xA5, 0x97,
+ 0x85, 0x1A, 0xB2, 0xB0, 0x45, 0x7A, 0x9D, 0x4E, 0xA7, 0x99, 0x55, 0x19, 0xCC, 0x3B, 0x01, 0xAA,
+ 0xA2, 0x45, 0x98, 0x6D, 0x8C, 0xBE, 0x08, 0x4D, 0x85, 0x60, 0xBF, 0xB8, 0x07, 0xC3, 0x30, 0x39,
+ 0xF1, 0xFC, 0x84, 0x15, 0x3C, 0x27, 0xD8, 0xED, 0xA4, 0xB5, 0x8E, 0x62, 0x30, 0xA2, 0x4E, 0x28,
+ 0x71, 0x54, 0x03, 0x5C, 0xCF, 0x12, 0xD9, 0x9F, 0x0E, 0x06, 0x60, 0x22, 0x33, 0x32, 0x95, 0x9B,
+ 0x10, 0x09, 0x1C, 0x51, 0x6E, 0x23, 0x51, 0x14, 0x71, 0x22, 0xA1, 0x2D, 0x02, 0xD7, 0xA9, 0x4B,
+ 0x81, 0x57, 0x85, 0x6B, 0xA1, 0x10, 0x6C, 0x72, 0x49, 0x9D, 0xE0, 0xA4, 0x34, 0x92, 0x93, 0xDB,
+ 0x92, 0xD7, 0x62, 0xFB, 0x81, 0xDB, 0x37, 0x76, 0x06, 0xAE, 0x08, 0x58, 0x76, 0x5B, 0x23, 0xF7,
+ 0x6C, 0xE8, 0x49, 0xC2, 0x85, 0xAA, 0x1D, 0x32, 0x5D, 0xD2, 0x10, 0x4E, 0xA5, 0x84, 0xB2, 0x9D,
+ 0xE2, 0x56, 0x18, 0xFB, 0xA4, 0xF9, 0xA1, 0xAC, 0x9E, 0x0C, 0xBE, 0x3B, 0xFA, 0x87, 0xB6, 0x97,
+ 0x31, 0x36, 0x5F, 0x33, 0x53, 0x98, 0x41, 0x57, 0x46, 0x9F, 0xA5, 0x4B, 0xC5, 0x14, 0x5A, 0x8E,
+ 0x30, 0xD9, 0x90, 0xDB, 0x46, 0x5A, 0x8C, 0x51, 0x2A, 0xED, 0x8A, 0x18, 0xA9, 0xE3, 0x57, 0x13,
+ 0x22, 0x2D, 0xEE, 0xD0, 0x24, 0xD1, 0x0A, 0xA6, 0xD0, 0x28, 0xE3, 0x6D, 0xAC, 0xB8, 0x16, 0x49,
+ 0x61, 0x17, 0x2F, 0x11, 0x2E, 0x3B, 0x2A, 0xE5, 0x37, 0x25, 0x15, 0x78, 0xA8, 0x31, 0xC5, 0x0E,
+ 0xF0, 0x37, 0x5C, 0x6B, 0x14, 0x49, 0x64, 0x65, 0x42, 0xBD, 0xCA, 0xB2, 0x16, 0x5E, 0xBD, 0xEB,
+ 0x6D, 0x6E, 0x02, 0xB1, 0x2C, 0x8A, 0xF0, 0x8A, 0xC2, 0xFF, 0xE2, 0x5B, 0x4A, 0x7D, 0xD2, 0x61,
+ 0xBB, 0x68, 0x28, 0xAB, 0x41, 0xE1, 0x20, 0x18, 0x8D, 0x9C, 0x49, 0x44, 0x5F, 0x9D, 0xF5, 0xFB,
+ 0x3C, 0xE6, 0x92, 0x6D, 0x1C, 0x8D, 0xF9, 0x29, 0x2A, 0xF5, 0xB9, 0x61, 0x0A, 0xFA, 0x62, 0xC0,
+ 0xA3, 0xE4, 0x06, 0x4E, 0x48, 0x55, 0x4D, 0x20, 0x0A, 0x6C, 0xE3, 0x8D, 0xFC, 0x93, 0xA3, 0x06,
+ 0x35, 0x79, 0x4B, 0x9B, 0x5C, 0x80, 0xD6, 0x5B, 0x65, 0xA5, 0xAB, 0xB8, 0x9A, 0xCF, 0xD8, 0x64,
+ 0x3A, 0x29, 0x6A, 0xC0, 0xE5, 0x28, 0x8B, 0x40, 0xED, 0x2B, 0x2C, 0xE4, 0x1E, 0x5A, 0x60, 0x2F,
+ 0x3D, 0x3A, 0x72, 0xA3, 0x46, 0x7A, 0x9F, 0xEA, 0x50, 0x30, 0x41, 0x58, 0x40, 0x83, 0x34, 0x80,
+ 0xD2, 0x64, 0x4F, 0xC2, 0x54, 0xB1, 0x15, 0x17, 0xB1, 0xAC, 0xC0, 0x6D, 0xB9, 0x9F, 0x47, 0xEC,
+ 0xD2, 0x4E, 0xC9, 0x6D, 0x9D, 0x0E, 0x09, 0x36, 0x1D, 0xFA, 0x51, 0x34, 0x6B, 0x3B, 0x1C, 0x4A,
+ 0xD1, 0x14, 0x7E, 0xAD, 0xD6, 0xFA, 0x20, 0x8D, 0x07, 0x86, 0xA6, 0x2C, 0x3A, 0xB8, 0x52, 0xBB,
+ 0xBE, 0x1A, 0x23, 0x0C, 0x2D, 0x45, 0xCC, 0x70, 0xA5, 0xB6, 0xE7, 0xE7, 0x0A, 0x8B, 0xD4, 0xBD,
+ 0x50, 0xB5, 0xD6, 0xB0, 0xA7, 0x8B, 0x54, 0x00, 0xF8, 0x67, 0xA5, 0x96, 0x18, 0xEF, 0x9B, 0x36,
+ 0xBC, 0xF5, 0xAE, 0xBD, 0x6A, 0xED, 0xDE, 0x1D, 0x9E, 0xBF, 0x3F, 0x52, 0x68, 0x06, 0xCB, 0xFF,
+ 0xBD, 0x3B, 0xA9, 0xD6, 0x96, 0x45, 0x45, 0xA5, 0x4D, 0x99, 0x15, 0x56, 0x91, 0xC3, 0xF7, 0x51,
+ 0x4C, 0xC7, 0x0A, 0x87, 0xF9, 0xDF, 0x95, 0xDA, 0x1E, 0xEB, 0x91, 0xD7, 0xD0, 0x3A, 0x89, 0xC5,
+ 0xAE, 0xD4, 0xFE, 0xEC, 0x9D, 0xD2, 0x5D, 0x0C, 0xCF, 0x96, 0xAD, 0xF8, 0x2A, 0x60, 0xAA, 0x2D,
+ 0x3E, 0x63, 0x84, 0xF8, 0x13, 0xDE, 0x32, 0xA3, 0xAA, 0x78, 0xD0, 0x6C, 0x1A, 0x83, 0x86, 0xAE,
+ 0xED, 0x16, 0xDE, 0xFB, 0xDD, 0xD8, 0x56, 0xB4, 0x56, 0x17, 0x63, 0x97, 0x37, 0xB6, 0xA1, 0xA5,
+ 0x13, 0x3A, 0x03, 0xD8, 0x73, 0x47, 0xAA, 0xDA, 0xD2, 0x67, 0x58, 0x33, 0x41, 0x8D, 0x73, 0x20,
+ 0x0F, 0x2F, 0x57, 0x91, 0x16, 0x47, 0x53, 0x0B, 0x6F, 0x68, 0x76, 0x3A, 0xDD, 0x5E, 0x8B, 0x5D,
+ 0x31, 0xCD, 0x2A, 0x4E, 0x5E, 0xCA, 0xD4, 0x67, 0xB7, 0xC3, 0x1A, 0xD8, 0x26, 0xAB, 0xE4, 0x25,
+ 0xA2, 0x3C, 0xC4, 0x7B, 0xD3, 0x14, 0xAA, 0x30, 0x6D, 0xD1, 0x90, 0x04, 0xDA, 0xC8, 0xF1, 0x7C,
+ 0xF8, 0xF0, 0xAD, 0xC6, 0x14, 0xF7, 0x76, 0x27, 0x57, 0x6F, 0x6F, 0x77, 0x2A, 0xA0, 0xCD, 0x80,
+ 0x3E, 0x3C, 0x7B, 0xBB, 0x10, 0xE4, 0xC4, 0x38, 0xC9, 0x73, 0xEE, 0x08, 0x8B, 0x5E, 0x35, 0x57,
+ 0x6C, 0x63, 0xED, 0xA7, 0x8D, 0x2E, 0x0F, 0x1D, 0x60, 0x4D, 0xF8, 0x3A, 0x88, 0x62, 0x31, 0xEC,
+ 0x9B, 0x99, 0x61, 0xDF, 0xCC, 0x19, 0xF6, 0x6C, 0xB7, 0x73, 0xBA, 0x9E, 0xC5, 0x87, 0xDA, 0x82,
+ 0xE3, 0xDB, 0xC6, 0x1F, 0x13, 0x25, 0xFB, 0x38, 0x1B, 0x2A, 0x5B, 0xDF, 0xD8, 0xD5, 0x52, 0xEE,
+ 0x34, 0x61, 0xB8, 0xD6, 0x3B, 0x26, 0xA2, 0xF5, 0xCE, 0xDC, 0x7D, 0xE3, 0x1D, 0x79, 0x1F, 0xD1,
+ 0xB0, 0x80, 0xA1, 0x96, 0x12, 0x6C, 0xC1, 0x74, 0x4E, 0xFC, 0xB5, 0x37, 0x0C, 0xDA, 0x83, 0x60,
+ 0x8C, 0x7F, 0xFD, 0x7B, 0x4D, 0x21, 0xCD, 0xF1, 0x09, 0x46, 0x78, 0x8F, 0x08, 0x2C, 0x95, 0xEC,
+ 0x5E, 0x5E, 0x21, 0x61, 0xD2, 0x9F, 0x4D, 0xB8, 0x3F, 0x19, 0x16, 0xE3, 0xF0, 0x36, 0xF4, 0x62,
+ 0xE1, 0x15, 0x07, 0x05, 0x43, 0x5C, 0x7E, 0xF5, 0x3F, 0x22, 0xA0, 0x6C, 0x68, 0x9B, 0x20, 0x05,
+ 0xB0, 0xC7, 0xBD, 0x27, 0xB7, 0x0E, 0xE8, 0x0A, 0xE0, 0x82, 0xEB, 0x45, 0x28, 0x4B, 0xE4, 0xEC,
+ 0xDD, 0xC5, 0xC9, 0x39, 0xEE, 0x8B, 0x09, 0x6C, 0x5A, 0xC9, 0x28, 0x80, 0x65, 0x16, 0x9B, 0xD3,
+ 0x3B, 0x58, 0xB6, 0xD1, 0xD3, 0x20, 0x2F, 0x33, 0xB4, 0x25, 0x46, 0xC5, 0xF0, 0x84, 0xBF, 0xF8,
+ 0x46, 0x19, 0x37, 0x11, 0x9C, 0x43, 0x45, 0x7C, 0x51, 0x7A, 0x2F, 0xBA, 0x51, 0x11, 0x80, 0x90,
+ 0x9C, 0x5E, 0xB7, 0xD3, 0xAD, 0xDA, 0x50, 0x13, 0x83, 0xDA, 0xD5, 0xC8, 0x0D, 0x2F, 0xFB, 0x13,
+ 0x27, 0xFC, 0xFA, 0x72, 0xEA, 0x77, 0x6B, 0xB3, 0x03, 0x39, 0xFF, 0x30, 0x23, 0xC9, 0x72, 0xC4,
+ 0xF1, 0x96, 0xC8, 0xBF, 0xE3, 0x3F, 0x73, 0x74, 0x1B, 0x61, 0x00, 0x62, 0x80, 0x62, 0x34, 0x1C,
+ 0x1A, 0xF3, 0xEB, 0x5D, 0xE8, 0xF8, 0xD1, 0xD8, 0x8B, 0x5F, 0xBD, 0xDA, 0xAF, 0xD9, 0x4F, 0xC3,
+ 0xA0, 0xA1, 0xE1, 0x16, 0x41, 0x5B, 0xEA, 0xFD, 0xC1, 0xEF, 0xF2, 0x90, 0xDB, 0x70, 0x08, 0x4D,
+ 0xAF, 0xEE, 0x84, 0xC9, 0x5B, 0xEC, 0xFB, 0xA8, 0xF3, 0xE0, 0x63, 0xCF, 0xFD, 0xBF, 0xBB, 0x18,
+ 0x48, 0xF1, 0x85, 0xC7, 0x54, 0x8C, 0xD9, 0x6E, 0x00, 0x31, 0x78, 0x6E, 0x44, 0x60, 0x3B, 0x1E,
+ 0x32, 0x51, 0x62, 0xB2, 0x89, 0xD5, 0x72, 0xF7, 0xFB, 0x0A, 0xDA, 0xBC, 0xBD, 0x7E, 0x36, 0xC0,
+ 0x49, 0x69, 0xC4, 0x7D, 0x1F, 0xC6, 0xDC, 0xD5, 0x6C, 0x77, 0xA5, 0x69, 0xD3, 0x70, 0x17, 0xB1,
+ 0x20, 0x13, 0x65, 0xE5, 0xB2, 0x5C, 0xF1, 0xFA, 0xA5, 0xE8, 0x16, 0xA6, 0x54, 0xD2, 0x96, 0xD3,
+ 0x0F, 0x8B, 0x8E, 0xCE, 0x51, 0x9B, 0xC1, 0x15, 0x0C, 0xFA, 0x37, 0xB6, 0x68, 0x81, 0x29, 0x10,
+ 0xF8, 0x2E, 0x2A, 0x82, 0xAD, 0x0E, 0xFE, 0xDF, 0xB6, 0x7E, 0x6C, 0x75, 0x70, 0x2E, 0x43, 0x99,
+ 0xAA, 0x2E, 0x0E, 0x72, 0x2F, 0x96, 0x15, 0x23, 0x3C, 0x0F, 0x22, 0x0F, 0xFF, 0xBB, 0x3F, 0x18,
+ 0x4C, 0x41, 0x3D, 0xDE, 0x73, 0x05, 0xBA, 0xD9, 0xEE, 0x5A, 0x10, 0x77, 0xDB, 0x0C, 0xF3, 0x66,
+ 0xBB, 0x00, 0xB3, 0x8E, 0x5A, 0x13, 0x76, 0x76, 0x7C, 0x72, 0xCC, 0x62, 0x7E, 0x5B, 0x5A, 0x82,
+ 0x1C, 0x93, 0x60, 0x7B, 0xAB, 0x8F, 0xD8, 0x4A, 0x49, 0x9E, 0x53, 0xAD, 0xD5, 0x27, 0x68, 0xA5,
+ 0x24, 0xD6, 0x29, 0x6F, 0xF4, 0xC6, 0x89, 0xD1, 0x65, 0xC2, 0x9A, 0xB5, 0x3B, 0xDB, 0x9D, 0xDE,
+ 0xF6, 0xC6, 0xD3, 0xED, 0x0A, 0xCD, 0x02, 0x7F, 0x28, 0xDA, 0xF1, 0x54, 0x41, 0xCF, 0x36, 0x3B,
+ 0x9B, 0x4F, 0xB7, 0xBA, 0xE5, 0x2D, 0xF7, 0x47, 0xB1, 0x17, 0x4F, 0x5D, 0x66, 0x8D, 0x6D, 0x6E,
+ 0x01, 0xCE, 0x67, 0xC5, 0xE8, 0xC4, 0x61, 0xC1, 0x6B, 0xEA, 0x0D, 0x6F, 0x10, 0x5D, 0xA7, 0x4A,
+ 0x6D, 0xE3, 0x68, 0x41, 0x6F, 0xF5, 0xBD, 0xE8, 0x74, 0x57, 0x07, 0x97, 0x23, 0xAA, 0x85, 0x24,
+ 0x14, 0x4B, 0x1B, 0x48, 0x53, 0xD3, 0x92, 0x84, 0xA0, 0xFC, 0x0C, 0x2F, 0x67, 0x7E, 0x2D, 0x30,
+ 0xAC, 0x8B, 0x0D, 0xED, 0xDC, 0xC3, 0x3B, 0xFB, 0x10, 0xCF, 0x36, 0xCC, 0xD9, 0xA6, 0x59, 0x8D,
+ 0xA0, 0xCF, 0xCD, 0xA7, 0x1D, 0xF6, 0xD3, 0x22, 0xC9, 0x2F, 0x89, 0x2E, 0x90, 0x65, 0xA8, 0x08,
+ 0xC4, 0xAF, 0xD5, 0xD4, 0x50, 0x19, 0xE2, 0x8F, 0x3F, 0x0A, 0xF1, 0xA7, 0x87, 0x42, 0x6C, 0xB9,
+ 0x41, 0x5C, 0x49, 0x54, 0x2B, 0xA8, 0xC8, 0xF9, 0xD5, 0xE4, 0x4C, 0xAA, 0xD2, 0xCE, 0xC7, 0xF4,
+ 0xB4, 0x0C, 0x16, 0xD0, 0xDF, 0x68, 0x88, 0xD7, 0x5F, 0xE1, 0xDB, 0x1A, 0x8B, 0x0C, 0x4A, 0x43,
+ 0x04, 0x2B, 0x0F, 0x82, 0x32, 0x43, 0x56, 0xBB, 0xDD, 0xCE, 0xFA, 0x46, 0x8B, 0x3C, 0x7B, 0xA6,
+ 0xED, 0x4A, 0xF9, 0x67, 0x1C, 0x00, 0x2C, 0xA8, 0xB8, 0xF8, 0xE4, 0xA0, 0x34, 0xE7, 0xD6, 0x2A,
+ 0xA6, 0x6F, 0xEB, 0xB4, 0x88, 0xF8, 0x8F, 0x82, 0x73, 0x53, 0x0C, 0x3A, 0xFB, 0x65, 0x21, 0x59,
+ 0xCB, 0x9B, 0x9D, 0xAB, 0xBD, 0x0E, 0x28, 0x04, 0x76, 0x0E, 0xD0, 0xD6, 0x70, 0xB3, 0x0F, 0x88,
+ 0x9B, 0xFD, 0x32, 0xAB, 0xB8, 0x29, 0xB1, 0x3D, 0x96, 0x4D, 0x64, 0x72, 0xAE, 0x9F, 0xD8, 0x8C,
+ 0x59, 0xDF, 0x77, 0xE1, 0xA6, 0x8B, 0x03, 0x50, 0xEC, 0xFC, 0xCB, 0xCE, 0xCC, 0x9E, 0x84, 0x99,
+ 0xCC, 0x95, 0x2C, 0x56, 0xDC, 0x1C, 0x48, 0xAC, 0xD5, 0x36, 0x96, 0xD5, 0x10, 0xE6, 0xF7, 0x13,
+ 0xED, 0x72, 0x86, 0xB1, 0x63, 0xF4, 0xB3, 0xB3, 0x84, 0x7E, 0x16, 0xA3, 0x3D, 0xFF, 0xF0, 0xA8,
+ 0x88, 0xD3, 0x0D, 0xD0, 0x83, 0x8D, 0x6B, 0x31, 0xDA, 0x47, 0xEE, 0xAF, 0x22, 0xC7, 0xDD, 0x07,
+ 0x42, 0x5B, 0x2C, 0xC7, 0x02, 0xAB, 0x29, 0xC7, 0x9D, 0x87, 0x93, 0xE3, 0xEE, 0x8F, 0x92, 0xE3,
+ 0xEE, 0x8F, 0x91, 0xE3, 0x1F, 0x83, 0x76, 0x89, 0xFD, 0x55, 0xB3, 0x1F, 0x3C, 0xB6, 0xFB, 0x27,
+ 0xEB, 0x02, 0x2A, 0x76, 0x67, 0xD8, 0x97, 0x07, 0x8B, 0x23, 0x68, 0x06, 0x30, 0x89, 0xBE, 0x57,
+ 0xDC, 0x41, 0x33, 0x34, 0x4F, 0x94, 0xB7, 0xDD, 0x35, 0x33, 0x23, 0x24, 0xA1, 0x9F, 0x6A, 0x33,
+ 0x35, 0x37, 0xB4, 0x2A, 0xF7, 0x4E, 0xB9, 0xB7, 0x7E, 0x7C, 0xEB, 0xF7, 0xE6, 0x04, 0x24, 0xE9,
+ 0xF8, 0x70, 0xB1, 0x79, 0x7B, 0x11, 0x6C, 0xBC, 0x9E, 0xA3, 0x3B, 0x89, 0xD6, 0xAB, 0xCD, 0x3B,
+ 0x28, 0x5D, 0x63, 0x7B, 0x32, 0xCB, 0x88, 0xCC, 0x8B, 0x58, 0x51, 0x26, 0x73, 0x0F, 0xC2, 0x22,
+ 0x8D, 0x73, 0x70, 0x7F, 0xD7, 0x5D, 0x4B, 0x6A, 0x98, 0x61, 0x89, 0x8B, 0x69, 0x59, 0xA9, 0xA0,
+ 0x74, 0x6B, 0x2F, 0x3F, 0xCE, 0xB1, 0xC4, 0xF2, 0x93, 0x31, 0xDB, 0x2C, 0x26, 0x5A, 0x01, 0x72,
+ 0xC4, 0xF2, 0x5C, 0x88, 0x43, 0xA0, 0x77, 0xC1, 0x57, 0x8A, 0x6E, 0x2E, 0x56, 0x79, 0xC7, 0x76,
+ 0xCD, 0x78, 0x4A, 0x93, 0x6B, 0x7E, 0xB6, 0xFB, 0xC6, 0xD6, 0xC3, 0xA9, 0x62, 0x6C, 0x2D, 0xB2,
+ 0xBE, 0xC5, 0xFF, 0x9F, 0x68, 0xD3, 0xF5, 0xAD, 0xBC, 0xB3, 0xAA, 0xF3, 0x22, 0x35, 0x5A, 0xEA,
+ 0xE8, 0x28, 0xA5, 0xA4, 0x56, 0xCB, 0xC6, 0x51, 0xB0, 0xD0, 0xCB, 0xBF, 0xD0, 0xFB, 0x0B, 0xEA,
+ 0xD3, 0x5B, 0x67, 0x54, 0xCB, 0xBF, 0xA5, 0x60, 0xC8, 0x0A, 0x3F, 0xCE, 0xAD, 0x20, 0x25, 0xA5,
+ 0x8E, 0xC8, 0xBC, 0xE0, 0x4F, 0x65, 0xC8, 0xB5, 0x10, 0x98, 0x02, 0x73, 0xC5, 0x1B, 0x4F, 0x3F,
+ 0x3A, 0xB7, 0x7C, 0x05, 0x5B, 0xDF, 0xCA, 0x39, 0xA2, 0x63, 0x87, 0x2B, 0x5B, 0xC6, 0x26, 0xA5,
+ 0x5F, 0x90, 0xC1, 0xCA, 0x8E, 0xE8, 0xDC, 0x8B, 0x07, 0x37, 0xB8, 0x23, 0xDA, 0xCE, 0x3B, 0x35,
+ 0x83, 0x12, 0x8E, 0x6D, 0x7B, 0x61, 0x64, 0x17, 0xD0, 0x92, 0xED, 0xFC, 0x9E, 0xE1, 0xBE, 0xEF,
+ 0x99, 0x15, 0x1B, 0x7C, 0xE6, 0xE8, 0xD8, 0x2F, 0x55, 0xF0, 0x19, 0x43, 0x8A, 0xA7, 0xDD, 0x65,
+ 0x67, 0xB1, 0x78, 0x06, 0x7E, 0x06, 0x18, 0x83, 0xF0, 0x6B, 0xA7, 0xDF, 0x3F, 0x39, 0xE2, 0xBC,
+ 0xDE, 0xEC, 0x98, 0xD6, 0xC2, 0x66, 0xDE, 0xE9, 0x95, 0x72, 0xA4, 0x6E, 0x39, 0x90, 0xB4, 0xA1,
+ 0x39, 0x77, 0xA2, 0x08, 0x7E, 0x71, 0x1F, 0x1E, 0x55, 0xF7, 0x71, 0x7A, 0xD4, 0x7D, 0xBC, 0x1E,
+ 0xF5, 0x1E, 0xA7, 0x47, 0xBD, 0xC7, 0xEB, 0xD1, 0xFA, 0xE3, 0xF4, 0x68, 0x7D, 0x49, 0x3D, 0x12,
+ 0x53, 0xEB, 0xDD, 0xE1, 0xF9, 0xDA, 0xFB, 0xA3, 0x73, 0x53, 0x5D, 0x2A, 0x2B, 0xDF, 0xB7, 0xB8,
+ 0xEA, 0x51, 0xB9, 0xD0, 0x0A, 0x13, 0xD9, 0x22, 0x3D, 0xB0, 0xDE, 0xDA, 0xDC, 0x5C, 0xCF, 0x9C,
+ 0x91, 0xB3, 0x8F, 0x2A, 0x81, 0x5A, 0x70, 0x4A, 0xC9, 0x91, 0x75, 0x82, 0x44, 0x39, 0x85, 0xAF,
+ 0xBE, 0xB5, 0xB6, 0x61, 0xFA, 0x6E, 0xEF, 0xBC, 0xE9, 0xE2, 0x29, 0xEF, 0x3C, 0x6F, 0xB1, 0x8C,
+ 0xCE, 0xE7, 0x90, 0xF4, 0xDE, 0x9D, 0xCC, 0x4E, 0x55, 0xD2, 0x68, 0x79, 0x84, 0xF1, 0x38, 0x11,
+ 0x91, 0x44, 0xE2, 0x74, 0x1A, 0xD3, 0xBB, 0xAC, 0xD0, 0x60, 0xBC, 0x8E, 0xC1, 0x4A, 0xF8, 0x04,
+ 0x30, 0x09, 0xAF, 0xC0, 0xE2, 0x37, 0x78, 0x01, 0x19, 0x38, 0x3E, 0xDB, 0x45, 0x01, 0x29, 0xBC,
+ 0x8D, 0x4B, 0x9C, 0x98, 0x6D, 0xA7, 0x22, 0x3C, 0xA9, 0x8C, 0x3D, 0x16, 0x66, 0x93, 0x4B, 0x98,
+ 0x5C, 0xFF, 0x59, 0x70, 0x51, 0xBE, 0x40, 0xA7, 0x77, 0x2F, 0xAA, 0xF1, 0x6E, 0xEC, 0xDC, 0x41,
+ 0x13, 0x0C, 0x38, 0xBE, 0x1C, 0x7B, 0x3E, 0xF4, 0x32, 0xE2, 0xDC, 0xEB, 0x76, 0x36, 0xBB, 0xBD,
+ 0x4E, 0x26, 0x3A, 0xA2, 0xDB, 0x82, 0xEF, 0xAD, 0x9E, 0xEE, 0xE5, 0xD4, 0x02, 0xA0, 0x8A, 0xE3,
+ 0x3E, 0x38, 0xBA, 0x37, 0xCC, 0xD8, 0x5B, 0x2E, 0x42, 0x6B, 0xDC, 0xAB, 0x6E, 0xA9, 0x59, 0xFB,
+ 0xBA, 0xD5, 0x21, 0x7F, 0x26, 0x3D, 0xF5, 0xB8, 0xCF, 0xD6, 0x28, 0x43, 0xB1, 0xDE, 0xEC, 0xBB,
+ 0x2D, 0x10, 0x47, 0xBB, 0x09, 0x53, 0x6D, 0x34, 0xD2, 0x26, 0x27, 0x7E, 0x8C, 0x47, 0x61, 0x23,
+ 0x21, 0xCB, 0x59, 0xC6, 0x6C, 0x2D, 0x85, 0x25, 0x76, 0x84, 0x1D, 0xF3, 0xF0, 0x5B, 0x86, 0xA9,
+ 0x15, 0x19, 0x9C, 0xC5, 0xC9, 0x4D, 0x13, 0xD6, 0x08, 0x48, 0x47, 0xAF, 0x0F, 0xCF, 0x67, 0x34,
+ 0x36, 0x4F, 0xCE, 0xF7, 0x79, 0xF0, 0x4B, 0x0A, 0xE5, 0xE4, 0x5C, 0x0D, 0x92, 0xF1, 0x7D, 0xBF,
+ 0xAD, 0xFC, 0x5F, 0x65, 0xCF, 0x71, 0x61, 0x86, 0xD3, 0x12, 0x2C, 0x47, 0x67, 0xFD, 0xC7, 0x40,
+ 0xF3, 0x8A, 0x3F, 0xA2, 0xF3, 0x18, 0xA8, 0xFA, 0xD3, 0x2B, 0xF8, 0x77, 0x61, 0x4C, 0xDF, 0x8B,
+ 0x64, 0x59, 0xE2, 0x3A, 0x8B, 0x27, 0x42, 0x29, 0x77, 0xB2, 0x4A, 0xB9, 0x63, 0x55, 0xCA, 0x76,
+ 0x8C, 0x89, 0x34, 0x9E, 0xBD, 0x3B, 0x9F, 0x5F, 0x10, 0xAD, 0xDE, 0x56, 0xA0, 0x70, 0x34, 0x82,
+ 0x1D, 0x53, 0xE0, 0x73, 0xAD, 0xBE, 0x0E, 0x93, 0xE0, 0xA9, 0xBA, 0x77, 0x64, 0xAA, 0xE8, 0xA9,
+ 0x4A, 0x63, 0x1A, 0x79, 0x59, 0x16, 0xE9, 0x36, 0x01, 0xDA, 0x06, 0x1E, 0xCB, 0xBA, 0x00, 0x5B,
+ 0x09, 0x8C, 0x38, 0xD3, 0x4E, 0x70, 0xD6, 0x19, 0x0F, 0x3A, 0x73, 0xC2, 0xBE, 0x08, 0x02, 0xD8,
+ 0x75, 0x8E, 0x98, 0xD8, 0xE0, 0x1E, 0xA5, 0x93, 0x39, 0x95, 0xE4, 0x7B, 0x93, 0x4E, 0x0B, 0xBF,
+ 0x1A, 0xC7, 0x53, 0xB3, 0xE2, 0xF1, 0xA2, 0x89, 0x48, 0x1F, 0xF1, 0x10, 0xC8, 0x52, 0x1F, 0xE9,
+ 0x24, 0x19, 0xC2, 0x13, 0x57, 0x58, 0x3F, 0x19, 0xE3, 0x87, 0xD9, 0x3E, 0x51, 0x21, 0x02, 0xC5,
+ 0xC1, 0x12, 0xC6, 0x15, 0x62, 0x77, 0xAC, 0x6F, 0x17, 0xE8, 0x3A, 0xDD, 0xBC, 0x5D, 0x5A, 0x3D,
+ 0x74, 0x87, 0xAA, 0x2D, 0x71, 0x0D, 0x3A, 0xE0, 0x1B, 0x48, 0xF6, 0xF7, 0xE5, 0x34, 0x4A, 0x14,
+ 0xBD, 0xC9, 0x52, 0xA1, 0xED, 0x6D, 0x2C, 0x55, 0x23, 0xAC, 0xCB, 0xF7, 0xB3, 0x1A, 0x05, 0x62,
+ 0x41, 0x5B, 0x2A, 0xDE, 0x19, 0x9C, 0x26, 0x55, 0xB8, 0xC1, 0x08, 0x2A, 0x8C, 0x17, 0xC9, 0xEF,
+ 0x51, 0xC7, 0xD6, 0x78, 0x68, 0x32, 0x01, 0x66, 0xBD, 0x13, 0x7A, 0xF1, 0x7D, 0x2D, 0xCD, 0x3E,
+ 0xD0, 0xC9, 0x6E, 0xD3, 0xE5, 0xDB, 0x31, 0xF6, 0x9C, 0x9B, 0xC6, 0x6D, 0x10, 0xBC, 0xD0, 0x2A,
+ 0xAA, 0x28, 0xF7, 0x2D, 0xF5, 0xAB, 0x0B, 0x4A, 0xD8, 0xAE, 0x1A, 0xA1, 0xBD, 0x4B, 0xB4, 0xE4,
+ 0xCE, 0xE9, 0xFD, 0xAE, 0x7A, 0xE4, 0x7C, 0xA3, 0x07, 0xB1, 0x5F, 0x87, 0x21, 0x38, 0x07, 0x06,
+ 0x00, 0x6F, 0x19, 0x1F, 0xD8, 0x3D, 0x17, 0x05, 0x02, 0x3E, 0xE7, 0xC1, 0xFE, 0xAC, 0x99, 0xD6,
+ 0x8C, 0xBC, 0x25, 0x95, 0x40, 0x52, 0x2A, 0x3C, 0x68, 0x8E, 0x50, 0xC5, 0x04, 0x31, 0xBA, 0xBB,
+ 0xB7, 0xB4, 0xDE, 0x46, 0x3F, 0x65, 0x77, 0xFF, 0xA1, 0xDD, 0x8D, 0xAE, 0x92, 0x30, 0x02, 0xDF,
+ 0xE8, 0x03, 0x05, 0xD8, 0x26, 0xA2, 0xD7, 0xB7, 0x8E, 0x17, 0xB7, 0xDB, 0xED, 0xBA, 0x12, 0xAD,
+ 0x90, 0x23, 0x83, 0xF6, 0x08, 0x4C, 0x19, 0x7E, 0x99, 0xED, 0xE8, 0x73, 0x49, 0x7C, 0xE2, 0x94,
+ 0x7D, 0xA2, 0xE6, 0x9A, 0xE3, 0x97, 0x85, 0x4D, 0x83, 0x58, 0x1F, 0x1C, 0xA5, 0x58, 0xBD, 0xE3,
+ 0xA7, 0x0E, 0x5E, 0xDF, 0xF9, 0x86, 0xDD, 0x69, 0xB7, 0xB5, 0x80, 0x0B, 0xE3, 0x46, 0x64, 0x2D,
+ 0xCD, 0x8C, 0xD1, 0x2A, 0x62, 0x7C, 0x4B, 0x03, 0x82, 0x2C, 0xCD, 0xB9, 0x6A, 0x60, 0x5E, 0x2B,
+ 0x52, 0xEF, 0x37, 0x2D, 0x72, 0xAD, 0x67, 0xF1, 0xAB, 0x3D, 0x8B, 0x5C, 0xEF, 0x59, 0xF4, 0x8A,
+ 0xCF, 0xE2, 0xD7, 0x7C, 0x16, 0xBB, 0xEA, 0xB3, 0xC8, 0x75, 0x9F, 0x45, 0xAF, 0xFC, 0x2C, 0x76,
+ 0xED, 0x67, 0xD1, 0xAB, 0x3F, 0xCB, 0xB8, 0xFE, 0x33, 0xDF, 0x15, 0xA0, 0x64, 0xF5, 0x4A, 0xEF,
+ 0x51, 0xC2, 0xE4, 0xE4, 0x75, 0xFF, 0x09, 0xD6, 0xA6, 0x9F, 0x6B, 0xE9, 0xB0, 0x6A, 0xF6, 0x87,
+ 0x52, 0x96, 0x96, 0xD4, 0x12, 0xD6, 0x5B, 0x4B, 0xCA, 0xE5, 0x64, 0xD4, 0x88, 0xD3, 0xAB, 0x3B,
+ 0xBD, 0xC6, 0xAB, 0xF3, 0xBE, 0x65, 0x8F, 0x9D, 0xD0, 0xF2, 0xEB, 0xAF, 0xC4, 0xDE, 0x0C, 0x64,
+ 0x62, 0xC4, 0x52, 0xD5, 0xCC, 0xDC, 0xF4, 0x80, 0x7A, 0x47, 0xC1, 0x74, 0x2E, 0xA4, 0x6F, 0xDE,
+ 0x9E, 0xED, 0xF7, 0x73, 0xE9, 0x6D, 0x1A, 0x0B, 0xAA, 0xED, 0xF2, 0x4B, 0x99, 0xEA, 0x57, 0x04,
+ 0x25, 0x83, 0x5E, 0x15, 0x99, 0x9B, 0x00, 0x03, 0x1D, 0x60, 0x2F, 0xC8, 0x6E, 0xFA, 0x27, 0x75,
+ 0x54, 0x59, 0x49, 0x85, 0x69, 0x65, 0xC5, 0x14, 0x10, 0x9B, 0x28, 0x64, 0xFB, 0x9B, 0xB9, 0xCB,
+ 0x8B, 0xBD, 0x3E, 0xF0, 0xE2, 0x53, 0x67, 0x92, 0xDE, 0x15, 0x1E, 0x7B, 0x3E, 0xFC, 0xE3, 0xDC,
+ 0xB5, 0xC8, 0x15, 0x2B, 0x51, 0xEE, 0xF9, 0xB7, 0x12, 0x8D, 0x70, 0x72, 0xA4, 0x4C, 0x66, 0x79,
+ 0x1C, 0x9B, 0x64, 0xD4, 0xE2, 0xD4, 0x8D, 0x9D, 0xE8, 0x2B, 0x2F, 0xE0, 0x80, 0xB4, 0x42, 0x94,
+ 0x1F, 0x7E, 0x0C, 0x4B, 0x5E, 0x20, 0xCA, 0x26, 0x1E, 0x2A, 0x8B, 0x0F, 0x7B, 0x88, 0x9E, 0x7F,
+ 0x68, 0x30, 0x20, 0xBF, 0x92, 0x46, 0x97, 0xBC, 0x78, 0xC1, 0x71, 0x35, 0x9B, 0x38, 0x4A, 0x9D,
+ 0xA6, 0x31, 0x3A, 0x05, 0xF9, 0x01, 0xEC, 0x49, 0x07, 0x94, 0xB6, 0x4A, 0xAF, 0xCA, 0xC6, 0xB3,
+ 0x68, 0x10, 0x32, 0x1E, 0x2F, 0x99, 0xC5, 0x40, 0xD1, 0x8B, 0xF2, 0xB2, 0x0B, 0xF4, 0xDF, 0x12,
+ 0x95, 0x1A, 0xE1, 0xEB, 0x82, 0xEC, 0xBD, 0xC9, 0x09, 0xA8, 0x64, 0xDC, 0xFC, 0x19, 0x83, 0x95,
+ 0x44, 0xB7, 0x2A, 0x69, 0x55, 0xD8, 0xA3, 0x8A, 0x7A, 0x1C, 0xF9, 0x4E, 0x9A, 0x47, 0x25, 0xE9,
+ 0x1A, 0x56, 0xB2, 0xAC, 0xFC, 0x69, 0xC6, 0x30, 0x97, 0xC6, 0xFC, 0x19, 0x5E, 0xD8, 0xA1, 0x0A,
+ 0x38, 0xE9, 0x1E, 0x25, 0x49, 0xD7, 0xF2, 0x4D, 0x66, 0x61, 0xC3, 0x60, 0x77, 0x9E, 0xC1, 0xCF,
+ 0x73, 0xC1, 0x7A, 0xF2, 0xAE, 0xEF, 0x93, 0x3C, 0x81, 0x0D, 0x55, 0x18, 0x9A, 0xC9, 0x78, 0xEB,
+ 0xCD, 0xAA, 0x3E, 0x72, 0x69, 0xBC, 0xAD, 0x69, 0xDE, 0xBC, 0x49, 0x46, 0x14, 0x89, 0x54, 0x9E,
+ 0xD8, 0xBC, 0x66, 0xBD, 0x20, 0x53, 0xFF, 0xAB, 0x8F, 0x89, 0x60, 0x76, 0x7E, 0x36, 0x59, 0x41,
+ 0x86, 0xD8, 0x9E, 0x06, 0x7D, 0xC1, 0x4E, 0x77, 0x51, 0xF8, 0x6D, 0xA5, 0x7B, 0x78, 0xE2, 0x5B,
+ 0xC8, 0x82, 0x34, 0x42, 0xF8, 0x99, 0x3C, 0x21, 0xFE, 0xE9, 0x3A, 0x9F, 0x3B, 0x4D, 0xB8, 0xA8,
+ 0x28, 0xF2, 0x2C, 0x2F, 0x37, 0xEC, 0x58, 0x64, 0x8F, 0xA7, 0x7A, 0x9C, 0x5D, 0xF8, 0x58, 0xBB,
+ 0x3F, 0xA4, 0xEF, 0x0F, 0xE9, 0xB3, 0x4B, 0x5F, 0x46, 0x56, 0x7E, 0xD9, 0xD5, 0x14, 0x5E, 0x61,
+ 0xFF, 0xB9, 0xEE, 0x8C, 0xC8, 0x18, 0xF9, 0xC0, 0x2E, 0x1D, 0xFE, 0x24, 0xDD, 0x2F, 0x54, 0xF2,
+ 0x2A, 0x1D, 0x93, 0x10, 0x36, 0x63, 0xB8, 0xF2, 0xA4, 0x13, 0x6A, 0xBE, 0x99, 0x92, 0xCB, 0xF6,
+ 0x79, 0xC9, 0xD0, 0x06, 0x41, 0xE4, 0x23, 0xC3, 0x85, 0xCC, 0x25, 0xF8, 0xF6, 0xD9, 0x0D, 0xB5,
+ 0x65, 0xAC, 0x6D, 0xC9, 0x99, 0x88, 0xE5, 0x60, 0x05, 0xAD, 0x8D, 0x70, 0x44, 0x63, 0x1C, 0x2D,
+ 0x7E, 0xBD, 0x34, 0x59, 0x5E, 0x8D, 0x5C, 0x6B, 0x69, 0x1E, 0x13, 0x7D, 0x65, 0x4D, 0x57, 0x44,
+ 0x31, 0x41, 0x18, 0xAA, 0x86, 0xAC, 0xD3, 0xB2, 0x11, 0x91, 0x66, 0x21, 0x31, 0xB4, 0x5A, 0x21,
+ 0x30, 0xA8, 0x54, 0x04, 0x2D, 0x7B, 0xD9, 0x85, 0xA7, 0xDA, 0x8C, 0x44, 0xB0, 0x2D, 0xE7, 0xAF,
+ 0xE8, 0x7E, 0xD6, 0xEA, 0xCB, 0xA4, 0x88, 0x49, 0xCD, 0xBE, 0x39, 0xCD, 0xBD, 0x24, 0xAE, 0x8E,
+ 0x1B, 0x74, 0xA8, 0x43, 0x14, 0x73, 0x2E, 0xFD, 0x13, 0x8F, 0x4B, 0x6A, 0x3F, 0xC6, 0x78, 0xE3,
+ 0x5A, 0x2F, 0x35, 0x88, 0x76, 0x73, 0x73, 0xDE, 0xCC, 0x6D, 0xEF, 0xCF, 0xA5, 0x78, 0xF2, 0x46,
+ 0x47, 0x9C, 0x4E, 0x2C, 0x77, 0x78, 0x64, 0xD8, 0xA3, 0x3E, 0x4A, 0x69, 0x30, 0x24, 0xB3, 0xBD,
+ 0xFF, 0xF1, 0xCF, 0xA2, 0xB3, 0xF3, 0x58, 0x97, 0x1E, 0x40, 0x6A, 0x99, 0xCA, 0x66, 0xE3, 0x99,
+ 0xF2, 0x10, 0x3A, 0x67, 0x92, 0x48, 0xDA, 0xDF, 0x96, 0x64, 0xB3, 0x5D, 0x8C, 0xFA, 0xF6, 0xF9,
+ 0x2F, 0xF8, 0x7A, 0xC1, 0x13, 0xE5, 0xDD, 0x34, 0x5E, 0xFC, 0xB9, 0xF3, 0x45, 0xCA, 0x3D, 0x7E,
+ 0xF3, 0xA2, 0x33, 0xE7, 0xAC, 0xC1, 0x1D, 0x8D, 0xB2, 0xBC, 0xD9, 0xE4, 0x65, 0xB2, 0xFA, 0x0B,
+ 0xD8, 0xE2, 0x68, 0x1F, 0xF6, 0x30, 0x4D, 0x53, 0xD3, 0x06, 0xBC, 0x5B, 0x02, 0xBC, 0xAB, 0x03,
+ 0xEF, 0x9A, 0xC0, 0xBB, 0x45, 0xC0, 0x7B, 0x25, 0xC0, 0x7B, 0x3A, 0xF0, 0x9E, 0x09, 0xBC, 0x57,
+ 0x04, 0x7C, 0xBD, 0x04, 0xF8, 0xBA, 0x0E, 0x7C, 0xDD, 0x04, 0xBE, 0x9E, 0x00, 0xFF, 0x17, 0x10,
+ 0x59, 0x25, 0x95, 0xC7, 0x4D, 0x10, 0x81, 0xA8, 0x4E, 0xE1, 0x57, 0xF8, 0x37, 0x1C, 0xB5, 0x88,
+ 0x4F, 0xA9, 0x1B, 0x95, 0xC8, 0xB1, 0x70, 0xC9, 0x60, 0x5B, 0x21, 0xC5, 0xED, 0x38, 0x78, 0x83,
+ 0xAF, 0x6B, 0x02, 0x64, 0xDA, 0x68, 0xA6, 0x69, 0xD4, 0x01, 0x64, 0x53, 0x37, 0x63, 0x92, 0x19,
+ 0x80, 0x38, 0x33, 0x61, 0xD3, 0xE9, 0x66, 0x3D, 0x55, 0x1E, 0x5D, 0x65, 0xC7, 0x9E, 0x2A, 0x8F,
+ 0x8D, 0x6D, 0xF5, 0x73, 0x82, 0x90, 0x91, 0xDF, 0x4C, 0x23, 0x2E, 0x2C, 0xD9, 0xED, 0x11, 0xF1,
+ 0x9C, 0x63, 0x36, 0xFB, 0xB8, 0xE5, 0xDB, 0x48, 0xFA, 0x79, 0xA2, 0xE5, 0x1C, 0x90, 0x8D, 0x23,
+ 0xE3, 0x52, 0x95, 0x01, 0x17, 0x15, 0xB3, 0x5E, 0x35, 0x2D, 0xC6, 0xCB, 0x73, 0xBB, 0x2D, 0xD8,
+ 0xC8, 0xF4, 0x2A, 0x0C, 0x30, 0xDF, 0xDD, 0x74, 0x95, 0x53, 0xE0, 0x5F, 0x7F, 0x25, 0xFC, 0x63,
+ 0x2F, 0xFD, 0x98, 0x99, 0x0D, 0xDD, 0xF9, 0xA7, 0x03, 0x00, 0xFE, 0x69, 0x3D, 0x2B, 0x5D, 0xBB,
+ 0x13, 0x14, 0x79, 0x61, 0xF3, 0x45, 0x6B, 0x27, 0xB9, 0xB8, 0x2E, 0xF0, 0x3D, 0xA2, 0x9E, 0x8E,
+ 0x4F, 0xC9, 0x09, 0x2F, 0xE7, 0x40, 0xD1, 0x0C, 0x66, 0x79, 0xD6, 0xDF, 0x05, 0x2F, 0x1D, 0x3C,
+ 0xFE, 0xBA, 0x3F, 0x12, 0x97, 0x9C, 0x74, 0x03, 0x52, 0x2F, 0xCB, 0x66, 0x63, 0xAF, 0xC9, 0x22,
+ 0xB2, 0x3F, 0x81, 0x15, 0x86, 0xBA, 0xDA, 0x79, 0x1C, 0xCB, 0x4B, 0xC2, 0x5F, 0xF1, 0xE4, 0xD8,
+ 0xD0, 0x63, 0x6B, 0x4D, 0xF5, 0x69, 0x60, 0x62, 0x6F, 0x50, 0xB6, 0xBA, 0x2D, 0xD3, 0xED, 0xF7,
+ 0x77, 0x1A, 0x06, 0x29, 0x17, 0x72, 0xBA, 0xDF, 0xC9, 0xB6, 0x91, 0x39, 0x4D, 0x58, 0xE7, 0xFE,
+ 0xD5, 0x12, 0xB3, 0xA8, 0x4C, 0xB1, 0xA5, 0x64, 0xF9, 0x9E, 0x19, 0xF2, 0xB3, 0x31, 0x75, 0x32,
+ 0xE3, 0xAD, 0xF3, 0x29, 0x35, 0xFC, 0xB1, 0x2F, 0x67, 0xA7, 0xC7, 0xFB, 0x97, 0x3C, 0x23, 0x8E,
+ 0xE4, 0x72, 0xD7, 0x56, 0xA3, 0x5F, 0x5E, 0xE3, 0x5D, 0x69, 0x8D, 0xDF, 0x94, 0x1A, 0x1B, 0x96,
+ 0x1A, 0x17, 0xA7, 0x87, 0x3A, 0x8C, 0x6C, 0x07, 0x45, 0x94, 0xDE, 0xBF, 0x50, 0x1F, 0xD5, 0x1A,
+ 0x17, 0xBF, 0x9F, 0x5E, 0x5E, 0xEC, 0x7F, 0xF8, 0xBD, 0xB8, 0x46, 0xFF, 0xE5, 0xC5, 0xC1, 0xEF,
+ 0x26, 0xA3, 0x32, 0x9C, 0xC2, 0xE7, 0x9A, 0xAC, 0x53, 0x5F, 0x79, 0xCB, 0xA9, 0xB3, 0x89, 0x9B,
+ 0xD5, 0x02, 0x6C, 0xEA, 0x93, 0x4F, 0xE5, 0xD5, 0x9E, 0x1A, 0xD5, 0x3A, 0xD6, 0x6A, 0xCF, 0xAA,
+ 0x41, 0x7B, 0x66, 0x81, 0x66, 0xAB, 0xB7, 0x5D, 0x0D, 0xDC, 0x76, 0x25, 0xE2, 0xBA, 0xBD, 0x4A,
+ 0xD0, 0xBA, 0xBD, 0x4A, 0xD0, 0x7A, 0xEB, 0x1D, 0x13, 0x9A, 0xB5, 0x13, 0xEC, 0xAD, 0xAF, 0x4E,
+ 0x05, 0x88, 0xFC, 0x51, 0xB0, 0x6C, 0x45, 0xCB, 0xF0, 0x83, 0x91, 0x85, 0x97, 0xDE, 0x6E, 0x3D,
+ 0x37, 0xBE, 0xC1, 0xB6, 0xD5, 0x45, 0xA0, 0x53, 0x4D, 0x06, 0x7A, 0x4B, 0x95, 0x81, 0xDE, 0x72,
+ 0x65, 0xA0, 0xB7, 0x54, 0x19, 0xE8, 0xFD, 0xB3, 0xC9, 0x00, 0x98, 0x78, 0x98, 0xF3, 0x27, 0x75,
+ 0x92, 0x45, 0x59, 0xEF, 0x11, 0x4F, 0x9A, 0x92, 0x82, 0x60, 0x8F, 0xC8, 0xEE, 0x64, 0xEB, 0x7C,
+ 0x34, 0xEA, 0x7C, 0xB4, 0xD4, 0xF9, 0x64, 0xD4, 0xF9, 0xA4, 0xA9, 0x6E, 0xA0, 0x46, 0x26, 0x12,
+ 0x2F, 0xA4, 0x28, 0xEB, 0xCF, 0x52, 0x9E, 0x11, 0x2E, 0xF7, 0x57, 0x29, 0xEF, 0xFE, 0x1A, 0x95,
+ 0x5F, 0xEF, 0x1F, 0x5F, 0xEE, 0x9F, 0x1F, 0x5A, 0xEA, 0xEE, 0x8F, 0x62, 0x31, 0x18, 0x7A, 0x3C,
+ 0xD4, 0xD8, 0x09, 0xBF, 0x6A, 0xC1, 0x50, 0xDD, 0x92, 0x88, 0xA7, 0x92, 0xE6, 0x3D, 0x5B, 0x73,
+ 0xBE, 0xEF, 0x10, 0xF8, 0x1D, 0xF7, 0x6F, 0xD3, 0x28, 0x06, 0x4A, 0x1B, 0x66, 0x7E, 0x73, 0xB4,
+ 0x3F, 0xCE, 0xE8, 0x2D, 0xAC, 0x7C, 0x09, 0xCB, 0x32, 0xC1, 0x43, 0x69, 0x1D, 0x8B, 0x6D, 0x45,
+ 0xEF, 0xBC, 0xD8, 0x08, 0x77, 0x60, 0x2F, 0x34, 0xD4, 0xC6, 0x8E, 0xE7, 0x9F, 0xB3, 0x08, 0xAC,
+ 0x9D, 0x24, 0x87, 0xB4, 0x78, 0xA5, 0xE7, 0xC4, 0x3F, 0x0F, 0x03, 0x3C, 0x9D, 0x4F, 0xE2, 0x8D,
+ 0x4C, 0x9C, 0x08, 0x75, 0xDF, 0x77, 0x55, 0x83, 0x2E, 0x79, 0x4D, 0xDC, 0x9A, 0xC5, 0x3D, 0x25,
+ 0xC3, 0x9A, 0xC7, 0xDD, 0x78, 0x86, 0x5C, 0xE6, 0x8E, 0x46, 0x33, 0x57, 0x42, 0x50, 0x61, 0x0B,
+ 0x74, 0xF6, 0x8E, 0xF0, 0xAF, 0xD9, 0x8E, 0x18, 0x9D, 0x4C, 0xDF, 0xE1, 0x96, 0x4F, 0x49, 0x80,
+ 0xD1, 0x87, 0xCF, 0x0B, 0xDD, 0x50, 0xEE, 0xBD, 0x3D, 0xEE, 0x9F, 0xAF, 0xF7, 0xC8, 0x0D, 0x7B,
+ 0x6D, 0x88, 0xBD, 0x63, 0x0E, 0x85, 0x21, 0x7F, 0xC6, 0x1C, 0xDF, 0x9A, 0x60, 0x7E, 0x18, 0x4C,
+ 0x2B, 0x90, 0x32, 0x87, 0x5C, 0x87, 0xC1, 0x98, 0xEC, 0x9F, 0xF3, 0x06, 0x43, 0x32, 0x01, 0xAA,
+ 0xD4, 0x44, 0xD5, 0xD9, 0xD7, 0xD0, 0x93, 0x2C, 0x91, 0x47, 0x40, 0x49, 0x38, 0xF6, 0x7C, 0xB0,
+ 0xB0, 0x6F, 0x3C, 0x30, 0x40, 0xD5, 0x87, 0x2B, 0xB8, 0xB3, 0x38, 0x08, 0x3D, 0x30, 0x7E, 0x9C,
+ 0x11, 0x7F, 0xD8, 0x42, 0x6E, 0xBF, 0xCC, 0xD4, 0xF9, 0xBF, 0xEC, 0x12, 0x7F, 0x3A, 0x1A, 0x35,
+ 0xCD, 0x0D, 0x8A, 0x99, 0x4D, 0x5F, 0x67, 0x64, 0x51, 0x50, 0x06, 0xBE, 0x76, 0xD5, 0x87, 0x3F,
+ 0xDD, 0x5F, 0x8A, 0xAF, 0x54, 0xA8, 0xEF, 0x01, 0xBC, 0xE7, 0xC7, 0x4C, 0x04, 0xFE, 0x0F, 0xFC,
+ 0x01, 0x66, 0xD9, 0x5E, 0x8B, 0xB7, 0x6D, 0x88, 0xF4, 0xA7, 0xC9, 0x3F, 0xC0, 0x66, 0x43, 0x3B,
+ 0x59, 0x66, 0x19, 0x7B, 0xF9, 0x4D, 0xEB, 0x68, 0x7A, 0x35, 0xF6, 0x62, 0xF9, 0xCE, 0x1D, 0x3E,
+ 0x3F, 0x07, 0x93, 0x8C, 0xBD, 0x4C, 0xF1, 0xB9, 0xF3, 0x25, 0xF5, 0x96, 0xA1, 0xBB, 0x59, 0x78,
+ 0xCC, 0xF0, 0xE5, 0x15, 0x74, 0xEF, 0xAB, 0xC1, 0x2A, 0xB2, 0xB8, 0xED, 0x4C, 0x26, 0x7C, 0x32,
+ 0x65, 0xC1, 0xB6, 0x18, 0xDA, 0x66, 0x0A, 0xD3, 0xF9, 0x9B, 0x73, 0x27, 0xE0, 0xFD, 0x7E, 0xFA,
+ 0xE6, 0x75, 0x8C, 0xA1, 0xDA, 0xB0, 0xBB, 0x48, 0x9F, 0x62, 0xC1, 0x0A, 0xED, 0x00, 0x00, 0x36,
+ 0x6A, 0xE7, 0x6F, 0xC1, 0x6C, 0x04, 0x2E, 0xAE, 0x4D, 0x59, 0x8F, 0x6A, 0x6A, 0x0D, 0x36, 0x95,
+ 0x24, 0x05, 0xEA, 0xCB, 0x5A, 0x3A, 0x17, 0x2C, 0xDB, 0x33, 0xCC, 0x11, 0xCF, 0x0B, 0x3D, 0x74,
+ 0xCA, 0x4F, 0xB4, 0x58, 0x49, 0xE3, 0x55, 0x95, 0xDC, 0xD7, 0xDE, 0x35, 0x0D, 0x5C, 0x8C, 0x90,
+ 0x3D, 0x47, 0x96, 0x0F, 0x34, 0x33, 0x71, 0xF9, 0x34, 0xCB, 0x7B, 0xF7, 0x3E, 0x6F, 0xF2, 0x6A,
+ 0x08, 0x82, 0x70, 0x48, 0xE3, 0x73, 0xC7, 0x0B, 0xA9, 0xCB, 0x94, 0xA9, 0xBE, 0x64, 0x5C, 0xC5,
+ 0xFE, 0x4B, 0x56, 0x83, 0x97, 0x59, 0x58, 0xC4, 0x1E, 0x69, 0x63, 0x85, 0x0C, 0x56, 0x10, 0xC7,
+ 0xD4, 0x6F, 0x2B, 0xC1, 0x05, 0x79, 0x6F, 0x77, 0x9F, 0xF1, 0x77, 0x69, 0xAC, 0x9B, 0x59, 0x86,
+ 0xF1, 0x38, 0x9A, 0x9C, 0xF1, 0x67, 0xC3, 0x23, 0x8B, 0xBA, 0x05, 0xC2, 0x2E, 0x94, 0x87, 0xCE,
+ 0x34, 0xA2, 0x8B, 0x5F, 0x40, 0xAB, 0xB1, 0x76, 0x6C, 0xB3, 0x29, 0xE2, 0x54, 0xF3, 0x36, 0xD5,
+ 0x2A, 0x9C, 0x94, 0x84, 0x64, 0xA3, 0xEB, 0xB8, 0x2E, 0x7B, 0x5A, 0x08, 0x3D, 0x1B, 0xD4, 0xC7,
+ 0x3C, 0xBC, 0x47, 0x6F, 0x4F, 0x41, 0xF7, 0xC6, 0xF8, 0x8D, 0x3F, 0xE3, 0xD6, 0x22, 0x0D, 0x8A,
+ 0x55, 0x9A, 0x64, 0x77, 0x4F, 0xDD, 0x3A, 0x0B, 0x86, 0x55, 0xD8, 0x35, 0x67, 0xE3, 0x69, 0xEB,
+ 0xEA, 0xD3, 0x36, 0xEC, 0x60, 0x1C, 0x4D, 0x13, 0x76, 0x46, 0x00, 0xBF, 0x70, 0xC8, 0xC9, 0xC6,
+ 0xD8, 0x43, 0x6F, 0xBF, 0x73, 0x07, 0xBF, 0xE8, 0x3B, 0x64, 0x5E, 0xED, 0xB3, 0xF7, 0xA5, 0x1D,
+ 0xF8, 0x83, 0x91, 0x37, 0xC0, 0x78, 0x9D, 0x84, 0xB7, 0x8D, 0xEC, 0x0D, 0x03, 0x2D, 0xD2, 0x3C,
+ 0xE7, 0x72, 0x79, 0xDE, 0xC3, 0x6D, 0x59, 0x3E, 0x71, 0x78, 0x38, 0xE1, 0xAD, 0x28, 0x2D, 0x80,
+ 0xFA, 0x74, 0xA0, 0x1A, 0x77, 0xED, 0x0E, 0x59, 0xCB, 0xC5, 0xA7, 0xF8, 0x6F, 0xBE, 0xAB, 0xB3,
+ 0xDD, 0x0A, 0x72, 0x19, 0xC4, 0xA5, 0x58, 0x8B, 0x68, 0x53, 0xBA, 0x60, 0x21, 0xCE, 0x92, 0xA9,
+ 0x75, 0x66, 0xD2, 0x66, 0xCF, 0xFA, 0xCA, 0xF5, 0x47, 0x24, 0xEA, 0xD9, 0x2F, 0x77, 0x70, 0xFD,
+ 0xC1, 0x4C, 0xBC, 0xDC, 0xEC, 0x02, 0xB6, 0x9E, 0xC8, 0x87, 0xF2, 0x16, 0xEE, 0x86, 0x04, 0x54,
+ 0xD8, 0x87, 0x1C, 0xFA, 0xD2, 0x0E, 0xE4, 0x77, 0xD2, 0xD2, 0x87, 0xE2, 0x14, 0x9F, 0x73, 0xF7,
+ 0x68, 0xAE, 0xCC, 0xA1, 0xBC, 0x7F, 0x68, 0xF0, 0x17, 0x75, 0x6F, 0x98, 0xD8, 0xFE, 0xF3, 0x77,
+ 0x8F, 0xBF, 0x18, 0xB8, 0xEC, 0xDE, 0xBD, 0x52, 0x83, 0x3B, 0xCD, 0xCE, 0x71, 0xE2, 0xF3, 0x3B,
+ 0xC7, 0x3B, 0x9F, 0xED, 0x5C, 0x26, 0xB1, 0x4B, 0x5E, 0x5A, 0x9A, 0x5A, 0xD3, 0xFE, 0x08, 0x68,
+ 0xEE, 0xE3, 0x76, 0x72, 0x1E, 0x6F, 0x75, 0xDB, 0x1B, 0xA5, 0x89, 0x33, 0x1F, 0x30, 0x69, 0xCE,
+ 0x4C, 0xB4, 0x6E, 0xB7, 0x3B, 0x73, 0x24, 0xF9, 0xAC, 0x02, 0xBA, 0x93, 0x0F, 0xD9, 0x22, 0x58,
+ 0xB6, 0x7C, 0x8E, 0x73, 0xCB, 0x53, 0x41, 0x72, 0x48, 0xFB, 0x1C, 0x51, 0xD2, 0x22, 0xCD, 0x78,
+ 0x5F, 0x8C, 0x8B, 0x61, 0x85, 0xF6, 0x79, 0xBD, 0x4D, 0xF2, 0x3A, 0x2C, 0xA3, 0xB7, 0x45, 0x49,
+ 0x22, 0x8C, 0xDE, 0xF2, 0xAA, 0xF3, 0xF7, 0xB6, 0xB8, 0xBD, 0xA5, 0xB7, 0xC6, 0x71, 0xC7, 0xE2,
+ 0x3D, 0xCE, 0x02, 0xCC, 0xE9, 0xB5, 0xE5, 0x40, 0x85, 0x3F, 0xFA, 0xC8, 0xD3, 0x1B, 0x64, 0x9E,
+ 0x2A, 0x2E, 0x64, 0x41, 0x29, 0x30, 0x6B, 0x5E, 0x24, 0x95, 0x1D, 0xD9, 0x87, 0x44, 0xE7, 0x66,
+ 0x44, 0x16, 0x54, 0x12, 0x30, 0xD3, 0xB1, 0x8F, 0xBD, 0x6C, 0x71, 0xE0, 0x4C, 0xDD, 0x23, 0xE3,
+ 0xC9, 0x43, 0x43, 0xB5, 0xAA, 0x57, 0x10, 0x4B, 0xE5, 0xA4, 0x02, 0x45, 0x5D, 0xBB, 0x0A, 0xAF,
+ 0x42, 0x91, 0x58, 0xC9, 0x66, 0xA2, 0x68, 0x5E, 0x5C, 0x33, 0xF4, 0x5E, 0x1F, 0xD6, 0x7B, 0xB0,
+ 0xB8, 0xBD, 0xC1, 0x29, 0x2C, 0x38, 0x8B, 0x8D, 0xA9, 0x0E, 0x87, 0xB3, 0x0F, 0x83, 0x44, 0xE4,
+ 0x9D, 0x04, 0x2E, 0xFD, 0xFC, 0x36, 0xD5, 0xCB, 0x29, 0xBB, 0xE7, 0x5C, 0x28, 0xFA, 0x2A, 0x40,
+ 0xB5, 0xD5, 0x31, 0xBF, 0xE7, 0x92, 0xDD, 0xB7, 0x7E, 0x70, 0x42, 0x9F, 0xBD, 0x15, 0x78, 0xC4,
+ 0x5B, 0x12, 0xD6, 0x94, 0x65, 0x05, 0x81, 0x6D, 0x0D, 0xC6, 0x68, 0xEE, 0x4F, 0xE3, 0x60, 0x1C,
+ 0xC4, 0xDE, 0x37, 0xDA, 0x26, 0x22, 0xD5, 0x17, 0x87, 0x49, 0xBC, 0x88, 0x5C, 0xC1, 0x3E, 0x1B,
+ 0x9D, 0x8A, 0x6E, 0x9A, 0xC8, 0x31, 0xAD, 0xAF, 0xC3, 0x6C, 0xD7, 0x66, 0x98, 0x75, 0x33, 0xF5,
+ 0xA3, 0x56, 0x41, 0x23, 0x19, 0x2C, 0xFC, 0x63, 0xC4, 0x7E, 0xFA, 0x11, 0xCB, 0xCF, 0x00, 0xB8,
+ 0xE0, 0x5A, 0x52, 0x9A, 0x5A, 0xD0, 0xAE, 0x4D, 0x27, 0x13, 0xF9, 0xFC, 0xFA, 0x5C, 0xEA, 0xA8,
+ 0xBC, 0xB9, 0x85, 0x07, 0xE6, 0xDD, 0xFE, 0x05, 0x7B, 0x5E, 0x31, 0x55, 0x40, 0xAE, 0x06, 0x3E,
+ 0xA2, 0xB1, 0xE3, 0x8D, 0xA2, 0x19, 0x7B, 0x3E, 0x0B, 0x20, 0x8D, 0x07, 0xEA, 0xDB, 0xD4, 0x73,
+ 0xF7, 0x5C, 0x05, 0x92, 0xBF, 0x60, 0x0A, 0x2F, 0x35, 0xD6, 0x2D, 0xEF, 0x64, 0x21, 0xE8, 0xAE,
+ 0x9D, 0x9B, 0xC5, 0xA0, 0x6D, 0x26, 0x94, 0xE2, 0x50, 0x5B, 0x82, 0xFD, 0xA4, 0x43, 0x2B, 0xD0,
+ 0x47, 0x86, 0x2F, 0x6F, 0x31, 0xE3, 0xA9, 0x10, 0x58, 0xA9, 0xF1, 0x64, 0xA6, 0x8F, 0x5A, 0x90,
+ 0x07, 0x85, 0x99, 0x8F, 0x14, 0xB9, 0x57, 0x2B, 0xCF, 0x29, 0xF1, 0x95, 0x40, 0x58, 0xFA, 0xAA,
+ 0xE5, 0x67, 0x5A, 0xB0, 0xBB, 0x65, 0xB9, 0x9E, 0xCC, 0x1E, 0xA7, 0xF5, 0x17, 0xE9, 0x74, 0x05,
+ 0x28, 0x59, 0x27, 0x43, 0xF2, 0x88, 0xC2, 0x1C, 0x9D, 0xD6, 0x0E, 0xEB, 0x32, 0xE0, 0xF5, 0x07,
+ 0x13, 0x1E, 0x0A, 0x7C, 0x66, 0x5F, 0xBC, 0x64, 0x34, 0xC6, 0xC9, 0xE9, 0xB2, 0x3B, 0x91, 0x93,
+ 0x75, 0x75, 0x6E, 0xF9, 0x2B, 0x49, 0xE3, 0x6A, 0xD3, 0x3A, 0x22, 0x5D, 0x6B, 0x81, 0x7E, 0x50,
+ 0x6A, 0xF2, 0x7C, 0xAB, 0xD5, 0xEA, 0xB2, 0x74, 0xA9, 0xA5, 0x6A, 0xA7, 0x40, 0x85, 0xD9, 0x28,
+ 0x33, 0xD4, 0x60, 0x31, 0x69, 0xF9, 0x95, 0xB3, 0xB4, 0x59, 0xF5, 0x2B, 0x1B, 0xAC, 0xEF, 0x4D,
+ 0xE5, 0x58, 0x03, 0x47, 0xE6, 0xF0, 0xF8, 0x65, 0xC2, 0x7C, 0xEB, 0xA3, 0x9A, 0xBE, 0x37, 0xF8,
+ 0x8A, 0xE7, 0x02, 0xEC, 0x2D, 0x74, 0xE9, 0xA8, 0xE1, 0xEF, 0xE0, 0x1E, 0x38, 0xEE, 0x21, 0xA6,
+ 0x2A, 0x6A, 0x64, 0xEB, 0xD8, 0x9E, 0x9D, 0x4C, 0xB2, 0x20, 0x29, 0xB5, 0x97, 0xF0, 0xB8, 0xC2,
+ 0xA3, 0xBE, 0xE4, 0xF3, 0xA8, 0xAF, 0xF7, 0x3C, 0xDA, 0x8B, 0x3D, 0x39, 0x89, 0x11, 0x34, 0xF3,
+ 0x46, 0x5E, 0x29, 0xC6, 0x91, 0x23, 0xCE, 0xD0, 0xF1, 0xFC, 0x48, 0xDC, 0x77, 0x82, 0x79, 0xAD,
+ 0x05, 0x43, 0x7A, 0xBE, 0x4B, 0xEF, 0xF4, 0x84, 0x43, 0xEC, 0xF8, 0x68, 0x47, 0x94, 0x24, 0xCF,
+ 0xDB, 0x33, 0x71, 0x91, 0x87, 0x47, 0x2B, 0x2B, 0xAC, 0x34, 0x9B, 0x87, 0x3B, 0x24, 0x13, 0x27,
+ 0x8C, 0x23, 0x26, 0x75, 0x49, 0xB3, 0xCF, 0xAC, 0xF2, 0x17, 0x79, 0x55, 0x82, 0x98, 0x61, 0xD7,
+ 0x52, 0x87, 0x68, 0xB2, 0xA6, 0x98, 0x57, 0x0C, 0x24, 0xDE, 0x87, 0xB0, 0x78, 0x2A, 0xB3, 0x68,
+ 0xF0, 0x5C, 0x38, 0x3B, 0x0D, 0x56, 0x48, 0x9D, 0xC5, 0x24, 0xA7, 0xD2, 0x96, 0x57, 0xF0, 0x31,
+ 0xAF, 0xE0, 0x93, 0x2D, 0x75, 0x38, 0xFE, 0x5C, 0x85, 0xD4, 0xF9, 0x5A, 0xEC, 0x0B, 0x4D, 0xAF,
+ 0x11, 0x32, 0x76, 0xEF, 0x5A, 0xB8, 0xAA, 0x27, 0x1C, 0x57, 0xCB, 0x27, 0xD3, 0xE8, 0xA6, 0xF1,
+ 0xA0, 0x7D, 0xD2, 0x33, 0x95, 0xF1, 0x7B, 0x6F, 0x88, 0x08, 0x97, 0x01, 0x33, 0x20, 0x06, 0xF6,
+ 0x7A, 0x94, 0x97, 0xEA, 0x41, 0xBF, 0x00, 0x48, 0x04, 0x06, 0x88, 0x7C, 0x77, 0x4A, 0xB0, 0x91,
+ 0x3A, 0xA2, 0xDA, 0xDD, 0x23, 0xB2, 0x67, 0xDC, 0xFC, 0xCA, 0x13, 0x21, 0xA8, 0x6B, 0x17, 0x20,
+ 0x6C, 0x80, 0xBC, 0x11, 0xE1, 0xBC, 0x52, 0x56, 0x76, 0x9E, 0x18, 0x57, 0xBC, 0x58, 0xF4, 0x43,
+ 0xA3, 0x76, 0xC4, 0xA8, 0x27, 0xA3, 0x60, 0xC0, 0x68, 0x64, 0x29, 0x39, 0x92, 0xE6, 0x2B, 0xA4,
+ 0xF6, 0x6F, 0xB5, 0x66, 0x5E, 0x96, 0x79, 0x75, 0x48, 0x90, 0x92, 0x01, 0xC5, 0x1E, 0x80, 0x4E,
+ 0xB4, 0x1C, 0x7B, 0x96, 0xB3, 0x11, 0x0F, 0xE4, 0x35, 0x85, 0xFE, 0x03, 0x79, 0x98, 0x1B, 0x94,
+ 0xC6, 0xB9, 0xD9, 0xFD, 0x92, 0x53, 0xF5, 0x63, 0xA6, 0x6A, 0x2F, 0xAF, 0xEA, 0xA7, 0x4C, 0xD5,
+ 0x75, 0xA3, 0xAA, 0x7D, 0xF6, 0x2B, 0x03, 0x6A, 0xCB, 0x67, 0xA1, 0x52, 0x5D, 0x52, 0xE3, 0x63,
+ 0x69, 0x8D, 0x4F, 0x79, 0x35, 0x74, 0xD2, 0xB4, 0xD4, 0x0D, 0xC9, 0x6D, 0x53, 0x85, 0xD1, 0xC4,
+ 0x09, 0x43, 0xE7, 0xBE, 0x25, 0x04, 0x80, 0x25, 0xDF, 0x1D, 0x07, 0x7E, 0x10, 0x4D, 0x9C, 0x01,
+ 0x25, 0xCC, 0x1D, 0xC2, 0xD4, 0xB0, 0x71, 0xCF, 0x34, 0x15, 0x14, 0x25, 0x60, 0x21, 0x57, 0x0C,
+ 0xC4, 0xF5, 0x9A, 0x74, 0xE9, 0x47, 0x61, 0xC8, 0x2A, 0x95, 0xCC, 0xCA, 0xC0, 0xED, 0xF6, 0x3C,
+ 0xB0, 0x69, 0xF7, 0xD5, 0x2E, 0x63, 0xA0, 0x9F, 0x19, 0xA2, 0xA1, 0x5C, 0x43, 0xC5, 0x18, 0xFD,
+ 0x20, 0xA4, 0x6E, 0xAD, 0x28, 0x45, 0x0D, 0xDB, 0x74, 0x2C, 0x8C, 0x56, 0x94, 0x3E, 0x27, 0xBF,
+ 0xAF, 0x7D, 0x5C, 0xFB, 0x54, 0xD3, 0xAF, 0x23, 0x59, 0x7A, 0xFF, 0x82, 0x6C, 0x36, 0x33, 0x56,
+ 0x58, 0x2E, 0x4F, 0x23, 0xEF, 0xEF, 0x94, 0xD8, 0x54, 0xB3, 0xA6, 0x1D, 0x93, 0x9B, 0x07, 0xE9,
+ 0xD2, 0x39, 0xEB, 0x5A, 0x89, 0x13, 0x34, 0x98, 0xB0, 0xB1, 0x57, 0x02, 0x3D, 0x06, 0xB0, 0x8C,
+ 0x80, 0x18, 0x88, 0x0B, 0x07, 0x75, 0x5E, 0x41, 0x9D, 0xA6, 0xFC, 0x4B, 0x3B, 0xE6, 0x17, 0xCB,
+ 0xB3, 0x2B, 0x5F, 0xA6, 0xA6, 0x9C, 0x43, 0xAC, 0x58, 0x9F, 0x6D, 0xB9, 0x4C, 0xC0, 0x8B, 0x38,
+ 0xBC, 0xB9, 0xBE, 0x28, 0xFC, 0xA9, 0x51, 0xFB, 0x5F, 0xF6, 0x46, 0x02, 0x1B, 0xB4, 0xA5, 0xCE,
+ 0xE0, 0xA6, 0x61, 0xDF, 0x27, 0xA8, 0x3A, 0xE9, 0x4F, 0x8D, 0xF8, 0xC6, 0x8B, 0x9A, 0xAC, 0x23,
+ 0x8D, 0x66, 0xA1, 0x56, 0xF7, 0x75, 0xAD, 0xDE, 0x8E, 0xA6, 0x57, 0x11, 0xB7, 0x4C, 0xD9, 0x0B,
+ 0x78, 0x5A, 0x42, 0x3D, 0x05, 0x68, 0xD2, 0x14, 0x16, 0x3C, 0x7E, 0x17, 0x49, 0x6A, 0xB2, 0x64,
+ 0x09, 0x94, 0xFA, 0xCA, 0xF8, 0xB0, 0xFE, 0x85, 0x83, 0x48, 0xF7, 0x4C, 0xDF, 0x75, 0x23, 0x5C,
+ 0x86, 0xD2, 0x56, 0x33, 0xC4, 0x65, 0xED, 0x32, 0x63, 0x5C, 0xAF, 0x57, 0xC5, 0x20, 0x97, 0x2D,
+ 0x96, 0x65, 0x94, 0xA7, 0xF7, 0xC6, 0x9F, 0x3C, 0xCE, 0xDB, 0x87, 0x8F, 0xF9, 0xDC, 0xE1, 0x63,
+ 0x3E, 0x71, 0xF8, 0x83, 0xEC, 0xF3, 0x44, 0x82, 0x66, 0xB7, 0xD1, 0x65, 0xD3, 0x59, 0xED, 0xF4,
+ 0x44, 0x04, 0x67, 0xB3, 0xD5, 0x0D, 0x74, 0x8A, 0xBD, 0x6E, 0xCC, 0x16, 0xCD, 0x5A, 0x15, 0xD1,
+ 0xE9, 0xD6, 0x22, 0x11, 0x8C, 0x6E, 0x2B, 0x93, 0xF2, 0x6A, 0x94, 0x69, 0x52, 0x66, 0x2F, 0xD3,
+ 0x25, 0xE3, 0x01, 0x6C, 0x7F, 0x63, 0xC4, 0xAC, 0xF6, 0x7F, 0x52, 0x47, 0xDB, 0x03, 0xFC, 0xF4,
+ 0x7C, 0xB2, 0xED, 0x27, 0x24, 0xD1, 0xF9, 0x7B, 0x8A, 0x8C, 0x6A, 0x2D, 0x33, 0x89, 0x4D, 0x01,
+ 0x9C, 0xD9, 0x2C, 0x4E, 0x44, 0xF1, 0xE7, 0xD9, 0x5E, 0x24, 0x63, 0x5B, 0x7D, 0x8B, 0x51, 0xC4,
+ 0x59, 0xC5, 0x0D, 0xA8, 0x6E, 0xD6, 0x6E, 0x1C, 0x7A, 0x4A, 0xE3, 0x9B, 0xC0, 0xAD, 0x96, 0xB6,
+ 0x35, 0xBD, 0x50, 0x91, 0xCD, 0xD9, 0xAA, 0x80, 0x4C, 0xF9, 0xAF, 0xC0, 0x37, 0x8E, 0x47, 0x2C,
+ 0xBE, 0xCD, 0x7C, 0x67, 0x98, 0xCD, 0x5B, 0x9C, 0xEB, 0xD6, 0x03, 0x9C, 0xF0, 0x4D, 0xE4, 0x0A,
+ 0xC8, 0xBB, 0x7C, 0xD2, 0x24, 0xAB, 0xA4, 0xA1, 0xD4, 0x31, 0x9D, 0xC5, 0xA2, 0xD2, 0x1A, 0xCB,
+ 0xDD, 0x0C, 0x23, 0x96, 0xAD, 0x6A, 0x0F, 0x88, 0x92, 0x4D, 0x9A, 0xC5, 0xC4, 0x4B, 0x33, 0x00,
+ 0x68, 0x6D, 0xC7, 0x01, 0x8B, 0x5F, 0x6C, 0xAC, 0x97, 0x65, 0xED, 0xCD, 0x67, 0x97, 0xCE, 0x80,
+ 0x12, 0x6E, 0x19, 0xBC, 0xB5, 0xB3, 0xCB, 0xA4, 0xB5, 0x09, 0x3C, 0x78, 0x6C, 0x7E, 0x65, 0xEF,
+ 0x0B, 0xD9, 0xD9, 0x65, 0x6E, 0xA7, 0x7F, 0x5A, 0xFD, 0x31, 0x1D, 0x9F, 0x8B, 0x76, 0xAC, 0xBD,
+ 0xB6, 0xAB, 0x90, 0x18, 0x93, 0x4A, 0x7B, 0xBB, 0x64, 0xCB, 0xB6, 0x68, 0x8F, 0x9C, 0x58, 0x42,
+ 0x49, 0x2B, 0xAF, 0x92, 0x0D, 0xE4, 0x64, 0x2F, 0xEB, 0x6B, 0xCE, 0x5F, 0xA4, 0x2D, 0x5B, 0xEA,
+ 0x92, 0x7B, 0x60, 0x59, 0x37, 0x80, 0xA4, 0x3A, 0x21, 0x6A, 0x2F, 0x7B, 0x08, 0xAA, 0x6D, 0x96,
+ 0xC6, 0xB0, 0x43, 0x94, 0xE4, 0x77, 0x77, 0x94, 0x3F, 0x5F, 0x24, 0x1D, 0x53, 0xBE, 0xEA, 0x21,
+ 0xE9, 0x95, 0xA8, 0x5C, 0xD9, 0xD5, 0xEC, 0xF8, 0x14, 0xC1, 0x0A, 0x31, 0x09, 0xCF, 0x86, 0x41,
+ 0x7E, 0xCF, 0xE1, 0x44, 0xF6, 0x96, 0x9B, 0x60, 0x05, 0x40, 0x95, 0x64, 0xFF, 0x5C, 0x5C, 0x31,
+ 0x29, 0x2E, 0x62, 0x4B, 0x7E, 0x27, 0x2A, 0x73, 0xC8, 0xA2, 0xDA, 0x38, 0x22, 0x45, 0x42, 0xD7,
+ 0x2D, 0x82, 0x66, 0x55, 0x23, 0xB6, 0xC6, 0xBD, 0xFC, 0xC6, 0xB9, 0x91, 0xA9, 0x19, 0x20, 0xDD,
+ 0x2F, 0x46, 0x5C, 0xF0, 0x3C, 0xB7, 0x0D, 0xE5, 0x75, 0xC1, 0x19, 0xE0, 0xF4, 0x0A, 0x6E, 0x2D,
+ 0x6A, 0x70, 0xB4, 0xC3, 0xBA, 0xBC, 0x0C, 0x23, 0x96, 0x29, 0xBD, 0x93, 0x5B, 0x57, 0x9F, 0x23,
+ 0x65, 0xF5, 0x12, 0xA9, 0x29, 0xA9, 0x98, 0x0E, 0x77, 0x7E, 0x45, 0x63, 0x68, 0x4B, 0x2B, 0x9A,
+ 0xC3, 0x58, 0xF5, 0x21, 0x07, 0xF5, 0xB6, 0x5B, 0xC4, 0x55, 0xBB, 0x64, 0x0C, 0x61, 0x9A, 0x38,
+ 0x9B, 0xE8, 0xF9, 0x7B, 0x91, 0xE7, 0x2E, 0x69, 0x3C, 0x97, 0xF7, 0x4E, 0xB7, 0xC1, 0x0A, 0x3D,
+ 0x78, 0x8A, 0x46, 0x2E, 0xF2, 0xE2, 0x19, 0xDB, 0x83, 0xCA, 0x9E, 0x3C, 0x9B, 0x74, 0x98, 0x92,
+ 0xF3, 0x80, 0x1E, 0xBD, 0x39, 0xD1, 0x27, 0x9E, 0x3D, 0x99, 0x02, 0x77, 0x0D, 0x64, 0xCD, 0xEE,
+ 0xE0, 0x33, 0x19, 0x53, 0xD1, 0xC9, 0xA7, 0xB0, 0x5D, 0x77, 0xF4, 0x99, 0x3B, 0xE7, 0x19, 0x9D,
+ 0x7D, 0x55, 0x36, 0xDE, 0xCB, 0x74, 0xF8, 0x19, 0xDB, 0xE7, 0xC5, 0x9C, 0x7E, 0x0A, 0x53, 0x66,
+ 0x73, 0xFC, 0x25, 0xB3, 0xE5, 0x87, 0x3A, 0xFF, 0x34, 0xF1, 0x50, 0x6D, 0x2B, 0xB4, 0xA2, 0x9E,
+ 0x9A, 0xAB, 0xE5, 0xD2, 0x5C, 0x84, 0x99, 0xB5, 0x52, 0xAF, 0xB0, 0x61, 0xB6, 0xD8, 0x34, 0x3F,
+ 0x6C, 0x95, 0x81, 0x78, 0xAA, 0xB9, 0x21, 0x4B, 0x95, 0xE1, 0x83, 0x38, 0x3F, 0x15, 0x19, 0xF8,
+ 0x9E, 0xB9, 0x2C, 0xAE, 0x79, 0x31, 0x95, 0xBB, 0xAC, 0xDC, 0xDC, 0x86, 0x7F, 0x95, 0x91, 0x6D,
+ 0xFF, 0x0D, 0x34, 0x7B, 0xA3, 0x5E, 0x4F, 0x2E, 0xF0, 0x1A, 0x55, 0x5A, 0xE5, 0x55, 0xFE, 0xFA,
+ 0xD7, 0x4C, 0x9D, 0x90, 0xC6, 0xD3, 0xD0, 0x67, 0xA6, 0xBA, 0x41, 0xDD, 0x90, 0xC6, 0x78, 0x6D,
+ 0x58, 0x53, 0xC5, 0xEC, 0xA6, 0x36, 0xA8, 0x2C, 0x90, 0x1E, 0x59, 0x66, 0x7D, 0xED, 0x2D, 0x53,
+ 0x47, 0xEC, 0xB1, 0x14, 0xD7, 0xDD, 0xC9, 0x35, 0x73, 0xD5, 0xC5, 0xCE, 0x15, 0xB9, 0x75, 0x22,
+ 0x82, 0x2B, 0x37, 0xC1, 0xAB, 0xC6, 0xD4, 0x6D, 0x11, 0x3E, 0xA5, 0x09, 0x4B, 0x53, 0xCA, 0xEF,
+ 0xA2, 0x47, 0x83, 0x10, 0xF3, 0x19, 0x19, 0xBB, 0x9C, 0x11, 0x3D, 0x75, 0x7C, 0x67, 0x48, 0xC3,
+ 0x77, 0x58, 0x33, 0x13, 0xF2, 0xCB, 0xDA, 0xEF, 0xBD, 0x88, 0x43, 0xE2, 0x60, 0x2C, 0xCD, 0x6E,
+ 0x7D, 0x44, 0xAF, 0xE3, 0x3A, 0x7C, 0xB8, 0xD9, 0x43, 0x4F, 0xC2, 0x8B, 0x35, 0xF8, 0x05, 0xFF,
+ 0xE8, 0x83, 0x22, 0x13, 0x7F, 0xB8, 0x7B, 0x2F, 0xD2, 0xA4, 0xE1, 0xBB, 0xDC, 0xDE, 0xB8, 0x0A,
+ 0xEE, 0xEA, 0xC4, 0x73, 0x77, 0xEB, 0x88, 0x90, 0x6F, 0xEC, 0xF7, 0x47, 0xA3, 0x3A, 0x61, 0x69,
+ 0xB7, 0xE0, 0x6B, 0x10, 0x8E, 0x57, 0x59, 0xC5, 0x55, 0xDE, 0x54, 0xA1, 0x8B, 0xF9, 0x27, 0xEB,
+ 0xB0, 0x3C, 0x1E, 0xE2, 0x1D, 0x50, 0x0E, 0x41, 0x92, 0x1C, 0x0C, 0x87, 0x78, 0xD9, 0x13, 0xE8,
+ 0x59, 0x43, 0xB4, 0x6B, 0x71, 0x98, 0xFC, 0xC3, 0xE8, 0x56, 0xD4, 0x36, 0x7B, 0x80, 0x1A, 0x3F,
+ 0xCA, 0x6C, 0xB8, 0x35, 0x85, 0x95, 0x77, 0xE3, 0xD1, 0x4D, 0x1C, 0x4F, 0x0A, 0x2F, 0x72, 0x2B,
+ 0xF5, 0xC4, 0x7D, 0xEE, 0x57, 0xC7, 0xFC, 0x3A, 0x37, 0xAE, 0xC2, 0xEC, 0xB6, 0x79, 0x2D, 0x6B,
+ 0x9B, 0xC9, 0x16, 0xEC, 0xB6, 0xAE, 0xAA, 0x27, 0x40, 0xCE, 0x23, 0x7A, 0xE2, 0x0F, 0x82, 0x31,
+ 0xAA, 0x11, 0x59, 0x2D, 0xA4, 0xD1, 0x04, 0xCC, 0x09, 0x46, 0x24, 0x4B, 0x41, 0x25, 0x72, 0x16,
+ 0x90, 0xC3, 0xFE, 0x6F, 0x3C, 0xCB, 0x00, 0x58, 0x26, 0x01, 0x5B, 0xFC, 0x9F, 0x54, 0x1F, 0x48,
+ 0x30, 0xBD, 0xB5, 0xEE, 0x97, 0x2C, 0xA6, 0xBA, 0xD4, 0x29, 0x9B, 0xF5, 0xEF, 0xA6, 0x70, 0x8B,
+ 0x44, 0x4B, 0x19, 0xF9, 0x0E, 0xAE, 0x62, 0xC7, 0x03, 0x31, 0x54, 0xCA, 0xAD, 0x32, 0x6E, 0xAD,
+ 0x67, 0xCA, 0x39, 0xBF, 0xA7, 0x9A, 0x54, 0x28, 0x88, 0xE6, 0x16, 0xB5, 0x1E, 0x66, 0x84, 0x65,
+ 0x52, 0xA9, 0x1F, 0x38, 0xC8, 0x79, 0x5C, 0x80, 0xF1, 0x55, 0xBA, 0x5E, 0x3E, 0x56, 0x07, 0x2C,
+ 0xE5, 0x62, 0xF1, 0x78, 0x61, 0x9D, 0xAA, 0x63, 0xC6, 0xEB, 0x96, 0x8C, 0x9B, 0x48, 0x54, 0xF3,
+ 0x43, 0xC7, 0x8E, 0xD1, 0xF0, 0x53, 0x8C, 0x5F, 0x86, 0x1B, 0xE5, 0x63, 0xA8, 0x4C, 0xF1, 0xA3,
+ 0xE0, 0xD6, 0x47, 0x3F, 0x53, 0x9A, 0x64, 0x81, 0xA9, 0x54, 0xEA, 0xE2, 0xBC, 0xAD, 0x7E, 0x67,
+ 0x1F, 0x21, 0x9E, 0x1C, 0x29, 0x9E, 0x54, 0x8E, 0xD6, 0x67, 0x9E, 0xB3, 0xB7, 0xD7, 0x0C, 0x58,
+ 0x5F, 0x40, 0x9E, 0x1F, 0xA8, 0x66, 0xCD, 0x9A, 0x4F, 0xC5, 0xC9, 0x57, 0x63, 0x5E, 0xF2, 0x5B,
+ 0xFB, 0xF8, 0xA0, 0x2F, 0xA6, 0xB0, 0xC1, 0xA4, 0x12, 0x51, 0xCC, 0x4A, 0xF4, 0x1C, 0x38, 0x49,
+ 0x55, 0x45, 0x7A, 0x55, 0x98, 0xBB, 0x76, 0xEA, 0x9B, 0x62, 0x91, 0x4E, 0x9D, 0xC4, 0xD3, 0x70,
+ 0x14, 0x07, 0x03, 0xCC, 0xED, 0x07, 0xD2, 0xB6, 0x86, 0x20, 0xFE, 0x8D, 0x91, 0x8F, 0x7E, 0x72,
+ 0x8D, 0x9B, 0x9F, 0x53, 0xF0, 0x98, 0x94, 0x0F, 0x7D, 0xE7, 0xBF, 0x3A, 0x8C, 0x9A, 0x5D, 0x57,
+ 0x0C, 0x83, 0x10, 0x63, 0x75, 0x23, 0x98, 0x80, 0x17, 0x3C, 0x05, 0xA5, 0x0A, 0xB5, 0xDB, 0xD2,
+ 0x1D, 0xDF, 0xBE, 0x09, 0xE9, 0x35, 0xE6, 0xFC, 0x93, 0xD5, 0x84, 0xD0, 0xA5, 0xB8, 0x30, 0xCF,
+ 0x64, 0xCE, 0xE8, 0xCB, 0x65, 0x4F, 0xF1, 0x2D, 0xCA, 0x45, 0x76, 0xF6, 0xC1, 0xCF, 0x4B, 0xD5,
+ 0x80, 0xFB, 0x83, 0x14, 0xAA, 0x96, 0xAC, 0xC1, 0xCF, 0xA4, 0x6A, 0x48, 0x2B, 0x62, 0xBA, 0x86,
+ 0xE4, 0xBD, 0x9A, 0x64, 0x65, 0x4A, 0x56, 0xFC, 0x34, 0x0E, 0xB7, 0x4C, 0xC2, 0xA9, 0x9E, 0x44,
+ 0x64, 0x59, 0xF2, 0xFD, 0x24, 0xEF, 0xA0, 0x24, 0x33, 0xEE, 0xD2, 0x7E, 0xC7, 0xB7, 0x91, 0xD8,
+ 0xEA, 0x2E, 0x4E, 0x4C, 0x32, 0x0A, 0x51, 0x95, 0x2C, 0x73, 0xB3, 0xA6, 0xE4, 0x84, 0xB4, 0x01,
+ 0xB7, 0x66, 0x85, 0xAC, 0x2E, 0x99, 0x77, 0x19, 0x81, 0x64, 0x7D, 0xA9, 0xED, 0x54, 0x56, 0x9D,
+ 0x85, 0xBA, 0x33, 0x21, 0xA4, 0x5C, 0x63, 0x6A, 0xAF, 0x8B, 0x5E, 0xD0, 0x6B, 0x50, 0x91, 0x37,
+ 0x3C, 0x43, 0x4F, 0x72, 0x8E, 0x5C, 0x68, 0x59, 0x68, 0x86, 0xB2, 0x91, 0xC2, 0x8C, 0x65, 0x8C,
+ 0x79, 0xE9, 0x19, 0x12, 0xAF, 0xA4, 0xFF, 0xC1, 0x5F, 0xBB, 0x4B, 0xC9, 0xF8, 0xC3, 0x21, 0xCD,
+ 0x99, 0xE4, 0x87, 0x13, 0x6A, 0x09, 0x5C, 0x9F, 0x84, 0xC1, 0x10, 0xB3, 0x7D, 0x03, 0x60, 0xF9,
+ 0xEB, 0x6B, 0xC7, 0x77, 0x47, 0x98, 0x40, 0x59, 0x65, 0x2C, 0x83, 0x92, 0x6D, 0xCE, 0x34, 0x0C,
+ 0xA6, 0xDC, 0xE5, 0x39, 0x73, 0x66, 0x69, 0xCA, 0xFD, 0x4E, 0x22, 0x73, 0xEF, 0x2C, 0x0D, 0x9D,
+ 0x2B, 0xFE, 0x42, 0x31, 0xFB, 0x6F, 0x7E, 0x43, 0x23, 0xAF, 0x51, 0x61, 0x46, 0x23, 0x25, 0x0F,
+ 0x9C, 0xC1, 0x05, 0x99, 0x7F, 0x26, 0x1D, 0xDC, 0x09, 0x0D, 0x07, 0x94, 0x85, 0xB1, 0xF0, 0xB2,
+ 0xF6, 0x88, 0xE5, 0xAA, 0x21, 0x6B, 0x84, 0xFF, 0x19, 0x07, 0x31, 0x6E, 0xF4, 0xFE, 0x8C, 0x07,
+ 0x37, 0x4A, 0x1A, 0x1F, 0x01, 0xF6, 0xC0, 0x09, 0x15, 0x1F, 0xEC, 0xA9, 0x13, 0xDF, 0xB4, 0xC3,
+ 0x60, 0x0A, 0xD4, 0x08, 0xB0, 0x4A, 0x56, 0xD0, 0xA9, 0x92, 0x08, 0xC9, 0x30, 0x4E, 0x2C, 0xED,
+ 0x70, 0xAA, 0xFD, 0x6F, 0x21, 0x90, 0xD4, 0x4D, 0x33, 0xDD, 0xB2, 0x3D, 0xBF, 0x20, 0x79, 0x0F,
+ 0xF3, 0xFA, 0x75, 0xCC, 0x73, 0xC6, 0x02, 0x3C, 0x35, 0x25, 0x9B, 0x6E, 0x8B, 0xDC, 0x86, 0x1E,
+ 0x4B, 0xFB, 0xC3, 0xC4, 0x1B, 0x8C, 0x08, 0x26, 0xD3, 0xEC, 0x11, 0xBD, 0x9A, 0x25, 0xD9, 0xAB,
+ 0x21, 0x13, 0x3A, 0x23, 0xCB, 0xF0, 0xF2, 0x54, 0x4C, 0x24, 0xC9, 0xC5, 0x54, 0xC6, 0x48, 0xE9,
+ 0x19, 0x5C, 0xC6, 0x04, 0x67, 0xDF, 0x12, 0x25, 0x8E, 0x99, 0x94, 0xB8, 0xEF, 0xE9, 0xE0, 0xFE,
+ 0xC4, 0x9D, 0x95, 0x6A, 0x85, 0x1F, 0xAA, 0x9C, 0xCF, 0xC5, 0x8C, 0x97, 0x0E, 0x10, 0xE9, 0xEA,
+ 0x40, 0xD5, 0x39, 0x30, 0x17, 0xD0, 0x7D, 0x04, 0x20, 0xA0, 0xA6, 0x60, 0xE3, 0x81, 0xC8, 0x6C,
+ 0x70, 0x80, 0xEB, 0x66, 0xE6, 0xED, 0x3A, 0x71, 0x5B, 0xF4, 0x5B, 0x5C, 0x94, 0x69, 0x81, 0xBB,
+ 0x3E, 0x13, 0x48, 0xF6, 0x3B, 0x9E, 0xD6, 0x7D, 0x1E, 0xF7, 0xD9, 0x96, 0xB4, 0xE4, 0xA3, 0xCD,
+ 0x24, 0x42, 0xD2, 0xC1, 0x5E, 0x2E, 0x57, 0x12, 0x62, 0x6E, 0x6F, 0x3C, 0xB5, 0xAD, 0xE4, 0x00,
+ 0x98, 0x27, 0xA9, 0x28, 0xE9, 0x5C, 0x51, 0xD2, 0x8C, 0xA4, 0x73, 0xBC, 0xD2, 0x3C, 0x9D, 0x2B,
+ 0x6A, 0xA9, 0x75, 0x8E, 0x57, 0xAC, 0xD8, 0xB9, 0xA9, 0x3B, 0x29, 0xE9, 0xD6, 0x7B, 0x77, 0x52,
+ 0xDE, 0xB3, 0xA9, 0x3B, 0x99, 0xB9, 0x4F, 0xB9, 0x6D, 0xB4, 0xDE, 0x24, 0xE8, 0x8D, 0x0E, 0x75,
+ 0x3B, 0x5D, 0x99, 0xD4, 0x55, 0xEF, 0x91, 0x7B, 0x33, 0x98, 0xC8, 0xA7, 0x37, 0xB3, 0xBD, 0x12,
+ 0x05, 0x47, 0xAF, 0x0F, 0xCF, 0xED, 0xFD, 0x51, 0xD2, 0x2F, 0x9D, 0x9C, 0xEB, 0xF4, 0x49, 0xA8,
+ 0x85, 0x7D, 0x53, 0xD2, 0x23, 0x95, 0xB7, 0x57, 0xE9, 0x86, 0x82, 0xDB, 0x20, 0xFC, 0xCA, 0x42,
+ 0xDB, 0x74, 0x7B, 0xD8, 0x88, 0x82, 0xC4, 0x6F, 0xF8, 0x8E, 0xEA, 0x19, 0x6F, 0x50, 0x39, 0x19,
+ 0xB8, 0xD2, 0xE6, 0x4B, 0x1D, 0x2F, 0xD9, 0x27, 0x65, 0x7F, 0xDA, 0xED, 0xF7, 0x4D, 0xF3, 0xD9,
+ 0xB0, 0xF8, 0x54, 0x84, 0x79, 0x06, 0x1F, 0x32, 0x59, 0xAD, 0x87, 0x36, 0x9D, 0xF1, 0x6C, 0x41,
+ 0xA7, 0x69, 0x1C, 0x37, 0x69, 0x59, 0xE8, 0x35, 0x0F, 0x24, 0x2B, 0xB3, 0xA5, 0xF4, 0x3F, 0xA3,
+ 0xB7, 0x32, 0x8F, 0xA1, 0x36, 0xBE, 0x06, 0x03, 0xCD, 0x93, 0x1D, 0x1C, 0x98, 0xD3, 0x68, 0x28,
+ 0xDE, 0x88, 0x94, 0x39, 0xFC, 0x0E, 0x05, 0x44, 0x28, 0xC1, 0xA4, 0x90, 0xF8, 0x5A, 0x2D, 0x5B,
+ 0x05, 0xF0, 0x82, 0x3F, 0x1D, 0x4F, 0xF0, 0xF9, 0xF0, 0x9D, 0x22, 0xEB, 0x58, 0xDC, 0xCD, 0x3D,
+ 0x34, 0x08, 0xCB, 0x0B, 0x28, 0x11, 0x44, 0xE4, 0xE2, 0x07, 0x41, 0xF1, 0xF1, 0x16, 0x3D, 0xBE,
+ 0x9D, 0x14, 0x10, 0xA4, 0xA6, 0x66, 0xA4, 0x54, 0xC5, 0xD1, 0x17, 0xF9, 0xF4, 0x70, 0x8B, 0xAE,
+ 0xB8, 0x17, 0x60, 0xBB, 0x09, 0x66, 0x8A, 0x7C, 0x64, 0x99, 0x35, 0x26, 0x38, 0xAE, 0xEC, 0xB0,
+ 0xED, 0xFC, 0x43, 0x94, 0x0A, 0xD4, 0x28, 0x12, 0x4B, 0xD4, 0x7C, 0xB2, 0x53, 0x20, 0x26, 0x0A,
+ 0xE8, 0x3C, 0x29, 0x51, 0x88, 0x5F, 0xD9, 0x55, 0x1B, 0xA4, 0x5B, 0x00, 0xFE, 0x58, 0xB4, 0x5E,
+ 0x22, 0xA3, 0xE4, 0xA0, 0x50, 0x1B, 0x00, 0x5D, 0x5F, 0x5D, 0x1C, 0x2A, 0x23, 0xA0, 0xBC, 0x35,
+ 0xCA, 0x8E, 0x87, 0x73, 0x48, 0xC8, 0x34, 0x6D, 0x61, 0x6D, 0x89, 0x46, 0x7B, 0x70, 0xA2, 0xAC,
+ 0x21, 0x1B, 0xA9, 0x96, 0x1C, 0x10, 0xA3, 0xB6, 0x29, 0xBC, 0x98, 0xE2, 0x50, 0x86, 0x24, 0xAB,
+ 0xC7, 0xAE, 0x52, 0x34, 0x98, 0x83, 0x80, 0xE5, 0xA7, 0xE0, 0xBB, 0x25, 0x09, 0xAC, 0x69, 0xCD,
+ 0x9E, 0xA8, 0x95, 0xA7, 0xE1, 0xB9, 0x0A, 0x3E, 0x6B, 0xFE, 0xDA, 0x0C, 0x51, 0xB6, 0x2C, 0xB6,
+ 0xAC, 0x12, 0x00, 0xB7, 0xCD, 0x3C, 0x2D, 0x0B, 0x6B, 0x0E, 0xCA, 0x66, 0xD6, 0xA7, 0x50, 0x3B,
+ 0xC4, 0x76, 0x4C, 0xCE, 0x05, 0x59, 0x28, 0x4E, 0x39, 0xED, 0x93, 0x00, 0xE0, 0xD2, 0xE9, 0x23,
+ 0x08, 0x4D, 0xF2, 0x78, 0x12, 0x58, 0x38, 0x22, 0xF1, 0xBC, 0xAB, 0xAE, 0x6C, 0x13, 0x1C, 0xBF,
+ 0xF1, 0x1A, 0x8D, 0x6B, 0xFD, 0x6F, 0xAD, 0x7F, 0xB9, 0x38, 0x95, 0xE7, 0x89, 0x8C, 0xF6, 0x2C,
+ 0xE9, 0xD8, 0xF1, 0xC5, 0xC5, 0xDB, 0x8B, 0xDA, 0x7C, 0x5A, 0x08, 0xD8, 0xC1, 0x17, 0x3E, 0x96,
+ 0x90, 0xC4, 0xF9, 0x06, 0xD6, 0x1C, 0x77, 0x86, 0xEF, 0x18, 0x6B, 0x95, 0xEB, 0x7D, 0x7B, 0x45,
+ 0x63, 0x4D, 0xF9, 0xE8, 0x07, 0x94, 0xA5, 0xFA, 0xC9, 0x88, 0x61, 0xD3, 0xD5, 0x9C, 0x96, 0xAC,
+ 0xC1, 0xD6, 0xC9, 0xC3, 0xF7, 0x17, 0x17, 0xC7, 0x67, 0xEF, 0x6C, 0xDD, 0xCC, 0xED, 0xA1, 0xA4,
+ 0x03, 0xD5, 0xEC, 0x74, 0x82, 0xBA, 0x0E, 0x8F, 0xFB, 0x1F, 0xBD, 0x6F, 0xAA, 0x0A, 0x37, 0xF1,
+ 0x98, 0xE6, 0x2F, 0x8B, 0x5B, 0x00, 0x42, 0xBF, 0xD5, 0x58, 0x74, 0xAF, 0xC6, 0x87, 0x9D, 0x42,
+ 0x40, 0x76, 0x6A, 0xF4, 0xFC, 0xB4, 0x08, 0xFD, 0x3C, 0x6F, 0xAB, 0x62, 0x97, 0x43, 0xBD, 0x91,
+ 0x22, 0x8D, 0x22, 0x67, 0x95, 0x8D, 0x75, 0x86, 0x1F, 0xDD, 0x9C, 0xD0, 0x8F, 0xB8, 0x96, 0xFE,
+ 0xF8, 0xF1, 0xB5, 0x2E, 0xD0, 0x95, 0xA6, 0x7B, 0x46, 0xBA, 0xB3, 0x43, 0x01, 0xFD, 0x7F, 0x25,
+ 0xB2, 0xDE, 0xA2, 0xAB, 0xE5, 0x5A, 0xE9, 0xDE, 0x1F, 0xCB, 0xB8, 0xBE, 0x8C, 0x5B, 0x96, 0x4A,
+ 0x5D, 0x34, 0x1F, 0x7A, 0xA1, 0xD4, 0xB1, 0x59, 0x97, 0x49, 0x83, 0x20, 0xDB, 0x22, 0x39, 0xE4,
+ 0x70, 0xD5, 0x29, 0xA5, 0x9C, 0xE0, 0xCB, 0xF0, 0x57, 0x54, 0x98, 0xDD, 0x5A, 0x6E, 0x1A, 0x73,
+ 0x2B, 0x29, 0x4D, 0xAB, 0x2A, 0x96, 0xD0, 0xB2, 0x6B, 0xCC, 0x03, 0x4D, 0xAD, 0x32, 0x9D, 0xCE,
+ 0xB4, 0x81, 0x64, 0x83, 0x2E, 0xF5, 0xDA, 0xAB, 0x02, 0xFA, 0xB6, 0x27, 0x88, 0x1D, 0x89, 0x5D,
+ 0xE4, 0xF0, 0x16, 0xDE, 0x27, 0xE9, 0x15, 0xB1, 0x1A, 0x1A, 0x79, 0x7C, 0xAA, 0x3E, 0x3D, 0x0D,
+ 0x2C, 0xDC, 0x1D, 0x66, 0xE6, 0xF2, 0xAE, 0xAA, 0xA3, 0x0D, 0x60, 0x8A, 0x6B, 0xDE, 0x44, 0xB3,
+ 0x9B, 0x71, 0xA9, 0x19, 0x4F, 0x04, 0x68, 0xF7, 0x60, 0x5F, 0x79, 0xDF, 0xA8, 0x4F, 0x1C, 0xF6,
+ 0x40, 0x5D, 0x1D, 0xE3, 0xC7, 0x42, 0x96, 0x17, 0x3D, 0x0E, 0xEF, 0x71, 0x25, 0x92, 0x2F, 0x32,
+ 0xF3, 0xC0, 0x89, 0xFB, 0x09, 0x0F, 0xAB, 0x13, 0xFB, 0x27, 0xFD, 0x9D, 0x56, 0xE2, 0xF9, 0xE4,
+ 0xE8, 0xA8, 0xED, 0xCA, 0x1F, 0xF9, 0x5C, 0x69, 0x32, 0x0C, 0xD9, 0xD7, 0x9D, 0x11, 0xE7, 0xB1,
+ 0x0F, 0xA8, 0xF4, 0xBD, 0x68, 0xE6, 0x99, 0x54, 0x32, 0xE7, 0x0B, 0xCF, 0xEA, 0xCB, 0x8F, 0xD1,
+ 0x8D, 0x72, 0xD5, 0x2F, 0x51, 0x8F, 0x18, 0x1B, 0x68, 0xF9, 0x8E, 0xAF, 0xB6, 0x8F, 0x9D, 0x91,
+ 0xA5, 0x84, 0xAB, 0xAA, 0xB7, 0xD7, 0x6F, 0x28, 0x4B, 0x20, 0x6F, 0x9E, 0xCD, 0xD9, 0x5E, 0x5A,
+ 0xE6, 0x19, 0x57, 0xF1, 0xEA, 0x18, 0x8A, 0x17, 0xB9, 0xBD, 0x71, 0xD8, 0x72, 0x35, 0x64, 0x8C,
+ 0x07, 0x1E, 0x4F, 0xA3, 0x44, 0x2B, 0x0F, 0x1C, 0x9F, 0x50, 0x64, 0x08, 0x53, 0x99, 0x9E, 0x0F,
+ 0x83, 0x0F, 0x7A, 0x0E, 0xAF, 0x03, 0x46, 0xA2, 0xCA, 0x3E, 0xE1, 0xDF, 0x78, 0x2D, 0x7C, 0x61,
+ 0x21, 0xF0, 0x47, 0xF7, 0xE2, 0x14, 0x2F, 0x6A, 0x91, 0xD5, 0x16, 0x06, 0x06, 0xB5, 0xD8, 0x30,
+ 0xB5, 0xF5, 0x93, 0x2A, 0xF5, 0xC1, 0x2D, 0xC9, 0x78, 0x53, 0xF5, 0xEA, 0xF7, 0x9E, 0x22, 0xD7,
+ 0x1B, 0x7A, 0x71, 0x3A, 0x4C, 0xA0, 0x5D, 0x33, 0xCF, 0xFD, 0x61, 0x45, 0x9D, 0x5D, 0x6C, 0x21,
+ 0xB7, 0xF2, 0x69, 0x65, 0xA5, 0x28, 0x4B, 0x97, 0x8A, 0x06, 0xA1, 0xD4, 0x57, 0xEB, 0xCD, 0x74,
+ 0xE0, 0x60, 0x0B, 0x8F, 0xDD, 0x1F, 0x8D, 0xC8, 0x30, 0x08, 0xDC, 0x0A, 0xAD, 0x09, 0xB4, 0x4E,
+ 0xC7, 0x77, 0xE6, 0xE6, 0x6D, 0x44, 0xAE, 0xF4, 0xAB, 0x08, 0x80, 0xF4, 0x27, 0xCC, 0xF9, 0xB8,
+ 0x32, 0x3B, 0xC5, 0x65, 0x1B, 0x15, 0x10, 0x1F, 0x8C, 0x16, 0x88, 0xB4, 0xA1, 0x67, 0x37, 0x41,
+ 0xF5, 0x23, 0x23, 0x30, 0xDB, 0x51, 0x78, 0x26, 0x41, 0x14, 0x79, 0x18, 0xB0, 0xC4, 0xA5, 0x01,
+ 0xE7, 0xA7, 0x94, 0x93, 0x74, 0x2A, 0xA6, 0x1F, 0x4E, 0x4F, 0xDB, 0x63, 0xFE, 0x93, 0x7E, 0x23,
+ 0xB6, 0x8F, 0xAB, 0xB6, 0x8F, 0xA7, 0xA7, 0xFD, 0x7E, 0x3B, 0x62, 0x3F, 0x5A, 0x73, 0x62, 0xF9,
+ 0x0C, 0x00, 0x56, 0xD3, 0xCF, 0x4F, 0xAC, 0x72, 0xC2, 0x42, 0xEC, 0x17, 0xE7, 0xDC, 0xFF, 0xC1,
+ 0x28, 0x2E, 0x3F, 0x68, 0x93, 0x0D, 0x98, 0x65, 0xDB, 0x9D, 0xF5, 0xF5, 0x8D, 0xA7, 0x1D, 0x9C,
+ 0x5E, 0x8C, 0x7F, 0xED, 0x34, 0x84, 0x2C, 0x9D, 0xE9, 0x7B, 0xA4, 0xB7, 0x0C, 0xC4, 0x6F, 0x71,
+ 0xDE, 0x75, 0xD8, 0x25, 0x5E, 0x98, 0x61, 0x3D, 0x7C, 0xF7, 0x2E, 0xB8, 0xC5, 0xE7, 0xFE, 0x36,
+ 0x3A, 0xA4, 0xB3, 0x49, 0x7A, 0x9B, 0xED, 0x5E, 0x67, 0x63, 0xDB, 0x42, 0x4A, 0xAA, 0x8B, 0xF6,
+ 0xC8, 0xFA, 0x92, 0x29, 0xE9, 0x31, 0x62, 0xD6, 0x53, 0x62, 0x56, 0xBB, 0x9D, 0xCD, 0xD5, 0x6E,
+ 0x77, 0xB5, 0xB3, 0xD9, 0xEE, 0x6E, 0xF5, 0x6C, 0xE4, 0xD8, 0x35, 0xDA, 0x1E, 0x46, 0x61, 0x2E,
+ 0x89, 0xB4, 0xA7, 0x48, 0xD3, 0x35, 0xBD, 0xA5, 0x21, 0xA7, 0xA7, 0xDB, 0x45, 0x6A, 0x9E, 0x3D,
+ 0xDB, 0xDE, 0xEE, 0x91, 0xC6, 0x11, 0x97, 0x2C, 0xAC, 0xC2, 0x7F, 0x6B, 0x26, 0x34, 0x4A, 0x69,
+ 0xD7, 0x2C, 0xB2, 0x64, 0xA2, 0x72, 0x53, 0x2C, 0x5D, 0x42, 0x76, 0x6C, 0xB5, 0x55, 0x89, 0xE3,
+ 0x0D, 0xD4, 0x2F, 0xF6, 0x36, 0xA9, 0xA8, 0x08, 0x6B, 0x2F, 0xF9, 0x3B, 0x07, 0x87, 0x1C, 0x4F,
+ 0x81, 0x40, 0xFE, 0x69, 0xAF, 0x6D, 0x65, 0x37, 0x6F, 0x69, 0x2D, 0x52, 0xAD, 0x79, 0x9F, 0x0E,
+ 0x1D, 0xCC, 0xFC, 0xD9, 0xF7, 0x86, 0xBE, 0x6E, 0x3C, 0xE9, 0x2A, 0xAC, 0x93, 0xEA, 0xCF, 0x54,
+ 0x59, 0x27, 0xC5, 0xDC, 0xEC, 0xC4, 0x00, 0xD0, 0x7D, 0x45, 0xB7, 0xB7, 0x50, 0x82, 0xEA, 0xFC,
+ 0x21, 0xC7, 0x0B, 0x16, 0x25, 0x0A, 0xF4, 0x30, 0x3A, 0xF0, 0x6D, 0xE9, 0x69, 0x94, 0xC6, 0x84,
+ 0xEB, 0x44, 0xE8, 0x37, 0xD9, 0x92, 0xCE, 0xAF, 0xAE, 0xB2, 0x67, 0xCC, 0x41, 0x39, 0xA6, 0xE2,
+ 0xEE, 0xF0, 0xF7, 0xCC, 0x3D, 0x9F, 0xA7, 0x98, 0x64, 0x25, 0x94, 0x2F, 0x60, 0x2D, 0xE6, 0x81,
+ 0x60, 0x17, 0xA9, 0x05, 0x52, 0x89, 0x86, 0x44, 0x80, 0xC7, 0xF4, 0xC6, 0xA9, 0xEB, 0x36, 0x2C,
+ 0x37, 0xE8, 0x69, 0x56, 0x56, 0x78, 0xF1, 0x25, 0x47, 0xB2, 0xA1, 0xF8, 0x29, 0x26, 0x70, 0xCF,
+ 0x2D, 0xDD, 0x82, 0x35, 0x6E, 0x6D, 0x2D, 0xAB, 0xF0, 0xD4, 0x07, 0x7B, 0xE6, 0x37, 0x51, 0x38,
+ 0x5C, 0x25, 0x94, 0x80, 0x5F, 0x16, 0x67, 0x27, 0x45, 0x3C, 0xCC, 0x9C, 0x9D, 0xA4, 0x02, 0x5B,
+ 0xFD, 0x81, 0xBC, 0xBF, 0x97, 0x0A, 0x39, 0x1B, 0x1E, 0xB0, 0xB9, 0x89, 0x98, 0x36, 0x1A, 0x14,
+ 0x21, 0xDA, 0x3A, 0x08, 0x05, 0x34, 0xBF, 0xB8, 0xD7, 0x51, 0x61, 0xA4, 0xEB, 0x7D, 0x5A, 0x6D,
+ 0x75, 0x37, 0xD1, 0xD4, 0xFC, 0xC8, 0x58, 0xBB, 0xEC, 0xC7, 0x1E, 0xE2, 0x04, 0x81, 0x88, 0x59,
+ 0x60, 0x49, 0x01, 0xA2, 0x04, 0xCD, 0xE9, 0xA9, 0x1A, 0x84, 0xFB, 0xD2, 0x03, 0xCB, 0x04, 0x56,
+ 0x57, 0xCD, 0xD2, 0x48, 0xED, 0x11, 0x16, 0xFC, 0xFE, 0xF6, 0x9A, 0xBD, 0xCB, 0x8D, 0x83, 0xB1,
+ 0xDA, 0x35, 0xDD, 0xF9, 0x8B, 0xB2, 0xFE, 0xF2, 0xEC, 0xED, 0xE5, 0xD1, 0xF1, 0xE1, 0xC9, 0xE9,
+ 0xFE, 0x1B, 0x63, 0x14, 0x22, 0x0A, 0x33, 0xD5, 0xC5, 0x6E, 0x25, 0xF4, 0x18, 0xEC, 0x56, 0xC5,
+ 0x81, 0xEF, 0x29, 0x79, 0x0B, 0x2B, 0xCB, 0xD2, 0x89, 0xA4, 0x32, 0x5A, 0x6D, 0x22, 0xF9, 0xF8,
+ 0xE7, 0x84, 0x5F, 0xA2, 0x01, 0xB0, 0x4C, 0xB9, 0x2E, 0x63, 0xB1, 0x28, 0x25, 0xB2, 0x95, 0x14,
+ 0xC8, 0x1A, 0xD9, 0x82, 0x15, 0x90, 0x5D, 0xFB, 0x94, 0x38, 0xD6, 0xC8, 0xFA, 0x16, 0xE6, 0x3C,
+ 0x68, 0x1A, 0x37, 0x16, 0x95, 0x19, 0xDC, 0xB4, 0x22, 0xF8, 0x33, 0x32, 0xDE, 0xBA, 0x25, 0x5C,
+ 0x70, 0xE6, 0x6D, 0x16, 0xCE, 0xBC, 0x8D, 0x64, 0xE6, 0x69, 0xE6, 0xC7, 0xF2, 0x26, 0x5E, 0xC5,
+ 0x69, 0xA7, 0x2C, 0x2A, 0xEA, 0xF8, 0xE7, 0x4C, 0x36, 0x53, 0xF0, 0x67, 0x9D, 0x5E, 0xB9, 0x93,
+ 0xCB, 0x2E, 0x85, 0x1A, 0x6B, 0x58, 0x28, 0xA8, 0x68, 0x60, 0x01, 0x6C, 0x17, 0xC1, 0x59, 0x45,
+ 0x6A, 0x2E, 0xE9, 0x69, 0x77, 0x34, 0x0F, 0x59, 0x22, 0x40, 0x9A, 0xB4, 0x74, 0xF9, 0x78, 0xAF,
+ 0x3E, 0xD0, 0x80, 0x5F, 0x9E, 0x9E, 0x5E, 0x1E, 0xED, 0xF7, 0x5F, 0x1B, 0xC3, 0x2E, 0x42, 0x9C,
+ 0x52, 0xA5, 0x23, 0xEE, 0x2A, 0xAC, 0x9A, 0x77, 0x59, 0xD2, 0x31, 0x56, 0x1F, 0xB2, 0xEF, 0x7C,
+ 0x51, 0xC4, 0x22, 0x67, 0xE8, 0xD4, 0xFA, 0xDD, 0x2F, 0x8A, 0x2A, 0xCC, 0x8C, 0xDE, 0x23, 0x0F,
+ 0x46, 0xFE, 0x58, 0xF4, 0x92, 0xB1, 0x90, 0xA6, 0xFB, 0xD2, 0xC7, 0x02, 0x94, 0xEF, 0x4F, 0x36,
+ 0x1C, 0x3F, 0xCD, 0xCA, 0xA4, 0xB0, 0xA7, 0xCA, 0x02, 0xA5, 0x76, 0xA8, 0x97, 0x76, 0x28, 0xBB,
+ 0x42, 0xFD, 0xB8, 0xE5, 0x23, 0x47, 0xE6, 0x8C, 0x05, 0x84, 0x0B, 0x5D, 0xEA, 0x35, 0x5A, 0xAA,
+ 0xCC, 0xED, 0x94, 0xB0, 0x21, 0x55, 0xB0, 0x0F, 0xD6, 0x41, 0xA1, 0xE1, 0xC8, 0xC3, 0x69, 0xB8,
+ 0x8A, 0xB3, 0x89, 0xFC, 0xFF, 0xA6, 0xDC, 0xF4, 0x71, 0xE8, 0x25, 0xE3, 0x40, 0x1E, 0xC6, 0xAA,
+ 0xE7, 0x13, 0xF8, 0x27, 0x1A, 0x8C, 0x9F, 0x4D, 0xB5, 0xFD, 0xAB, 0x69, 0x35, 0xDB, 0xE6, 0xDE,
+ 0xD2, 0x8C, 0x6F, 0xED, 0x45, 0xCF, 0x2C, 0xE5, 0xCD, 0x24, 0x3F, 0xCA, 0x76, 0xD3, 0xEE, 0x31,
+ 0xC0, 0xF7, 0x6D, 0xD9, 0x95, 0x12, 0xF4, 0xEF, 0x71, 0x70, 0x93, 0x10, 0x0C, 0x4A, 0x3C, 0x43,
+ 0x49, 0x7D, 0xE9, 0x96, 0x31, 0x6A, 0x1A, 0x17, 0x2E, 0x6D, 0x55, 0x76, 0xF4, 0x53, 0x00, 0xC5,
+ 0x9D, 0x8F, 0x1E, 0xE4, 0xF4, 0x62, 0x62, 0x8B, 0x04, 0xD3, 0x18, 0xFF, 0x70, 0xC4, 0x11, 0x01,
+ 0x34, 0xEA, 0x07, 0xBA, 0xC3, 0x1F, 0xBD, 0xD7, 0x57, 0xC0, 0xE5, 0x31, 0x25, 0x75, 0x63, 0xAA,
+ 0xD5, 0x5B, 0x84, 0xC6, 0x03, 0xED, 0x41, 0x5F, 0x64, 0x04, 0xA3, 0x43, 0xA1, 0xAB, 0x65, 0x13,
+ 0x35, 0xFB, 0xE1, 0x80, 0xF0, 0x98, 0x2A, 0xA7, 0x9A, 0xFC, 0x9A, 0x82, 0x45, 0x52, 0xAB, 0x8B,
+ 0x6A, 0xD3, 0xAA, 0x0E, 0x52, 0xC9, 0x1C, 0x58, 0x87, 0xCD, 0x3C, 0x1A, 0x56, 0xC9, 0xB0, 0x1F,
+ 0xAC, 0x2D, 0x48, 0xE6, 0xA5, 0xB2, 0x17, 0x81, 0xFD, 0xD4, 0x82, 0xD0, 0x96, 0x0A, 0x4C, 0x9A,
+ 0xDC, 0x4B, 0x06, 0xD9, 0xFF, 0x78, 0x7A, 0xF0, 0xF6, 0x0D, 0x03, 0x6A, 0xDE, 0xFF, 0x18, 0x05,
+ 0xFE, 0x90, 0xE5, 0xB6, 0x38, 0xA2, 0xC3, 0x90, 0x9A, 0x5E, 0x89, 0xCC, 0x48, 0x18, 0x43, 0x0B,
+ 0x9B, 0x26, 0x13, 0x80, 0xB5, 0x1E, 0x4C, 0xFE, 0xAD, 0xCE, 0x8E, 0x91, 0x3B, 0x2C, 0x29, 0x7D,
+ 0x41, 0x72, 0x35, 0xA6, 0xDC, 0x4B, 0xDB, 0xE5, 0x2A, 0xFD, 0x23, 0x91, 0xA7, 0xA7, 0xCD, 0x4C,
+ 0x8E, 0xB2, 0x05, 0x87, 0x37, 0x8F, 0xB4, 0x64, 0x0A, 0x65, 0x58, 0xB8, 0x02, 0xB3, 0x0A, 0x0F,
+ 0xD6, 0x93, 0xAA, 0x3B, 0xD9, 0x13, 0x93, 0x25, 0x09, 0xCA, 0x5C, 0xC4, 0xAD, 0x3E, 0x06, 0x75,
+ 0x5C, 0xE6, 0xE6, 0xA2, 0xEF, 0x7F, 0xFE, 0x5B, 0x27, 0x10, 0xBF, 0xD5, 0x6B, 0x3A, 0x99, 0xF3,
+ 0xC0, 0x25, 0xB6, 0x7E, 0x2F, 0x5D, 0xB7, 0x5C, 0x2A, 0x7E, 0xC5, 0x65, 0xA8, 0x97, 0xA5, 0x82,
+ 0x53, 0x36, 0x4A, 0xCB, 0x87, 0xAA, 0xA8, 0x99, 0xE5, 0x75, 0x5E, 0xB1, 0x7C, 0x96, 0x4F, 0xF1,
+ 0x43, 0xC2, 0x36, 0x36, 0xA3, 0xFF, 0x12, 0xDA, 0x57, 0xA3, 0xF9, 0xD4, 0xE6, 0xC7, 0x9E, 0x85,
+ 0x66, 0x01, 0xA0, 0x88, 0xE6, 0xCA, 0x7A, 0x7F, 0x6B, 0xE9, 0x7A, 0xBF, 0xDF, 0x9F, 0x5B, 0xF3,
+ 0x67, 0x58, 0xF4, 0x48, 0x2B, 0x82, 0x90, 0xBA, 0xF9, 0x17, 0x05, 0x1B, 0xE1, 0xAB, 0x8F, 0x44,
+ 0xF9, 0xC2, 0x0B, 0x86, 0x8D, 0xF8, 0x7A, 0x76, 0x25, 0xF9, 0x6B, 0xAD, 0xF6, 0x50, 0x7D, 0x68,
+ 0xCE, 0xBD, 0x2A, 0xD9, 0x68, 0x27, 0x0F, 0xCD, 0x78, 0x43, 0xBD, 0x2E, 0x5D, 0xDC, 0x95, 0x4B,
+ 0x8D, 0x56, 0xB5, 0xB0, 0x64, 0x01, 0x5A, 0xB0, 0x27, 0xC5, 0xE3, 0xF0, 0xC8, 0x7D, 0x31, 0x16,
+ 0x8F, 0x07, 0x98, 0xD1, 0x45, 0x1D, 0xCA, 0x5C, 0xF4, 0xD1, 0xF1, 0x19, 0x71, 0x95, 0x32, 0xD0,
+ 0x69, 0x00, 0xAB, 0x9B, 0x6C, 0xB3, 0xF6, 0x57, 0x77, 0xAD, 0x1D, 0xE3, 0xA5, 0xE5, 0x41, 0x73,
+ 0x87, 0xE8, 0x49, 0x15, 0xE4, 0xE1, 0x39, 0xEC, 0x84, 0x5B, 0x3C, 0x5D, 0x17, 0x6C, 0x5C, 0x6F,
+ 0x42, 0x35, 0x10, 0x93, 0xE7, 0x1A, 0xDB, 0xC3, 0xCD, 0xB2, 0xBC, 0x9A, 0xB4, 0xAA, 0x04, 0xBC,
+ 0xC0, 0x67, 0x6D, 0x6F, 0x8E, 0xD5, 0xB4, 0x2C, 0x59, 0x22, 0x13, 0xD9, 0x0A, 0xC2, 0xC5, 0x18,
+ 0x04, 0xAD, 0x9C, 0x03, 0x5F, 0x61, 0xD9, 0x85, 0xD5, 0xED, 0xBB, 0xAF, 0x6D, 0xD8, 0x05, 0x68,
+ 0x27, 0xF5, 0x16, 0xC8, 0xAD, 0xBB, 0x72, 0xB3, 0xB8, 0x92, 0x1F, 0x41, 0x66, 0x15, 0xB8, 0xC5,
+ 0x07, 0xB3, 0x48, 0x51, 0x15, 0x16, 0x03, 0xC0, 0x5F, 0x5F, 0x7E, 0x6E, 0x24, 0x49, 0xE6, 0x23,
+ 0x51, 0x7B, 0xEF, 0x7F, 0xF5, 0x2D, 0x8F, 0xFB, 0x1A, 0xE9, 0xB8, 0x07, 0x78, 0xC5, 0xB7, 0x51,
+ 0x79, 0xDB, 0x9E, 0x83, 0x4B, 0xF5, 0x4E, 0x2C, 0x15, 0x21, 0xEC, 0xA9, 0x72, 0x51, 0xA6, 0x6E,
+ 0xD0, 0xA5, 0xA2, 0xBC, 0x2C, 0xC2, 0x49, 0x1E, 0x0E, 0x29, 0x5F, 0x8F, 0x73, 0x31, 0xAF, 0x3E,
+ 0x10, 0x66, 0x66, 0xBD, 0x14, 0xF0, 0x38, 0xF1, 0x2C, 0x2D, 0xBD, 0xBF, 0x45, 0x78, 0x35, 0x9F,
+ 0xD6, 0x03, 0x60, 0x2E, 0x67, 0xF6, 0xEA, 0xC3, 0xA0, 0x37, 0x17, 0xD2, 0x42, 0xCE, 0x3F, 0x44,
+ 0xC7, 0xAB, 0xE0, 0xE6, 0xDC, 0x7F, 0x28, 0xB6, 0x57, 0x22, 0x81, 0x8F, 0x40, 0x01, 0x09, 0xDF,
+ 0x9F, 0xE4, 0x69, 0xBB, 0xEF, 0x4F, 0xFE, 0x1F, 0xBA, 0xC0, 0x1A, 0x6B, 0xBF, 0x23, 0x00
+}; ///main_js
+
+//To convert AP-Config\index.html to index_html[], run the Python index_html_zipper.py script in the Tools folder:
+// cd Firmware\Tools
+// python index_html_zipper.py
+
+static const uint8_t index_html[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x3A, 0xEE, 0x9C, 0x67, 0x02, 0xFF, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E,
+ 0x68, 0x74, 0x6D, 0x6C, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0xED, 0x7D, 0xDB, 0x72, 0xDB, 0xC8,
+ 0x92, 0xE0, 0xBB, 0xBF, 0xA2, 0x86, 0xB3, 0x33, 0x96, 0xE7, 0x88, 0x14, 0x49, 0x5D, 0x6C, 0xEB,
+ 0xD8, 0x8C, 0xD0, 0xD5, 0x56, 0x1C, 0xC9, 0xE6, 0x8A, 0xF2, 0x71, 0x77, 0x6F, 0xEC, 0x76, 0x80,
+ 0x40, 0x91, 0xC4, 0x31, 0x08, 0xA0, 0x81, 0x82, 0x2E, 0x3D, 0x31, 0x13, 0xE7, 0x33, 0x66, 0x3E,
+ 0x64, 0x7F, 0x60, 0x3F, 0xE5, 0x7C, 0xC9, 0x66, 0x66, 0x15, 0x40, 0x00, 0x04, 0x49, 0x00, 0xBC,
+ 0xAB, 0xE5, 0x8E, 0x96, 0x44, 0x10, 0x75, 0xCB, 0xCA, 0xCC, 0xCA, 0xCC, 0xCA, 0xCB, 0x87, 0x7F,
+ 0x3A, 0xFF, 0x7A, 0x76, 0xF7, 0x73, 0xFB, 0x82, 0x0D, 0xC4, 0xD0, 0x6A, 0xBD, 0xFA, 0x80, 0xBF,
+ 0x98, 0xA5, 0xD9, 0xFD, 0x8F, 0x15, 0x6E, 0x57, 0x5A, 0xAF, 0xE0, 0x09, 0xD7, 0x8C, 0xD6, 0x2B,
+ 0x06, 0xFF, 0x3E, 0x0C, 0xB9, 0xD0, 0x98, 0x3E, 0xD0, 0x3C, 0x9F, 0x8B, 0x8F, 0x95, 0x40, 0xF4,
+ 0xAA, 0xEF, 0x2A, 0x6C, 0x2F, 0xFE, 0xE5, 0x40, 0x08, 0xB7, 0xCA, 0x7F, 0x0B, 0xCC, 0xFB, 0x8F,
+ 0x95, 0x9F, 0xAA, 0xDF, 0x4E, 0xAA, 0x67, 0xCE, 0xD0, 0xD5, 0x84, 0xD9, 0xB5, 0x78, 0x85, 0xE9,
+ 0x8E, 0x2D, 0xB8, 0x0D, 0x2D, 0xAF, 0x2E, 0x3E, 0x72, 0xA3, 0xCF, 0x77, 0xF5, 0x81, 0xE7, 0x0C,
+ 0xF9, 0xC7, 0xC6, 0xA8, 0x13, 0x61, 0x0A, 0x8B, 0xB7, 0x3A, 0xAE, 0xE6, 0xFD, 0xB8, 0x0C, 0x6C,
+ 0x76, 0x7B, 0xF7, 0x17, 0xD6, 0xE1, 0x22, 0x70, 0x3F, 0xEC, 0xC9, 0x6F, 0x62, 0x43, 0xD9, 0x1A,
+ 0x34, 0xAD, 0xDC, 0x9B, 0xFC, 0xC1, 0x75, 0x3C, 0x51, 0xA1, 0x6F, 0xF0, 0x5F, 0x34, 0xCA, 0x83,
+ 0x69, 0x88, 0xC1, 0x47, 0x83, 0xDF, 0x9B, 0x3A, 0xAF, 0xD2, 0x87, 0x5D, 0x66, 0xDA, 0xA6, 0x30,
+ 0x35, 0xAB, 0xEA, 0xEB, 0x9A, 0x05, 0x03, 0xEF, 0xB2, 0xA1, 0xF6, 0x68, 0x0E, 0x83, 0xE1, 0xE8,
+ 0x41, 0xE0, 0x73, 0x8F, 0x3E, 0x69, 0x30, 0xE7, 0x8F, 0xF5, 0x5D, 0xE6, 0x0F, 0x3C, 0xD3, 0xFE,
+ 0x51, 0x15, 0x4E, 0xB5, 0x67, 0x8A, 0x8F, 0x4F, 0xDC, 0x1F, 0xCD, 0xD6, 0x82, 0x2F, 0x98, 0xC7,
+ 0xAD, 0x8F, 0x15, 0x5F, 0x3C, 0x59, 0xDC, 0x1F, 0x70, 0x2E, 0x2A, 0x6C, 0xE0, 0xF1, 0x1E, 0x3C,
+ 0xF1, 0xF4, 0xBD, 0xAE, 0xE3, 0x08, 0x5F, 0x78, 0x9A, 0x5B, 0x1B, 0x9A, 0x76, 0x4D, 0xF7, 0xFD,
+ 0x4A, 0xCE, 0x86, 0xF4, 0x34, 0xDE, 0xC0, 0xD7, 0x3D, 0xD3, 0x15, 0x0C, 0xBE, 0x93, 0x2F, 0xFC,
+ 0xED, 0xB7, 0x80, 0x7B, 0x4F, 0xD5, 0xFD, 0xDA, 0x51, 0xAD, 0x4E, 0x9D, 0xFF, 0x0D, 0x5E, 0xFD,
+ 0xB0, 0x27, 0x5F, 0x9B, 0xD0, 0x26, 0x39, 0x9B, 0x42, 0x0D, 0xBA, 0x81, 0x6D, 0xC0, 0x84, 0xC6,
+ 0xDB, 0xC5, 0x1B, 0xB6, 0xA2, 0x2D, 0xF8, 0x1F, 0x3B, 0x86, 0xA3, 0x07, 0x43, 0xD8, 0x85, 0x37,
+ 0x35, 0xC7, 0xDE, 0x79, 0xAD, 0x5B, 0xA6, 0xFE, 0xE3, 0xF5, 0x2E, 0x7B, 0x5D, 0x13, 0x4E, 0xBF,
+ 0x6F, 0xF1, 0x6A, 0x57, 0xD8, 0xF0, 0xB1, 0x17, 0xD8, 0xBA, 0x30, 0x1D, 0x9B, 0xED, 0xF0, 0x37,
+ 0xEC, 0xDF, 0xA3, 0xD6, 0xB2, 0x07, 0x58, 0x7E, 0xE0, 0x79, 0xD0, 0xC5, 0x9D, 0xE6, 0xF5, 0xB9,
+ 0xA8, 0xE9, 0x03, 0xD3, 0x32, 0xE0, 0xF3, 0xFF, 0xAA, 0xFF, 0xEF, 0x37, 0xAA, 0x9B, 0x33, 0x4B,
+ 0xF3, 0xFD, 0x9D, 0xD7, 0x26, 0xEC, 0x78, 0x55, 0xD7, 0x3C, 0x2E, 0xAA, 0x86, 0xF3, 0x60, 0xB3,
+ 0xD8, 0xE7, 0xC0, 0x7D, 0xFD, 0xE6, 0xCF, 0x51, 0xC7, 0xFF, 0xF1, 0x46, 0x4E, 0x37, 0x3D, 0x7B,
+ 0x04, 0xF6, 0x68, 0xF2, 0x35, 0x5F, 0x00, 0xC2, 0xEA, 0xD5, 0xBE, 0xE7, 0x04, 0x6E, 0x6A, 0x5A,
+ 0x03, 0x6E, 0xF6, 0x07, 0xE2, 0x98, 0xD5, 0xFF, 0x9C, 0x78, 0xEC, 0xDC, 0x73, 0xAF, 0x67, 0x39,
+ 0x0F, 0xC7, 0x6C, 0x60, 0x1A, 0x06, 0xB7, 0x93, 0xDF, 0x02, 0x04, 0x6D, 0xDF, 0xC4, 0x85, 0x1E,
+ 0xAB, 0x0E, 0x58, 0xBD, 0x76, 0xE0, 0x33, 0xAE, 0xF9, 0x3C, 0xF9, 0x66, 0xD7, 0xF1, 0x0C, 0xC0,
+ 0xBE, 0xAE, 0x23, 0x84, 0x33, 0x3C, 0x66, 0xBE, 0x63, 0x99, 0x06, 0x6B, 0xB8, 0x8F, 0xEC, 0x9F,
+ 0xF5, 0x3A, 0xFE, 0x17, 0x5B, 0xCA, 0xAB, 0xD1, 0x7C, 0x2D, 0xD3, 0x17, 0x9B, 0x3D, 0x5B, 0xFC,
+ 0xE7, 0x6A, 0x86, 0x61, 0xDA, 0xFD, 0xAA, 0x27, 0xE7, 0x74, 0x58, 0x77, 0x1F, 0xB3, 0x97, 0x23,
+ 0xBB, 0x05, 0xA2, 0xF0, 0x99, 0x30, 0x76, 0xB3, 0x9F, 0x0F, 0x52, 0x2B, 0x95, 0xDF, 0x1D, 0x33,
+ 0xDB, 0xB1, 0x53, 0x93, 0x1C, 0x02, 0xF6, 0x98, 0x76, 0xD5, 0xE2, 0x3D, 0x04, 0xC4, 0x84, 0x31,
+ 0xBB, 0x01, 0x2C, 0xC1, 0x3E, 0xEE, 0x01, 0xD2, 0xFA, 0xA9, 0x9E, 0x9D, 0x40, 0x00, 0xB1, 0xF2,
+ 0x04, 0x10, 0xE3, 0xB3, 0x35, 0x6D, 0xFC, 0xFA, 0xC2, 0xF3, 0x1C, 0x2F, 0xD5, 0xD2, 0x30, 0x7D,
+ 0xD7, 0xD2, 0x9E, 0x8E, 0x99, 0x7C, 0x25, 0x39, 0x2D, 0xDD, 0xB1, 0x1C, 0x98, 0xAF, 0xC7, 0x8D,
+ 0xE4, 0xF3, 0x1E, 0x30, 0xAF, 0xAA, 0x6F, 0xFE, 0x0E, 0x03, 0xFA, 0x43, 0xCD, 0xB2, 0xB8, 0x37,
+ 0x6D, 0xD8, 0x4E, 0xA0, 0xEB, 0x08, 0x8F, 0xE2, 0x03, 0xF7, 0x3D, 0x9E, 0xDE, 0xF8, 0x69, 0x43,
+ 0x47, 0xDF, 0x3F, 0x28, 0x94, 0xEA, 0x3A, 0x96, 0x31, 0x69, 0xFB, 0x1E, 0xAB, 0xD4, 0x3E, 0x35,
+ 0xAB, 0xC9, 0x1B, 0x81, 0xFF, 0x88, 0x3D, 0x1F, 0xB3, 0xFD, 0xFA, 0xBF, 0x4C, 0xEE, 0x55, 0xF6,
+ 0xD0, 0xAC, 0x4F, 0xEB, 0xB8, 0x39, 0x05, 0xAD, 0xC2, 0x1E, 0x0E, 0xA6, 0xF6, 0x70, 0x30, 0xB9,
+ 0x07, 0x4D, 0x08, 0x60, 0xBA, 0xA9, 0xC6, 0xAE, 0x13, 0x52, 0x8B, 0xD6, 0x05, 0x12, 0x08, 0x44,
+ 0x0A, 0xE0, 0xBF, 0x57, 0x4D, 0xDB, 0xE0, 0x8F, 0xC7, 0xAC, 0x51, 0xAF, 0xA7, 0x48, 0x42, 0x91,
+ 0x42, 0x63, 0x0C, 0x1A, 0x70, 0x28, 0x55, 0x15, 0x44, 0x8E, 0xEA, 0x19, 0xDF, 0xD2, 0x74, 0x85,
+ 0xE3, 0x02, 0x19, 0x25, 0x27, 0xAB, 0xD8, 0x9B, 0x64, 0x68, 0x1F, 0xF6, 0xE4, 0xD1, 0xFD, 0xEA,
+ 0x43, 0xD7, 0x31, 0x9E, 0x14, 0x8F, 0x37, 0xCC, 0x7B, 0xA6, 0x23, 0xDF, 0xFC, 0x58, 0xC1, 0x83,
+ 0x52, 0x03, 0x04, 0xF1, 0x2A, 0xCC, 0x34, 0x3E, 0x56, 0xD4, 0xF2, 0xAE, 0xE0, 0x71, 0x65, 0xC4,
+ 0x0D, 0xA9, 0x81, 0x66, 0x99, 0x7D, 0xFB, 0x63, 0x85, 0xE6, 0x5B, 0x09, 0x9B, 0xAB, 0xF7, 0x63,
+ 0xEF, 0xD2, 0xFB, 0xE6, 0xB0, 0x9F, 0xEE, 0xEE, 0xD2, 0xB4, 0xF8, 0x17, 0x38, 0xAD, 0x2B, 0xA3,
+ 0xA3, 0xE5, 0x54, 0x7E, 0xDB, 0xFC, 0xF5, 0x6C, 0x40, 0xAB, 0xE9, 0xD7, 0x5C, 0xBB, 0x5F, 0x81,
+ 0x81, 0xE0, 0xDC, 0x56, 0xDF, 0x31, 0x8B, 0xDF, 0x73, 0xAB, 0xD2, 0x02, 0x06, 0xED, 0x6A, 0x76,
+ 0xBC, 0xCF, 0x36, 0xF7, 0x74, 0x38, 0x18, 0x2A, 0x89, 0x81, 0x09, 0xB9, 0xE5, 0xCC, 0x08, 0xFD,
+ 0x60, 0x30, 0x04, 0xC3, 0xC7, 0x4A, 0x48, 0x0E, 0x8A, 0x1A, 0x2A, 0xAD, 0x3F, 0x1D, 0xBD, 0x05,
+ 0x18, 0x41, 0x9F, 0xB1, 0x55, 0xEE, 0xC1, 0x32, 0x15, 0x84, 0xE4, 0x9F, 0xD3, 0xA0, 0x95, 0xEA,
+ 0x98, 0x98, 0x4E, 0x7C, 0x57, 0x08, 0x09, 0x47, 0x7B, 0x28, 0xB7, 0x50, 0x02, 0xD9, 0xE3, 0x20,
+ 0x3A, 0x5D, 0xD9, 0x6D, 0xCF, 0x41, 0xC2, 0x8D, 0xC3, 0xB9, 0xDB, 0xBA, 0xC5, 0xEF, 0x04, 0xC0,
+ 0xE2, 0xC3, 0x5E, 0xB7, 0xF5, 0xA1, 0xEB, 0xD1, 0xFF, 0x28, 0x01, 0x49, 0x11, 0x86, 0x99, 0x3E,
+ 0xF0, 0x0B, 0x3C, 0x93, 0x11, 0x5C, 0xAC, 0x6D, 0x21, 0x47, 0x66, 0x0F, 0x9A, 0x29, 0x6A, 0xB5,
+ 0xDA, 0xAA, 0xA6, 0x8E, 0xE2, 0x9C, 0xC5, 0x05, 0xCF, 0x98, 0x39, 0x3B, 0x87, 0xCE, 0x26, 0x4C,
+ 0x7D, 0xA0, 0xF9, 0xC0, 0x9B, 0x1F, 0x18, 0xF5, 0xB1, 0x8A, 0xC9, 0xF6, 0x4C, 0x6F, 0xF8, 0x00,
+ 0x22, 0xC0, 0x37, 0xD7, 0x72, 0x34, 0x23, 0x7B, 0xD6, 0xE9, 0xF9, 0x5E, 0xAA, 0x36, 0x2C, 0x70,
+ 0x0D, 0x4D, 0x70, 0x60, 0x95, 0xB2, 0x55, 0x8D, 0x25, 0x37, 0x41, 0x2E, 0x24, 0xDC, 0x88, 0x9C,
+ 0x8B, 0x69, 0xBD, 0xCA, 0x24, 0x29, 0xC4, 0x63, 0x5C, 0x6A, 0xF4, 0x3E, 0x88, 0xA6, 0xC3, 0x6A,
+ 0xA3, 0x09, 0x32, 0x16, 0x12, 0x52, 0x44, 0x2F, 0x9E, 0xF8, 0x51, 0xF5, 0x51, 0x10, 0x8E, 0xD1,
+ 0x49, 0x42, 0x4A, 0xFE, 0x6E, 0x5E, 0x9A, 0x52, 0x54, 0x46, 0xF1, 0x2C, 0x36, 0x9B, 0xDC, 0x60,
+ 0x8E, 0x01, 0xB6, 0x31, 0x19, 0xB0, 0x43, 0x68, 0xD4, 0xD6, 0xFA, 0x3C, 0xE7, 0x82, 0x3C, 0xE7,
+ 0x61, 0x6C, 0x1F, 0xBB, 0x96, 0xA3, 0xFF, 0xF8, 0x73, 0xBC, 0x83, 0x19, 0x9D, 0xC8, 0xE3, 0x04,
+ 0xD9, 0x73, 0x8A, 0xD9, 0xE0, 0x3F, 0x5C, 0x7C, 0xB8, 0x73, 0xC7, 0x31, 0x5E, 0x01, 0x10, 0x0B,
+ 0x1F, 0xFF, 0x95, 0x7B, 0x3E, 0x70, 0xE8, 0xC9, 0x1C, 0xE1, 0xBE, 0x5E, 0xAB, 0x2B, 0x96, 0x80,
+ 0x88, 0x30, 0x36, 0xC6, 0xA8, 0xD7, 0xDF, 0xB9, 0x91, 0xBB, 0xD7, 0x5F, 0x2E, 0xCE, 0xAB, 0x97,
+ 0xEF, 0xDB, 0xB1, 0xD9, 0xC5, 0xC6, 0x61, 0x33, 0x06, 0x92, 0xE8, 0x76, 0x7A, 0x77, 0x75, 0x3E,
+ 0x79, 0x80, 0x73, 0x89, 0x92, 0xA7, 0x56, 0xC0, 0x05, 0x60, 0xE3, 0x80, 0x5D, 0x9D, 0xC3, 0xD1,
+ 0x0A, 0xFF, 0x72, 0x8E, 0xA1, 0x3B, 0x20, 0x36, 0x99, 0x36, 0xE0, 0xBA, 0x7F, 0x7D, 0xFD, 0x79,
+ 0xF2, 0x38, 0xD7, 0xD7, 0x83, 0xE3, 0xB1, 0x6E, 0x92, 0x5D, 0xF5, 0xB9, 0x63, 0x70, 0x90, 0x9A,
+ 0xAF, 0x35, 0x31, 0xB9, 0x9F, 0x03, 0x58, 0xFE, 0xFB, 0x7A, 0xF3, 0xFD, 0xC1, 0xDB, 0xF7, 0x6A,
+ 0x86, 0xBB, 0x79, 0xBB, 0x9D, 0x06, 0xE7, 0x6A, 0xA3, 0x7E, 0x58, 0x6B, 0xBC, 0x3B, 0xAC, 0x1F,
+ 0xBE, 0x3D, 0x6A, 0x14, 0xEC, 0xF8, 0xC4, 0x9A, 0x32, 0xDF, 0xC6, 0xE1, 0x11, 0xCC, 0xF8, 0x5D,
+ 0x38, 0x59, 0xB6, 0x73, 0xD2, 0x3E, 0x7B, 0x33, 0x0E, 0xCF, 0x5C, 0x88, 0x13, 0x83, 0xF5, 0xC5,
+ 0xD9, 0xC5, 0xE5, 0xE4, 0x41, 0xF1, 0xDB, 0x59, 0xD0, 0xE6, 0x3A, 0xEF, 0xFD, 0x34, 0x0D, 0x20,
+ 0xCD, 0x77, 0xF5, 0x66, 0xFD, 0xA8, 0x76, 0x78, 0xF4, 0x2E, 0x27, 0x3C, 0xB0, 0xC7, 0x9F, 0xA7,
+ 0xF4, 0x78, 0xF0, 0xB6, 0x71, 0xF4, 0xAE, 0x7E, 0x50, 0x3B, 0xA8, 0xEF, 0x17, 0xE8, 0xF1, 0x97,
+ 0x69, 0xB8, 0xF0, 0xEE, 0xE8, 0xE8, 0xE8, 0xB0, 0x76, 0xF0, 0xEE, 0x20, 0x7D, 0x18, 0xCF, 0x02,
+ 0x6C, 0x9C, 0xA5, 0x65, 0x7E, 0x1E, 0x78, 0x21, 0xE7, 0x18, 0x8A, 0x6A, 0x3D, 0x2D, 0xCC, 0x8C,
+ 0xB3, 0x3B, 0x3A, 0x47, 0xC6, 0xF8, 0xD1, 0x3F, 0x55, 0xAB, 0xAC, 0x1A, 0xFE, 0x63, 0x70, 0x62,
+ 0xF7, 0x40, 0x8C, 0x61, 0x67, 0x8E, 0xDD, 0x33, 0xFB, 0xB1, 0x2F, 0xAA, 0xD5, 0xD6, 0x38, 0x1F,
+ 0x53, 0xC3, 0x1B, 0xA0, 0x96, 0x81, 0x4E, 0xD4, 0xD7, 0xDC, 0x6A, 0x33, 0x83, 0x75, 0x7D, 0x90,
+ 0x7A, 0x47, 0x24, 0x50, 0x09, 0x9B, 0xC1, 0xFF, 0x55, 0xD7, 0x33, 0x61, 0x72, 0x4F, 0x6C, 0xA4,
+ 0x2B, 0x4B, 0xD6, 0xEB, 0xCA, 0x29, 0xC8, 0x19, 0x54, 0x98, 0x78, 0x72, 0x61, 0x19, 0xB2, 0x8B,
+ 0x0A, 0x83, 0x13, 0x4B, 0xAB, 0xCA, 0x16, 0x74, 0x8C, 0x58, 0x9A, 0xEB, 0xF3, 0x4A, 0xE6, 0x1E,
+ 0xC9, 0x57, 0x49, 0xAB, 0xFE, 0x58, 0xF9, 0xE7, 0xF0, 0xDD, 0x76, 0xB2, 0x77, 0xCD, 0x33, 0xB5,
+ 0x2A, 0x7F, 0x84, 0x0D, 0x30, 0x38, 0x9E, 0xA7, 0x9A, 0x05, 0xDD, 0xC9, 0xA7, 0x78, 0x74, 0x78,
+ 0x8E, 0xE5, 0x8F, 0xC6, 0x49, 0xB6, 0x6D, 0x65, 0x8E, 0x9A, 0x04, 0x60, 0xE0, 0x69, 0xA4, 0xF9,
+ 0x7F, 0x30, 0x13, 0x4B, 0x43, 0xA5, 0x7D, 0x74, 0x18, 0x92, 0x0A, 0x8F, 0xDA, 0x3C, 0xEB, 0x9A,
+ 0x2C, 0xA5, 0xE5, 0xE3, 0x29, 0x67, 0x66, 0xE1, 0x8C, 0x04, 0x48, 0x26, 0xC6, 0x4C, 0xDA, 0xA6,
+ 0x70, 0x19, 0x6C, 0xD8, 0x85, 0x7D, 0x52, 0xE4, 0x9A, 0x6F, 0x65, 0x89, 0x6E, 0x34, 0xCF, 0x60,
+ 0xF8, 0xA3, 0x8A, 0xF2, 0x76, 0x1A, 0x9F, 0xB2, 0x5A, 0xF4, 0x1C, 0x6F, 0xA8, 0x34, 0x77, 0xC0,
+ 0xD4, 0xE6, 0x04, 0xC8, 0xC5, 0xA0, 0x77, 0x3C, 0xF1, 0x05, 0x49, 0x78, 0xAA, 0x5F, 0x21, 0x14,
+ 0x42, 0x74, 0xFD, 0x2A, 0x90, 0x9D, 0xCE, 0x87, 0x64, 0x10, 0x93, 0xF2, 0xFB, 0xC4, 0x2E, 0x48,
+ 0xFB, 0x47, 0x2B, 0x1B, 0x88, 0x16, 0xDC, 0xE2, 0xBA, 0x60, 0x1A, 0x53, 0x1B, 0xC3, 0x00, 0x0D,
+ 0x40, 0xEB, 0xE6, 0x36, 0x83, 0xFE, 0xE1, 0x88, 0x66, 0x4A, 0x52, 0x05, 0x55, 0xDC, 0x81, 0xE7,
+ 0x9A, 0x08, 0x5F, 0xAC, 0xB1, 0x13, 0x21, 0xA5, 0xBC, 0xDD, 0xB8, 0xD4, 0xF4, 0x60, 0xC2, 0x01,
+ 0x1E, 0x00, 0x84, 0x7D, 0xEA, 0x98, 0x1B, 0xA3, 0xF7, 0x43, 0xBC, 0xD0, 0x07, 0x9A, 0xDD, 0xE7,
+ 0x3E, 0x43, 0x01, 0xCC, 0xD7, 0xEE, 0xE1, 0x95, 0x07, 0x1A, 0x0F, 0xD4, 0xD9, 0x5E, 0x8F, 0xA3,
+ 0x21, 0x28, 0x9A, 0x0C, 0xC8, 0x60, 0x51, 0x3F, 0xA0, 0x76, 0xD3, 0x7B, 0xAF, 0x3B, 0xD0, 0x26,
+ 0x89, 0x5C, 0xAF, 0xF1, 0x45, 0x17, 0x26, 0xE3, 0x73, 0xA3, 0x36, 0x05, 0xB6, 0x63, 0xE0, 0x23,
+ 0x54, 0x33, 0xED, 0x9E, 0x53, 0xD5, 0x4D, 0x4F, 0x87, 0x01, 0x05, 0x7F, 0x14, 0x11, 0x59, 0x0E,
+ 0x7D, 0xDC, 0xA9, 0x49, 0x4C, 0x2B, 0xC5, 0xBC, 0xB2, 0x51, 0x20, 0xC5, 0xB0, 0x72, 0xA0, 0x07,
+ 0xCA, 0x51, 0xAD, 0x19, 0x9B, 0x9F, 0xCD, 0x6A, 0x13, 0xDA, 0x6D, 0xC8, 0xE9, 0xA6, 0x82, 0xC2,
+ 0xB4, 0xDD, 0x40, 0x28, 0xF6, 0xE2, 0x69, 0x86, 0xE9, 0x54, 0x94, 0xAD, 0x55, 0xC1, 0xFF, 0x56,
+ 0x3E, 0xBB, 0xD7, 0x40, 0xEE, 0xF8, 0x58, 0xA9, 0xCF, 0xEA, 0xCE, 0xD2, 0xBA, 0xDC, 0x8A, 0x53,
+ 0x79, 0x9D, 0x74, 0xC1, 0x96, 0xDA, 0x78, 0x38, 0xB1, 0xE9, 0x8D, 0xB9, 0x40, 0xF9, 0x2C, 0x20,
+ 0xD9, 0x28, 0x0C, 0xC9, 0x86, 0x84, 0x64, 0xA3, 0xB9, 0x7F, 0x70, 0x78, 0xF4, 0xF6, 0xDD, 0xFB,
+ 0xFA, 0xE8, 0xAF, 0x17, 0xA8, 0x2A, 0xA8, 0x36, 0x0B, 0x43, 0xB5, 0x29, 0xA1, 0xFA, 0x02, 0x41,
+ 0x05, 0xC1, 0xFD, 0xC2, 0x10, 0xDC, 0x7F, 0x81, 0x60, 0x02, 0x82, 0x07, 0x85, 0x21, 0x78, 0xF0,
+ 0x02, 0xC1, 0x04, 0x04, 0x0F, 0x0B, 0x43, 0xF0, 0xF0, 0x05, 0x82, 0x09, 0x08, 0x1E, 0x15, 0x86,
+ 0xE0, 0xD1, 0x0B, 0x04, 0x13, 0x10, 0x7C, 0x5B, 0x18, 0x82, 0x6F, 0x17, 0x07, 0xC1, 0x05, 0x82,
+ 0x50, 0x4E, 0x13, 0xDE, 0x8F, 0xE6, 0x29, 0x8D, 0xF3, 0xA1, 0x1A, 0x1A, 0xBF, 0x6A, 0x51, 0x06,
+ 0xC9, 0x7D, 0xFA, 0xE3, 0x80, 0x7E, 0xD2, 0x40, 0xD4, 0x47, 0x24, 0xC3, 0x4D, 0x05, 0x0B, 0x76,
+ 0x7E, 0x3C, 0x1B, 0x04, 0x49, 0x7D, 0x0C, 0x07, 0x7D, 0x47, 0xC3, 0xBD, 0x2D, 0xB2, 0x89, 0x28,
+ 0xA8, 0x57, 0x12, 0x10, 0x51, 0xCA, 0x6A, 0x42, 0x83, 0x96, 0xBB, 0x32, 0xBD, 0x57, 0x37, 0xDD,
+ 0x80, 0xAE, 0xF8, 0xA2, 0xBE, 0x63, 0xD7, 0x7E, 0xB8, 0xBF, 0xEE, 0xB4, 0xBD, 0x9D, 0x48, 0x01,
+ 0xE3, 0x03, 0x9D, 0x91, 0x12, 0x74, 0x03, 0x2A, 0x0B, 0x9A, 0x58, 0x93, 0x83, 0xA9, 0xCB, 0xBE,
+ 0x29, 0xC3, 0x2D, 0x1E, 0x55, 0xC6, 0xED, 0x25, 0xEA, 0xB6, 0xF7, 0x30, 0x07, 0x75, 0x29, 0xEB,
+ 0x46, 0xD2, 0x4E, 0x41, 0xD7, 0x38, 0xC2, 0xA6, 0x1B, 0x03, 0x85, 0x3D, 0x95, 0x49, 0x06, 0x90,
+ 0x4C, 0x4C, 0x9C, 0xAE, 0xBF, 0xD2, 0x95, 0xAD, 0x7D, 0x86, 0xFE, 0x06, 0x63, 0xE3, 0xEC, 0xBC,
+ 0xA9, 0xA8, 0x9B, 0x0A, 0xF5, 0x20, 0xDB, 0x58, 0xB0, 0x24, 0xCD, 0x3A, 0xA6, 0x5D, 0xCB, 0x49,
+ 0xA4, 0x55, 0x61, 0x54, 0xA5, 0x7B, 0x9A, 0x2E, 0x1C, 0x58, 0xB9, 0xC1, 0x7B, 0x5A, 0x60, 0x09,
+ 0x7F, 0x96, 0xDA, 0xBA, 0x14, 0xD5, 0x75, 0x16, 0x47, 0xCA, 0x8F, 0xD5, 0x5E, 0x0C, 0xFA, 0x37,
+ 0x7E, 0x7F, 0x61, 0x08, 0x9D, 0x65, 0xCF, 0xC9, 0x78, 0x35, 0x65, 0xC2, 0xFB, 0xF4, 0xA5, 0xD3,
+ 0x59, 0xA5, 0xFD, 0x6E, 0x28, 0x80, 0x6F, 0xC6, 0x8D, 0x78, 0x0B, 0xB6, 0xD8, 0xE1, 0x7A, 0x4A,
+ 0x99, 0xEB, 0x62, 0x0D, 0xB3, 0xE1, 0x1E, 0x83, 0x54, 0xCA, 0x50, 0xD7, 0xB7, 0x7D, 0x7F, 0xCD,
+ 0x56, 0xBA, 0xA4, 0x81, 0x6E, 0xEA, 0x5A, 0x26, 0x5B, 0xE7, 0x98, 0x2B, 0xAA, 0x8D, 0xA9, 0x26,
+ 0x3A, 0xBA, 0xEC, 0xE2, 0x9A, 0x1F, 0x78, 0x44, 0xE3, 0xB7, 0x9A, 0xE0, 0x57, 0x78, 0xD2, 0x4C,
+ 0xA1, 0xC8, 0x9B, 0xD1, 0xEB, 0x0C, 0xDF, 0x3F, 0xCE, 0x75, 0xDA, 0x4D, 0x67, 0xC1, 0x13, 0xCE,
+ 0xC6, 0x26, 0xF1, 0xC3, 0x46, 0x13, 0x49, 0x7A, 0x9F, 0x8D, 0x18, 0x7A, 0x1E, 0x7E, 0x11, 0x3B,
+ 0xFF, 0x53, 0x0B, 0xFC, 0xFC, 0x7B, 0xE2, 0x26, 0x32, 0x7E, 0xD2, 0x5F, 0xD9, 0x33, 0x3B, 0xC6,
+ 0x7F, 0x9F, 0x7F, 0x3F, 0xCE, 0xF5, 0xDE, 0x02, 0x99, 0x6A, 0x8A, 0xB9, 0xDE, 0x0D, 0x38, 0xB3,
+ 0x83, 0x61, 0x97, 0x7B, 0xCC, 0xE9, 0x31, 0xF2, 0xC8, 0x00, 0xFC, 0xF5, 0xD1, 0x50, 0x68, 0x39,
+ 0xBA, 0x44, 0xE6, 0x7F, 0xFC, 0xFD, 0xBF, 0x7A, 0xE6, 0x23, 0xF7, 0xFF, 0xF1, 0xF7, 0xFF, 0x66,
+ 0x2E, 0xBC, 0xE8, 0x73, 0x40, 0x5B, 0xA3, 0xC6, 0x4E, 0xEC, 0x27, 0x31, 0x30, 0xED, 0x3E, 0xD3,
+ 0xBA, 0xCE, 0x3D, 0x67, 0x07, 0x9F, 0x7F, 0x07, 0x99, 0xF2, 0x09, 0xB0, 0x06, 0x4D, 0x97, 0xA3,
+ 0x6B, 0x36, 0x78, 0xB9, 0xCF, 0x7D, 0xEA, 0x09, 0xE8, 0x6D, 0x8F, 0x7A, 0xEE, 0xA3, 0x13, 0x03,
+ 0xBA, 0xDE, 0xE8, 0x1E, 0x87, 0xA5, 0xE9, 0x26, 0xF7, 0x6B, 0xEC, 0x8B, 0x03, 0x48, 0xC0, 0x70,
+ 0x46, 0x31, 0x40, 0x33, 0x0F, 0x2F, 0x97, 0x4D, 0x9F, 0xFC, 0xB0, 0x3C, 0xF2, 0xC0, 0x42, 0xD6,
+ 0xDF, 0x80, 0xC1, 0xC8, 0x92, 0x69, 0xDA, 0xEC, 0x14, 0x6F, 0xF6, 0x87, 0x8E, 0xC1, 0x6B, 0xEC,
+ 0x5C, 0x9E, 0x04, 0xC7, 0x38, 0x99, 0x1A, 0xBB, 0x36, 0x87, 0x26, 0xBA, 0xCF, 0xD4, 0xEA, 0xF5,
+ 0x7A, 0xA3, 0xD9, 0xA4, 0x76, 0x75, 0xF8, 0x22, 0xC7, 0xB6, 0x2F, 0xF5, 0xB8, 0xC8, 0x7B, 0x6C,
+ 0x8C, 0xDE, 0x9B, 0x21, 0x0E, 0xE6, 0x38, 0x63, 0x26, 0x50, 0x86, 0x14, 0x52, 0x0F, 0xCB, 0x10,
+ 0x46, 0x5C, 0x92, 0x94, 0x28, 0x94, 0x29, 0x4B, 0xC6, 0xAE, 0x09, 0xC6, 0xE9, 0x27, 0xC7, 0x30,
+ 0x6E, 0x76, 0xD3, 0xB2, 0x02, 0xE6, 0x2C, 0xC9, 0x2F, 0x36, 0x6E, 0xE2, 0x3E, 0x9D, 0x00, 0x84,
+ 0x17, 0x64, 0xB8, 0x9E, 0x3A, 0x0C, 0xC2, 0x87, 0x2D, 0xC7, 0xFB, 0xB0, 0x07, 0xBF, 0x68, 0xBC,
+ 0xCD, 0x60, 0x49, 0x1D, 0xAE, 0x57, 0x5A, 0x1D, 0xA2, 0x4F, 0x9F, 0x75, 0xB9, 0x78, 0xE0, 0x40,
+ 0x22, 0xB1, 0x77, 0xFC, 0x0D, 0xE3, 0x37, 0x53, 0x66, 0x5A, 0x03, 0x4E, 0x00, 0x64, 0x2F, 0xB1,
+ 0xCC, 0x44, 0x2F, 0x46, 0x60, 0x03, 0x36, 0x30, 0x01, 0x20, 0x76, 0x68, 0x2A, 0xB2, 0xD8, 0x04,
+ 0xDE, 0xBB, 0xC0, 0xAB, 0xC0, 0x7F, 0x7A, 0x81, 0x25, 0xF9, 0x83, 0xD0, 0x7E, 0x10, 0x8B, 0x4A,
+ 0xBC, 0xCC, 0xEF, 0xD1, 0x63, 0xAA, 0xC7, 0x1F, 0xA2, 0x19, 0x68, 0xBA, 0xE7, 0xF8, 0xF0, 0x0B,
+ 0x78, 0x13, 0xBC, 0x0D, 0x2F, 0xDE, 0xF3, 0x27, 0xB6, 0xD3, 0x3C, 0xF8, 0x13, 0x1B, 0x38, 0x81,
+ 0xE7, 0xBF, 0x59, 0x04, 0x77, 0x8A, 0xD8, 0x51, 0x03, 0xDF, 0x79, 0xD7, 0x78, 0x7F, 0x14, 0x0E,
+ 0x0F, 0x1C, 0x89, 0x40, 0x9E, 0x1B, 0x9A, 0x31, 0xF7, 0xAD, 0x17, 0xA6, 0xB4, 0x50, 0xA6, 0x44,
+ 0x14, 0x54, 0x92, 0x2B, 0x41, 0xDB, 0x05, 0xB0, 0xA5, 0x12, 0x5F, 0xCF, 0x54, 0x65, 0xC9, 0x47,
+ 0xE6, 0xC9, 0xD6, 0x86, 0xA6, 0x7E, 0x03, 0xA8, 0x68, 0x9D, 0x7B, 0x8E, 0x2B, 0x25, 0xCE, 0x5C,
+ 0x86, 0x8F, 0x78, 0xD3, 0x4A, 0xEB, 0x5C, 0x7E, 0x62, 0xF4, 0x31, 0x87, 0xAD, 0x42, 0xAA, 0x6E,
+ 0xCA, 0x56, 0x94, 0xE8, 0x6A, 0x6C, 0x5E, 0xC9, 0xCD, 0x32, 0xD4, 0x2C, 0xE5, 0x6E, 0xCD, 0x00,
+ 0x9E, 0xE3, 0x92, 0x88, 0x31, 0xBA, 0x67, 0x6B, 0x3B, 0x9E, 0xC0, 0xA8, 0x84, 0x0F, 0x7B, 0xF2,
+ 0xAB, 0x42, 0xED, 0x61, 0xB8, 0x8E, 0x20, 0xF1, 0x07, 0xA8, 0xA8, 0x54, 0x0F, 0xFB, 0x30, 0x03,
+ 0x6E, 0x80, 0xE0, 0x03, 0x5A, 0x85, 0x5D, 0xAA, 0x87, 0x83, 0x4A, 0xEB, 0x24, 0x10, 0xCE, 0xD0,
+ 0x11, 0xE6, 0x7D, 0xB9, 0x55, 0x1C, 0xE2, 0x61, 0xA0, 0x95, 0x6A, 0x7A, 0x04, 0x83, 0x9B, 0x5E,
+ 0xD7, 0xF1, 0x6C, 0xCE, 0x1A, 0xFD, 0x52, 0x5D, 0xBC, 0x8D, 0x75, 0xD1, 0x2C, 0xD7, 0xC5, 0xBB,
+ 0x58, 0x17, 0x07, 0xE5, 0xBA, 0x78, 0x5F, 0x69, 0x7D, 0xF7, 0x4C, 0x5F, 0x94, 0x6A, 0xDC, 0x00,
+ 0x3C, 0x3A, 0x35, 0x7F, 0xE4, 0x80, 0x3E, 0xB0, 0x4D, 0x42, 0xF3, 0xD6, 0x8A, 0x7C, 0x0E, 0x4E,
+ 0x8C, 0xBF, 0x05, 0xBE, 0x08, 0x8F, 0x45, 0xC1, 0x3D, 0x5B, 0xB3, 0x98, 0x66, 0xF5, 0x1D, 0xCF,
+ 0x14, 0x83, 0x21, 0x1E, 0x30, 0x43, 0x4D, 0xE8, 0x03, 0xFA, 0x1E, 0x34, 0x5D, 0x69, 0x3B, 0xD1,
+ 0x5C, 0xD7, 0x32, 0x95, 0x54, 0xCF, 0xED, 0x7B, 0xD3, 0x73, 0x6C, 0x1C, 0x57, 0x1D, 0xB5, 0xCA,
+ 0x4B, 0x81, 0x99, 0x43, 0xD7, 0x83, 0xF3, 0x4C, 0xF6, 0xED, 0x71, 0x9D, 0x03, 0xFA, 0x79, 0xAF,
+ 0x7D, 0x39, 0x8C, 0x0B, 0xCA, 0xAA, 0xEC, 0x00, 0x0E, 0xE1, 0xF8, 0x79, 0xAD, 0x9C, 0x1E, 0x02,
+ 0x74, 0x22, 0x70, 0xEE, 0x4D, 0x03, 0x9D, 0x13, 0xE0, 0xDC, 0xF3, 0xE0, 0x54, 0xD6, 0xF5, 0x80,
+ 0xCE, 0xC9, 0xD0, 0xF9, 0x1B, 0xC3, 0x04, 0x80, 0x4D, 0xD7, 0xD0, 0x03, 0x93, 0x46, 0x8C, 0x0F,
+ 0x84, 0x53, 0x07, 0x38, 0x05, 0xB6, 0x1F, 0x98, 0x44, 0xBF, 0x0C, 0xE0, 0x23, 0x90, 0x1F, 0xD0,
+ 0x29, 0x4A, 0x1C, 0x89, 0x1A, 0xF4, 0xE1, 0x6D, 0x7B, 0xD2, 0x92, 0xF0, 0x4C, 0xB6, 0x60, 0xE3,
+ 0x2C, 0x74, 0x0C, 0x42, 0x17, 0x0B, 0xD0, 0x0A, 0xF0, 0x34, 0xC6, 0x03, 0x1E, 0xCE, 0x79, 0x98,
+ 0x7B, 0x34, 0x1E, 0xA8, 0x34, 0xD8, 0x3B, 0x68, 0x21, 0x52, 0x80, 0x88, 0x66, 0x29, 0xE7, 0xAD,
+ 0x3F, 0xC5, 0xD4, 0x8A, 0x90, 0xA7, 0x6C, 0x92, 0x7F, 0x44, 0xEC, 0x3C, 0x8A, 0xB3, 0xD2, 0x0E,
+ 0xB7, 0x7D, 0xC7, 0xBB, 0x0C, 0xD0, 0xEB, 0xB2, 0xB4, 0x35, 0x36, 0xCB, 0x4B, 0x6F, 0xE9, 0x66,
+ 0xF6, 0xA1, 0x69, 0x5F, 0x58, 0xFC, 0xBE, 0x92, 0x29, 0x15, 0x1C, 0x8D, 0x59, 0xD5, 0x6F, 0x60,
+ 0x5B, 0x3B, 0x7F, 0x65, 0xD8, 0x84, 0xD0, 0xE0, 0x78, 0xC5, 0xF6, 0x49, 0x18, 0x1F, 0x63, 0xE2,
+ 0x18, 0x0F, 0x27, 0x80, 0x78, 0x66, 0x70, 0x8C, 0x19, 0xF1, 0x09, 0x59, 0x35, 0x69, 0x21, 0xF2,
+ 0x81, 0x04, 0x2C, 0xCB, 0x14, 0x64, 0xBA, 0xEC, 0x72, 0x94, 0x52, 0x0D, 0x89, 0x92, 0xA0, 0x5D,
+ 0x6F, 0xB0, 0xE9, 0xB2, 0xF8, 0x55, 0x84, 0xDA, 0xA9, 0x22, 0x57, 0x11, 0x53, 0x64, 0x35, 0x25,
+ 0xA6, 0x29, 0xA4, 0xC8, 0x75, 0x11, 0xA1, 0x5E, 0x5E, 0xC2, 0x25, 0xC4, 0xB2, 0x11, 0xFF, 0xEC,
+ 0xCB, 0xD7, 0x22, 0x78, 0x7F, 0xB6, 0xF7, 0xA5, 0xBE, 0x2E, 0x7C, 0x1F, 0xE1, 0x33, 0xFA, 0xB8,
+ 0x69, 0x96, 0x8C, 0x40, 0xC9, 0x44, 0xEE, 0x11, 0x03, 0x3D, 0x32, 0x4E, 0x41, 0x37, 0x42, 0xAA,
+ 0x08, 0xFD, 0xBD, 0x91, 0xE5, 0x36, 0xEB, 0xA9, 0xC7, 0xB7, 0x2F, 0xF4, 0x30, 0x9B, 0x1E, 0x10,
+ 0x57, 0xF2, 0x92, 0x03, 0xBC, 0xBB, 0x0E, 0x6A, 0x90, 0xB6, 0x67, 0xDB, 0x47, 0x4C, 0x21, 0xDE,
+ 0x78, 0x36, 0xE0, 0xFA, 0x8F, 0x53, 0xE7, 0x91, 0xFB, 0x39, 0x69, 0x22, 0xD1, 0x1A, 0x1A, 0x9D,
+ 0x25, 0x3E, 0xE7, 0xD1, 0x3E, 0x16, 0x29, 0x75, 0xDD, 0xC5, 0x05, 0x15, 0x10, 0x31, 0x74, 0xCD,
+ 0x25, 0x11, 0x05, 0xE4, 0x09, 0x98, 0xA8, 0x0A, 0xC1, 0x05, 0x99, 0x43, 0xBE, 0x83, 0xA2, 0x8D,
+ 0xA4, 0x0D, 0x38, 0x08, 0x3C, 0x07, 0x04, 0x18, 0xA0, 0x01, 0xD3, 0xB5, 0xF8, 0x88, 0x76, 0x22,
+ 0xCB, 0x43, 0xF4, 0x55, 0x72, 0xC1, 0x20, 0x23, 0x39, 0x43, 0x1E, 0x97, 0x72, 0x7C, 0x66, 0x98,
+ 0xBA, 0x40, 0x71, 0x0A, 0x85, 0x20, 0x9B, 0x03, 0x9D, 0xA1, 0x57, 0x69, 0xE0, 0xA1, 0x44, 0x06,
+ 0xD3, 0xE0, 0x1E, 0x46, 0xAA, 0x24, 0xBB, 0xA1, 0xF9, 0x90, 0x00, 0x8B, 0xA4, 0x28, 0x05, 0x55,
+ 0x69, 0x15, 0x91, 0x42, 0x54, 0xF2, 0x65, 0x72, 0x3F, 0x95, 0x56, 0xDC, 0xB4, 0x60, 0x06, 0x8B,
+ 0x41, 0x89, 0x4E, 0x0C, 0x1C, 0x9F, 0x47, 0x4B, 0x03, 0x91, 0x10, 0x3A, 0x09, 0x05, 0xB9, 0xA1,
+ 0x5C, 0x6A, 0x97, 0xE3, 0x67, 0x62, 0x03, 0x46, 0xE0, 0xE1, 0xDF, 0x91, 0x5C, 0xA5, 0x6B, 0x96,
+ 0x1E, 0x44, 0xEB, 0x8B, 0x58, 0xC3, 0xA7, 0x76, 0x67, 0x97, 0x75, 0x4E, 0x4F, 0xE0, 0xE7, 0xA7,
+ 0xEB, 0xAF, 0x5F, 0x4E, 0x3A, 0xF0, 0xC7, 0x29, 0x37, 0xCF, 0x9D, 0x60, 0x97, 0x98, 0xC4, 0x27,
+ 0xCD, 0x32, 0x2D, 0xEE, 0x6C, 0xA2, 0x6F, 0x6A, 0xAE, 0xD3, 0x40, 0x47, 0xD4, 0x4F, 0xDC, 0xD2,
+ 0xCE, 0x26, 0x81, 0xB1, 0xF6, 0x8A, 0xF9, 0x4B, 0xDA, 0x08, 0xBA, 0x8F, 0x49, 0x72, 0x00, 0x10,
+ 0x56, 0x5A, 0xF0, 0x63, 0xEF, 0x7F, 0xFE, 0xD2, 0xE9, 0xCC, 0x26, 0x0D, 0xC9, 0x70, 0xC6, 0xC7,
+ 0xA0, 0xE7, 0xE1, 0x05, 0x1C, 0x3D, 0x82, 0x59, 0x47, 0x8E, 0x1D, 0x92, 0xFF, 0x64, 0x8F, 0xBD,
+ 0x0D, 0x40, 0x42, 0x14, 0x03, 0xBD, 0x18, 0x7E, 0xAE, 0x18, 0x42, 0x72, 0xE0, 0xAD, 0xC0, 0x23,
+ 0x49, 0x6A, 0x80, 0x4B, 0xF2, 0x8F, 0x55, 0xA3, 0x52, 0x38, 0xFC, 0x36, 0xC0, 0x4A, 0xB2, 0xA8,
+ 0x4A, 0x4B, 0xFE, 0x5E, 0x31, 0xA4, 0xC2, 0xC1, 0xB7, 0x02, 0xA9, 0x24, 0x53, 0x07, 0xA4, 0x92,
+ 0x7F, 0xAC, 0x1A, 0xA9, 0xC2, 0xE1, 0x0B, 0xC3, 0xCA, 0xCD, 0xEE, 0x70, 0x96, 0x3C, 0x55, 0x04,
+ 0xF0, 0xE8, 0xFC, 0x30, 0x2F, 0xC0, 0xB9, 0x8D, 0xA2, 0xC8, 0x17, 0xE1, 0x99, 0xEE, 0x99, 0x65,
+ 0x62, 0xC4, 0x77, 0xEB, 0x82, 0x1E, 0xB1, 0x2F, 0x77, 0xB7, 0x57, 0x6D, 0x26, 0x1F, 0x2E, 0x17,
+ 0xEA, 0x19, 0x73, 0x58, 0x91, 0x64, 0xF6, 0x8D, 0xA4, 0x15, 0x10, 0xC9, 0xE4, 0x62, 0x75, 0x1A,
+ 0x1D, 0x45, 0x16, 0x34, 0x20, 0x63, 0x54, 0x33, 0x08, 0x39, 0x20, 0x9D, 0xC9, 0x0C, 0x2A, 0x38,
+ 0x92, 0x14, 0x53, 0x34, 0x90, 0x46, 0x7C, 0x12, 0x5D, 0x7C, 0xEE, 0x61, 0xF0, 0x4D, 0x8D, 0xFD,
+ 0xEC, 0x04, 0x5E, 0x18, 0x89, 0x33, 0x0C, 0x7C, 0x81, 0xEA, 0xCC, 0x83, 0x89, 0x37, 0xDD, 0x32,
+ 0x6C, 0xD8, 0x43, 0xA7, 0x33, 0xA6, 0x09, 0x86, 0xD7, 0x82, 0xC2, 0x1C, 0xF2, 0xB8, 0x10, 0x73,
+ 0x6E, 0xFA, 0x08, 0x81, 0x2D, 0x8B, 0xA0, 0xC1, 0xAD, 0xB3, 0x47, 0x9B, 0x16, 0xBA, 0xC9, 0x64,
+ 0x06, 0x7C, 0xE5, 0xD3, 0x7C, 0x72, 0xEB, 0xBF, 0x69, 0x79, 0x3F, 0x36, 0x8D, 0x5F, 0xCF, 0x60,
+ 0x6B, 0xB8, 0xF7, 0xD9, 0xF1, 0x73, 0x28, 0xA7, 0x33, 0xDD, 0x31, 0x0F, 0xC7, 0x14, 0x68, 0xD9,
+ 0xFD, 0xCC, 0x9E, 0x71, 0xFC, 0xE3, 0x7C, 0xB7, 0x5D, 0x93, 0xBC, 0x32, 0x8F, 0x8A, 0x5E, 0x5D,
+ 0xCD, 0xF0, 0xCC, 0x9C, 0x00, 0xA4, 0xDC, 0xF7, 0x56, 0xD9, 0xED, 0x97, 0x7F, 0x77, 0xB5, 0x6A,
+ 0xD4, 0x69, 0x27, 0x52, 0x5D, 0xAD, 0x1E, 0x75, 0x70, 0xFC, 0x55, 0xA3, 0xCE, 0x4C, 0xCB, 0xC1,
+ 0x04, 0x30, 0xCD, 0x81, 0x3C, 0xD8, 0xFE, 0xF9, 0x21, 0x0F, 0x9C, 0x28, 0xDE, 0x3A, 0x91, 0x07,
+ 0xC7, 0xDF, 0x78, 0xBE, 0x43, 0x40, 0x9A, 0x03, 0x75, 0xB0, 0xFD, 0xF3, 0x44, 0x9D, 0xF6, 0xF7,
+ 0x75, 0x23, 0x0F, 0x6B, 0x7F, 0xDF, 0x0A, 0xFC, 0x01, 0x48, 0xCD, 0x89, 0x41, 0xED, 0xEF, 0xCF,
+ 0x09, 0x87, 0x6E, 0x9C, 0xC0, 0x16, 0x6D, 0xC7, 0xB4, 0x45, 0xA5, 0x0C, 0x86, 0x50, 0xF3, 0x1C,
+ 0x47, 0x13, 0xF4, 0xBF, 0xC1, 0xE8, 0x11, 0x03, 0x42, 0x29, 0xE4, 0x18, 0xB5, 0x7F, 0x9E, 0xA8,
+ 0x31, 0x27, 0x7B, 0x99, 0x78, 0xA9, 0x94, 0x1F, 0x79, 0x66, 0xBF, 0x55, 0x9E, 0xFD, 0xBC, 0x95,
+ 0xB8, 0xBD, 0x12, 0xFC, 0x2A, 0xCB, 0x7E, 0xE2, 0x3D, 0x6C, 0x12, 0x8E, 0x45, 0x16, 0x85, 0x46,
+ 0x5E, 0x7B, 0x4E, 0x6E, 0x13, 0x43, 0x7C, 0xF9, 0x77, 0x98, 0xF9, 0x72, 0x68, 0x8A, 0x4F, 0x9F,
+ 0x4E, 0x2A, 0xAD, 0xF0, 0x03, 0x83, 0x4F, 0xA8, 0x74, 0x4B, 0xD6, 0x9C, 0x6F, 0xF7, 0xE7, 0xB1,
+ 0x37, 0x4C, 0x9A, 0xD0, 0xCC, 0xFD, 0x0C, 0x6C, 0xEA, 0x91, 0x1B, 0xAB, 0x8E, 0x66, 0xA2, 0xEB,
+ 0x1C, 0x9D, 0xC0, 0x83, 0x09, 0xEA, 0x7E, 0x0B, 0x4C, 0xF4, 0x90, 0x81, 0xBF, 0xFA, 0x81, 0xA5,
+ 0x79, 0xA3, 0x4B, 0x12, 0x95, 0x55, 0x8D, 0x8C, 0x14, 0x74, 0x0B, 0x83, 0x1E, 0xA6, 0x6C, 0x07,
+ 0xE1, 0x3B, 0x94, 0x01, 0x6F, 0x6F, 0x98, 0xAF, 0xB2, 0x89, 0xE0, 0xF7, 0xB2, 0x4B, 0xF8, 0x65,
+ 0x33, 0x0D, 0x9D, 0xC6, 0x30, 0x29, 0xAB, 0x66, 0x59, 0x4F, 0x2A, 0x70, 0x4A, 0x5D, 0x16, 0x69,
+ 0x1E, 0xF7, 0x05, 0xD3, 0xEE, 0x35, 0x93, 0x12, 0xF6, 0xA6, 0x6D, 0x22, 0x19, 0x86, 0x8C, 0x0D,
+ 0x0F, 0xA9, 0x9A, 0xFC, 0x3D, 0x46, 0x33, 0x5D, 0x3A, 0x1E, 0xEB, 0x05, 0x22, 0xF0, 0xC8, 0x07,
+ 0x0A, 0x33, 0xB5, 0x3A, 0xF6, 0x74, 0x42, 0x42, 0xA4, 0x02, 0xB0, 0x39, 0x0F, 0x68, 0xEA, 0xF9,
+ 0x0A, 0x20, 0xBF, 0x00, 0xC0, 0x79, 0x36, 0x17, 0x33, 0xCD, 0x59, 0x0B, 0xA4, 0xC5, 0x32, 0xF4,
+ 0x08, 0x62, 0x10, 0x4E, 0xF9, 0x8B, 0x23, 0xC2, 0x19, 0x57, 0x5A, 0x68, 0x13, 0x23, 0x93, 0x15,
+ 0x5E, 0xAC, 0xC7, 0x8D, 0x80, 0xAC, 0xCA, 0x6C, 0x47, 0xB0, 0xF0, 0xCD, 0x5C, 0x74, 0xBA, 0x48,
+ 0x5A, 0xCD, 0x9A, 0x6C, 0x31, 0x34, 0x9B, 0xDF, 0x29, 0x5D, 0xD1, 0xE3, 0x57, 0x99, 0x10, 0xD0,
+ 0xF5, 0x1C, 0x23, 0xD0, 0x85, 0xCF, 0x1E, 0x06, 0xA6, 0x3E, 0x60, 0x03, 0x4C, 0x8F, 0x13, 0xCE,
+ 0x0D, 0xAF, 0x47, 0x6D, 0xA4, 0x92, 0x7B, 0x53, 0x3C, 0xED, 0xB2, 0x27, 0x27, 0x20, 0x32, 0x0B,
+ 0x42, 0xE8, 0x9A, 0x68, 0x15, 0xD6, 0x0C, 0xBC, 0x04, 0x8E, 0x9A, 0x8C, 0x41, 0xBC, 0xFB, 0xC4,
+ 0xC8, 0x38, 0x2A, 0x6F, 0x49, 0xD1, 0x63, 0x3C, 0xBA, 0x8F, 0x2D, 0x4E, 0x71, 0x4B, 0xA3, 0xBA,
+ 0xBC, 0x0E, 0xE0, 0xB9, 0xCE, 0xAC, 0xB1, 0x60, 0xC1, 0x42, 0xF6, 0x48, 0xC5, 0xE5, 0xD0, 0xAF,
+ 0xFA, 0x54, 0xC6, 0x00, 0x4E, 0xF3, 0x53, 0x93, 0xA1, 0x85, 0x51, 0x2C, 0x6B, 0x9F, 0x0B, 0x15,
+ 0x16, 0x7C, 0x6D, 0xFA, 0x62, 0xE7, 0xCD, 0x58, 0xD0, 0xEC, 0xD0, 0xA0, 0x5F, 0x2A, 0x5F, 0xF1,
+ 0xC4, 0x20, 0xC4, 0xE9, 0x96, 0xE7, 0x1C, 0x01, 0x8A, 0x33, 0x83, 0x11, 0x31, 0xD0, 0x73, 0xEA,
+ 0x28, 0x25, 0x63, 0x15, 0xB1, 0xDF, 0xE9, 0x7B, 0xA8, 0x00, 0x44, 0xB1, 0x77, 0x7E, 0x3C, 0x62,
+ 0x11, 0x9A, 0xCE, 0x1F, 0xB4, 0x38, 0xDA, 0xEB, 0x59, 0x51, 0xC3, 0x8B, 0xBD, 0x07, 0xF8, 0x72,
+ 0x73, 0x71, 0x42, 0xEE, 0x02, 0xB7, 0x27, 0xDF, 0x7F, 0xA2, 0xF4, 0x58, 0x78, 0xF2, 0x89, 0x07,
+ 0x87, 0x0D, 0x1D, 0x1F, 0x49, 0x79, 0x38, 0x74, 0x6C, 0xF2, 0xD0, 0xC0, 0x3C, 0xF8, 0xE8, 0x3C,
+ 0x01, 0xBB, 0xE8, 0x4B, 0x67, 0x56, 0x09, 0x8F, 0x6E, 0x20, 0x92, 0x7E, 0x0F, 0x7E, 0xE0, 0xE2,
+ 0xBB, 0xBE, 0xF4, 0x66, 0x85, 0x13, 0xD7, 0x66, 0x6F, 0xEB, 0xB1, 0x44, 0x5B, 0xAA, 0xA1, 0x5F,
+ 0x63, 0x17, 0x1A, 0xF0, 0x8E, 0xB0, 0x1F, 0x19, 0x1A, 0x22, 0x19, 0xA6, 0xDA, 0x28, 0xC5, 0x5D,
+ 0xC2, 0x06, 0x34, 0x3D, 0x43, 0x91, 0x3D, 0xDB, 0xA9, 0xBF, 0xA1, 0x89, 0x0F, 0x9C, 0x07, 0x98,
+ 0x8E, 0xC0, 0x48, 0x12, 0x0A, 0x36, 0x91, 0xBD, 0x51, 0x3A, 0x5B, 0x35, 0xE5, 0x9D, 0x06, 0xFB,
+ 0x08, 0xB8, 0x3E, 0xFA, 0x32, 0xFA, 0x06, 0xC3, 0xF3, 0x1A, 0xE8, 0x72, 0xB5, 0xCB, 0x0E, 0xD5,
+ 0x3B, 0xF2, 0x3B, 0x15, 0xF2, 0x72, 0xC8, 0x28, 0x92, 0xEF, 0x4D, 0x8C, 0xED, 0x10, 0xC0, 0x40,
+ 0xAC, 0xD8, 0x65, 0x9F, 0x3A, 0xF4, 0xE3, 0x0E, 0x7F, 0xFC, 0x55, 0xFA, 0x5C, 0xDC, 0xDE, 0x9C,
+ 0xA9, 0x30, 0x3A, 0xFF, 0x98, 0xD5, 0x51, 0xB2, 0x6B, 0xD6, 0xB7, 0x32, 0x39, 0xD8, 0xAC, 0x70,
+ 0xD8, 0xE9, 0xE4, 0x32, 0x25, 0x6F, 0x5D, 0x46, 0x82, 0xC4, 0xC3, 0x8C, 0xFC, 0x88, 0x99, 0x3D,
+ 0x96, 0xCD, 0x15, 0x10, 0x67, 0x79, 0xE3, 0xF9, 0x02, 0xEC, 0x21, 0xD7, 0xD4, 0xEE, 0xFA, 0x93,
+ 0x92, 0x05, 0xE4, 0x3B, 0x22, 0x23, 0x76, 0x4A, 0x91, 0xE9, 0x77, 0xCE, 0x97, 0x58, 0xCF, 0xA3,
+ 0xF4, 0x00, 0x80, 0x14, 0x1D, 0x8A, 0x97, 0x82, 0xA3, 0x2D, 0x57, 0xB7, 0x61, 0x17, 0x6C, 0x07,
+ 0x71, 0xEF, 0xF1, 0xF0, 0x4D, 0xCE, 0xCC, 0x02, 0x39, 0x63, 0x89, 0x96, 0x03, 0x55, 0x15, 0xB3,
+ 0xBA, 0x1C, 0xC0, 0x5E, 0x27, 0x3B, 0x4F, 0xC0, 0x56, 0x7D, 0x57, 0x0A, 0xB2, 0xEC, 0x4F, 0xEC,
+ 0xF6, 0xA7, 0x9B, 0xC7, 0x66, 0x31, 0x08, 0xE7, 0xC0, 0xDC, 0xD8, 0x01, 0x8D, 0x07, 0x6C, 0x65,
+ 0xC9, 0x81, 0x4D, 0x39, 0x1E, 0x67, 0xCE, 0x3D, 0x95, 0xD2, 0x80, 0x42, 0xF1, 0xD6, 0x99, 0xD2,
+ 0x40, 0x66, 0x65, 0xF7, 0x97, 0x95, 0x94, 0xF4, 0x34, 0xD6, 0x75, 0x21, 0xB1, 0x21, 0xD6, 0x70,
+ 0xC2, 0x3E, 0x20, 0x20, 0x33, 0x12, 0x8C, 0x57, 0x26, 0x0B, 0x79, 0x31, 0x60, 0xA7, 0xB2, 0x22,
+ 0x10, 0x04, 0x36, 0x29, 0x2B, 0xC2, 0xD4, 0xE5, 0x97, 0xCC, 0x59, 0x8A, 0xFD, 0xCB, 0x30, 0xD2,
+ 0x2B, 0x5B, 0x26, 0x46, 0x9A, 0xE9, 0x72, 0x91, 0xC8, 0xAC, 0x14, 0x02, 0xEA, 0x0E, 0x1E, 0x76,
+ 0x54, 0x3F, 0x61, 0xBE, 0xA5, 0xF0, 0x79, 0xD2, 0xD4, 0xA5, 0x1A, 0xCE, 0xB4, 0x6B, 0xC4, 0x6D,
+ 0x8B, 0x63, 0x23, 0xB4, 0xE4, 0x5F, 0xD5, 0x2B, 0x7B, 0xC5, 0x9E, 0xB5, 0x57, 0x32, 0xA4, 0xD7,
+ 0x05, 0xA9, 0xCB, 0x04, 0xC4, 0x89, 0x92, 0x0F, 0x80, 0x60, 0xA6, 0x31, 0x9C, 0x26, 0xF3, 0x55,
+ 0xDC, 0x11, 0xE5, 0x94, 0x17, 0xEC, 0x87, 0x4D, 0xD5, 0x6B, 0x04, 0xE5, 0x1C, 0xE8, 0x72, 0xE6,
+ 0x74, 0x29, 0x43, 0xBB, 0x81, 0x9A, 0xD6, 0x3F, 0xFE, 0xFE, 0x5F, 0x7E, 0x78, 0x24, 0x61, 0xD6,
+ 0x02, 0xEC, 0x39, 0xEC, 0xB1, 0x46, 0x31, 0xBC, 0xD4, 0x23, 0xF4, 0x84, 0xE2, 0x10, 0x39, 0xAA,
+ 0xA3, 0xA8, 0x44, 0x13, 0x97, 0x71, 0x4C, 0xDA, 0x0F, 0x14, 0xCF, 0x5C, 0x50, 0x0B, 0x1F, 0x81,
+ 0x96, 0x05, 0xC6, 0x11, 0x1D, 0xD5, 0xA3, 0x68, 0xE1, 0x07, 0x90, 0xA8, 0x06, 0x32, 0x88, 0x48,
+ 0x33, 0xC2, 0xC4, 0xAE, 0x72, 0x02, 0x38, 0x5B, 0xB4, 0xB1, 0xF4, 0x60, 0x66, 0xD1, 0x22, 0x70,
+ 0x34, 0xD0, 0x0F, 0xED, 0x74, 0xD4, 0x72, 0x18, 0xE4, 0x3C, 0xE4, 0x62, 0xE0, 0x18, 0x0C, 0xA4,
+ 0x47, 0x93, 0x63, 0xE0, 0xD5, 0x7F, 0xEE, 0xD7, 0xF5, 0x61, 0x46, 0xDC, 0x14, 0x8A, 0xA6, 0xA8,
+ 0x7A, 0xDE, 0x03, 0x77, 0xA9, 0xB1, 0x2B, 0x5B, 0x87, 0xF1, 0xFD, 0x30, 0x80, 0x2A, 0xF4, 0xCB,
+ 0xFF, 0xDA, 0x45, 0x57, 0x16, 0x39, 0xF0, 0x9D, 0x39, 0xE4, 0x61, 0xD6, 0x85, 0x5B, 0x69, 0x6C,
+ 0x32, 0x30, 0xF1, 0x86, 0xCD, 0x30, 0x1B, 0x7A, 0xCC, 0xDD, 0xD7, 0x94, 0x7D, 0xF1, 0x28, 0xE8,
+ 0x89, 0xC6, 0x22, 0x49, 0x19, 0xE3, 0xB1, 0x60, 0x0A, 0x26, 0xC6, 0x89, 0x9D, 0x72, 0x2C, 0x29,
+ 0x31, 0x7A, 0xC9, 0xF4, 0xC3, 0x49, 0x1B, 0xE4, 0x23, 0x43, 0xE1, 0xD3, 0x55, 0x78, 0x65, 0x18,
+ 0x25, 0x79, 0x50, 0x21, 0x55, 0x94, 0x8F, 0x16, 0x13, 0xD8, 0x50, 0x39, 0x86, 0x51, 0x3C, 0x40,
+ 0x5D, 0x06, 0x8E, 0x1D, 0xD6, 0xEA, 0xC3, 0x91, 0xA4, 0x79, 0x44, 0xA2, 0xE6, 0x51, 0xBD, 0xEE,
+ 0xEF, 0xB2, 0x46, 0x8D, 0x3E, 0xD0, 0x0B, 0x5B, 0xE7, 0x54, 0x13, 0x72, 0x80, 0x75, 0x7A, 0xD4,
+ 0x38, 0x23, 0x7C, 0x50, 0x99, 0x01, 0xA6, 0x5E, 0x2B, 0x35, 0x9A, 0x59, 0x91, 0x26, 0x88, 0x59,
+ 0xB3, 0x53, 0x49, 0xC5, 0x30, 0x0F, 0x1D, 0xA3, 0xD8, 0x8E, 0xFF, 0xA6, 0xF4, 0x15, 0x40, 0x22,
+ 0x08, 0x3C, 0x47, 0x00, 0x70, 0x71, 0x47, 0x88, 0x0C, 0xC0, 0xE4, 0xBE, 0x08, 0x18, 0x6F, 0xBB,
+ 0xED, 0xD7, 0x4C, 0xB1, 0x15, 0xB5, 0x15, 0xBF, 0x39, 0x51, 0x64, 0x3E, 0xD7, 0x4D, 0x53, 0x06,
+ 0x3E, 0x85, 0x9C, 0x68, 0x66, 0xB7, 0xC4, 0xA9, 0xF6, 0xCF, 0x59, 0x47, 0x00, 0x8F, 0xC0, 0xB3,
+ 0x37, 0xE2, 0x5A, 0x33, 0x9B, 0xEE, 0x0C, 0xE7, 0x41, 0x3D, 0x42, 0x3A, 0x85, 0x7F, 0xDD, 0xEA,
+ 0xFE, 0x32, 0x51, 0x6F, 0x0C, 0xD8, 0x65, 0x70, 0x30, 0xDD, 0xC9, 0x06, 0xA7, 0x21, 0xA0, 0x13,
+ 0x77, 0x5E, 0x89, 0xE8, 0x12, 0x3B, 0x19, 0x17, 0x87, 0xA2, 0xD4, 0xD0, 0x19, 0x82, 0x51, 0x31,
+ 0x81, 0x48, 0x0E, 0xD0, 0xA2, 0x5F, 0x89, 0x63, 0x45, 0x56, 0x4B, 0x6A, 0xB1, 0x9D, 0xB3, 0x81,
+ 0x83, 0x41, 0x30, 0x58, 0x80, 0x82, 0x39, 0xB3, 0x1D, 0x32, 0x3E, 0xA9, 0x12, 0x1A, 0x6F, 0xC2,
+ 0x3A, 0x0D, 0x6B, 0x11, 0xAF, 0xE2, 0x62, 0x95, 0x88, 0x89, 0x41, 0x91, 0x30, 0x85, 0xD2, 0x05,
+ 0x08, 0x53, 0x54, 0x51, 0x46, 0x0A, 0x46, 0xDC, 0x44, 0x9B, 0x76, 0xB8, 0xCE, 0x68, 0x1D, 0x2C,
+ 0x56, 0xA2, 0x03, 0x45, 0x11, 0x69, 0xD4, 0xC6, 0x08, 0xED, 0xA4, 0xF1, 0x4C, 0x4A, 0x17, 0xC3,
+ 0x21, 0x37, 0x4C, 0x29, 0x46, 0x75, 0x79, 0xDF, 0x0C, 0x23, 0xC0, 0xC9, 0xF1, 0xF6, 0xF6, 0xEE,
+ 0xEC, 0x66, 0xFC, 0x22, 0xEA, 0x44, 0x89, 0x66, 0x91, 0xEC, 0x63, 0xFA, 0x52, 0xB0, 0x8A, 0xE4,
+ 0x3C, 0x92, 0x37, 0xDA, 0xED, 0x36, 0xDB, 0x71, 0x65, 0x71, 0x27, 0x9F, 0x83, 0x14, 0x18, 0x78,
+ 0x4C, 0x04, 0xC2, 0x01, 0x3D, 0xC7, 0x7A, 0x43, 0xE9, 0xEE, 0xF1, 0xAD, 0x94, 0xFC, 0xC1, 0xB4,
+ 0xBE, 0x86, 0x46, 0x7B, 0xBA, 0x77, 0x23, 0x23, 0x9E, 0x1E, 0x89, 0x97, 0x31, 0xD9, 0x24, 0xAC,
+ 0x18, 0xF4, 0xDA, 0x1F, 0xC1, 0x0D, 0x13, 0xBA, 0x38, 0x81, 0x65, 0x70, 0x6F, 0x17, 0x94, 0x1B,
+ 0xCB, 0x01, 0xDC, 0x72, 0xB6, 0x4F, 0x2C, 0x21, 0xD0, 0x2E, 0x40, 0x26, 0x99, 0xE1, 0xB6, 0x3E,
+ 0x8D, 0x92, 0x69, 0x0A, 0x52, 0xF1, 0x0A, 0xF1, 0x08, 0xE9, 0x4E, 0x56, 0x7B, 0x51, 0x69, 0x65,
+ 0xD5, 0xD6, 0x27, 0x68, 0xBB, 0x9E, 0xFB, 0x30, 0x1A, 0xD1, 0x7E, 0x89, 0x7B, 0x70, 0x62, 0x06,
+ 0xD3, 0xE6, 0x48, 0x95, 0x67, 0xD8, 0xE8, 0x0B, 0x3F, 0x24, 0xE8, 0x89, 0x31, 0xF3, 0xB9, 0x4F,
+ 0xF2, 0xB0, 0x1E, 0xCC, 0x8C, 0x0D, 0x2A, 0x76, 0x13, 0x99, 0x57, 0x24, 0x48, 0x37, 0x4E, 0x14,
+ 0x02, 0x4C, 0x78, 0xFF, 0xBC, 0x1B, 0x3B, 0xD7, 0xF3, 0xDD, 0x5B, 0x4D, 0x34, 0xB1, 0x05, 0x3E,
+ 0x81, 0x36, 0x06, 0xD3, 0x5C, 0xB9, 0x4E, 0xF3, 0xE7, 0xA0, 0x8A, 0xCC, 0x6E, 0xE3, 0x23, 0xA1,
+ 0xC9, 0xED, 0x1A, 0xA3, 0x04, 0x68, 0x5B, 0x2F, 0xF1, 0xD6, 0xFD, 0x97, 0x8B, 0xF3, 0x7C, 0x66,
+ 0xB3, 0x25, 0x5D, 0x50, 0xC6, 0xB8, 0x77, 0x3B, 0x50, 0x99, 0x3E, 0x54, 0x84, 0xA9, 0x9C, 0x65,
+ 0x8C, 0x03, 0x63, 0x6E, 0x0E, 0x87, 0xDE, 0x90, 0x5C, 0xF3, 0xA7, 0xBD, 0x9F, 0xF7, 0x7E, 0x41,
+ 0x28, 0x71, 0x7F, 0x0B, 0xB3, 0xDD, 0xCD, 0xB6, 0xF3, 0xE6, 0x49, 0xE0, 0xB6, 0x28, 0x0A, 0x48,
+ 0x24, 0x6C, 0xDB, 0xCF, 0x8B, 0xE5, 0x69, 0x36, 0x72, 0x21, 0x8B, 0x50, 0x4D, 0xA2, 0xAA, 0x38,
+ 0x19, 0xFD, 0x74, 0x9C, 0xFF, 0x42, 0x7E, 0x36, 0xAC, 0xA6, 0xAA, 0x57, 0xEF, 0xF2, 0x2E, 0xA7,
+ 0x90, 0x74, 0x1B, 0x5B, 0x70, 0xCE, 0xEE, 0xDD, 0x54, 0xBB, 0x79, 0xE4, 0xD7, 0xE7, 0x8D, 0x43,
+ 0x3F, 0xE7, 0xC3, 0xA1, 0x9F, 0x9F, 0x0D, 0x0E, 0xFD, 0x5C, 0x12, 0x87, 0x7E, 0x7E, 0xC1, 0xA1,
+ 0x49, 0x38, 0xF4, 0x4B, 0x3E, 0x1C, 0xFA, 0xE5, 0xD9, 0xE0, 0xD0, 0x2F, 0x25, 0x71, 0xE8, 0x97,
+ 0x17, 0x1C, 0x4A, 0x3B, 0x23, 0x83, 0xFC, 0x84, 0xD2, 0xB9, 0x94, 0xD3, 0xF3, 0x60, 0x51, 0xBE,
+ 0x82, 0x06, 0x6B, 0x45, 0xA4, 0x59, 0x6E, 0xC3, 0xF1, 0x35, 0x17, 0x42, 0xA4, 0x78, 0xCB, 0x67,
+ 0x81, 0x4A, 0x72, 0x17, 0xCF, 0x42, 0x27, 0x9A, 0x6F, 0x78, 0xAD, 0x11, 0x93, 0xA2, 0xD7, 0x9E,
+ 0x5C, 0x96, 0xB2, 0xBE, 0xA1, 0x91, 0xA0, 0x97, 0x90, 0x8E, 0x95, 0x55, 0x83, 0xB2, 0x91, 0x80,
+ 0x9C, 0xFC, 0x5B, 0x00, 0xFB, 0x02, 0xD3, 0xD7, 0x1D, 0xF7, 0x69, 0xCF, 0x45, 0xC7, 0x59, 0x65,
+ 0x00, 0x19, 0x93, 0xA8, 0xB7, 0x5B, 0x8E, 0xCE, 0x9F, 0xA0, 0x75, 0xB9, 0xDC, 0xA7, 0x88, 0xFA,
+ 0x94, 0x26, 0x9B, 0x3B, 0xA2, 0xCD, 0x49, 0x75, 0x51, 0xBF, 0xA8, 0x37, 0x8F, 0xE5, 0x4E, 0x4D,
+ 0xAD, 0xE4, 0x9B, 0x3D, 0x66, 0x3C, 0x37, 0xA9, 0xCA, 0xF8, 0x79, 0x96, 0x2E, 0x3E, 0x8B, 0x73,
+ 0x9A, 0xF4, 0x9D, 0x6F, 0xFE, 0x4E, 0x89, 0x36, 0x73, 0xE3, 0x46, 0xE4, 0x9E, 0x12, 0xD6, 0x8B,
+ 0xDE, 0x3F, 0xAC, 0xE7, 0x73, 0x4E, 0x89, 0x26, 0x9D, 0xCA, 0x51, 0x89, 0x9A, 0xAB, 0x34, 0x4F,
+ 0x9D, 0xB0, 0x78, 0x31, 0x5B, 0x16, 0xAF, 0x43, 0xCB, 0x12, 0x25, 0x64, 0xF3, 0xA4, 0xC2, 0x9C,
+ 0x32, 0x64, 0x73, 0x34, 0xE4, 0xE9, 0x8A, 0x86, 0xDC, 0xA7, 0x21, 0xCF, 0x56, 0x34, 0x1A, 0xA6,
+ 0x5E, 0xC5, 0x83, 0xEB, 0x6E, 0xA0, 0x89, 0x2B, 0xFF, 0xCE, 0x71, 0xAE, 0x1D, 0xBB, 0x7F, 0xE7,
+ 0x9C, 0xF2, 0x73, 0x89, 0x81, 0xC0, 0x47, 0x26, 0x4E, 0xA4, 0x50, 0x12, 0xE7, 0xD2, 0x93, 0x9E,
+ 0x9D, 0x6F, 0x74, 0x53, 0x65, 0x90, 0xB1, 0x4B, 0xA2, 0x05, 0x9A, 0x90, 0x34, 0xC3, 0x48, 0x4A,
+ 0x26, 0x8B, 0xB5, 0x1B, 0xA9, 0xEE, 0xD1, 0x58, 0x74, 0x62, 0x18, 0x05, 0xAD, 0x43, 0x53, 0x5C,
+ 0xCB, 0xB4, 0xA5, 0xCE, 0x3A, 0xEC, 0x3F, 0xB4, 0x71, 0x2D, 0x6A, 0xDE, 0x06, 0x20, 0xA0, 0xE0,
+ 0xCB, 0x9C, 0xF9, 0x68, 0x04, 0x9C, 0xFB, 0x39, 0x7D, 0xDA, 0x14, 0x9B, 0x1C, 0x20, 0x00, 0xC8,
+ 0x12, 0xBB, 0xB2, 0x52, 0x86, 0x66, 0x50, 0xA4, 0xCE, 0x2E, 0x99, 0xE0, 0xA2, 0x52, 0x45, 0x1E,
+ 0xD7, 0xE1, 0xB4, 0x88, 0x49, 0x12, 0xF4, 0xB6, 0x5C, 0x14, 0x33, 0xC5, 0x33, 0xB6, 0xCC, 0x2D,
+ 0xE2, 0x26, 0x7B, 0xF1, 0x97, 0x0B, 0x9F, 0xB8, 0x33, 0xF5, 0x6E, 0xA1, 0xB1, 0xFE, 0xBB, 0x85,
+ 0x4F, 0x94, 0x75, 0x4B, 0xDD, 0xB1, 0x2D, 0xF6, 0x4A, 0xA1, 0xAF, 0x7A, 0x2D, 0x79, 0xEF, 0xB3,
+ 0xC5, 0xB7, 0x0A, 0x9F, 0xA2, 0x95, 0x8F, 0x6E, 0x16, 0x72, 0x53, 0x5E, 0x1E, 0xCE, 0x96, 0xB8,
+ 0x59, 0xC8, 0x18, 0x2D, 0xBA, 0x5D, 0xB8, 0xBE, 0x1E, 0xE4, 0x1E, 0x78, 0xE3, 0x2F, 0x21, 0x60,
+ 0x31, 0xE9, 0x7B, 0x07, 0x7C, 0xF4, 0x72, 0xEB, 0x30, 0x9F, 0x7A, 0x1D, 0xE3, 0x0F, 0xD7, 0x9A,
+ 0x20, 0xFD, 0xE7, 0x55, 0x11, 0x44, 0xCD, 0x20, 0xAD, 0x49, 0x05, 0x29, 0xA1, 0x7F, 0x53, 0x04,
+ 0x46, 0x11, 0xFB, 0x4C, 0xD9, 0xEC, 0xB6, 0x65, 0x0C, 0x2F, 0x09, 0x18, 0x14, 0xB7, 0xE0, 0xA9,
+ 0x96, 0xCF, 0xC7, 0xF0, 0x12, 0xC7, 0x0C, 0xD4, 0x47, 0x96, 0x8A, 0x1A, 0x30, 0xC0, 0xE6, 0xE3,
+ 0x46, 0x08, 0x85, 0x12, 0xC8, 0xA1, 0x9A, 0x3E, 0x1B, 0x0B, 0xAF, 0x94, 0xC8, 0x05, 0x49, 0x9E,
+ 0x97, 0x58, 0xCF, 0x40, 0x64, 0xDA, 0x67, 0x8B, 0xD9, 0x7C, 0x27, 0x85, 0x1F, 0x83, 0x2C, 0x2E,
+ 0x47, 0x62, 0x72, 0xA8, 0xFC, 0x48, 0x92, 0xB4, 0xF4, 0x24, 0x27, 0x3C, 0xDD, 0xD6, 0xB3, 0xAD,
+ 0xF7, 0xC7, 0x85, 0x1B, 0x67, 0x12, 0xFC, 0x89, 0x25, 0x59, 0x75, 0x65, 0x36, 0x31, 0x8F, 0x67,
+ 0xF5, 0xF8, 0x7C, 0x72, 0x91, 0xFB, 0x34, 0xBE, 0xD1, 0xBC, 0x1F, 0x7B, 0x30, 0x1A, 0x3A, 0x50,
+ 0x96, 0x3B, 0xC2, 0x17, 0x23, 0x75, 0xC4, 0x24, 0x8F, 0xCF, 0x1C, 0x9B, 0xB2, 0x13, 0x2A, 0x33,
+ 0x78, 0x61, 0x59, 0xA6, 0xEB, 0x3B, 0xA6, 0x11, 0x55, 0x01, 0x83, 0x09, 0x87, 0xC5, 0xC2, 0x94,
+ 0x80, 0x12, 0x49, 0x5E, 0xA8, 0x6F, 0x69, 0x0A, 0x72, 0xF1, 0xF7, 0xF1, 0xF9, 0xD0, 0xB1, 0x03,
+ 0x2A, 0x15, 0xA2, 0xFC, 0xF2, 0x71, 0x8B, 0x6C, 0xA3, 0x88, 0xE0, 0xB2, 0x74, 0xE1, 0xA5, 0x88,
+ 0x00, 0x93, 0xDB, 0xE4, 0x3B, 0x93, 0x5D, 0x1F, 0x14, 0x91, 0xDD, 0x8A, 0x5F, 0xC8, 0x45, 0x98,
+ 0x9C, 0x77, 0x4D, 0xF9, 0x6E, 0x84, 0x72, 0xBE, 0xE6, 0x8E, 0x4F, 0x64, 0xE9, 0xA5, 0x04, 0xE7,
+ 0xE2, 0x24, 0x79, 0x4B, 0xB8, 0x1F, 0x14, 0xB0, 0x22, 0x4F, 0x2E, 0xE5, 0x8E, 0xC4, 0x91, 0xAC,
+ 0xE3, 0xDE, 0xA8, 0xC4, 0xED, 0x5F, 0x58, 0x74, 0x08, 0xB8, 0x09, 0x2A, 0x34, 0x45, 0xF8, 0x18,
+ 0x45, 0x5B, 0x69, 0x96, 0x0E, 0x4D, 0x91, 0xBF, 0x60, 0x1A, 0x33, 0x99, 0x03, 0x9E, 0x33, 0x78,
+ 0x44, 0x3C, 0xA7, 0xC4, 0x09, 0xB2, 0x0C, 0x2D, 0xE7, 0xAA, 0x27, 0xF3, 0x34, 0x70, 0x63, 0x37,
+ 0x9A, 0x9A, 0xCC, 0xF4, 0xAF, 0x26, 0x4C, 0x01, 0x46, 0xFF, 0xE6, 0x07, 0x5D, 0xE1, 0x69, 0x94,
+ 0x45, 0xFF, 0xDF, 0x64, 0x8D, 0x25, 0xE5, 0x31, 0x7B, 0x02, 0xBF, 0x6D, 0x5B, 0x63, 0x8A, 0x67,
+ 0x61, 0x6C, 0xCB, 0xC9, 0x6D, 0x7B, 0x94, 0xC7, 0x25, 0xE2, 0x46, 0x51, 0xCC, 0x36, 0x74, 0x87,
+ 0x5F, 0x50, 0x8D, 0x98, 0xD0, 0x6D, 0x76, 0xC4, 0xCF, 0x06, 0xD0, 0xC3, 0x93, 0x2C, 0x26, 0x28,
+ 0x03, 0x87, 0x54, 0x35, 0x43, 0x9C, 0x16, 0xF9, 0xB6, 0x6A, 0xEA, 0xEE, 0xCA, 0x75, 0x80, 0xDF,
+ 0x0C, 0x12, 0xA3, 0xEE, 0x52, 0x10, 0x4F, 0x60, 0xCB, 0x17, 0xA8, 0x49, 0x54, 0xA4, 0x26, 0x5E,
+ 0xD0, 0x30, 0x5A, 0x27, 0x6D, 0x39, 0x05, 0xF5, 0x58, 0x0F, 0xDA, 0x93, 0xAF, 0x0C, 0x58, 0xAA,
+ 0xC2, 0xC0, 0x00, 0xC3, 0xF9, 0x26, 0x5C, 0xFC, 0x31, 0xAA, 0x8A, 0xB4, 0x85, 0x4A, 0xDF, 0xDC,
+ 0xA9, 0x2E, 0xB6, 0x9F, 0xC0, 0x9B, 0x13, 0x08, 0x9C, 0xE5, 0xCA, 0x90, 0x94, 0x4D, 0xE8, 0x16,
+ 0x74, 0x71, 0xD2, 0x3E, 0x4B, 0xD3, 0x39, 0x3C, 0xDA, 0x50, 0x32, 0x87, 0x99, 0x65, 0x50, 0xB9,
+ 0x66, 0x18, 0x79, 0x09, 0x5C, 0x51, 0x48, 0x5E, 0xF2, 0x7E, 0x21, 0xBE, 0x22, 0xC4, 0xB7, 0x22,
+ 0xCD, 0x5A, 0x93, 0x5B, 0x2B, 0x77, 0x36, 0x87, 0xA0, 0x3D, 0x9E, 0x7B, 0x51, 0x21, 0x47, 0xBE,
+ 0xE2, 0xDF, 0x34, 0xCC, 0xCE, 0x30, 0xAF, 0x9C, 0xBD, 0x3C, 0x62, 0x38, 0x37, 0x7D, 0x41, 0xE5,
+ 0xEF, 0xA2, 0x53, 0x8A, 0xE2, 0x42, 0x94, 0xBC, 0xAC, 0xA0, 0x12, 0xA2, 0xA1, 0x14, 0x9F, 0x13,
+ 0x22, 0x73, 0x74, 0x58, 0x05, 0x7E, 0x40, 0x39, 0xC7, 0x28, 0xE5, 0x8A, 0x23, 0xA8, 0x26, 0x94,
+ 0xDD, 0x97, 0xA1, 0xB2, 0x32, 0xBA, 0xD7, 0xF4, 0x87, 0xF2, 0xA4, 0x42, 0xBA, 0xD1, 0xEC, 0x27,
+ 0x06, 0xB8, 0xC6, 0x29, 0x2F, 0x97, 0x5F, 0x63, 0x27, 0x43, 0x4C, 0xA9, 0x47, 0x24, 0x60, 0x28,
+ 0xC4, 0x47, 0xEA, 0xE8, 0xF2, 0x1E, 0x26, 0x5E, 0x81, 0x39, 0x7A, 0x14, 0x30, 0x22, 0x4D, 0x8D,
+ 0x38, 0xC5, 0xED, 0xF5, 0xC9, 0x28, 0x6F, 0x4C, 0x39, 0x5C, 0x8A, 0xAB, 0x5C, 0x12, 0xF5, 0x0B,
+ 0x99, 0x53, 0x12, 0x4D, 0x9F, 0x9B, 0xB1, 0x4D, 0x2D, 0xEE, 0x36, 0x0C, 0x13, 0x9A, 0x94, 0x96,
+ 0x55, 0xB1, 0x86, 0xF9, 0x38, 0x42, 0x34, 0x8A, 0x4C, 0xB0, 0xB9, 0x09, 0xAC, 0x01, 0x8F, 0x36,
+ 0xA5, 0x55, 0x1B, 0x45, 0xB8, 0x44, 0xF8, 0xD1, 0x1D, 0xE0, 0x3B, 0x3A, 0x1D, 0x9E, 0xE3, 0x7C,
+ 0x02, 0x08, 0xCE, 0x16, 0xA3, 0xC8, 0x78, 0xDF, 0x34, 0xC6, 0xFA, 0x53, 0x25, 0xBD, 0x63, 0x07,
+ 0xB3, 0x2A, 0x27, 0x15, 0xBE, 0x00, 0xDF, 0x98, 0x5D, 0x4F, 0xC9, 0xB4, 0xC5, 0x39, 0x88, 0x3A,
+ 0x56, 0xF1, 0x0C, 0xF7, 0x8F, 0xD9, 0x25, 0x80, 0x4B, 0xEC, 0xBC, 0x6D, 0xD4, 0xDE, 0x01, 0xEC,
+ 0xE5, 0x27, 0x76, 0x5D, 0x3D, 0xC5, 0x49, 0xEC, 0x1C, 0xBD, 0xC7, 0x67, 0x77, 0x5F, 0xDB, 0x8D,
+ 0xFA, 0xD1, 0xCE, 0x61, 0xB3, 0xF6, 0xFE, 0xCD, 0xB3, 0xF4, 0x08, 0x5B, 0x1F, 0xF3, 0x49, 0x11,
+ 0x59, 0x19, 0x26, 0x94, 0xEC, 0xE2, 0x59, 0x5A, 0xFE, 0x01, 0x9F, 0x7F, 0x45, 0xD1, 0xBA, 0x8C,
+ 0x78, 0xA2, 0x24, 0xDD, 0x9D, 0xF5, 0xF3, 0x95, 0xE9, 0x26, 0xBD, 0x50, 0xC6, 0x6E, 0x13, 0xF7,
+ 0x38, 0x4B, 0x71, 0x0F, 0x62, 0x15, 0xC1, 0x30, 0xCD, 0x29, 0xA4, 0xEE, 0xBB, 0x9B, 0x60, 0x39,
+ 0x32, 0x8B, 0x19, 0x89, 0x2C, 0xF2, 0x6B, 0x92, 0xBC, 0xD1, 0x11, 0xD4, 0x63, 0xDC, 0x90, 0xF5,
+ 0x85, 0x77, 0x99, 0x3F, 0x40, 0xFD, 0x18, 0x53, 0x36, 0x22, 0x51, 0xA2, 0x25, 0x9A, 0x42, 0x64,
+ 0x41, 0xD6, 0xAE, 0xBD, 0x90, 0xF8, 0x02, 0x2D, 0x7F, 0x11, 0xEA, 0x86, 0x39, 0xEF, 0x8A, 0x5F,
+ 0xDB, 0xA8, 0x2E, 0x56, 0x46, 0xD9, 0xE4, 0x7A, 0xB1, 0x1A, 0xEA, 0x0E, 0xBD, 0x5E, 0x43, 0xFF,
+ 0x81, 0x4A, 0x99, 0x6B, 0x3B, 0xF2, 0xB6, 0x5F, 0x33, 0x6D, 0x7F, 0x02, 0x15, 0x57, 0x86, 0x83,
+ 0x53, 0xD6, 0x18, 0xA6, 0xB1, 0x70, 0x69, 0xE1, 0x41, 0x2C, 0xF5, 0x6A, 0x8C, 0x37, 0x57, 0xE2,
+ 0x42, 0x94, 0x96, 0x91, 0x5C, 0xB4, 0xE3, 0x6E, 0xD8, 0xE8, 0xCE, 0xFD, 0x07, 0xA4, 0xC2, 0xA3,
+ 0x25, 0xC6, 0x31, 0x44, 0x08, 0x56, 0x2A, 0x96, 0x21, 0x6C, 0xFD, 0x12, 0xCF, 0xB0, 0x74, 0x42,
+ 0x5A, 0x7C, 0x3C, 0xC3, 0xB5, 0x26, 0xF6, 0xF0, 0xE6, 0x1B, 0x2F, 0xF8, 0x5E, 0xC2, 0x1A, 0xD6,
+ 0x14, 0xD6, 0x10, 0x52, 0x50, 0xCE, 0xD0, 0x86, 0xDC, 0xDB, 0x13, 0xDF, 0xDC, 0x85, 0x47, 0x42,
+ 0x8C, 0x4E, 0xA5, 0xEC, 0x68, 0x88, 0xD1, 0xF7, 0x1B, 0x16, 0x11, 0x71, 0x50, 0xAF, 0xD5, 0xDF,
+ 0xD7, 0x9B, 0xEF, 0x0F, 0xDE, 0xBE, 0x67, 0xD5, 0x46, 0xFD, 0xB0, 0xD6, 0x78, 0x77, 0x58, 0x3F,
+ 0x7C, 0x7B, 0xD4, 0x60, 0x8D, 0xC3, 0x23, 0xF8, 0xEE, 0xDD, 0xFB, 0xC5, 0x86, 0x43, 0xAC, 0x60,
+ 0xBC, 0x30, 0x16, 0x62, 0x05, 0x43, 0xE5, 0x08, 0x84, 0x98, 0x38, 0x8B, 0x42, 0xF7, 0xE8, 0xE5,
+ 0x66, 0xFC, 0x12, 0x05, 0x31, 0x21, 0x0A, 0x62, 0x5C, 0x88, 0x5C, 0x78, 0x24, 0x44, 0x38, 0xC4,
+ 0x12, 0xA2, 0x21, 0x96, 0x3D, 0xFB, 0xF8, 0x18, 0xCB, 0x89, 0x8A, 0x58, 0xF6, 0x0A, 0x92, 0xA3,
+ 0x6C, 0x7D, 0x74, 0x44, 0xFC, 0xE8, 0xFA, 0xC3, 0x04, 0x49, 0x2C, 0x25, 0xC5, 0xDA, 0xD2, 0x6B,
+ 0x0B, 0x77, 0xB8, 0x77, 0x8F, 0x35, 0xFF, 0x12, 0xB5, 0x85, 0xE5, 0xC3, 0x95, 0xD5, 0x16, 0x0E,
+ 0xE7, 0xB0, 0x9E, 0xDA, 0xC2, 0x3E, 0x8D, 0x8E, 0x82, 0x77, 0xE0, 0x12, 0x72, 0x87, 0xB6, 0xF0,
+ 0xD7, 0xFE, 0x58, 0x91, 0x61, 0x4A, 0x9E, 0x3A, 0x56, 0x62, 0xF8, 0x2B, 0xA5, 0x54, 0x93, 0x35,
+ 0x86, 0xA5, 0x34, 0x2F, 0xD0, 0xC1, 0x43, 0xD3, 0x31, 0x41, 0x99, 0x52, 0x88, 0x93, 0xFD, 0x04,
+ 0x32, 0x6F, 0x99, 0x9D, 0x28, 0x6F, 0xFC, 0x7C, 0xEA, 0x0E, 0xCB, 0x0D, 0x5D, 0x7B, 0xDD, 0x61,
+ 0x39, 0x8D, 0x58, 0x49, 0xDC, 0x5F, 0xEB, 0xEB, 0xAE, 0x3C, 0xCC, 0x1A, 0x9B, 0x58, 0xA4, 0x2F,
+ 0x0B, 0x50, 0xC5, 0x8A, 0xA8, 0x65, 0xF4, 0xF0, 0x2C, 0x0A, 0xF5, 0x25, 0xD6, 0x85, 0x85, 0x71,
+ 0xD7, 0x8B, 0x42, 0x38, 0x83, 0xD5, 0xA3, 0x50, 0xBE, 0x1A, 0xC4, 0x59, 0xA0, 0x9A, 0x03, 0x89,
+ 0x64, 0x0F, 0xCF, 0x0F, 0x89, 0xB0, 0xC0, 0xE9, 0x7A, 0x91, 0x88, 0x8A, 0xC9, 0x6E, 0x3E, 0x1F,
+ 0x52, 0x80, 0x9A, 0x03, 0x85, 0x64, 0x0F, 0xCF, 0x13, 0x85, 0xDA, 0xDF, 0x37, 0x00, 0x89, 0xDA,
+ 0xDF, 0xB7, 0x04, 0x8F, 0x08, 0x5A, 0x73, 0x62, 0x12, 0xF6, 0xF1, 0x9C, 0x70, 0x69, 0x54, 0xF0,
+ 0x14, 0x60, 0xB3, 0xE4, 0xCA, 0xC4, 0x1B, 0x8D, 0x26, 0x09, 0x40, 0x94, 0x42, 0x92, 0x78, 0x0F,
+ 0xCF, 0x13, 0x45, 0xE6, 0x66, 0x37, 0xAB, 0xA9, 0x50, 0x3C, 0x07, 0x9E, 0x2D, 0xB1, 0x46, 0x71,
+ 0x36, 0x34, 0xE7, 0xC4, 0xB4, 0x75, 0xB3, 0xA3, 0xA9, 0x37, 0xE9, 0xCB, 0xD4, 0xE1, 0x1A, 0x6B,
+ 0xD7, 0xE1, 0x9A, 0xDB, 0xA1, 0xC3, 0x35, 0xE6, 0xD6, 0xE1, 0x1A, 0xCF, 0x54, 0x87, 0x6B, 0xAC,
+ 0x5D, 0x87, 0x6B, 0x6E, 0x8B, 0x0E, 0xD7, 0x98, 0x5B, 0x87, 0x6B, 0x3C, 0x53, 0x1D, 0xAE, 0xB1,
+ 0x76, 0xF1, 0xBB, 0xB9, 0x1D, 0x3A, 0x5C, 0x63, 0x6E, 0x1D, 0xAE, 0xF1, 0x6C, 0x75, 0xB8, 0xC6,
+ 0x26, 0xE8, 0x70, 0xCD, 0x6D, 0xD1, 0xE1, 0x1A, 0x0B, 0xD0, 0xE1, 0x1A, 0xCF, 0x54, 0x87, 0x6B,
+ 0x2C, 0x5D, 0x87, 0x6B, 0x6E, 0x87, 0x0E, 0xD7, 0x98, 0x5B, 0x87, 0x6B, 0x3C, 0x5B, 0x1D, 0xAE,
+ 0xB1, 0x0D, 0x3A, 0x5C, 0x73, 0x5B, 0x74, 0xB8, 0xC6, 0x02, 0x74, 0xB8, 0xB5, 0xE2, 0x1A, 0x96,
+ 0x81, 0xBD, 0x74, 0x3C, 0xD6, 0x0B, 0x44, 0xE0, 0x71, 0x46, 0xF5, 0x65, 0xFD, 0x69, 0xE5, 0xD8,
+ 0xA2, 0x6B, 0x3E, 0xCD, 0xB2, 0x9C, 0x87, 0xEF, 0xE6, 0xA5, 0xF9, 0x15, 0x56, 0x74, 0x81, 0xF7,
+ 0x9F, 0x36, 0x17, 0x33, 0xEF, 0x71, 0xA7, 0x5F, 0x6B, 0x37, 0x8A, 0x24, 0xDF, 0xCB, 0x7D, 0xD7,
+ 0x4D, 0x90, 0x07, 0xDE, 0x8F, 0x93, 0xFD, 0xE2, 0x88, 0x70, 0xAE, 0x95, 0x16, 0x5E, 0x03, 0xE3,
+ 0x43, 0x8A, 0x14, 0x88, 0xDF, 0x7B, 0xB3, 0x2A, 0x55, 0x24, 0x0D, 0xDF, 0xCC, 0xEF, 0xFD, 0x3B,
+ 0xCF, 0x75, 0x78, 0x0C, 0x41, 0xB2, 0x26, 0x9B, 0x33, 0xEE, 0x7C, 0x61, 0xBE, 0x1F, 0xEA, 0xAA,
+ 0xFC, 0xAB, 0xCD, 0x6E, 0xEF, 0xFE, 0x82, 0xB5, 0xB6, 0x8C, 0x40, 0x17, 0x61, 0xC1, 0xFB, 0x81,
+ 0x86, 0x51, 0x1E, 0x6A, 0x6E, 0x40, 0x72, 0xB6, 0x8D, 0x77, 0xDB, 0xF7, 0xA6, 0x78, 0xDA, 0x65,
+ 0x4F, 0x4E, 0x40, 0x37, 0xE0, 0x41, 0x08, 0x5D, 0xAC, 0xCC, 0xC5, 0x35, 0x8A, 0x05, 0x89, 0x9A,
+ 0x8C, 0x41, 0xBC, 0xFB, 0x24, 0xE3, 0xB7, 0x65, 0xF1, 0x53, 0xD3, 0x67, 0xD2, 0x6B, 0x2C, 0xE3,
+ 0x46, 0xBC, 0x52, 0xC2, 0x15, 0x66, 0x91, 0xBE, 0x23, 0x0B, 0xC8, 0x31, 0x20, 0xBF, 0x9E, 0x58,
+ 0x69, 0x79, 0xD6, 0x35, 0xFB, 0x94, 0x8E, 0x95, 0x17, 0x53, 0xE4, 0x56, 0xD4, 0xE7, 0xE2, 0x66,
+ 0x54, 0xD6, 0x1B, 0xD3, 0x53, 0x52, 0x22, 0x80, 0xA4, 0x0B, 0xD3, 0xD0, 0xA0, 0x5F, 0x4E, 0x20,
+ 0x90, 0xF5, 0x4C, 0xAC, 0x76, 0x3D, 0xDD, 0xB3, 0x22, 0x47, 0xE5, 0xEB, 0x09, 0x55, 0xAE, 0x31,
+ 0x92, 0x5E, 0x7A, 0x0A, 0xDC, 0xF8, 0x7D, 0x9C, 0xE2, 0xF4, 0x91, 0x0A, 0x15, 0xC2, 0x1E, 0xEF,
+ 0x7B, 0xFA, 0xC6, 0x51, 0xED, 0xBA, 0x5B, 0xF2, 0xAF, 0xFE, 0x60, 0xCE, 0x53, 0xCD, 0x7A, 0xB4,
+ 0x97, 0xB3, 0xFC, 0xB7, 0x16, 0xEB, 0xDA, 0x42, 0x0B, 0xC0, 0x98, 0x2A, 0x0F, 0x18, 0xF9, 0xD0,
+ 0x14, 0xB1, 0x8C, 0x05, 0x14, 0xDD, 0xA9, 0x61, 0xF8, 0x86, 0x21, 0x69, 0x0A, 0x29, 0xB2, 0xF1,
+ 0xF9, 0x77, 0x22, 0x46, 0x55, 0xFC, 0xDD, 0x67, 0x8D, 0x7A, 0xFD, 0x70, 0x17, 0x7E, 0xBE, 0x3D,
+ 0xC0, 0x9F, 0xEF, 0xE8, 0xE7, 0x7B, 0xFC, 0xD9, 0x68, 0x1E, 0xC8, 0xE8, 0xAB, 0x7A, 0x2D, 0x6C,
+ 0xD4, 0x68, 0xEE, 0xD7, 0x55, 0x18, 0x97, 0xF2, 0x60, 0x87, 0x33, 0x81, 0x92, 0x2A, 0xE0, 0xB7,
+ 0x94, 0xA6, 0xC2, 0x0F, 0xCB, 0x08, 0x3F, 0xC0, 0xF8, 0xB6, 0x41, 0xFE, 0xC0, 0xE8, 0x0F, 0x26,
+ 0x34, 0xD3, 0x72, 0x3C, 0x95, 0x0C, 0x41, 0xCD, 0x15, 0xE3, 0xC7, 0xF7, 0xE0, 0x5C, 0x91, 0xBB,
+ 0x10, 0xCE, 0x68, 0x54, 0x45, 0x98, 0xEA, 0x06, 0x37, 0xEB, 0xF1, 0x64, 0x0B, 0xB1, 0xDA, 0xCB,
+ 0x8C, 0x4A, 0x2B, 0x23, 0xE7, 0x00, 0x7E, 0xE2, 0x99, 0x86, 0xC1, 0x6D, 0x7C, 0x1F, 0xE7, 0x4A,
+ 0xD9, 0x56, 0xB0, 0xD2, 0x1F, 0x42, 0x00, 0x4B, 0x1A, 0x6E, 0x97, 0x57, 0xCD, 0xD4, 0x5A, 0xE7,
+ 0x45, 0x30, 0x7C, 0x4A, 0xE9, 0xF3, 0x91, 0xD3, 0x36, 0x25, 0x28, 0x11, 0x8E, 0x7B, 0x7C, 0x28,
+ 0x7D, 0xB6, 0x67, 0x9F, 0xE5, 0xC9, 0xA6, 0x5D, 0x07, 0xF0, 0x7D, 0x18, 0xB6, 0x2E, 0xE7, 0x77,
+ 0x99, 0xED, 0x63, 0x19, 0xCB, 0x6B, 0xE2, 0x71, 0x9F, 0x8B, 0x3B, 0x07, 0x11, 0x45, 0x1D, 0x10,
+ 0x94, 0x93, 0xF5, 0x16, 0x1F, 0xC3, 0xAE, 0xE7, 0x3A, 0x21, 0xC2, 0x86, 0xF9, 0x5C, 0x2C, 0xF3,
+ 0xA6, 0x8D, 0x59, 0x1F, 0x30, 0xAE, 0x9D, 0x87, 0xD3, 0x90, 0xC4, 0x10, 0x30, 0x11, 0x40, 0xF2,
+ 0x9D, 0xF7, 0x0E, 0x83, 0x0E, 0x58, 0xD4, 0x03, 0x50, 0x9D, 0xFD, 0xA3, 0x10, 0x68, 0x66, 0xC3,
+ 0x06, 0xF1, 0x76, 0x98, 0x3C, 0x8E, 0x2A, 0xCB, 0xF6, 0xAC, 0xCC, 0xF1, 0x38, 0x6B, 0x10, 0x92,
+ 0x8C, 0xAB, 0xE1, 0x3F, 0x90, 0x52, 0x6C, 0x1F, 0xB8, 0x96, 0x24, 0xB4, 0xD8, 0xF3, 0xF4, 0x11,
+ 0x1E, 0xA7, 0x30, 0x03, 0x34, 0x37, 0xD3, 0x60, 0x7D, 0xCD, 0xCD, 0xF4, 0x76, 0x0B, 0x37, 0x7B,
+ 0x82, 0x3F, 0x71, 0xFA, 0xF4, 0x95, 0xE5, 0xC7, 0x69, 0x1A, 0xA1, 0x5B, 0x5D, 0x9E, 0x33, 0x37,
+ 0x13, 0x34, 0xD9, 0xE7, 0x70, 0x27, 0xD1, 0x79, 0xA1, 0x63, 0x36, 0xD1, 0x34, 0x73, 0xC8, 0x54,
+ 0x54, 0x8C, 0xED, 0xD8, 0x93, 0xB6, 0x3E, 0x01, 0xEA, 0x40, 0x26, 0x02, 0xC0, 0xC3, 0x38, 0xB6,
+ 0x7E, 0x3C, 0x7C, 0x2B, 0xF3, 0x9C, 0xCE, 0xD9, 0x78, 0x9D, 0x85, 0x1A, 0x33, 0xF9, 0x6F, 0x62,
+ 0xE9, 0x19, 0x23, 0x4D, 0x66, 0xB9, 0xAD, 0xF5, 0x38, 0xFD, 0xCA, 0x09, 0x5F, 0x06, 0xA8, 0xE6,
+ 0x55, 0x5A, 0x0A, 0xDA, 0xF2, 0xE3, 0x2A, 0xFC, 0x7D, 0x93, 0xC3, 0xE7, 0x90, 0x8A, 0x40, 0xF0,
+ 0x78, 0xBB, 0x30, 0x9F, 0x5F, 0x0E, 0x5C, 0xB3, 0xEB, 0xE0, 0x3E, 0x5C, 0xDD, 0x7C, 0x43, 0x9E,
+ 0x37, 0xD4, 0x40, 0x0B, 0x87, 0xFF, 0x47, 0x75, 0x89, 0x49, 0x58, 0xA0, 0xEC, 0x4E, 0x98, 0xD8,
+ 0xC2, 0xD6, 0xEE, 0x41, 0x52, 0xA1, 0x7C, 0x4C, 0xEC, 0x2B, 0x86, 0x14, 0x6A, 0xAE, 0x0B, 0x5C,
+ 0x97, 0x5C, 0xA5, 0xD1, 0x17, 0x38, 0x00, 0xF6, 0xEE, 0x08, 0x0C, 0x90, 0x45, 0x35, 0x07, 0x84,
+ 0x17, 0xC2, 0x57, 0x10, 0x5A, 0xBE, 0x63, 0x9D, 0x64, 0x83, 0xF7, 0x41, 0x0A, 0xE2, 0x51, 0xE7,
+ 0x9A, 0x85, 0x3E, 0xC1, 0x54, 0xD1, 0x1B, 0xE5, 0x11, 0xA0, 0x82, 0x6A, 0xAC, 0x07, 0xD5, 0xB5,
+ 0xEA, 0xA0, 0xA8, 0xE2, 0xB3, 0x0E, 0x99, 0x65, 0xC5, 0x08, 0x8C, 0xB0, 0x82, 0x7D, 0xA3, 0x9C,
+ 0x20, 0x27, 0x96, 0xD9, 0xB7, 0x11, 0x09, 0x2A, 0xAD, 0x13, 0x78, 0x4C, 0xFB, 0x49, 0x76, 0x15,
+ 0x16, 0x7D, 0xB3, 0x5C, 0x74, 0x9E, 0x30, 0x99, 0x15, 0xC9, 0xF9, 0xB8, 0x66, 0xCC, 0x6B, 0xA0,
+ 0x53, 0xDE, 0x15, 0x13, 0x84, 0x5D, 0x61, 0xF6, 0x9E, 0xD8, 0x00, 0x8E, 0x70, 0x99, 0x98, 0x89,
+ 0xF4, 0x75, 0x46, 0x25, 0xBC, 0x03, 0xCA, 0xC9, 0x02, 0xF8, 0x36, 0x2A, 0x8E, 0xAD, 0xC2, 0xB4,
+ 0xEF, 0x39, 0xA8, 0xF3, 0x16, 0xFF, 0xC7, 0xDF, 0xFF, 0xDB, 0x67, 0x3D, 0x0F, 0x03, 0xBA, 0x41,
+ 0x25, 0x88, 0xDE, 0x8A, 0xE1, 0xA0, 0x0C, 0x0E, 0x30, 0xB6, 0x51, 0x6C, 0xCE, 0x6D, 0x43, 0x8D,
+ 0xDB, 0x4F, 0xCD, 0x61, 0xF0, 0xB3, 0xF6, 0x50, 0xC9, 0x0E, 0xE5, 0x1E, 0x2F, 0xCE, 0xA9, 0x3D,
+ 0xB0, 0x13, 0x1B, 0x0E, 0xDE, 0xE3, 0xFC, 0xE0, 0x99, 0x8A, 0x01, 0x31, 0xA6, 0x85, 0x61, 0x05,
+ 0x3D, 0xAA, 0x8F, 0x8E, 0x18, 0x2E, 0xD3, 0xE1, 0x68, 0x38, 0x16, 0xE9, 0x58, 0x4F, 0xDA, 0x43,
+ 0xAD, 0xA8, 0x6D, 0x68, 0x51, 0xC6, 0x90, 0xD9, 0x9B, 0x37, 0x8B, 0xFC, 0xCA, 0x06, 0xCB, 0x17,
+ 0xBB, 0xD7, 0x56, 0x7B, 0x39, 0xA3, 0x4B, 0x37, 0xF6, 0x6E, 0x59, 0x8B, 0xEC, 0xA2, 0xE2, 0x7C,
+ 0x4A, 0x60, 0x6B, 0xDB, 0x14, 0xFA, 0x20, 0x2F, 0xBE, 0xD2, 0xCB, 0x6B, 0xC2, 0x58, 0x17, 0xC7,
+ 0x7E, 0xC1, 0xD9, 0xD9, 0x38, 0x2B, 0x77, 0x34, 0x2F, 0xD6, 0xD2, 0xDB, 0x5B, 0x88, 0xB7, 0xB7,
+ 0x20, 0x46, 0xE7, 0x45, 0x5B, 0x7C, 0x77, 0x4D, 0x58, 0x0B, 0x1B, 0x63, 0xBD, 0x20, 0xED, 0x6C,
+ 0xA4, 0xA5, 0xED, 0xCC, 0x8B, 0xB3, 0xF8, 0xF2, 0xBA, 0x51, 0x76, 0x71, 0xC2, 0xA9, 0xDF, 0x53,
+ 0x82, 0xFA, 0xF7, 0x01, 0xE7, 0xD6, 0xB9, 0xA9, 0xC2, 0xFD, 0x2A, 0x2D, 0xF5, 0x98, 0xD1, 0x73,
+ 0x76, 0x67, 0xC2, 0x78, 0xD1, 0xB7, 0xAF, 0xA6, 0x1B, 0xA8, 0x84, 0x7C, 0x6B, 0xB9, 0x82, 0xEC,
+ 0xC4, 0x89, 0xAF, 0x57, 0x94, 0x45, 0xF9, 0xF4, 0x81, 0x40, 0x26, 0x10, 0x64, 0x46, 0x38, 0x2F,
+ 0x15, 0x3A, 0x49, 0x1A, 0xDA, 0x80, 0x6B, 0x98, 0x78, 0x28, 0x26, 0xA8, 0x5E, 0x92, 0x9D, 0x62,
+ 0x5B, 0xC5, 0xD4, 0x45, 0x62, 0xE3, 0x99, 0x33, 0xEC, 0x02, 0x31, 0xD1, 0xA6, 0x22, 0xD2, 0xF9,
+ 0x95, 0x96, 0x7A, 0x14, 0xC3, 0x44, 0x7F, 0xD9, 0xA8, 0x95, 0x31, 0x8B, 0x57, 0x0B, 0xE5, 0xDB,
+ 0x98, 0x6A, 0xAA, 0x4B, 0x7C, 0xDB, 0xE3, 0x9A, 0x17, 0xC3, 0x18, 0x3F, 0x7E, 0xF1, 0x08, 0xC8,
+ 0x34, 0x15, 0x25, 0x10, 0xA5, 0x70, 0xEF, 0xA1, 0xDD, 0x36, 0x22, 0x53, 0x39, 0xA3, 0xA6, 0x74,
+ 0xEA, 0x69, 0x73, 0xAF, 0x87, 0x89, 0x62, 0xD6, 0x6D, 0xDB, 0x74, 0xDD, 0x25, 0xD9, 0x35, 0xDB,
+ 0xED, 0x52, 0x36, 0xCD, 0xA8, 0xD9, 0x9C, 0xF6, 0x4C, 0xE9, 0xE7, 0x91, 0x00, 0x72, 0xCA, 0xAA,
+ 0xE9, 0xC6, 0xDE, 0xD8, 0x28, 0xDB, 0x66, 0x04, 0x82, 0x82, 0x76, 0xCD, 0x12, 0x37, 0xD7, 0xE7,
+ 0x14, 0x3B, 0xCF, 0xAE, 0xCE, 0x8F, 0x43, 0x01, 0x61, 0x00, 0x1D, 0x3E, 0xC0, 0x62, 0xAF, 0xCE,
+ 0xA7, 0xE4, 0x54, 0xDA, 0x3B, 0x41, 0x29, 0x61, 0x6A, 0x52, 0xA4, 0x73, 0xCC, 0xBD, 0x0E, 0xD2,
+ 0x9C, 0x69, 0xB1, 0x1F, 0x1C, 0xFE, 0x84, 0xFD, 0x87, 0xA3, 0x24, 0x1A, 0xC6, 0x80, 0xAF, 0x6F,
+ 0x39, 0x9A, 0x01, 0x81, 0x05, 0x4C, 0x19, 0xC9, 0x61, 0x7F, 0x81, 0xD6, 0x13, 0x65, 0x92, 0xB5,
+ 0xE6, 0x71, 0x88, 0xA3, 0xD8, 0x59, 0x94, 0x68, 0xC0, 0x8F, 0x72, 0x3A, 0xC4, 0xBF, 0x9F, 0xCA,
+ 0xCC, 0x62, 0x8D, 0x57, 0x61, 0x0B, 0x9E, 0x38, 0xEF, 0x15, 0xE6, 0x81, 0x50, 0xD9, 0x80, 0x13,
+ 0x54, 0x3A, 0x4A, 0xD6, 0xE0, 0x4B, 0x73, 0x70, 0xCC, 0x08, 0xBC, 0xD5, 0x26, 0x31, 0x55, 0x36,
+ 0xB4, 0xEF, 0x69, 0xEE, 0xC0, 0xD4, 0x6F, 0x79, 0x1F, 0x96, 0x78, 0xEE, 0x39, 0xAE, 0x64, 0x27,
+ 0xB9, 0x74, 0xB6, 0x74, 0x73, 0xAA, 0x6E, 0xAA, 0x9E, 0x30, 0xF9, 0x68, 0xB6, 0xF7, 0x5E, 0x32,
+ 0x39, 0xD9, 0x58, 0x97, 0x99, 0xF3, 0x4C, 0x2A, 0x1E, 0x86, 0x9A, 0x75, 0xAE, 0x2A, 0xA7, 0xC9,
+ 0x14, 0x58, 0xF5, 0x4A, 0xEB, 0x5B, 0x27, 0x5F, 0x5A, 0xAA, 0xF1, 0x3C, 0x64, 0x17, 0xDF, 0x66,
+ 0xB7, 0x9C, 0x9D, 0xC4, 0x6A, 0xB1, 0x68, 0xDC, 0x91, 0xC0, 0x7C, 0x72, 0x02, 0x8F, 0x8D, 0xA0,
+ 0x06, 0xF2, 0x50, 0x9F, 0x3C, 0xA8, 0xC8, 0x3F, 0x43, 0x2A, 0xB7, 0x2A, 0xF1, 0xB7, 0x72, 0xFF,
+ 0x50, 0xA8, 0xDF, 0xF3, 0xF8, 0x6F, 0x01, 0xB7, 0xF5, 0xA7, 0x18, 0x72, 0x7F, 0xEB, 0x6C, 0x12,
+ 0x5E, 0x4B, 0x91, 0xC2, 0x9B, 0x03, 0xE9, 0x5D, 0xB7, 0xC3, 0x05, 0xE6, 0x6C, 0xF1, 0x27, 0x9E,
+ 0x68, 0xF3, 0x71, 0xEB, 0x42, 0x77, 0x18, 0x70, 0x92, 0xDC, 0x72, 0x9B, 0x3F, 0x68, 0x96, 0xBA,
+ 0xBC, 0x80, 0x07, 0x4C, 0x3D, 0xC9, 0x99, 0x0F, 0x71, 0xDE, 0x9B, 0x8B, 0xF8, 0x14, 0x16, 0x64,
+ 0x4C, 0x99, 0x69, 0x1C, 0x51, 0xE8, 0xFA, 0x1D, 0x39, 0x2A, 0x1D, 0xC5, 0x48, 0xC4, 0x0C, 0xD6,
+ 0xEA, 0x3C, 0xB0, 0xE6, 0x3B, 0x86, 0x27, 0x31, 0x5E, 0x4C, 0xD0, 0x01, 0xFD, 0xB4, 0x2B, 0xEF,
+ 0xD6, 0x22, 0xBD, 0x50, 0x13, 0x82, 0x0F, 0x5D, 0x4A, 0x30, 0xAB, 0xFC, 0x0D, 0xF1, 0xCF, 0xD7,
+ 0x9F, 0x9D, 0x21, 0x7F, 0x4D, 0x7E, 0x86, 0xBB, 0xCC, 0xC1, 0x9B, 0x0D, 0x0D, 0xFB, 0xD9, 0xC5,
+ 0xEF, 0x3C, 0x5C, 0x1F, 0x0D, 0x54, 0x98, 0x6B, 0xAF, 0xD1, 0x94, 0xB3, 0x02, 0xE7, 0xF0, 0xB8,
+ 0xBC, 0x29, 0xE5, 0xAE, 0xB6, 0xE7, 0xF4, 0x4C, 0x8B, 0xDF, 0x39, 0x3F, 0xB8, 0xBD, 0x80, 0x78,
+ 0x94, 0xF1, 0x34, 0xC6, 0x4A, 0xBC, 0x53, 0xE3, 0x30, 0x1A, 0xE8, 0x78, 0x4D, 0x2E, 0xAD, 0x57,
+ 0x3D, 0xC9, 0x2A, 0x1D, 0xAF, 0xAF, 0xD9, 0xE6, 0xEF, 0x52, 0x14, 0x1F, 0x68, 0x3E, 0x26, 0x76,
+ 0x72, 0x3C, 0xD7, 0x21, 0x3F, 0x32, 0x60, 0x9A, 0x76, 0xBF, 0x23, 0x40, 0xA7, 0x1C, 0xB2, 0x31,
+ 0xF9, 0x40, 0xD3, 0x75, 0xB4, 0x0F, 0xEE, 0x4A, 0x4F, 0x55, 0x93, 0x84, 0x04, 0x8F, 0x93, 0xE7,
+ 0x2B, 0x52, 0x25, 0xF5, 0xAE, 0x07, 0x3E, 0xA0, 0xAF, 0xCA, 0x09, 0x85, 0xF7, 0x71, 0xB4, 0x70,
+ 0x81, 0x0B, 0x8F, 0xA3, 0x23, 0xA0, 0xF4, 0x13, 0xAB, 0xB2, 0xF0, 0x76, 0xBA, 0xE3, 0x6A, 0xDE,
+ 0x8F, 0xCB, 0xC0, 0x96, 0x10, 0xDA, 0x0E, 0x57, 0xD7, 0x79, 0x22, 0x42, 0xDE, 0x2E, 0xD8, 0x53,
+ 0x7F, 0x06, 0x6A, 0xE7, 0xF6, 0xD6, 0x9F, 0xDE, 0xCF, 0xF2, 0x3D, 0xF6, 0x97, 0xEE, 0xD0, 0x84,
+ 0x81, 0x99, 0xFE, 0x5A, 0x75, 0xFE, 0x45, 0x6B, 0xF9, 0xB8, 0xA0, 0x72, 0x8A, 0x7E, 0xAC, 0xE5,
+ 0x24, 0x15, 0x7E, 0x04, 0xAC, 0x31, 0xDD, 0x1D, 0x1B, 0x6F, 0x94, 0xD2, 0x3E, 0x75, 0x39, 0xE5,
+ 0xF4, 0x76, 0x1A, 0x80, 0xFC, 0x79, 0xB1, 0xF7, 0xB3, 0x81, 0x06, 0x07, 0xB0, 0x15, 0x29, 0x0E,
+ 0x61, 0x7F, 0xB3, 0xD2, 0xBA, 0xC5, 0x8E, 0xA0, 0xA8, 0xAF, 0x53, 0x2D, 0x80, 0xC3, 0x98, 0x0A,
+ 0xD8, 0xC9, 0xB8, 0x68, 0x7C, 0x40, 0x1E, 0xD8, 0xC7, 0xAC, 0xA0, 0x1A, 0x91, 0xEC, 0x33, 0x39,
+ 0x65, 0xF9, 0x28, 0x4B, 0x81, 0x28, 0xA6, 0x3B, 0x1C, 0xBC, 0xAB, 0x83, 0xFA, 0x80, 0x3F, 0x4B,
+ 0x29, 0x10, 0xEF, 0x8F, 0xB0, 0x39, 0xFE, 0x2C, 0xA7, 0x7F, 0xBC, 0x6F, 0x62, 0x7B, 0xFA, 0x55,
+ 0xAA, 0x83, 0xFD, 0x77, 0x07, 0xD8, 0x01, 0xFD, 0x2A, 0xD5, 0xC1, 0xE1, 0x5B, 0x5A, 0x01, 0xFD,
+ 0x2A, 0xB7, 0x84, 0xC6, 0xA1, 0x5C, 0x03, 0xFD, 0x2E, 0xD5, 0x45, 0x73, 0xBF, 0x4E, 0xAB, 0x90,
+ 0xBF, 0x4B, 0x75, 0x71, 0x70, 0x54, 0x97, 0x1B, 0x49, 0xBF, 0xCB, 0x6D, 0x65, 0xB3, 0x21, 0x37,
+ 0x93, 0x7E, 0x6F, 0x9E, 0x52, 0x78, 0x47, 0x1E, 0xFF, 0x40, 0x4B, 0x24, 0xCE, 0xE0, 0x1D, 0x26,
+ 0xCA, 0x17, 0xB7, 0x27, 0xE7, 0x57, 0x5F, 0x19, 0x32, 0xAD, 0xD0, 0x63, 0x5F, 0x07, 0x62, 0xEE,
+ 0x4B, 0x3F, 0x7C, 0x10, 0xB6, 0x75, 0xAC, 0x3F, 0x8A, 0xD5, 0x54, 0x30, 0xF5, 0xE5, 0xC0, 0x41,
+ 0xD1, 0x05, 0x3D, 0x82, 0x90, 0x8C, 0x62, 0x52, 0x0B, 0x6D, 0x7F, 0xD7, 0xF5, 0x9F, 0x97, 0x8E,
+ 0x88, 0xE0, 0x5E, 0x10, 0x7B, 0x4B, 0x75, 0x55, 0x69, 0xDD, 0x04, 0x8F, 0x4C, 0x7D, 0x28, 0xCC,
+ 0xD7, 0xD2, 0x9D, 0x65, 0x4D, 0x76, 0x11, 0xBC, 0x0D, 0x90, 0xF9, 0xCB, 0xCD, 0xC5, 0x49, 0x59,
+ 0xCB, 0x48, 0xBB, 0xDD, 0xD9, 0xBB, 0xB8, 0xC7, 0x40, 0x8C, 0x3B, 0xC0, 0xD2, 0x3E, 0x66, 0x8F,
+ 0x2D, 0x43, 0xDC, 0x72, 0x79, 0xC3, 0xE0, 0x51, 0xAD, 0x0C, 0x40, 0x7E, 0xD5, 0x3C, 0x2B, 0xC7,
+ 0xED, 0x40, 0xBB, 0x3E, 0x3F, 0xDB, 0x3B, 0x3F, 0x39, 0xDB, 0x4C, 0xF2, 0x3C, 0x3F, 0xB9, 0x3B,
+ 0x49, 0xD2, 0xA2, 0x92, 0x30, 0x54, 0x00, 0x8D, 0x61, 0xF6, 0xC8, 0x13, 0x4F, 0xA4, 0x3C, 0x43,
+ 0x71, 0x97, 0x64, 0xD5, 0x35, 0xF5, 0x22, 0xD0, 0xAB, 0xA9, 0x59, 0xEC, 0xEE, 0xA7, 0xBD, 0xDB,
+ 0x9F, 0x90, 0x5E, 0x2D, 0x95, 0xBC, 0x3E, 0x5E, 0xFE, 0xA8, 0xC6, 0x70, 0x83, 0xD4, 0xD6, 0x24,
+ 0x5A, 0xBB, 0x01, 0x48, 0x47, 0xCC, 0xE5, 0xD8, 0x0F, 0xCC, 0xC0, 0x60, 0x4E, 0x20, 0x50, 0xE0,
+ 0x46, 0xB5, 0xE7, 0xD0, 0xF6, 0x95, 0xC7, 0x2A, 0x30, 0x11, 0x11, 0x36, 0x46, 0x79, 0x1C, 0xE6,
+ 0x01, 0xFB, 0x12, 0x75, 0x84, 0xEA, 0x93, 0x4D, 0x75, 0x60, 0x94, 0x9F, 0x6B, 0xE7, 0xFC, 0x64,
+ 0xAF, 0x73, 0x76, 0x2D, 0xB3, 0xDD, 0xEA, 0x72, 0x2F, 0x6B, 0xF2, 0xE6, 0x71, 0xEF, 0xDC, 0xF4,
+ 0xE4, 0x3D, 0x78, 0x7C, 0x1A, 0x09, 0x57, 0x5A, 0x01, 0xA2, 0xB7, 0xA6, 0xD3, 0xD5, 0xAE, 0xDA,
+ 0xC2, 0xC4, 0xBB, 0x4A, 0xB9, 0xEA, 0x99, 0xDE, 0x10, 0xAF, 0x08, 0x26, 0x79, 0xCE, 0x22, 0xA0,
+ 0xB6, 0xCE, 0x3E, 0x1B, 0x52, 0x36, 0x8A, 0x2C, 0x8B, 0xE0, 0x41, 0x52, 0xC2, 0x3A, 0xC7, 0x6D,
+ 0x98, 0x53, 0xC0, 0x4A, 0xF4, 0x38, 0x36, 0xD7, 0x17, 0xF1, 0xEA, 0x45, 0xBC, 0xFA, 0xE3, 0x88,
+ 0x57, 0x57, 0xB6, 0x64, 0xC3, 0x18, 0x4D, 0xB8, 0xAB, 0xC2, 0x2B, 0xD3, 0xC2, 0xD6, 0x38, 0x7F,
+ 0x9F, 0x28, 0x6B, 0xC5, 0x18, 0x58, 0x8C, 0x7F, 0x49, 0xF8, 0x6D, 0x98, 0xA0, 0x95, 0x8B, 0x89,
+ 0x61, 0x15, 0x60, 0x0F, 0x8E, 0x81, 0x36, 0x9E, 0x2D, 0x8B, 0x4A, 0x03, 0xBE, 0x58, 0x43, 0xBC,
+ 0xBC, 0x82, 0xBC, 0x88, 0x4F, 0x34, 0xBA, 0x30, 0x0D, 0x9F, 0x32, 0x7A, 0xBC, 0x7C, 0x8B, 0x7C,
+ 0xE6, 0x5C, 0x56, 0x6C, 0x96, 0xFF, 0xC4, 0x6D, 0x4E, 0xE8, 0xAB, 0xC9, 0x55, 0xA3, 0x63, 0x08,
+ 0xEB, 0x28, 0x89, 0x00, 0x8E, 0x67, 0x78, 0x6F, 0xA8, 0x82, 0x88, 0x87, 0x01, 0xE0, 0x6E, 0x8F,
+ 0x72, 0xEA, 0xEC, 0xCA, 0xF8, 0x5E, 0xD8, 0x6F, 0x8C, 0xC2, 0x8D, 0xA2, 0x5B, 0x6A, 0xEC, 0x96,
+ 0xFF, 0x16, 0x98, 0x1E, 0xF7, 0x47, 0x85, 0xD2, 0xD3, 0x01, 0xFD, 0xCA, 0x14, 0x2F, 0x3B, 0x68,
+ 0xD4, 0xEB, 0xBB, 0xF5, 0x7A, 0x3D, 0xF0, 0x95, 0x38, 0xA2, 0xEA, 0x4C, 0xA3, 0x0C, 0xF2, 0x3E,
+ 0xFA, 0x4A, 0xCE, 0x80, 0x8B, 0x07, 0xCE, 0x6D, 0xF9, 0x9E, 0x5F, 0xDB, 0x6A, 0x4B, 0xFE, 0x04,
+ 0x52, 0x39, 0xE7, 0x18, 0x1E, 0xED, 0x17, 0xA6, 0x98, 0xB9, 0x6E, 0x08, 0xD2, 0xF2, 0x43, 0x62,
+ 0x5E, 0x77, 0x00, 0xF9, 0x53, 0x09, 0x78, 0xFA, 0xFC, 0x6B, 0xE0, 0xE7, 0xB3, 0xB9, 0x97, 0xB8,
+ 0x2B, 0xB8, 0x1B, 0xDF, 0x65, 0xB6, 0xF3, 0xFF, 0xFE, 0xAF, 0x9F, 0xA3, 0xC6, 0xE9, 0x3C, 0x75,
+ 0xFA, 0xF2, 0x1B, 0xA4, 0x8B, 0x7B, 0xC6, 0xCE, 0x84, 0x65, 0xA1, 0xCA, 0x7D, 0xB3, 0x7A, 0x7B,
+ 0x6E, 0x65, 0x72, 0x13, 0xEB, 0xBD, 0x26, 0xC6, 0xB0, 0x4C, 0xFC, 0x6B, 0xC7, 0x19, 0x90, 0x44,
+ 0xBC, 0xAD, 0xC0, 0xAB, 0x11, 0x64, 0xCA, 0x63, 0x53, 0xD4, 0xC7, 0x26, 0xE1, 0x10, 0xD9, 0xD9,
+ 0x71, 0x76, 0x17, 0x46, 0x9F, 0x17, 0x55, 0x89, 0x66, 0x22, 0x54, 0xDB, 0xB1, 0x34, 0xCF, 0x14,
+ 0x4F, 0x4B, 0xC6, 0xA7, 0x70, 0x98, 0xE3, 0x02, 0x4C, 0x2A, 0xA1, 0x7D, 0x65, 0x4F, 0x7A, 0x7C,
+ 0x0B, 0x47, 0x5F, 0x95, 0xD0, 0xC7, 0x26, 0x9B, 0x86, 0x6E, 0x4D, 0x72, 0xBB, 0xC6, 0x2D, 0x28,
+ 0x58, 0xD2, 0x6D, 0xCC, 0x52, 0x74, 0xA9, 0x59, 0x56, 0xF1, 0xBE, 0x0A, 0x94, 0x86, 0x5B, 0xCA,
+ 0xB5, 0x31, 0x5A, 0x6C, 0xA4, 0x6C, 0xE2, 0x9B, 0x7D, 0x94, 0x0D, 0x1F, 0x30, 0xC2, 0x17, 0xA4,
+ 0xFA, 0x01, 0x74, 0xA5, 0xDC, 0xC4, 0x74, 0x32, 0x5F, 0xEC, 0x78, 0x12, 0x56, 0x1C, 0xD6, 0xF7,
+ 0x46, 0x56, 0xCC, 0x7A, 0x48, 0xBE, 0xD0, 0x53, 0x10, 0xA0, 0x37, 0x62, 0x22, 0x51, 0x0C, 0xC8,
+ 0xCF, 0x26, 0xCD, 0xD1, 0xAB, 0xCD, 0x10, 0xF0, 0x3F, 0x2B, 0x77, 0x52, 0x32, 0x51, 0x5E, 0x3B,
+ 0xFD, 0x3E, 0xBA, 0x7B, 0xA6, 0xE5, 0xFD, 0x99, 0x50, 0x91, 0x06, 0x4E, 0xD5, 0x7C, 0xA9, 0x7A,
+ 0xC1, 0xCC, 0xA9, 0x8C, 0xEB, 0x0D, 0xD9, 0x4B, 0x5C, 0xB1, 0x1A, 0x01, 0xE3, 0xB2, 0x90, 0x1F,
+ 0x45, 0xD2, 0x1B, 0xC7, 0x19, 0xF9, 0x6F, 0xA4, 0xA8, 0xBF, 0x5F, 0x8F, 0x8C, 0x8A, 0xFA, 0x13,
+ 0xBB, 0xBD, 0xE9, 0xD4, 0xD0, 0xFD, 0xA1, 0x17, 0x58, 0x4A, 0x71, 0x46, 0x2F, 0x2D, 0x66, 0x99,
+ 0x3F, 0x40, 0x9B, 0x08, 0xF0, 0x5A, 0x50, 0x78, 0x26, 0xA8, 0xCD, 0x81, 0xA5, 0xB4, 0xE3, 0x0E,
+ 0x97, 0xAE, 0x12, 0x28, 0xF8, 0x50, 0x21, 0x78, 0x5F, 0xC5, 0x85, 0xA8, 0xF2, 0xF1, 0xBF, 0x5C,
+ 0x9C, 0x57, 0x2F, 0xDF, 0xB7, 0xD9, 0x95, 0x2D, 0x30, 0xFA, 0x9E, 0xBE, 0xBA, 0xD1, 0xEC, 0x40,
+ 0x93, 0xFD, 0x0F, 0xB1, 0x5E, 0x75, 0xC2, 0x2E, 0x3A, 0x5B, 0x53, 0x29, 0x90, 0x7A, 0x6C, 0x9D,
+ 0xBA, 0xC6, 0xC6, 0xF9, 0x25, 0x7F, 0x3B, 0xB9, 0xBD, 0x6B, 0x7E, 0x3B, 0xFD, 0xE9, 0xCA, 0x8E,
+ 0x28, 0x0D, 0x3E, 0xC1, 0xCE, 0x20, 0x45, 0x7C, 0x9D, 0x1E, 0xFC, 0x44, 0x9B, 0x41, 0x1D, 0xAC,
+ 0xC2, 0x13, 0x39, 0x3E, 0xD3, 0x15, 0x59, 0x90, 0x62, 0x00, 0x91, 0xF3, 0x77, 0xEC, 0xD8, 0xA2,
+ 0x59, 0x95, 0xF1, 0x5A, 0xBF, 0x36, 0x56, 0x37, 0x0E, 0xEB, 0x2E, 0xA2, 0xA5, 0x3D, 0x22, 0xB1,
+ 0x2F, 0x17, 0x5F, 0xAB, 0xE7, 0xEF, 0xCF, 0xB6, 0x20, 0x6A, 0x65, 0x31, 0x8E, 0x2B, 0x94, 0x52,
+ 0xF0, 0x19, 0xF9, 0xAD, 0xE0, 0x7A, 0x4A, 0xB9, 0xAD, 0xC4, 0x1A, 0x66, 0x83, 0x3D, 0x06, 0xA9,
+ 0x94, 0xD3, 0xCA, 0x83, 0xD9, 0x33, 0x37, 0xCA, 0x67, 0x65, 0xEA, 0x5A, 0x4A, 0xBA, 0xAC, 0x8C,
+ 0x6B, 0x9E, 0xB0, 0x95, 0xD3, 0x34, 0x85, 0x2F, 0xA0, 0x45, 0x3B, 0xDE, 0x0F, 0xFF, 0x78, 0x65,
+ 0x0C, 0x00, 0x48, 0x98, 0xE9, 0x1E, 0xA7, 0x88, 0x47, 0xD8, 0x6B, 0x3A, 0x9F, 0x60, 0x9E, 0xC2,
+ 0x61, 0x07, 0x72, 0xF7, 0x6C, 0x35, 0xA5, 0x1A, 0x3B, 0x01, 0x59, 0x33, 0xFC, 0x14, 0x49, 0x9E,
+ 0x70, 0x32, 0xE2, 0xD5, 0x9B, 0xAD, 0x2A, 0x56, 0x72, 0x5F, 0x84, 0xEF, 0x8C, 0x22, 0x16, 0xA2,
+ 0x97, 0xF1, 0xF2, 0xAC, 0xF6, 0x87, 0x49, 0xE0, 0x81, 0x28, 0xAE, 0x36, 0xB4, 0xDE, 0xE9, 0x60,
+ 0x0C, 0x51, 0x4C, 0x2B, 0xDF, 0x1F, 0xD3, 0xD1, 0xF0, 0x95, 0x3C, 0xA5, 0x63, 0x4A, 0xEB, 0xF7,
+ 0x45, 0x9C, 0x18, 0xC7, 0xE7, 0xAE, 0x4E, 0x2C, 0x01, 0x3B, 0x9C, 0x2F, 0xBA, 0x3A, 0xDD, 0xC5,
+ 0xB6, 0x65, 0x06, 0x88, 0xCF, 0xBF, 0x0D, 0xDD, 0xC0, 0x1F, 0xC6, 0x8C, 0x2D, 0x6C, 0x7F, 0x9F,
+ 0x0A, 0x98, 0x4D, 0xDC, 0xDC, 0x68, 0x65, 0x85, 0xF7, 0x34, 0x6C, 0xB9, 0xC5, 0xFB, 0xDA, 0xC8,
+ 0x4B, 0x96, 0xCD, 0xCD, 0xDB, 0x39, 0x39, 0xF7, 0xA2, 0xBB, 0xD6, 0xD8, 0x72, 0x4A, 0x6C, 0x2C,
+ 0x86, 0x12, 0x37, 0x71, 0x3F, 0x4B, 0x53, 0x62, 0xE3, 0x19, 0x50, 0x62, 0x33, 0x2F, 0x25, 0xEE,
+ 0x6F, 0xDE, 0xCE, 0x35, 0x4B, 0x51, 0x62, 0x73, 0xCB, 0x29, 0xB1, 0xB9, 0x18, 0x4A, 0xDC, 0xC4,
+ 0xFD, 0x2C, 0x4D, 0x89, 0xCD, 0x67, 0x40, 0x89, 0xFB, 0x79, 0x29, 0xF1, 0x60, 0xF3, 0x76, 0x6E,
+ 0xBF, 0x14, 0x25, 0xEE, 0x6F, 0x39, 0x25, 0xEE, 0x2F, 0x86, 0x12, 0x37, 0x71, 0x3F, 0x4B, 0x53,
+ 0xE2, 0xFE, 0x3A, 0x29, 0x31, 0xB2, 0x6F, 0x90, 0x39, 0xE1, 0x0E, 0x16, 0x3D, 0x7E, 0x63, 0x98,
+ 0xCB, 0xC0, 0x19, 0xED, 0xF4, 0xA8, 0x27, 0xCC, 0xA0, 0xA3, 0x3C, 0x74, 0xD9, 0x8D, 0x63, 0x14,
+ 0xF7, 0xA0, 0x4C, 0x75, 0x97, 0x9A, 0x2C, 0x56, 0x52, 0x39, 0x69, 0x2F, 0xC2, 0x8F, 0xB2, 0x51,
+ 0x69, 0x9D, 0xB4, 0x4B, 0xF9, 0xDD, 0xD5, 0x2B, 0x2D, 0xB4, 0x3C, 0x6C, 0xA4, 0xC3, 0xDD, 0x49,
+ 0x3B, 0xE6, 0x6E, 0xA7, 0x02, 0x2B, 0xA5, 0x75, 0xC3, 0x73, 0x34, 0x43, 0xD7, 0x7C, 0x11, 0xBA,
+ 0x21, 0xEB, 0x3A, 0xF7, 0x7D, 0x46, 0x41, 0x7C, 0x0C, 0xC3, 0x87, 0xB9, 0x81, 0x55, 0x4C, 0xAA,
+ 0x12, 0xCC, 0x35, 0x06, 0x7D, 0x91, 0x79, 0x25, 0xBB, 0xB7, 0x58, 0x78, 0x31, 0x3A, 0x52, 0x5B,
+ 0xF2, 0x5D, 0xB4, 0xB2, 0xC4, 0x5C, 0xB4, 0xC9, 0xB4, 0x82, 0xA9, 0xDA, 0xB1, 0xB1, 0x4C, 0x16,
+ 0x82, 0xA9, 0xE3, 0xDA, 0xE8, 0x02, 0xED, 0xC1, 0xE0, 0xB5, 0xE7, 0x9B, 0x36, 0xE8, 0xEE, 0xAC,
+ 0xBD, 0xF7, 0xED, 0xBC, 0xFD, 0x9C, 0x8C, 0xB0, 0xB0, 0x24, 0x58, 0x51, 0x29, 0x33, 0x6C, 0xA2,
+ 0x69, 0x36, 0xF8, 0x93, 0x00, 0x4B, 0xD9, 0x62, 0x85, 0xEE, 0x7E, 0x33, 0xDC, 0x8D, 0xB2, 0xC6,
+ 0xCE, 0x58, 0xD2, 0x02, 0xEC, 0xB1, 0x0B, 0x4F, 0x80, 0x73, 0x2F, 0xCE, 0x2C, 0x93, 0xF2, 0x2F,
+ 0xC3, 0xE4, 0x99, 0xFC, 0x7B, 0x25, 0x19, 0x6C, 0xA2, 0x81, 0xA7, 0x52, 0x7B, 0x54, 0xAA, 0x01,
+ 0x76, 0x5B, 0xBE, 0x7F, 0xEA, 0x3C, 0x72, 0x2A, 0x59, 0xB1, 0x2A, 0xF6, 0xD9, 0x93, 0xF5, 0x95,
+ 0xB8, 0xB1, 0x3B, 0x89, 0xD9, 0x45, 0x6C, 0xCE, 0x0D, 0xFC, 0x81, 0xF4, 0x6E, 0xC6, 0x52, 0x2A,
+ 0xC4, 0xE2, 0xFA, 0xE6, 0x3D, 0xB7, 0x11, 0x91, 0xC9, 0x9F, 0xB9, 0xB6, 0xAD, 0xA9, 0xC8, 0x73,
+ 0x09, 0x7A, 0x6E, 0xD6, 0xFE, 0x96, 0x11, 0x65, 0x72, 0xC9, 0x2A, 0x11, 0x46, 0x94, 0x4F, 0x50,
+ 0x52, 0x2A, 0x07, 0x44, 0xB8, 0x30, 0x2C, 0xF3, 0x5C, 0x29, 0xE3, 0xD6, 0x44, 0x85, 0xB0, 0xE1,
+ 0xC1, 0xCC, 0x2B, 0xF1, 0x11, 0x45, 0x1E, 0xAF, 0xD1, 0x75, 0xC7, 0xA6, 0xEC, 0xE6, 0x5E, 0x78,
+ 0x3E, 0x87, 0xDE, 0x02, 0x03, 0x5C, 0x85, 0x2C, 0x6A, 0x46, 0x4E, 0x05, 0x30, 0x57, 0x9D, 0xE6,
+ 0x2A, 0xAF, 0x5B, 0xC3, 0xCB, 0x13, 0x24, 0x09, 0x72, 0xDC, 0xAF, 0xBD, 0xE4, 0x64, 0x28, 0xAA,
+ 0x53, 0xA4, 0x50, 0x2D, 0xFF, 0x5D, 0x46, 0x8C, 0x1A, 0x13, 0x7D, 0x6C, 0x7B, 0x71, 0xCE, 0x68,
+ 0x31, 0x18, 0x3A, 0x55, 0x8A, 0xF6, 0xB0, 0xE1, 0x22, 0x89, 0x29, 0x22, 0x14, 0xC5, 0xE0, 0xF1,
+ 0x38, 0x08, 0xFC, 0x78, 0xB6, 0xB3, 0xE6, 0xFB, 0x83, 0xB7, 0x2F, 0xA8, 0x5F, 0x1A, 0xF5, 0x69,
+ 0xA7, 0x8B, 0x63, 0x3B, 0x36, 0x5B, 0x6B, 0xB2, 0x91, 0xB5, 0x89, 0x73, 0x61, 0x25, 0x51, 0xC4,
+ 0x48, 0xF9, 0xF7, 0x8A, 0xC4, 0x39, 0x35, 0x70, 0x6E, 0x71, 0x4E, 0xBE, 0xBF, 0x29, 0xE2, 0x1C,
+ 0x55, 0x65, 0x65, 0xA6, 0xDD, 0x75, 0x02, 0x10, 0xE4, 0xE8, 0x34, 0x53, 0xC5, 0x39, 0x31, 0x97,
+ 0x61, 0x52, 0xB8, 0x93, 0x1E, 0xA9, 0xE1, 0x71, 0x87, 0x35, 0xFC, 0xE4, 0xAB, 0x9B, 0xE9, 0x26,
+ 0xB0, 0x34, 0xF9, 0x4E, 0x6E, 0xE0, 0x32, 0xE5, 0x3B, 0x39, 0xC2, 0xCA, 0xE5, 0x3B, 0x39, 0xEC,
+ 0xCB, 0x19, 0xF3, 0xFC, 0xCF, 0x98, 0xD8, 0x4E, 0x17, 0x39, 0x63, 0x46, 0xCD, 0xFE, 0x98, 0x67,
+ 0xCC, 0x37, 0xC3, 0x0D, 0x8F, 0x19, 0x34, 0xCF, 0xAC, 0xF4, 0x98, 0x19, 0x8D, 0x9D, 0xEF, 0xA4,
+ 0x09, 0x0C, 0x77, 0x53, 0xCE, 0x98, 0x91, 0xB5, 0x95, 0x8E, 0x11, 0x1F, 0xBA, 0xC5, 0x6A, 0x51,
+ 0xB2, 0xF0, 0x2A, 0x03, 0x48, 0xFE, 0x91, 0x4E, 0x8F, 0x68, 0x1F, 0x97, 0x76, 0x80, 0x04, 0x86,
+ 0xBB, 0xF2, 0xA3, 0x23, 0x5A, 0xD5, 0xC6, 0x9D, 0x1E, 0x48, 0xA8, 0xD9, 0xA7, 0x47, 0xA3, 0xDE,
+ 0x68, 0xD4, 0x5F, 0x8E, 0x8F, 0x12, 0xC7, 0x47, 0x72, 0xB7, 0x8B, 0x9C, 0x20, 0x89, 0x96, 0xCF,
+ 0x20, 0x2B, 0xA2, 0xCC, 0x43, 0xF7, 0x8C, 0x2E, 0x36, 0x68, 0x41, 0xA5, 0xEE, 0x35, 0xE2, 0x2D,
+ 0xB3, 0x21, 0x1F, 0x07, 0x56, 0xEA, 0x52, 0x83, 0x12, 0x86, 0x6D, 0xD4, 0x9D, 0xC6, 0xF4, 0xE5,
+ 0xCC, 0x9B, 0x15, 0x31, 0xFB, 0x9A, 0xB9, 0x60, 0x3A, 0x44, 0x79, 0xC3, 0x4C, 0x13, 0x2D, 0x97,
+ 0xFB, 0x70, 0x74, 0xA7, 0x1C, 0xFB, 0xB8, 0x90, 0xBC, 0x60, 0x5F, 0x7B, 0xBD, 0xD2, 0x09, 0xD3,
+ 0x3B, 0xED, 0xEA, 0x17, 0xE7, 0x61, 0x33, 0x33, 0x70, 0x51, 0xA2, 0x29, 0x8C, 0xA2, 0x81, 0x59,
+ 0xEE, 0x37, 0xC3, 0x34, 0x2D, 0x51, 0x42, 0x2B, 0x79, 0x83, 0x0C, 0x04, 0x58, 0xA5, 0x3F, 0x76,
+ 0xD1, 0x7E, 0x1C, 0x3D, 0x1A, 0xC2, 0xA9, 0x63, 0xCA, 0x2B, 0x66, 0x02, 0x37, 0x79, 0xEF, 0x53,
+ 0xE5, 0x7A, 0x55, 0xCF, 0xDE, 0xF7, 0x29, 0x8A, 0xEC, 0xB3, 0xF3, 0xC0, 0xEF, 0x31, 0x51, 0xC6,
+ 0x29, 0x80, 0x44, 0x38, 0x8E, 0x18, 0xB0, 0x4E, 0xBB, 0x8D, 0x63, 0xD9, 0x8E, 0x18, 0x0D, 0x27,
+ 0xC8, 0xFE, 0xEC, 0xA3, 0x95, 0x1A, 0xD3, 0x5C, 0xFC, 0x19, 0x1F, 0xC7, 0x9A, 0x9C, 0x5E, 0x5F,
+ 0xC4, 0xE3, 0x44, 0x69, 0x40, 0xD8, 0x95, 0xAD, 0x4B, 0x0C, 0x43, 0xA0, 0x2A, 0x9B, 0xDF, 0x22,
+ 0x5B, 0x29, 0x98, 0x01, 0x03, 0x09, 0xAB, 0x9B, 0x93, 0xB3, 0xA8, 0xE4, 0x07, 0xCD, 0x01, 0x1E,
+ 0x4C, 0xAE, 0xF6, 0x71, 0x72, 0x72, 0x7C, 0x7A, 0x7A, 0x7C, 0x76, 0x76, 0x7C, 0x7E, 0x7E, 0x7C,
+ 0x71, 0x71, 0x7C, 0x79, 0x59, 0xD6, 0x5D, 0xA5, 0xF4, 0xAC, 0xDB, 0x9A, 0x89, 0x69, 0xE0, 0x68,
+ 0xF2, 0xFE, 0x71, 0xB1, 0x3C, 0x0B, 0x79, 0xFA, 0x8F, 0x9F, 0xE3, 0x9C, 0x7B, 0x00, 0x0D, 0x7F,
+ 0x5A, 0xF1, 0x13, 0x9B, 0x6F, 0xAE, 0x65, 0x1D, 0x1B, 0xAA, 0x99, 0x4B, 0xE9, 0x14, 0x18, 0x37,
+ 0x9C, 0x1A, 0xC3, 0xE3, 0x43, 0xF7, 0xF1, 0xCF, 0x79, 0xC0, 0xA0, 0x8E, 0xE8, 0xE4, 0xD1, 0x8B,
+ 0x90, 0x81, 0x13, 0x19, 0xF7, 0x41, 0x6E, 0x42, 0x65, 0xD2, 0x19, 0x9E, 0x29, 0x16, 0xE7, 0xBB,
+ 0x9F, 0x8A, 0xD4, 0x3B, 0x37, 0x1A, 0x06, 0x14, 0x3C, 0x74, 0x28, 0x21, 0xBD, 0x0B, 0xB8, 0x9C,
+ 0x17, 0xC0, 0x99, 0x80, 0x93, 0x50, 0xA8, 0x90, 0x7D, 0x24, 0xAE, 0xF0, 0x2A, 0x2D, 0x96, 0x7E,
+ 0xFF, 0xC1, 0x51, 0x7A, 0xA1, 0xBF, 0xCB, 0xF8, 0xA3, 0x29, 0xD0, 0x41, 0x47, 0xC9, 0x4E, 0xD2,
+ 0xB3, 0x86, 0xEC, 0x8D, 0x74, 0xDB, 0xE6, 0x73, 0x01, 0x9B, 0xA9, 0x00, 0x2D, 0x8B, 0xF1, 0xBC,
+ 0xC6, 0x55, 0xBD, 0x46, 0xBB, 0xA3, 0xCF, 0x31, 0x27, 0xFA, 0x37, 0xDB, 0x14, 0x2A, 0xB4, 0xC9,
+ 0x7F, 0xD0, 0x5C, 0xA4, 0xD8, 0xF0, 0xB2, 0x8E, 0xFB, 0xCA, 0xFD, 0x06, 0x60, 0x8C, 0xF5, 0xBE,
+ 0x86, 0x81, 0x4D, 0xC9, 0xB2, 0xEC, 0x3E, 0xF2, 0x4D, 0x1B, 0xD8, 0x18, 0xB0, 0xE5, 0x07, 0x50,
+ 0x3E, 0x81, 0xDF, 0xFE, 0x61, 0xE3, 0xE6, 0x8B, 0xE6, 0x05, 0xCA, 0x1D, 0x6C, 0x5F, 0xD0, 0xB0,
+ 0x72, 0xE9, 0xA0, 0x0C, 0xAA, 0x68, 0x26, 0x0C, 0xFB, 0x95, 0x0F, 0x31, 0x8A, 0x2D, 0xD7, 0xEE,
+ 0x84, 0xB8, 0x9E, 0x37, 0x51, 0xC6, 0xFC, 0x26, 0x99, 0xC4, 0xAC, 0x73, 0xCD, 0x31, 0xB0, 0xA9,
+ 0x43, 0x6E, 0xAC, 0x91, 0x14, 0x81, 0xB8, 0x28, 0x21, 0x81, 0xA6, 0xEB, 0x54, 0x37, 0x51, 0xC3,
+ 0x92, 0xF3, 0x16, 0x0F, 0xC3, 0xE1, 0x5D, 0x79, 0x88, 0xD0, 0x79, 0x27, 0x4B, 0xBF, 0x84, 0x30,
+ 0x60, 0xC3, 0x00, 0x6F, 0xEF, 0x4D, 0xCF, 0x17, 0x32, 0x31, 0x1D, 0xAD, 0x04, 0xFE, 0xEC, 0x61,
+ 0x78, 0x3C, 0x45, 0x13, 0x4A, 0x5A, 0x05, 0xF2, 0x24, 0x0A, 0x9E, 0x7D, 0x29, 0xB0, 0x0D, 0x94,
+ 0x35, 0xDF, 0x71, 0x39, 0xED, 0x9C, 0x48, 0xE0, 0xCF, 0x84, 0x93, 0xA2, 0xE0, 0x99, 0xD0, 0xA3,
+ 0x2E, 0xA5, 0x20, 0x30, 0x7E, 0x36, 0xB4, 0x46, 0x24, 0x55, 0xFC, 0x68, 0x70, 0xB3, 0xA6, 0x7D,
+ 0xE3, 0xF7, 0x53, 0xDA, 0x7B, 0x27, 0x20, 0x47, 0xCA, 0x35, 0x5C, 0xAB, 0x6F, 0x0A, 0x47, 0xF3,
+ 0x5D, 0xDB, 0x79, 0x38, 0x0D, 0x0D, 0x9E, 0x95, 0x56, 0xF4, 0x27, 0x43, 0x67, 0x5D, 0x50, 0xFC,
+ 0xF9, 0x8A, 0x98, 0x54, 0x6A, 0x22, 0xEB, 0xE7, 0x3E, 0x54, 0xC5, 0x07, 0xC5, 0x93, 0x5D, 0xB2,
+ 0xFB, 0x22, 0x28, 0x80, 0x55, 0x80, 0x28, 0x29, 0xCF, 0x6A, 0x9F, 0xDB, 0x86, 0x8F, 0xD7, 0x91,
+ 0xD2, 0x8F, 0xE6, 0xDE, 0xD4, 0x62, 0x76, 0x63, 0xE1, 0xD0, 0x37, 0xA4, 0x74, 0x61, 0x99, 0x4E,
+ 0xE0, 0x50, 0x76, 0x1F, 0xD9, 0x0E, 0x9C, 0xE6, 0x03, 0xED, 0x9E, 0x33, 0xA5, 0x34, 0x86, 0xF6,
+ 0xE7, 0x72, 0xA9, 0x38, 0xB6, 0xF9, 0x98, 0x5F, 0xA8, 0x55, 0xAB, 0xF3, 0xE4, 0x0B, 0x3E, 0x7C,
+ 0x4E, 0x66, 0x2D, 0xB9, 0xA2, 0x52, 0x76, 0xAD, 0x44, 0xD3, 0xEC, 0xCD, 0x49, 0xC0, 0x2B, 0x65,
+ 0xD9, 0xF2, 0x65, 0xF3, 0x4D, 0x32, 0x6D, 0xCD, 0x58, 0xD1, 0x1C, 0xB6, 0xAD, 0xC0, 0xB5, 0x80,
+ 0x64, 0xBF, 0xF0, 0x87, 0x4B, 0x95, 0x68, 0xFB, 0xDC, 0xBC, 0x2F, 0x68, 0xDE, 0x9A, 0xCE, 0x5F,
+ 0xC7, 0x06, 0xA8, 0xB4, 0x14, 0xF0, 0xC3, 0x07, 0x79, 0x6A, 0x09, 0x22, 0x89, 0x93, 0x5E, 0x2F,
+ 0x7E, 0x84, 0xCD, 0xFE, 0x0A, 0x7C, 0x08, 0x76, 0xED, 0x9B, 0xDB, 0x07, 0xE1, 0x87, 0x4F, 0xD6,
+ 0x6C, 0xEF, 0xEB, 0xB5, 0xFA, 0x3C, 0x36, 0x8D, 0x19, 0x5A, 0xBF, 0x66, 0x99, 0x7D, 0x1B, 0xC0,
+ 0x8E, 0x57, 0x64, 0xDE, 0xAC, 0xE3, 0x6C, 0x9A, 0x78, 0x71, 0x86, 0xA0, 0x8B, 0xC3, 0x29, 0x8F,
+ 0x32, 0x3A, 0x9B, 0x9F, 0x47, 0xB2, 0x86, 0x9E, 0xEA, 0x1F, 0xAF, 0x19, 0x69, 0x4C, 0xB2, 0x6A,
+ 0xC1, 0x17, 0xD1, 0x86, 0x8C, 0x10, 0x77, 0xC5, 0x89, 0xA5, 0xE4, 0x74, 0xA2, 0x02, 0x59, 0x70,
+ 0x86, 0x80, 0xDE, 0xC8, 0xCC, 0x1E, 0xC3, 0x2A, 0x6F, 0x51, 0x26, 0x78, 0x90, 0x57, 0x47, 0xA9,
+ 0x2F, 0x54, 0x3E, 0x29, 0x5F, 0xA9, 0xA9, 0x1B, 0x9B, 0x2B, 0x36, 0x26, 0x93, 0x85, 0x0B, 0x09,
+ 0x37, 0x1C, 0x85, 0x32, 0x29, 0x7E, 0xCD, 0x94, 0x85, 0x16, 0xAA, 0xC5, 0xDD, 0x9E, 0x8D, 0x48,
+ 0xF2, 0x84, 0x9C, 0x8A, 0x4E, 0x39, 0x9C, 0xE3, 0x23, 0x2C, 0x58, 0x9D, 0x6A, 0x16, 0x9F, 0xCA,
+ 0x9A, 0x84, 0x1D, 0x5A, 0x7B, 0x84, 0x63, 0x78, 0xA8, 0x68, 0xA6, 0x2D, 0x35, 0x2A, 0xC0, 0x3E,
+ 0xCC, 0xBB, 0xD2, 0xE3, 0x9A, 0x08, 0x30, 0x89, 0x18, 0x50, 0x07, 0x1B, 0x6A, 0x4F, 0x4C, 0x19,
+ 0x77, 0x7D, 0x21, 0xCB, 0xC6, 0x7E, 0x81, 0x8F, 0x1E, 0x47, 0xDB, 0x05, 0xC7, 0x53, 0x8A, 0x50,
+ 0xB3, 0x67, 0x72, 0xCB, 0x60, 0x18, 0x31, 0xF7, 0x8C, 0xAC, 0x16, 0x93, 0xD9, 0x9B, 0x37, 0x4B,
+ 0x01, 0x48, 0x72, 0x4B, 0x59, 0x60, 0xC0, 0xBC, 0xFF, 0xC4, 0x45, 0x82, 0xF1, 0xE5, 0xAF, 0xBC,
+ 0x9D, 0x8B, 0xB3, 0xA6, 0xBB, 0x5F, 0x2C, 0x5F, 0xED, 0x27, 0x7A, 0x47, 0xAE, 0xFA, 0xCD, 0x35,
+ 0xA8, 0x96, 0x86, 0xC3, 0xEE, 0xF7, 0x6B, 0x8D, 0x83, 0x5A, 0xE3, 0x30, 0x2F, 0x47, 0x95, 0x70,
+ 0x69, 0x7D, 0x70, 0x3D, 0xA7, 0x4F, 0x46, 0xB5, 0x38, 0xBF, 0x90, 0xDD, 0xB6, 0xD5, 0x57, 0xA7,
+ 0x9A, 0x17, 0x91, 0x52, 0xA3, 0x59, 0x01, 0x84, 0x7C, 0x84, 0x3F, 0xEA, 0xF5, 0x7C, 0x48, 0xAF,
+ 0x20, 0xFC, 0x60, 0x1A, 0x62, 0x70, 0xBC, 0x5F, 0xAF, 0x93, 0x19, 0x15, 0x78, 0x90, 0xEA, 0x3C,
+ 0xB7, 0x7E, 0x99, 0x3D, 0xB7, 0x29, 0x6A, 0xE6, 0xFE, 0xDB, 0x7F, 0xC9, 0xA1, 0x69, 0x2A, 0x38,
+ 0x2C, 0xC5, 0xE3, 0x28, 0xEF, 0x81, 0x9D, 0xE4, 0xA4, 0x69, 0x3D, 0xBF, 0x40, 0x3C, 0x2E, 0xD6,
+ 0x14, 0xAC, 0xA8, 0x4B, 0xBC, 0x2E, 0x10, 0x33, 0x7D, 0x24, 0x41, 0x33, 0xE8, 0x0E, 0x4D, 0x11,
+ 0x62, 0xCE, 0x25, 0x3D, 0x77, 0x6C, 0x59, 0x41, 0x20, 0x0E, 0x5A, 0x14, 0xA0, 0xBE, 0x6B, 0xA6,
+ 0x40, 0xDB, 0xC0, 0xDE, 0xF4, 0x81, 0xE5, 0xCB, 0xEC, 0xF4, 0xEA, 0xCB, 0xAB, 0xD2, 0xBE, 0x09,
+ 0x0B, 0x2E, 0xC0, 0xAD, 0x88, 0x01, 0xF8, 0x68, 0x60, 0x9B, 0xE2, 0xB5, 0x3F, 0x62, 0xB2, 0xDD,
+ 0x27, 0x26, 0xA5, 0x43, 0xB2, 0xF2, 0x82, 0x60, 0x6D, 0x23, 0x19, 0x52, 0x11, 0x4D, 0xCA, 0x81,
+ 0x47, 0xD6, 0xAC, 0xD0, 0x61, 0x95, 0xB9, 0x03, 0x60, 0x03, 0x78, 0x3D, 0xD7, 0x3E, 0x5B, 0x97,
+ 0xF3, 0xEA, 0xD4, 0x22, 0xF4, 0x63, 0x14, 0x81, 0x2B, 0x2B, 0x63, 0x70, 0x99, 0x86, 0xDC, 0x13,
+ 0x39, 0x6C, 0x24, 0xD0, 0x63, 0xF6, 0xFE, 0x5B, 0x47, 0x3A, 0xA3, 0x06, 0xEE, 0x3C, 0x37, 0xD6,
+ 0xC9, 0x9E, 0x22, 0xB1, 0xFD, 0x0A, 0x76, 0x11, 0x4B, 0x11, 0x75, 0x44, 0x99, 0x02, 0x33, 0xA9,
+ 0x4E, 0x33, 0xA6, 0xBC, 0xA0, 0xFB, 0x6C, 0xEA, 0xB0, 0xEC, 0x8D, 0xF6, 0xA9, 0xE6, 0xF3, 0x92,
+ 0xB5, 0xAD, 0x5A, 0x5F, 0xEE, 0xDA, 0x9B, 0x77, 0x13, 0xDE, 0x0E, 0xD4, 0xE5, 0xB3, 0xDC, 0x43,
+ 0xD3, 0xC6, 0xE2, 0xC9, 0x0E, 0xDD, 0x59, 0x53, 0xB9, 0x07, 0xD4, 0x00, 0xEE, 0x64, 0xEC, 0x75,
+ 0x74, 0xDF, 0xD2, 0xA6, 0xFB, 0x96, 0xAF, 0x36, 0x7E, 0x79, 0xCB, 0x7D, 0x2E, 0xB6, 0x37, 0xB5,
+ 0xDC, 0xA2, 0x3D, 0x4B, 0xA3, 0xCC, 0xBB, 0x98, 0x0A, 0x17, 0x40, 0xD9, 0x39, 0x67, 0x67, 0xA0,
+ 0x72, 0xAF, 0xC2, 0xB1, 0x74, 0x76, 0xD2, 0xDF, 0x85, 0xBB, 0x88, 0x6A, 0x6C, 0x68, 0xEA, 0x9E,
+ 0x03, 0x8B, 0x24, 0xE3, 0x02, 0x29, 0x5B, 0x82, 0x98, 0xF2, 0x2E, 0x19, 0xF9, 0x86, 0xC0, 0xCF,
+ 0xB4, 0x3E, 0x1F, 0x25, 0x2D, 0xB4, 0x60, 0x8E, 0x09, 0x93, 0x5E, 0xBE, 0x92, 0xDC, 0x1B, 0x58,
+ 0xE3, 0x26, 0x0E, 0xF2, 0x59, 0x2E, 0x0D, 0xCB, 0x75, 0xD9, 0x04, 0xF9, 0x0E, 0xE6, 0x81, 0x19,
+ 0x91, 0x7F, 0x1D, 0x9A, 0x76, 0x20, 0xB8, 0x9F, 0xE1, 0xB4, 0x79, 0x90, 0x72, 0xDA, 0x7C, 0x3B,
+ 0xE6, 0xB4, 0x79, 0xA3, 0x3D, 0x62, 0x6E, 0xEB, 0xD9, 0x11, 0x9D, 0x58, 0xA1, 0x63, 0x07, 0x46,
+ 0x7A, 0xB3, 0xAE, 0x88, 0xCE, 0xAF, 0x58, 0xEA, 0x1D, 0x79, 0x16, 0xAC, 0x1C, 0x31, 0x4A, 0x96,
+ 0x86, 0x41, 0xE5, 0x5F, 0x1F, 0x98, 0xFC, 0x1E, 0x91, 0xCF, 0x92, 0x1B, 0xA3, 0xC2, 0x9C, 0xB9,
+ 0x86, 0xCE, 0xA2, 0x77, 0xAA, 0x5E, 0x77, 0x30, 0xCA, 0x37, 0x6D, 0x99, 0x20, 0x66, 0xE1, 0x7B,
+ 0x96, 0x03, 0x3F, 0x40, 0xEA, 0x1B, 0x4A, 0x83, 0xB6, 0x8D, 0x73, 0xDA, 0x45, 0xFD, 0x4D, 0x98,
+ 0x3A, 0xF3, 0x03, 0xEF, 0x9E, 0xCB, 0xF2, 0xF4, 0x1A, 0x74, 0xE6, 0xA1, 0xFE, 0x17, 0x96, 0x88,
+ 0xC0, 0x48, 0x52, 0x18, 0x3D, 0xEE, 0x8A, 0x7A, 0x00, 0xB0, 0x56, 0x1B, 0x51, 0x63, 0xD7, 0x38,
+ 0x02, 0x3C, 0xC4, 0xC6, 0xCD, 0x77, 0xEF, 0x46, 0xDF, 0x3C, 0x1F, 0x67, 0xD5, 0x9C, 0x45, 0x2B,
+ 0x48, 0x8B, 0xF2, 0x87, 0xC0, 0x16, 0x54, 0x95, 0xC7, 0x71, 0xB4, 0xCD, 0x65, 0x24, 0x19, 0x6F,
+ 0x37, 0x67, 0xA6, 0x99, 0x55, 0x50, 0xA7, 0x2A, 0xAD, 0xB1, 0x0A, 0xFA, 0xBC, 0x56, 0xB5, 0x4B,
+ 0x88, 0x42, 0x97, 0x4A, 0x80, 0x40, 0x4B, 0x49, 0x2A, 0x48, 0xD2, 0xA0, 0x46, 0xC6, 0x39, 0xA4,
+ 0xCF, 0x90, 0xFB, 0xEB, 0x1E, 0xD7, 0x30, 0xC2, 0x2C, 0x8B, 0x14, 0xE9, 0x3B, 0x24, 0x45, 0xE9,
+ 0x5A, 0x67, 0xD1, 0x59, 0xA1, 0xC2, 0x0A, 0x34, 0x49, 0x9F, 0x92, 0x0E, 0x9F, 0x39, 0xA9, 0x2D,
+ 0x91, 0xD6, 0x52, 0x48, 0x58, 0x80, 0xDA, 0x92, 0x2D, 0x97, 0x4A, 0x6F, 0x38, 0x28, 0xEC, 0xBC,
+ 0xD4, 0x79, 0xD5, 0x10, 0x39, 0xFD, 0xC0, 0xCA, 0xD7, 0x84, 0x49, 0x0F, 0x18, 0xA3, 0xF6, 0x84,
+ 0xED, 0x87, 0xA4, 0x3A, 0x52, 0x43, 0x51, 0x67, 0x39, 0x96, 0x28, 0x93, 0x0B, 0xB9, 0xF0, 0x5F,
+ 0x6C, 0x69, 0x5F, 0xA0, 0xF9, 0xE4, 0x8B, 0x89, 0xCE, 0xE5, 0xC5, 0xAF, 0x30, 0x52, 0x2D, 0xE8,
+ 0x3E, 0xE6, 0xC7, 0xB1, 0x3C, 0xAC, 0xFA, 0xF9, 0x79, 0xE9, 0xC1, 0x11, 0xED, 0xA1, 0x95, 0x0D,
+ 0xC0, 0xB5, 0x20, 0xF3, 0x5D, 0xC2, 0x84, 0x17, 0xEB, 0x1E, 0xED, 0x77, 0x1D, 0xFC, 0x48, 0x37,
+ 0x22, 0xF0, 0x60, 0xDD, 0xDE, 0x78, 0x67, 0x96, 0x83, 0xCE, 0x71, 0x64, 0x0B, 0x09, 0x3C, 0x2A,
+ 0x15, 0x8C, 0xCC, 0x96, 0x2E, 0xE0, 0x71, 0x9E, 0xBE, 0xE2, 0xC0, 0x8E, 0xCD, 0x6B, 0x7F, 0xE8,
+ 0x82, 0x32, 0x8D, 0x14, 0x11, 0x2F, 0xAA, 0xBA, 0xCC, 0xC9, 0x6D, 0x3B, 0x5D, 0x4B, 0x06, 0x1E,
+ 0xAD, 0xA4, 0x40, 0x4C, 0x4C, 0x07, 0x89, 0xCD, 0x22, 0xAF, 0x63, 0xC8, 0xE2, 0xEF, 0xE7, 0xE2,
+ 0xE1, 0x82, 0x88, 0x90, 0x27, 0x18, 0x16, 0x68, 0x6B, 0xEC, 0x96, 0x53, 0x11, 0x6B, 0x1D, 0x4B,
+ 0x60, 0xF9, 0x54, 0x1D, 0x5A, 0x1A, 0xF0, 0xC8, 0x55, 0xBE, 0x51, 0xAF, 0x1F, 0xEE, 0xC1, 0x8F,
+ 0xA3, 0x48, 0x40, 0xD0, 0x0C, 0x43, 0x56, 0x48, 0xC5, 0x3E, 0x00, 0x99, 0x9F, 0x41, 0x81, 0xC7,
+ 0xD4, 0x1E, 0x6D, 0x44, 0x75, 0xC7, 0xD1, 0x74, 0xB0, 0x4E, 0x8E, 0x07, 0x48, 0x55, 0x4A, 0x22,
+ 0x85, 0x6E, 0x72, 0x71, 0x15, 0x35, 0x16, 0x0B, 0x07, 0x63, 0x3B, 0x79, 0xCA, 0x39, 0x2E, 0x81,
+ 0x71, 0x26, 0x11, 0x16, 0x69, 0x35, 0xD4, 0x13, 0x41, 0x16, 0xCD, 0x8B, 0xBF, 0x49, 0x53, 0x06,
+ 0xDA, 0xC4, 0x4C, 0xB5, 0x2E, 0x95, 0x57, 0x08, 0xFA, 0x1A, 0x6A, 0xF6, 0x93, 0x2A, 0xA8, 0x9E,
+ 0x5B, 0xFC, 0x5C, 0x2A, 0xFF, 0xCD, 0xCB, 0x83, 0x73, 0x8B, 0x12, 0xF3, 0x48, 0xA3, 0x19, 0xE8,
+ 0x97, 0xFB, 0x5A, 0x69, 0xBC, 0xED, 0x86, 0x86, 0xAF, 0x93, 0xF9, 0x3A, 0x8C, 0xE0, 0xB9, 0x9D,
+ 0x1A, 0x27, 0xC6, 0x0A, 0xE4, 0x24, 0x1D, 0xEF, 0xB2, 0xD2, 0x1A, 0x05, 0x0A, 0xB5, 0x3D, 0x47,
+ 0x38, 0x40, 0xA6, 0xC5, 0x8D, 0xEF, 0xE3, 0xDD, 0x4E, 0x58, 0xC1, 0x82, 0x8C, 0xF0, 0x9D, 0x76,
+ 0xBB, 0xB4, 0x09, 0xFE, 0xFA, 0xA2, 0xAC, 0x05, 0x3E, 0x57, 0x28, 0xDB, 0xAA, 0x2D, 0xF0, 0x67,
+ 0x51, 0x4C, 0x03, 0x86, 0x83, 0x21, 0x37, 0x3A, 0xC3, 0x9E, 0x4D, 0x3D, 0x1E, 0x34, 0xC6, 0x3D,
+ 0xBC, 0x59, 0xC1, 0x48, 0x5B, 0xDC, 0x64, 0x52, 0x41, 0x76, 0x00, 0x86, 0x6F, 0xE2, 0x91, 0x65,
+ 0xD7, 0xCE, 0x43, 0xF5, 0xC2, 0xE6, 0x5E, 0xFF, 0x89, 0xED, 0x00, 0x90, 0xDE, 0x50, 0xC8, 0x9A,
+ 0x08, 0x3C, 0x3B, 0xF6, 0x8E, 0xD3, 0xEB, 0xC5, 0x14, 0x68, 0x8C, 0x45, 0xD3, 0x7C, 0x90, 0x12,
+ 0x35, 0xCF, 0x7A, 0x22, 0x8B, 0xED, 0xA7, 0xAB, 0x4E, 0xBC, 0x1E, 0x39, 0xA9, 0xE8, 0xF8, 0xDA,
+ 0x8B, 0x85, 0x3F, 0x0A, 0x16, 0xD0, 0x74, 0xE1, 0x78, 0x4F, 0x0A, 0x86, 0xB1, 0x28, 0x07, 0xF9,
+ 0x3C, 0x04, 0xAE, 0xBF, 0x0A, 0x93, 0x7F, 0x7A, 0x2E, 0x79, 0x04, 0xC0, 0xC5, 0x22, 0x6F, 0x7A,
+ 0xD5, 0xF2, 0x78, 0xE4, 0x1E, 0x5E, 0x1D, 0xE1, 0x21, 0x88, 0x25, 0xEE, 0x31, 0x00, 0x88, 0x52,
+ 0x18, 0x90, 0x6E, 0xE2, 0xE1, 0x85, 0x11, 0x1D, 0xB2, 0x51, 0x00, 0x26, 0x96, 0xDD, 0x33, 0x29,
+ 0xDB, 0xA4, 0x03, 0xAA, 0x8B, 0xA3, 0xFF, 0x88, 0x5A, 0xD4, 0xE0, 0x24, 0x7E, 0x52, 0x96, 0x20,
+ 0x1B, 0x2F, 0x53, 0xF0, 0x76, 0x78, 0xA8, 0x99, 0xE4, 0x83, 0x43, 0x96, 0xA4, 0xCC, 0x38, 0x88,
+ 0xD1, 0x18, 0x4B, 0x8C, 0x79, 0xD8, 0xD8, 0x54, 0x16, 0xB3, 0xFC, 0xE7, 0x27, 0x6B, 0xD3, 0xBD,
+ 0x34, 0x3E, 0x95, 0x89, 0x65, 0x88, 0x94, 0x67, 0xDA, 0x85, 0x3B, 0x27, 0x85, 0xA4, 0x89, 0x38,
+ 0x86, 0x5B, 0xB9, 0x51, 0x4E, 0x48, 0x3D, 0x53, 0x3B, 0x1E, 0x51, 0xD6, 0x2C, 0xC5, 0x3B, 0xBC,
+ 0x71, 0x4F, 0x8E, 0x3C, 0xC7, 0x95, 0xFB, 0xAB, 0xD9, 0x4C, 0x83, 0x0A, 0x8C, 0x49, 0x46, 0x61,
+ 0xF0, 0x6E, 0xD0, 0xFF, 0x85, 0x7C, 0x93, 0xCE, 0xF1, 0x4F, 0x86, 0x7F, 0x4F, 0x77, 0x66, 0x5D,
+ 0x80, 0xF6, 0x5A, 0xC4, 0xC1, 0x0F, 0xC1, 0x7E, 0x2E, 0xCD, 0x4E, 0x30, 0x47, 0xF9, 0x87, 0xBC,
+ 0xCA, 0x65, 0x67, 0x4E, 0x80, 0x74, 0xB9, 0x0A, 0xF6, 0x95, 0x98, 0xC6, 0xEA, 0x79, 0xD7, 0x09,
+ 0x23, 0xA1, 0x94, 0x49, 0xD1, 0x35, 0x5B, 0x09, 0x55, 0xB6, 0x39, 0xE0, 0x55, 0x46, 0x18, 0x75,
+ 0x48, 0x8E, 0x7F, 0xB2, 0x09, 0xC8, 0xFB, 0x36, 0xD0, 0x3D, 0xC5, 0x1F, 0x56, 0xE1, 0x0F, 0xC9,
+ 0x77, 0xF4, 0x27, 0x60, 0x00, 0x3E, 0x15, 0x36, 0x95, 0xBE, 0x7E, 0x84, 0x0F, 0x28, 0xB3, 0x16,
+ 0x8F, 0x65, 0xD8, 0xDE, 0x5A, 0x8D, 0x17, 0x00, 0x27, 0xCF, 0x26, 0x84, 0x5A, 0x5F, 0xE8, 0x01,
+ 0xE1, 0x9A, 0x9A, 0x48, 0x18, 0x31, 0xB0, 0xE0, 0x70, 0x84, 0x8B, 0x54, 0xF7, 0x85, 0x02, 0x12,
+ 0x52, 0x8D, 0x5F, 0x4D, 0xF1, 0xC8, 0xCB, 0xE1, 0xF3, 0x98, 0x02, 0x79, 0x2A, 0x7A, 0x21, 0x82,
+ 0xC3, 0x26, 0xC5, 0x2F, 0xA4, 0x00, 0xB0, 0xE9, 0x09, 0xC7, 0xD5, 0x6C, 0xCF, 0x3F, 0x9F, 0xB5,
+ 0x41, 0xF0, 0x0B, 0xC1, 0x1D, 0xA0, 0x1D, 0x16, 0x9F, 0x2D, 0x99, 0x69, 0xC6, 0x47, 0xCF, 0x77,
+ 0x04, 0x1B, 0x03, 0xDD, 0x0D, 0xA7, 0x89, 0x07, 0xEF, 0x8A, 0x39, 0x6C, 0xDC, 0x2C, 0x18, 0x41,
+ 0x8B, 0xF8, 0x2C, 0xAA, 0x15, 0xB8, 0x8E, 0x5A, 0xC8, 0x08, 0x55, 0x4E, 0x27, 0x86, 0xAE, 0xD6,
+ 0x8F, 0xB2, 0x9A, 0xC2, 0x89, 0x0C, 0x00, 0xDF, 0x65, 0xE7, 0x5F, 0x3A, 0xBB, 0xEC, 0x13, 0xE8,
+ 0x47, 0x0F, 0xC0, 0x89, 0x51, 0x86, 0xEC, 0x04, 0x5D, 0xEC, 0xE9, 0x46, 0xF3, 0x7F, 0x6C, 0xB1,
+ 0x2F, 0xC9, 0x6C, 0x97, 0x3D, 0x02, 0xC5, 0x55, 0x3B, 0x99, 0x02, 0x2C, 0x04, 0xE4, 0xB2, 0x53,
+ 0x81, 0x85, 0xE8, 0x76, 0xD5, 0x4E, 0x94, 0xB7, 0x39, 0x1C, 0xB3, 0x03, 0x5E, 0xA6, 0x36, 0x6C,
+ 0x5D, 0x7E, 0x21, 0x80, 0x6D, 0x88, 0x52, 0xE4, 0x86, 0xA4, 0x4E, 0xD7, 0x2C, 0xB4, 0x23, 0x0B,
+ 0x5D, 0x1A, 0xC9, 0x9E, 0x73, 0xEE, 0xB0, 0xC3, 0x05, 0x67, 0x0D, 0x8B, 0x21, 0x46, 0x6E, 0xBB,
+ 0xDD, 0xA8, 0xCD, 0x7A, 0xEC, 0x75, 0x0B, 0xA7, 0x0A, 0xE0, 0x49, 0x33, 0xC8, 0x02, 0xDE, 0xD8,
+ 0x06, 0x4A, 0xC0, 0x85, 0xBC, 0x20, 0x7F, 0x61, 0xE4, 0xCF, 0x07, 0xB6, 0x14, 0xF6, 0x43, 0xA3,
+ 0x67, 0x82, 0xFE, 0xEA, 0x34, 0x9E, 0x41, 0x02, 0xEA, 0xAD, 0x6D, 0x20, 0x83, 0x70, 0x41, 0x2F,
+ 0xA4, 0x50, 0x98, 0x14, 0xF2, 0x83, 0x2E, 0x45, 0x0E, 0xAA, 0xE1, 0x33, 0x21, 0x09, 0x29, 0x94,
+ 0xCE, 0xA0, 0x88, 0x98, 0xE4, 0xBA, 0x0D, 0x54, 0xE1, 0xCB, 0xE9, 0x0E, 0x61, 0xBA, 0x2F, 0x94,
+ 0x51, 0x9C, 0x32, 0x14, 0x4A, 0x14, 0x26, 0x0C, 0xD9, 0x6E, 0x03, 0x6F, 0x36, 0xA7, 0xAB, 0x2C,
+ 0xF3, 0x94, 0xD5, 0x0C, 0x97, 0xFE, 0x45, 0xB8, 0x89, 0xBC, 0xC3, 0xD9, 0x64, 0x14, 0xA1, 0x2B,
+ 0xC6, 0xA6, 0xCC, 0x4E, 0x37, 0x5C, 0x3C, 0xD5, 0xF0, 0xD8, 0x00, 0x91, 0x9E, 0x89, 0x74, 0xD3,
+ 0x68, 0xEE, 0x6F, 0xAE, 0x67, 0x4A, 0xF1, 0x52, 0xA2, 0x87, 0x95, 0xC5, 0xB8, 0x78, 0x27, 0x91,
+ 0x3F, 0xDC, 0xC9, 0x5C, 0x7E, 0xA6, 0xA9, 0x46, 0xCB, 0x2B, 0x1E, 0xBA, 0x18, 0xE3, 0x26, 0x62,
+ 0xC5, 0xBA, 0xED, 0x9A, 0xB6, 0x70, 0x97, 0x64, 0xD2, 0x84, 0xD5, 0x95, 0xB2, 0x66, 0x8E, 0xDA,
+ 0xCD, 0x69, 0xC8, 0x1C, 0x81, 0x37, 0x65, 0xC3, 0xC4, 0x35, 0x6F, 0x92, 0xF9, 0x72, 0xB4, 0xE2,
+ 0xA5, 0x58, 0x2E, 0x8B, 0x32, 0x51, 0x1B, 0xA9, 0xC7, 0xB2, 0x2E, 0x1E, 0x5D, 0x80, 0xAD, 0x3D,
+ 0x8B, 0x89, 0xE2, 0xAB, 0x2C, 0x7C, 0xF7, 0x78, 0xC5, 0x1E, 0x83, 0x32, 0x04, 0x40, 0xDE, 0xAA,
+ 0x28, 0x2F, 0xD6, 0xA1, 0xF6, 0x68, 0x0E, 0x83, 0x61, 0xE4, 0x4F, 0xC5, 0xBA, 0x5C, 0x3C, 0x70,
+ 0x6E, 0x83, 0x14, 0x42, 0xD7, 0x74, 0xE6, 0x3D, 0x8F, 0x22, 0xCA, 0x6A, 0xEC, 0xA2, 0xD6, 0xAF,
+ 0xA9, 0xF2, 0xAD, 0x92, 0x2F, 0x93, 0xAD, 0x14, 0xAF, 0x64, 0x8E, 0xF0, 0xFA, 0xD8, 0x1F, 0x5D,
+ 0x31, 0x63, 0x57, 0x18, 0x13, 0xF0, 0x7F, 0x8E, 0xD8, 0x47, 0x76, 0x74, 0xE0, 0xBF, 0xB0, 0xEE,
+ 0x49, 0xAC, 0x3B, 0x8D, 0x3F, 0xB9, 0x58, 0x77, 0xAA, 0xD1, 0xEA, 0x58, 0xF7, 0xC2, 0x48, 0xC6,
+ 0xE3, 0xBA, 0x89, 0x19, 0x83, 0x66, 0xD1, 0x4B, 0xF8, 0xDE, 0x06, 0xD0, 0x8A, 0x1B, 0xCE, 0x25,
+ 0xAC, 0x65, 0xA8, 0xE2, 0x77, 0x75, 0xCB, 0xD1, 0x7F, 0x4C, 0x23, 0x8E, 0x6A, 0xB3, 0x3E, 0x22,
+ 0x8F, 0x51, 0x2F, 0x44, 0x1F, 0xF8, 0xDD, 0x47, 0xF6, 0x9F, 0x8D, 0xE0, 0x85, 0x44, 0xA6, 0x92,
+ 0x48, 0x84, 0x2F, 0xB9, 0xE9, 0x23, 0x6C, 0xB1, 0x7D, 0xC4, 0x71, 0xEB, 0x38, 0xE2, 0x9C, 0x5B,
+ 0x33, 0x4D, 0x3D, 0xF8, 0x1E, 0xA3, 0x17, 0x37, 0x80, 0x3A, 0x84, 0x43, 0x5E, 0x40, 0x58, 0x07,
+ 0xAD, 0x2A, 0x3C, 0xD3, 0xC5, 0xC4, 0xA8, 0xDA, 0x53, 0x78, 0xA7, 0xEF, 0x45, 0x4E, 0xBD, 0x44,
+ 0x2D, 0x98, 0x83, 0x30, 0xF0, 0x63, 0xF7, 0x48, 0xF5, 0x17, 0xEC, 0x9F, 0x82, 0xFD, 0x23, 0x84,
+ 0xC8, 0x8B, 0xFD, 0x51, 0x8B, 0x2D, 0xC5, 0x7E, 0x90, 0x57, 0x65, 0x46, 0xB9, 0x5C, 0x24, 0x10,
+ 0xBD, 0xBD, 0x41, 0x12, 0x15, 0x47, 0x20, 0x03, 0xDE, 0x5B, 0x20, 0x4C, 0xDF, 0xF3, 0xBC, 0x74,
+ 0xD0, 0xA8, 0xD7, 0x5F, 0x48, 0x61, 0x26, 0x29, 0x8C, 0xB0, 0xA3, 0x10, 0x3D, 0x44, 0xCD, 0xB6,
+ 0x90, 0x28, 0x42, 0xBC, 0xB9, 0x32, 0x66, 0x51, 0x44, 0x84, 0x61, 0x57, 0xE7, 0x6B, 0x21, 0x07,
+ 0x1D, 0xD3, 0x96, 0x90, 0x57, 0xA8, 0xD9, 0x33, 0x15, 0x51, 0xA4, 0xD0, 0x3E, 0x86, 0xF0, 0x9F,
+ 0xDA, 0x9D, 0x51, 0x18, 0x31, 0xD6, 0xB5, 0x1A, 0x68, 0x9E, 0xA6, 0x83, 0x06, 0xF1, 0xC7, 0x95,
+ 0x87, 0x66, 0x18, 0x3A, 0x53, 0xD8, 0x90, 0x9B, 0x02, 0x46, 0x6D, 0x96, 0x87, 0xFE, 0x0B, 0x32,
+ 0xF5, 0x60, 0xB8, 0x2E, 0xBB, 0xD1, 0x6C, 0xD0, 0x3A, 0xBD, 0xC5, 0x1A, 0x7B, 0x1C, 0x5B, 0x8F,
+ 0x12, 0xC5, 0xE1, 0x28, 0xD7, 0xA6, 0x4F, 0x4E, 0x3A, 0xD2, 0xF3, 0xC3, 0xE2, 0x6A, 0xD0, 0x4A,
+ 0xB9, 0x44, 0xBB, 0x53, 0xEC, 0x3D, 0x69, 0xD3, 0xD0, 0x04, 0x33, 0xD0, 0x65, 0x7C, 0x0E, 0x99,
+ 0x86, 0xA0, 0xCC, 0x31, 0x26, 0x18, 0x87, 0x12, 0xBD, 0xE5, 0x37, 0x05, 0x25, 0xC0, 0xAF, 0xAC,
+ 0x40, 0x31, 0xE0, 0x6C, 0x94, 0x35, 0x28, 0xBE, 0xC4, 0x85, 0xD9, 0x83, 0xF2, 0x66, 0xA5, 0xEB,
+ 0x9C, 0xB3, 0x8E, 0xF9, 0x3B, 0x8F, 0x0A, 0xDD, 0xF8, 0x06, 0x7E, 0x9C, 0x1C, 0x64, 0x5E, 0x67,
+ 0x37, 0xA7, 0x48, 0x61, 0x6C, 0x8F, 0x5D, 0x7A, 0x3C, 0xDE, 0x0E, 0x3F, 0x76, 0x5C, 0x60, 0xBB,
+ 0xD3, 0x79, 0xED, 0xAC, 0x8E, 0x8B, 0xDF, 0x2E, 0x50, 0xBA, 0xCC, 0xF4, 0x0E, 0xDF, 0xE1, 0xC3,
+ 0x69, 0x87, 0x93, 0xF0, 0x14, 0x8C, 0x5E, 0x5B, 0xBC, 0x27, 0x5E, 0xCF, 0xE0, 0x41, 0x62, 0xD0,
+ 0xC2, 0xE0, 0xFB, 0x0F, 0x7B, 0xF0, 0xC7, 0xCC, 0x37, 0x11, 0x82, 0x79, 0xDE, 0x34, 0x5A, 0x09,
+ 0xA6, 0x39, 0xF2, 0xDF, 0x0B, 0xD7, 0xD2, 0xA1, 0x88, 0xA2, 0x13, 0xCB, 0xAA, 0x4C, 0xF4, 0xFD,
+ 0x2B, 0x58, 0x1F, 0x20, 0x06, 0x20, 0x22, 0x67, 0x0C, 0x4F, 0x87, 0xA9, 0x4E, 0x73, 0xED, 0xDB,
+ 0x13, 0xDE, 0xE2, 0xC0, 0x68, 0x50, 0x96, 0x82, 0x8B, 0x47, 0x0A, 0x96, 0xF8, 0x35, 0xF4, 0x50,
+ 0xFB, 0xB5, 0x5E, 0x13, 0x8F, 0x62, 0xFA, 0x3C, 0xC2, 0xF6, 0x87, 0x84, 0x27, 0x79, 0xDE, 0x9C,
+ 0x00, 0x5C, 0x19, 0xBA, 0x86, 0x90, 0xB8, 0x3A, 0x97, 0xA0, 0x9E, 0x3C, 0xA5, 0x7C, 0xE0, 0x9D,
+ 0xB0, 0x37, 0x2C, 0xCE, 0x70, 0xF2, 0x14, 0x27, 0x58, 0xDB, 0x3E, 0x34, 0x9B, 0x8D, 0xFA, 0x7E,
+ 0xE3, 0xD7, 0x7A, 0xB3, 0xDE, 0xA8, 0x1F, 0xC9, 0xFC, 0x11, 0x79, 0x00, 0x0C, 0xCD, 0xD8, 0x5F,
+ 0x96, 0xB9, 0x19, 0x63, 0xF3, 0xFA, 0xC3, 0xED, 0x47, 0xB3, 0xFE, 0x3E, 0xFF, 0x7E, 0x1C, 0xD4,
+ 0xDF, 0xAD, 0x6A, 0x3F, 0xD4, 0xBC, 0xB6, 0x62, 0x3F, 0xE0, 0x1B, 0x3C, 0x0C, 0x5A, 0x53, 0x4E,
+ 0x8F, 0x5C, 0x1B, 0x29, 0xCF, 0xD2, 0x1C, 0x5B, 0x39, 0xFD, 0x5A, 0xAE, 0x32, 0x92, 0xE0, 0x62,
+ 0x00, 0x38, 0xC7, 0x1A, 0x3C, 0xC4, 0x8E, 0xE5, 0x5F, 0xF9, 0xD2, 0x84, 0xE4, 0xDC, 0xEB, 0x45,
+ 0xBE, 0x55, 0x66, 0x6D, 0x20, 0x46, 0x61, 0xF2, 0x52, 0x5A, 0x9D, 0xFA, 0x7B, 0x11, 0xEB, 0xCB,
+ 0xB1, 0xE5, 0x93, 0xFD, 0x11, 0xA6, 0x68, 0xAC, 0x85, 0x53, 0xF4, 0x4E, 0x4C, 0xCF, 0x8B, 0x7F,
+ 0x37, 0x46, 0xE7, 0x79, 0x23, 0x9E, 0x8F, 0x57, 0xA6, 0xAA, 0x45, 0xE1, 0x6F, 0x46, 0x1E, 0x5E,
+ 0x99, 0xF9, 0xF5, 0x55, 0x61, 0x45, 0x2E, 0x3B, 0xFB, 0xB3, 0x9B, 0x91, 0xEF, 0xB9, 0x1E, 0x4B,
+ 0xF7, 0x3C, 0x33, 0x95, 0xF3, 0xF4, 0x6C, 0xCA, 0x4A, 0x1C, 0x94, 0x8B, 0xC3, 0x94, 0xAE, 0x81,
+ 0x3F, 0x29, 0x19, 0x7D, 0xC9, 0x00, 0x21, 0xDB, 0x48, 0xE9, 0x53, 0xC9, 0xB7, 0x07, 0xDE, 0x64,
+ 0xF9, 0x3B, 0xDB, 0x5E, 0x91, 0x95, 0x48, 0x3C, 0xA5, 0xFB, 0x62, 0x00, 0xB4, 0x0F, 0x0A, 0x01,
+ 0xFC, 0xDD, 0x20, 0x73, 0x45, 0xA3, 0x19, 0x3E, 0xAA, 0x4F, 0xCB, 0xC1, 0x31, 0x25, 0x03, 0x91,
+ 0x76, 0xCF, 0x4F, 0x85, 0x3D, 0xA6, 0xA5, 0xC9, 0xF4, 0x0F, 0x49, 0x52, 0xC2, 0x77, 0xE5, 0x7D,
+ 0x31, 0x65, 0x13, 0x82, 0x4F, 0x13, 0x71, 0x25, 0x75, 0xFD, 0x3D, 0x66, 0x5C, 0xC0, 0xAE, 0xD0,
+ 0x90, 0xD0, 0x88, 0x0C, 0x09, 0xD3, 0x09, 0x31, 0x94, 0xEE, 0xE5, 0x6C, 0x4B, 0x15, 0x2C, 0x4F,
+ 0xF4, 0x10, 0x86, 0x4E, 0x16, 0x09, 0xA8, 0x9C, 0xE4, 0x82, 0x91, 0x63, 0xDB, 0xE4, 0x56, 0x15,
+ 0xDE, 0x1C, 0xAC, 0x4C, 0x98, 0xB5, 0x39, 0x4E, 0x20, 0x70, 0xBA, 0x99, 0x9B, 0x84, 0x6D, 0x46,
+ 0x9B, 0x74, 0x81, 0xB5, 0x0D, 0x41, 0xE9, 0x9D, 0xB8, 0x51, 0x14, 0xC4, 0x38, 0xBE, 0x3F, 0x1E,
+ 0x1F, 0x3A, 0xF7, 0xCA, 0xEE, 0x13, 0x8D, 0x52, 0x62, 0xC3, 0xD4, 0x0A, 0xB2, 0x36, 0x2C, 0x47,
+ 0xF4, 0xAA, 0xDB, 0xFA, 0x57, 0xBB, 0xEB, 0xBB, 0x7F, 0xCE, 0xBB, 0x1D, 0xA9, 0x47, 0xE9, 0x8F,
+ 0xBE, 0xEE, 0x99, 0xAE, 0x60, 0xBE, 0xA7, 0x03, 0x1E, 0x78, 0xFA, 0x1E, 0x46, 0x62, 0xD7, 0xFE,
+ 0x46, 0x33, 0x90, 0x5F, 0xC9, 0x77, 0x63, 0xCD, 0x3E, 0xC4, 0x9F, 0x93, 0x08, 0xE1, 0xD8, 0x54,
+ 0x04, 0xCA, 0xC1, 0x2C, 0x7F, 0x3E, 0xFB, 0xC8, 0x0C, 0x47, 0x0F, 0xD0, 0xBE, 0x57, 0xFB, 0x2D,
+ 0xE0, 0xDE, 0x93, 0x54, 0x94, 0x1C, 0x0F, 0x54, 0xA5, 0x9D, 0xD7, 0x35, 0x21, 0x5E, 0xBF, 0x89,
+ 0x5A, 0x86, 0x6D, 0x6A, 0x20, 0x7E, 0x5C, 0x68, 0xFA, 0x60, 0x47, 0xB0, 0x8F, 0x2D, 0xF6, 0xEF,
+ 0x89, 0xF9, 0x63, 0xBE, 0xAB, 0xAE, 0xE3, 0x08, 0x5F, 0x78, 0x9A, 0x5B, 0xBB, 0x93, 0x2D, 0x76,
+ 0xC4, 0xA8, 0x93, 0xFF, 0x78, 0xA3, 0x66, 0x18, 0xCE, 0x0B, 0x36, 0x01, 0x34, 0x70, 0xE0, 0x3D,
+ 0x1F, 0xF6, 0x06, 0x62, 0x08, 0x0C, 0xF8, 0xFF, 0x03, 0x5E, 0x8B, 0x87, 0x84, 0x17, 0xDA, 0x00
+}; ///index_html
+
+//Content of bootstrap.bundle.min.jss with gzip compression
+static const uint8_t bootstrap_bundle_min_js[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x21, 0x7F, 0x4E, 0x61, 0x04, 0x00, 0x62, 0x6F, 0x6F, 0x74, 0x73, 0x74,
+ 0x72, 0x61, 0x70, 0x2E, 0x62, 0x75, 0x6E, 0x64, 0x6C, 0x65, 0x2E, 0x6D, 0x69, 0x6E, 0x2E, 0x6A,
+ 0x73, 0x00, 0xCC, 0x5D, 0xE9, 0x76, 0xDB, 0xC6, 0x15, 0xFE, 0xDF, 0xA7, 0x90, 0xD0, 0x54, 0x05,
+ 0x22, 0x88, 0x92, 0xBA, 0xFD, 0x00, 0x83, 0xF0, 0x28, 0xB2, 0xD2, 0xB8, 0xB1, 0x2D, 0xD7, 0x56,
+ 0xEC, 0x24, 0x0C, 0xEB, 0x42, 0xE4, 0x48, 0x84, 0x4D, 0x01, 0x0C, 0x00, 0x4A, 0x56, 0x48, 0xBE,
+ 0x7B, 0xBF, 0x7B, 0x67, 0xC5, 0x42, 0x4A, 0x4E, 0xDB, 0xD3, 0xE6, 0x38, 0x22, 0x30, 0x98, 0xF5,
+ 0xCE, 0x9D, 0xBB, 0xCF, 0xCC, 0xE1, 0xE7, 0xBB, 0xBF, 0xD9, 0xD9, 0xF9, 0x7C, 0xE7, 0xAB, 0x3C,
+ 0xAF, 0xCA, 0xAA, 0x48, 0xE6, 0x3B, 0xB7, 0x7F, 0xEE, 0x1D, 0xF5, 0xFE, 0xB0, 0xE3, 0x4F, 0xAB,
+ 0x6A, 0x5E, 0x46, 0x87, 0x87, 0xD7, 0xA2, 0xBA, 0xD4, 0x1F, 0x7B, 0xE3, 0xFC, 0xE6, 0x30, 0xE0,
+ 0x02, 0xA7, 0xF9, 0xFC, 0xBE, 0x48, 0xAF, 0xA7, 0xD5, 0xCE, 0x1F, 0x8E, 0x8E, 0x8F, 0x0F, 0xFE,
+ 0x70, 0xF4, 0x87, 0xE3, 0x9D, 0x8B, 0xA9, 0x70, 0x2A, 0x3A, 0x59, 0x54, 0xD3, 0xBC, 0x28, 0x9D,
+ 0x9A, 0xD2, 0x6A, 0xBA, 0xB8, 0xE4, 0x3A, 0xAA, 0xBB, 0xCB, 0xF2, 0xD0, 0x54, 0x7B, 0x78, 0x8D,
+ 0x3F, 0xD3, 0xF2, 0x70, 0x9C, 0x67, 0x55, 0x91, 0x5E, 0x2E, 0x2A, 0x14, 0x93, 0xAD, 0x3C, 0x4B,
+ 0xC7, 0x22, 0x2B, 0xC5, 0x64, 0x67, 0x91, 0x4D, 0x44, 0xB1, 0xF3, 0xFC, 0xE9, 0xC5, 0x63, 0xAA,
+ 0xBB, 0x9C, 0xE5, 0x97, 0x87, 0x37, 0x49, 0x9A, 0x1D, 0x3E, 0x7B, 0x7A, 0x7A, 0xF6, 0xE2, 0xF5,
+ 0x19, 0x57, 0x76, 0xF8, 0x9B, 0xDD, 0xAB, 0x45, 0x36, 0xAE, 0xD2, 0x3C, 0xF3, 0xAB, 0x50, 0x04,
+ 0x4B, 0x2F, 0xBF, 0x7C, 0x2F, 0xC6, 0x95, 0x17, 0xC7, 0xD5, 0xFD, 0x5C, 0xE4, 0x57, 0x3B, 0xE2,
+ 0xE3, 0x3C, 0x2F, 0xAA, 0x72, 0x6F, 0xCF, 0xA3, 0xE6, 0xAE, 0xD2, 0x4C, 0x4C, 0xBC, 0x5D, 0xFD,
+ 0xF1, 0x26, 0x9F, 0x2C, 0x66, 0x62, 0x20, 0x7F, 0x7A, 0x2A, 0x6B, 0x2C, 0xFC, 0x20, 0xF2, 0x74,
+ 0xB5, 0xB6, 0x26, 0x59, 0x7A, 0x6F, 0x4F, 0xFE, 0xF6, 0x92, 0x9B, 0xC9, 0x40, 0x3E, 0xFA, 0x22,
+ 0x88, 0xFC, 0x2A, 0xEE, 0x6A, 0xE0, 0x1A, 0xBD, 0x4E, 0x66, 0x17, 0xD3, 0xB4, 0x1C, 0xD8, 0xC7,
+ 0xA8, 0x5A, 0xAD, 0x4A, 0x31, 0xBB, 0x0A, 0x7A, 0x66, 0x78, 0xD4, 0xE6, 0xDA, 0xAF, 0xF0, 0x31,
+ 0xF4, 0xCD, 0x80, 0x30, 0x9A, 0x45, 0x29, 0x76, 0x90, 0x21, 0xC5, 0x88, 0xFA, 0x80, 0x64, 0x59,
+ 0xED, 0x54, 0xF1, 0x12, 0x6D, 0x4C, 0x22, 0x1A, 0x6E, 0x3C, 0xC9, 0xC7, 0x8B, 0x1B, 0x91, 0x55,
+ 0x3D, 0xFD, 0x70, 0x36, 0x13, 0xF4, 0x13, 0xC4, 0x5F, 0x0E, 0x47, 0x3D, 0x14, 0x18, 0x27, 0x95,
+ 0xDF, 0xEB, 0xF5, 0x54, 0x72, 0x6F, 0x5E, 0xE4, 0x55, 0x4E, 0x5D, 0xEB, 0xFD, 0xBC, 0x10, 0xC5,
+ 0xFD, 0x6B, 0x31, 0x13, 0x63, 0x4C, 0xCC, 0xC9, 0x6C, 0xD6, 0x1B, 0x27, 0xB3, 0x99, 0x2F, 0xC2,
+ 0x2A, 0x08, 0x42, 0xAA, 0xFF, 0x3C, 0x13, 0x0F, 0x36, 0xF1, 0x40, 0xAD, 0xB6, 0xCA, 0x70, 0x3C,
+ 0x4D, 0x67, 0x93, 0x42, 0x64, 0x5C, 0x65, 0xB3, 0x73, 0x55, 0x4F, 0x7F, 0x0E, 0x7A, 0x57, 0xE9,
+ 0xAC, 0x12, 0x05, 0x80, 0xF9, 0x65, 0xD5, 0xBB, 0x49, 0xAA, 0xF1, 0x54, 0x94, 0xBE, 0x40, 0x97,
+ 0xE6, 0x09, 0x3E, 0x57, 0xA5, 0x9C, 0x63, 0x09, 0x88, 0x34, 0x1E, 0x8E, 0xFA, 0x33, 0x51, 0xED,
+ 0x64, 0x71, 0xD5, 0x93, 0xDF, 0x5F, 0xE4, 0x13, 0xD1, 0xBF, 0xCA, 0x0B, 0xBF, 0x9F, 0xED, 0xED,
+ 0x65, 0xBD, 0x0C, 0xAF, 0x17, 0xE8, 0x56, 0x1C, 0xC7, 0xF4, 0xA5, 0x77, 0xF6, 0xEC, 0xEC, 0xF9,
+ 0xD9, 0x8B, 0x8B, 0x77, 0x2F, 0xCE, 0x9F, 0x9C, 0xED, 0xED, 0xFD, 0x71, 0x37, 0x8E, 0x6D, 0x9E,
+ 0x7E, 0x90, 0x39, 0x0D, 0xEE, 0xED, 0xA5, 0xBD, 0xF9, 0xA2, 0x9C, 0xFA, 0x59, 0x10, 0x66, 0xC8,
+ 0xE5, 0x54, 0x5F, 0x88, 0x6A, 0x51, 0x64, 0x3B, 0xE9, 0x3A, 0x9C, 0x17, 0xE2, 0x56, 0x76, 0x88,
+ 0x7A, 0x91, 0xA2, 0x17, 0x94, 0x92, 0xE6, 0x8B, 0x52, 0x01, 0xE6, 0x75, 0x7A, 0x39, 0x4B, 0xB3,
+ 0x6B, 0xD9, 0xA3, 0xB4, 0x1F, 0x2C, 0xD3, 0x2B, 0x3F, 0x75, 0x87, 0x25, 0xEB, 0x1A, 0xA6, 0xA3,
+ 0x7E, 0x1A, 0xA7, 0x1B, 0x4A, 0xAF, 0x55, 0xA6, 0xD1, 0x3A, 0xCC, 0xC4, 0xC7, 0xAA, 0xDE, 0x20,
+ 0xA5, 0xFC, 0xBA, 0xC6, 0x6C, 0xC9, 0x76, 0x43, 0x6B, 0x4C, 0x3B, 0x66, 0x60, 0x39, 0xC9, 0x97,
+ 0xD5, 0x7E, 0xFC, 0x3C, 0xA9, 0xA6, 0xBD, 0xAB, 0x59, 0x8E, 0x7A, 0x8F, 0xC5, 0x5F, 0x3E, 0xE7,
+ 0xD7, 0x22, 0xC9, 0x26, 0xF9, 0x8D, 0x1F, 0x04, 0xEB, 0x3B, 0x4C, 0x9D, 0xF0, 0x0D, 0x92, 0x5C,
+ 0x0B, 0x5D, 0xEB, 0x57, 0xF7, 0x4F, 0x27, 0x3E, 0xB0, 0x49, 0x03, 0xAC, 0x5A, 0x87, 0x29, 0xD5,
+ 0xCA, 0x7D, 0x47, 0xFD, 0x94, 0xF5, 0xA4, 0x92, 0xA4, 0x41, 0xF8, 0xDE, 0x24, 0xA9, 0x92, 0x83,
+ 0xCB, 0xF2, 0xA0, 0x4A, 0x0A, 0x7C, 0xF0, 0x82, 0x3E, 0xBA, 0xBF, 0x2B, 0x56, 0x2B, 0xEF, 0xB7,
+ 0x1E, 0xA6, 0x4F, 0x0F, 0xB9, 0x55, 0x6C, 0x5A, 0x88, 0x2B, 0x95, 0x39, 0x5D, 0xAD, 0x76, 0xD3,
+ 0x5E, 0x9A, 0x8D, 0x67, 0x8B, 0x09, 0x46, 0x8C, 0x82, 0x98, 0x46, 0xA4, 0x94, 0xA8, 0xB2, 0x2A,
+ 0xDF, 0x82, 0xB0, 0xF8, 0x5E, 0xCF, 0xD3, 0x70, 0xD8, 0xC9, 0x16, 0xB3, 0x59, 0x7F, 0x7B, 0x7E,
+ 0x95, 0xE6, 0xA7, 0x31, 0x1E, 0xF6, 0xF1, 0x65, 0x3E, 0x4B, 0x2B, 0x4E, 0x1C, 0x1E, 0x8F, 0x02,
+ 0x00, 0x29, 0xDD, 0xDB, 0xC3, 0x1B, 0xF0, 0x28, 0x1D, 0xA4, 0x3D, 0xF4, 0x09, 0x10, 0x89, 0xA8,
+ 0xDE, 0xB5, 0x6A, 0x43, 0x60, 0xCE, 0x68, 0xCC, 0x0A, 0x63, 0x51, 0xC0, 0xAF, 0x0C, 0x40, 0x40,
+ 0x49, 0x34, 0xD8, 0xEA, 0xEB, 0x06, 0x93, 0x35, 0x10, 0xB2, 0x9E, 0xB0, 0xDC, 0x5C, 0x7C, 0xB0,
+ 0xB1, 0xB4, 0x2A, 0x9B, 0x73, 0x59, 0x2C, 0xDC, 0xB4, 0x9C, 0x13, 0x1A, 0x9C, 0xDD, 0x22, 0xB3,
+ 0x9F, 0x89, 0xBB, 0x1D, 0xF9, 0xE4, 0x55, 0x98, 0xC6, 0x32, 0x25, 0x52, 0x23, 0xB2, 0x89, 0x87,
+ 0xC9, 0x0C, 0x0B, 0x2A, 0xB2, 0xEB, 0xEF, 0x82, 0x40, 0x29, 0x42, 0x6A, 0x49, 0x59, 0x45, 0xA0,
+ 0xB8, 0xCD, 0xD3, 0xC9, 0xCE, 0x11, 0x46, 0x5C, 0xF5, 0xDE, 0x73, 0xB3, 0x48, 0xAB, 0xE2, 0x6A,
+ 0x78, 0x04, 0x78, 0xD8, 0x6F, 0x66, 0x55, 0x05, 0x61, 0x12, 0x8B, 0xF8, 0x4B, 0x39, 0x24, 0x55,
+ 0x62, 0x20, 0x90, 0x3B, 0x12, 0x91, 0x47, 0x74, 0x2D, 0xBB, 0x36, 0xF4, 0x95, 0x00, 0x22, 0x7A,
+ 0x33, 0x91, 0x5D, 0x57, 0xD3, 0x2F, 0x8F, 0x06, 0x55, 0x4F, 0x91, 0x21, 0x3D, 0xA0, 0x70, 0x16,
+ 0x13, 0xF2, 0x87, 0x69, 0x80, 0x51, 0x9D, 0x73, 0xE7, 0x7A, 0x1F, 0xC4, 0x7D, 0xE9, 0xA7, 0x41,
+ 0x0F, 0x48, 0x7F, 0x96, 0x8C, 0xB1, 0x5C, 0x0D, 0xB0, 0xCA, 0x38, 0x1D, 0x66, 0x23, 0xC0, 0x40,
+ 0xE0, 0x07, 0xBD, 0xC8, 0xF7, 0xF6, 0x0A, 0x3F, 0x0F, 0x06, 0x9E, 0x90, 0xE8, 0xE9, 0x71, 0x9D,
+ 0x71, 0xEC, 0xCF, 0x62, 0x4A, 0xF5, 0xF6, 0x67, 0xD1, 0x72, 0xDD, 0xAB, 0xF2, 0xD7, 0xDC, 0x29,
+ 0x49, 0xBA, 0x66, 0x81, 0x5C, 0x3F, 0xFE, 0xE1, 0x4F, 0xA5, 0x3F, 0x4C, 0x0E, 0x7E, 0x19, 0xED,
+ 0x07, 0x87, 0x29, 0x4D, 0x3E, 0x32, 0x3E, 0xCB, 0xEF, 0x44, 0x71, 0x9A, 0x94, 0x20, 0xDC, 0xFD,
+ 0xDB, 0xA4, 0xD8, 0x99, 0x31, 0x12, 0x12, 0x74, 0x5F, 0x89, 0xEB, 0xB3, 0x8F, 0x73, 0xBF, 0x0C,
+ 0x7A, 0x95, 0x28, 0x2B, 0x3F, 0x09, 0x82, 0x6A, 0x5A, 0xE4, 0x77, 0x3B, 0xF4, 0x8D, 0x80, 0x72,
+ 0x56, 0x14, 0x98, 0xA6, 0x7F, 0x7E, 0xB6, 0xAC, 0x50, 0xCD, 0x77, 0xF3, 0xB9, 0xAE, 0x66, 0x1D,
+ 0xED, 0x9C, 0xCF, 0x69, 0x36, 0x76, 0xBC, 0xCF, 0x96, 0xD9, 0xDA, 0xDB, 0x01, 0x5D, 0xBD, 0x4D,
+ 0x27, 0x60, 0x94, 0x04, 0x20, 0x4A, 0x4C, 0x90, 0x08, 0xBC, 0x27, 0x9E, 0x86, 0xD1, 0x3B, 0x1F,
+ 0xCA, 0xB5, 0xD7, 0xFB, 0x67, 0xB0, 0xC6, 0x0C, 0x8E, 0xD5, 0x0C, 0x16, 0x40, 0x95, 0xD5, 0xEA,
+ 0x28, 0x8E, 0xE5, 0x92, 0x39, 0x9D, 0xA5, 0x18, 0xF4, 0x2B, 0x94, 0x2A, 0xFD, 0x40, 0x01, 0x19,
+ 0xF3, 0xE9, 0xDD, 0xA6, 0x25, 0x56, 0xBF, 0xA0, 0x35, 0x46, 0xB9, 0xF2, 0x9B, 0xF9, 0x02, 0xF5,
+ 0xBE, 0xAE, 0xEE, 0x67, 0x02, 0x15, 0x50, 0xC9, 0x97, 0x45, 0x8E, 0x1E, 0x56, 0xF7, 0x6F, 0x92,
+ 0xD9, 0x42, 0xF8, 0xB2, 0x40, 0x8A, 0x75, 0x70, 0xEF, 0x05, 0xE1, 0x94, 0x1A, 0x23, 0x64, 0xB1,
+ 0x33, 0xBE, 0xDB, 0x41, 0x6B, 0xB1, 0x30, 0x77, 0x41, 0xE4, 0x67, 0x49, 0x59, 0x3E, 0x4B, 0x4B,
+ 0x3C, 0x41, 0x24, 0x00, 0x23, 0xC7, 0x9A, 0x03, 0x6A, 0x26, 0x68, 0x7E, 0xE2, 0xA1, 0xAF, 0x2E,
+ 0x6E, 0xE9, 0xF4, 0x81, 0x7D, 0x8C, 0xAA, 0xDE, 0x34, 0x29, 0xED, 0xD2, 0xB7, 0x65, 0x69, 0x1C,
+ 0x57, 0xC9, 0xAC, 0x14, 0xDE, 0xAE, 0x1A, 0x6D, 0x67, 0xAE, 0x20, 0x9C, 0xF0, 0x82, 0xA0, 0x69,
+ 0xDA, 0xC4, 0xD4, 0x7A, 0x49, 0x55, 0x01, 0x8F, 0x5E, 0x4F, 0x93, 0x49, 0x7E, 0x57, 0xA3, 0x14,
+ 0x28, 0xD5, 0x21, 0x12, 0x70, 0x6B, 0xAF, 0xF2, 0x9C, 0x59, 0x43, 0x60, 0xD6, 0x69, 0x2D, 0xD9,
+ 0xB7, 0x4B, 0x76, 0x27, 0xC5, 0xF7, 0x24, 0x1B, 0x53, 0x51, 0xD9, 0x06, 0x65, 0xD2, 0x8B, 0x5D,
+ 0x13, 0xCA, 0x0D, 0xB9, 0xAA, 0xC8, 0x65, 0x73, 0x83, 0x89, 0xEF, 0xBC, 0x9A, 0x35, 0xBF, 0x88,
+ 0x7D, 0x5A, 0x1E, 0xEB, 0xF0, 0x2A, 0x66, 0x1E, 0x9A, 0x5F, 0x5D, 0x95, 0xA2, 0xFA, 0x46, 0x90,
+ 0x50, 0x17, 0xCE, 0xF9, 0xA3, 0xEC, 0xE5, 0xF2, 0xFD, 0xDF, 0x69, 0x31, 0x46, 0xD5, 0x3A, 0xBE,
+ 0x4B, 0x33, 0xB4, 0x61, 0xE8, 0xF4, 0xDE, 0x9E, 0x05, 0xCF, 0x65, 0x3E, 0xB9, 0x67, 0xB0, 0xB7,
+ 0x09, 0x75, 0x96, 0x1F, 0xC8, 0xF5, 0xEC, 0x05, 0x83, 0x4A, 0xB5, 0x7E, 0x03, 0x96, 0x1C, 0x5E,
+ 0x73, 0x2B, 0x5E, 0x51, 0xCD, 0x08, 0xAB, 0x36, 0x42, 0x7A, 0x92, 0x16, 0xE1, 0x3B, 0x9E, 0x10,
+ 0x5A, 0x3D, 0xA2, 0x2F, 0xB8, 0x98, 0x01, 0xE1, 0xDC, 0x67, 0xAA, 0xAE, 0x80, 0x2A, 0x49, 0xFF,
+ 0x8B, 0x93, 0xE7, 0x67, 0x20, 0xA8, 0xA2, 0x77, 0xC5, 0x6C, 0x4C, 0xFD, 0xE2, 0x83, 0x1C, 0xCB,
+ 0xD3, 0x0C, 0xA2, 0xC3, 0x55, 0x32, 0x16, 0xA1, 0xFA, 0xD2, 0x3B, 0xA5, 0xA2, 0xC5, 0x82, 0xC8,
+ 0x62, 0x5C, 0x99, 0xD4, 0x2C, 0x47, 0xFA, 0xD5, 0x0C, 0x32, 0x15, 0x37, 0xE9, 0xEB, 0x7A, 0xB2,
+ 0xB0, 0x55, 0x53, 0x00, 0x5E, 0xE8, 0xCD, 0xF2, 0x64, 0x22, 0x09, 0x95, 0x1D, 0x4D, 0x21, 0x92,
+ 0xC9, 0xFD, 0xEB, 0x2A, 0xA9, 0xC4, 0xC0, 0xBF, 0x51, 0xEB, 0x69, 0xB5, 0x32, 0x9F, 0x93, 0xC9,
+ 0x84, 0x69, 0x2C, 0xA1, 0xBB, 0xC8, 0x20, 0xD0, 0x78, 0x4F, 0xCE, 0x9F, 0xA3, 0xD5, 0x8A, 0xD2,
+ 0x50, 0x1D, 0xF0, 0x31, 0xE4, 0xE1, 0xDE, 0x18, 0xD2, 0x45, 0xD3, 0x45, 0x0C, 0x35, 0x08, 0x6F,
+ 0x58, 0xF4, 0x20, 0x96, 0x1D, 0x11, 0x55, 0x08, 0x2F, 0x19, 0x4A, 0x6D, 0xEC, 0xA3, 0xA9, 0xAA,
+ 0x28, 0xC3, 0xAD, 0x22, 0x8C, 0xF1, 0xEE, 0x11, 0xD5, 0xC9, 0xCC, 0x50, 0x23, 0x2F, 0x2F, 0xAA,
+ 0x4B, 0xE2, 0x1B, 0x12, 0x8E, 0x19, 0xF2, 0xAA, 0x3C, 0x95, 0xCE, 0x73, 0x44, 0x62, 0xD4, 0xD2,
+ 0x72, 0x83, 0x27, 0x8B, 0x22, 0xA1, 0xDF, 0x48, 0x84, 0x4E, 0xA2, 0x98, 0x25, 0xF7, 0x51, 0xAA,
+ 0xD1, 0xA5, 0xD7, 0x26, 0x17, 0xB6, 0x89, 0x17, 0x8B, 0x9B, 0x4B, 0x51, 0x10, 0x7A, 0x96, 0xE2,
+ 0x6B, 0x80, 0xAF, 0xC2, 0x60, 0xC2, 0xB2, 0x23, 0x39, 0x35, 0x6B, 0x23, 0x83, 0x48, 0x3C, 0xF0,
+ 0x45, 0x2C, 0x34, 0x97, 0x0D, 0xBD, 0x00, 0x7C, 0x02, 0x83, 0x4A, 0x1B, 0x29, 0xC7, 0xE2, 0x8F,
+ 0x9F, 0xFB, 0x5D, 0x2D, 0xEC, 0x77, 0xD5, 0x0F, 0x20, 0x1E, 0xAD, 0x03, 0xFA, 0xFA, 0x67, 0x8C,
+ 0x92, 0xF8, 0xC2, 0xEE, 0xB1, 0xEA, 0x67, 0x11, 0xFB, 0x4B, 0x29, 0x6E, 0x60, 0x58, 0x0C, 0x38,
+ 0x92, 0x34, 0xC0, 0xD0, 0x90, 0xE7, 0x28, 0x14, 0x98, 0xE3, 0x9B, 0xFC, 0x56, 0xD4, 0xE6, 0xB1,
+ 0xC9, 0x33, 0xC3, 0x22, 0x08, 0x09, 0xB8, 0xC1, 0xBA, 0x2F, 0x68, 0xD2, 0x1F, 0xCC, 0x8C, 0xC5,
+ 0x78, 0x91, 0xDE, 0x88, 0x7C, 0x51, 0xF9, 0x3C, 0xFF, 0xE5, 0x6A, 0x95, 0xA3, 0x73, 0x90, 0x13,
+ 0xF0, 0xFF, 0xBD, 0x9A, 0x47, 0xBC, 0xE0, 0x93, 0xEC, 0x6D, 0xD5, 0x03, 0xB8, 0xC5, 0xC7, 0x73,
+ 0x2C, 0x05, 0x5E, 0x10, 0x07, 0xC7, 0xE8, 0x64, 0xA9, 0x67, 0xAE, 0x1A, 0xEE, 0xA6, 0x90, 0x74,
+ 0x07, 0x95, 0x42, 0xC1, 0x83, 0xE3, 0xE8, 0x68, 0xA4, 0x86, 0x97, 0xC7, 0x3A, 0x55, 0xC3, 0xB8,
+ 0xDC, 0x87, 0xA0, 0x72, 0x1C, 0x1D, 0x1C, 0x87, 0x19, 0x8F, 0xD2, 0x2F, 0xF7, 0xF3, 0xE0, 0x77,
+ 0x79, 0x10, 0x56, 0x43, 0x96, 0xEA, 0x6E, 0x92, 0x8F, 0xFE, 0x51, 0x28, 0x1F, 0xD3, 0xCC, 0x2F,
+ 0xC3, 0xFC, 0xE0, 0x38, 0x08, 0x20, 0x78, 0xDE, 0xC5, 0x87, 0xC3, 0x7F, 0xF4, 0x46, 0x9F, 0xFB,
+ 0x83, 0xF8, 0xA7, 0x5E, 0xEF, 0xF3, 0xE0, 0xA7, 0xDE, 0xAA, 0xF7, 0xF9, 0x61, 0x78, 0x16, 0x1F,
+ 0xE2, 0x15, 0x0F, 0x27, 0xF1, 0x61, 0x14, 0xFD, 0x34, 0xD9, 0xFF, 0xEC, 0x30, 0xBC, 0x88, 0x97,
+ 0x6B, 0x06, 0xF4, 0x79, 0xAC, 0xE1, 0x7C, 0x1A, 0x2F, 0x6F, 0x72, 0x28, 0x32, 0x82, 0x56, 0x55,
+ 0xE4, 0xF1, 0x33, 0x00, 0x5B, 0x78, 0x21, 0x3F, 0xCE, 0x44, 0x72, 0x2B, 0x74, 0xF2, 0xA2, 0xF2,
+ 0xD6, 0xE1, 0x87, 0xF8, 0xF0, 0x1F, 0xBE, 0x2D, 0xB2, 0xB2, 0xD9, 0xC0, 0x7E, 0xC3, 0x67, 0x31,
+ 0x31, 0xD2, 0xD7, 0xA2, 0xF2, 0x87, 0xDE, 0x18, 0xAB, 0xF8, 0x03, 0x10, 0x63, 0x72, 0x39, 0xD3,
+ 0x8F, 0x9C, 0x79, 0x31, 0xD7, 0x4F, 0x40, 0xD5, 0x0C, 0xCF, 0xC4, 0x72, 0x20, 0xF1, 0x62, 0x71,
+ 0x2E, 0xF4, 0x97, 0xBB, 0xA9, 0x10, 0x33, 0xBC, 0x60, 0x65, 0x3E, 0xA7, 0xF7, 0xD7, 0xE3, 0x22,
+ 0x9F, 0x21, 0xC1, 0xED, 0xA0, 0xE9, 0x95, 0x7A, 0x64, 0x8C, 0xC0, 0x73, 0xC9, 0xC2, 0x16, 0x4B,
+ 0x8A, 0xE6, 0x8D, 0xA7, 0xD8, 0x83, 0x44, 0x42, 0x4D, 0xCA, 0x27, 0x48, 0xF4, 0x65, 0x29, 0x1F,
+ 0xB9, 0x47, 0x79, 0x41, 0x5C, 0x98, 0xD7, 0xD6, 0x78, 0x9A, 0x64, 0xD7, 0x54, 0x55, 0x95, 0x2F,
+ 0xC6, 0x53, 0xAE, 0x49, 0xBF, 0x50, 0x1B, 0xFA, 0x19, 0x95, 0xEA, 0xC7, 0x31, 0x58, 0x02, 0x77,
+ 0x78, 0x9E, 0xA7, 0x04, 0x16, 0x6A, 0xC6, 0xBE, 0x51, 0x21, 0xFB, 0x86, 0xD6, 0xCC, 0x33, 0x03,
+ 0xCE, 0xBE, 0x9A, 0x6A, 0xAE, 0x45, 0x09, 0xAC, 0x10, 0xDC, 0xB4, 0x7D, 0x95, 0xFD, 0xB2, 0xEF,
+ 0xB2, 0x03, 0x57, 0x20, 0x6C, 0x34, 0x90, 0xCB, 0xD9, 0x82, 0xA0, 0x62, 0x32, 0xA1, 0xB8, 0x30,
+ 0x10, 0xE0, 0x87, 0xC5, 0xE5, 0x4D, 0x5A, 0xE9, 0x12, 0x69, 0xA6, 0x9F, 0x08, 0x84, 0x92, 0x92,
+ 0xE2, 0x67, 0x91, 0xA9, 0x87, 0x4B, 0x01, 0xEA, 0x27, 0xE4, 0xAB, 0xAC, 0x2D, 0xFD, 0x85, 0xAA,
+ 0x55, 0x83, 0x69, 0xD3, 0x4C, 0x8F, 0xE9, 0x2E, 0xBA, 0x5C, 0xD9, 0x8E, 0x0A, 0x12, 0xA7, 0xF0,
+ 0x9B, 0x5C, 0xE6, 0x72, 0x36, 0xE4, 0x3C, 0x8E, 0x82, 0xBE, 0xA6, 0x98, 0x3B, 0x1F, 0xA5, 0xD6,
+ 0x64, 0x05, 0x6F, 0x08, 0x5F, 0x62, 0x1D, 0x45, 0x9F, 0x2D, 0xCF, 0xF7, 0xF7, 0xD7, 0xFF, 0x24,
+ 0xF9, 0x65, 0x91, 0xCA, 0xC5, 0xBB, 0x5A, 0x51, 0x92, 0x29, 0xF9, 0xC4, 0xAF, 0x2C, 0x5B, 0xFF,
+ 0xE8, 0x88, 0xDF, 0xB6, 0x44, 0x2C, 0xC2, 0x8B, 0xA1, 0x18, 0xC5, 0xF4, 0x67, 0xB5, 0x5A, 0xAE,
+ 0xF9, 0xCD, 0xD6, 0xF0, 0x5A, 0xD1, 0x66, 0xE2, 0x90, 0xBA, 0xAA, 0x2C, 0x76, 0xE4, 0x57, 0x54,
+ 0xCA, 0x4A, 0x9B, 0x5C, 0xEF, 0x47, 0x10, 0x59, 0x33, 0xBD, 0x6E, 0xCB, 0x2F, 0xF2, 0x7E, 0xB9,
+ 0xBF, 0x8F, 0x62, 0x7A, 0x45, 0x0F, 0xB3, 0x61, 0x39, 0x1A, 0x11, 0x21, 0xC8, 0x7B, 0xC0, 0xA6,
+ 0xEB, 0x34, 0x4B, 0x66, 0xDF, 0x40, 0x19, 0x9B, 0x89, 0x42, 0x12, 0xAF, 0xBC, 0x37, 0xC1, 0x5C,
+ 0x5C, 0x33, 0x8E, 0x29, 0x95, 0x80, 0xBE, 0x18, 0x7E, 0x90, 0xAF, 0x1D, 0xA9, 0xC6, 0xF6, 0xF2,
+ 0xA9, 0xEC, 0xA5, 0xED, 0x60, 0x5B, 0x3A, 0x07, 0x01, 0xCF, 0x06, 0x69, 0x24, 0xB0, 0xBA, 0xA9,
+ 0x2B, 0xCF, 0x1D, 0x60, 0x3C, 0x23, 0x19, 0xC1, 0xCF, 0x49, 0x88, 0x43, 0x1F, 0x83, 0x70, 0x98,
+ 0x85, 0x20, 0x1E, 0x0E, 0x10, 0x5E, 0x28, 0xC2, 0x86, 0x74, 0x56, 0x4C, 0x75, 0xED, 0xBB, 0xA6,
+ 0xF6, 0xD5, 0xCA, 0xF0, 0x23, 0x1A, 0x5D, 0x8A, 0xAA, 0x00, 0xB3, 0x30, 0x93, 0x70, 0x0B, 0x3F,
+ 0x48, 0x59, 0x5A, 0x04, 0xBA, 0x87, 0x15, 0x31, 0x45, 0x5D, 0x3F, 0xD2, 0x99, 0xA3, 0x11, 0xCD,
+ 0x9E, 0x01, 0x37, 0x26, 0x17, 0x4C, 0xD6, 0x57, 0xAB, 0x46, 0xC2, 0x2E, 0x40, 0xA4, 0xE1, 0x23,
+ 0x38, 0x89, 0xA4, 0x9D, 0x66, 0x92, 0x95, 0x4F, 0x1B, 0xC5, 0x03, 0x43, 0x76, 0x59, 0x3F, 0x90,
+ 0xE6, 0x1A, 0x01, 0xDA, 0x9F, 0x0D, 0xA0, 0xE6, 0xC1, 0x56, 0x10, 0xA5, 0x31, 0xF1, 0x9C, 0x35,
+ 0x77, 0x71, 0x98, 0x87, 0x45, 0x98, 0x8C, 0xE2, 0xA7, 0xBE, 0x24, 0xE9, 0xD0, 0x5F, 0x08, 0x9B,
+ 0x20, 0xA1, 0xCF, 0x86, 0xC9, 0x08, 0xE3, 0xA3, 0x1F, 0x50, 0x4B, 0x12, 0xA3, 0x5F, 0xFB, 0x63,
+ 0x64, 0xCE, 0x01, 0x5E, 0x1E, 0x2E, 0x41, 0x60, 0xEA, 0x72, 0x70, 0x7F, 0xDA, 0x03, 0x0F, 0x39,
+ 0xBF, 0xBA, 0x8A, 0xF5, 0xC3, 0xDE, 0x5E, 0xA9, 0xF9, 0xED, 0x04, 0x88, 0x59, 0x30, 0xBF, 0x9A,
+ 0xCF, 0x20, 0xB3, 0xF8, 0x77, 0xA1, 0x47, 0xD2, 0xEE, 0x22, 0xCE, 0x07, 0x1A, 0x40, 0x7A, 0x76,
+ 0x55, 0x95, 0x66, 0x5E, 0x40, 0xE4, 0x1D, 0xE4, 0x6A, 0x19, 0x85, 0x7C, 0x61, 0x50, 0x53, 0x73,
+ 0xCA, 0x62, 0x1D, 0x97, 0xFD, 0x02, 0xCA, 0x14, 0x60, 0x89, 0xF1, 0xE3, 0x39, 0x2E, 0x5C, 0x39,
+ 0x54, 0x63, 0x32, 0x34, 0x2E, 0x8D, 0xC5, 0xC9, 0xC1, 0x41, 0x3F, 0x20, 0x8C, 0xA5, 0xF1, 0xC6,
+ 0x71, 0xA1, 0x07, 0x56, 0x36, 0xE0, 0x1E, 0x17, 0x61, 0x66, 0x46, 0xF7, 0x92, 0x44, 0x57, 0x74,
+ 0xBB, 0xEC, 0x11, 0x86, 0x70, 0xEF, 0xC3, 0xB4, 0x97, 0xCC, 0xE7, 0xB3, 0x7B, 0x0C, 0x76, 0x58,
+ 0x62, 0x79, 0xBB, 0x88, 0x0C, 0xDB, 0x19, 0x03, 0x39, 0x72, 0x47, 0xDC, 0x1E, 0x6F, 0xEA, 0x67,
+ 0x26, 0x31, 0x6B, 0x36, 0x8F, 0x1A, 0x5A, 0xCD, 0x67, 0xAA, 0x79, 0x68, 0xFB, 0xB2, 0x71, 0xA4,
+ 0x41, 0xA1, 0x0C, 0x64, 0x83, 0x41, 0x7F, 0xD1, 0xB5, 0xDC, 0xF4, 0x3C, 0x86, 0x8B, 0xD6, 0x1A,
+ 0x2D, 0x90, 0xA6, 0x66, 0xB2, 0xC4, 0xA3, 0xA1, 0x21, 0x93, 0x70, 0x3C, 0x9C, 0x8C, 0xE2, 0x45,
+ 0xD8, 0x21, 0x3B, 0x26, 0xE1, 0x22, 0xCC, 0x03, 0xBB, 0x98, 0xDE, 0xDB, 0xC5, 0x64, 0x27, 0xEF,
+ 0xB5, 0x2F, 0x20, 0xC3, 0x72, 0x5A, 0x3F, 0x27, 0xA5, 0xBC, 0x53, 0x7C, 0x49, 0xC3, 0x3C, 0x84,
+ 0x31, 0x17, 0x2C, 0x81, 0x66, 0x3E, 0x08, 0xA9, 0xF3, 0x95, 0xD8, 0xA1, 0xA2, 0xC3, 0xDC, 0xF4,
+ 0x66, 0xE4, 0x34, 0x86, 0x65, 0x6E, 0x20, 0x06, 0x10, 0x19, 0x24, 0x3B, 0x23, 0x24, 0x0B, 0x4F,
+ 0x87, 0x15, 0x30, 0xB9, 0x5A, 0xCB, 0x5E, 0xBC, 0x8C, 0x97, 0x12, 0xF0, 0x3C, 0x15, 0x4B, 0xBB,
+ 0xE8, 0x77, 0x8F, 0x21, 0xDD, 0x60, 0xD8, 0x9D, 0xDF, 0x8E, 0xE8, 0xDB, 0xD5, 0x95, 0x4E, 0x78,
+ 0x04, 0x7D, 0x90, 0xCB, 0xAB, 0xC4, 0x58, 0x0A, 0x67, 0x79, 0x25, 0x71, 0x41, 0xEB, 0xDB, 0x2E,
+ 0x33, 0xD1, 0x30, 0xFD, 0xD0, 0xB2, 0xB2, 0x4A, 0x66, 0x2E, 0x49, 0xC6, 0x0C, 0x15, 0xCF, 0x86,
+ 0xC5, 0x48, 0xD7, 0xED, 0x2C, 0x3A, 0x06, 0xF4, 0x8C, 0xD6, 0x65, 0x58, 0xEA, 0x95, 0xB9, 0x1E,
+ 0xEF, 0xED, 0xB9, 0xB4, 0x7B, 0x66, 0x6D, 0x0F, 0x29, 0x64, 0x36, 0x63, 0x8C, 0x36, 0xA3, 0x31,
+ 0xE6, 0x08, 0xC0, 0x98, 0xD9, 0x43, 0xDF, 0x2D, 0x5E, 0xDA, 0xE2, 0xB9, 0x94, 0xCA, 0x73, 0x6B,
+ 0x91, 0xCA, 0x02, 0x4B, 0x8E, 0xCB, 0x61, 0x3E, 0xEA, 0x9B, 0x89, 0x6F, 0xA2, 0x15, 0x52, 0xDA,
+ 0x68, 0x08, 0x1C, 0x0D, 0xD6, 0x34, 0x02, 0x94, 0x00, 0x28, 0x66, 0x29, 0x66, 0xED, 0x18, 0x92,
+ 0xAA, 0x26, 0x1B, 0xD3, 0x18, 0xE3, 0x6E, 0x77, 0x69, 0x5A, 0x1B, 0x91, 0xE9, 0x40, 0x6A, 0x66,
+ 0xFE, 0x24, 0xF4, 0x94, 0xC5, 0x2D, 0x01, 0x81, 0xED, 0xEA, 0xAE, 0x88, 0xA7, 0xA4, 0x9F, 0x69,
+ 0xF0, 0x89, 0x56, 0x77, 0xC5, 0xA6, 0xEE, 0x86, 0x15, 0x72, 0x5E, 0x8B, 0x42, 0x0E, 0xF4, 0x61,
+ 0x54, 0x90, 0xBA, 0xB9, 0xEE, 0xE3, 0xDC, 0x27, 0x2D, 0xE3, 0x39, 0x29, 0x1B, 0x79, 0x4C, 0x96,
+ 0x88, 0x12, 0x06, 0x2D, 0xC9, 0x9B, 0xB0, 0x2A, 0x98, 0x2A, 0x85, 0x33, 0x92, 0xED, 0xC7, 0xF4,
+ 0x67, 0x0A, 0x4D, 0x00, 0x26, 0x01, 0xAE, 0x41, 0xD5, 0x86, 0x85, 0x43, 0x92, 0x71, 0x02, 0x0E,
+ 0x2C, 0x4D, 0x64, 0x4C, 0x76, 0x32, 0xB2, 0x84, 0xE8, 0x8E, 0x25, 0x01, 0x55, 0x91, 0xF4, 0xD2,
+ 0x92, 0x0C, 0x23, 0x89, 0x1A, 0x43, 0x95, 0xC3, 0x8C, 0x33, 0x41, 0xF3, 0x63, 0xF9, 0xED, 0xE9,
+ 0xCD, 0x8D, 0x98, 0xA4, 0x20, 0x2D, 0x5D, 0x99, 0xD0, 0x32, 0xE5, 0x79, 0x22, 0xAE, 0x92, 0xC5,
+ 0x0C, 0xF6, 0x15, 0x41, 0x4D, 0xD1, 0x87, 0x20, 0x2C, 0x06, 0xFE, 0xC4, 0x2A, 0x99, 0xE3, 0x42,
+ 0xA0, 0x0A, 0x65, 0xAC, 0xFB, 0xE6, 0xE2, 0xF9, 0x33, 0x7E, 0x2C, 0x3D, 0xAC, 0x5B, 0x80, 0x3D,
+ 0xAD, 0xE4, 0x97, 0x32, 0x9C, 0xD1, 0x22, 0x0A, 0xA2, 0x09, 0x0B, 0xC6, 0xA7, 0x8B, 0xB2, 0xCA,
+ 0x6F, 0x74, 0xF7, 0x97, 0x97, 0x8B, 0x4B, 0x98, 0x3F, 0xCA, 0x68, 0x16, 0x4A, 0xC1, 0x2F, 0xC1,
+ 0x5B, 0xB4, 0x7B, 0xB4, 0xB6, 0xE6, 0x3A, 0xB2, 0x62, 0x6E, 0x30, 0xA7, 0x55, 0xD6, 0xD2, 0x26,
+ 0x3D, 0x1B, 0xDA, 0x18, 0xE4, 0x4F, 0xC2, 0x2A, 0x5C, 0x12, 0x3F, 0x20, 0x2D, 0x26, 0x05, 0x15,
+ 0xC0, 0xE4, 0x61, 0x60, 0x30, 0x69, 0xF6, 0xE6, 0x72, 0x40, 0x6A, 0x78, 0x04, 0x93, 0xBD, 0xBD,
+ 0xA6, 0x11, 0x72, 0x42, 0x43, 0x98, 0x34, 0x00, 0xB0, 0xB7, 0x67, 0xBA, 0x94, 0xEC, 0xED, 0x25,
+ 0xED, 0x8A, 0x26, 0xD0, 0xC6, 0xBF, 0xE1, 0x41, 0x3E, 0x4F, 0xE6, 0x6C, 0x74, 0x7B, 0x15, 0x2F,
+ 0x21, 0x80, 0x6A, 0x6C, 0xF9, 0x86, 0xA6, 0x9A, 0xED, 0x5E, 0xDF, 0xF4, 0x64, 0xB2, 0xCA, 0x6B,
+ 0x35, 0xD3, 0x6F, 0x48, 0x6F, 0x25, 0xC9, 0x25, 0xE3, 0xBC, 0x42, 0xD9, 0xC8, 0xB2, 0x1E, 0x49,
+ 0x9E, 0x03, 0xFC, 0x08, 0x39, 0xE9, 0x11, 0x15, 0xC8, 0xC9, 0x27, 0x24, 0x8D, 0x75, 0xD6, 0x03,
+ 0x36, 0xC9, 0x45, 0x99, 0xFD, 0x1E, 0xA8, 0x34, 0x9B, 0xC1, 0xA6, 0x77, 0x03, 0xF9, 0x75, 0xA7,
+ 0x9A, 0x26, 0xC0, 0x9D, 0xCC, 0x9A, 0x77, 0x76, 0x00, 0xA4, 0x1D, 0xA1, 0x0C, 0x1D, 0xF0, 0x9E,
+ 0xC1, 0x31, 0x64, 0xBE, 0x45, 0x3B, 0x9F, 0x2D, 0x4F, 0x8A, 0x22, 0xB9, 0xEF, 0x5D, 0x15, 0x30,
+ 0xA1, 0x67, 0x12, 0xE8, 0x01, 0xA9, 0xB6, 0x6B, 0xB2, 0xE1, 0x85, 0xE8, 0xA0, 0x76, 0x93, 0xE8,
+ 0x01, 0xED, 0xED, 0xE9, 0x7E, 0xD3, 0x0F, 0xF7, 0x9A, 0xF9, 0x8B, 0x24, 0xF1, 0x94, 0x59, 0x12,
+ 0x33, 0x9D, 0xBF, 0x46, 0x28, 0x77, 0x52, 0x3B, 0xEA, 0xB4, 0x27, 0x29, 0x3E, 0x2D, 0x11, 0x1A,
+ 0x77, 0xCA, 0xE3, 0x46, 0xF5, 0x3A, 0xBD, 0xC2, 0x32, 0xEC, 0xB3, 0xA5, 0x6E, 0xE7, 0xAB, 0xE5,
+ 0xD8, 0x9A, 0x4F, 0x88, 0x0B, 0x00, 0x1D, 0x12, 0xFC, 0xA2, 0x37, 0x2C, 0xF9, 0xF4, 0xDE, 0xC9,
+ 0x11, 0x12, 0xE3, 0x7C, 0x45, 0x80, 0xAB, 0xA7, 0x86, 0xFC, 0xE6, 0x54, 0xD1, 0x7B, 0x72, 0x72,
+ 0x71, 0xF2, 0xEE, 0xDB, 0xB3, 0x1F, 0xF8, 0x0B, 0x08, 0x11, 0xA1, 0x44, 0x4E, 0x86, 0xCF, 0xE5,
+ 0x2B, 0xC5, 0xAC, 0x1E, 0x5B, 0x43, 0x10, 0x32, 0x7F, 0x7E, 0x28, 0xFB, 0xD9, 0x1B, 0x58, 0x20,
+ 0x65, 0x7E, 0x89, 0xC5, 0x04, 0x86, 0xF3, 0xBB, 0x4C, 0x63, 0xF1, 0x8B, 0xE4, 0x46, 0x94, 0x5C,
+ 0x49, 0x1D, 0xE5, 0x29, 0x05, 0x28, 0xCD, 0xA4, 0x01, 0x78, 0xFD, 0x0E, 0x62, 0xD1, 0x42, 0x9C,
+ 0x62, 0xC2, 0x2F, 0x93, 0xF1, 0x07, 0x6B, 0x6A, 0x59, 0xDE, 0x2A, 0xC4, 0x5B, 0x97, 0xA4, 0xD3,
+ 0x8D, 0x77, 0x50, 0xFB, 0x53, 0x35, 0xCB, 0x0E, 0xD7, 0x7C, 0xC5, 0xC0, 0x97, 0xDD, 0x33, 0x23,
+ 0x70, 0xCB, 0x9C, 0x17, 0xA7, 0xBC, 0xD0, 0x75, 0x59, 0xAA, 0x15, 0x72, 0xA1, 0xA9, 0x80, 0x0B,
+ 0xD6, 0xEA, 0xA6, 0xF9, 0x17, 0x77, 0xFC, 0x01, 0x99, 0xDB, 0x2E, 0x4F, 0x65, 0x51, 0x74, 0x1B,
+ 0xD9, 0x79, 0x73, 0xF6, 0xEA, 0xF5, 0xD3, 0xF3, 0x17, 0xBE, 0xAE, 0xD6, 0x63, 0x97, 0xB0, 0xE7,
+ 0x66, 0x21, 0xFB, 0x1A, 0xBE, 0x5B, 0x6B, 0xB5, 0xB4, 0x54, 0xFF, 0xFE, 0x87, 0x7C, 0xB1, 0x33,
+ 0x4D, 0x6E, 0x81, 0xE8, 0xF9, 0x4E, 0x7A, 0x33, 0x97, 0xE0, 0x46, 0xEB, 0x62, 0x47, 0x15, 0xBE,
+ 0x11, 0xF0, 0x0A, 0x4F, 0x76, 0x3C, 0xAA, 0xC0, 0x0B, 0x77, 0x00, 0xCB, 0x1D, 0x01, 0x60, 0xEE,
+ 0x8C, 0x61, 0x20, 0x22, 0xDB, 0x47, 0xB5, 0xFB, 0xFB, 0x5A, 0x5F, 0x34, 0x14, 0x6C, 0x67, 0x2E,
+ 0xCB, 0x9E, 0xB7, 0xCF, 0x03, 0xA5, 0x3A, 0xDC, 0xBC, 0x66, 0x12, 0x6D, 0x66, 0x9D, 0x55, 0x57,
+ 0xB3, 0x5E, 0x4B, 0x8C, 0x7D, 0x0B, 0xC3, 0x38, 0xA9, 0xE1, 0x84, 0xBB, 0x8D, 0x61, 0xD9, 0xC2,
+ 0xC9, 0x0C, 0x53, 0xEF, 0xA1, 0x04, 0x50, 0xCF, 0x55, 0xEE, 0xAA, 0x81, 0xC4, 0x26, 0x69, 0xB9,
+ 0xD5, 0x66, 0x4A, 0xE4, 0x88, 0xEA, 0x58, 0x96, 0xC6, 0xF2, 0x5D, 0x31, 0x84, 0x53, 0xAA, 0x47,
+ 0x51, 0xDB, 0xA0, 0x2F, 0x7D, 0x0A, 0x31, 0xB4, 0x97, 0xB4, 0x45, 0xDE, 0x56, 0x2B, 0x59, 0x50,
+ 0xA2, 0xBA, 0xAE, 0x1F, 0xEA, 0x43, 0xAB, 0x49, 0x33, 0xF3, 0x92, 0x98, 0x55, 0x3D, 0xEE, 0x6C,
+ 0x09, 0x26, 0xD0, 0x93, 0xBD, 0x47, 0x99, 0x76, 0xF3, 0xB6, 0xD8, 0xCB, 0x9E, 0x65, 0xA3, 0x1E,
+ 0x97, 0xED, 0x5D, 0x96, 0xB6, 0xA8, 0xED, 0x80, 0x2A, 0xE6, 0xDA, 0xE6, 0xE5, 0x47, 0x70, 0xDD,
+ 0x69, 0x7E, 0x07, 0x36, 0xAF, 0xA1, 0xD3, 0x6D, 0xBE, 0xBF, 0x82, 0x56, 0x8E, 0x4C, 0x3C, 0xAE,
+ 0xC6, 0x2A, 0x21, 0xBE, 0x20, 0xD3, 0x27, 0xE8, 0x79, 0x91, 0xDF, 0xDB, 0xF6, 0xC0, 0x3A, 0x30,
+ 0xEA, 0x76, 0xFA, 0xD2, 0xB4, 0x1E, 0x84, 0xED, 0x21, 0x4C, 0xEC, 0x18, 0x0C, 0x2E, 0x35, 0xAC,
+ 0xB5, 0x0E, 0x08, 0xB8, 0x69, 0x41, 0x0B, 0xDA, 0xBA, 0xD3, 0xED, 0x5C, 0xBF, 0xED, 0x75, 0x2C,
+ 0x3B, 0x49, 0x08, 0xFA, 0xDC, 0x1A, 0x3B, 0x44, 0xA0, 0x1B, 0x0A, 0x90, 0x01, 0x99, 0xBE, 0x0E,
+ 0x4C, 0xAB, 0x53, 0x96, 0x62, 0x9E, 0xA4, 0xE5, 0x4D, 0x5A, 0x96, 0x4E, 0x9B, 0xAE, 0x36, 0x4A,
+ 0xBE, 0xAC, 0x36, 0x03, 0x53, 0x33, 0xA9, 0x6A, 0x5C, 0xAF, 0x41, 0xC5, 0x32, 0xE3, 0x3B, 0x0D,
+ 0xA5, 0x35, 0xCB, 0x0C, 0xB3, 0xC7, 0x96, 0xF6, 0x64, 0x9E, 0x7A, 0xE1, 0xEF, 0x87, 0xDA, 0xEA,
+ 0x3E, 0x91, 0xAD, 0xC6, 0x0A, 0x8D, 0x47, 0xBF, 0x0F, 0xDF, 0xF6, 0xEA, 0xFD, 0xA1, 0x75, 0xFB,
+ 0x16, 0xA2, 0xC4, 0x3B, 0xFF, 0x6D, 0xA0, 0x28, 0xF9, 0xCF, 0x8F, 0x59, 0x17, 0xB0, 0xEE, 0x57,
+ 0x30, 0x2E, 0xAF, 0xAB, 0xFC, 0xFA, 0x7A, 0x46, 0x44, 0xB9, 0x86, 0xF6, 0xBD, 0xB2, 0xE6, 0x55,
+ 0x49, 0x8A, 0x34, 0x39, 0x60, 0xEB, 0x16, 0x59, 0x65, 0xEA, 0x39, 0x2D, 0xA2, 0x70, 0x55, 0x94,
+ 0x1B, 0x60, 0xB9, 0x05, 0xA2, 0xFC, 0xDB, 0xF3, 0xF6, 0xF3, 0xB6, 0x79, 0xE3, 0xD6, 0x6A, 0x13,
+ 0xC7, 0x93, 0x66, 0x55, 0x99, 0x5F, 0x6C, 0x3B, 0x1E, 0x58, 0x84, 0xCC, 0xBA, 0x5A, 0x39, 0xBE,
+ 0x23, 0x76, 0x66, 0xC6, 0xCA, 0x4E, 0x8D, 0xDC, 0xC6, 0x1F, 0xE8, 0x07, 0x03, 0x93, 0x18, 0x79,
+ 0xBA, 0x20, 0xAD, 0x76, 0x7E, 0x1E, 0xD0, 0x53, 0x54, 0x39, 0x6A, 0xD3, 0x67, 0xCE, 0x98, 0xAC,
+ 0xD2, 0x74, 0x38, 0x3C, 0x39, 0xF8, 0x71, 0x74, 0x78, 0x1D, 0x82, 0xCB, 0x78, 0x07, 0xA0, 0x62,
+ 0x75, 0x3F, 0x62, 0xB0, 0x11, 0x21, 0xE4, 0xE4, 0x74, 0x62, 0x84, 0x1C, 0x76, 0xAC, 0xE6, 0x8F,
+ 0x30, 0x82, 0x59, 0x58, 0x13, 0xF9, 0xDC, 0xA5, 0xCC, 0xAA, 0xBC, 0xA5, 0x2A, 0x5B, 0xEA, 0x0A,
+ 0xFA, 0xDD, 0x20, 0x17, 0x81, 0x9E, 0xDC, 0x60, 0x4D, 0xA8, 0xF6, 0xB3, 0xAE, 0xFE, 0x3B, 0x16,
+ 0xC8, 0x9E, 0xA0, 0x3E, 0x83, 0x2D, 0x5A, 0x3A, 0x6B, 0xE2, 0x90, 0x6E, 0xD4, 0xDB, 0xFF, 0x0C,
+ 0xF5, 0x11, 0x1B, 0x55, 0xF2, 0x4C, 0xAB, 0xB4, 0xA5, 0x0C, 0x1B, 0x8B, 0x4B, 0xB9, 0xA9, 0x56,
+ 0x92, 0x96, 0x66, 0xCD, 0xCF, 0xB1, 0x5C, 0x1B, 0x18, 0xE0, 0x51, 0x4D, 0x8E, 0x2B, 0xF9, 0xCA,
+ 0x25, 0x87, 0x5E, 0xD6, 0xE2, 0x50, 0x6A, 0x5A, 0xE4, 0x25, 0xA4, 0xEF, 0xBA, 0x82, 0x34, 0x13,
+ 0x75, 0xF5, 0xE8, 0xF0, 0x1F, 0x08, 0x5B, 0x62, 0x15, 0x89, 0x02, 0x47, 0x60, 0xA5, 0x2C, 0x4E,
+ 0x2A, 0xFF, 0x28, 0xA8, 0xCF, 0xF4, 0x7E, 0xA6, 0x15, 0xB2, 0x30, 0xD3, 0xDE, 0xD8, 0x90, 0x3C,
+ 0xD5, 0xF1, 0x2F, 0xB6, 0x17, 0xD0, 0xA1, 0x08, 0xBA, 0xA2, 0x3D, 0x34, 0x2D, 0x1F, 0x52, 0xE6,
+ 0xEB, 0x2D, 0x40, 0x05, 0x19, 0x90, 0x0E, 0x40, 0x97, 0xD1, 0x71, 0x11, 0x96, 0x49, 0x81, 0xD8,
+ 0xD6, 0x2F, 0x6C, 0xDC, 0x94, 0x4B, 0xE8, 0x29, 0x91, 0xE8, 0xE1, 0xEF, 0x7E, 0xDD, 0x0F, 0x28,
+ 0x0D, 0xAB, 0x17, 0xF9, 0x3C, 0x9C, 0x89, 0xAB, 0x0A, 0x59, 0xE8, 0xA7, 0x33, 0xCF, 0x33, 0x7C,
+ 0x80, 0xAC, 0x3E, 0xCF, 0xA5, 0xF3, 0x23, 0x02, 0x18, 0x7D, 0xAE, 0x56, 0x3B, 0x24, 0x75, 0x25,
+ 0x26, 0x81, 0x4B, 0x60, 0x12, 0xBF, 0x8E, 0x97, 0x6C, 0xA5, 0xBE, 0x4D, 0x66, 0xD1, 0x9F, 0xC5,
+ 0x1F, 0x43, 0xCC, 0xCB, 0x65, 0x9E, 0x14, 0x93, 0x08, 0x0A, 0x1B, 0x20, 0x36, 0x81, 0xEE, 0x72,
+ 0x8C, 0x60, 0xA0, 0x45, 0x09, 0x8F, 0xC1, 0x54, 0xDA, 0xE8, 0xEF, 0x20, 0x95, 0xD3, 0x67, 0x36,
+ 0x92, 0x93, 0x6A, 0x13, 0xBE, 0x71, 0x2A, 0xF1, 0xFC, 0x8C, 0x17, 0xEE, 0xEA, 0x52, 0x9A, 0x3D,
+ 0x02, 0xCF, 0xD6, 0xE9, 0xA9, 0x34, 0x4F, 0x56, 0x8D, 0xBC, 0x2A, 0x61, 0x25, 0x75, 0x4E, 0xE4,
+ 0x55, 0x4D, 0xF9, 0x32, 0xC1, 0x54, 0x22, 0x5B, 0xB5, 0x15, 0xC8, 0xC6, 0xED, 0xFB, 0x3A, 0xFC,
+ 0x36, 0xF6, 0x28, 0xC4, 0xC6, 0x0B, 0xBF, 0x8F, 0x3D, 0x5A, 0x8A, 0x5E, 0xF8, 0x43, 0xEC, 0x61,
+ 0xC8, 0x48, 0xF9, 0x7B, 0xEC, 0x51, 0x90, 0x1D, 0x9E, 0xFE, 0x1A, 0x93, 0x2A, 0x90, 0xDF, 0xD1,
+ 0xE8, 0xA3, 0xBF, 0x87, 0xFC, 0xFC, 0x8A, 0x3E, 0x45, 0x3F, 0x68, 0x29, 0xFC, 0x47, 0x45, 0xBB,
+ 0x9B, 0xF2, 0x38, 0x2F, 0xA9, 0x72, 0x31, 0x17, 0x78, 0x0C, 0x14, 0x05, 0x4E, 0x2B, 0x71, 0x53,
+ 0xB2, 0xD4, 0xAA, 0x12, 0x34, 0x14, 0xDC, 0x34, 0x22, 0xC6, 0x86, 0xFB, 0xD7, 0x32, 0x97, 0x2F,
+ 0x69, 0xB0, 0x13, 0xA8, 0xC5, 0x26, 0xE5, 0x35, 0xE0, 0x82, 0x71, 0x9B, 0x24, 0x1E, 0xA6, 0x74,
+ 0x60, 0xA9, 0xB2, 0x36, 0xF9, 0x35, 0x2D, 0x93, 0xEF, 0xE3, 0x23, 0x27, 0x09, 0x3E, 0xC3, 0x2A,
+ 0x31, 0x49, 0xEF, 0xD0, 0xFF, 0xAB, 0xF4, 0x3A, 0x36, 0x72, 0xD6, 0x29, 0xBF, 0xFB, 0xA9, 0xE9,
+ 0x3F, 0x50, 0x72, 0x9C, 0x50, 0xA0, 0xA0, 0xEE, 0x9E, 0x0D, 0x02, 0xF1, 0x60, 0x72, 0x2D, 0xD8,
+ 0x0F, 0x74, 0x60, 0xB3, 0x35, 0x58, 0x8F, 0xAE, 0x47, 0x76, 0x67, 0x31, 0xA7, 0x68, 0x3E, 0x8C,
+ 0xC7, 0xCB, 0x33, 0xC7, 0xC9, 0x92, 0x66, 0x3B, 0x9B, 0x7C, 0xD2, 0x90, 0xAA, 0x93, 0xDB, 0xF4,
+ 0x9A, 0xAA, 0x26, 0x5F, 0xD8, 0x05, 0x15, 0x7A, 0x49, 0x5E, 0x93, 0xF2, 0x4B, 0x3D, 0x04, 0xE5,
+ 0x44, 0x91, 0x46, 0x3B, 0x6D, 0x49, 0x53, 0x1E, 0xD1, 0x97, 0xE6, 0x9B, 0xED, 0x4A, 0xD3, 0x98,
+ 0x57, 0xFA, 0x35, 0x41, 0x58, 0x53, 0x67, 0xC3, 0x2B, 0xBE, 0x5E, 0x6F, 0x64, 0xD0, 0x7A, 0xFC,
+ 0xDE, 0x9A, 0x10, 0xCB, 0x30, 0x68, 0x46, 0x5D, 0xFF, 0xDB, 0x80, 0x53, 0xDF, 0xC2, 0x61, 0xF4,
+ 0x46, 0x86, 0x74, 0x20, 0x83, 0xF5, 0xE3, 0x4F, 0xD3, 0xC9, 0x44, 0xC0, 0xB4, 0x31, 0xAE, 0xA9,
+ 0x4C, 0xA4, 0x58, 0xF2, 0xBB, 0xAC, 0x70, 0x4D, 0x78, 0xDA, 0xA8, 0xF7, 0x7B, 0xA4, 0x12, 0x56,
+ 0xB0, 0x4C, 0x03, 0x9B, 0x75, 0x13, 0x55, 0x8E, 0x82, 0xB0, 0x73, 0x8E, 0x08, 0x15, 0x0F, 0xA8,
+ 0xDA, 0x70, 0xA7, 0x91, 0x28, 0x17, 0x43, 0xB3, 0x1B, 0x7E, 0xEE, 0x77, 0x4D, 0xE5, 0xF8, 0x7E,
+ 0x8C, 0x91, 0x90, 0x75, 0x23, 0xC4, 0x43, 0x52, 0x3C, 0x55, 0x08, 0xED, 0xD7, 0xF1, 0x3B, 0xE8,
+ 0xC2, 0xF7, 0x35, 0x17, 0x66, 0xB9, 0xB2, 0xA3, 0xE3, 0xC7, 0xCD, 0x32, 0xE8, 0xC3, 0x27, 0x37,
+ 0x11, 0xD4, 0x10, 0x7B, 0x6F, 0xCF, 0x7D, 0xEB, 0xD9, 0x8A, 0x77, 0xEB, 0x8D, 0x1B, 0xFD, 0x79,
+ 0x31, 0x9F, 0x80, 0xA9, 0x9A, 0x06, 0x5B, 0x4D, 0x80, 0x2E, 0x9A, 0x8F, 0x36, 0xC4, 0xCE, 0x44,
+ 0xE0, 0xA8, 0xF8, 0x03, 0x2E, 0xD4, 0x98, 0xFC, 0xC8, 0x24, 0x22, 0xC6, 0x14, 0xB3, 0x23, 0x65,
+ 0xA4, 0xB0, 0xB3, 0x7F, 0xE0, 0x9D, 0x55, 0x4E, 0xF3, 0x5B, 0xA7, 0x0F, 0xED, 0x05, 0x28, 0xBF,
+ 0xD4, 0xA7, 0xB3, 0x39, 0x93, 0xDA, 0xE4, 0x60, 0x17, 0xF9, 0x53, 0xE4, 0x7A, 0x4A, 0x2E, 0x6C,
+ 0xBF, 0xA3, 0x7E, 0x19, 0xE3, 0xF1, 0xA5, 0x43, 0xBE, 0x8C, 0x23, 0x1B, 0xB6, 0xC5, 0x2F, 0x8E,
+ 0x1C, 0x8F, 0x50, 0x83, 0x24, 0xD5, 0xE2, 0x1B, 0x48, 0x6C, 0x6A, 0x1A, 0x0F, 0x3C, 0x20, 0x30,
+ 0xEB, 0x0C, 0x66, 0xDD, 0x84, 0x46, 0x27, 0xE1, 0xF1, 0x72, 0xDB, 0xEC, 0xF2, 0xD7, 0x75, 0xF1,
+ 0x37, 0x89, 0xEE, 0xCA, 0x3C, 0xE6, 0x20, 0xA1, 0xB5, 0x21, 0x89, 0x2F, 0xD3, 0xC1, 0xB7, 0xD1,
+ 0xF7, 0x7D, 0x77, 0x9D, 0x64, 0x2E, 0x09, 0x1E, 0x0A, 0x30, 0x6E, 0x4B, 0xE0, 0x6A, 0x16, 0xF4,
+ 0x25, 0x02, 0x5E, 0xBF, 0x0E, 0xF1, 0xE7, 0xBB, 0x5E, 0x5B, 0x60, 0xA9, 0xC3, 0x92, 0x72, 0xB5,
+ 0x74, 0x7B, 0x8A, 0x03, 0x5A, 0x82, 0xB7, 0xCE, 0x7C, 0x4B, 0x10, 0xA0, 0x50, 0xBD, 0xC1, 0xE4,
+ 0xAE, 0xDF, 0x49, 0x4D, 0xE0, 0xF5, 0x5D, 0x3A, 0x17, 0x46, 0x6A, 0xAE, 0x64, 0x00, 0x67, 0x72,
+ 0x89, 0xEA, 0x1B, 0x84, 0x39, 0x60, 0xB8, 0x7E, 0x11, 0xFF, 0x49, 0xC3, 0xD9, 0xCA, 0x88, 0x87,
+ 0xCD, 0xBC, 0xFD, 0x0E, 0xAA, 0x0E, 0x25, 0xC7, 0x85, 0x81, 0x40, 0xD4, 0xDE, 0xDF, 0x23, 0x98,
+ 0x36, 0xBA, 0xA8, 0xDE, 0xB2, 0x86, 0x7B, 0x9A, 0x05, 0xB3, 0xA3, 0x25, 0xAB, 0x8F, 0xDB, 0xF8,
+ 0xD0, 0xEB, 0x93, 0x57, 0x69, 0x7D, 0x52, 0x7D, 0x25, 0x4B, 0x54, 0xA8, 0x64, 0x00, 0x4C, 0xA2,
+ 0x5B, 0xBD, 0x9C, 0x43, 0x2C, 0xB3, 0xAE, 0xCA, 0x6D, 0x5C, 0x41, 0x67, 0xFD, 0xB2, 0x2C, 0x57,
+ 0xBE, 0xB1, 0x34, 0x7B, 0xD4, 0xBB, 0x4B, 0x6B, 0x9A, 0x13, 0xD4, 0x17, 0x9B, 0x04, 0xDC, 0xDE,
+ 0x5E, 0x17, 0x7F, 0x52, 0xA9, 0x04, 0x34, 0xE6, 0x38, 0x2D, 0x7E, 0xB1, 0xF1, 0x8B, 0x15, 0xE9,
+ 0xC8, 0xF5, 0xD0, 0x62, 0x50, 0xA4, 0xA2, 0xCC, 0x45, 0x26, 0xA3, 0xE2, 0x54, 0x3A, 0x85, 0xE8,
+ 0xED, 0xED, 0xC9, 0x80, 0x82, 0xD6, 0x87, 0x41, 0x57, 0x15, 0x7E, 0x8B, 0xC3, 0x57, 0xF2, 0x4D,
+ 0x94, 0x30, 0x59, 0xF6, 0xC6, 0x2C, 0x40, 0x7E, 0x1F, 0x44, 0xED, 0x6C, 0xFA, 0x9B, 0x0E, 0xFC,
+ 0x6D, 0x62, 0x90, 0xAD, 0x68, 0x6F, 0xCF, 0x3C, 0xEA, 0x00, 0xD0, 0xE3, 0xC1, 0x51, 0xD4, 0xD5,
+ 0xD0, 0x41, 0xB3, 0x1D, 0x84, 0xCE, 0xFC, 0xA7, 0xC6, 0x8F, 0xC1, 0xB6, 0xFB, 0xB8, 0xB1, 0x61,
+ 0x35, 0xC3, 0xB5, 0x45, 0xF7, 0x20, 0x46, 0xBA, 0x24, 0xA6, 0x2D, 0x52, 0xED, 0xED, 0x31, 0x0B,
+ 0x52, 0x6F, 0x7E, 0xEB, 0x7B, 0x47, 0x91, 0xD8, 0x89, 0x28, 0x6A, 0x22, 0x61, 0xF8, 0xE7, 0xA3,
+ 0xA3, 0xFD, 0x4D, 0x34, 0xBF, 0x2F, 0x49, 0x7B, 0x93, 0x69, 0xC3, 0xC0, 0x77, 0xDD, 0x20, 0xEA,
+ 0x75, 0xE3, 0xA8, 0x5C, 0x15, 0x08, 0xB9, 0x29, 0x92, 0x6B, 0x96, 0xA8, 0x5A, 0x0B, 0xA1, 0xAD,
+ 0x7B, 0x92, 0x1A, 0xD3, 0x9E, 0x9E, 0x41, 0xE7, 0xFA, 0x54, 0x39, 0x3A, 0x09, 0xC0, 0xC6, 0x75,
+ 0x69, 0xE3, 0x5E, 0x5A, 0x65, 0x98, 0x50, 0x6C, 0x34, 0x51, 0x60, 0x69, 0xF9, 0xBA, 0xF0, 0x01,
+ 0x77, 0x1A, 0xFA, 0x5D, 0xD4, 0xD9, 0x31, 0x2B, 0x43, 0x7E, 0x42, 0xBF, 0x4C, 0x40, 0x4F, 0xAB,
+ 0x4C, 0xBA, 0xBD, 0x0C, 0x44, 0xFE, 0xEE, 0xA1, 0x04, 0x6B, 0x4D, 0x01, 0x95, 0x7E, 0x7B, 0x98,
+ 0x66, 0x88, 0xC2, 0x5B, 0x51, 0x78, 0x53, 0x02, 0x2D, 0xFD, 0x30, 0x95, 0xA1, 0x12, 0x4A, 0xCD,
+ 0xA7, 0x9F, 0x6B, 0x32, 0x74, 0x07, 0x4D, 0x1A, 0xFF, 0xD7, 0x21, 0xAB, 0xBD, 0xA3, 0x3E, 0xE3,
+ 0x65, 0xDB, 0x58, 0x55, 0x23, 0xED, 0x68, 0xB6, 0xCE, 0xCC, 0xAD, 0x73, 0xDD, 0xD5, 0x3D, 0xA4,
+ 0xD9, 0xCB, 0x06, 0xA9, 0x76, 0xE3, 0x98, 0x17, 0x8A, 0x5A, 0xE8, 0xEA, 0x70, 0xE4, 0x72, 0x4F,
+ 0x1B, 0xF0, 0x66, 0xDB, 0xFC, 0xEA, 0xFE, 0xBC, 0xC0, 0x5E, 0xA1, 0xFA, 0xF6, 0x0F, 0x32, 0xD5,
+ 0x7C, 0xAB, 0xF5, 0xF8, 0x7B, 0xDF, 0xA9, 0x82, 0xBD, 0xA4, 0x35, 0xC4, 0x27, 0x75, 0xCD, 0x5A,
+ 0x4D, 0x49, 0x86, 0x50, 0x56, 0xD3, 0x5A, 0xD0, 0x4B, 0x5B, 0x68, 0xE1, 0xC0, 0xC5, 0xAE, 0xF4,
+ 0x4F, 0x15, 0x8C, 0x82, 0x7E, 0xDB, 0x38, 0xDB, 0x96, 0x57, 0x1A, 0x68, 0xB2, 0xAC, 0x85, 0x9F,
+ 0x20, 0x1C, 0x73, 0x92, 0x22, 0x42, 0x8B, 0x55, 0xEA, 0x34, 0x24, 0xF7, 0x51, 0x54, 0x42, 0xF3,
+ 0x8C, 0x32, 0x72, 0x52, 0x90, 0x71, 0x85, 0xFB, 0xF1, 0x54, 0xAB, 0x47, 0xC6, 0xC4, 0xCC, 0x68,
+ 0xB2, 0x41, 0xC5, 0xB2, 0xE0, 0x6C, 0x8F, 0x48, 0x8F, 0xA1, 0x5D, 0xA8, 0x9F, 0x76, 0x98, 0x8C,
+ 0x55, 0x21, 0x98, 0x72, 0x54, 0x52, 0xD3, 0x60, 0x38, 0x5E, 0x14, 0x34, 0xE9, 0x9E, 0x15, 0xA5,
+ 0x34, 0x7E, 0x0C, 0xEB, 0xDB, 0x3F, 0x46, 0x1B, 0x5B, 0xB6, 0xA1, 0x52, 0x55, 0x7C, 0xD4, 0xAF,
+ 0xBE, 0x30, 0xA1, 0x52, 0xD5, 0xFE, 0x3E, 0x62, 0x4C, 0x6A, 0x71, 0xA1, 0x4F, 0x69, 0xB7, 0x03,
+ 0xEC, 0x80, 0x1B, 0x4C, 0x24, 0x0C, 0x6F, 0x98, 0xBC, 0xD0, 0xE1, 0x63, 0xC4, 0xCD, 0xC6, 0xDD,
+ 0xB3, 0x1F, 0x2C, 0xA9, 0x8A, 0x06, 0xC5, 0xB0, 0x43, 0xE5, 0x8F, 0x00, 0xFD, 0xA6, 0xA1, 0x86,
+ 0x6C, 0x69, 0xC4, 0x88, 0x2F, 0xB1, 0x34, 0x3F, 0xC0, 0xE4, 0xDB, 0x12, 0xFA, 0x2D, 0x13, 0x6F,
+ 0x8B, 0xC7, 0xAB, 0xD5, 0xA7, 0xA2, 0x99, 0xDC, 0x36, 0xD3, 0xF4, 0xFF, 0x35, 0xA1, 0x22, 0x36,
+ 0x80, 0x44, 0xB3, 0x07, 0x06, 0x09, 0xEA, 0x1A, 0xF8, 0xEE, 0x22, 0xD2, 0x1E, 0x0D, 0xDD, 0xF7,
+ 0x78, 0xDB, 0xC7, 0xD5, 0xAA, 0x93, 0xF1, 0x74, 0xAB, 0x20, 0x31, 0x1C, 0xAE, 0xF5, 0x0F, 0xFF,
+ 0x46, 0x13, 0x6B, 0x4D, 0xB3, 0xDA, 0x2B, 0xDB, 0x2C, 0x9F, 0x8B, 0x9C, 0xE9, 0x09, 0xAF, 0xEE,
+ 0x4F, 0xD6, 0x71, 0xC2, 0xBC, 0x0B, 0x55, 0xA0, 0x5E, 0x15, 0x71, 0xAA, 0xFA, 0xD4, 0xA4, 0x5B,
+ 0x59, 0x88, 0xCF, 0x49, 0x57, 0xB1, 0x02, 0xF1, 0x04, 0xDA, 0x90, 0xD0, 0x52, 0x39, 0xC7, 0x71,
+ 0x46, 0x34, 0x0E, 0x01, 0x03, 0xE3, 0x81, 0x57, 0x57, 0xA3, 0xA5, 0x31, 0x23, 0x6A, 0xA4, 0x72,
+ 0xB4, 0xE6, 0x84, 0x72, 0xB7, 0x34, 0x71, 0x99, 0xB9, 0xAD, 0x89, 0x2F, 0x54, 0xAF, 0x72, 0xEA,
+ 0xE8, 0x45, 0xFE, 0x44, 0x83, 0xC8, 0xCF, 0x18, 0x99, 0x28, 0xD4, 0xAB, 0xC3, 0xF7, 0x63, 0x97,
+ 0x40, 0x2D, 0x4C, 0xAD, 0x6D, 0x37, 0x0A, 0x36, 0xEB, 0x6E, 0xF6, 0x4B, 0x9B, 0x2C, 0x23, 0x4E,
+ 0x2A, 0x68, 0x79, 0xD0, 0x9C, 0x72, 0xBB, 0x25, 0xA2, 0x40, 0x0A, 0x9D, 0xD0, 0x6A, 0xF6, 0x28,
+ 0x84, 0xDA, 0xDD, 0x96, 0xB5, 0xB6, 0x10, 0xC9, 0x22, 0xE8, 0xB4, 0x8F, 0x15, 0x6A, 0x0D, 0xA9,
+ 0xAD, 0x13, 0x86, 0x78, 0x3F, 0xAC, 0x6C, 0x36, 0x68, 0x77, 0xE1, 0xD0, 0xEE, 0x85, 0xA4, 0xDD,
+ 0x39, 0xD1, 0xEE, 0x04, 0xB4, 0x1B, 0x03, 0xAA, 0xD7, 0xD7, 0x05, 0x70, 0x6E, 0x82, 0xE0, 0xBD,
+ 0x2C, 0x1A, 0xD4, 0x68, 0x82, 0x1D, 0x9B, 0xD4, 0xFF, 0xB2, 0x91, 0x3E, 0x05, 0x46, 0xB6, 0x92,
+ 0xF4, 0x2E, 0x52, 0x39, 0x9E, 0xA2, 0x4D, 0xC6, 0xA7, 0xE1, 0xC4, 0x96, 0x6B, 0x13, 0xBC, 0x72,
+ 0x33, 0xE1, 0x0F, 0x27, 0x21, 0x9A, 0x6C, 0xA3, 0x80, 0x1B, 0xF4, 0x3E, 0x0F, 0x11, 0xEE, 0xD5,
+ 0xE5, 0x30, 0xA4, 0x50, 0x3F, 0x8E, 0x05, 0x13, 0x70, 0xC6, 0xEC, 0x6C, 0x69, 0x65, 0x73, 0xE7,
+ 0x3A, 0xDB, 0xA6, 0x7D, 0x24, 0x8C, 0x0B, 0x56, 0x91, 0x5F, 0xB7, 0xE8, 0x80, 0xD5, 0xD0, 0x87,
+ 0x7F, 0x0F, 0x7F, 0x18, 0x99, 0x98, 0x26, 0xA4, 0x0F, 0xC8, 0xF7, 0x43, 0x92, 0xC6, 0x0F, 0x83,
+ 0xEF, 0xA3, 0x6F, 0x23, 0x7E, 0x22, 0xED, 0x1F, 0x1B, 0x67, 0xDA, 0x4B, 0xC6, 0x56, 0xF3, 0x6D,
+ 0xF8, 0x7D, 0x77, 0x35, 0xDF, 0x0F, 0x7E, 0x88, 0xFE, 0x1E, 0xF1, 0x13, 0x29, 0xCB, 0xA8, 0x46,
+ 0x19, 0xFC, 0x34, 0xE6, 0x18, 0xAF, 0x58, 0x5D, 0xD8, 0xF9, 0xB1, 0xED, 0x87, 0x51, 0x59, 0x78,
+ 0xF3, 0x86, 0x22, 0x82, 0x10, 0x05, 0xE2, 0xB4, 0xDF, 0x32, 0x1A, 0x90, 0x84, 0x97, 0xB1, 0xE9,
+ 0x21, 0x0B, 0xF1, 0x47, 0x98, 0x18, 0xB0, 0x52, 0xC7, 0xF9, 0x36, 0x82, 0x07, 0x7A, 0x8C, 0x6C,
+ 0xBC, 0xE9, 0x49, 0x9A, 0xCF, 0x9D, 0xEF, 0x41, 0x2A, 0xED, 0x27, 0x7D, 0x9E, 0x29, 0x64, 0x69,
+ 0x55, 0x21, 0x63, 0x7C, 0xD9, 0x80, 0xC2, 0x41, 0x26, 0x88, 0xD6, 0xEC, 0xDE, 0x02, 0xF7, 0x22,
+ 0xD7, 0xA1, 0x03, 0x19, 0x24, 0xD4, 0x89, 0xDA, 0xC9, 0xF6, 0xCF, 0xA0, 0x4F, 0x45, 0x7C, 0x85,
+ 0x0B, 0x99, 0x63, 0x49, 0xCB, 0x7A, 0x05, 0xFA, 0x45, 0x5B, 0x32, 0xCD, 0xC2, 0x4E, 0xD5, 0xB4,
+ 0xFE, 0x7A, 0xEF, 0xE2, 0x8F, 0xBD, 0x16, 0xEC, 0x65, 0x68, 0x6F, 0xE5, 0x7A, 0x7D, 0x89, 0x47,
+ 0x9E, 0xCC, 0xD3, 0x53, 0x72, 0xC9, 0xA9, 0x38, 0x36, 0xD7, 0xAB, 0x22, 0xAD, 0x2B, 0x66, 0xDB,
+ 0xEA, 0xAE, 0xE8, 0x5C, 0xC3, 0x86, 0x3C, 0xB4, 0x42, 0x74, 0x96, 0x1B, 0x4C, 0x42, 0x22, 0xD8,
+ 0x66, 0x2C, 0x82, 0xC7, 0x44, 0xF2, 0xB6, 0x07, 0x65, 0x9C, 0x7E, 0x46, 0x50, 0x33, 0xA0, 0x64,
+ 0x03, 0x68, 0xC7, 0xC0, 0x65, 0xC4, 0x1B, 0xF2, 0x32, 0xBE, 0xD5, 0xFD, 0x7D, 0xB4, 0x19, 0xBA,
+ 0xAD, 0x27, 0x6C, 0xF4, 0x62, 0x9B, 0xCA, 0x1D, 0xB7, 0xA5, 0x37, 0xAC, 0xF5, 0x6C, 0x14, 0xEE,
+ 0x0C, 0x9B, 0x5D, 0x1D, 0x79, 0xE8, 0x57, 0x07, 0xB0, 0x59, 0x57, 0xD2, 0xB6, 0x76, 0xB9, 0x95,
+ 0x60, 0x43, 0x23, 0xEE, 0x3E, 0x32, 0xC5, 0xDC, 0x1D, 0x1F, 0x27, 0x21, 0x50, 0x6C, 0x27, 0x62,
+ 0xF4, 0xFB, 0x9A, 0x48, 0x09, 0x53, 0x85, 0x30, 0x22, 0xE5, 0x17, 0x29, 0x8B, 0x95, 0x9D, 0x60,
+ 0x82, 0xDC, 0x17, 0x3A, 0x30, 0x52, 0x49, 0x81, 0x74, 0x87, 0xFE, 0xA8, 0xD7, 0xD7, 0xDF, 0xE2,
+ 0xA5, 0xF4, 0x93, 0x92, 0x83, 0x4A, 0xAA, 0x3C, 0x70, 0x1F, 0x23, 0xCC, 0xB1, 0xD2, 0x1F, 0xAC,
+ 0xCF, 0x48, 0x7D, 0xB7, 0xCE, 0x26, 0x2D, 0x6F, 0x78, 0xDA, 0x17, 0x24, 0xAA, 0x4F, 0x71, 0x06,
+ 0x95, 0x17, 0x66, 0xF7, 0x91, 0xA4, 0x85, 0x8F, 0xF4, 0xC2, 0x28, 0x0E, 0xC7, 0x31, 0x6A, 0x1A,
+ 0x7C, 0xFF, 0x6C, 0xB9, 0x88, 0xC7, 0x70, 0xF3, 0x25, 0x73, 0xF8, 0xCD, 0x47, 0x43, 0xDA, 0x83,
+ 0x8D, 0x1D, 0xD2, 0x9F, 0x35, 0x22, 0x06, 0xD2, 0xC9, 0xDA, 0x1B, 0x85, 0x5B, 0x0A, 0x36, 0x84,
+ 0xFE, 0x4D, 0x55, 0xFC, 0xD3, 0x10, 0x2B, 0x33, 0x99, 0x1B, 0x2B, 0xAD, 0x4D, 0xA7, 0xE0, 0xE9,
+ 0x2C, 0xF5, 0x74, 0x0A, 0x4C, 0xA7, 0xD8, 0xDF, 0xB7, 0x24, 0x95, 0xEC, 0xB4, 0xB4, 0xDB, 0x82,
+ 0x86, 0x5E, 0xE8, 0xBA, 0x73, 0xD7, 0xF7, 0x4B, 0x64, 0xBA, 0xD6, 0x23, 0x19, 0xE1, 0xB3, 0x1B,
+ 0xF3, 0x76, 0x62, 0x55, 0xB1, 0x31, 0xE7, 0x97, 0x26, 0xFE, 0xBB, 0x03, 0x92, 0x72, 0x4F, 0x5F,
+ 0x0A, 0x04, 0x91, 0xDF, 0xE4, 0x64, 0xD7, 0xE5, 0x5B, 0x99, 0x66, 0xE3, 0x90, 0x5E, 0xF2, 0xBB,
+ 0xAF, 0xB6, 0x3F, 0x77, 0x64, 0x5D, 0xAD, 0x8C, 0xE9, 0xF0, 0x04, 0x5A, 0xC7, 0x49, 0x36, 0x39,
+ 0x55, 0x80, 0x98, 0x9C, 0x12, 0xC6, 0x34, 0xA4, 0x94, 0x76, 0xAF, 0x1A, 0x56, 0x4A, 0x09, 0x4E,
+ 0xC9, 0x2D, 0xD5, 0xCB, 0x03, 0xFE, 0xAA, 0xBF, 0x6D, 0xF1, 0x57, 0xA9, 0xAE, 0x38, 0x21, 0x25,
+ 0x8F, 0x10, 0x72, 0x38, 0xEC, 0x48, 0x82, 0x60, 0x4A, 0x52, 0xBC, 0x52, 0x0C, 0x28, 0x1D, 0x5D,
+ 0xE1, 0x1F, 0xE6, 0x2E, 0x9D, 0x48, 0xBE, 0x5A, 0x3D, 0xB6, 0x05, 0x4D, 0x7E, 0x19, 0x4F, 0xC2,
+ 0xB4, 0xEF, 0x4E, 0x0A, 0x26, 0xD4, 0xD0, 0x0D, 0x8F, 0x5B, 0x86, 0x13, 0x4B, 0x8D, 0x86, 0xF8,
+ 0x5C, 0xE8, 0x66, 0x76, 0xD1, 0xA5, 0xC5, 0x08, 0x3B, 0x67, 0x77, 0x03, 0xA9, 0x96, 0x9F, 0x3D,
+ 0x56, 0x44, 0xDB, 0xC5, 0xA2, 0xCE, 0xC1, 0x58, 0x18, 0x07, 0x32, 0x9E, 0x53, 0x58, 0x9C, 0x14,
+ 0xD2, 0x43, 0x65, 0x74, 0x6D, 0xAB, 0xE6, 0x34, 0xD0, 0xB5, 0xBE, 0xCF, 0x16, 0xDB, 0x99, 0xE4,
+ 0xC8, 0xC9, 0xDE, 0x44, 0x06, 0x52, 0xE5, 0x26, 0xC1, 0x2E, 0xE5, 0xAA, 0x1E, 0x84, 0xA8, 0xD0,
+ 0x12, 0xD1, 0xC3, 0x69, 0x6B, 0x22, 0x14, 0x78, 0xD7, 0x28, 0xBA, 0x49, 0x64, 0xE6, 0x89, 0x20,
+ 0x0A, 0x6E, 0xC6, 0xB0, 0x59, 0xE0, 0x27, 0xB3, 0x52, 0xCD, 0x08, 0xC9, 0x3D, 0x43, 0x62, 0x65,
+ 0x4A, 0xBB, 0x62, 0x93, 0x07, 0xCC, 0x61, 0x3B, 0xC4, 0x6A, 0xC5, 0x81, 0xA9, 0x94, 0xE4, 0x36,
+ 0x14, 0x32, 0x64, 0xAC, 0x10, 0x64, 0x69, 0xE1, 0x93, 0x14, 0x7D, 0x2B, 0x59, 0x2C, 0xE8, 0xD7,
+ 0xBA, 0xDB, 0x21, 0x8F, 0x9A, 0xEA, 0x1E, 0xB0, 0x2E, 0x5A, 0xCC, 0x69, 0xE5, 0x2C, 0x69, 0xE3,
+ 0x2C, 0x44, 0x9D, 0xF8, 0xA8, 0x63, 0x6D, 0x9A, 0xA9, 0x6C, 0x7F, 0xAA, 0xC1, 0x62, 0x5B, 0xDF,
+ 0x26, 0xD4, 0x64, 0x97, 0x7D, 0x02, 0x27, 0x08, 0x80, 0xAD, 0xE2, 0x3B, 0xCB, 0xDC, 0xB2, 0x5F,
+ 0x94, 0xAF, 0x36, 0x8B, 0xE4, 0x78, 0x35, 0x9B, 0x49, 0xF5, 0xBE, 0xBA, 0x7D, 0x9F, 0xCC, 0xF1,
+ 0xF5, 0x93, 0x0B, 0xF6, 0x4B, 0xB3, 0xBF, 0x60, 0x73, 0x34, 0xE0, 0xF2, 0x91, 0x00, 0x45, 0xC3,
+ 0x8F, 0x04, 0xA9, 0xF0, 0x24, 0x16, 0x6D, 0x86, 0xAB, 0xE7, 0x6D, 0x18, 0x19, 0x89, 0x3F, 0x5B,
+ 0x31, 0x33, 0xAB, 0xA3, 0xE6, 0xBA, 0xDE, 0x02, 0xA0, 0xB6, 0xB1, 0xCD, 0x5A, 0x32, 0xB6, 0x6D,
+ 0xEC, 0x7B, 0xF3, 0x8F, 0xDE, 0x9A, 0x50, 0x72, 0x2B, 0xE5, 0xDA, 0xFD, 0x54, 0xD2, 0xB5, 0x6D,
+ 0x6D, 0x51, 0x6B, 0x8F, 0x5C, 0x5B, 0x6A, 0xD9, 0x3F, 0x66, 0x0D, 0xC8, 0x21, 0x56, 0xF5, 0x21,
+ 0x6E, 0x0C, 0x27, 0x42, 0x46, 0x1E, 0x7A, 0xD8, 0x50, 0x6E, 0x1F, 0x37, 0xB7, 0x12, 0x0B, 0x1E,
+ 0x89, 0x32, 0x16, 0x11, 0x8C, 0x75, 0x7B, 0xE3, 0x7A, 0x92, 0x1E, 0xE7, 0xA3, 0xA0, 0x6E, 0x47,
+ 0x14, 0x2C, 0xE9, 0x2D, 0x37, 0x97, 0xC6, 0x68, 0x48, 0x98, 0xE0, 0x5D, 0xD8, 0x74, 0x4C, 0xCC,
+ 0x96, 0x09, 0x22, 0xE2, 0xDB, 0x1E, 0x95, 0x5D, 0x90, 0xE2, 0x81, 0x05, 0x49, 0x9B, 0x74, 0x36,
+ 0xAE, 0xC7, 0xB0, 0x73, 0x4A, 0x18, 0xD3, 0x37, 0x2E, 0xBC, 0x4D, 0x4B, 0xE0, 0xD1, 0xF0, 0xFD,
+ 0x94, 0x25, 0xB9, 0x65, 0x5D, 0x71, 0x14, 0xCA, 0xC3, 0x0B, 0x6B, 0xDD, 0xEC, 0x2A, 0xE9, 0x59,
+ 0xDD, 0x42, 0x6D, 0xD5, 0xE9, 0x66, 0xF7, 0xA5, 0x9B, 0xFD, 0x6F, 0x21, 0xFE, 0x54, 0x6B, 0x1D,
+ 0xAA, 0x68, 0xAD, 0x6C, 0x2A, 0x01, 0x96, 0x37, 0x17, 0x83, 0x20, 0x1F, 0x11, 0x6E, 0xAE, 0x6B,
+ 0x6B, 0xC0, 0x4A, 0x3A, 0x0F, 0x2F, 0xCF, 0xBB, 0x74, 0x52, 0x4D, 0x21, 0xBC, 0xA8, 0x07, 0x04,
+ 0xA8, 0x09, 0x0E, 0xF3, 0x5A, 0xBB, 0xB2, 0xDC, 0x92, 0xB4, 0x75, 0xC5, 0xD3, 0xC5, 0xBA, 0xC6,
+ 0xEA, 0xFB, 0x02, 0xFB, 0x2B, 0x84, 0x0D, 0xA1, 0xF8, 0xE7, 0x63, 0x64, 0x67, 0x25, 0x47, 0x42,
+ 0x73, 0x16, 0x24, 0x2B, 0x6B, 0x2F, 0x83, 0x12, 0x5F, 0xB0, 0xF3, 0xAB, 0xE6, 0xBD, 0x73, 0xD5,
+ 0xD5, 0xA0, 0xFF, 0x80, 0xF0, 0x28, 0xC2, 0x61, 0xA5, 0x22, 0x10, 0x37, 0x66, 0xB2, 0x7B, 0x4F,
+ 0x2A, 0x52, 0x7A, 0xD5, 0x1A, 0x6B, 0x2A, 0xB7, 0xDB, 0xC8, 0x59, 0xBF, 0xCE, 0xD9, 0xD3, 0xC1,
+ 0x03, 0xFC, 0x2C, 0x6A, 0xA0, 0xDE, 0x27, 0x31, 0x3B, 0x04, 0x99, 0x1A, 0x79, 0xB6, 0x25, 0x39,
+ 0xB8, 0x47, 0x6B, 0x35, 0x85, 0x1D, 0x23, 0x46, 0x49, 0xCC, 0xDA, 0xA8, 0x93, 0xB3, 0xB6, 0xDE,
+ 0x65, 0x7F, 0x21, 0xB3, 0xCA, 0x72, 0xCD, 0xA6, 0x3E, 0x3E, 0xB2, 0xC1, 0xC8, 0xDA, 0x5D, 0xC7,
+ 0x20, 0x1D, 0x12, 0x68, 0x56, 0x44, 0xC4, 0x0F, 0xD5, 0xBE, 0x67, 0x22, 0x2A, 0xBA, 0x0C, 0xEB,
+ 0xEE, 0x72, 0x83, 0x34, 0xD9, 0x53, 0xE4, 0x46, 0x27, 0x08, 0x80, 0xED, 0x9A, 0x9A, 0x76, 0x18,
+ 0xF1, 0x78, 0x3B, 0x8C, 0x50, 0x76, 0x18, 0x41, 0x76, 0x98, 0x5F, 0x6B, 0x59, 0x71, 0xE5, 0xB3,
+ 0xB6, 0x69, 0x65, 0xB3, 0xE9, 0xC0, 0x94, 0x72, 0xB4, 0xFA, 0xAD, 0x4A, 0x5F, 0xE8, 0xBB, 0xC1,
+ 0xF7, 0xBE, 0x77, 0xC2, 0x87, 0x86, 0x35, 0x3C, 0xA0, 0xAB, 0x55, 0x7B, 0xF3, 0xB7, 0xCA, 0x69,
+ 0xD3, 0xEB, 0x3E, 0xD3, 0x8E, 0x28, 0x7E, 0xBB, 0x3E, 0x37, 0x1A, 0x65, 0x68, 0xA7, 0xBC, 0x7C,
+ 0xD2, 0x4E, 0xF6, 0xB2, 0x7B, 0x15, 0xB6, 0xB1, 0x8C, 0xA3, 0x89, 0xFB, 0x38, 0x54, 0x46, 0xED,
+ 0x24, 0x11, 0x56, 0x29, 0x69, 0x4D, 0x6F, 0xDA, 0xB3, 0xFA, 0x4A, 0x43, 0x5D, 0x88, 0xF5, 0xB7,
+ 0xD0, 0x54, 0x80, 0xAA, 0x5C, 0x3A, 0x44, 0xE7, 0xDA, 0xE9, 0x90, 0x79, 0x48, 0xF2, 0x71, 0x1A,
+ 0x6E, 0x10, 0xA6, 0x33, 0x5A, 0x32, 0xBC, 0x9F, 0x40, 0x54, 0xF2, 0xA4, 0xAB, 0xB4, 0xA2, 0x92,
+ 0x73, 0x88, 0xD1, 0x78, 0xB8, 0xCC, 0x11, 0x2A, 0x0E, 0x17, 0x49, 0x59, 0x99, 0xA8, 0xD6, 0xBC,
+ 0xD2, 0x91, 0xAE, 0x45, 0x15, 0x0F, 0xD3, 0x0A, 0xF9, 0xF0, 0x19, 0xC9, 0x38, 0x87, 0x0B, 0x16,
+ 0x75, 0x5A, 0xD5, 0x93, 0x05, 0xEA, 0xF6, 0x3B, 0xF7, 0x6A, 0x57, 0xFA, 0x64, 0xC0, 0xA1, 0xD8,
+ 0xF7, 0x94, 0x9B, 0x23, 0xC4, 0x23, 0xFB, 0x36, 0x98, 0x1C, 0x0D, 0x47, 0xA0, 0xDC, 0xA8, 0x79,
+ 0xA4, 0x73, 0x16, 0xD8, 0x93, 0xED, 0x25, 0x38, 0x61, 0x12, 0xDF, 0x1F, 0x5D, 0x79, 0xB8, 0xAD,
+ 0xFA, 0x31, 0xAA, 0x57, 0xA7, 0x53, 0xBC, 0x12, 0xF2, 0x6C, 0x0A, 0xF9, 0x93, 0x5C, 0x01, 0x32,
+ 0x32, 0x49, 0x7D, 0x7F, 0x9E, 0xF0, 0x29, 0x17, 0x37, 0xF8, 0xD1, 0xDF, 0x29, 0xC9, 0x7C, 0x7F,
+ 0x5B, 0xC0, 0xD9, 0x82, 0xB7, 0x3B, 0xFA, 0xD5, 0x39, 0x64, 0xE2, 0xC8, 0x9E, 0x50, 0x31, 0xAD,
+ 0xDC, 0xC5, 0x34, 0xF0, 0xE5, 0x81, 0x5A, 0x12, 0x6B, 0x3D, 0xAF, 0x11, 0x3D, 0x1E, 0xD5, 0xCF,
+ 0x6E, 0x98, 0x54, 0x32, 0x12, 0x40, 0xE1, 0x8C, 0xD9, 0x1B, 0x2B, 0x4D, 0x65, 0x6C, 0xC4, 0x1D,
+ 0x4A, 0x8A, 0xB4, 0xF3, 0x96, 0x93, 0x46, 0x32, 0xF2, 0xC5, 0x6E, 0x6E, 0x08, 0xE4, 0x19, 0x4C,
+ 0x48, 0x83, 0xE0, 0x2B, 0x8A, 0x27, 0x6A, 0x41, 0x3A, 0x27, 0xD5, 0x09, 0x2D, 0x3F, 0xBE, 0x49,
+ 0xC5, 0xDD, 0x6A, 0x25, 0xAB, 0x36, 0x27, 0x56, 0xD9, 0xBE, 0x2C, 0xE4, 0x38, 0xDA, 0x27, 0x59,
+ 0xC9, 0x5E, 0xF6, 0x8C, 0x63, 0xB3, 0xF6, 0x4D, 0xA5, 0xDA, 0x6A, 0xAE, 0x1E, 0xA8, 0x86, 0x36,
+ 0xC3, 0x76, 0x57, 0x45, 0x5F, 0x5A, 0xD5, 0xCD, 0x9D, 0xEA, 0x9A, 0xA7, 0x74, 0xD6, 0x4E, 0xD9,
+ 0xA2, 0xB5, 0xD4, 0xD1, 0x9C, 0xF9, 0xDE, 0x6C, 0xCD, 0x7E, 0x08, 0xD6, 0x04, 0xC2, 0x1B, 0x48,
+ 0x1E, 0x44, 0x42, 0x23, 0x8F, 0x0F, 0x0B, 0xE0, 0x83, 0x91, 0x4A, 0xE0, 0x57, 0xC6, 0x67, 0x90,
+ 0xB1, 0x11, 0x70, 0x8A, 0x29, 0x8C, 0x34, 0x36, 0x5C, 0x65, 0xCE, 0x81, 0x05, 0x76, 0x16, 0xF8,
+ 0x80, 0x93, 0xDA, 0xAE, 0x6C, 0xD1, 0x53, 0x92, 0x87, 0x43, 0x49, 0xFC, 0x66, 0x51, 0xB2, 0x5D,
+ 0x96, 0xDC, 0xE4, 0xB0, 0x92, 0x47, 0x91, 0x64, 0x48, 0x49, 0x14, 0x75, 0xB2, 0xA9, 0x65, 0x6C,
+ 0xAA, 0xA3, 0xB4, 0x3E, 0xA0, 0x5D, 0x82, 0xD4, 0x4D, 0xE5, 0x8F, 0xAF, 0xDA, 0x05, 0x8F, 0x4D,
+ 0xAF, 0x71, 0x4C, 0x00, 0x57, 0xC9, 0x66, 0x61, 0xB7, 0x43, 0xD9, 0x96, 0x7E, 0xC0, 0xF4, 0x40,
+ 0xD5, 0xEE, 0x1E, 0x13, 0x01, 0x1B, 0x94, 0x8D, 0x68, 0x00, 0x36, 0x1D, 0x94, 0x75, 0x3E, 0x4D,
+ 0x72, 0x1F, 0x67, 0xF6, 0xBC, 0x48, 0x10, 0xB9, 0xA1, 0xFF, 0x71, 0x0C, 0xE5, 0xD5, 0x15, 0x1A,
+ 0xDC, 0x0C, 0x22, 0x48, 0xE4, 0xCB, 0x79, 0x4E, 0x7A, 0x68, 0xB4, 0x34, 0x9B, 0x0D, 0xB0, 0xDD,
+ 0x7C, 0x4E, 0x0F, 0xD4, 0xF1, 0x02, 0x99, 0xAE, 0xEF, 0xE5, 0x56, 0x03, 0xEF, 0x88, 0xC2, 0xF4,
+ 0xE7, 0xFC, 0x7B, 0x03, 0x32, 0x9F, 0x66, 0xF4, 0xB8, 0x0E, 0x13, 0x8A, 0xBA, 0x77, 0xCA, 0xE3,
+ 0x20, 0x19, 0xEC, 0xFC, 0x45, 0xAF, 0xF0, 0x0D, 0x96, 0x4D, 0x01, 0x7A, 0x39, 0x66, 0xDE, 0xAD,
+ 0x97, 0x43, 0x1D, 0x3E, 0x16, 0x94, 0x3D, 0xD9, 0x15, 0x0D, 0x2F, 0xF5, 0x1A, 0x84, 0x7A, 0x52,
+ 0x88, 0xC6, 0xDA, 0xCC, 0xDC, 0xEC, 0xDE, 0x5E, 0xBD, 0xB2, 0xE6, 0x77, 0x5D, 0x97, 0x7C, 0x0B,
+ 0x42, 0x87, 0xB7, 0x7E, 0x32, 0x76, 0x64, 0xF5, 0x59, 0x0F, 0xCB, 0x4E, 0xDC, 0xC8, 0x6B, 0x47,
+ 0xD3, 0xE8, 0xAE, 0xD3, 0xBE, 0x62, 0x67, 0xE3, 0x2C, 0x39, 0xB4, 0x1C, 0x54, 0x8B, 0xB0, 0x01,
+ 0xFC, 0x41, 0xCA, 0x3B, 0x14, 0x52, 0x49, 0x01, 0x85, 0xC5, 0xDE, 0x56, 0xC2, 0xB9, 0x4C, 0xE2,
+ 0x5C, 0xD6, 0xC6, 0xB9, 0x4C, 0x8D, 0x3B, 0xAF, 0xE3, 0xDC, 0xA6, 0xD1, 0x65, 0x6D, 0x0C, 0x33,
+ 0x38, 0x44, 0x73, 0xF8, 0xF3, 0x02, 0x1E, 0xBA, 0x32, 0xC2, 0x71, 0x51, 0xF2, 0xC0, 0x32, 0xB5,
+ 0x2C, 0x47, 0x6B, 0x4B, 0x7F, 0xAF, 0x6B, 0x04, 0x47, 0x9F, 0x38, 0x76, 0xC0, 0x27, 0x8E, 0x59,
+ 0x3A, 0xF2, 0xAE, 0x52, 0xC0, 0x7C, 0xCC, 0x36, 0x1A, 0xD6, 0x02, 0x80, 0x8D, 0xFC, 0x1B, 0x4A,
+ 0x5D, 0x00, 0xAF, 0xF2, 0x21, 0x34, 0x9B, 0x6C, 0xC2, 0x42, 0x7D, 0xE0, 0xDF, 0x50, 0xF2, 0x53,
+ 0xBC, 0xCA, 0x07, 0x77, 0xAB, 0x4D, 0xF8, 0x51, 0x3F, 0xDC, 0xCB, 0xA2, 0xCE, 0xDE, 0xB6, 0x4B,
+ 0xA7, 0x67, 0xDC, 0x4B, 0x2C, 0x0D, 0xBD, 0xB7, 0xE6, 0x2D, 0x77, 0x20, 0x8B, 0xEB, 0xA7, 0x01,
+ 0x6A, 0x6C, 0x36, 0xD1, 0xC1, 0xAA, 0xA7, 0x07, 0x69, 0xF0, 0x45, 0x7C, 0xCC, 0xA7, 0x9E, 0xAA,
+ 0x94, 0x20, 0x34, 0x79, 0x74, 0xF7, 0x0F, 0x32, 0x95, 0x29, 0x8B, 0x75, 0x12, 0xA6, 0xF5, 0xA3,
+ 0xDD, 0xCE, 0xC3, 0xBD, 0xAC, 0x6D, 0xF7, 0x91, 0xE0, 0x48, 0x35, 0x20, 0x32, 0xA7, 0xF3, 0xB7,
+ 0xBC, 0x83, 0xDF, 0xD0, 0x30, 0xF7, 0xF0, 0xC4, 0xBD, 0xBD, 0xDA, 0xAB, 0x3C, 0x14, 0xD0, 0x2A,
+ 0x11, 0xF6, 0x5C, 0xDB, 0xDD, 0x23, 0xFA, 0x02, 0xC9, 0x7A, 0x4E, 0x27, 0xF0, 0x18, 0x8C, 0xEF,
+ 0x4F, 0x72, 0xE6, 0x8C, 0x14, 0x61, 0x0A, 0xBF, 0x30, 0x48, 0x33, 0x57, 0x93, 0x39, 0xC5, 0xEA,
+ 0xE7, 0xFB, 0x62, 0x0F, 0x48, 0x6F, 0x9A, 0x97, 0x95, 0x3A, 0xD8, 0x16, 0xD2, 0x8F, 0xCA, 0x78,
+ 0x6C, 0xFB, 0x7B, 0x6F, 0x91, 0x45, 0x71, 0xDF, 0xAE, 0xD3, 0xF0, 0x6C, 0xFE, 0x3B, 0x27, 0xFF,
+ 0xD0, 0xAB, 0x88, 0x17, 0x50, 0x60, 0xD0, 0x84, 0xFE, 0x4C, 0xBD, 0x91, 0x09, 0x78, 0x63, 0x21,
+ 0x20, 0xF8, 0x32, 0x3E, 0xB2, 0x45, 0xCF, 0x9C, 0xA2, 0xBE, 0xCF, 0xDC, 0x75, 0xD0, 0xE0, 0xD2,
+ 0x91, 0xDD, 0xC7, 0x12, 0x68, 0xCE, 0x6C, 0x53, 0xCC, 0x53, 0x8B, 0x29, 0x9E, 0xB8, 0x4C, 0x71,
+ 0x5A, 0xDD, 0xF0, 0x56, 0xC6, 0xA9, 0x6C, 0x21, 0xD2, 0x4B, 0x11, 0xC3, 0x99, 0x31, 0xD7, 0xAB,
+ 0x81, 0xC8, 0x9F, 0xCB, 0x6C, 0x0C, 0x29, 0x96, 0x46, 0xD0, 0x32, 0x77, 0xD5, 0x56, 0x7F, 0x61,
+ 0xAB, 0x97, 0xFC, 0x9C, 0x8F, 0xE9, 0x4C, 0x3F, 0x32, 0xDF, 0x8D, 0xEF, 0x19, 0x6A, 0x9A, 0xE6,
+ 0x0E, 0x24, 0xA2, 0x68, 0xE9, 0xB4, 0x21, 0xDF, 0x9C, 0x73, 0x4D, 0x64, 0xAF, 0x91, 0xF8, 0x3D,
+ 0x51, 0xF8, 0x4D, 0x2D, 0xB0, 0x35, 0xE6, 0x0E, 0x53, 0x4E, 0xB5, 0x4B, 0x2D, 0x85, 0x86, 0x71,
+ 0x8F, 0x14, 0x5B, 0x7D, 0x3F, 0xE0, 0xCC, 0xF6, 0xB0, 0x41, 0x94, 0xF1, 0xED, 0x90, 0xE9, 0x0B,
+ 0xA4, 0x2C, 0xDA, 0xBF, 0xC6, 0xEF, 0x0F, 0xD4, 0x46, 0x87, 0xF3, 0x42, 0xFB, 0x6A, 0x33, 0xA5,
+ 0x83, 0x63, 0x0C, 0xCC, 0xEE, 0x20, 0x5A, 0x94, 0x30, 0x1D, 0x5D, 0x63, 0x38, 0x75, 0xE1, 0xCD,
+ 0xCC, 0x36, 0x80, 0x01, 0xF6, 0x92, 0x7F, 0x94, 0x87, 0xA9, 0x6C, 0x2A, 0x6C, 0xB3, 0x5F, 0xC0,
+ 0x73, 0x89, 0x04, 0xB2, 0x2F, 0xD5, 0xC1, 0x19, 0x37, 0xC1, 0x59, 0x3B, 0x1C, 0x45, 0xC3, 0x2D,
+ 0x8D, 0x4F, 0x18, 0x5E, 0x57, 0x72, 0x78, 0x43, 0x39, 0xFE, 0x90, 0x87, 0xED, 0xE2, 0x20, 0x2F,
+ 0x9F, 0x2F, 0x8E, 0xFA, 0x6A, 0x09, 0xC9, 0xC1, 0xCB, 0x48, 0x00, 0x6C, 0xB2, 0xF0, 0xF8, 0xD0,
+ 0x6C, 0x3E, 0xD7, 0x10, 0x15, 0xDF, 0xAC, 0x56, 0x4E, 0x2A, 0x58, 0x43, 0x39, 0x17, 0x1C, 0x8B,
+ 0x81, 0xF4, 0x39, 0xD6, 0x67, 0x45, 0x7D, 0xCB, 0xF4, 0x6A, 0x5D, 0xAD, 0x78, 0x90, 0x43, 0xCF,
+ 0x14, 0x47, 0xF3, 0x4E, 0x21, 0xA7, 0x17, 0x19, 0x28, 0xCF, 0x6C, 0x76, 0xCA, 0x47, 0xBC, 0x61,
+ 0x6E, 0x48, 0x4D, 0x96, 0x6E, 0x16, 0x59, 0xA1, 0xFD, 0xC8, 0xDF, 0x32, 0xE5, 0x83, 0x41, 0x2E,
+ 0xDD, 0x1B, 0x9D, 0x14, 0xE8, 0x19, 0xE7, 0x43, 0xAF, 0x2D, 0x0E, 0xD7, 0x4E, 0x41, 0xE3, 0x5D,
+ 0xF6, 0xC2, 0x22, 0xDC, 0xA9, 0xBB, 0x5E, 0xA5, 0xAA, 0xA3, 0xF5, 0x1C, 0xDB, 0xC5, 0x8A, 0x16,
+ 0xEA, 0xC0, 0xFB, 0x08, 0x3B, 0xCF, 0xBD, 0xC7, 0xC2, 0xDF, 0x87, 0x2A, 0xD6, 0xC7, 0x28, 0x86,
+ 0xCF, 0xF4, 0x73, 0x9A, 0x85, 0x1F, 0xD5, 0x73, 0x41, 0xCC, 0xC2, 0xB2, 0x9C, 0x27, 0x55, 0xE3,
+ 0x54, 0xAE, 0x0F, 0x94, 0xF0, 0x4C, 0x86, 0xA4, 0x3A, 0x0B, 0xE9, 0xB5, 0xEE, 0x4D, 0x5B, 0xF6,
+ 0x00, 0xBF, 0xE6, 0x6D, 0x95, 0x47, 0x8A, 0x89, 0x1C, 0x69, 0xF6, 0x71, 0x24, 0xF9, 0x06, 0xF6,
+ 0x42, 0xBA, 0x4B, 0xF2, 0x69, 0x55, 0x3F, 0x07, 0xAF, 0xCD, 0xB1, 0xDD, 0xEE, 0x08, 0x3A, 0x13,
+ 0x15, 0xF9, 0x25, 0xC3, 0xE6, 0x11, 0xBE, 0x30, 0xE2, 0x2D, 0xCB, 0x23, 0x1D, 0x82, 0x2D, 0xEB,
+ 0x41, 0x9D, 0x72, 0x2D, 0xAA, 0xD6, 0x62, 0x1B, 0xF3, 0x22, 0xAA, 0x87, 0x82, 0xEF, 0xB4, 0xB4,
+ 0x06, 0xC9, 0x23, 0x6D, 0x48, 0x3F, 0x61, 0x81, 0x24, 0x9C, 0xCD, 0x9F, 0x5E, 0xA5, 0xC0, 0x12,
+ 0xD2, 0xB4, 0x95, 0x4C, 0x75, 0xCE, 0xC4, 0xA2, 0x44, 0x44, 0xDD, 0x35, 0xB0, 0xB3, 0x47, 0x1B,
+ 0x6F, 0xB9, 0x1C, 0xC5, 0xD2, 0x61, 0xF2, 0x12, 0x8A, 0x9C, 0x1B, 0xE6, 0xA4, 0x75, 0xDA, 0xF9,
+ 0x4A, 0x78, 0xBE, 0xB4, 0x4D, 0x2E, 0x52, 0x46, 0x3A, 0x42, 0x6C, 0xF2, 0xE5, 0xCA, 0x3E, 0x4E,
+ 0xE3, 0x4E, 0xD9, 0xE5, 0x75, 0xA5, 0xC3, 0x60, 0x8C, 0xCA, 0x40, 0xC7, 0xFD, 0xEB, 0xBC, 0xB5,
+ 0xCD, 0x39, 0x7E, 0x6B, 0x86, 0x08, 0xCC, 0x63, 0x74, 0x76, 0xA9, 0xBB, 0x49, 0xFC, 0xDC, 0x3C,
+ 0x43, 0x46, 0x89, 0x98, 0xE2, 0xF2, 0xE4, 0x14, 0x74, 0x94, 0xA8, 0x4F, 0x51, 0x6B, 0x13, 0x12,
+ 0x2B, 0x48, 0xE8, 0x9E, 0xC4, 0xE0, 0xED, 0x39, 0x1D, 0xE7, 0xE6, 0x31, 0x79, 0x9A, 0x0D, 0xD2,
+ 0x2A, 0xC2, 0xD8, 0xAE, 0xCC, 0x3B, 0x2A, 0x2C, 0xE9, 0x70, 0xDF, 0x54, 0x36, 0xD4, 0x33, 0x42,
+ 0xEB, 0x70, 0x3C, 0xDA, 0x6F, 0x27, 0xCE, 0x46, 0x07, 0x05, 0xFD, 0x51, 0x5F, 0x14, 0x48, 0x91,
+ 0x17, 0xC7, 0xF7, 0xCA, 0x0F, 0xED, 0x22, 0x74, 0xAA, 0xEF, 0x39, 0x77, 0xE3, 0x5D, 0x7C, 0x3D,
+ 0xD0, 0x0D, 0x5F, 0xCB, 0xBD, 0x11, 0x4A, 0xA0, 0xC0, 0x49, 0x3A, 0x91, 0x4A, 0x91, 0x22, 0x07,
+ 0x12, 0x08, 0x23, 0xE3, 0xF9, 0xE1, 0x1F, 0x0E, 0x6E, 0x0E, 0xFF, 0x10, 0xDE, 0xE2, 0x58, 0xA8,
+ 0xC5, 0x08, 0xA7, 0x9C, 0xBE, 0x3B, 0x98, 0xA0, 0xB9, 0x83, 0xE9, 0xF0, 0x6A, 0x84, 0xA3, 0x45,
+ 0xDF, 0xE1, 0x33, 0xBD, 0x1F, 0xFE, 0x61, 0xFF, 0x12, 0x47, 0x8A, 0x62, 0x59, 0xDC, 0x86, 0x77,
+ 0x21, 0x5C, 0xD3, 0x27, 0xF1, 0xAC, 0xDF, 0x98, 0x7F, 0xDA, 0x21, 0xED, 0xFB, 0x7C, 0x3A, 0xCA,
+ 0xF0, 0x64, 0x14, 0x9F, 0x01, 0xB8, 0x63, 0x34, 0xA7, 0x31, 0x22, 0x3E, 0x3B, 0xB8, 0xA3, 0xB3,
+ 0x25, 0x1E, 0xA5, 0x39, 0x18, 0xCC, 0xD3, 0x58, 0x07, 0xB4, 0xB4, 0xB6, 0xB2, 0x81, 0x0A, 0x55,
+ 0x91, 0xD0, 0x39, 0x60, 0x7C, 0x1C, 0x79, 0x51, 0xAA, 0x02, 0x00, 0x20, 0x50, 0xB4, 0x4F, 0xA6,
+ 0x02, 0x85, 0x83, 0x40, 0xD4, 0x52, 0x08, 0x1A, 0xE7, 0xB3, 0x67, 0x01, 0x1D, 0xA1, 0x03, 0x99,
+ 0xA7, 0x95, 0x31, 0x24, 0x51, 0xB8, 0xA5, 0x03, 0xC4, 0x59, 0x50, 0x13, 0x62, 0x6B, 0x4B, 0xC0,
+ 0x1B, 0x99, 0x4F, 0x4F, 0xAF, 0xCE, 0x3E, 0xC2, 0x26, 0x4A, 0x59, 0x94, 0xC9, 0xEA, 0xFC, 0x16,
+ 0x56, 0x9C, 0x19, 0xD6, 0x29, 0x8E, 0x70, 0x7D, 0x5F, 0xC5, 0x4C, 0x2A, 0xA4, 0x9D, 0x44, 0xD2,
+ 0x0B, 0xF5, 0xA2, 0x88, 0x86, 0x7A, 0x63, 0xCA, 0xA1, 0x9E, 0x1D, 0x01, 0xF9, 0x79, 0x55, 0x5B,
+ 0xC8, 0xA6, 0xC7, 0xE6, 0x91, 0x84, 0x5F, 0x5A, 0xCF, 0x16, 0xB5, 0xC3, 0xDC, 0x88, 0x9C, 0x25,
+ 0x05, 0x58, 0x18, 0xFE, 0x14, 0x26, 0x24, 0x3D, 0xCF, 0x17, 0x27, 0xE3, 0x31, 0x86, 0x2A, 0xCF,
+ 0x1A, 0xC6, 0xCA, 0x25, 0x73, 0x6E, 0x32, 0x27, 0x4E, 0x40, 0xA7, 0x98, 0x4B, 0x72, 0xA9, 0x57,
+ 0xFA, 0x34, 0x66, 0x05, 0x71, 0x3C, 0xE8, 0x9A, 0xD1, 0x8F, 0xDC, 0xA3, 0x7B, 0x74, 0x46, 0x4B,
+ 0x3F, 0xE2, 0x16, 0xDE, 0xC8, 0x97, 0xE0, 0x8E, 0xB3, 0x57, 0x54, 0xFB, 0x6A, 0x75, 0xAC, 0xA5,
+ 0xF2, 0x8F, 0x11, 0xB6, 0xBA, 0xE2, 0x9F, 0xF8, 0x3C, 0x0B, 0x0E, 0x33, 0x3A, 0xFE, 0x29, 0xBC,
+ 0x57, 0x49, 0xA9, 0x49, 0xC2, 0xE9, 0x7E, 0x79, 0xE7, 0x15, 0x20, 0xE3, 0xC1, 0x98, 0xBE, 0xE4,
+ 0x58, 0x95, 0x53, 0x34, 0xBB, 0xB0, 0x08, 0x33, 0xC1, 0x7E, 0xA5, 0x09, 0x16, 0xE5, 0x14, 0xFD,
+ 0x98, 0xDB, 0xE4, 0x2B, 0x24, 0x5F, 0x61, 0x61, 0xE5, 0x4D, 0x0D, 0xCA, 0x03, 0xDF, 0x0F, 0xAF,
+ 0x3B, 0xD2, 0xEF, 0x3D, 0x5A, 0x64, 0x79, 0x85, 0x85, 0x93, 0x56, 0x58, 0x33, 0xD6, 0x8A, 0x33,
+ 0x93, 0x23, 0xBE, 0xA7, 0x95, 0x98, 0x06, 0x58, 0x39, 0x9E, 0xBB, 0xFC, 0xBC, 0xF0, 0x4C, 0x27,
+ 0xBC, 0x95, 0xA4, 0xED, 0x9E, 0xBA, 0x55, 0x13, 0x66, 0x94, 0xE4, 0x75, 0x1F, 0x9F, 0x51, 0xB2,
+ 0x11, 0x18, 0x08, 0x99, 0xEF, 0xB4, 0x83, 0xD7, 0xD4, 0xA6, 0x13, 0x64, 0x6D, 0x01, 0xD6, 0xED,
+ 0x7D, 0x58, 0xD2, 0xD2, 0x20, 0x23, 0xC9, 0x65, 0x8C, 0xF9, 0x9D, 0x1F, 0xC4, 0xF7, 0xC3, 0xBB,
+ 0xD1, 0x41, 0xA6, 0x95, 0x9A, 0xF9, 0xE7, 0x71, 0xC2, 0x07, 0x0C, 0x07, 0x9C, 0x93, 0xCD, 0x29,
+ 0xEF, 0x62, 0x10, 0xA7, 0x05, 0xE5, 0x3C, 0x43, 0x4E, 0xA5, 0x0D, 0x2D, 0x74, 0x46, 0xC9, 0x57,
+ 0x4E, 0x70, 0x6C, 0x70, 0x9D, 0x6E, 0x5A, 0x75, 0xBC, 0x58, 0x53, 0x2C, 0xEF, 0x7B, 0x7B, 0xD8,
+ 0x68, 0x32, 0x68, 0x91, 0xD8, 0x8B, 0xD0, 0xF7, 0x4F, 0x98, 0x28, 0x5C, 0x8E, 0x88, 0x3E, 0x1D,
+ 0x81, 0xBE, 0x7B, 0xE1, 0xC9, 0xF0, 0xDD, 0x28, 0xBE, 0x31, 0x6F, 0x56, 0x6A, 0x89, 0xFD, 0xDB,
+ 0x2E, 0x14, 0x09, 0xBE, 0xF8, 0xC3, 0x40, 0xCA, 0x26, 0xB3, 0x84, 0x7C, 0x07, 0xFB, 0x0B, 0xF2,
+ 0x5F, 0x86, 0x3B, 0xDE, 0xFE, 0x9C, 0x1E, 0x02, 0x54, 0x63, 0xBE, 0xFE, 0x71, 0x52, 0xFB, 0x2E,
+ 0x1F, 0x8E, 0x02, 0x34, 0x03, 0x7A, 0xDE, 0xD9, 0x3F, 0x61, 0xFB, 0xC7, 0xD9, 0xB9, 0x53, 0x42,
+ 0x76, 0x71, 0x61, 0x13, 0x9C, 0x5E, 0xD2, 0x6B, 0x20, 0x01, 0xF4, 0x52, 0x33, 0xDE, 0x86, 0x0A,
+ 0xDB, 0x66, 0xC0, 0xDA, 0xEA, 0xF8, 0xB0, 0x7D, 0xC9, 0x25, 0x81, 0x58, 0x3A, 0x69, 0x6B, 0x45,
+ 0x96, 0x16, 0x8D, 0x41, 0xD9, 0x32, 0x62, 0xD0, 0x76, 0x85, 0x16, 0xF6, 0x23, 0x60, 0x97, 0x63,
+ 0x49, 0xA7, 0xF5, 0x15, 0x3B, 0xB3, 0x19, 0x70, 0x4E, 0x60, 0x82, 0x25, 0xED, 0x30, 0xC0, 0x6B,
+ 0x2C, 0x40, 0x97, 0x6D, 0x2B, 0x23, 0x4E, 0x9B, 0x22, 0x5A, 0xDA, 0x12, 0x31, 0x1F, 0xB5, 0x5F,
+ 0x1A, 0xBD, 0x8D, 0xCA, 0xB5, 0x22, 0xCD, 0x62, 0x9B, 0xC8, 0xB0, 0xB7, 0x67, 0x6C, 0x1A, 0xEA,
+ 0x43, 0xDC, 0x9A, 0xAD, 0x46, 0x86, 0xF0, 0x79, 0x07, 0x53, 0x1F, 0x87, 0x4B, 0x45, 0xD9, 0xA2,
+ 0xAD, 0xED, 0x85, 0xDB, 0xCC, 0x52, 0x1A, 0x98, 0x08, 0x0E, 0x77, 0x41, 0x17, 0xE1, 0xD0, 0x31,
+ 0xFC, 0x17, 0x76, 0x0E, 0x47, 0xF2, 0x04, 0x67, 0x18, 0x32, 0xA1, 0x3D, 0x8A, 0xFA, 0xF7, 0x4F,
+ 0x1D, 0x84, 0x2C, 0xD4, 0x61, 0x13, 0xB3, 0x9D, 0xDE, 0x3D, 0xEE, 0xEC, 0xB5, 0x35, 0x2A, 0x6D,
+ 0x03, 0x71, 0x3B, 0x53, 0xB8, 0xF4, 0x5C, 0x86, 0x6B, 0x90, 0xC3, 0xAB, 0x4B, 0x4B, 0xEB, 0x90,
+ 0x72, 0xF1, 0x16, 0xE8, 0x6F, 0x2A, 0xA0, 0x14, 0xEA, 0xE4, 0xCE, 0x40, 0xD4, 0x7D, 0x65, 0x16,
+ 0x89, 0x70, 0x37, 0xCA, 0x3E, 0xD6, 0xFE, 0x1A, 0x2C, 0x1F, 0x29, 0x36, 0x68, 0xFB, 0x6F, 0x98,
+ 0xD9, 0x05, 0x44, 0x2E, 0x1F, 0x75, 0xF8, 0x49, 0x98, 0x5B, 0xCC, 0xC7, 0x2E, 0x05, 0xB0, 0x3E,
+ 0x7C, 0x92, 0x47, 0x5B, 0x87, 0x89, 0xFD, 0x54, 0xAC, 0x56, 0x05, 0x16, 0xC9, 0xA4, 0x43, 0x0E,
+ 0x20, 0xF1, 0xD5, 0x7A, 0x37, 0x84, 0xAA, 0x97, 0x75, 0x63, 0x57, 0x2C, 0x0B, 0x6B, 0x5F, 0x6C,
+ 0x69, 0xE7, 0x34, 0xCB, 0x71, 0xB7, 0xC1, 0xAC, 0xE3, 0x90, 0x59, 0x1D, 0xDE, 0x03, 0x5B, 0xA3,
+ 0xDC, 0xA1, 0x04, 0xF0, 0xB2, 0xDF, 0x07, 0x47, 0x31, 0xCE, 0x3A, 0xB2, 0xCB, 0x01, 0xD5, 0xB2,
+ 0xD7, 0xCC, 0x93, 0x9B, 0x1B, 0xD7, 0x5E, 0xDD, 0x4F, 0x69, 0x5F, 0x96, 0x78, 0x44, 0x17, 0xD6,
+ 0x0E, 0x7E, 0x7C, 0x05, 0x7C, 0x90, 0xC2, 0x0C, 0x4B, 0x3B, 0x46, 0xE8, 0xA1, 0x34, 0x23, 0xF4,
+ 0x48, 0xF5, 0x8E, 0x25, 0x23, 0x99, 0xE2, 0x0A, 0x3D, 0x6F, 0xAB, 0xEE, 0x63, 0x9C, 0xA8, 0x86,
+ 0x15, 0xD7, 0xB6, 0x92, 0x85, 0x56, 0xA8, 0xE0, 0xF0, 0xDA, 0x7A, 0x21, 0x9D, 0x62, 0x5F, 0x55,
+ 0x74, 0x3A, 0xA6, 0xA2, 0xE3, 0x3F, 0xA3, 0x4B, 0xE4, 0x73, 0x42, 0x2F, 0x78, 0xAB, 0x0E, 0xFE,
+ 0x44, 0x1E, 0x27, 0xB8, 0xCD, 0xFE, 0xB2, 0xA1, 0x59, 0xCE, 0xB8, 0x42, 0x99, 0x0D, 0x4D, 0xFD,
+ 0xAC, 0x9B, 0xB2, 0xC7, 0x50, 0x71, 0x4D, 0xD6, 0x6C, 0x62, 0x8C, 0x93, 0xF6, 0x9C, 0x1E, 0x5A,
+ 0x5E, 0xC9, 0xB5, 0xF8, 0x5E, 0x2E, 0xE3, 0xD0, 0x1C, 0xF2, 0xA3, 0xD2, 0x7F, 0x90, 0xE9, 0x8E,
+ 0xB5, 0xEE, 0x3B, 0xDB, 0x3B, 0xB6, 0x88, 0x9E, 0xD1, 0x7B, 0x20, 0x0F, 0x03, 0xE2, 0xF6, 0x7A,
+ 0xB6, 0x72, 0x5B, 0xEA, 0x6B, 0xA7, 0x27, 0xF7, 0xCA, 0x80, 0x03, 0x92, 0xA8, 0x24, 0xD4, 0x30,
+ 0x73, 0x5E, 0xBE, 0x0F, 0x4B, 0xE7, 0xED, 0x07, 0xD5, 0xE5, 0x43, 0x92, 0x48, 0x57, 0xB2, 0xEA,
+ 0x15, 0x7D, 0xC4, 0x75, 0x14, 0x2B, 0x19, 0x51, 0xA2, 0x9C, 0xE4, 0xE9, 0x7E, 0xB9, 0x9F, 0x39,
+ 0x83, 0x7F, 0xE3, 0x9A, 0x15, 0xFB, 0x66, 0xE1, 0x09, 0x0E, 0x8E, 0x84, 0x97, 0xAF, 0x2F, 0x0D,
+ 0x1C, 0x26, 0x7F, 0x65, 0x37, 0xB5, 0xD6, 0xAC, 0x23, 0xF8, 0xF9, 0xAD, 0xB6, 0xA2, 0xB9, 0x96,
+ 0x12, 0xCA, 0xCF, 0x4A, 0xA5, 0x30, 0x06, 0x39, 0x7B, 0x12, 0x52, 0x74, 0x25, 0xBD, 0xF6, 0x5F,
+ 0x57, 0xF2, 0x3A, 0xA7, 0xCA, 0x3F, 0xE1, 0xFC, 0x6B, 0x1A, 0x7A, 0xC9, 0xFB, 0xBA, 0x94, 0xBF,
+ 0xCE, 0x4F, 0x9B, 0x7E, 0xB7, 0x60, 0x20, 0x3B, 0x1B, 0xA5, 0x5C, 0x15, 0x36, 0x9C, 0xD1, 0xE4,
+ 0x65, 0x14, 0xA5, 0x5C, 0x0E, 0x86, 0xB9, 0xA1, 0x0C, 0x39, 0x9D, 0x05, 0xB2, 0x48, 0x66, 0xE4,
+ 0x93, 0xA3, 0xB3, 0x04, 0x56, 0x2B, 0xEC, 0xA4, 0xFD, 0x9A, 0x72, 0x0E, 0xB2, 0x08, 0x03, 0x8C,
+ 0x48, 0xD2, 0x16, 0x3A, 0xB7, 0x25, 0x0C, 0xE5, 0x20, 0x89, 0x12, 0x9D, 0xFC, 0x06, 0x3D, 0xA3,
+ 0x8F, 0x2E, 0xDA, 0x7C, 0xBB, 0xD5, 0xE4, 0x50, 0x85, 0x72, 0x59, 0x91, 0xE0, 0x2D, 0x0F, 0x75,
+ 0xBA, 0xE7, 0xA5, 0xC5, 0x29, 0xFB, 0x95, 0x14, 0xF1, 0xD4, 0xFA, 0xA2, 0x8F, 0x48, 0x92, 0xF2,
+ 0xE1, 0xDA, 0x69, 0xE2, 0xFB, 0x9A, 0x2D, 0xC2, 0xBB, 0x55, 0x43, 0xF0, 0xD8, 0x0B, 0x84, 0xF6,
+ 0x5D, 0xF4, 0xAE, 0x9B, 0xFD, 0x18, 0xDF, 0x08, 0x61, 0x1A, 0xA3, 0x07, 0x54, 0x53, 0x57, 0xFD,
+ 0x24, 0x79, 0xA5, 0xA6, 0xA0, 0x02, 0x7C, 0x47, 0x00, 0xC8, 0x91, 0x06, 0x83, 0xBC, 0x04, 0x03,
+ 0x22, 0xA9, 0xCA, 0x6D, 0xC4, 0x58, 0xDC, 0x3E, 0xE1, 0x0F, 0x76, 0xC7, 0x88, 0x94, 0x80, 0xFB,
+ 0x15, 0x91, 0x23, 0x05, 0x66, 0x23, 0xE8, 0x05, 0x9F, 0x97, 0xC9, 0x15, 0xE2, 0x49, 0xF4, 0x06,
+ 0xEB, 0x0E, 0x63, 0x1D, 0x1D, 0x80, 0x0F, 0x6A, 0xEF, 0x1A, 0xC1, 0x13, 0xF3, 0x8A, 0x35, 0x05,
+ 0x72, 0xA6, 0xDC, 0x02, 0xA5, 0xB6, 0x83, 0xE7, 0xB0, 0xEA, 0x17, 0xFB, 0xDF, 0xF1, 0x98, 0xEE,
+ 0xB1, 0x1B, 0x6C, 0x4D, 0x8B, 0x49, 0x22, 0x4F, 0x87, 0xAE, 0xF3, 0xCE, 0x59, 0xC3, 0x3B, 0xEC,
+ 0x03, 0x88, 0xF9, 0xEF, 0xBE, 0x3A, 0xA6, 0x80, 0x2D, 0xED, 0xD2, 0x49, 0x10, 0xCB, 0x1F, 0xFD,
+ 0x45, 0xF6, 0x46, 0xBB, 0x15, 0x74, 0xA9, 0x3A, 0x7C, 0xA4, 0x0F, 0xC2, 0x96, 0xAC, 0x41, 0x53,
+ 0x39, 0x03, 0xE2, 0x66, 0xAA, 0x1C, 0x47, 0xDC, 0xAA, 0xEA, 0xA3, 0xAC, 0x86, 0x1E, 0xEF, 0x65,
+ 0x73, 0x30, 0x25, 0xD1, 0x5D, 0x5E, 0x5D, 0x93, 0xEB, 0x4C, 0xEB, 0x67, 0x95, 0x5C, 0x21, 0x6A,
+ 0x71, 0x88, 0x8D, 0x8B, 0x43, 0xF0, 0xE2, 0xC0, 0xBC, 0x7D, 0x20, 0x1B, 0x90, 0xA3, 0xAB, 0x84,
+ 0x75, 0x34, 0x28, 0x07, 0xA5, 0xFB, 0x35, 0x3A, 0xA2, 0x14, 0x95, 0x43, 0xA5, 0x60, 0x65, 0xB9,
+ 0xB5, 0xA8, 0x31, 0xD4, 0xB1, 0x87, 0x4A, 0xD5, 0xBE, 0xD7, 0x2A, 0xD2, 0x49, 0xB4, 0xCB, 0xF3,
+ 0x20, 0x73, 0x28, 0xA0, 0x9A, 0xDA, 0x99, 0x4D, 0xC5, 0x14, 0xA9, 0x19, 0x54, 0x77, 0x24, 0x11,
+ 0x3D, 0x84, 0xA0, 0x00, 0x13, 0xB3, 0xDE, 0x97, 0xC6, 0x87, 0x50, 0xEF, 0x53, 0x97, 0xEA, 0x43,
+ 0x69, 0x77, 0xFC, 0x20, 0x37, 0x28, 0x95, 0x6B, 0x94, 0x2A, 0x80, 0x52, 0x09, 0x90, 0x89, 0x4E,
+ 0xC5, 0x27, 0xA8, 0xD6, 0x96, 0xF6, 0x0F, 0x1B, 0x1D, 0x5D, 0xC7, 0x8E, 0xA3, 0xEB, 0xEF, 0x75,
+ 0x6D, 0x5F, 0xC9, 0x1C, 0x4A, 0xE6, 0x51, 0xE2, 0x4A, 0x5B, 0xDB, 0xC7, 0x1D, 0x97, 0x18, 0x89,
+ 0x0C, 0x4F, 0x60, 0x5A, 0xF5, 0x83, 0x79, 0x65, 0xFD, 0xE0, 0xE3, 0x7E, 0x2A, 0x91, 0x08, 0x16,
+ 0x9F, 0x4C, 0x3F, 0x85, 0x33, 0x7C, 0xB9, 0xC7, 0x17, 0xD9, 0x7B, 0xFA, 0x64, 0x1E, 0xFB, 0xE5,
+ 0x5D, 0x5A, 0xD1, 0xD9, 0xE8, 0x88, 0xCC, 0x4C, 0x68, 0xB3, 0x19, 0xB8, 0x55, 0xBC, 0x94, 0xA3,
+ 0x43, 0x21, 0x93, 0x73, 0x2D, 0xB7, 0x57, 0xF7, 0x39, 0x53, 0xE6, 0x64, 0x52, 0x35, 0xB7, 0x33,
+ 0x95, 0x2A, 0x93, 0xD3, 0x29, 0x82, 0x58, 0x2D, 0x4F, 0x6E, 0xF3, 0xA0, 0xA5, 0x56, 0x1E, 0x19,
+ 0x10, 0x61, 0xF3, 0xC8, 0xE6, 0xD6, 0xCC, 0xE7, 0xC7, 0xB8, 0x03, 0xE0, 0xB4, 0x22, 0x53, 0x81,
+ 0xBE, 0x3C, 0x4C, 0x0A, 0xF3, 0x63, 0x65, 0x67, 0x54, 0xB6, 0xBB, 0x71, 0xCB, 0x1E, 0xA9, 0x47,
+ 0x5C, 0xC8, 0x11, 0x2B, 0xD9, 0x20, 0x22, 0x53, 0x5E, 0x4C, 0x7F, 0x0E, 0xFC, 0x74, 0x38, 0x1D,
+ 0x11, 0x90, 0xF8, 0x27, 0x70, 0xFA, 0xCB, 0x12, 0x85, 0xCD, 0xB9, 0xDF, 0xC8, 0xB9, 0x36, 0x97,
+ 0x12, 0xDA, 0x69, 0xFE, 0xAB, 0x22, 0xC2, 0x0D, 0xDE, 0x48, 0xFE, 0x59, 0xE5, 0x8B, 0x23, 0xC5,
+ 0xD0, 0xCE, 0x71, 0x4D, 0x25, 0x1C, 0x38, 0x93, 0x1F, 0x49, 0xED, 0xF0, 0x92, 0xF4, 0x81, 0xA4,
+ 0xB8, 0xAF, 0x69, 0x87, 0x03, 0xB2, 0x4A, 0xCC, 0xE7, 0xB0, 0x94, 0x29, 0x39, 0xD5, 0x8B, 0xB4,
+ 0xBA, 0x98, 0x57, 0x5F, 0xA9, 0x12, 0x35, 0x75, 0x71, 0x60, 0xD9, 0x01, 0x66, 0x71, 0x6C, 0xED,
+ 0xC2, 0xA7, 0xF2, 0x16, 0x9F, 0x70, 0x6A, 0x33, 0x8F, 0x07, 0xCA, 0x20, 0xE6, 0x45, 0xE3, 0x70,
+ 0x42, 0x1A, 0xEA, 0xCC, 0x56, 0xBA, 0x88, 0xCD, 0x89, 0xE2, 0x38, 0x66, 0x87, 0xCC, 0x33, 0xA9,
+ 0x36, 0xAD, 0x76, 0x19, 0x69, 0x3A, 0xEC, 0xBC, 0x3B, 0xF3, 0xC1, 0x9C, 0x4C, 0xB3, 0x73, 0x32,
+ 0xCD, 0x92, 0xC1, 0x46, 0xB5, 0xC6, 0x8E, 0xA2, 0x81, 0x67, 0xD6, 0x85, 0x17, 0xE9, 0x0F, 0xE1,
+ 0x3B, 0xB5, 0x42, 0x1A, 0xC2, 0xFA, 0x25, 0x92, 0x6B, 0x0A, 0xEC, 0xAD, 0x93, 0x6F, 0xB8, 0x18,
+ 0x5C, 0x47, 0x53, 0xB2, 0x8D, 0x36, 0x6F, 0xEC, 0x90, 0x32, 0x4A, 0x0B, 0x84, 0x34, 0x57, 0x1D,
+ 0xFC, 0x41, 0x32, 0x73, 0xEA, 0x29, 0x6E, 0x47, 0x75, 0x94, 0x36, 0xE5, 0x29, 0xB2, 0xC2, 0x4B,
+ 0xDD, 0x61, 0x44, 0x52, 0x8C, 0xF2, 0x2A, 0x0D, 0xCE, 0xE9, 0x6F, 0x64, 0xDC, 0xC3, 0x0B, 0xB2,
+ 0x14, 0x0D, 0x84, 0xDE, 0xE5, 0xD2, 0x25, 0x76, 0x2E, 0xA4, 0x37, 0x8A, 0xBD, 0xB8, 0x29, 0x1E,
+ 0xA4, 0xD0, 0xB4, 0xAB, 0x9C, 0x89, 0x64, 0xE0, 0xC6, 0x9D, 0xA2, 0x54, 0xA9, 0xA3, 0xD3, 0x10,
+ 0x85, 0xB7, 0xAF, 0x59, 0x08, 0x5F, 0x03, 0x89, 0x39, 0xB4, 0xB5, 0x80, 0x28, 0x47, 0xDB, 0x33,
+ 0xE1, 0x00, 0xE3, 0x7B, 0x6E, 0xA8, 0xC1, 0x06, 0x3F, 0xA0, 0x16, 0xC9, 0x64, 0xE8, 0x6F, 0x60,
+ 0x78, 0xD9, 0x33, 0xA4, 0xCB, 0x47, 0x9D, 0x14, 0x18, 0x2E, 0xC8, 0x1F, 0xD5, 0xB3, 0x49, 0x0C,
+ 0x14, 0xFF, 0xE4, 0x1A, 0xF9, 0x49, 0x25, 0x04, 0xEC, 0x0A, 0xE1, 0xC6, 0x73, 0x7B, 0x9C, 0x47,
+ 0xA1, 0xD8, 0x62, 0x21, 0x2B, 0x3F, 0x28, 0x64, 0x91, 0x42, 0xF3, 0xC5, 0x42, 0x55, 0x8B, 0x0F,
+ 0xEC, 0xEC, 0x07, 0x5F, 0x34, 0x59, 0xEE, 0x63, 0x95, 0xB8, 0x26, 0xCF, 0xEE, 0x2D, 0x38, 0x5B,
+ 0x74, 0xCB, 0xCE, 0x32, 0x7B, 0x37, 0xAB, 0x74, 0xAB, 0xB6, 0x15, 0x44, 0xA8, 0x8E, 0xB0, 0x00,
+ 0x92, 0x3C, 0xF0, 0x2E, 0x80, 0xA9, 0x0E, 0xF4, 0x7A, 0x69, 0x63, 0x55, 0xEE, 0x42, 0x95, 0x3F,
+ 0xBA, 0x0C, 0xB5, 0xB1, 0xC1, 0x55, 0xE1, 0xED, 0xB2, 0x2D, 0xD7, 0x64, 0x66, 0xFF, 0xB6, 0xC3,
+ 0x36, 0x00, 0x53, 0x3C, 0xB0, 0xE8, 0xA2, 0x8E, 0xEF, 0x27, 0xA8, 0xFA, 0x5C, 0xDA, 0x91, 0xEF,
+ 0xA9, 0xE7, 0x07, 0x17, 0xF4, 0x77, 0xFF, 0x86, 0xFE, 0x6A, 0x81, 0xEF, 0x42, 0x0F, 0xF8, 0x5E,
+ 0x3D, 0xE0, 0xB3, 0x1B, 0xCB, 0x70, 0xCF, 0xA3, 0x47, 0x49, 0xFA, 0xC1, 0x37, 0xFA, 0x51, 0xE2,
+ 0xE3, 0x85, 0x82, 0xE1, 0x3D, 0xFF, 0xE2, 0xA3, 0xFC, 0x5D, 0x87, 0xA7, 0x71, 0x55, 0xB7, 0x4F,
+ 0x28, 0x91, 0x8A, 0x08, 0xAB, 0xDB, 0xC1, 0xBD, 0xBD, 0x53, 0x89, 0x24, 0x1F, 0xE2, 0x53, 0x6C,
+ 0xC5, 0xA8, 0x45, 0x50, 0x9D, 0x6F, 0x0D, 0x58, 0x1A, 0x96, 0x14, 0xA6, 0xD8, 0xF0, 0xEE, 0xB1,
+ 0x85, 0x92, 0x56, 0x52, 0xDA, 0xF5, 0x11, 0x24, 0x3C, 0x82, 0x15, 0xB7, 0x7F, 0x4E, 0x1B, 0x1D,
+ 0xE2, 0x0F, 0xC0, 0xE0, 0xCF, 0x05, 0x29, 0x5F, 0x5A, 0x6C, 0xB6, 0x64, 0xF6, 0xC7, 0x4F, 0x27,
+ 0xB3, 0x0E, 0x2D, 0xCD, 0x9B, 0xA4, 0xB2, 0xB0, 0x54, 0x8C, 0xE9, 0xE8, 0x15, 0x68, 0xC3, 0x1B,
+ 0x0A, 0x74, 0x66, 0x4B, 0x04, 0xF3, 0x53, 0xBE, 0x9C, 0x40, 0x4C, 0x4E, 0xA0, 0x32, 0xBD, 0xD4,
+ 0xB5, 0x96, 0xE1, 0xD8, 0x92, 0xBC, 0xD9, 0x00, 0x9C, 0x6B, 0x06, 0x42, 0xFA, 0x03, 0x2B, 0x16,
+ 0x13, 0x4C, 0x6E, 0x32, 0x48, 0xAA, 0x28, 0xA9, 0x36, 0xAF, 0x74, 0x25, 0x3C, 0x10, 0xA0, 0x69,
+ 0x51, 0x17, 0x15, 0xE8, 0xEB, 0x64, 0x5B, 0xF6, 0x71, 0x1D, 0x62, 0x28, 0xD4, 0xA7, 0xB6, 0x17,
+ 0x76, 0x93, 0x1A, 0x2A, 0x90, 0x30, 0xB8, 0x8A, 0x17, 0x8F, 0xF0, 0x46, 0x12, 0xBF, 0x72, 0xFD,
+ 0x68, 0x69, 0xA8, 0xA1, 0x04, 0x81, 0xDA, 0x85, 0x11, 0x38, 0x8C, 0x82, 0x50, 0x54, 0xC0, 0xEA,
+ 0x7A, 0x4D, 0x44, 0x6C, 0xA4, 0x5D, 0x99, 0x5D, 0x47, 0xFF, 0x5E, 0x41, 0x6B, 0x05, 0xBF, 0xD9,
+ 0x10, 0xBC, 0x74, 0x85, 0x29, 0x3E, 0xB8, 0x1A, 0x0A, 0xA3, 0xC7, 0xFF, 0xCD, 0x98, 0x9A, 0x08,
+ 0xF8, 0x9F, 0xE6, 0x07, 0xED, 0xB2, 0xBF, 0x1A, 0x8F, 0xA8, 0xDC, 0x3F, 0xDE, 0x72, 0x7A, 0x61,
+ 0xB7, 0xDE, 0x87, 0x74, 0x6E, 0x83, 0x20, 0x08, 0x41, 0xA8, 0x85, 0x13, 0x78, 0x7C, 0xDA, 0xF6,
+ 0x26, 0xC9, 0x05, 0xE9, 0x5B, 0xDB, 0xE0, 0x44, 0xF8, 0xA2, 0x36, 0x87, 0xD4, 0x30, 0xC3, 0xA2,
+ 0xD4, 0xD4, 0x45, 0xBE, 0x49, 0x13, 0xF9, 0x16, 0x0D, 0x16, 0x7B, 0xD5, 0x42, 0xC0, 0x1A, 0x67,
+ 0x5D, 0xAD, 0xC0, 0x58, 0x37, 0x22, 0xE4, 0x75, 0x6C, 0xAD, 0xA2, 0x16, 0xFB, 0xDF, 0x91, 0x57,
+ 0xF7, 0x3A, 0x00, 0xC7, 0x9C, 0x41, 0x9F, 0x7A, 0x07, 0x46, 0x72, 0x8D, 0xB0, 0x9E, 0x1A, 0xB7,
+ 0x03, 0x94, 0xA4, 0x87, 0x2A, 0xC6, 0x47, 0x66, 0x78, 0xFA, 0xF2, 0xEA, 0xBE, 0x84, 0xF1, 0x5B,
+ 0x47, 0x51, 0x1A, 0xFE, 0x42, 0x2F, 0x60, 0xA7, 0xBF, 0x10, 0xE3, 0x01, 0x2B, 0xBA, 0x06, 0x2B,
+ 0x7A, 0x4B, 0x4D, 0xD0, 0xA5, 0xC5, 0xF1, 0xF0, 0xDA, 0xF0, 0xA1, 0xCB, 0xCE, 0x20, 0xB6, 0xB4,
+ 0x1D, 0x3E, 0x6C, 0x1B, 0x67, 0xF6, 0xF8, 0x23, 0x2A, 0xDE, 0x80, 0x97, 0xD3, 0x3A, 0x5E, 0x4E,
+ 0x0C, 0x5E, 0x8E, 0xC3, 0x3A, 0xD8, 0xA2, 0x79, 0xD8, 0x09, 0xA4, 0xE8, 0x66, 0x8D, 0x7B, 0xC5,
+ 0x74, 0x60, 0x32, 0x54, 0xA9, 0xA6, 0x77, 0x16, 0x6C, 0xA0, 0x61, 0x1A, 0x3F, 0xD3, 0x77, 0xA4,
+ 0x80, 0xB6, 0x03, 0x23, 0x2F, 0xE2, 0x5B, 0x62, 0xAB, 0xE7, 0xD0, 0x7C, 0xCF, 0xBF, 0xB8, 0xD5,
+ 0xDB, 0xA0, 0x70, 0xCD, 0x9D, 0x44, 0xC8, 0x53, 0x7C, 0x3E, 0x1F, 0x85, 0x1F, 0x68, 0x2C, 0xA7,
+ 0x01, 0xAE, 0x57, 0x54, 0x42, 0x27, 0x9D, 0x74, 0xC1, 0x29, 0x1F, 0x5B, 0x04, 0xF0, 0x03, 0x2D,
+ 0xE7, 0xF0, 0x49, 0xFC, 0xB1, 0xB5, 0xD7, 0x25, 0x7C, 0x1D, 0xFF, 0xB5, 0x01, 0x8C, 0xD3, 0x2D,
+ 0xC0, 0x70, 0x30, 0x29, 0x5A, 0x58, 0xD0, 0x60, 0xB0, 0x4F, 0x51, 0xF9, 0xB3, 0x41, 0x49, 0xAE,
+ 0xEE, 0xE8, 0xD9, 0x80, 0x60, 0x5A, 0xF5, 0xEF, 0x87, 0x4F, 0x46, 0x5F, 0xDE, 0xE1, 0x0F, 0x28,
+ 0xC7, 0x53, 0x9A, 0xE2, 0xA7, 0x81, 0xA4, 0x1E, 0x2F, 0xE4, 0x4B, 0xF8, 0x1E, 0x32, 0x85, 0xF2,
+ 0xE8, 0xBF, 0x97, 0x1B, 0xAE, 0x5F, 0x0F, 0x3F, 0x8C, 0xBE, 0x88, 0x49, 0x2D, 0x73, 0x92, 0x9E,
+ 0x52, 0x52, 0xF8, 0x7A, 0xF8, 0x42, 0x7E, 0x7A, 0xDF, 0x13, 0xB7, 0x70, 0xD0, 0x76, 0x92, 0xB0,
+ 0x8A, 0x8C, 0xDA, 0xCB, 0x8B, 0xF8, 0x94, 0x40, 0x79, 0xAC, 0x4E, 0x71, 0x3A, 0xE3, 0x4D, 0xA5,
+ 0xA7, 0xE1, 0xFB, 0x80, 0x36, 0xB8, 0x9E, 0x04, 0x7A, 0x55, 0x3E, 0x8F, 0xDB, 0x8B, 0xFD, 0x56,
+ 0x6E, 0x00, 0xB0, 0x75, 0x9B, 0x38, 0xB7, 0x33, 0x79, 0x97, 0x8B, 0xDC, 0x5F, 0x6B, 0xE2, 0x54,
+ 0xD4, 0x46, 0xCA, 0x23, 0x04, 0x6E, 0x3C, 0xD0, 0x2D, 0xFC, 0x93, 0x3B, 0x77, 0x55, 0xDA, 0x05,
+ 0xD8, 0x88, 0xC7, 0x1D, 0x44, 0xE0, 0xEA, 0xCB, 0x78, 0x3E, 0xF8, 0x63, 0x74, 0xDC, 0x7F, 0xF9,
+ 0x25, 0x04, 0x3B, 0x95, 0x8A, 0x85, 0xF4, 0xDC, 0x7F, 0x19, 0xF4, 0x5F, 0x1E, 0x1C, 0x04, 0x7D,
+ 0xC7, 0xA8, 0x8E, 0xF4, 0x0B, 0x40, 0x74, 0x13, 0xD5, 0x21, 0x14, 0x72, 0x72, 0xC7, 0x17, 0x78,
+ 0xE3, 0x7B, 0x1F, 0xF1, 0x01, 0x5A, 0x45, 0x97, 0xFF, 0x59, 0x72, 0x67, 0x6F, 0xA4, 0x6C, 0xB1,
+ 0x5C, 0x0D, 0xFC, 0x05, 0x6B, 0xC7, 0xC6, 0x59, 0x89, 0x46, 0xF4, 0x8B, 0x75, 0xBD, 0x53, 0xEC,
+ 0x21, 0xE9, 0x54, 0xE4, 0x99, 0xA5, 0x8B, 0x8A, 0xD4, 0xC9, 0xE1, 0xF8, 0x7B, 0x60, 0xE2, 0x0F,
+ 0x53, 0x6B, 0x73, 0x52, 0x42, 0x83, 0xB2, 0x4F, 0xEC, 0x43, 0x13, 0xB3, 0xB6, 0x27, 0xF9, 0x60,
+ 0x8A, 0xED, 0x53, 0x31, 0x65, 0xBB, 0xC2, 0x8F, 0x29, 0x74, 0x80, 0x42, 0x8E, 0x85, 0x53, 0x38,
+ 0x3B, 0x5B, 0x08, 0xF5, 0x49, 0x36, 0xE0, 0xFD, 0x0B, 0xE0, 0x11, 0x37, 0x2E, 0x6D, 0xA8, 0x87,
+ 0xB7, 0x4A, 0x16, 0xB7, 0xE6, 0xD9, 0x15, 0x8A, 0x3F, 0xF0, 0x4E, 0xCC, 0x8D, 0xFC, 0xE1, 0x51,
+ 0xBE, 0xFB, 0x87, 0x99, 0x08, 0x87, 0xD2, 0x64, 0x1D, 0x94, 0xA1, 0x6C, 0x52, 0x86, 0xBC, 0xE5,
+ 0x4E, 0x6A, 0x34, 0x17, 0x16, 0x6A, 0xFD, 0xD6, 0x35, 0xAE, 0xC8, 0xD1, 0x74, 0x30, 0x23, 0x89,
+ 0xCA, 0xE4, 0x2C, 0x5F, 0x79, 0xA7, 0xD4, 0x2C, 0xC6, 0xBC, 0x52, 0x1C, 0x43, 0x38, 0xA6, 0xA7,
+ 0x84, 0xAE, 0x81, 0xA4, 0xFB, 0xAE, 0x00, 0xD1, 0x19, 0xA4, 0x0C, 0xFA, 0x1D, 0x07, 0xFD, 0x46,
+ 0x1F, 0x88, 0xB3, 0x5B, 0xE9, 0xF5, 0x54, 0x69, 0x3A, 0xC6, 0xAD, 0xA4, 0x7C, 0x80, 0x67, 0xE5,
+ 0x38, 0x99, 0x0B, 0x9D, 0x3A, 0x0E, 0xD3, 0xF2, 0x95, 0x2E, 0xF2, 0x0D, 0x5B, 0x81, 0x41, 0x5D,
+ 0x00, 0xDA, 0x97, 0x36, 0x33, 0xE0, 0x3D, 0x59, 0xFF, 0xA7, 0xFC, 0x51, 0xA6, 0x7F, 0x07, 0xD2,
+ 0xE6, 0xEC, 0xA1, 0xB9, 0x5A, 0x06, 0x21, 0x9B, 0xF4, 0xD0, 0x26, 0xAD, 0x8A, 0x4C, 0x63, 0x80,
+ 0x5A, 0x0C, 0x0F, 0xE3, 0x40, 0x33, 0xB4, 0xE3, 0x31, 0x33, 0xDF, 0x14, 0x1F, 0x58, 0x72, 0x94,
+ 0x0D, 0xD6, 0xC4, 0x82, 0xC1, 0xF0, 0x28, 0xC4, 0xA5, 0xFB, 0x24, 0x1B, 0xCC, 0xAA, 0x07, 0x78,
+ 0x1C, 0xA6, 0x63, 0x83, 0x16, 0x7A, 0xAD, 0x4C, 0x73, 0xC3, 0x1C, 0x05, 0x1C, 0x76, 0x90, 0xB1,
+ 0x3C, 0x8C, 0xBB, 0x87, 0x8F, 0xD1, 0x68, 0x47, 0xD4, 0x44, 0x3A, 0x48, 0xDB, 0x0A, 0x46, 0x8D,
+ 0x47, 0x70, 0x18, 0x54, 0x8A, 0xDE, 0xE5, 0xC4, 0xA9, 0x12, 0xFC, 0x1C, 0x8F, 0x8C, 0x86, 0x45,
+ 0x92, 0x0B, 0x19, 0x6E, 0xFD, 0x04, 0xBF, 0xB0, 0xC2, 0x86, 0x1C, 0xDA, 0xD5, 0x6A, 0x5F, 0x99,
+ 0x7C, 0x8A, 0x75, 0x84, 0xA7, 0x42, 0x19, 0x53, 0x53, 0x13, 0x7E, 0x95, 0x07, 0x2A, 0xF6, 0x9C,
+ 0x6F, 0x0A, 0x1C, 0x3A, 0x14, 0x6D, 0x04, 0xB4, 0x4D, 0x40, 0x35, 0xC6, 0xF8, 0x7B, 0xFF, 0x68,
+ 0x27, 0xF2, 0x96, 0xEF, 0xB0, 0x29, 0xC5, 0xB3, 0x70, 0x7B, 0x96, 0xFB, 0x7D, 0x18, 0x83, 0x6C,
+ 0x1E, 0x1B, 0xDE, 0x54, 0x00, 0x79, 0x4A, 0x83, 0x3C, 0xB5, 0x42, 0x1D, 0x38, 0xC4, 0xFB, 0x92,
+ 0x1E, 0x21, 0x67, 0x52, 0x6D, 0x1D, 0xEB, 0xAE, 0xA1, 0x38, 0xB6, 0x28, 0x88, 0x51, 0x24, 0xEB,
+ 0x74, 0x64, 0xBB, 0x56, 0xB9, 0xD1, 0x61, 0x9B, 0xEB, 0x61, 0xD5, 0x89, 0xDC, 0xA7, 0x06, 0x12,
+ 0x3E, 0xBC, 0x02, 0x3E, 0x49, 0x34, 0x06, 0xFB, 0xA3, 0x43, 0xEE, 0xC2, 0x99, 0x2B, 0xF7, 0x8E,
+ 0x9B, 0x72, 0xEF, 0xB4, 0x21, 0xF7, 0x4E, 0xAC, 0xC4, 0xCC, 0x32, 0x71, 0x85, 0xFD, 0x93, 0x00,
+ 0xCC, 0x95, 0x6D, 0x70, 0xB1, 0x5A, 0x41, 0xA4, 0x31, 0x9F, 0x94, 0x7F, 0xED, 0xC6, 0x66, 0x98,
+ 0x0F, 0x8E, 0x20, 0xF4, 0x5D, 0x2B, 0x5A, 0x6A, 0x84, 0xA4, 0x59, 0x5D, 0x48, 0x1A, 0x6B, 0xB1,
+ 0xA8, 0x21, 0x2E, 0x4D, 0xD7, 0x14, 0x27, 0xD4, 0x0C, 0xA4, 0xB8, 0x8C, 0x7F, 0x68, 0xA4, 0xDC,
+ 0xC6, 0xBB, 0x97, 0xE1, 0x3D, 0x85, 0x45, 0xBE, 0xE3, 0x98, 0xA1, 0x8F, 0x6C, 0x71, 0xD6, 0xDA,
+ 0x2B, 0x64, 0xC5, 0xED, 0xF1, 0x0B, 0x27, 0x1D, 0x6C, 0xE5, 0xA2, 0xC9, 0x56, 0xCE, 0xBB, 0x56,
+ 0xFD, 0xCD, 0xE0, 0xE6, 0xD7, 0x04, 0x43, 0xDE, 0x84, 0xA7, 0x96, 0xF9, 0x93, 0x80, 0x73, 0xC6,
+ 0xE2, 0x7E, 0x8E, 0x18, 0x12, 0xA5, 0xDB, 0x2B, 0xE3, 0xE9, 0xBD, 0x0A, 0x84, 0x7C, 0xA6, 0xDF,
+ 0x55, 0x20, 0xE4, 0x47, 0xFD, 0xDE, 0x32, 0xAE, 0x42, 0x62, 0x3D, 0x1B, 0xDE, 0x8F, 0xC2, 0xD7,
+ 0xFC, 0xB3, 0x7F, 0x0D, 0xD9, 0x30, 0x7C, 0xCA, 0xCF, 0x07, 0xD7, 0xC3, 0x67, 0xA3, 0xF0, 0x05,
+ 0x2C, 0x81, 0x07, 0x17, 0xC3, 0x8F, 0x30, 0x97, 0xA2, 0xFD, 0xF7, 0x8E, 0x20, 0x7C, 0x39, 0x38,
+ 0x41, 0x72, 0x44, 0xDF, 0xC2, 0xE7, 0x6E, 0xBA, 0xCC, 0x1F, 0x1D, 0xD0, 0x67, 0x08, 0x5D, 0xCD,
+ 0xD8, 0x3D, 0x5C, 0x57, 0x48, 0x97, 0xC9, 0x0E, 0x2E, 0x2B, 0x08, 0x5E, 0x91, 0x32, 0xD4, 0x1F,
+ 0x69, 0x43, 0x3D, 0xC5, 0x2C, 0x34, 0xC1, 0x3F, 0x94, 0xA1, 0xB5, 0xBF, 0x9D, 0xE3, 0x1D, 0x12,
+ 0x01, 0xBB, 0x1C, 0x07, 0x8F, 0xC8, 0xD3, 0xD3, 0x58, 0xF2, 0x50, 0x40, 0xF0, 0x57, 0xF1, 0x2B,
+ 0x1A, 0xF7, 0x5B, 0xFC, 0x60, 0xC8, 0x3F, 0x53, 0xBC, 0xE5, 0x51, 0xC8, 0xDD, 0xFF, 0x06, 0x7F,
+ 0x82, 0xF0, 0x97, 0xF8, 0x96, 0x06, 0x4B, 0x96, 0xE3, 0x17, 0x07, 0x3F, 0x1F, 0x7C, 0x75, 0x70,
+ 0x1E, 0xBD, 0x97, 0xBF, 0xE1, 0x67, 0xF8, 0x74, 0x20, 0xBF, 0xED, 0xBF, 0xD8, 0xFF, 0x79, 0xFF,
+ 0xED, 0xFE, 0x79, 0xF4, 0x5C, 0xFE, 0x86, 0xDF, 0xC5, 0x1D, 0x7B, 0x9B, 0x2A, 0xBF, 0x99, 0x18,
+ 0xE0, 0x86, 0x97, 0xEF, 0x06, 0x7A, 0x82, 0xBE, 0xB3, 0x5E, 0x2B, 0x0A, 0x16, 0xFD, 0xCE, 0x71,
+ 0x55, 0xC9, 0xE0, 0xD1, 0x37, 0x4D, 0xF0, 0x28, 0xE6, 0x36, 0xE8, 0x4E, 0xAE, 0x51, 0x75, 0x4C,
+ 0x2B, 0x6A, 0xF8, 0x56, 0xCE, 0xF5, 0x2F, 0x07, 0x6F, 0x0E, 0xBE, 0x0E, 0xBF, 0x97, 0x2F, 0x9F,
+ 0x1D, 0xBC, 0x61, 0x2D, 0x41, 0xE2, 0xD3, 0x0F, 0x04, 0x82, 0xAB, 0x01, 0x6C, 0x81, 0xAF, 0xC3,
+ 0x6F, 0x83, 0xE8, 0x75, 0xF8, 0x24, 0xBC, 0x1A, 0x7C, 0x80, 0x3E, 0x11, 0xE2, 0x54, 0xF3, 0xA7,
+ 0x41, 0x9F, 0x8A, 0xC4, 0x3F, 0x84, 0xA7, 0xFC, 0x73, 0xF0, 0x84, 0x44, 0x7E, 0x85, 0x89, 0x7F,
+ 0xE7, 0xD5, 0x64, 0x31, 0xF1, 0xAF, 0xFC, 0x6E, 0x31, 0xF1, 0x47, 0x34, 0x77, 0x37, 0x0A, 0xFF,
+ 0x16, 0xFF, 0x08, 0x5C, 0xFB, 0xFB, 0x88, 0x4E, 0xFD, 0xFA, 0x11, 0x98, 0xF6, 0x57, 0x58, 0x22,
+ 0x2A, 0xD3, 0xE8, 0xDF, 0xA8, 0xD1, 0xBF, 0x85, 0x3F, 0xCA, 0x46, 0xAB, 0x8A, 0x5A, 0xAD, 0xA0,
+ 0xBB, 0x52, 0xD1, 0x18, 0x04, 0xE3, 0x54, 0xFE, 0x1E, 0xFC, 0xB8, 0x5E, 0x77, 0x70, 0x8B, 0xD3,
+ 0xED, 0x02, 0xB8, 0x23, 0x70, 0x17, 0x46, 0xE0, 0x6E, 0x48, 0xDA, 0xBB, 0xC7, 0xCA, 0x13, 0x1E,
+ 0x12, 0xBD, 0x3C, 0x63, 0xE3, 0x6C, 0xA1, 0x36, 0x11, 0x25, 0xF1, 0x15, 0xBF, 0xCF, 0x62, 0x37,
+ 0x62, 0xE0, 0xC8, 0x46, 0x09, 0x10, 0x46, 0x8D, 0xED, 0x7A, 0x55, 0x3C, 0x9B, 0x38, 0xF5, 0x6E,
+ 0x42, 0xC7, 0x64, 0x50, 0x40, 0xAB, 0x6F, 0x0D, 0xC2, 0xF2, 0xEE, 0xCA, 0xAF, 0xC9, 0x33, 0x42,
+ 0x5F, 0x66, 0x31, 0xC5, 0xCB, 0x06, 0x64, 0x9E, 0x97, 0xFB, 0xC0, 0xAE, 0xE8, 0x67, 0xE0, 0xB6,
+ 0x45, 0x1E, 0x5A, 0x37, 0xA2, 0xC0, 0x69, 0xBA, 0xEC, 0x99, 0xE7, 0x75, 0xF4, 0x19, 0x95, 0x0C,
+ 0x42, 0xE9, 0x38, 0xF5, 0xFD, 0x71, 0xFC, 0x8E, 0x9E, 0x02, 0x62, 0xC5, 0xC2, 0xF5, 0x80, 0x8E,
+ 0x7B, 0xF7, 0x36, 0x05, 0x25, 0x11, 0xCC, 0x49, 0x37, 0x7F, 0xC0, 0x2C, 0xFB, 0x1D, 0xF7, 0x8A,
+ 0xB7, 0x31, 0x15, 0xD2, 0x38, 0x39, 0x73, 0xDA, 0x3D, 0x18, 0xB3, 0x87, 0x87, 0x0D, 0xB6, 0xE6,
+ 0x03, 0xCA, 0x23, 0xFD, 0x5E, 0xED, 0x6D, 0x2A, 0xEA, 0x5B, 0xBD, 0xB4, 0x09, 0x58, 0x7A, 0x84,
+ 0x12, 0xE1, 0xC6, 0xBF, 0x99, 0x6D, 0xD3, 0x66, 0x42, 0x61, 0x1C, 0xEF, 0xE2, 0xAA, 0xCE, 0x04,
+ 0x62, 0x53, 0x91, 0x35, 0x00, 0x55, 0x31, 0x76, 0x30, 0xB2, 0x3F, 0x54, 0x9F, 0x6F, 0x16, 0x0A,
+ 0xD6, 0xFA, 0xF9, 0xB0, 0x12, 0xE9, 0x28, 0xC7, 0x8E, 0xA8, 0x2F, 0x60, 0x22, 0x85, 0x9E, 0x4F,
+ 0xC6, 0x33, 0x5B, 0x00, 0x2F, 0x6A, 0x9E, 0x76, 0x85, 0xD2, 0x68, 0xDA, 0x2A, 0xE6, 0xAE, 0x4F,
+ 0x5B, 0xCE, 0xDB, 0x54, 0x7D, 0xC3, 0xB6, 0xB7, 0xA0, 0x16, 0x76, 0xC2, 0x7B, 0xD9, 0x2D, 0x9E,
+ 0xF1, 0xAE, 0x5A, 0x36, 0x77, 0x2A, 0xFE, 0x8D, 0xCE, 0x99, 0x0D, 0xC5, 0xCF, 0x35, 0x04, 0xEA,
+ 0xE1, 0xD7, 0xC3, 0x11, 0xE4, 0xC1, 0xD2, 0x66, 0x3B, 0x97, 0x7C, 0xBE, 0x2E, 0xD5, 0x26, 0x22,
+ 0x2A, 0xFB, 0x8D, 0xDB, 0xF7, 0x36, 0x60, 0x79, 0x2E, 0x1B, 0x2F, 0xE9, 0x4E, 0xF6, 0xCE, 0x99,
+ 0xE0, 0xC3, 0x3C, 0xC5, 0xE4, 0xB9, 0x3B, 0x21, 0x4A, 0xB8, 0x68, 0x07, 0x74, 0x26, 0xBC, 0x37,
+ 0xB1, 0xB6, 0x1A, 0x23, 0x24, 0x6B, 0x5A, 0x17, 0x39, 0x32, 0x55, 0x65, 0x82, 0x1B, 0x51, 0xCC,
+ 0x28, 0x19, 0x94, 0x5B, 0xC6, 0xE7, 0xC9, 0xDB, 0x3C, 0x62, 0xB4, 0x36, 0xC6, 0x52, 0x84, 0x9C,
+ 0xB1, 0x64, 0xE1, 0x26, 0x4A, 0x20, 0x05, 0xEA, 0x51, 0x5B, 0x19, 0x08, 0x03, 0xA3, 0x1B, 0x84,
+ 0x13, 0x2D, 0xF7, 0xB4, 0x55, 0x99, 0xDC, 0x7E, 0x04, 0x18, 0xF0, 0x52, 0x8B, 0x11, 0x8B, 0x9D,
+ 0x9E, 0xC9, 0xDD, 0x64, 0x6F, 0xA4, 0xBF, 0xA7, 0xE1, 0x77, 0xA0, 0xE4, 0x46, 0x1A, 0x3B, 0x70,
+ 0xF4, 0x60, 0xDE, 0xF0, 0xCD, 0x95, 0x06, 0xA4, 0xE3, 0xB6, 0xE5, 0xA3, 0x9D, 0x62, 0xAC, 0x52,
+ 0x29, 0x3F, 0xBD, 0x06, 0x71, 0xCB, 0x30, 0x6E, 0x73, 0x64, 0x49, 0xB7, 0x7D, 0x5E, 0xB0, 0xB1,
+ 0x45, 0x09, 0x77, 0x32, 0x24, 0x6C, 0x43, 0xCE, 0x94, 0x62, 0xA9, 0x65, 0x4E, 0xA2, 0x32, 0x4E,
+ 0x5C, 0x4F, 0x89, 0x8F, 0x7C, 0x56, 0x48, 0x29, 0x3F, 0x86, 0xC6, 0xF5, 0x84, 0x14, 0x4D, 0x3E,
+ 0x39, 0x7A, 0x46, 0xBD, 0x5A, 0x6A, 0xCA, 0xC9, 0x5D, 0xAE, 0x83, 0x4C, 0x1E, 0x7B, 0xC2, 0x8D,
+ 0xE2, 0x45, 0x8E, 0xB1, 0x94, 0xBB, 0x17, 0xF1, 0xDE, 0x87, 0x96, 0x40, 0x0D, 0xAF, 0xA9, 0xC3,
+ 0x19, 0x5B, 0x9E, 0xE8, 0x0D, 0xFD, 0xE4, 0x04, 0x7A, 0xD0, 0x03, 0x1F, 0x57, 0x8F, 0xB4, 0x3A,
+ 0x76, 0x7A, 0xE1, 0x6C, 0x2E, 0x16, 0x9C, 0x09, 0xE1, 0xD1, 0x82, 0xB4, 0x17, 0xA2, 0x3D, 0xBF,
+ 0xE6, 0x67, 0xB3, 0xB8, 0x61, 0x59, 0x49, 0xD0, 0xE9, 0x70, 0x73, 0x77, 0x64, 0x82, 0xAF, 0x32,
+ 0xDC, 0xCC, 0x54, 0x99, 0x04, 0x2C, 0xD4, 0x16, 0x02, 0xA6, 0xA4, 0xDA, 0x6D, 0x5C, 0x39, 0xA9,
+ 0x41, 0x4E, 0xA1, 0x9F, 0x02, 0xD6, 0x10, 0xBA, 0xB2, 0x52, 0x3A, 0xF2, 0xD1, 0x0F, 0x06, 0x14,
+ 0x09, 0xA3, 0xC1, 0xB9, 0xA6, 0xFB, 0x22, 0x80, 0xBC, 0x3F, 0xEF, 0x84, 0x49, 0x21, 0xE3, 0xE1,
+ 0xAC, 0xB7, 0x2E, 0xE9, 0x35, 0x97, 0x79, 0x3C, 0xDE, 0x0E, 0x54, 0xA5, 0x98, 0xD0, 0xA4, 0xB5,
+ 0x0B, 0x6F, 0xF3, 0x28, 0x29, 0x8C, 0xB5, 0x3A, 0x4A, 0x83, 0xC0, 0x2D, 0xD7, 0x44, 0xE0, 0xF0,
+ 0x55, 0xC6, 0x9C, 0x92, 0x40, 0xD2, 0x41, 0x6E, 0x4B, 0x59, 0x1D, 0x3C, 0xA3, 0xBE, 0xA1, 0x0B,
+ 0x54, 0x2F, 0x60, 0x61, 0x2E, 0x94, 0x9E, 0x1A, 0x3A, 0x95, 0x81, 0xC4, 0xCE, 0x18, 0xD7, 0x48,
+ 0x5E, 0x76, 0x23, 0x5B, 0x25, 0x1E, 0x4E, 0x55, 0xA4, 0xA4, 0x0F, 0xAD, 0x0C, 0x5D, 0x1F, 0x8B,
+ 0xEF, 0xF8, 0xD5, 0x92, 0x17, 0x89, 0xD3, 0x88, 0x52, 0x50, 0x3C, 0xC6, 0x48, 0x6F, 0xA1, 0xA8,
+ 0x45, 0x81, 0xD8, 0x1D, 0x20, 0xBC, 0x31, 0x41, 0xC8, 0x3D, 0x6B, 0xCB, 0x84, 0xA4, 0xFB, 0x3A,
+ 0x91, 0x29, 0xE8, 0x9B, 0xDC, 0xB0, 0x60, 0xB7, 0x2B, 0x26, 0xAD, 0x58, 0x68, 0x13, 0x02, 0x7E,
+ 0x49, 0x59, 0x41, 0x29, 0xB5, 0x89, 0xF1, 0x18, 0x8F, 0x86, 0x5A, 0xDB, 0x82, 0x36, 0xED, 0xD1,
+ 0xD3, 0xA2, 0x51, 0xA0, 0x21, 0x44, 0x55, 0x0A, 0x93, 0xDB, 0x61, 0x6E, 0x1A, 0xEF, 0x02, 0xB3,
+ 0x87, 0x32, 0x03, 0x4F, 0xCD, 0xBE, 0x68, 0x37, 0xA8, 0x6D, 0xEA, 0x99, 0xBC, 0x84, 0x60, 0x97,
+ 0x74, 0x4A, 0x35, 0x02, 0x4D, 0x17, 0xDA, 0x85, 0x86, 0x19, 0x9D, 0x3E, 0x8A, 0xCE, 0x66, 0xB4,
+ 0x00, 0xD5, 0xC0, 0x6A, 0x71, 0xF3, 0x05, 0x21, 0x49, 0x01, 0x55, 0x53, 0x92, 0xAD, 0x7E, 0x07,
+ 0x7E, 0xE4, 0x7C, 0x5D, 0x7D, 0x6E, 0x90, 0xC3, 0xA0, 0xC2, 0x4C, 0xA2, 0xC9, 0xC4, 0x41, 0x93,
+ 0x35, 0x88, 0x62, 0xA2, 0x0E, 0x55, 0x76, 0xE0, 0x9B, 0x61, 0xE3, 0xEA, 0x1A, 0x3C, 0x48, 0xA2,
+ 0x06, 0x09, 0x5C, 0x06, 0x6A, 0x06, 0x68, 0x44, 0xAF, 0xB1, 0x45, 0x05, 0x17, 0xCC, 0x36, 0x84,
+ 0x85, 0x69, 0xCF, 0xE2, 0x11, 0x1F, 0xB3, 0xEE, 0x27, 0xF2, 0xE8, 0x86, 0x76, 0x1D, 0x05, 0x07,
+ 0xC7, 0x6D, 0xAC, 0x49, 0x25, 0x52, 0xBF, 0xF2, 0xD9, 0x2D, 0x6D, 0x94, 0x85, 0xD6, 0x9C, 0xD5,
+ 0x8E, 0x13, 0xD2, 0x51, 0x25, 0x68, 0xA5, 0xA4, 0xF3, 0x9E, 0xF9, 0x1F, 0xC8, 0x17, 0x08, 0x82,
+ 0xBA, 0x4C, 0xD8, 0xC5, 0x64, 0x75, 0x1D, 0xFF, 0x91, 0x3A, 0x74, 0x69, 0xC6, 0xF2, 0xAF, 0x39,
+ 0xAE, 0x7E, 0x6A, 0x05, 0xAB, 0x09, 0x32, 0xCF, 0xB6, 0xE2, 0x4C, 0xC5, 0x57, 0xC9, 0x32, 0x97,
+ 0x5E, 0xEB, 0xF2, 0x3D, 0xCB, 0x9E, 0xFD, 0xD4, 0x76, 0xD6, 0x96, 0xDD, 0x1D, 0xD3, 0xC9, 0x98,
+ 0x79, 0xF6, 0x75, 0x5A, 0x94, 0x95, 0x04, 0x50, 0x2B, 0x41, 0xB1, 0x83, 0xA9, 0x94, 0x11, 0xA7,
+ 0x22, 0x1E, 0x13, 0x10, 0x27, 0xFC, 0xBB, 0x6C, 0x0A, 0x48, 0xD1, 0xF0, 0x55, 0x05, 0xA1, 0x20,
+ 0x7C, 0x09, 0x2B, 0x02, 0xD1, 0xB5, 0x70, 0xF1, 0x88, 0x7C, 0x30, 0x44, 0x86, 0x7F, 0xAB, 0x60,
+ 0x7A, 0x09, 0x5F, 0x80, 0xA7, 0x08, 0x2A, 0x76, 0x25, 0x34, 0xB2, 0x5F, 0x15, 0x42, 0xFC, 0x82,
+ 0x1A, 0xDE, 0xBD, 0x9B, 0x17, 0x79, 0x95, 0xBF, 0x7B, 0xC7, 0xE1, 0x4A, 0x6A, 0x29, 0xFE, 0x95,
+ 0xA2, 0xAC, 0x29, 0xBE, 0x31, 0x1A, 0x0B, 0x74, 0xA9, 0x42, 0x01, 0x6D, 0xAF, 0x89, 0xFE, 0x0A,
+ 0x41, 0x9A, 0x4F, 0x22, 0x97, 0xE6, 0xD6, 0xAF, 0xC8, 0x62, 0x33, 0x15, 0xB5, 0xB4, 0x68, 0x51,
+ 0x7F, 0x7F, 0x96, 0x56, 0x40, 0x47, 0xC1, 0x11, 0xA3, 0xA9, 0x39, 0xE7, 0x20, 0xD3, 0xBE, 0xFF,
+ 0xB2, 0x22, 0x0D, 0x95, 0xD5, 0x28, 0xF2, 0xAD, 0x99, 0xAD, 0x69, 0xA8, 0xD9, 0xFA, 0xC2, 0xC8,
+ 0xE7, 0xAC, 0xC2, 0xA7, 0xF9, 0x87, 0x03, 0xA8, 0x55, 0x28, 0x75, 0x23, 0x42, 0x26, 0x6A, 0x85,
+ 0xCC, 0x84, 0x3A, 0xA8, 0x28, 0xB2, 0xE1, 0x45, 0x6A, 0xA8, 0x36, 0x78, 0xC7, 0x12, 0x2E, 0xCF,
+ 0x3C, 0xA2, 0xA4, 0xF6, 0xD2, 0x39, 0x5D, 0x49, 0x20, 0xD8, 0xD9, 0xB7, 0x59, 0x15, 0xDA, 0xE3,
+ 0x82, 0xA2, 0xDA, 0xD1, 0x41, 0x05, 0xFE, 0x6A, 0x3B, 0x9D, 0x39, 0x3E, 0x28, 0x72, 0x4F, 0x12,
+ 0xB2, 0x07, 0x09, 0x45, 0xB5, 0x43, 0x85, 0x6E, 0x38, 0x85, 0xFE, 0xAA, 0x92, 0x9C, 0xC5, 0x3D,
+ 0x63, 0xC8, 0x6E, 0xF6, 0x69, 0xEE, 0xFC, 0xB9, 0x93, 0x69, 0xFC, 0xA3, 0x4A, 0xAB, 0x6C, 0xF6,
+ 0xD9, 0xAA, 0x1F, 0x2F, 0x49, 0x74, 0x80, 0x55, 0xBD, 0x0A, 0x9D, 0x73, 0x6B, 0xA2, 0x9B, 0x4A,
+ 0x1D, 0x8E, 0x02, 0xD4, 0xA9, 0xED, 0x3B, 0x8A, 0x80, 0x5A, 0xF5, 0x3D, 0x16, 0xD1, 0xAB, 0x8A,
+ 0xFD, 0x99, 0x11, 0x70, 0x8D, 0x5C, 0x1E, 0x51, 0x2A, 0xD4, 0xD5, 0xB3, 0x11, 0x10, 0xB0, 0x66,
+ 0x52, 0x8A, 0x80, 0x9A, 0x0D, 0xFB, 0x5F, 0x94, 0xDB, 0x23, 0xE9, 0xE7, 0x52, 0x4E, 0x7C, 0x25,
+ 0xAE, 0xCF, 0x3E, 0xCE, 0x7D, 0x8F, 0xEF, 0x44, 0xFD, 0x6E, 0xBE, 0xE2, 0xDF, 0x27, 0x88, 0x04,
+ 0x5D, 0x49, 0xDB, 0xBE, 0x07, 0xF1, 0x5B, 0xC4, 0x74, 0xAA, 0xBE, 0x47, 0x8E, 0x21, 0x8E, 0x7D,
+ 0xE3, 0x27, 0x85, 0x18, 0xD7, 0xEA, 0xA3, 0x4D, 0x8A, 0x4C, 0xC6, 0xF0, 0x9D, 0xFA, 0x28, 0x91,
+ 0x50, 0x16, 0x56, 0x2F, 0xBA, 0xFC, 0x65, 0x2D, 0x8B, 0xAE, 0xC2, 0x2D, 0x11, 0xDE, 0xAA, 0x2C,
+ 0x84, 0xB7, 0x26, 0x03, 0x63, 0xB3, 0xAE, 0xE4, 0x5E, 0xE6, 0xA8, 0x25, 0x46, 0x6E, 0xFE, 0xF0,
+ 0x0E, 0x6A, 0xA1, 0x82, 0x13, 0x4C, 0xF4, 0x7F, 0x18, 0x59, 0x5F, 0x69, 0x1B, 0x7F, 0x2D, 0x6E,
+ 0xEA, 0x83, 0xBB, 0x42, 0x5C, 0x07, 0x0E, 0x24, 0x44, 0xE6, 0xC9, 0x3D, 0xC8, 0x7D, 0x3A, 0xD6,
+ 0x08, 0x2D, 0x4F, 0x44, 0xE4, 0xD5, 0xCC, 0xCB, 0x89, 0xAE, 0x89, 0x97, 0x9B, 0x64, 0xCE, 0x6C,
+ 0x83, 0x9E, 0x9F, 0x90, 0xB2, 0xA8, 0x2E, 0xBB, 0x35, 0xE2, 0x42, 0xE0, 0xD9, 0x4E, 0xB4, 0xCF,
+ 0x22, 0xB7, 0xBD, 0x68, 0x7D, 0x5C, 0xC9, 0x43, 0xA6, 0x02, 0xA7, 0x5B, 0x32, 0x43, 0xA3, 0x57,
+ 0x1E, 0x07, 0x43, 0xAA, 0xDC, 0x4E, 0xB3, 0xA6, 0xA7, 0xED, 0x9B, 0x78, 0xED, 0x19, 0xE8, 0x27,
+ 0x62, 0xC3, 0x19, 0xE8, 0xC0, 0x48, 0x7D, 0x06, 0xBA, 0x39, 0x62, 0x54, 0xB9, 0x7B, 0xA8, 0xB9,
+ 0xED, 0x87, 0x9F, 0x0B, 0x5D, 0x02, 0xE3, 0x58, 0xD8, 0xAF, 0xCF, 0xF1, 0xA6, 0x94, 0x1F, 0xE7,
+ 0x76, 0xCE, 0x17, 0xC9, 0xED, 0x65, 0x52, 0xA8, 0x5C, 0x92, 0x30, 0xCA, 0x24, 0xFF, 0xD7, 0xDE,
+ 0xFA, 0x7A, 0x27, 0x3A, 0x3E, 0xD3, 0xF9, 0x7C, 0x36, 0xCB, 0x99, 0xD8, 0x7C, 0xD2, 0xF6, 0x04,
+ 0x9B, 0x47, 0xB1, 0x47, 0xD4, 0xBD, 0xBC, 0x7D, 0xDA, 0x38, 0x73, 0x75, 0xB5, 0x7A, 0xCC, 0x0D,
+ 0x23, 0x5B, 0x0F, 0xDF, 0x76, 0x4F, 0xDF, 0x6E, 0x57, 0x6F, 0xC1, 0xF7, 0x88, 0xA3, 0x6B, 0xF5,
+ 0x99, 0xB3, 0x27, 0xAC, 0x2D, 0x49, 0x04, 0xFF, 0x1A, 0xFC, 0x5F, 0xC1, 0xBA, 0x51, 0x39, 0x64,
+ 0xCF, 0xC6, 0x65, 0x2A, 0xB5, 0xEF, 0x92, 0xBD, 0x3F, 0x78, 0xCC, 0xB4, 0x01, 0x52, 0x28, 0xDA,
+ 0x87, 0xE1, 0xF2, 0xA0, 0xEA, 0xF3, 0x1B, 0x7C, 0xD7, 0x6B, 0xDD, 0x64, 0x6E, 0x07, 0x19, 0x1A,
+ 0x8E, 0x21, 0x8F, 0x9C, 0x90, 0xB7, 0x61, 0xD4, 0x8E, 0x5B, 0xBC, 0x12, 0x9D, 0x87, 0x2D, 0x7A,
+ 0x5F, 0xC1, 0xBA, 0x4F, 0x62, 0xEF, 0xFC, 0xF7, 0xE5, 0x8E, 0xEE, 0x54, 0xB9, 0xA3, 0xD4, 0xCE,
+ 0x1D, 0xC9, 0x2C, 0x77, 0xB0, 0xBB, 0xA4, 0x9A, 0x97, 0xD1, 0xE1, 0xA1, 0xDA, 0x42, 0xFE, 0xBE,
+ 0x84, 0xFC, 0x78, 0x1D, 0xA0, 0x1D, 0x79, 0xB2, 0x7D, 0x6D, 0x90, 0x7D, 0x4F, 0x1E, 0x6C, 0xD1,
+ 0xBA, 0x75, 0xD0, 0xAC, 0xD5, 0x01, 0x4A, 0x44, 0x85, 0xDF, 0xFD, 0x31, 0xC0, 0xD7, 0x64, 0xD3,
+ 0xB7, 0xA8, 0x75, 0x98, 0x65, 0x67, 0x46, 0x19, 0xD1, 0xD6, 0xFD, 0xA9, 0xE3, 0x52, 0x58, 0x39,
+ 0x4A, 0xB5, 0xF8, 0xA0, 0x0E, 0xBB, 0xE7, 0x3C, 0x98, 0xE3, 0xC7, 0xDD, 0x33, 0xD4, 0xD0, 0xBC,
+ 0xD4, 0xA8, 0xD0, 0x10, 0x9F, 0x2B, 0x66, 0x34, 0xB3, 0xA0, 0x5F, 0x5B, 0xEB, 0x0B, 0xD2, 0x37,
+ 0xEC, 0x3C, 0xA9, 0xDB, 0x2C, 0x1E, 0x3B, 0x97, 0x6A, 0x9B, 0x74, 0xB0, 0x7E, 0xF4, 0x9D, 0xD1,
+ 0xE8, 0x8E, 0xBD, 0x49, 0xDF, 0xEB, 0x65, 0x8C, 0x3B, 0x07, 0xF8, 0xA1, 0x73, 0x5A, 0xAC, 0xE6,
+ 0x8D, 0x53, 0x41, 0xEB, 0x17, 0xA5, 0x8F, 0x71, 0xBC, 0xD0, 0x04, 0xE0, 0xA9, 0x1D, 0x0C, 0xC9,
+ 0xC7, 0x61, 0xEA, 0x5B, 0x46, 0xE5, 0xED, 0xE6, 0x8B, 0xD6, 0xE5, 0x85, 0x57, 0xA8, 0xA7, 0xF4,
+ 0xEB, 0xA9, 0x0F, 0x9F, 0xF1, 0x1D, 0x76, 0x2E, 0x50, 0x45, 0x2F, 0x3A, 0x4F, 0xD1, 0xDE, 0x94,
+ 0xED, 0x31, 0x07, 0x67, 0xBB, 0x8B, 0x6D, 0xCD, 0x67, 0x5F, 0x6F, 0xA2, 0x1B, 0xBB, 0xBF, 0x8A,
+ 0x70, 0x6C, 0xA7, 0x07, 0xF2, 0x95, 0x24, 0x16, 0x2C, 0x16, 0x8A, 0x0E, 0x60, 0x39, 0x9B, 0x98,
+ 0x52, 0x5E, 0x52, 0x47, 0x5C, 0x84, 0xD9, 0xDB, 0x73, 0xDF, 0x7A, 0x4A, 0x8F, 0x00, 0x78, 0x99,
+ 0x95, 0xF4, 0x4C, 0xA1, 0xB5, 0xD6, 0x9C, 0x97, 0x8F, 0x67, 0x03, 0x9D, 0x2D, 0xA8, 0x7A, 0x36,
+ 0xDC, 0x92, 0xDB, 0xB8, 0x0F, 0xD2, 0x9E, 0x8A, 0x5A, 0x87, 0x2A, 0x9F, 0xFB, 0xDE, 0xBE, 0xB2,
+ 0xB1, 0x7E, 0x6F, 0x03, 0x9A, 0x68, 0xC0, 0x60, 0xF9, 0xE0, 0x81, 0xE1, 0x4E, 0x13, 0x6D, 0x2A,
+ 0x09, 0x26, 0xF2, 0x09, 0xEB, 0xE2, 0x57, 0x20, 0xFF, 0xD5, 0x55, 0x07, 0xF6, 0x3F, 0x6A, 0xB6,
+ 0x2C, 0x16, 0x35, 0x4F, 0x0F, 0x7E, 0x18, 0xBB, 0x6B, 0xD9, 0x3E, 0x69, 0x5D, 0x79, 0x08, 0xA5,
+ 0xE4, 0x23, 0xAF, 0xBF, 0x53, 0x95, 0x3C, 0x48, 0x62, 0x1E, 0x75, 0x3A, 0xB6, 0x3B, 0x09, 0xCD,
+ 0x8B, 0xA4, 0xD3, 0x2B, 0x75, 0xBA, 0x35, 0x97, 0x76, 0x04, 0xA0, 0x9E, 0x42, 0x83, 0xC7, 0xDF,
+ 0x2D, 0x5D, 0xF1, 0x2D, 0xD2, 0x4E, 0x63, 0x61, 0xBB, 0x4E, 0x2B, 0x8A, 0x04, 0x61, 0x9B, 0x13,
+ 0xB8, 0xE4, 0x7F, 0x17, 0x03, 0x72, 0x68, 0xBE, 0xEB, 0x3C, 0xD8, 0xED, 0x28, 0xD0, 0xED, 0x46,
+ 0xE8, 0xE4, 0x98, 0xB6, 0x8B, 0x8D, 0x5B, 0x07, 0x7E, 0x1F, 0xED, 0x48, 0x0D, 0x7C, 0xC7, 0x51,
+ 0xD6, 0x76, 0xA0, 0xCA, 0xDE, 0xA6, 0x98, 0x9E, 0x1D, 0x6A, 0x74, 0x47, 0x77, 0x7A, 0x07, 0xFB,
+ 0x6A, 0xA6, 0xB8, 0x81, 0x6C, 0x27, 0x51, 0xAC, 0x16, 0x19, 0xBC, 0xCE, 0x3E, 0x78, 0xEA, 0x2C,
+ 0xE4, 0x1E, 0xAE, 0x92, 0x31, 0xE1, 0x76, 0x4D, 0x69, 0xD0, 0x9A, 0x06, 0xF8, 0x96, 0xF5, 0xE6,
+ 0x5C, 0x9A, 0x29, 0x3C, 0xA0, 0xD9, 0xE7, 0x53, 0x04, 0xB9, 0x0A, 0xA3, 0x3F, 0x3A, 0xD7, 0x73,
+ 0xBB, 0x45, 0xDD, 0x63, 0xD6, 0x48, 0xAC, 0xE9, 0x96, 0xD3, 0xA8, 0x72, 0xD2, 0x40, 0xEC, 0xDD,
+ 0x7B, 0x0F, 0x64, 0xE6, 0xD5, 0x6A, 0xB3, 0xDF, 0x0B, 0x73, 0x4C, 0x3E, 0xE9, 0xCE, 0xC4, 0x5B,
+ 0x5B, 0xE7, 0xD7, 0x59, 0xDC, 0xE5, 0xC3, 0xED, 0xF4, 0xF9, 0x24, 0x6F, 0x92, 0xD9, 0x02, 0xEB,
+ 0xE0, 0x80, 0xCE, 0x61, 0xD6, 0xBB, 0x47, 0xBC, 0x80, 0x50, 0xFA, 0xC6, 0x37, 0xE0, 0xDA, 0xDC,
+ 0x91, 0xC5, 0xDC, 0x83, 0xB4, 0x31, 0xB8, 0x16, 0x11, 0x99, 0x30, 0x07, 0x97, 0x22, 0x7A, 0x27,
+ 0xD6, 0x35, 0xE2, 0x69, 0x21, 0x2B, 0x03, 0x71, 0x6A, 0xE0, 0x69, 0x73, 0x5A, 0x4F, 0xAE, 0x0F,
+ 0x56, 0x2D, 0x0D, 0x50, 0xB5, 0x82, 0x53, 0x35, 0xCE, 0x57, 0x97, 0x15, 0xB7, 0x6F, 0x69, 0x19,
+ 0x98, 0x7D, 0x70, 0xA1, 0xC7, 0xC6, 0x64, 0x22, 0x47, 0xCD, 0x3B, 0x33, 0x2B, 0xBA, 0x12, 0xB3,
+ 0xF3, 0x50, 0x98, 0x0A, 0xF2, 0xD3, 0x97, 0x14, 0xC3, 0x51, 0x5F, 0x62, 0x68, 0xBE, 0x29, 0xEA,
+ 0xD8, 0x59, 0x77, 0x5D, 0x50, 0x5C, 0xAC, 0x81, 0x1D, 0xAE, 0x63, 0x70, 0x43, 0xCC, 0x8C, 0x31,
+ 0xF5, 0xD9, 0xD0, 0x91, 0x9A, 0xFC, 0xA5, 0x53, 0x61, 0xDA, 0xAB, 0x47, 0xA2, 0xD9, 0x82, 0x32,
+ 0x41, 0x16, 0x73, 0xC1, 0xB8, 0x5E, 0x8F, 0xB4, 0x2B, 0xD7, 0x39, 0x06, 0xAF, 0x56, 0xBB, 0x52,
+ 0xF1, 0xF8, 0xEA, 0x60, 0xD3, 0xD7, 0x78, 0xB8, 0xF5, 0xF4, 0xDB, 0xE3, 0x35, 0xE2, 0x0B, 0x88,
+ 0x76, 0x31, 0x91, 0xEA, 0x80, 0x64, 0xAD, 0x05, 0x57, 0x67, 0x1C, 0x6C, 0xFC, 0x42, 0xCE, 0xAB,
+ 0x8D, 0x1F, 0xD7, 0x6B, 0x79, 0xCB, 0x0D, 0x2F, 0x5D, 0xBA, 0x73, 0xD3, 0x5F, 0xC2, 0x65, 0x40,
+ 0x7E, 0x04, 0x29, 0x37, 0xC0, 0x53, 0xB2, 0x6C, 0x5C, 0x45, 0xDB, 0x58, 0xBA, 0x3B, 0xF6, 0x35,
+ 0x45, 0xF9, 0x28, 0xCB, 0x2B, 0x9F, 0xC6, 0x2E, 0x45, 0x4F, 0x7E, 0x8D, 0xCC, 0xAB, 0xE7, 0xB0,
+ 0x21, 0x7D, 0x2F, 0x10, 0xC5, 0x3B, 0x66, 0x66, 0xC3, 0xC3, 0xBD, 0x9F, 0xC1, 0x3F, 0xE2, 0x19,
+ 0x63, 0x06, 0xEF, 0xE3, 0x0A, 0x77, 0x33, 0x7B, 0x55, 0x20, 0xCC, 0xE8, 0x52, 0xC2, 0xB3, 0x2A,
+ 0xA3, 0xEE, 0xC1, 0x86, 0x9B, 0x01, 0x4F, 0xC4, 0xC6, 0xAB, 0x01, 0x89, 0x2A, 0xFC, 0xFF, 0x9D,
+ 0x0A, 0x8F, 0x0E, 0xEB, 0x21, 0x6D, 0xBB, 0x70, 0x8F, 0xAF, 0x42, 0xA7, 0x89, 0x2B, 0xD5, 0x0D,
+ 0xC6, 0xA4, 0x63, 0xFC, 0x81, 0xCF, 0x44, 0x07, 0x47, 0xAB, 0xF2, 0x0C, 0x87, 0x0C, 0x62, 0x36,
+ 0x17, 0x73, 0x75, 0xD6, 0xBB, 0xBC, 0xD9, 0xFD, 0x22, 0xB9, 0xF4, 0x76, 0xE9, 0x1D, 0x9F, 0x9A,
+ 0x97, 0xEC, 0x6D, 0xBE, 0x3F, 0x4C, 0xF7, 0xA7, 0x79, 0x1D, 0x1C, 0x69, 0x26, 0xF6, 0x3A, 0xB8,
+ 0xCC, 0xBD, 0x24, 0x24, 0x53, 0x80, 0x37, 0x00, 0xE7, 0x43, 0x70, 0x59, 0x17, 0x45, 0xCF, 0x58,
+ 0x47, 0xC9, 0x0C, 0x52, 0x1A, 0xCB, 0x46, 0x40, 0xF4, 0x30, 0xCD, 0x16, 0x42, 0x66, 0x7C, 0x8C,
+ 0x3E, 0x6E, 0x8A, 0xE8, 0xEB, 0x8A, 0x9A, 0xD2, 0xAF, 0xAD, 0x65, 0xDD, 0xBA, 0xCA, 0x89, 0x04,
+ 0x40, 0x34, 0x3B, 0x79, 0x89, 0x03, 0x08, 0x7D, 0x3E, 0x14, 0xD6, 0xA2, 0x5A, 0xA6, 0x30, 0x95,
+ 0x0A, 0xD5, 0x93, 0xAD, 0x78, 0xEE, 0xA1, 0x23, 0x14, 0xC1, 0xDC, 0x3D, 0x1A, 0x8A, 0xF0, 0x40,
+ 0x1E, 0xB0, 0xD5, 0xAD, 0x99, 0x52, 0x77, 0xD4, 0xBA, 0x59, 0x3B, 0x4E, 0x7D, 0x4E, 0x3F, 0x45,
+ 0x84, 0x6C, 0x98, 0xD1, 0x58, 0xCD, 0xE8, 0x6A, 0xA5, 0x6E, 0x3B, 0x97, 0xCB, 0x7A, 0x25, 0x29,
+ 0x99, 0xB9, 0xFB, 0x7C, 0x45, 0xE7, 0x26, 0xE9, 0xFD, 0xF9, 0xA2, 0x75, 0x01, 0xBA, 0xED, 0x86,
+ 0x94, 0xA9, 0xDD, 0x66, 0xE0, 0x08, 0xE6, 0x34, 0x96, 0xC8, 0x63, 0x68, 0x2D, 0x59, 0x43, 0x83,
+ 0x28, 0x2D, 0xC2, 0x77, 0xDA, 0x2C, 0x2C, 0xD6, 0x97, 0x78, 0xAE, 0x9F, 0x98, 0xDA, 0xB8, 0x43,
+ 0xF2, 0x5B, 0x79, 0x7F, 0xBB, 0xBE, 0x45, 0x52, 0x6C, 0xB9, 0xC6, 0xBD, 0x3D, 0x8A, 0x81, 0xF7,
+ 0x7A, 0x8E, 0x05, 0xE3, 0x80, 0xC4, 0x93, 0x16, 0x51, 0x83, 0xF6, 0x18, 0x8C, 0xA5, 0x30, 0x4E,
+ 0xAA, 0xB6, 0xA5, 0x22, 0x49, 0x17, 0x55, 0xB5, 0x5B, 0xBE, 0xDA, 0x92, 0x5C, 0x82, 0x68, 0x77,
+ 0x2E, 0x64, 0x57, 0xBA, 0xD7, 0x14, 0x2D, 0xDC, 0x2D, 0xD8, 0xAB, 0x2F, 0xBC, 0x90, 0x9D, 0xB4,
+ 0xDD, 0x76, 0x6E, 0x31, 0x6A, 0x5D, 0xB1, 0x20, 0x8F, 0xCC, 0xCE, 0xE7, 0x24, 0x74, 0x24, 0xD7,
+ 0x6C, 0x78, 0x47, 0x9A, 0xD4, 0x1D, 0x1B, 0xED, 0x67, 0x7C, 0x8F, 0x2D, 0x7D, 0x00, 0xDF, 0xC6,
+ 0xC6, 0x69, 0x51, 0x3E, 0xB0, 0xBA, 0x99, 0x93, 0x44, 0x52, 0x77, 0x92, 0x44, 0x67, 0x7B, 0xFE,
+ 0xE1, 0x91, 0xF6, 0x7A, 0x37, 0x47, 0x30, 0xF0, 0xD1, 0x2B, 0xA3, 0x8D, 0x33, 0x3D, 0x05, 0x45,
+ 0x70, 0xA8, 0x16, 0x60, 0x67, 0x40, 0x6E, 0x67, 0xCB, 0x4E, 0x8D, 0x53, 0x13, 0x56, 0x11, 0x2A,
+ 0x93, 0x18, 0xA8, 0x2B, 0x6B, 0x90, 0x17, 0x7C, 0x0F, 0x7A, 0x4D, 0x76, 0x26, 0xD0, 0x06, 0x65,
+ 0xA6, 0x83, 0x8F, 0x15, 0x5E, 0xD8, 0xC9, 0x6D, 0xF4, 0xA6, 0x7D, 0xDD, 0x06, 0x72, 0x4D, 0x1A,
+ 0x0A, 0xBB, 0xBD, 0x70, 0x63, 0x3B, 0x5C, 0x42, 0x54, 0xDE, 0x89, 0xCD, 0x41, 0xF8, 0x49, 0x8D,
+ 0x34, 0xF1, 0xED, 0xF1, 0xF5, 0x32, 0xAC, 0x36, 0xD4, 0x5A, 0x1B, 0x79, 0xB3, 0xA4, 0x24, 0x31,
+ 0xBF, 0xAA, 0x24, 0xB7, 0xF9, 0xAB, 0xC0, 0x55, 0x3F, 0x01, 0xA9, 0x8D, 0xF0, 0x1B, 0x79, 0xA2,
+ 0xBA, 0x7B, 0xE3, 0x44, 0x04, 0xCA, 0x00, 0x7E, 0x21, 0x5C, 0xB3, 0x77, 0xF3, 0x52, 0xC5, 0xB8,
+ 0xA6, 0x68, 0xAF, 0xAF, 0xD5, 0xE9, 0xDF, 0x8E, 0xE0, 0xB9, 0x41, 0x61, 0xAF, 0x1D, 0xEB, 0xD1,
+ 0x3A, 0x1B, 0x5C, 0x9D, 0xAB, 0x98, 0x66, 0x30, 0x53, 0x70, 0x8E, 0x83, 0x2A, 0x60, 0xB3, 0x4E,
+ 0x53, 0x8F, 0x31, 0x0D, 0x6A, 0xB3, 0x9C, 0x12, 0x8E, 0x48, 0x6A, 0xFD, 0x1A, 0x52, 0xAB, 0x73,
+ 0x13, 0xB7, 0x6E, 0x78, 0x93, 0x8E, 0x1A, 0x7A, 0x2A, 0xA4, 0xF5, 0x95, 0xDC, 0x59, 0x06, 0x21,
+ 0x5B, 0xEC, 0x57, 0x5B, 0xCB, 0x7B, 0x3D, 0x0E, 0x31, 0x38, 0xA0, 0xED, 0xC8, 0x3B, 0xEA, 0x59,
+ 0x7A, 0x6C, 0xF0, 0x9A, 0x96, 0x07, 0x9C, 0x82, 0xC7, 0xB2, 0xC2, 0x3C, 0xDE, 0x53, 0x36, 0xEF,
+ 0x57, 0xB5, 0x52, 0x2B, 0x2F, 0xEF, 0x12, 0x70, 0x8A, 0x13, 0x6C, 0xDA, 0xE3, 0x5E, 0xAA, 0x1A,
+ 0x13, 0xBA, 0x7C, 0x1C, 0x7A, 0x53, 0x32, 0x33, 0x55, 0x36, 0xC7, 0x2D, 0x8F, 0x3E, 0xDA, 0x74,
+ 0xAF, 0x5D, 0x4F, 0x7F, 0x8E, 0x95, 0xC9, 0xC0, 0x5B, 0x6F, 0x80, 0x66, 0xD8, 0xBA, 0x02, 0xBF,
+ 0x3D, 0x3F, 0x2C, 0xA9, 0x3F, 0x4F, 0xB2, 0x74, 0xBE, 0x98, 0x31, 0xA5, 0xB5, 0x17, 0x72, 0xB3,
+ 0x95, 0x89, 0x64, 0x83, 0xA6, 0x16, 0xB6, 0xB7, 0xD7, 0xC2, 0x87, 0x2F, 0x6B, 0xF8, 0xB3, 0x9F,
+ 0x69, 0x2A, 0xBD, 0x79, 0xD0, 0xD4, 0x3D, 0x7B, 0x03, 0xA3, 0xAC, 0xB0, 0xEB, 0x18, 0x75, 0xC8,
+ 0x9A, 0x7D, 0x35, 0x72, 0x3C, 0xC6, 0xA9, 0xEF, 0x2A, 0x66, 0x00, 0x6D, 0x42, 0x51, 0x56, 0x81,
+ 0xBC, 0x64, 0x2F, 0x58, 0x73, 0x54, 0x83, 0x01, 0x76, 0x21, 0x1E, 0x83, 0x66, 0x0E, 0xB8, 0x3F,
+ 0xA5, 0x58, 0x0D, 0x6F, 0x6C, 0xD1, 0xFF, 0x12, 0x62, 0x3E, 0xD8, 0xC0, 0x46, 0x9C, 0x0C, 0xD6,
+ 0x1B, 0xE1, 0x6F, 0x95, 0x08, 0x0B, 0xE1, 0x7E, 0xDA, 0x65, 0x2E, 0x57, 0xC8, 0xB4, 0xDE, 0xD0,
+ 0xBE, 0xAC, 0xEC, 0x71, 0x18, 0xD5, 0xBA, 0x3E, 0xA9, 0x81, 0x12, 0x4E, 0x8C, 0x94, 0xEA, 0x95,
+ 0x32, 0xAE, 0x69, 0x1B, 0x04, 0x1D, 0x22, 0xE4, 0x6B, 0x8B, 0x5B, 0xBB, 0x82, 0xD0, 0xC5, 0x16,
+ 0xB2, 0x82, 0x6E, 0xEE, 0x91, 0xDA, 0x61, 0x4E, 0x21, 0xC3, 0x29, 0xD5, 0x2A, 0xF5, 0x83, 0xA6,
+ 0x26, 0x6F, 0xAD, 0x94, 0xA8, 0x2E, 0x2D, 0xB5, 0x02, 0x8E, 0xA9, 0xB1, 0xC6, 0x8A, 0xC6, 0xE2,
+ 0xFA, 0x12, 0x51, 0x27, 0x72, 0x9C, 0xE7, 0x70, 0x5E, 0xA5, 0xE5, 0x9B, 0xB4, 0x4C, 0x2F, 0xE5,
+ 0x2D, 0xD0, 0x69, 0x79, 0x92, 0xA5, 0x37, 0x24, 0xBE, 0xCB, 0x13, 0x14, 0x73, 0x0D, 0xCD, 0x48,
+ 0x1D, 0x36, 0xC6, 0x4C, 0x46, 0xF7, 0x50, 0x1E, 0x85, 0x1F, 0x9E, 0xDA, 0x5A, 0x6A, 0x57, 0x46,
+ 0xDB, 0xCA, 0x6C, 0x62, 0xBD, 0x4E, 0x5F, 0x0D, 0x42, 0x3B, 0x52, 0x9B, 0xF5, 0x7B, 0x86, 0x33,
+ 0xAD, 0xA8, 0x29, 0xEB, 0x68, 0xFD, 0x50, 0xE3, 0x33, 0xC4, 0xB6, 0xB6, 0x7A, 0x50, 0x0D, 0xC9,
+ 0x44, 0x97, 0xA0, 0x80, 0x93, 0x7D, 0x94, 0x82, 0x8B, 0xEA, 0xCC, 0x89, 0x5A, 0x60, 0x2F, 0x62,
+ 0xB3, 0xBA, 0x9E, 0x19, 0xDC, 0xC0, 0x57, 0x58, 0xC4, 0x95, 0xF8, 0x8D, 0x0B, 0x8A, 0xED, 0x78,
+ 0x11, 0x2B, 0xEE, 0x9B, 0x3E, 0x18, 0xAB, 0x1C, 0xB2, 0xB7, 0xD2, 0xEA, 0xD7, 0xC5, 0x35, 0xCD,
+ 0xBC, 0x37, 0x84, 0x12, 0x82, 0x6B, 0x25, 0x20, 0xC8, 0x0B, 0x13, 0x2F, 0xD5, 0xB9, 0x25, 0xF8,
+ 0x95, 0x6C, 0xEE, 0xE1, 0xFE, 0xBA, 0x2D, 0x3E, 0x68, 0x5B, 0xEE, 0x6C, 0x94, 0xBE, 0x59, 0x7F,
+ 0x43, 0x58, 0xEB, 0x83, 0x53, 0xBD, 0x8A, 0xD1, 0xAB, 0x01, 0xB6, 0x83, 0xBB, 0xCB, 0xD0, 0x1B,
+ 0x5D, 0xC6, 0x9B, 0xA4, 0xB7, 0x90, 0xC5, 0x95, 0x9A, 0x49, 0x7A, 0x44, 0xEC, 0xC1, 0x6A, 0x93,
+ 0xCC, 0x0E, 0x08, 0x09, 0x48, 0xF2, 0xF0, 0x36, 0x03, 0xBA, 0x75, 0xE1, 0xDE, 0x55, 0x32, 0x69,
+ 0xDD, 0x7A, 0x8B, 0xAB, 0x18, 0x3B, 0x2E, 0x4A, 0xDC, 0x7A, 0x3F, 0xE3, 0xB9, 0xA8, 0x5F, 0x96,
+ 0x67, 0xCD, 0x71, 0x14, 0x13, 0x1D, 0xF4, 0x1C, 0x3C, 0x26, 0xFF, 0xA4, 0xFB, 0xCE, 0x57, 0x37,
+ 0x3A, 0x5D, 0xC7, 0x02, 0xA1, 0xED, 0x7D, 0x06, 0x6B, 0x96, 0x4D, 0x5C, 0x64, 0xFF, 0xB7, 0x1D,
+ 0x9F, 0x5B, 0x57, 0x4F, 0x16, 0x3A, 0x25, 0x9F, 0x44, 0xC7, 0x64, 0x2A, 0x21, 0xB0, 0xFD, 0x41,
+ 0x39, 0x2A, 0xB4, 0x70, 0x6B, 0x7B, 0x23, 0x51, 0xA8, 0xDE, 0x5E, 0x6D, 0xD1, 0x05, 0xEB, 0xAE,
+ 0xC5, 0x72, 0xD4, 0x76, 0x51, 0xD9, 0xCF, 0xD0, 0xE9, 0xA4, 0x7F, 0xA4, 0x06, 0xDD, 0x0D, 0x5D,
+ 0x68, 0xCC, 0x8D, 0xC1, 0xC2, 0xCE, 0x25, 0x1A, 0xAC, 0x5B, 0xF8, 0x48, 0x33, 0x05, 0xE5, 0xA8,
+ 0xB5, 0x96, 0x36, 0xAF, 0xC6, 0x40, 0x93, 0xBB, 0x67, 0x20, 0x54, 0xBA, 0x1B, 0x44, 0xED, 0x20,
+ 0x6B, 0x5F, 0xE6, 0x49, 0x31, 0xA1, 0x67, 0x56, 0x95, 0x38, 0x08, 0xE5, 0xA3, 0x9B, 0xAB, 0x1D,
+ 0xEB, 0x61, 0x4A, 0x59, 0xA2, 0x26, 0x0B, 0x9B, 0x77, 0x43, 0xA5, 0x9E, 0x88, 0x4F, 0xB8, 0x12,
+ 0xFF, 0xA1, 0x9B, 0xEF, 0x27, 0xE0, 0x8F, 0xF9, 0xB5, 0x73, 0x51, 0xB6, 0xD7, 0x93, 0x8B, 0x44,
+ 0x7E, 0xF0, 0xEA, 0x50, 0xD5, 0xA5, 0xF4, 0x40, 0x54, 0xBD, 0xA9, 0xE4, 0xB3, 0x38, 0x99, 0xF4,
+ 0x2B, 0x7C, 0x78, 0x82, 0x0F, 0x0E, 0xDC, 0x5F, 0x93, 0x83, 0xD4, 0xD2, 0x45, 0x44, 0x9A, 0x22,
+ 0x62, 0xEB, 0x2B, 0x55, 0xC1, 0x29, 0xE1, 0x08, 0x3E, 0x3E, 0x74, 0x81, 0xBF, 0x0C, 0xE8, 0xFF,
+ 0x2A, 0x91, 0xC1, 0x94, 0x17, 0x62, 0x6B, 0x68, 0xC9, 0xB3, 0x2D, 0x71, 0x23, 0x3C, 0x38, 0x13,
+ 0x34, 0x62, 0x0D, 0x18, 0xB5, 0xDE, 0x76, 0x87, 0x83, 0x20, 0xB7, 0x21, 0xE4, 0xB5, 0xEC, 0xAB,
+ 0xD5, 0x86, 0xFB, 0x8D, 0x1F, 0x0C, 0xD0, 0x90, 0xBD, 0x09, 0x9B, 0xAE, 0xDD, 0x75, 0xA7, 0x1F,
+ 0xB2, 0x01, 0x50, 0x7D, 0x7F, 0xB6, 0xC5, 0x48, 0x9F, 0xAC, 0x49, 0x1B, 0x00, 0x79, 0x14, 0x34,
+ 0x21, 0xA9, 0xC6, 0x17, 0xD6, 0xB4, 0xA7, 0x26, 0xD9, 0x93, 0xB8, 0x40, 0xAE, 0x17, 0xCF, 0x06,
+ 0xFA, 0xBC, 0x5F, 0x94, 0xD5, 0x13, 0x46, 0x0F, 0x57, 0xB5, 0x91, 0xA6, 0x02, 0xB6, 0x25, 0xB9,
+ 0xC9, 0xAF, 0xE8, 0xC4, 0x5A, 0x93, 0xBC, 0xD1, 0xD7, 0x4B, 0x5C, 0x00, 0xB1, 0xB0, 0xA5, 0x81,
+ 0x8A, 0xAB, 0x5D, 0xAA, 0x6F, 0x8A, 0x7E, 0x93, 0x6E, 0x59, 0x29, 0x03, 0x88, 0x62, 0x54, 0xA6,
+ 0x62, 0x8B, 0xD2, 0x0E, 0x9D, 0x68, 0xD5, 0xAD, 0xE8, 0x15, 0x95, 0x11, 0x9D, 0xD4, 0x65, 0x31,
+ 0x6F, 0x95, 0x51, 0x0E, 0x68, 0x65, 0x3C, 0x8A, 0x5B, 0xEA, 0x81, 0xBF, 0x11, 0xBD, 0x99, 0xCC,
+ 0x59, 0xFA, 0x47, 0x73, 0xAF, 0xBF, 0x33, 0x17, 0xB4, 0xC9, 0xD6, 0xAE, 0x16, 0x18, 0x1E, 0xCC,
+ 0xDA, 0x08, 0xDD, 0x5D, 0x73, 0x02, 0x19, 0xF7, 0xE4, 0xD5, 0xD9, 0x89, 0x37, 0xB2, 0x76, 0x4B,
+ 0x74, 0xA7, 0x79, 0x79, 0x67, 0x87, 0xA2, 0xBD, 0xFB, 0x18, 0x6C, 0xFD, 0x94, 0x2B, 0xB6, 0x19,
+ 0x20, 0x5B, 0xEE, 0xD7, 0x6E, 0x2E, 0x7D, 0xE3, 0x7A, 0x6B, 0x63, 0x6C, 0x5F, 0x3C, 0x06, 0x67,
+ 0x1F, 0x8F, 0x5D, 0x57, 0x57, 0x8E, 0xED, 0x82, 0x09, 0x68, 0x9A, 0x99, 0x2E, 0x3F, 0xDE, 0x21,
+ 0xCE, 0x35, 0x3D, 0x12, 0x51, 0x55, 0xEE, 0xC7, 0x63, 0x5F, 0xB0, 0xF1, 0xDA, 0x6A, 0x99, 0x4E,
+ 0x60, 0x7E, 0x4E, 0x39, 0x9B, 0x11, 0x2F, 0xA1, 0x70, 0xF9, 0xE5, 0x50, 0x2A, 0x90, 0x35, 0x42,
+ 0x3E, 0xEA, 0x8C, 0x2E, 0x70, 0x9A, 0x6E, 0x52, 0x70, 0x2D, 0x7D, 0x35, 0xA3, 0x3F, 0x1E, 0x05,
+ 0xCD, 0xF5, 0x94, 0x0D, 0x55, 0xDF, 0xD9, 0x30, 0x91, 0x36, 0x81, 0x58, 0x77, 0x70, 0x87, 0x7A,
+ 0x90, 0xFD, 0x07, 0xE1, 0x3B, 0x82, 0xBE, 0xBE, 0xB1, 0xBA, 0xC6, 0x75, 0x75, 0x6F, 0x03, 0x57,
+ 0xFA, 0x6F, 0x23, 0xD3, 0xBA, 0x1E, 0x39, 0x60, 0x5A, 0x91, 0xB2, 0xD7, 0x33, 0xF1, 0xF8, 0x38,
+ 0x81, 0x4D, 0x12, 0x1A, 0xC9, 0x60, 0x9A, 0x1C, 0x84, 0x1F, 0x59, 0x00, 0xAB, 0xAD, 0x5C, 0x47,
+ 0xBD, 0x6C, 0x77, 0x2F, 0xCC, 0x3A, 0xF8, 0x2C, 0x2B, 0x40, 0xB5, 0x39, 0x0C, 0xFA, 0x9B, 0xDC,
+ 0xE3, 0x7B, 0x7B, 0x9B, 0xBE, 0xF0, 0xD5, 0xA9, 0xE4, 0xC7, 0x02, 0x61, 0xE2, 0xF7, 0xB3, 0x67,
+ 0x67, 0xCF, 0xCF, 0x5E, 0x5C, 0xBC, 0x7B, 0x71, 0xFE, 0xE4, 0x6C, 0xB5, 0xAA, 0xD1, 0xF8, 0xA6,
+ 0xF0, 0x67, 0x2B, 0xEC, 0xB4, 0xBD, 0x68, 0xB7, 0x27, 0xEE, 0xBD, 0x9D, 0xE5, 0x70, 0x20, 0x74,
+ 0xCA, 0x59, 0xCD, 0x78, 0x11, 0x69, 0xA5, 0x79, 0x4C, 0x64, 0x89, 0x82, 0xE6, 0xEE, 0xD1, 0xF6,
+ 0xBC, 0x45, 0xCE, 0x17, 0xC8, 0x49, 0x08, 0x35, 0xEB, 0xB5, 0x5B, 0x53, 0xC9, 0x7D, 0xC5, 0xD7,
+ 0x46, 0xDB, 0x14, 0x54, 0x0C, 0xF5, 0xFE, 0xD3, 0x2E, 0xE4, 0x57, 0x54, 0xA0, 0x86, 0x83, 0x8C,
+ 0xFE, 0x66, 0x06, 0x32, 0xDE, 0x03, 0xF2, 0xB5, 0xB4, 0x84, 0x3F, 0x70, 0x0D, 0x7D, 0xBB, 0x86,
+ 0x76, 0x68, 0xDB, 0x46, 0x01, 0xE8, 0x11, 0x91, 0x67, 0xDD, 0x52, 0x84, 0xC0, 0x6A, 0xA8, 0xE1,
+ 0x15, 0xD9, 0x32, 0x1A, 0x1D, 0x5F, 0x3E, 0xBC, 0xC6, 0x99, 0xB3, 0xB6, 0x73, 0x34, 0xF8, 0xA2,
+ 0xFE, 0x0E, 0xF4, 0xD3, 0x3C, 0x69, 0xB5, 0x32, 0xFD, 0xDD, 0xFE, 0xC5, 0xFA, 0x4F, 0x74, 0x86,
+ 0xA0, 0x99, 0x83, 0x1B, 0xE5, 0xF5, 0xDD, 0xE2, 0x05, 0xCB, 0xBA, 0xE4, 0xD6, 0x12, 0x30, 0xAC,
+ 0x39, 0xBE, 0x8B, 0xA5, 0x37, 0xA7, 0x48, 0x4B, 0xE1, 0x35, 0xE7, 0x4D, 0x25, 0x1D, 0x16, 0x1D,
+ 0x9C, 0xD5, 0x91, 0x13, 0x83, 0xA8, 0xB3, 0x22, 0xC7, 0x55, 0xA5, 0x2A, 0xD2, 0x63, 0x53, 0xB3,
+ 0xAA, 0x45, 0x01, 0x3B, 0xEF, 0x34, 0xCE, 0x88, 0x27, 0xE6, 0xD1, 0x03, 0x09, 0xD6, 0x86, 0x19,
+ 0x6E, 0x83, 0x8B, 0x62, 0x18, 0xEA, 0x16, 0x81, 0x86, 0x38, 0xD4, 0x45, 0xBC, 0x55, 0x3F, 0x5A,
+ 0x05, 0x6D, 0xBB, 0x2E, 0xB3, 0x5A, 0x6E, 0xA3, 0x1E, 0x32, 0x66, 0xF8, 0x61, 0x7A, 0x20, 0x29,
+ 0x07, 0x13, 0x84, 0x47, 0x51, 0x1A, 0xD9, 0x91, 0x07, 0xF3, 0x32, 0xFD, 0xD8, 0xBC, 0xCA, 0x9A,
+ 0x5C, 0x91, 0x46, 0x25, 0x57, 0x70, 0xB7, 0x78, 0xAC, 0x5B, 0x68, 0x4A, 0xC8, 0xD6, 0x5E, 0x79,
+ 0xC2, 0x80, 0xE4, 0x9D, 0x32, 0x7E, 0x5B, 0xF6, 0x96, 0x36, 0xDB, 0x47, 0x45, 0xC6, 0x69, 0x48,
+ 0x07, 0xEB, 0x9A, 0xE8, 0x48, 0xEC, 0xED, 0x53, 0xA4, 0x69, 0x42, 0xF5, 0x8D, 0x22, 0xEA, 0x60,
+ 0x9B, 0x6E, 0x16, 0xB9, 0x42, 0x6F, 0x6F, 0xBC, 0x28, 0x88, 0xE9, 0xE8, 0xCB, 0xE4, 0x7D, 0xBE,
+ 0xA3, 0xAA, 0x93, 0x59, 0xD7, 0xB4, 0x28, 0x1D, 0x53, 0xB3, 0x29, 0xF3, 0xDE, 0xDE, 0x83, 0xAB,
+ 0xC2, 0x8A, 0xD0, 0xBA, 0x90, 0x51, 0xCB, 0x5C, 0x36, 0x6B, 0xB8, 0xFE, 0xC3, 0xC1, 0xFF, 0xD2,
+ 0xA8, 0xB3, 0xDE, 0xD6, 0xE8, 0xF2, 0x21, 0x79, 0xD8, 0x08, 0xBF, 0x8F, 0x11, 0x8C, 0x65, 0xA8,
+ 0x96, 0xE9, 0x0A, 0x36, 0xCA, 0xD7, 0x4E, 0x57, 0x17, 0x72, 0x7F, 0x3C, 0x62, 0x74, 0xEA, 0xDA,
+ 0x05, 0x1D, 0x74, 0xF6, 0xE5, 0x76, 0x27, 0x96, 0xBE, 0xE1, 0x76, 0x17, 0xEC, 0x4F, 0xFB, 0x48,
+ 0xC8, 0x9E, 0x6C, 0xEF, 0x94, 0x58, 0xAD, 0xEC, 0xD0, 0x35, 0xDA, 0xEA, 0xD8, 0x6F, 0xBA, 0xC4,
+ 0x0D, 0xFF, 0x3B, 0xB9, 0x63, 0xCF, 0xB0, 0x70, 0xE6, 0x89, 0x8D, 0x12, 0x5B, 0x58, 0x9E, 0x5E,
+ 0x1A, 0xAD, 0x12, 0x68, 0x61, 0x63, 0xA1, 0x5A, 0xCB, 0x5E, 0x9D, 0x6F, 0x35, 0xD8, 0xD8, 0x86,
+ 0xD8, 0xF0, 0x60, 0x5D, 0xA3, 0x5D, 0x1B, 0x83, 0x0D, 0x5D, 0x80, 0x3F, 0x0E, 0xA6, 0x70, 0x70,
+ 0x35, 0x96, 0xAF, 0xB5, 0x7A, 0xF3, 0x95, 0x1E, 0x5F, 0x1E, 0xF5, 0xD9, 0xDF, 0x4F, 0x81, 0xF1,
+ 0x68, 0x18, 0xC7, 0xDA, 0xE3, 0x01, 0x2F, 0x78, 0x96, 0x1A, 0x79, 0x9B, 0x2A, 0x2A, 0x3F, 0xC7,
+ 0x33, 0xBE, 0xB4, 0x80, 0x1D, 0x39, 0x41, 0xE8, 0x53, 0x31, 0x53, 0x07, 0x55, 0xF8, 0xA8, 0x3A,
+ 0xC8, 0xE3, 0x61, 0x2A, 0x59, 0x77, 0x10, 0x9F, 0xE5, 0x03, 0x1D, 0xA0, 0x4B, 0xB7, 0x1E, 0xAC,
+ 0xDF, 0xF3, 0x36, 0x86, 0x3E, 0x85, 0x62, 0x6B, 0xF0, 0x93, 0x95, 0x88, 0x9F, 0x74, 0x44, 0x6D,
+ 0x99, 0x30, 0xA8, 0xEE, 0xC0, 0xAD, 0xAA, 0x19, 0xB8, 0x55, 0x3D, 0x3E, 0x70, 0xAB, 0xE2, 0xC0,
+ 0x2D, 0x2A, 0x42, 0xE7, 0x36, 0xF0, 0xC5, 0xD8, 0x5D, 0xBE, 0x6D, 0xB3, 0x6E, 0xB7, 0x3A, 0xB6,
+ 0x39, 0x47, 0xCB, 0xAB, 0xAD, 0xF5, 0x5A, 0xA9, 0x42, 0x04, 0xFD, 0x2E, 0x2D, 0x5D, 0x7E, 0xDB,
+ 0xAE, 0xA1, 0x4B, 0x4B, 0x84, 0x90, 0x32, 0x5D, 0x93, 0x68, 0x77, 0xD8, 0x82, 0x74, 0x7E, 0xB5,
+ 0x48, 0x9B, 0x86, 0x8D, 0xB1, 0xEC, 0x8E, 0xA2, 0xA9, 0x56, 0x78, 0x0A, 0xC2, 0x0D, 0x53, 0x20,
+ 0x02, 0x19, 0x42, 0x5F, 0xF3, 0xBF, 0x3F, 0x31, 0xBE, 0xCA, 0xD7, 0x5B, 0x2C, 0x9C, 0x72, 0x51,
+ 0x50, 0xA0, 0x63, 0xF8, 0xD4, 0xCD, 0x66, 0xCD, 0x98, 0x6D, 0xCB, 0xA6, 0x2A, 0xD4, 0x36, 0x6D,
+ 0xBE, 0xF8, 0x94, 0x9D, 0x6E, 0x0F, 0xED, 0x6B, 0xB3, 0xF6, 0x87, 0x4F, 0xB6, 0x5A, 0x6E, 0xD9,
+ 0xCC, 0xD6, 0x36, 0x29, 0x42, 0x40, 0x1A, 0x27, 0xD9, 0x6D, 0x52, 0x7A, 0x5B, 0xAD, 0x92, 0xAF,
+ 0xC5, 0x7F, 0xDA, 0xEA, 0xF8, 0xA0, 0x79, 0xD1, 0xF4, 0xEC, 0xDF, 0x34, 0x31, 0xD6, 0xE9, 0xC2,
+ 0x2D, 0x69, 0xEA, 0x29, 0x62, 0x87, 0x21, 0xD4, 0xF1, 0x33, 0x24, 0xAB, 0x4E, 0xC6, 0x5C, 0x9F,
+ 0x28, 0x45, 0x79, 0xD1, 0x8A, 0x2F, 0x6D, 0xB8, 0x81, 0xB6, 0x40, 0xB6, 0x55, 0xAA, 0xF3, 0xAC,
+ 0x7B, 0x5B, 0xDC, 0x83, 0x62, 0xDE, 0xFF, 0x42, 0xF9, 0xEC, 0xD0, 0x1D, 0xB7, 0xF1, 0xC7, 0x87,
+ 0xF5, 0xB9, 0xAD, 0xD3, 0xB6, 0xAE, 0xB7, 0x8E, 0xBE, 0x6B, 0x5B, 0x61, 0x03, 0x3F, 0xF6, 0xF6,
+ 0x1E, 0x36, 0xE4, 0xD9, 0x96, 0x3A, 0xD1, 0x61, 0xB3, 0x76, 0x68, 0x4B, 0xB6, 0xC0, 0x71, 0x39,
+ 0x5B, 0x14, 0x1D, 0xE6, 0xFF, 0xC7, 0x5A, 0xDE, 0x3A, 0x04, 0xF1, 0x87, 0x15, 0xEC, 0xFF, 0xB1,
+ 0x3E, 0xB1, 0x79, 0x7D, 0xA8, 0xD6, 0xBB, 0x17, 0x82, 0x5E, 0x07, 0x9F, 0xA2, 0x0D, 0x58, 0xB8,
+ 0x77, 0x62, 0x82, 0x6B, 0x1F, 0xFC, 0x4F, 0x18, 0xFA, 0x6C, 0x73, 0x5B, 0x4D, 0x6B, 0xAF, 0xFF,
+ 0x43, 0xA6, 0x35, 0x07, 0xF3, 0xAB, 0xF0, 0x29, 0x51, 0xF2, 0x4F, 0x35, 0x1F, 0x76, 0x2A, 0x17,
+ 0xB5, 0x00, 0x84, 0xA3, 0x5A, 0xB0, 0xC0, 0x26, 0x83, 0x5A, 0x23, 0x64, 0x80, 0x70, 0xCD, 0x90,
+ 0x67, 0xE6, 0xA5, 0x9B, 0xE8, 0x55, 0xB0, 0x7C, 0x1C, 0x38, 0xB7, 0xD8, 0x55, 0x1C, 0x28, 0x88,
+ 0xBA, 0x6D, 0x45, 0x18, 0x0B, 0x4A, 0xE3, 0xAD, 0x23, 0xB2, 0x18, 0xA9, 0x96, 0xF5, 0x87, 0xEA,
+ 0xF9, 0xD3, 0xF7, 0xE0, 0x59, 0x4D, 0xD2, 0xF6, 0xAB, 0xD3, 0x37, 0x63, 0xBE, 0x92, 0x94, 0xD4,
+ 0x80, 0x57, 0x10, 0x3E, 0xD2, 0x2E, 0x63, 0x2A, 0xF9, 0x24, 0xDB, 0xCC, 0xDE, 0x5E, 0x6D, 0x6E,
+ 0x7E, 0x6D, 0x90, 0xBE, 0x96, 0xE6, 0x5E, 0xFC, 0xDB, 0x72, 0xAA, 0x80, 0xD0, 0x49, 0xF0, 0xE7,
+ 0x4D, 0x48, 0xE5, 0xDB, 0x14, 0x9A, 0x82, 0xF7, 0x8E, 0xD4, 0x2C, 0xCF, 0x11, 0x6A, 0xB8, 0xFB,
+ 0x9F, 0x28, 0xCD, 0x52, 0xC5, 0xDC, 0x91, 0xED, 0x02, 0xAD, 0x85, 0xE3, 0x56, 0xA1, 0xD6, 0xE4,
+ 0xAA, 0x09, 0xB6, 0xAE, 0x11, 0x5B, 0x2E, 0x60, 0x1E, 0xF3, 0xA3, 0x64, 0xDB, 0x66, 0x5C, 0x73,
+ 0x3B, 0x86, 0x59, 0x8A, 0xAE, 0x69, 0x27, 0x4D, 0xDB, 0x2A, 0xBE, 0x6A, 0x61, 0xB4, 0x6E, 0x44,
+ 0xB7, 0x03, 0x55, 0xB1, 0xD7, 0xE0, 0x7B, 0xD9, 0x2E, 0x9F, 0x80, 0xF7, 0xA2, 0x11, 0x4C, 0x6C,
+ 0xE4, 0x8D, 0x0D, 0xF3, 0x9B, 0xB6, 0x85, 0xE0, 0x9A, 0xD5, 0x0C, 0x71, 0x77, 0x93, 0x6E, 0xD8,
+ 0xCA, 0x7E, 0xCB, 0x6E, 0xB5, 0xFB, 0x54, 0xDB, 0xF1, 0xB9, 0x09, 0xB7, 0x02, 0x25, 0x32, 0xB1,
+ 0xE4, 0xFD, 0xC2, 0x48, 0xDE, 0xEF, 0x85, 0x3E, 0x37, 0x0E, 0xF0, 0x27, 0x4A, 0x74, 0xCD, 0xF7,
+ 0xCF, 0x7A, 0x98, 0xEA, 0x94, 0x2F, 0x39, 0x9A, 0x62, 0x17, 0x20, 0x7E, 0xF0, 0x72, 0x43, 0xA8,
+ 0xE8, 0x51, 0x37, 0xB3, 0x6B, 0xCC, 0xCC, 0x18, 0x8F, 0xA0, 0xEF, 0x95, 0xDC, 0xE3, 0x5D, 0xD0,
+ 0xEB, 0xC7, 0x59, 0x9A, 0x7D, 0x88, 0xB8, 0x04, 0x36, 0x25, 0x3D, 0x17, 0x31, 0x2E, 0xFD, 0x1B,
+ 0x44, 0xF8, 0xC7, 0x1B, 0xEF, 0x07, 0x2B, 0x9C, 0xBA, 0x32, 0xC3, 0x7D, 0x93, 0x57, 0xD5, 0x1C,
+ 0x21, 0xF9, 0xB3, 0x15, 0x36, 0xF3, 0x20, 0x3E, 0x6C, 0x35, 0xFC, 0xC7, 0x6F, 0xF7, 0x0E, 0xA3,
+ 0xC1, 0xE8, 0x73, 0x64, 0x1C, 0xFE, 0xF6, 0x70, 0x30, 0x5A, 0x7D, 0x16, 0x04, 0x87, 0x69, 0xF8,
+ 0x92, 0xCA, 0x13, 0x04, 0xA8, 0x06, 0x10, 0xD7, 0x6B, 0xF1, 0xD3, 0x21, 0x9E, 0x2E, 0x6F, 0xE6,
+ 0xAB, 0xEB, 0xF4, 0x6A, 0xF5, 0x7E, 0x2E, 0xAE, 0xF1, 0xE7, 0x7A, 0x35, 0x87, 0x27, 0xBC, 0x4A,
+ 0xAF, 0xAE, 0x56, 0x77, 0xE2, 0x72, 0x1E, 0xAC, 0x68, 0x9F, 0x62, 0xCE, 0x39, 0x6F, 0x28, 0xC7,
+ 0xCD, 0xFC, 0x4F, 0x2B, 0x00, 0x9D, 0x3E, 0xDE, 0x04, 0xAB, 0x64, 0x31, 0x49, 0xF5, 0xC7, 0x3F,
+ 0x22, 0x3D, 0xE1, 0x6F, 0xD8, 0x43, 0x07, 0xFC, 0xE9, 0xD3, 0xB9, 0x38, 0x7F, 0xF9, 0x53, 0x38,
+ 0xFC, 0x69, 0xB2, 0x7F, 0x98, 0x1C, 0xFC, 0x82, 0x0B, 0x68, 0x3E, 0xFF, 0x0C, 0xFD, 0xF8, 0x46,
+ 0xC4, 0xAC, 0x1E, 0x38, 0x61, 0x76, 0x15, 0xBB, 0x43, 0x08, 0x25, 0x31, 0xA3, 0xCF, 0xF2, 0x3B,
+ 0xBD, 0x7B, 0x92, 0x23, 0xEF, 0x6B, 0xFB, 0x8E, 0xD4, 0xC1, 0x8F, 0xEF, 0x05, 0x1F, 0x69, 0x97,
+ 0x62, 0x85, 0x6A, 0xFF, 0xD3, 0x73, 0xB5, 0x03, 0x40, 0xD6, 0xC5, 0x7B, 0x00, 0xF1, 0xF5, 0x65,
+ 0x47, 0xAA, 0x45, 0x4C, 0x7D, 0x58, 0x1D, 0x4D, 0x34, 0xFA, 0xA1, 0x26, 0x37, 0xBF, 0x52, 0x27,
+ 0xBB, 0xD4, 0xB7, 0xD9, 0x60, 0x66, 0xED, 0x36, 0x1B, 0xC1, 0xDB, 0x6C, 0x68, 0xA7, 0x08, 0x16,
+ 0xB8, 0x6C, 0xC2, 0xF6, 0xEE, 0xA8, 0xAF, 0x1E, 0x8E, 0x9D, 0xA3, 0x31, 0x5F, 0xA9, 0xF8, 0x44,
+ 0x15, 0x1A, 0xA5, 0xAA, 0x0A, 0x34, 0x81, 0xE3, 0x1B, 0x16, 0x3A, 0x4F, 0xB3, 0xB4, 0xF7, 0x2E,
+ 0xF8, 0x95, 0xED, 0x3A, 0xCB, 0x22, 0x2A, 0x22, 0xF5, 0xC9, 0xF9, 0xF3, 0x97, 0x14, 0x69, 0x5A,
+ 0x04, 0x32, 0xE2, 0x94, 0xB6, 0x7C, 0xBC, 0x66, 0x82, 0x47, 0xCE, 0x44, 0xDA, 0xAD, 0x71, 0xC8,
+ 0x57, 0x85, 0xD2, 0xB9, 0xE8, 0xEE, 0xA9, 0x74, 0x82, 0xAE, 0xB3, 0xAA, 0x6D, 0x81, 0xCE, 0xA4,
+ 0xD5, 0xF4, 0x67, 0xA2, 0xC3, 0xAF, 0x41, 0xF7, 0x89, 0xEC, 0x9D, 0xCC, 0xC0, 0xE8, 0x3F, 0xF7,
+ 0x82, 0x3A, 0x34, 0x70, 0x5A, 0xA5, 0x85, 0x46, 0xEA, 0x6E, 0x3A, 0xC2, 0x17, 0xC0, 0x84, 0x76,
+ 0x25, 0x6D, 0x99, 0xD5, 0xDD, 0xD2, 0xD9, 0xCC, 0x13, 0x00, 0x26, 0x26, 0xFE, 0xA7, 0xAF, 0x37,
+ 0xC0, 0xA8, 0x68, 0x9D, 0xA2, 0xDE, 0xC3, 0xD4, 0x39, 0x05, 0x9F, 0xCE, 0x7E, 0xB5, 0x1F, 0xC5,
+ 0x10, 0xBD, 0x1C, 0xF1, 0x09, 0x89, 0x62, 0x98, 0xF1, 0x03, 0x76, 0x7B, 0xBA, 0x6B, 0x79, 0xF9,
+ 0x0D, 0xCD, 0x41, 0x42, 0x06, 0x9F, 0x96, 0x80, 0x68, 0x51, 0x90, 0x18, 0x92, 0x82, 0xB8, 0x04,
+ 0x87, 0x0C, 0x21, 0xFE, 0xE6, 0xE2, 0xF9, 0x33, 0xD5, 0xA5, 0xAF, 0xEA, 0xC7, 0xFF, 0xF8, 0xFF,
+ 0x58, 0xFD, 0xF4, 0x53, 0x19, 0x30, 0x9D, 0xCE, 0x67, 0x55, 0x3A, 0xFF, 0xE9, 0xA7, 0xD7, 0xFB,
+ 0x58, 0xBE, 0xA4, 0x79, 0xBC, 0x75, 0xE9, 0x41, 0x99, 0x90, 0x68, 0xF4, 0x0B, 0x2D, 0x7A, 0xBE,
+ 0x46, 0x85, 0x18, 0x3A, 0x9E, 0x75, 0xF2, 0xD7, 0x19, 0x2D, 0xF3, 0x9F, 0xA1, 0x94, 0x27, 0x3A,
+ 0xCA, 0xC9, 0x51, 0xC1, 0x41, 0x30, 0xE6, 0xA4, 0x5A, 0xD8, 0x03, 0x6B, 0xAA, 0xB4, 0x9A, 0xB5,
+ 0x4E, 0xB8, 0xA9, 0x1D, 0x56, 0xA3, 0xA4, 0x53, 0x5B, 0x64, 0x22, 0xF8, 0xC8, 0x1B, 0x5F, 0xDE,
+ 0x20, 0x67, 0x0F, 0xC3, 0x21, 0xF4, 0x70, 0xD5, 0x7D, 0x35, 0xF5, 0xB6, 0x72, 0xF5, 0x29, 0x70,
+ 0x8E, 0x50, 0x37, 0x1F, 0xDD, 0x16, 0x1F, 0x3C, 0xB6, 0x47, 0xC9, 0x3D, 0xA2, 0x68, 0x77, 0xDC,
+ 0xB6, 0xD1, 0xBE, 0xFD, 0x27, 0xF2, 0xB8, 0xC2, 0xED, 0xC7, 0xFE, 0x80, 0xEF, 0x20, 0x5A, 0xF9,
+ 0x94, 0x34, 0x95, 0xCE, 0xCE, 0x69, 0x40, 0x3B, 0x23, 0xB5, 0xB0, 0x57, 0x47, 0xFE, 0xB8, 0xF9,
+ 0xCD, 0x24, 0xE9, 0x03, 0x46, 0x1E, 0x79, 0x48, 0xD0, 0x3A, 0xFC, 0x05, 0x93, 0x78, 0xF2, 0xDD,
+ 0xC5, 0xB9, 0x3E, 0x22, 0xEC, 0xE2, 0xFC, 0xA5, 0xBA, 0xC5, 0xF9, 0xD5, 0xD3, 0xBF, 0x7E, 0x73,
+ 0x11, 0x99, 0x33, 0x99, 0x3C, 0x73, 0xF1, 0xF3, 0x57, 0xE7, 0x17, 0x17, 0xE7, 0xCF, 0xED, 0x59,
+ 0xAF, 0xCF, 0xCE, 0xBE, 0x96, 0xF9, 0x54, 0x86, 0x88, 0xF3, 0xA3, 0xEE, 0xCF, 0x6A, 0x08, 0xB2,
+ 0x7B, 0x64, 0x31, 0xE3, 0xF7, 0x5F, 0x20, 0x84, 0x72, 0x87, 0x35, 0xB5, 0xD8, 0x53, 0xB8, 0xE8,
+ 0xED, 0x90, 0xE6, 0x63, 0x5F, 0xBF, 0xEC, 0xC8, 0x73, 0xC0, 0x27, 0x68, 0xE1, 0xD3, 0x21, 0xBE,
+ 0x75, 0x66, 0xE0, 0x15, 0xA0, 0x32, 0xA8, 0xBF, 0xBF, 0xB7, 0xD8, 0x35, 0x25, 0x5B, 0xF0, 0x0E,
+ 0xF3, 0x7D, 0x83, 0x95, 0x1A, 0xD7, 0x8E, 0x24, 0x6E, 0xED, 0x1E, 0x1B, 0xA4, 0xC2, 0xB3, 0x45,
+ 0x22, 0x05, 0x15, 0x7B, 0xBC, 0xD4, 0xD1, 0xC8, 0xA0, 0x08, 0xE5, 0xEC, 0x42, 0x85, 0xA1, 0x2C,
+ 0xA3, 0x01, 0x67, 0x20, 0x26, 0x01, 0xB4, 0xE5, 0x78, 0xAA, 0x06, 0x86, 0xD8, 0xD9, 0x07, 0x14,
+ 0x5D, 0x4C, 0xA0, 0x59, 0x75, 0x26, 0x7F, 0x09, 0xBA, 0x82, 0x46, 0x19, 0x24, 0x6C, 0x55, 0x28,
+ 0x88, 0x81, 0x4F, 0xA8, 0xC1, 0x84, 0x16, 0x95, 0x32, 0x37, 0x1C, 0xFE, 0x83, 0xF5, 0xD1, 0xE1,
+ 0x4F, 0x77, 0x07, 0x23, 0x62, 0x74, 0xB8, 0x23, 0x02, 0xA5, 0xA4, 0x70, 0x6F, 0x19, 0x3F, 0x83,
+ 0x87, 0xCA, 0x88, 0x19, 0xBA, 0x8A, 0x7E, 0x21, 0x13, 0xBA, 0xCC, 0x7F, 0x0A, 0xFC, 0xC5, 0xE8,
+ 0x67, 0xF2, 0x67, 0x22, 0xE8, 0x17, 0xA0, 0xA6, 0x1F, 0x71, 0x43, 0x7F, 0xA7, 0x05, 0xFF, 0x3D,
+ 0xE6, 0xBF, 0x7F, 0xE0, 0xBF, 0x7F, 0xE4, 0xBF, 0x7F, 0xE2, 0xBF, 0x7F, 0xE6, 0xBF, 0x7F, 0xA1,
+ 0xBF, 0x29, 0xFF, 0xB9, 0xB9, 0x8E, 0x86, 0x52, 0x96, 0xA0, 0xBF, 0x80, 0x30, 0x53, 0x9E, 0xCA,
+ 0x76, 0x43, 0x1F, 0x3A, 0xAF, 0x4F, 0xA1, 0xC7, 0x35, 0x13, 0x5C, 0x52, 0xF6, 0x61, 0xCE, 0x7F,
+ 0x0A, 0xEE, 0x47, 0xC9, 0x7F, 0xFE, 0x55, 0xDD, 0xB5, 0xF7, 0x36, 0x4E, 0x04, 0xF1, 0xFF, 0xF9,
+ 0x14, 0x21, 0xA0, 0x2A, 0xD6, 0x6D, 0x4B, 0x2B, 0x84, 0x84, 0x1C, 0x7C, 0xD1, 0xD1, 0xF6, 0xE0,
+ 0x1E, 0x50, 0xB8, 0xB6, 0xC0, 0x51, 0x55, 0x5C, 0xDA, 0x5B, 0xA8, 0x21, 0x97, 0x94, 0xD8, 0xE5,
+ 0xD5, 0xF8, 0xBB, 0x33, 0x33, 0xFB, 0x98, 0x7D, 0xD9, 0x71, 0xAE, 0x05, 0x84, 0x78, 0x34, 0xDE,
+ 0x97, 0xD7, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xBF, 0x9D, 0x79, 0x03, 0x54, 0xA1, 0x1F, 0xD7, 0xD3,
+ 0x39, 0xFD, 0xBD, 0xB9, 0x50, 0x7F, 0xA8, 0x28, 0xCC, 0x38, 0xD0, 0x65, 0xF0, 0xD7, 0x0D, 0xFD,
+ 0x0F, 0x8B, 0x36, 0xB1, 0xB7, 0xAF, 0x46, 0x9C, 0x02, 0x0B, 0x7F, 0xFE, 0xE4, 0xE0, 0x30, 0xB7,
+ 0x86, 0x0D, 0xC3, 0x9E, 0x02, 0x92, 0x0F, 0x0E, 0xBF, 0xCC, 0x59, 0xDB, 0xE4, 0xAC, 0xE3, 0xCF,
+ 0x8F, 0xBE, 0xCD, 0xC9, 0xEC, 0x10, 0x25, 0x7F, 0xA9, 0xD2, 0xFD, 0xF2, 0x4F, 0xBE, 0x3C, 0x3E,
+ 0x7C, 0x71, 0x72, 0x78, 0x90, 0xE3, 0x05, 0x51, 0xB9, 0x84, 0xCD, 0xA4, 0x97, 0xBD, 0xFF, 0xFC,
+ 0xC9, 0xFE, 0xB3, 0xDC, 0xAA, 0xE0, 0x9C, 0xF1, 0xF8, 0x68, 0xFF, 0xF4, 0xF8, 0x09, 0x34, 0xA9,
+ 0xB7, 0x78, 0x71, 0xE6, 0xD1, 0xE9, 0x89, 0xCE, 0x5D, 0xDC, 0xD4, 0x5E, 0xF6, 0x17, 0x47, 0xA7,
+ 0xC7, 0x87, 0x80, 0xE3, 0x38, 0x7C, 0x91, 0x2B, 0x44, 0x91, 0xC4, 0xDD, 0x4C, 0x5C, 0xE4, 0xF9,
+ 0xE1, 0xA3, 0x6F, 0x0E, 0x75, 0x11, 0x90, 0x44, 0xBF, 0xB9, 0x44, 0xB0, 0xD6, 0xD5, 0xC7, 0x5D,
+ 0xD6, 0xD5, 0x8D, 0xFD, 0x38, 0xE9, 0xD6, 0x37, 0x70, 0xE3, 0x14, 0xD8, 0x6F, 0xCB, 0xEA, 0x50,
+ 0xDD, 0x92, 0x67, 0x43, 0x63, 0x5D, 0xBE, 0x91, 0x40, 0x82, 0xC2, 0x3C, 0x93, 0x24, 0x38, 0x46,
+ 0x37, 0x9F, 0x7C, 0x48, 0x31, 0x05, 0xA9, 0xF8, 0x9B, 0x3C, 0x51, 0xC2, 0xA2, 0xB8, 0x6D, 0xDE,
+ 0xD6, 0xEF, 0x19, 0xF4, 0xDD, 0x2D, 0x5D, 0xC9, 0xDE, 0xAE, 0xCB, 0xDE, 0xEF, 0xC0, 0x97, 0x1A,
+ 0x9A, 0xBB, 0x25, 0x68, 0xB7, 0xCD, 0xD5, 0x4F, 0xD7, 0x7B, 0x3E, 0xFB, 0x55, 0x36, 0x92, 0x68,
+ 0x03, 0x49, 0x31, 0xB1, 0xD0, 0xC6, 0xD3, 0x96, 0xB9, 0xA7, 0x8D, 0xCC, 0x3A, 0x21, 0x55, 0x24,
+ 0x48, 0x50, 0x15, 0x0C, 0xD4, 0x30, 0xC8, 0xCC, 0x20, 0x89, 0xCF, 0x39, 0x22, 0xC3, 0xF9, 0xD1,
+ 0xFC, 0x00, 0xD8, 0xED, 0x27, 0xB6, 0x50, 0xA2, 0x4A, 0x29, 0x83, 0x31, 0xDA, 0xB9, 0x54, 0x07,
+ 0xC9, 0xE9, 0x74, 0x01, 0xC9, 0x25, 0x6D, 0x91, 0x1F, 0xB9, 0x99, 0xB0, 0x50, 0x41, 0x06, 0x71,
+ 0x3B, 0xAD, 0x8A, 0xC0, 0xA1, 0x39, 0x24, 0x10, 0x6F, 0x9B, 0x04, 0x72, 0xEB, 0x6A, 0x3B, 0x0D,
+ 0x6F, 0x3F, 0x29, 0xAF, 0x2D, 0x6C, 0x7B, 0xBD, 0x67, 0x25, 0x15, 0x7E, 0x8B, 0xEA, 0xBA, 0xED,
+ 0xAA, 0x0D, 0x2F, 0x25, 0xBB, 0xEF, 0xD7, 0x9B, 0x6E, 0x36, 0xAF, 0xD1, 0x9D, 0xC9, 0x13, 0xC5,
+ 0xB0, 0x23, 0x8F, 0x7D, 0x93, 0xF0, 0x41, 0xBE, 0x6E, 0x6C, 0x4E, 0x89, 0x43, 0x3C, 0xA5, 0x08,
+ 0xD0, 0x7F, 0xFA, 0x16, 0x28, 0xB3, 0xEB, 0xD6, 0x96, 0xF9, 0x65, 0xB4, 0xDB, 0x9E, 0x9E, 0x82,
+ 0x7C, 0x83, 0x9F, 0xEB, 0xD4, 0x4E, 0xA1, 0x32, 0x60, 0xC6, 0x77, 0x40, 0x37, 0x1C, 0x51, 0xA0,
+ 0xC5, 0xC0, 0x57, 0xF0, 0xE9, 0x95, 0x1C, 0xDC, 0xC0, 0x7F, 0xD8, 0xD4, 0x00, 0xB6, 0x23, 0xFA,
+ 0x20, 0x60, 0xA0, 0x5B, 0xA8, 0xF4, 0x2D, 0x68, 0x6A, 0x56, 0x0D, 0x2F, 0x86, 0xD5, 0xA2, 0x91,
+ 0x59, 0xAD, 0x42, 0x16, 0x0C, 0x5D, 0x5D, 0xA5, 0xED, 0xA0, 0x09, 0x5F, 0x3D, 0x34, 0xB3, 0x76,
+ 0x50, 0x62, 0xE3, 0x41, 0x6C, 0x84, 0x66, 0x9B, 0xD3, 0x04, 0xA7, 0x6B, 0x55, 0x6E, 0x8E, 0x89,
+ 0xAE, 0x9E, 0x3E, 0xFC, 0x65, 0x7E, 0x09, 0x9A, 0xCB, 0xCB, 0x96, 0x1C, 0xFA, 0xD4, 0xD4, 0x31,
+ 0xDD, 0xBB, 0x73, 0xFF, 0xCB, 0xAA, 0x22, 0xC5, 0xA9, 0x18, 0xA1, 0x6C, 0x14, 0x7D, 0x1A, 0x8A,
+ 0x15, 0x10, 0x99, 0xBE, 0x49, 0x9B, 0xD4, 0x84, 0x45, 0x8F, 0x83, 0x0D, 0x34, 0x00, 0x40, 0x82,
+ 0x7C, 0x7D, 0xF1, 0x27, 0x57, 0x80, 0x72, 0x7A, 0x10, 0xC2, 0xA3, 0x19, 0xAB, 0xFF, 0x6D, 0x6D,
+ 0x55, 0xE9, 0x9B, 0x2F, 0x63, 0xB3, 0x7B, 0x5A, 0xEB, 0xC2, 0xC4, 0x28, 0x56, 0x93, 0x64, 0x32,
+ 0x50, 0x17, 0xD6, 0x7C, 0x65, 0x4B, 0xAB, 0x44, 0x40, 0xE0, 0x74, 0x0D, 0x31, 0x65, 0x19, 0x0E,
+ 0xDF, 0x09, 0x9B, 0x2F, 0xA2, 0xDB, 0xD2, 0x4C, 0x52, 0xE8, 0x25, 0x27, 0x93, 0x2E, 0x36, 0x9A,
+ 0xEA, 0xFE, 0xDE, 0xB2, 0xEE, 0x37, 0x0B, 0x1C, 0xE3, 0xBC, 0x40, 0x72, 0x8C, 0xAA, 0x84, 0xFF,
+ 0xA7, 0x47, 0x27, 0x8F, 0x7E, 0x78, 0x76, 0xF8, 0x52, 0xCD, 0x78, 0xF1, 0xF6, 0x8C, 0x03, 0xF3,
+ 0x14, 0x81, 0x15, 0x33, 0x0F, 0x6F, 0x09, 0x2D, 0x6E, 0xC8, 0xDD, 0x46, 0xED, 0x08, 0xDC, 0x82,
+ 0x4D, 0x92, 0x1E, 0xD6, 0xF2, 0xC0, 0x31, 0x60, 0xF0, 0x8A, 0x4A, 0xA4, 0xDD, 0x11, 0x4E, 0xA1,
+ 0xF1, 0x2A, 0x75, 0x90, 0x65, 0x07, 0xFE, 0x72, 0xED, 0xC0, 0x3B, 0xBA, 0xF0, 0xA4, 0x2D, 0x03,
+ 0x3A, 0xD8, 0x96, 0x35, 0xBE, 0x8C, 0x79, 0x0F, 0xF6, 0xE7, 0x97, 0xC6, 0x4D, 0xD1, 0x00, 0xB1,
+ 0xCB, 0xFF, 0xA0, 0x2B, 0xB6, 0xDB, 0x94, 0x23, 0x42, 0xB6, 0x3A, 0x5E, 0x15, 0x66, 0x4C, 0x3B,
+ 0x90, 0x44, 0xE3, 0xD6, 0x63, 0x2A, 0x23, 0xD9, 0x42, 0xB5, 0x66, 0x1C, 0x26, 0x90, 0xC4, 0xDA,
+ 0x94, 0x45, 0x48, 0x65, 0x45, 0xEA, 0xDC, 0xD4, 0x43, 0x15, 0x7B, 0x24, 0xBD, 0xAA, 0x35, 0x76,
+ 0x29, 0x11, 0x57, 0xF6, 0xD0, 0x10, 0xC5, 0xB4, 0xCB, 0x35, 0x81, 0x30, 0x4E, 0x89, 0x2C, 0x12,
+ 0x78, 0x1B, 0xF6, 0x11, 0xF5, 0xF3, 0x76, 0x60, 0x54, 0xEB, 0x81, 0xE0, 0x3F, 0x3A, 0xE6, 0x9D,
+ 0xEE, 0xF7, 0xD2, 0x4A, 0xCC, 0x5E, 0x32, 0x93, 0x74, 0xF7, 0xB6, 0x4C, 0x1A, 0x5C, 0xE7, 0x16,
+ 0xC4, 0xDD, 0x38, 0x89, 0x32, 0xD2, 0x1A, 0xD3, 0x6A, 0xA5, 0x89, 0xF6, 0x6E, 0x11, 0x31, 0x1A,
+ 0xB0, 0x44, 0xA8, 0x34, 0xA0, 0x02, 0x33, 0x87, 0x51, 0x55, 0x33, 0x73, 0xED, 0x89, 0x67, 0xB4,
+ 0xAE, 0x6C, 0x2C, 0xC9, 0xD4, 0x5E, 0x2C, 0xD4, 0x59, 0x46, 0x69, 0xA5, 0x25, 0x56, 0xEB, 0x33,
+ 0x97, 0x7F, 0x65, 0x96, 0xDA, 0x22, 0xB0, 0xC7, 0x4A, 0xED, 0x49, 0xAD, 0x8F, 0x53, 0x4A, 0x56,
+ 0x4F, 0x3C, 0x35, 0xDD, 0x18, 0x82, 0x99, 0xFF, 0x61, 0x8B, 0x8B, 0x38, 0xB3, 0x60, 0x2E, 0xC0,
+ 0x04, 0x62, 0x79, 0xAF, 0xEB, 0x9A, 0xE7, 0x71, 0xBF, 0x3B, 0xAA, 0xA6, 0x16, 0x5B, 0x06, 0x7D,
+ 0x90, 0xA4, 0xB1, 0xC8, 0xF0, 0xC6, 0xA5, 0xB6, 0x0C, 0x0D, 0xCE, 0x5A, 0x6C, 0x72, 0xC3, 0x2B,
+ 0x3C, 0x1F, 0x67, 0xA5, 0x27, 0xB0, 0x51, 0x08, 0x54, 0x8A, 0xAD, 0xE5, 0x1E, 0xAD, 0xF8, 0xC6,
+ 0x1A, 0xA4, 0x78, 0x48, 0x0A, 0x21, 0xA3, 0x69, 0xAB, 0x19, 0x58, 0xE8, 0xD9, 0xDB, 0x44, 0xAF,
+ 0xB0, 0x9B, 0x4D, 0x33, 0x42, 0x86, 0x66, 0x74, 0x25, 0x7D, 0x84, 0x6E, 0x79, 0xA5, 0xF2, 0x06,
+ 0xE3, 0x5F, 0x01, 0x41, 0xB3, 0xCF, 0x44, 0x3A, 0x27, 0xB4, 0x58, 0x17, 0xB8, 0xC7, 0x21, 0x1A,
+ 0xED, 0x11, 0xBD, 0xD5, 0x56, 0x22, 0x2C, 0x1B, 0xC8, 0xF7, 0x87, 0x79, 0x7B, 0x21, 0xDD, 0x27,
+ 0xC8, 0x6D, 0x79, 0x91, 0x9F, 0x64, 0x6C, 0x39, 0xE4, 0xF0, 0x17, 0x0C, 0xEF, 0xD2, 0xD7, 0x9E,
+ 0xAC, 0x69, 0xC7, 0x4F, 0x66, 0x0B, 0x10, 0x50, 0xCA, 0xED, 0xA6, 0x8C, 0x3A, 0x95, 0x35, 0x4C,
+ 0xD5, 0xDB, 0x99, 0x8C, 0x51, 0x8B, 0x3F, 0x79, 0x2A, 0x9E, 0x39, 0xC7, 0x5B, 0xC0, 0xEC, 0x2B,
+ 0xE7, 0x80, 0xB1, 0x24, 0xF3, 0x8B, 0xC3, 0x48, 0x08, 0xE5, 0x59, 0xBB, 0x60, 0x53, 0xA5, 0x49,
+ 0x9C, 0x44, 0x1A, 0x5A, 0xA8, 0xFB, 0xC6, 0xC5, 0xE0, 0x9B, 0xF4, 0x74, 0x63, 0xED, 0x8B, 0x8F,
+ 0x58, 0xC9, 0x9A, 0xA6, 0x56, 0xA3, 0xC9, 0x10, 0xC6, 0x43, 0xD9, 0x1B, 0x4D, 0x82, 0x76, 0x0B,
+ 0x5F, 0x37, 0xDD, 0x7B, 0x49, 0xD7, 0x5B, 0x43, 0xAB, 0x9E, 0xA6, 0x3F, 0x1A, 0x46, 0x46, 0xAE,
+ 0x56, 0x2F, 0x90, 0x52, 0xA8, 0x8B, 0xEB, 0xA6, 0x54, 0x4B, 0x70, 0x4A, 0x82, 0xC2, 0x51, 0xD9,
+ 0xCE, 0xC3, 0x86, 0xA2, 0xD2, 0xAC, 0x23, 0x99, 0x0E, 0x69, 0x2D, 0x09, 0x86, 0x11, 0xB4, 0xC6,
+ 0x64, 0xFB, 0x42, 0xE2, 0x6C, 0xF8, 0x5F, 0xF9, 0x65, 0xE4, 0x6D, 0xBD, 0xE7, 0x98, 0x31, 0xF6,
+ 0xC2, 0x48, 0xF1, 0xFE, 0x1D, 0x0F, 0x8A, 0xB1, 0x09, 0xD5, 0xE7, 0x8E, 0x38, 0xFF, 0xAD, 0xDC,
+ 0x31, 0x8A, 0x7B, 0xF3, 0x01, 0x09, 0xE6, 0x68, 0xAF, 0x9E, 0xA6, 0x4A, 0xFE, 0x6A, 0xE7, 0xFD,
+ 0xDB, 0xE4, 0xBE, 0xAB, 0x51, 0x16, 0xEC, 0x57, 0x4E, 0xBF, 0xE7, 0xFB, 0x57, 0x60, 0x9C, 0x8D,
+ 0xC3, 0x5A, 0x07, 0xC1, 0x25, 0x7E, 0x9C, 0xE7, 0xB5, 0xBD, 0xC6, 0x47, 0xFB, 0x77, 0x45, 0x73,
+ 0x4B, 0x0B, 0xD5, 0x0C, 0x82, 0xFA, 0xCE, 0x85, 0x17, 0x16, 0x25, 0x27, 0xBC, 0x43, 0x1C, 0xEB,
+ 0x07, 0xC5, 0x9C, 0x7D, 0x02, 0x81, 0xD7, 0xAB, 0x6D, 0xE3, 0xC9, 0x12, 0xB1, 0x39, 0xF2, 0x1E,
+ 0x1D, 0x4F, 0xCA, 0x6E, 0xC7, 0x93, 0xF1, 0x4E, 0x0C, 0xB9, 0x2C, 0xB5, 0xFE, 0x84, 0x7B, 0x0D,
+ 0x3E, 0xEC, 0xDA, 0x1E, 0x3E, 0xA0, 0x0A, 0x09, 0xD1, 0xA2, 0x20, 0x48, 0x29, 0x69, 0x33, 0xF8,
+ 0x4B, 0x9E, 0xD5, 0xBE, 0xA7, 0xDC, 0xF3, 0x26, 0x30, 0xE9, 0x79, 0x60, 0x12, 0xA3, 0xB8, 0xF0,
+ 0xEE, 0xC2, 0xDF, 0x0C, 0xC0, 0xFA, 0xC4, 0xFE, 0xF2, 0xEA, 0x0C, 0xF7, 0x06, 0xFD, 0x74, 0x1C,
+ 0xB2, 0x02, 0xFB, 0xEB, 0x80, 0x39, 0xA8, 0xB0, 0x97, 0x96, 0xAD, 0xC1, 0x4D, 0x79, 0xD0, 0x1F,
+ 0xE0, 0xCB, 0xDE, 0x4C, 0xE7, 0x37, 0xD3, 0x19, 0x69, 0x6E, 0x3C, 0x2F, 0xD5, 0x49, 0x88, 0x92,
+ 0x99, 0x2D, 0xEF, 0x73, 0x0C, 0xC4, 0x2D, 0x25, 0xB4, 0xF9, 0x59, 0x94, 0xBD, 0x9B, 0x23, 0x63,
+ 0x72, 0x67, 0x73, 0x60, 0xB0, 0x1E, 0x27, 0x68, 0x22, 0xBB, 0x3E, 0x9C, 0xED, 0x69, 0x75, 0x1A,
+ 0x0F, 0x54, 0x76, 0xD4, 0xE6, 0xED, 0x0C, 0xF1, 0x41, 0x93, 0xB5, 0x18, 0xCB, 0x8A, 0x18, 0x14,
+ 0xE8, 0x83, 0x83, 0xE8, 0xC5, 0xF7, 0x6B, 0x99, 0x0B, 0x3B, 0xEC, 0xCD, 0x1F, 0x82, 0xC7, 0xB9,
+ 0x09, 0xF6, 0x98, 0x4B, 0x0F, 0xB9, 0x7B, 0x3A, 0x3A, 0x6C, 0xF4, 0xF4, 0x02, 0xDF, 0x48, 0x5A,
+ 0x1B, 0x68, 0xF8, 0xB7, 0xE1, 0x8B, 0x6E, 0xDD, 0x40, 0xAB, 0x02, 0x42, 0x9A, 0x79, 0xFE, 0x56,
+ 0x8A, 0xC4, 0x08, 0x54, 0x08, 0xB3, 0x38, 0x01, 0x4F, 0xCA, 0xC4, 0x1D, 0x84, 0x3E, 0x0D, 0x09,
+ 0x6C, 0x06, 0x3A, 0xF3, 0x2E, 0xFC, 0xED, 0xEA, 0x07, 0xED, 0x2D, 0x66, 0xD3, 0x0B, 0x09, 0xC4,
+ 0x0F, 0x4B, 0xB2, 0xA2, 0x04, 0x8D, 0xAC, 0x35, 0x7C, 0x51, 0x23, 0xF0, 0xDE, 0x4E, 0x1B, 0x99,
+ 0x3D, 0xAE, 0x1A, 0x02, 0x2F, 0x69, 0xA6, 0x24, 0x65, 0xA3, 0x97, 0x85, 0x9B, 0x34, 0x61, 0xA0,
+ 0x47, 0x68, 0xCF, 0x3E, 0x33, 0x07, 0x3C, 0x0A, 0x97, 0x86, 0xE4, 0x9F, 0xA8, 0xA4, 0x61, 0xAE,
+ 0xA7, 0xDE, 0x39, 0x5D, 0x6A, 0x97, 0x1B, 0xD8, 0xAA, 0x81, 0x7E, 0xF4, 0x03, 0x9A, 0x94, 0xEE,
+ 0x66, 0x67, 0xE2, 0x3D, 0x15, 0xAA, 0x50, 0x3E, 0xF2, 0x8C, 0xD2, 0xD2, 0xB1, 0x48, 0xA7, 0x8A,
+ 0x0B, 0xC9, 0xAE, 0x8D, 0x25, 0x39, 0x36, 0x0E, 0x12, 0x08, 0x93, 0x34, 0xE1, 0x66, 0x0A, 0xA0,
+ 0xA2, 0x69, 0x9C, 0x26, 0x5A, 0xBA, 0x6F, 0xD0, 0x0E, 0xD5, 0xC4, 0xE9, 0x96, 0x6A, 0x31, 0xCB,
+ 0x4D, 0x7E, 0xD6, 0xA8, 0x49, 0x7D, 0x7F, 0xD4, 0x87, 0xAE, 0x75, 0x91, 0x5F, 0x26, 0x6F, 0x9D,
+ 0x7A, 0x20, 0x6B, 0x54, 0xE4, 0x5A, 0x37, 0xD8, 0xBD, 0x09, 0x0C, 0x69, 0x3D, 0xE8, 0x8B, 0x12,
+ 0xA5, 0x93, 0xBE, 0xF0, 0x2B, 0x45, 0x5E, 0x2B, 0xCD, 0x52, 0x0D, 0x66, 0xB9, 0xC9, 0xCF, 0x9A,
+ 0xF4, 0x77, 0x50, 0x48, 0x5D, 0x2D, 0x4C, 0x06, 0x60, 0x51, 0x49, 0x58, 0x2D, 0xE8, 0xE0, 0x27,
+ 0x4E, 0x06, 0x24, 0x4E, 0x8C, 0x4A, 0xA2, 0x65, 0x39, 0xD6, 0x2A, 0xD7, 0xC3, 0x81, 0xCD, 0x7E,
+ 0xC5, 0x03, 0x11, 0xF9, 0x0B, 0xF1, 0xB7, 0x0A, 0x99, 0x85, 0x8E, 0x72, 0x51, 0xE7, 0xAE, 0xE1,
+ 0x84, 0x81, 0x02, 0x5C, 0x8A, 0x1E, 0x21, 0x01, 0xD2, 0xBE, 0x96, 0x80, 0x79, 0xAC, 0xBB, 0x25,
+ 0x6B, 0x1E, 0x2E, 0x54, 0x80, 0x15, 0x4E, 0x98, 0x78, 0xE6, 0xA3, 0x7C, 0x3A, 0x72, 0xF2, 0x60,
+ 0x7D, 0x50, 0x80, 0x17, 0xA7, 0x51, 0x33, 0xC2, 0x23, 0xFD, 0xAB, 0xB8, 0x45, 0x0E, 0xCF, 0xF5,
+ 0x13, 0xC5, 0x07, 0x33, 0x0F, 0x4D, 0xB2, 0x3E, 0x89, 0x24, 0xA8, 0xAF, 0x7F, 0x15, 0xFA, 0x2F,
+ 0x28, 0x0B, 0x1A, 0x7A, 0x95, 0x25, 0xAB, 0x5D, 0x2A, 0xC9, 0x08, 0x15, 0xED, 0xEF, 0xC2, 0xFE,
+ 0xF2, 0x2A, 0xCF, 0x46, 0xE6, 0x70, 0x72, 0x7D, 0xD8, 0x83, 0xDA, 0xDD, 0xF2, 0xD6, 0xD6, 0xFE,
+ 0x00, 0x7B, 0x5F, 0xE7, 0x49, 0xD4, 0xEE, 0xCE, 0x37, 0xD8, 0xEE, 0x36, 0xEE, 0xFE, 0x29, 0xF6,
+ 0x04, 0x4F, 0x5E, 0x9B, 0xDD, 0x65, 0x31, 0x63, 0x96, 0x94, 0xC4, 0x92, 0x6E, 0x5E, 0x5B, 0x67,
+ 0xCF, 0xE4, 0x39, 0x2C, 0x4E, 0x6E, 0x51, 0x48, 0xC1, 0x1E, 0xC3, 0x9F, 0x30, 0xD9, 0x09, 0x6C,
+ 0x10, 0x5A, 0xBE, 0x6E, 0xBB, 0x4C, 0x9D, 0xB0, 0x90, 0x86, 0xAB, 0x15, 0x49, 0x6B, 0xDA, 0x9D,
+ 0xD5, 0xC0, 0xA5, 0x9F, 0xCA, 0x6C, 0xAC, 0x6D, 0x19, 0x38, 0x35, 0x35, 0x8A, 0xED, 0xE1, 0x2E,
+ 0xFC, 0x36, 0xFB, 0xB7, 0x5A, 0x07, 0x0B, 0x60, 0xD6, 0x96, 0x90, 0x18, 0xDB, 0x4E, 0x24, 0xCC,
+ 0xD8, 0x6E, 0xD5, 0x5E, 0xF7, 0x55, 0x87, 0xBE, 0x94, 0xB0, 0xA9, 0x64, 0x07, 0x2A, 0x30, 0xB2,
+ 0x4E, 0x40, 0x7A, 0xAD, 0x9B, 0x77, 0xD9, 0xFA, 0x52, 0xFA, 0x7A, 0xEA, 0xA4, 0xC5, 0x89, 0x30,
+ 0x9F, 0x65, 0x77, 0x46, 0x57, 0x3F, 0xBE, 0x17, 0x74, 0xF5, 0xDB, 0xE0, 0xA6, 0x35, 0x66, 0xFA,
+ 0x87, 0xD1, 0x63, 0x8B, 0xAF, 0xFD, 0xA6, 0x0D, 0x7A, 0x07, 0xD4, 0x43, 0x81, 0xEB, 0x40, 0xEF,
+ 0x9E, 0x49, 0x12, 0x38, 0xD0, 0x7D, 0x23, 0x62, 0x2C, 0x55, 0x2C, 0xE8, 0x8A, 0x71, 0x47, 0x1F,
+ 0x9F, 0xB3, 0xA2, 0x47, 0x1B, 0x09, 0x05, 0x55, 0xA3, 0xC2, 0xC3, 0x16, 0x88, 0x95, 0x7E, 0x67,
+ 0x0C, 0xB1, 0x8A, 0xCB, 0x04, 0x10, 0xAB, 0xAB, 0x0F, 0xC3, 0xFC, 0x2B, 0x39, 0x7D, 0xAD, 0x20,
+ 0x56, 0x57, 0x1F, 0x26, 0x1B, 0x40, 0xB1, 0x16, 0x20, 0xB0, 0x1A, 0xF1, 0x5D, 0xF8, 0x8D, 0x48,
+ 0x5B, 0xD3, 0xF1, 0x2E, 0x68, 0x60, 0x23, 0x5E, 0x46, 0x18, 0x1C, 0xFD, 0xA6, 0x18, 0x83, 0xC3,
+ 0x59, 0x2E, 0x06, 0x87, 0x93, 0x43, 0x0C, 0x0E, 0x67, 0xA4, 0x31, 0x38, 0x9C, 0xED, 0x62, 0x70,
+ 0xDC, 0x8C, 0x18, 0x83, 0xC3, 0x99, 0x49, 0x0C, 0x0E, 0x67, 0x77, 0x61, 0x70, 0xB8, 0x48, 0x1A,
+ 0x83, 0xC3, 0x45, 0x2C, 0x06, 0xE7, 0x6B, 0xC6, 0xE0, 0x3C, 0x96, 0xB7, 0x5D, 0x28, 0x93, 0x67,
+ 0x1D, 0x28, 0x13, 0xDD, 0x6A, 0x17, 0xCA, 0xE4, 0xE5, 0x7A, 0x94, 0xC9, 0x77, 0xB2, 0xCD, 0xF6,
+ 0xED, 0x1B, 0x7A, 0xAD, 0x4A, 0x0E, 0x29, 0xA6, 0x68, 0x6C, 0x06, 0x0F, 0x4C, 0xDF, 0xAB, 0x15,
+ 0x4B, 0x25, 0xC2, 0x1F, 0x44, 0x82, 0x35, 0x7A, 0x89, 0x6B, 0x7C, 0x0E, 0xF8, 0x58, 0x58, 0x0B,
+ 0x3B, 0x9F, 0x62, 0x84, 0x7D, 0x8A, 0x9B, 0x60, 0x4E, 0x4F, 0x35, 0x90, 0xFD, 0x43, 0x76, 0x73,
+ 0xB7, 0xEB, 0x69, 0xC3, 0xB9, 0x8A, 0x68, 0x57, 0x16, 0xF1, 0x17, 0xA4, 0xE2, 0x1D, 0xAB, 0x60,
+ 0xFE, 0x65, 0xCA, 0x2C, 0x9B, 0x89, 0x0D, 0xFA, 0x83, 0x74, 0xA0, 0xDE, 0x94, 0x7D, 0x0C, 0xF7,
+ 0x77, 0x33, 0xE6, 0xE8, 0x77, 0xAE, 0x35, 0xE6, 0x84, 0x9C, 0xD7, 0x6B, 0xB7, 0xAA, 0xE5, 0x11,
+ 0x6D, 0x15, 0xDD, 0x0D, 0xB8, 0x4A, 0xBF, 0xEF, 0xF5, 0xFD, 0x9B, 0xFB, 0x5D, 0xDF, 0xEF, 0xB8,
+ 0x7A, 0x7E, 0xFD, 0xDF, 0xAF, 0x9E, 0x5F, 0xDB, 0xD5, 0xF3, 0x33, 0x0E, 0xAC, 0xBA, 0xB7, 0x2B,
+ 0x54, 0x5D, 0x0D, 0x51, 0x36, 0xE1, 0x66, 0xD0, 0x99, 0xC3, 0xF7, 0x5C, 0xCC, 0x68, 0xB0, 0xA6,
+ 0xB0, 0xEE, 0xAC, 0x29, 0x1E, 0xA3, 0xB0, 0xAD, 0xE0, 0x7C, 0xBA, 0xC9, 0xD5, 0x70, 0x75, 0xC3,
+ 0xD2, 0x38, 0x52, 0x1D, 0x7E, 0x7A, 0x74, 0xF0, 0x32, 0x02, 0x39, 0x99, 0x5B, 0x4B, 0x13, 0x75,
+ 0xEB, 0x21, 0xF7, 0x32, 0xFB, 0x41, 0x08, 0x7F, 0x30, 0xA6, 0x9B, 0xE2, 0xD5, 0xFB, 0x81, 0x79,
+ 0x91, 0x3E, 0xA7, 0x19, 0x60, 0xC8, 0xA6, 0x6D, 0xBC, 0x76, 0x23, 0x06, 0x6D, 0x25, 0x66, 0xC0,
+ 0x20, 0xDB, 0x78, 0xAD, 0xE7, 0x9A, 0xC2, 0xED, 0xB4, 0x15, 0x0C, 0xA2, 0xF2, 0xBC, 0xD2, 0x3D,
+ 0x50, 0x74, 0xAD, 0xE0, 0xDE, 0x82, 0x4E, 0xC0, 0xE2, 0x9C, 0xC0, 0xFB, 0x37, 0x4A, 0x76, 0x01,
+ 0x8F, 0xAE, 0x23, 0x8C, 0x62, 0x57, 0xDB, 0xC5, 0x62, 0xEA, 0x89, 0xA1, 0x7A, 0xC4, 0xC5, 0x4C,
+ 0xFD, 0xAA, 0xAE, 0xFF, 0x74, 0x3D, 0x06, 0x5D, 0x2F, 0x17, 0x97, 0xB2, 0xAA, 0xAC, 0x3C, 0x05,
+ 0x5C, 0xF2, 0x52, 0x56, 0xE8, 0x1A, 0xC3, 0xCD, 0x5F, 0x87, 0xA9, 0xFC, 0xAC, 0x63, 0xB5, 0xE3,
+ 0xD7, 0x36, 0xA6, 0x71, 0x9E, 0x10, 0xA9, 0x01, 0x2F, 0x52, 0xA9, 0x3B, 0x6A, 0x9C, 0x27, 0xE6,
+ 0x0C, 0x22, 0x1F, 0xDA, 0x60, 0x5E, 0x60, 0x1B, 0x25, 0xAE, 0x8D, 0xBC, 0xD3, 0x28, 0x2E, 0x9D,
+ 0xC8, 0x3C, 0x91, 0x2A, 0xAA, 0x82, 0x5B, 0x70, 0x30, 0x66, 0xD0, 0xFF, 0x63, 0xE3, 0x7C, 0x6C,
+ 0x94, 0xE5, 0xBB, 0xE3, 0xBE, 0x03, 0xE5, 0x0D, 0x08, 0xB7, 0xE5, 0xA4, 0x02, 0x4D, 0xF5, 0x25,
+ 0x33, 0x9F, 0xFD, 0xD4, 0x41, 0x91, 0xB4, 0x38, 0x93, 0x45, 0x31, 0x47, 0x1E, 0x5D, 0x16, 0x8B,
+ 0x09, 0x2F, 0x04, 0x8B, 0x8C, 0xD0, 0xD2, 0x28, 0x22, 0x96, 0x2C, 0x12, 0x97, 0xE9, 0xA0, 0x74,
+ 0x23, 0x8D, 0x69, 0x23, 0x3C, 0x37, 0x2E, 0xAB, 0x04, 0xE8, 0x36, 0x67, 0xB5, 0x67, 0xA7, 0x67,
+ 0xE5, 0x39, 0xB4, 0x02, 0xFB, 0xCB, 0xEB, 0x07, 0x95, 0x58, 0xD8, 0x08, 0xF1, 0x04, 0xC7, 0xCE,
+ 0xDC, 0x5B, 0x50, 0xD9, 0x4E, 0xB5, 0x58, 0xD6, 0x23, 0x9A, 0xA5, 0xF0, 0x08, 0xA7, 0xD5, 0xDB,
+ 0x12, 0xFE, 0xE7, 0x6F, 0xF2, 0x3D, 0x0A, 0xED, 0xC0, 0x6D, 0x2F, 0x48, 0xC6, 0x42, 0x2E, 0xA5,
+ 0x38, 0x63, 0xEF, 0x3C, 0x6B, 0xDC, 0x7B, 0xCE, 0x84, 0xE2, 0x48, 0xB3, 0xAE, 0xCF, 0xB4, 0x11,
+ 0x10, 0x32, 0x30, 0x5C, 0xB0, 0xF0, 0xB4, 0xE1, 0xFB, 0xB4, 0x95, 0xE1, 0xB3, 0x3B, 0xDD, 0x6F,
+ 0x76, 0x0C, 0x0E, 0xF4, 0x29, 0x90, 0xB0, 0x64, 0x5F, 0x67, 0x74, 0xE0, 0x7B, 0x5B, 0xBE, 0x46,
+ 0xAF, 0x3F, 0x26, 0x71, 0x5C, 0xA2, 0x37, 0x9E, 0x02, 0xD6, 0x22, 0xB7, 0xF7, 0x26, 0x77, 0xA7,
+ 0x7C, 0x5D, 0x94, 0xFC, 0x58, 0x0C, 0xDF, 0x1B, 0x3E, 0x28, 0xCD, 0x10, 0xCC, 0xDC, 0x3A, 0xA2,
+ 0x06, 0xE1, 0x4B, 0xBB, 0x71, 0x97, 0x2B, 0x79, 0xD6, 0xA5, 0x27, 0x8F, 0x9E, 0x28, 0xA9, 0x39,
+ 0x04, 0xB1, 0x69, 0xE4, 0xCB, 0x23, 0x25, 0xCB, 0x53, 0xF9, 0x95, 0x79, 0x47, 0x13, 0xF3, 0x6E,
+ 0xFA, 0xA5, 0xB1, 0x57, 0x9E, 0xD5, 0x8A, 0x62, 0x70, 0xBC, 0x99, 0xFE, 0x31, 0xF2, 0x8C, 0x30,
+ 0x5E, 0x21, 0x11, 0xA1, 0x80, 0x52, 0x4D, 0xB9, 0x91, 0xF2, 0x7A, 0x74, 0x83, 0xBF, 0xDD, 0x0D,
+ 0xF6, 0x40, 0x15, 0x93, 0x9F, 0xDB, 0x36, 0x77, 0xF4, 0x6C, 0x69, 0xAC, 0xEC, 0xE3, 0xF9, 0x96,
+ 0x92, 0x11, 0x0F, 0x3C, 0xE9, 0xA2, 0xA6, 0x81, 0x90, 0xAD, 0x22, 0xA0, 0x2C, 0x12, 0xE5, 0x1F,
+ 0xC8, 0x6D, 0x4A, 0x8D, 0xBE, 0x97, 0x26, 0x72, 0x2C, 0x60, 0x94, 0x36, 0x13, 0x0A, 0xEC, 0x87,
+ 0x45, 0xC9, 0x5D, 0xF5, 0x26, 0xDF, 0x99, 0xF7, 0xA4, 0x95, 0xA0, 0xED, 0xBD, 0xF3, 0x71, 0xBC,
+ 0xC8, 0x28, 0x60, 0x86, 0x93, 0x8E, 0x30, 0x9B, 0x9A, 0xD1, 0xDA, 0x71, 0x0D, 0x28, 0xFD, 0x89,
+ 0x27, 0x03, 0x60, 0xF2, 0x43, 0x5A, 0x90, 0xF2, 0x70, 0x97, 0xE1, 0x35, 0xE9, 0x85, 0xCD, 0x01,
+ 0x74, 0x93, 0x9D, 0x76, 0xC4, 0x77, 0x0C, 0x65, 0xC1, 0xCD, 0xF1, 0x07, 0x8C, 0xE5, 0xF6, 0xF6,
+ 0x38, 0x4B, 0x7D, 0x42, 0xF0, 0xF5, 0x12, 0xFB, 0xF3, 0xD0, 0x69, 0xC3, 0x24, 0x5A, 0xBD, 0x2A,
+ 0xCC, 0x7B, 0xB0, 0x87, 0x37, 0xC0, 0x3F, 0x89, 0x53, 0xB3, 0x88, 0x36, 0xE1, 0xAB, 0x40, 0xCB,
+ 0xE2, 0x4C, 0x50, 0x6E, 0x12, 0x9F, 0x2B, 0x85, 0xFF, 0x99, 0x8C, 0x92, 0x70, 0xD7, 0x85, 0x04,
+ 0x94, 0x00, 0x15, 0x95, 0x86, 0x2F, 0x82, 0xAB, 0xD6, 0x54, 0x94, 0xBB, 0x73, 0x41, 0x79, 0x78,
+ 0xB1, 0xC8, 0xA4, 0xBC, 0xF2, 0x7D, 0x96, 0x96, 0x3B, 0x3F, 0x2F, 0xCA, 0x39, 0xB5, 0x07, 0x3A,
+ 0x71, 0xF2, 0xD4, 0xC2, 0x53, 0x52, 0x86, 0xD9, 0xC4, 0xDB, 0x89, 0xD8, 0x4C, 0x13, 0xCB, 0x7F,
+ 0x9E, 0x08, 0xC2, 0x35, 0xCC, 0xA2, 0x0D, 0x85, 0xFA, 0xF4, 0x21, 0x74, 0xA6, 0x2D, 0x27, 0xCB,
+ 0x47, 0xF3, 0xD6, 0x5A, 0xC6, 0xAF, 0x43, 0x35, 0x9A, 0x0B, 0x0A, 0xA2, 0x29, 0x5C, 0xAD, 0x6B,
+ 0xE8, 0x68, 0xED, 0x36, 0xC4, 0x30, 0xFA, 0x56, 0x62, 0xDD, 0x2D, 0x52, 0xD2, 0xFC, 0xD3, 0xE2,
+ 0xBA, 0xB5, 0x57, 0xC2, 0x6F, 0x8D, 0xEB, 0xF2, 0xFB, 0x18, 0xC9, 0xE5, 0xBD, 0xB3, 0xEF, 0x1B,
+ 0x1A, 0xF8, 0xC7, 0x01, 0xE2, 0xA5, 0x97, 0x41, 0xC3, 0x4C, 0xDE, 0x72, 0x98, 0x74, 0x18, 0x6A,
+ 0x18, 0xEA, 0x36, 0xAD, 0x67, 0xB8, 0x2B, 0x7B, 0x6A, 0xF8, 0xB9, 0x5F, 0xDC, 0xFB, 0xD4, 0x4E,
+ 0xC8, 0x16, 0xBC, 0xFB, 0x86, 0xE8, 0xE9, 0x7F, 0xBD, 0x21, 0x8A, 0x7D, 0x04, 0xB8, 0x54, 0x0E,
+ 0x7C, 0x04, 0xDC, 0x46, 0xE1, 0x18, 0xA1, 0x4C, 0xA1, 0x57, 0x6C, 0xF0, 0xC2, 0xE0, 0xD1, 0x0D,
+ 0x7B, 0xF3, 0x54, 0x1D, 0x78, 0xA3, 0x57, 0x80, 0xA7, 0x36, 0x1E, 0x56, 0x5D, 0x3A, 0x7B, 0xA1,
+ 0xF6, 0xAB, 0x48, 0xD3, 0x8B, 0xA1, 0x73, 0xD5, 0xE3, 0x9E, 0x9D, 0x0A, 0x6F, 0x6D, 0xAD, 0xF7,
+ 0xAF, 0x68, 0x06, 0xD9, 0xA0, 0x7C, 0x49, 0x10, 0x5B, 0x59, 0x55, 0xC5, 0xD7, 0x35, 0xD2, 0xE7,
+ 0xF2, 0xA9, 0x39, 0x4B, 0x51, 0x15, 0x19, 0x1D, 0x36, 0x3C, 0x7D, 0x4E, 0xA1, 0x18, 0xED, 0xAD,
+ 0x6B, 0x38, 0x37, 0x3D, 0x0A, 0x92, 0x26, 0xC3, 0xBC, 0xBA, 0x5C, 0x5C, 0xCB, 0xC1, 0xC3, 0xC1,
+ 0xAC, 0x84, 0xFF, 0xED, 0xE8, 0xEE, 0xE5, 0x43, 0xF3, 0x6B, 0x2C, 0x4D, 0xB8, 0xCC, 0x52, 0xCC,
+ 0xD1, 0x32, 0x20, 0xCF, 0x24, 0x2F, 0x75, 0x8D, 0x51, 0xAE, 0xE5, 0x84, 0xE7, 0x9B, 0x64, 0xAC,
+ 0x00, 0xD2, 0x5B, 0x74, 0x46, 0x69, 0x67, 0x0D, 0x7C, 0xAD, 0xEB, 0xAE, 0x54, 0x63, 0x32, 0xE9,
+ 0xB4, 0x4B, 0xDB, 0x26, 0x16, 0x5B, 0x5B, 0x8B, 0x76, 0x78, 0x75, 0xBC, 0xE0, 0xF0, 0x3B, 0xE7,
+ 0x7C, 0xB9, 0xC3, 0x77, 0x50, 0xC5, 0x0E, 0xE6, 0x7A, 0x7E, 0x5E, 0x0F, 0x5F, 0xC5, 0x2D, 0xDF,
+ 0x05, 0xE7, 0x41, 0x93, 0xA0, 0x93, 0xA5, 0x28, 0x5D, 0x7F, 0x3C, 0xCB, 0x2C, 0x07, 0xB9, 0xE4,
+ 0x2E, 0x8C, 0xA2, 0x14, 0x96, 0x03, 0xAA, 0x62, 0x44, 0x61, 0x39, 0x81, 0x0D, 0x80, 0x16, 0xEC,
+ 0x29, 0x00, 0x3C, 0x20, 0x1C, 0x05, 0x49, 0x13, 0x96, 0xB8, 0xF0, 0x0E, 0x3B, 0xF6, 0x36, 0x12,
+ 0x52, 0x0B, 0x97, 0x20, 0x86, 0x10, 0xB1, 0xB6, 0x8B, 0x02, 0x2F, 0xC9, 0xC0, 0xBF, 0x1D, 0xE8,
+ 0x6D, 0xA1, 0x28, 0xA9, 0xD7, 0x75, 0xEB, 0x4D, 0x74, 0x9F, 0x62, 0x6B, 0x52, 0xD7, 0x2B, 0xA4,
+ 0x3A, 0xB4, 0xB2, 0x98, 0x8C, 0xAA, 0x48, 0x3A, 0x76, 0xFA, 0x10, 0x5B, 0x0A, 0x49, 0x9E, 0x9E,
+ 0x88, 0x1A, 0x51, 0xE3, 0x4C, 0x17, 0x60, 0xB2, 0x12, 0xFE, 0xDF, 0x21, 0x7A, 0x2D, 0x1E, 0x9D,
+ 0x97, 0x68, 0xFE, 0xF6, 0x30, 0x24, 0xB0, 0xAE, 0xE4, 0x8D, 0x09, 0x39, 0xEF, 0x97, 0x1D, 0x6F,
+ 0x10, 0x24, 0x83, 0x0A, 0xA4, 0xFE, 0x4F, 0xB1, 0x43, 0x35, 0x50, 0x84, 0xCA, 0x14, 0x84, 0x43,
+ 0xAD, 0x36, 0x18, 0x6B, 0x1E, 0x23, 0xB5, 0xC8, 0x60, 0xE9, 0x8B, 0x1B, 0x97, 0x2D, 0x8D, 0xCB,
+ 0xEE, 0xC6, 0x11, 0x89, 0xF1, 0x23, 0x6E, 0x93, 0x65, 0xC7, 0x48, 0x42, 0x2B, 0x2D, 0xD7, 0x62,
+ 0x66, 0x12, 0xB9, 0x4E, 0x06, 0xC1, 0xCB, 0x2B, 0x60, 0xB8, 0xE7, 0x4F, 0xB0, 0x57, 0x95, 0xC3,
+ 0x83, 0xA3, 0x0A, 0x1E, 0x1D, 0xBA, 0x89, 0x56, 0xFE, 0x09, 0x22, 0x91, 0xB2, 0x80, 0x93, 0x49,
+ 0x4D, 0x09, 0x03, 0x97, 0x19, 0xB6, 0x8D, 0xB5, 0xAB, 0xB2, 0xA7, 0x0E, 0x21, 0x64, 0x77, 0xC8,
+ 0x7F, 0xA0, 0x54, 0x03, 0x4C, 0x3F, 0x1F, 0xDD, 0x79, 0xB1, 0xAE, 0xCB, 0xD6, 0xC5, 0xFA, 0x5F,
+ 0x5B, 0xAA, 0x13, 0xDE, 0x92, 0x80, 0x91, 0x3A, 0xFD, 0x24, 0x41, 0x3E, 0x68, 0xC7, 0x83, 0x28,
+ 0xFD, 0xBA, 0x9C, 0xCD, 0x92, 0x19, 0xB8, 0x52, 0x45, 0xCE, 0x42, 0x7B, 0x39, 0x50, 0xAA, 0x5B,
+ 0x1D, 0x28, 0xAD, 0x56, 0x5D, 0xD4, 0x33, 0x80, 0x1B, 0xF2, 0x1F, 0x54, 0x97, 0x76, 0x72, 0x97,
+ 0x69, 0xEF, 0x1F, 0x68, 0xED, 0xC2, 0x75, 0xCB, 0x49, 0x22, 0x6C, 0x82, 0x35, 0xD0, 0x36, 0xA2,
+ 0x2C, 0x7D, 0xBF, 0x10, 0xB6, 0x0E, 0xFC, 0xD6, 0x85, 0x3F, 0x92, 0x1F, 0x1A, 0xF3, 0xEC, 0xBC,
+ 0xDC, 0xC0, 0x3C, 0xDB, 0x6D, 0x56, 0xB5, 0x70, 0x18, 0xD7, 0x5A, 0x09, 0x20, 0x90, 0x2F, 0xF0,
+ 0x60, 0x8D, 0x78, 0x6E, 0x4A, 0x34, 0x05, 0xCC, 0x06, 0x67, 0x3E, 0xD3, 0xCE, 0xC1, 0xFC, 0xFC,
+ 0xBE, 0x77, 0xBB, 0xFD, 0x73, 0x31, 0x59, 0x76, 0x9A, 0x2A, 0xCB, 0xB2, 0xEB, 0xFA, 0xF7, 0xB4,
+ 0xAA, 0xAD, 0xD6, 0xB5, 0x7E, 0x95, 0xA7, 0xE2, 0x1D, 0xAE, 0x38, 0x1D, 0xB8, 0x51, 0xD7, 0xA5,
+ 0x4D, 0xB7, 0xF9, 0x5E, 0xA1, 0xCB, 0x12, 0x42, 0xFB, 0xAA, 0xA4, 0x62, 0x9B, 0x07, 0x4A, 0xC0,
+ 0x09, 0xDB, 0xDB, 0x43, 0x63, 0xF8, 0x5E, 0xD3, 0x44, 0x5F, 0xB7, 0x9A, 0x3D, 0x34, 0x0C, 0xA4,
+ 0xA9, 0x6D, 0xED, 0xCD, 0xF4, 0xCF, 0x0B, 0x79, 0x7C, 0x79, 0x25, 0x5F, 0xDF, 0xCC, 0x54, 0x50,
+ 0xEB, 0xC8, 0x6F, 0x62, 0x9A, 0xA8, 0xEC, 0x55, 0x73, 0xBD, 0x9E, 0xAB, 0xFA, 0xD6, 0xC7, 0xE1,
+ 0xE6, 0xDA, 0x01, 0xEF, 0xE9, 0x1F, 0x73, 0x03, 0x3A, 0x03, 0xF5, 0xF4, 0xE0, 0xF6, 0x72, 0x30,
+ 0xA9, 0x7A, 0xD8, 0x9B, 0x48, 0xAE, 0x21, 0x36, 0xC9, 0xB2, 0xFD, 0xA9, 0xD7, 0x93, 0x00, 0xBE,
+ 0x0D, 0xB7, 0xD3, 0x2D, 0x65, 0x59, 0xDE, 0x8B, 0xD9, 0x16, 0xA1, 0x53, 0x44, 0x96, 0x1E, 0xC0,
+ 0xA9, 0x26, 0xE6, 0xB8, 0x10, 0x7A, 0x6E, 0xE4, 0x28, 0xF0, 0x4B, 0xAB, 0x6C, 0x5B, 0xAD, 0xBA,
+ 0x24, 0x9B, 0xE6, 0x96, 0x76, 0xDC, 0xA0, 0x87, 0x78, 0xF6, 0x5E, 0x4E, 0x82, 0x3B, 0xC3, 0xD3,
+ 0xD7, 0xC5, 0xDC, 0x69, 0x51, 0x8B, 0xE9, 0xDF, 0x4B, 0x3C, 0xF1, 0x54, 0xE0, 0x49, 0x58, 0xB5,
+ 0xA7, 0x95, 0x74, 0xAE, 0x46, 0xE6, 0xFC, 0x0C, 0x2F, 0x1A, 0xE6, 0xED, 0x82, 0x59, 0x8E, 0x2F,
+ 0x60, 0x89, 0xFA, 0x65, 0x8C, 0x15, 0x2C, 0x28, 0x36, 0xE7, 0x27, 0xAA, 0xDE, 0x29, 0xBA, 0x65,
+ 0x03, 0xCB, 0xBE, 0x0C, 0x1C, 0x35, 0xC4, 0xDC, 0x65, 0xF7, 0x92, 0x01, 0x86, 0x73, 0x1C, 0x86,
+ 0x1E, 0x29, 0xDB, 0x62, 0x8E, 0x90, 0xCF, 0xB7, 0x76, 0x51, 0x01, 0x4B, 0xC7, 0xE6, 0x7E, 0x32,
+ 0x89, 0x57, 0x92, 0x3E, 0x32, 0x29, 0xA7, 0xB7, 0x7F, 0x4C, 0x4B, 0x7A, 0x6E, 0xD4, 0x82, 0xE1,
+ 0xA3, 0xE1, 0x7B, 0x77, 0xB7, 0xB3, 0x95, 0x9B, 0xBA, 0x57, 0x23, 0x7B, 0xE9, 0x46, 0xCC, 0x20,
+ 0xF6, 0x6A, 0x63, 0xB7, 0xAB, 0x8D, 0x0D, 0x3A, 0xD2, 0xF8, 0x83, 0xDD, 0xED, 0x71, 0x23, 0x56,
+ 0x1C, 0xEE, 0xAA, 0xAB, 0xCE, 0xCB, 0xFF, 0xCA, 0xB0, 0xE4, 0xF8, 0xF7, 0xD4, 0x3D, 0xFD, 0x61,
+ 0x34, 0x87, 0x03, 0x9F, 0xDB, 0x47, 0x33, 0xB9, 0xAC, 0xF3, 0x6F, 0xC5, 0xA7, 0x37, 0x75, 0x0D,
+ 0x3A, 0xD9, 0xAF, 0x62, 0x7F, 0xBA, 0x24, 0xB4, 0x51, 0xFE, 0xBD, 0xD8, 0x07, 0x03, 0xD2, 0xF4,
+ 0xBA, 0x92, 0xB9, 0xAC, 0xC5, 0x81, 0xDE, 0x09, 0xE4, 0x8F, 0xA4, 0xA0, 0x0B, 0x0B, 0xF9, 0x81,
+ 0x14, 0x47, 0xC6, 0x2B, 0x65, 0xFE, 0xA5, 0x14, 0x5F, 0x29, 0x7C, 0x46, 0xFE, 0xB5, 0x14, 0xEA,
+ 0x74, 0xE1, 0xF8, 0xFA, 0xCF, 0xFC, 0xA9, 0x14, 0x27, 0xD3, 0x8B, 0xBC, 0x2E, 0xC5, 0x09, 0x0E,
+ 0x4F, 0x3E, 0x87, 0x1F, 0x0A, 0x83, 0x96, 0x3F, 0x96, 0xD8, 0x9F, 0xF1, 0x3B, 0x1F, 0x7C, 0xF0,
+ 0xDE, 0x00, 0xC6, 0x70, 0x79, 0x29, 0xBF, 0x98, 0x92, 0x2B, 0xAC, 0xD3, 0x17, 0xCF, 0x8B, 0x0B,
+ 0xE3, 0x15, 0x68, 0xE7, 0xE2, 0x06, 0xB1, 0x8B, 0x3B, 0x6F, 0x80, 0x53, 0x7E, 0xAE, 0xD0, 0xE4,
+ 0xFC, 0x37, 0x2B, 0x75, 0xB8, 0x40, 0x97, 0x33, 0x01, 0x00
+}; //bootsrap.bundle.min.jss
+
+//Content of bootstrap.min.css with gzip compression
+static const uint8_t bootstrap_min_css[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xDF, 0x38, 0x1D, 0x61, 0x04, 0x00, 0x62, 0x6F, 0x6F, 0x74, 0x73, 0x74,
+ 0x72, 0x61, 0x70, 0x2E, 0x6D, 0x69, 0x6E, 0x2E, 0x63, 0x73, 0x73, 0x00, 0xEC, 0x5D, 0xD9, 0xAE,
+ 0xF3, 0x34, 0x10, 0xBE, 0xE7, 0x29, 0x0A, 0x08, 0x41, 0x21, 0x29, 0x4D, 0xD2, 0xFD, 0x08, 0xC4,
+ 0x2E, 0x90, 0x80, 0x0B, 0x16, 0x09, 0x84, 0xB8, 0x48, 0x13, 0xB7, 0x0D, 0x24, 0x6D, 0x49, 0x5A,
+ 0x68, 0xA9, 0x0E, 0xE2, 0x21, 0x78, 0x00, 0x9E, 0x85, 0x47, 0xE1, 0x49, 0x18, 0x2F, 0x89, 0xF7,
+ 0xC4, 0xE9, 0xCF, 0x0E, 0xFF, 0x72, 0x4E, 0x6B, 0x8F, 0xC7, 0x33, 0xE3, 0x99, 0xCF, 0x5B, 0x62,
+ 0xBF, 0x96, 0xEC, 0xE2, 0xB2, 0x42, 0xA7, 0xC1, 0x33, 0x9F, 0x7E, 0xF2, 0x8E, 0xBF, 0x78, 0xE6,
+ 0xE1, 0xE5, 0x17, 0x9F, 0x7E, 0x6A, 0xF0, 0xE2, 0xE0, 0x8D, 0xC3, 0xE1, 0x54, 0x9D, 0xCA, 0xF8,
+ 0x38, 0xF8, 0x76, 0x3A, 0x1A, 0x8F, 0xC2, 0xC1, 0x0B, 0xBB, 0xD3, 0xE9, 0x58, 0xAD, 0x5E, 0x7E,
+ 0x79, 0x8B, 0x4E, 0xEB, 0x3A, 0x73, 0x94, 0x1C, 0x8A, 0x97, 0x87, 0x98, 0xFE, 0xCD, 0xC3, 0xF1,
+ 0x5A, 0x66, 0xDB, 0xDD, 0x69, 0x10, 0x8E, 0x83, 0xC0, 0x0F, 0xC7, 0x61, 0x30, 0xF8, 0x64, 0x87,
+ 0x04, 0x3E, 0xAF, 0x9F, 0x4F, 0xBB, 0x43, 0x59, 0x59, 0x89, 0xBF, 0xCB, 0x4E, 0x27, 0x54, 0x7A,
+ 0x83, 0xF7, 0xF6, 0xC9, 0x08, 0x13, 0xBD, 0x9F, 0x25, 0x68, 0x5F, 0xA1, 0x74, 0x70, 0xDE, 0xA7,
+ 0xA8, 0x1C, 0x7C, 0xF0, 0xDE, 0x27, 0x82, 0x0C, 0xD9, 0x69, 0x77, 0x5E, 0x93, 0xDA, 0x4F, 0xDF,
+ 0xAD, 0xAB, 0x97, 0x1B, 0x81, 0x5E, 0x5E, 0xE7, 0x87, 0xF5, 0xCB, 0x45, 0x9C, 0xED, 0x5F, 0x7E,
+ 0xFF, 0xBD, 0x37, 0xDF, 0xFE, 0xF0, 0xE3, 0xB7, 0xB1, 0x74, 0x2F, 0xAF, 0x4A, 0x20, 0xB8, 0xF9,
+ 0xFE, 0xBA, 0xF2, 0xD7, 0xF9, 0x19, 0xAD, 0x9E, 0x1D, 0xA7, 0x33, 0xB4, 0x49, 0x1F, 0x48, 0x4A,
+ 0xB6, 0x4F, 0xB3, 0xED, 0x61, 0xF5, 0xEC, 0x6C, 0x16, 0x8C, 0x37, 0x21, 0x4D, 0x3B, 0x9E, 0xCB,
+ 0x63, 0x8E, 0x20, 0x6D, 0x33, 0x09, 0x93, 0x80, 0xA5, 0x65, 0xFB, 0xAF, 0x57, 0xCF, 0xA6, 0xB3,
+ 0x28, 0x5A, 0x4C, 0x68, 0x4A, 0x89, 0x52, 0x48, 0x48, 0xA2, 0xE9, 0x64, 0x4A, 0x13, 0x0E, 0x65,
+ 0xBC, 0xDF, 0x42, 0xB1, 0x4D, 0x3A, 0x47, 0x01, 0x23, 0xBA, 0xA2, 0x3C, 0x3F, 0x7C, 0x07, 0x69,
+ 0x9B, 0x24, 0x18, 0xCF, 0x69, 0xDA, 0xB6, 0x44, 0x68, 0xBF, 0x7A, 0x36, 0x58, 0x2E, 0xE6, 0x53,
+ 0x46, 0x76, 0x42, 0x71, 0xBE, 0x7A, 0x36, 0x1C, 0x27, 0xCB, 0x25, 0x23, 0x4A, 0xAE, 0xF1, 0x1E,
+ 0x4B, 0x9A, 0xC4, 0x9B, 0x31, 0x4D, 0xF9, 0x6E, 0x97, 0x9D, 0x10, 0xE6, 0xB4, 0xA9, 0xD9, 0xC4,
+ 0x57, 0x90, 0x31, 0x99, 0x4F, 0xE7, 0x29, 0x4F, 0xF1, 0xD3, 0xB8, 0x04, 0x41, 0xA3, 0x49, 0x14,
+ 0x4F, 0x58, 0xC1, 0x63, 0x99, 0x15, 0x71, 0x79, 0x95, 0xF5, 0xAE, 0x50, 0x72, 0xD8, 0x03, 0xAD,
+ 0xC2, 0xA2, 0x3A, 0x27, 0x09, 0xAA, 0xAA, 0x5A, 0xBA, 0xDA, 0x46, 0x9B, 0x83, 0x22, 0x4B, 0x5C,
+ 0xEE, 0xB3, 0xFD, 0x56, 0xD6, 0x2B, 0xC5, 0xEA, 0x97, 0xB2, 0x4D, 0x72, 0xDC, 0xD0, 0x40, 0xB6,
+ 0xD8, 0x2C, 0x37, 0x31, 0x49, 0x62, 0x02, 0x86, 0x41, 0x38, 0x0D, 0x97, 0x34, 0x65, 0x73, 0xD8,
+ 0x9F, 0xFC, 0x2A, 0xDE, 0x63, 0xA9, 0xCA, 0x6C, 0xB3, 0xAA, 0xAE, 0xD5, 0x09, 0x15, 0xFE, 0x39,
+ 0xF3, 0xFC, 0xF8, 0x08, 0x2D, 0xE1, 0xD3, 0x04, 0xEF, 0x99, 0x8F, 0xD1, 0xF6, 0x80, 0x06, 0x9F,
+ 0xBE, 0xF7, 0x8C, 0xF7, 0xD1, 0x61, 0x7D, 0x38, 0x1D, 0xBC, 0x67, 0xDE, 0x45, 0xF9, 0xB7, 0xE8,
+ 0x94, 0x25, 0xF1, 0xE0, 0x43, 0x74, 0x46, 0xCF, 0x78, 0xAF, 0x97, 0x59, 0x9C, 0x7B, 0xCF, 0x7C,
+ 0x08, 0x99, 0x83, 0x8F, 0x81, 0xE3, 0x33, 0xDE, 0x33, 0xEF, 0x67, 0x6B, 0x54, 0xC6, 0xA7, 0xEC,
+ 0xB0, 0x67, 0x29, 0xBC, 0x26, 0xEF, 0x99, 0xD7, 0x31, 0x7F, 0x70, 0xC9, 0xFC, 0x50, 0x0E, 0xDE,
+ 0x2E, 0x0E, 0x5F, 0x65, 0xCF, 0xF0, 0x5A, 0xF4, 0x84, 0x8F, 0xAF, 0xC5, 0xFA, 0x90, 0x3F, 0x43,
+ 0xF9, 0x4B, 0xA5, 0x04, 0x45, 0x8A, 0xC3, 0xFE, 0x50, 0x1D, 0xE3, 0x04, 0xAD, 0x3E, 0x7E, 0xE7,
+ 0x03, 0xF8, 0xEC, 0x7F, 0x84, 0xB6, 0xE7, 0x3C, 0x2E, 0xBD, 0x0F, 0xD0, 0x3E, 0x3F, 0x78, 0x90,
+ 0x14, 0x27, 0x07, 0xEF, 0xCD, 0xC3, 0xBE, 0x3A, 0xE4, 0x71, 0x25, 0xC9, 0x87, 0xC9, 0x81, 0xFB,
+ 0x9B, 0x87, 0x73, 0x99, 0xA1, 0x12, 0x54, 0xFA, 0xEE, 0x19, 0xAF, 0x61, 0xD7, 0xB4, 0x71, 0x9A,
+ 0xA1, 0xFD, 0x69, 0x95, 0x67, 0x7B, 0x14, 0x97, 0xCD, 0xF7, 0x17, 0x82, 0xC5, 0x38, 0x45, 0x5B,
+ 0x6F, 0x50, 0x6E, 0xD7, 0xF1, 0x0B, 0xE1, 0x74, 0xEA, 0x0D, 0xF8, 0x8F, 0xF1, 0x28, 0x98, 0x0E,
+ 0x2D, 0x59, 0xC3, 0xE1, 0xE3, 0x8B, 0xDE, 0x6A, 0x15, 0x6F, 0x20, 0xFE, 0xE0, 0xF7, 0x1A, 0x6D,
+ 0x0E, 0x25, 0xBA, 0xAD, 0x0F, 0x17, 0xBF, 0xCA, 0xBE, 0xC7, 0x6D, 0xBC, 0x3E, 0x94, 0x10, 0x82,
+ 0x3E, 0xA4, 0x3C, 0xBE, 0x56, 0xA0, 0x34, 0x8B, 0x07, 0x2F, 0x1C, 0x4B, 0xB4, 0x41, 0x25, 0x09,
+ 0x81, 0x73, 0x82, 0x52, 0x50, 0x19, 0x8B, 0xBF, 0x02, 0x5D, 0x69, 0x0E, 0xDA, 0x27, 0x68, 0x78,
+ 0xA3, 0x71, 0x57, 0x25, 0xE5, 0x21, 0xCF, 0xFD, 0x35, 0xDA, 0xC5, 0xDF, 0x66, 0x87, 0x72, 0x55,
+ 0x15, 0x90, 0xBA, 0x7B, 0x7C, 0x5C, 0x1F, 0xD2, 0xEB, 0x0D, 0xFC, 0x72, 0x9B, 0xED, 0x57, 0xE3,
+ 0x07, 0x62, 0xB9, 0x4D, 0x5C, 0x64, 0xF9, 0x75, 0xF5, 0x6D, 0x5C, 0xBE, 0x60, 0xF2, 0x8B, 0x21,
+ 0xA1, 0xC2, 0x62, 0xA1, 0x55, 0x50, 0xA2, 0x82, 0x7E, 0xFD, 0x0E, 0x11, 0x17, 0x9B, 0x8C, 0xC7,
+ 0x0F, 0xD8, 0x24, 0xFE, 0x8E, 0x7E, 0x0F, 0x46, 0xD3, 0x87, 0x04, 0x37, 0x50, 0xE3, 0x6A, 0xEB,
+ 0x38, 0xF9, 0x7A, 0x5B, 0x1E, 0x00, 0x50, 0x7C, 0x92, 0x41, 0x43, 0x09, 0xCA, 0xAF, 0xBF, 0xCE,
+ 0x4E, 0x10, 0x81, 0x17, 0xCA, 0xDA, 0x8F, 0xD3, 0xAF, 0xCE, 0x15, 0x30, 0x18, 0x8F, 0x9F, 0xE3,
+ 0xB9, 0xF1, 0xD1, 0xDF, 0x01, 0x5F, 0xE2, 0xCE, 0xAC, 0xF8, 0x09, 0xC2, 0x1D, 0x1A, 0x06, 0x94,
+ 0x3D, 0x3D, 0xEE, 0x4A, 0xA6, 0x0A, 0x11, 0x6C, 0x30, 0x66, 0x55, 0x67, 0xFB, 0x1D, 0x08, 0x7E,
+ 0xD2, 0xAB, 0x4E, 0xCE, 0x25, 0x2E, 0x47, 0x3C, 0xE8, 0x81, 0x1A, 0x18, 0x8C, 0x70, 0x80, 0x66,
+ 0xCE, 0x4E, 0xD7, 0xD5, 0x28, 0x9C, 0x02, 0x47, 0x30, 0xE7, 0xE9, 0x85, 0x2F, 0xB0, 0x48, 0x5F,
+ 0x0E, 0x6F, 0xB5, 0x56, 0xC7, 0xCB, 0xE3, 0x68, 0x17, 0x78, 0xA3, 0x5D, 0x08, 0xFF, 0x23, 0xF8,
+ 0x3F, 0x81, 0xFF, 0x53, 0xF8, 0x3F, 0xF3, 0x20, 0x19, 0x52, 0x21, 0x11, 0xD2, 0x20, 0x69, 0x37,
+ 0x63, 0x22, 0xF9, 0xA7, 0xC3, 0x11, 0x98, 0xB3, 0x2F, 0x10, 0x36, 0xA7, 0x43, 0xB1, 0x1A, 0x4D,
+ 0x55, 0x03, 0x4E, 0x35, 0x03, 0x86, 0xB8, 0x2A, 0x60, 0x7B, 0xE3, 0x66, 0x4F, 0xE2, 0x3C, 0x79,
+ 0x21, 0x18, 0x45, 0x73, 0x5C, 0x7C, 0xF0, 0xD2, 0x00, 0xAC, 0xFC, 0xED, 0x77, 0xC3, 0xC6, 0x2F,
+ 0x0A, 0xA8, 0xE0, 0xBB, 0x2C, 0x3D, 0xED, 0x56, 0x41, 0x38, 0x1E, 0x1F, 0x2F, 0xC3, 0x9B, 0xC6,
+ 0x21, 0x24, 0x35, 0x3F, 0x02, 0xEB, 0x10, 0xC4, 0x35, 0xB0, 0x0E, 0x19, 0xEB, 0xD1, 0xB2, 0x9D,
+ 0xB3, 0xC6, 0x20, 0x64, 0x7C, 0x23, 0xB0, 0x81, 0x81, 0x2F, 0xE3, 0x3A, 0x6B, 0xE7, 0xAA, 0x15,
+ 0x0F, 0x46, 0xF3, 0x5A, 0xE0, 0x09, 0x58, 0x56, 0x67, 0x1C, 0xCE, 0x6B, 0x81, 0xA3, 0x76, 0xD6,
+ 0x1A, 0x83, 0xA0, 0x31, 0xC5, 0x14, 0x1A, 0x4C, 0xCA, 0x08, 0x49, 0x0E, 0x69, 0xD5, 0x99, 0x98,
+ 0x81, 0x53, 0x8F, 0x2D, 0xED, 0x4A, 0x29, 0xE2, 0xF5, 0xBA, 0xFC, 0x22, 0x8D, 0x4F, 0x31, 0xED,
+ 0x95, 0x32, 0xC8, 0x8F, 0x73, 0xFF, 0x94, 0x9D, 0x72, 0xF4, 0xA5, 0x47, 0x32, 0xE9, 0xE7, 0x9B,
+ 0xE4, 0xFC, 0x29, 0xF4, 0x08, 0x14, 0x87, 0x56, 0xA4, 0xD3, 0xC5, 0xCE, 0x30, 0x48, 0x81, 0x2D,
+ 0x4A, 0x1F, 0x3A, 0x09, 0xC0, 0xA1, 0x2B, 0xF0, 0xEB, 0x1D, 0xCA, 0x8F, 0x0F, 0x16, 0xAE, 0x7E,
+ 0xF5, 0x75, 0x76, 0xF4, 0x71, 0x37, 0xBA, 0x3F, 0xEC, 0xD1, 0x43, 0x6B, 0xEE, 0x63, 0x9C, 0xA6,
+ 0x25, 0xF4, 0x43, 0x37, 0x5D, 0x3B, 0x06, 0x02, 0xA7, 0x6B, 0x8E, 0x80, 0xB4, 0x2C, 0xE2, 0x5C,
+ 0x74, 0xDB, 0x3A, 0xE4, 0x1E, 0x0F, 0xB9, 0x77, 0xCE, 0x6F, 0x47, 0xE0, 0x03, 0x18, 0xE6, 0xE7,
+ 0x68, 0x73, 0xA2, 0x0E, 0x92, 0xE6, 0x1E, 0xC9, 0xEA, 0x34, 0xE2, 0x21, 0x1F, 0x00, 0x21, 0xFC,
+ 0x3C, 0x03, 0x39, 0xF9, 0x7C, 0xC6, 0x9F, 0x15, 0x89, 0xC6, 0x8F, 0xE9, 0xE9, 0x26, 0x86, 0xD1,
+ 0x7C, 0x0C, 0x49, 0xE9, 0xCD, 0x14, 0x6D, 0x34, 0x8D, 0xC8, 0x02, 0xE5, 0x60, 0xAC, 0x92, 0x7C,
+ 0xFD, 0xCD, 0xF9, 0x70, 0x42, 0x0D, 0x0A, 0x0E, 0xC6, 0x03, 0x52, 0xF5, 0xDA, 0x83, 0xE1, 0xCC,
+ 0x61, 0xBF, 0x95, 0x18, 0x43, 0xC7, 0x03, 0x36, 0x7F, 0x1C, 0x55, 0xA0, 0x71, 0xEE, 0x91, 0x9F,
+ 0x82, 0x67, 0x8C, 0xE6, 0xF3, 0x29, 0x54, 0x31, 0x60, 0xC8, 0xB6, 0x20, 0x7F, 0x1E, 0x1E, 0x47,
+ 0xC0, 0xFA, 0x6B, 0x0F, 0xFF, 0xA8, 0x4D, 0x01, 0xE0, 0x02, 0x74, 0x06, 0x2C, 0x4C, 0x36, 0x0B,
+ 0x14, 0x3D, 0x56, 0x67, 0xA8, 0xFC, 0x7C, 0xBC, 0x1D, 0x0F, 0x55, 0x46, 0x5A, 0xBA, 0x44, 0x39,
+ 0xB4, 0xCF, 0xB7, 0xE8, 0x41, 0xA8, 0x0B, 0x57, 0x25, 0x19, 0x7D, 0xFC, 0xF0, 0x2D, 0x2A, 0x71,
+ 0x2F, 0x9C, 0xFB, 0x31, 0x40, 0xE4, 0x7E, 0xB5, 0x8E, 0x2B, 0x84, 0x09, 0x30, 0xBF, 0x1B, 0x33,
+ 0x82, 0x0F, 0x5E, 0x0D, 0xCA, 0x61, 0xEE, 0x60, 0x74, 0xF8, 0x8A, 0xBF, 0xC5, 0x37, 0x52, 0x3F,
+ 0x1B, 0x9B, 0xB4, 0x38, 0xDA, 0x63, 0xBC, 0xDA, 0x1D, 0xA0, 0x96, 0x86, 0x3E, 0x9E, 0x2E, 0x92,
+ 0x18, 0x52, 0x09, 0x4A, 0xEE, 0xA0, 0xDB, 0xF9, 0x72, 0x48, 0x3F, 0x27, 0xD0, 0xAB, 0x56, 0x5F,
+ 0x0E, 0x3D, 0x7B, 0x16, 0xE7, 0xC4, 0x31, 0x5A, 0xAD, 0x99, 0xBA, 0x61, 0x72, 0x48, 0x91, 0xF7,
+ 0xF5, 0x3A, 0xF5, 0xA0, 0x5F, 0x83, 0x71, 0x43, 0x71, 0xBC, 0x69, 0x1D, 0x95, 0xDE, 0xEF, 0x4B,
+ 0xFD, 0x14, 0x58, 0x2A, 0xCD, 0x4A, 0x94, 0x10, 0x9E, 0xF9, 0xA9, 0x7C, 0x38, 0xEF, 0x33, 0xCC,
+ 0xD5, 0x5F, 0x67, 0x69, 0xB6, 0xC2, 0x3F, 0x7C, 0x2C, 0x4C, 0x99, 0xA5, 0xE8, 0x11, 0x2A, 0xB9,
+ 0xA5, 0x59, 0x75, 0xCC, 0x61, 0x60, 0x47, 0xDC, 0xE3, 0xA1, 0xC3, 0x49, 0x1F, 0x70, 0xD1, 0x0D,
+ 0x1E, 0x60, 0xC6, 0xE7, 0xD3, 0x41, 0x6C, 0xA2, 0x05, 0x6E, 0x23, 0xCC, 0x70, 0x80, 0x2B, 0xE3,
+ 0x8E, 0xD2, 0xA8, 0x2B, 0x2B, 0xFF, 0x1D, 0x74, 0x41, 0xFE, 0xBA, 0x44, 0xF1, 0xD7, 0x2C, 0xA6,
+ 0x1E, 0x49, 0x31, 0x95, 0x21, 0x2D, 0x55, 0x0F, 0x83, 0x69, 0xA9, 0xEF, 0x60, 0xE0, 0xBD, 0x22,
+ 0x45, 0x7D, 0xFC, 0xFD, 0x31, 0x7E, 0x95, 0x14, 0x95, 0xF8, 0x3F, 0x82, 0x09, 0x05, 0x07, 0xC4,
+ 0x78, 0x39, 0x9A, 0x60, 0xF9, 0xB5, 0x0A, 0x78, 0xCF, 0xAC, 0xFA, 0x28, 0xEF, 0xC8, 0xD9, 0x80,
+ 0x04, 0x0F, 0x7C, 0xCE, 0x15, 0x65, 0x87, 0x2B, 0x18, 0x88, 0x95, 0x8C, 0x95, 0x36, 0x50, 0x43,
+ 0x74, 0x93, 0x6D, 0xCF, 0xA5, 0x1E, 0x79, 0x59, 0xB1, 0xF5, 0xAA, 0x6F, 0xB7, 0x37, 0xC5, 0x9D,
+ 0x8B, 0x2C, 0x4D, 0x73, 0xF4, 0x78, 0x8A, 0xD7, 0x39, 0x68, 0x16, 0x1F, 0x29, 0x60, 0x41, 0x93,
+ 0xAD, 0x68, 0x63, 0x30, 0x91, 0xB0, 0x9C, 0x79, 0x7C, 0xAC, 0xD0, 0xAA, 0xFE, 0xF0, 0xC8, 0x88,
+ 0x6B, 0xB9, 0x70, 0x4B, 0x32, 0x34, 0xA8, 0x53, 0x38, 0x44, 0xD4, 0xEA, 0xB3, 0x11, 0x39, 0x0D,
+ 0x06, 0x26, 0x01, 0x06, 0x8D, 0xC7, 0xD3, 0xEE, 0xC6, 0x93, 0x9A, 0xB6, 0x13, 0x92, 0x6A, 0xE4,
+ 0x2D, 0xE2, 0x53, 0xB2, 0xF3, 0xD9, 0xD8, 0xE4, 0x84, 0x47, 0x5A, 0xDE, 0x29, 0xF5, 0x4E, 0x1B,
+ 0x18, 0x79, 0x79, 0xA7, 0x1D, 0xFC, 0x43, 0x31, 0x7C, 0x2D, 0x6F, 0x4C, 0x6C, 0xD9, 0x17, 0x68,
+ 0x22, 0x43, 0x58, 0x18, 0xA3, 0x66, 0x29, 0x4B, 0x62, 0xFD, 0xD9, 0xF8, 0x31, 0x8F, 0xD7, 0x28,
+ 0xAF, 0x3D, 0x15, 0xCA, 0x11, 0x28, 0x20, 0x0E, 0xFB, 0xB8, 0x3E, 0x9F, 0x4E, 0xA0, 0xAE, 0xDC,
+ 0x44, 0x63, 0x96, 0xBC, 0xDA, 0x1C, 0x12, 0xF8, 0x8A, 0x43, 0x91, 0x7E, 0xF4, 0xBF, 0xCD, 0xAA,
+ 0x0C, 0x6C, 0x3A, 0xBC, 0x1D, 0xCE, 0x27, 0xCC, 0xA5, 0x21, 0xF5, 0xB2, 0xFD, 0xF1, 0x7C, 0xF2,
+ 0x0E, 0xC7, 0x13, 0x76, 0x81, 0xA3, 0x07, 0x70, 0x02, 0x31, 0xE4, 0x61, 0x5D, 0x41, 0xAB, 0xD8,
+ 0x38, 0x70, 0x6C, 0x34, 0xD0, 0xBC, 0xDD, 0xD4, 0x43, 0xB0, 0x8A, 0x18, 0x67, 0x6A, 0x58, 0x32,
+ 0xA4, 0xDB, 0x40, 0x00, 0xD0, 0xE8, 0xFF, 0x02, 0x46, 0xAE, 0xE8, 0x15, 0x4A, 0xF7, 0xE5, 0x8D,
+ 0x75, 0x71, 0xC7, 0x43, 0xB6, 0x3F, 0x01, 0x10, 0xB3, 0x72, 0x4D, 0x0C, 0xB0, 0xC0, 0x61, 0xE9,
+ 0x2B, 0xB0, 0x0E, 0xF6, 0x96, 0xF4, 0x56, 0x0F, 0xEA, 0x82, 0xC7, 0x2F, 0xF2, 0xAC, 0x3A, 0x7D,
+ 0xB9, 0x6A, 0x5A, 0x09, 0xFC, 0x0B, 0xE1, 0x29, 0x18, 0xCC, 0x2D, 0x93, 0xAF, 0x51, 0x49, 0xA6,
+ 0xA2, 0x49, 0x7C, 0x3A, 0x94, 0xB5, 0x69, 0x99, 0x14, 0xA7, 0xEB, 0xB1, 0x91, 0xC2, 0xA3, 0xDF,
+ 0xA0, 0x6F, 0x44, 0xA7, 0xFA, 0x0B, 0x60, 0x6C, 0x91, 0xC1, 0x37, 0x66, 0x7B, 0xC6, 0x1E, 0x4F,
+ 0x9D, 0x50, 0x0C, 0x0A, 0xC1, 0x44, 0x84, 0xE6, 0xC8, 0x9C, 0x48, 0x33, 0x34, 0x62, 0x0E, 0x45,
+ 0xC6, 0x7A, 0x1E, 0xAF, 0x47, 0xCB, 0x64, 0xD5, 0x2A, 0xA9, 0xAA, 0xB5, 0x40, 0xEB, 0xE2, 0xF0,
+ 0xBD, 0x4F, 0x5A, 0x1D, 0x14, 0xDD, 0xA3, 0x92, 0x87, 0xAA, 0xE4, 0x71, 0x54, 0xE7, 0xA6, 0x9D,
+ 0x4B, 0x44, 0xDA, 0xB1, 0x8E, 0x47, 0x88, 0x5B, 0x94, 0xA7, 0x20, 0xE2, 0x8D, 0x8F, 0xAF, 0xC6,
+ 0x75, 0x28, 0x35, 0x10, 0xD9, 0xB0, 0xC4, 0xBE, 0x8A, 0xB6, 0x60, 0xE4, 0x1B, 0x80, 0x64, 0x7C,
+ 0x22, 0x51, 0xF4, 0x40, 0x4A, 0x91, 0xE1, 0xBD, 0x56, 0x50, 0x0E, 0xC6, 0x8E, 0xA1, 0x9E, 0xC9,
+ 0xA9, 0x5A, 0x86, 0x7F, 0x4C, 0x10, 0x7D, 0xF8, 0x47, 0x33, 0x5E, 0x7A, 0xF1, 0x96, 0xE4, 0xD0,
+ 0x5E, 0x34, 0xD2, 0xB9, 0x93, 0xC0, 0x30, 0x0E, 0xE6, 0xB7, 0x05, 0xF2, 0x81, 0x2F, 0xFE, 0x76,
+ 0xF5, 0x89, 0x09, 0x3C, 0x1B, 0x05, 0x35, 0x10, 0xF1, 0xC8, 0x23, 0x2A, 0xAD, 0x64, 0x3B, 0x98,
+ 0x62, 0x76, 0x70, 0x02, 0x15, 0xCE, 0x27, 0x64, 0xCF, 0x06, 0x45, 0x76, 0x1D, 0x2C, 0x70, 0x33,
+ 0x5A, 0x33, 0xAF, 0xA0, 0x2D, 0x2D, 0xCF, 0x5D, 0x81, 0x2B, 0x4E, 0x9D, 0xC4, 0xAF, 0x8E, 0x60,
+ 0x48, 0xE6, 0xD8, 0xD4, 0xD4, 0xA4, 0xAB, 0x7B, 0x64, 0x1E, 0x09, 0x2C, 0x92, 0xDD, 0x97, 0x35,
+ 0x7A, 0xF8, 0x87, 0xCD, 0x06, 0x7C, 0x63, 0xE5, 0x87, 0xC7, 0xCB, 0x83, 0x21, 0x0C, 0xB0, 0x38,
+ 0xA4, 0x42, 0xA1, 0x1A, 0xCA, 0xA2, 0xE9, 0xF8, 0x4D, 0xF1, 0x43, 0x7D, 0x92, 0x97, 0x21, 0x78,
+ 0x09, 0xA2, 0x7D, 0x87, 0x51, 0xB6, 0xB6, 0xB4, 0xA4, 0xC3, 0x26, 0x83, 0x45, 0x0B, 0x0A, 0x04,
+ 0x40, 0x48, 0xC5, 0xC7, 0x2D, 0xCF, 0xFD, 0x84, 0x33, 0x23, 0xB4, 0xE7, 0x23, 0xF8, 0x67, 0x6A,
+ 0xA2, 0x7C, 0xB0, 0x87, 0x33, 0x68, 0x0D, 0x18, 0x69, 0xC6, 0xE1, 0x6C, 0x53, 0xC6, 0x05, 0xBA,
+ 0x35, 0x61, 0x50, 0x9D, 0x0B, 0xBC, 0x06, 0xD4, 0x10, 0x63, 0x28, 0xF2, 0xB3, 0x13, 0x78, 0xB9,
+ 0x12, 0xA8, 0xC7, 0xF2, 0xB0, 0x25, 0x23, 0x6F, 0xDB, 0x88, 0xEE, 0x8B, 0x1D, 0x74, 0x86, 0x68,
+ 0xFF, 0xA5, 0x04, 0x52, 0x4F, 0x67, 0xC5, 0xF1, 0x50, 0x9E, 0x62, 0xE8, 0x6E, 0x46, 0xE0, 0xC5,
+ 0xB2, 0x9B, 0x87, 0xDA, 0x5C, 0x33, 0x82, 0x1E, 0x78, 0xC4, 0xCA, 0xFB, 0x86, 0xF9, 0xE5, 0xAC,
+ 0x9E, 0x04, 0x4E, 0xC8, 0xFC, 0x52, 0x2E, 0xAB, 0xCF, 0x53, 0x5B, 0xA2, 0xCE, 0x58, 0x0B, 0x8D,
+ 0xBB, 0x26, 0xCB, 0x30, 0x0B, 0x9D, 0xD6, 0x91, 0x1E, 0x8D, 0x96, 0xBF, 0x93, 0x00, 0x62, 0x2D,
+ 0x93, 0x91, 0x22, 0x42, 0x64, 0x10, 0x21, 0x6C, 0x44, 0x88, 0x7E, 0x27, 0x11, 0xC4, 0x5A, 0x26,
+ 0xB2, 0x00, 0x86, 0x89, 0xED, 0xA4, 0xB6, 0x41, 0x38, 0x9A, 0xFF, 0x4E, 0x02, 0x88, 0xB5, 0x44,
+ 0xAA, 0x0D, 0xA6, 0x06, 0x11, 0xC2, 0x46, 0x84, 0xE0, 0x77, 0x12, 0x41, 0xAC, 0x25, 0x92, 0x05,
+ 0x98, 0x75, 0x2E, 0x74, 0xFC, 0x1E, 0x02, 0xCC, 0x4C, 0x8B, 0x21, 0x24, 0x1C, 0xCF, 0x7B, 0xD2,
+ 0x03, 0xA6, 0xF2, 0xAC, 0x15, 0x2A, 0xC1, 0x99, 0xBC, 0x73, 0x64, 0xD4, 0x34, 0xDC, 0xFB, 0xD0,
+ 0x92, 0x78, 0x37, 0xC2, 0x85, 0x4E, 0x46, 0xBB, 0xF3, 0x3C, 0x86, 0xD4, 0x64, 0x97, 0xE5, 0xE9,
+ 0xB0, 0x9E, 0xD1, 0x96, 0x44, 0x57, 0x2A, 0xF7, 0x28, 0xDB, 0xC3, 0x24, 0x31, 0x86, 0xB2, 0x85,
+ 0x3E, 0x55, 0x50, 0x06, 0x54, 0x67, 0x0C, 0x92, 0x49, 0x5C, 0x81, 0x48, 0x7C, 0xE6, 0x6B, 0x9A,
+ 0xDE, 0x6B, 0xC8, 0x21, 0x16, 0x78, 0x55, 0x10, 0x49, 0x9D, 0x89, 0x8B, 0x74, 0x30, 0xD0, 0x80,
+ 0x9F, 0xA5, 0x38, 0xC9, 0xF7, 0x09, 0x77, 0x4B, 0x85, 0x5C, 0x72, 0x75, 0x10, 0x6E, 0xE0, 0xDA,
+ 0x2C, 0x92, 0x26, 0x50, 0x12, 0x2F, 0xC5, 0x3E, 0xF3, 0xEB, 0x8F, 0x3F, 0xFD, 0xF2, 0xF3, 0x33,
+ 0x60, 0x8F, 0x62, 0xEB, 0x6F, 0xF2, 0x73, 0x86, 0x65, 0xBB, 0xF8, 0xC2, 0x40, 0x43, 0xE8, 0xBA,
+ 0x28, 0x15, 0xEC, 0x60, 0x14, 0xEB, 0x7D, 0x9C, 0xE5, 0x4D, 0xBF, 0x41, 0x95, 0xB5, 0xAC, 0x52,
+ 0x52, 0x1C, 0xC7, 0xCB, 0x7F, 0x03, 0x32, 0x22, 0x1F, 0x3C, 0x9B, 0x22, 0x14, 0xA2, 0x99, 0x3C,
+ 0x17, 0xAA, 0x79, 0xB4, 0x55, 0x4E, 0x27, 0x3E, 0x46, 0x2F, 0xA8, 0x33, 0x7D, 0x10, 0xD0, 0xB4,
+ 0x80, 0x21, 0x7B, 0x7B, 0x43, 0x5D, 0x4F, 0x71, 0xBA, 0xEC, 0x88, 0xAD, 0x15, 0x03, 0x87, 0xD2,
+ 0xE3, 0x1F, 0xA9, 0xB5, 0xC4, 0x84, 0x7C, 0x2B, 0x7E, 0x2B, 0xA4, 0xBC, 0xAA, 0x10, 0xBF, 0x5D,
+ 0x72, 0xE9, 0xDB, 0x25, 0xBF, 0xE9, 0x23, 0x3B, 0xEA, 0xAE, 0x7C, 0xA2, 0xBE, 0x85, 0x9E, 0x13,
+ 0x13, 0x7B, 0x74, 0x55, 0x6F, 0xF8, 0x20, 0x84, 0x4F, 0x1B, 0x59, 0xED, 0xFC, 0x8D, 0x21, 0x85,
+ 0xC5, 0x1C, 0x9A, 0x60, 0x08, 0xFD, 0xE9, 0x7C, 0x86, 0x23, 0x9F, 0x0B, 0x29, 0xEB, 0x22, 0xF8,
+ 0xC8, 0x74, 0x02, 0x18, 0xF1, 0x68, 0x60, 0x31, 0x9F, 0x2D, 0x74, 0x16, 0xDC, 0x38, 0x56, 0x86,
+ 0xF3, 0xD0, 0xC2, 0x70, 0xB9, 0x0C, 0x65, 0x86, 0xEE, 0xB6, 0x17, 0xD8, 0x2F, 0x67, 0x0D, 0x7B,
+ 0x1D, 0xED, 0xEE, 0xE5, 0x2F, 0xB7, 0xAD, 0x18, 0x41, 0xC1, 0xC4, 0x56, 0xDD, 0xE4, 0xF7, 0xAA,
+ 0x4E, 0xFA, 0x26, 0xD7, 0x1E, 0x51, 0x5B, 0x8E, 0xCA, 0xC3, 0x77, 0x37, 0xC9, 0x3B, 0xD8, 0x08,
+ 0xFE, 0x41, 0x4C, 0xBC, 0x02, 0x06, 0xD7, 0xB1, 0xB5, 0xC9, 0xD1, 0xE5, 0x01, 0xFF, 0xA0, 0xF3,
+ 0x43, 0xFC, 0x43, 0x58, 0xD0, 0xA1, 0x5D, 0x8C, 0xEA, 0x72, 0xD7, 0x21, 0x6C, 0x7E, 0xFA, 0x81,
+ 0xEC, 0x71, 0x66, 0xD2, 0x0B, 0x21, 0x1D, 0x4D, 0x87, 0x82, 0x2F, 0x76, 0x91, 0x12, 0x2D, 0x5E,
+ 0x7D, 0xF1, 0x46, 0xA4, 0xAA, 0x76, 0x25, 0x5E, 0x6F, 0x1D, 0x8B, 0x33, 0x22, 0xAC, 0xB7, 0x1E,
+ 0x46, 0x9D, 0x62, 0x00, 0x6B, 0x31, 0x94, 0x3A, 0x48, 0x45, 0x2B, 0xE8, 0x06, 0xC0, 0x48, 0x91,
+ 0x13, 0x09, 0x57, 0xC1, 0x00, 0x56, 0x66, 0x9E, 0x23, 0x42, 0x63, 0x34, 0xAC, 0x7C, 0x1C, 0x69,
+ 0x4C, 0x7C, 0xB2, 0x68, 0x83, 0xBF, 0x33, 0xF1, 0xF1, 0x47, 0x81, 0x32, 0xD0, 0xC8, 0xB8, 0x5A,
+ 0x02, 0x59, 0x68, 0x21, 0x9B, 0x4A, 0x54, 0x91, 0x85, 0x2A, 0x82, 0xB1, 0x59, 0xF3, 0x47, 0x2C,
+ 0x30, 0xB1, 0x14, 0x08, 0xA7, 0x22, 0xD5, 0xD4, 0x46, 0x25, 0x55, 0x3E, 0xB3, 0x69, 0x32, 0x1B,
+ 0xCD, 0xEA, 0x3F, 0xF3, 0xE7, 0x5A, 0xF1, 0x27, 0x07, 0x7F, 0x37, 0x5B, 0x14, 0x32, 0x7A, 0x18,
+ 0xB5, 0x2A, 0x5C, 0xED, 0x5A, 0x15, 0x8E, 0xA6, 0xAD, 0x8A, 0xFE, 0xD6, 0xAD, 0x0A, 0x47, 0x03,
+ 0x57, 0x85, 0xA3, 0x8D, 0xAB, 0xC2, 0xD5, 0xCC, 0xAD, 0x20, 0x9D, 0x03, 0xCA, 0x98, 0xED, 0x0C,
+ 0x19, 0x3D, 0xEC, 0x5C, 0xA4, 0xAE, 0x76, 0x2E, 0x52, 0x47, 0x3B, 0x17, 0x69, 0x7F, 0x3B, 0x17,
+ 0xA9, 0xA3, 0x9D, 0x8B, 0xD4, 0xD1, 0xCE, 0x45, 0xFA, 0x04, 0x76, 0xE6, 0x7D, 0x57, 0x0E, 0xD8,
+ 0x6E, 0xB6, 0x33, 0x64, 0xF4, 0xB0, 0x73, 0xBE, 0x75, 0xB5, 0x73, 0xBE, 0x75, 0xB4, 0x73, 0xBE,
+ 0xED, 0x6F, 0xE7, 0x7C, 0xEB, 0x68, 0xE7, 0x7C, 0xEB, 0x68, 0xE7, 0x7C, 0xFB, 0x04, 0x76, 0xC6,
+ 0x9D, 0x78, 0x63, 0xE8, 0x8B, 0x05, 0x8A, 0x21, 0xA3, 0x87, 0xA1, 0x2F, 0xB9, 0xAB, 0xA1, 0x2F,
+ 0xB9, 0xA3, 0xA1, 0x2F, 0x79, 0x7F, 0x43, 0x5F, 0x72, 0x47, 0x43, 0x5F, 0x72, 0x47, 0x43, 0x5F,
+ 0xF2, 0x27, 0x31, 0xF4, 0x44, 0x34, 0xB4, 0xDD, 0xD2, 0xFD, 0x4C, 0xDD, 0xC3, 0xD6, 0xEE, 0xC6,
+ 0xBE, 0xCB, 0xDA, 0xEE, 0xE6, 0x76, 0xB7, 0x77, 0x0F, 0x83, 0x13, 0xB3, 0xE2, 0x7C, 0xAB, 0xD9,
+ 0x28, 0x49, 0x60, 0xCC, 0x5F, 0x8C, 0x1A, 0x9D, 0x28, 0x59, 0xD8, 0x51, 0xE9, 0x9C, 0xD1, 0x45,
+ 0x16, 0x85, 0x59, 0xF6, 0xA4, 0xCB, 0x88, 0x8C, 0x6E, 0x6A, 0xA4, 0x9B, 0x04, 0x6A, 0x75, 0x33,
+ 0x4B, 0xF3, 0xB1, 0xEC, 0xB9, 0x39, 0x5B, 0xD3, 0x6E, 0x61, 0xA4, 0x9B, 0x69, 0xDA, 0x2D, 0x8D,
+ 0x74, 0xF3, 0x5A, 0xBB, 0x60, 0x6C, 0x36, 0xA6, 0xA6, 0x5E, 0x60, 0xB6, 0xFA, 0x52, 0xD3, 0x2F,
+ 0x08, 0x5B, 0x5C, 0x99, 0x2E, 0xF8, 0x42, 0x0B, 0x8A, 0x43, 0x60, 0xAE, 0x1B, 0xA7, 0x08, 0x45,
+ 0x0A, 0xB1, 0xD1, 0x38, 0x49, 0x24, 0x91, 0x84, 0x53, 0x9E, 0x33, 0x11, 0x73, 0xC4, 0xA6, 0xE2,
+ 0x24, 0x53, 0x91, 0x44, 0x6C, 0x25, 0x4E, 0x32, 0x93, 0x48, 0xA6, 0x82, 0xF8, 0x73, 0x39, 0xC7,
+ 0x24, 0xFF, 0x42, 0x22, 0x99, 0x99, 0xE4, 0x5F, 0x4A, 0x24, 0x73, 0x2E, 0x3F, 0x34, 0x8A, 0x6C,
+ 0x1F, 0x93, 0x02, 0x81, 0x64, 0x43, 0xA5, 0x1D, 0xB6, 0xFE, 0xD8, 0x1B, 0x6D, 0x2F, 0xFE, 0x58,
+ 0x99, 0x00, 0x8D, 0xEB, 0xAC, 0xAB, 0x92, 0x75, 0xA5, 0x59, 0x01, 0x29, 0x15, 0xA8, 0xA5, 0xE8,
+ 0x32, 0x45, 0x4D, 0x70, 0x55, 0x08, 0xAE, 0x22, 0x41, 0x48, 0x38, 0x84, 0x1A, 0x07, 0x31, 0xFF,
+ 0xAA, 0xE4, 0x5F, 0x85, 0xFC, 0x88, 0x94, 0x8F, 0xD4, 0x89, 0x9B, 0x90, 0x7B, 0x55, 0x72, 0xAF,
+ 0x4D, 0xEE, 0x84, 0x94, 0x9D, 0xE8, 0x93, 0x3E, 0x21, 0xFF, 0xAA, 0xE4, 0x5F, 0x85, 0xFC, 0x29,
+ 0x29, 0x3F, 0x55, 0xCA, 0x47, 0x42, 0xEE, 0x55, 0xC9, 0xBD, 0x92, 0xDC, 0xCE, 0xA1, 0xBC, 0x0B,
+ 0xA8, 0x55, 0x85, 0x2B, 0xAE, 0x55, 0x85, 0x33, 0xB4, 0x01, 0xA9, 0x0D, 0xDD, 0x1A, 0x0A, 0x67,
+ 0x80, 0xAB, 0x0A, 0x67, 0x8C, 0x03, 0x52, 0x1B, 0xCC, 0x35, 0x14, 0xCE, 0x48, 0x57, 0x15, 0xCE,
+ 0x60, 0x07, 0xA4, 0x36, 0xBC, 0x6B, 0x28, 0xDC, 0x21, 0xAF, 0x2A, 0xDC, 0x51, 0xAF, 0x2A, 0x9C,
+ 0x80, 0x0F, 0xC8, 0xE4, 0xD8, 0x1E, 0x0B, 0x59, 0x0E, 0xB0, 0x58, 0x15, 0x2E, 0xC8, 0x08, 0x54,
+ 0x3A, 0x38, 0xF2, 0x4C, 0x17, 0x7C, 0xAC, 0x0A, 0x17, 0x88, 0x04, 0x2A, 0x1D, 0x25, 0x79, 0xA6,
+ 0x0B, 0x50, 0x56, 0x85, 0x0B, 0x56, 0x02, 0x95, 0x0E, 0x97, 0x3C, 0xD3, 0x09, 0x31, 0x81, 0xAC,
+ 0x1D, 0x34, 0x81, 0x82, 0xE2, 0x26, 0xFE, 0xA0, 0x43, 0x27, 0x27, 0xB8, 0xEA, 0x04, 0xD7, 0x86,
+ 0x20, 0x60, 0x1C, 0x5A, 0x60, 0x94, 0xE4, 0x32, 0x3E, 0x2D, 0x60, 0x4A, 0x5A, 0x9B, 0x71, 0xB3,
+ 0x43, 0x2A, 0xC9, 0x64, 0xCC, 0xEC, 0xC0, 0x4A, 0x7C, 0xA2, 0xE6, 0xA5, 0xC3, 0x2B, 0xA7, 0x61,
+ 0x9C, 0x6C, 0x20, 0x4B, 0xDC, 0x87, 0xF1, 0xB1, 0x43, 0x2D, 0xC9, 0x64, 0x9C, 0xEC, 0x80, 0x4B,
+ 0x9C, 0xAC, 0xE6, 0xA5, 0xC3, 0x2E, 0xA7, 0x61, 0x9C, 0x8C, 0xE0, 0xDB, 0x3D, 0xC1, 0x77, 0x41,
+ 0xDF, 0x22, 0x75, 0x45, 0xDF, 0x22, 0x75, 0x46, 0x5F, 0x20, 0xB5, 0xA1, 0x6F, 0x43, 0xE1, 0x8C,
+ 0xBE, 0x45, 0xEA, 0x8C, 0xBE, 0x40, 0x6A, 0x43, 0xDF, 0x86, 0xC2, 0x19, 0x7D, 0x8B, 0xD4, 0x19,
+ 0x7D, 0x81, 0xD4, 0x86, 0xBE, 0x0D, 0x85, 0x3B, 0xFA, 0x16, 0xA9, 0x3B, 0xFA, 0x16, 0xA9, 0x13,
+ 0xFA, 0x02, 0x99, 0x8A, 0xBE, 0x3C, 0xCB, 0x01, 0x7D, 0x8B, 0xD4, 0x05, 0x7D, 0x81, 0x4A, 0x47,
+ 0x5F, 0x9E, 0xE9, 0x82, 0xBE, 0x45, 0xEA, 0x82, 0xBE, 0x40, 0xA5, 0xA3, 0x2F, 0xCF, 0x74, 0x41,
+ 0xDF, 0x22, 0x75, 0x41, 0x5F, 0xA0, 0xD2, 0xD1, 0x97, 0x67, 0x3A, 0xA1, 0x2F, 0x90, 0xB5, 0xA3,
+ 0x2F, 0x50, 0x50, 0xF4, 0xC5, 0x1F, 0x74, 0xF4, 0xE5, 0x04, 0x57, 0x9D, 0xE0, 0xDA, 0x10, 0x04,
+ 0x8C, 0x43, 0x0B, 0xFA, 0x92, 0x5C, 0xC6, 0xA7, 0x05, 0x7D, 0x49, 0x6B, 0x33, 0x6E, 0x76, 0xF4,
+ 0x25, 0x99, 0x8C, 0x99, 0x1D, 0x7D, 0x89, 0x4F, 0xD4, 0xBC, 0x74, 0xF4, 0xE5, 0x34, 0x8C, 0x93,
+ 0x0D, 0x7D, 0x89, 0xFB, 0x30, 0x3E, 0x76, 0xF4, 0x25, 0x99, 0x8C, 0x93, 0x1D, 0x7D, 0x89, 0x93,
+ 0xD5, 0xBC, 0x74, 0xF4, 0xE5, 0x34, 0x8C, 0x93, 0x33, 0xFA, 0xCA, 0xCB, 0x7E, 0x2E, 0xE8, 0x9B,
+ 0x6F, 0x5D, 0xD1, 0x37, 0xDF, 0x3A, 0xA3, 0x2F, 0x90, 0xDA, 0xD0, 0xB7, 0xA1, 0x70, 0x46, 0xDF,
+ 0x7C, 0xEB, 0x8C, 0xBE, 0x40, 0x6A, 0x43, 0xDF, 0x86, 0xC2, 0x19, 0x7D, 0xF3, 0xAD, 0x33, 0xFA,
+ 0x02, 0xA9, 0x0D, 0x7D, 0x1B, 0x0A, 0x77, 0xF4, 0xCD, 0xB7, 0xEE, 0xE8, 0x9B, 0x6F, 0x9D, 0xD0,
+ 0x17, 0xC8, 0x54, 0xF4, 0xE5, 0x59, 0x0E, 0xE8, 0x9B, 0x6F, 0x5D, 0xD0, 0x17, 0xA8, 0x74, 0xF4,
+ 0xE5, 0x99, 0x2E, 0xE8, 0x9B, 0x6F, 0x5D, 0xD0, 0x17, 0xA8, 0x74, 0xF4, 0xE5, 0x99, 0x2E, 0xE8,
+ 0x9B, 0x6F, 0x5D, 0xD0, 0x17, 0xA8, 0x74, 0xF4, 0xE5, 0x99, 0x4E, 0xE8, 0x0B, 0x64, 0x1D, 0xE8,
+ 0x9B, 0xB3, 0x35, 0x03, 0xFC, 0x41, 0x47, 0x5F, 0x4E, 0x70, 0xD5, 0x09, 0xAE, 0x0D, 0x41, 0xC0,
+ 0x38, 0xB4, 0xA0, 0x2F, 0xC9, 0x65, 0x7C, 0x5A, 0xD0, 0x97, 0xB4, 0x36, 0xE3, 0x66, 0x47, 0x5F,
+ 0x92, 0xC9, 0x98, 0xD9, 0xD1, 0x97, 0xF8, 0x44, 0xCD, 0x4B, 0x47, 0x5F, 0x4E, 0xC3, 0x38, 0xD9,
+ 0xD0, 0x97, 0xB8, 0x0F, 0xE3, 0x63, 0x47, 0x5F, 0x92, 0xC9, 0x38, 0xD9, 0xD1, 0x97, 0x38, 0x59,
+ 0xCD, 0x4B, 0x47, 0x5F, 0x4E, 0xC3, 0x38, 0x39, 0xA3, 0xAF, 0xBC, 0x19, 0xE0, 0xB4, 0x9E, 0x7A,
+ 0x71, 0x5E, 0x52, 0xBD, 0xB8, 0xAF, 0xAA, 0x5E, 0x3A, 0x17, 0x56, 0x2F, 0xEE, 0x6B, 0xAB, 0x17,
+ 0xF7, 0xE5, 0xD5, 0x4B, 0xE7, 0x0A, 0xEB, 0xC5, 0x7D, 0x91, 0xF5, 0xE2, 0xBE, 0xCE, 0x7A, 0xE9,
+ 0x5C, 0x6A, 0xBD, 0xF4, 0x58, 0x6D, 0xBD, 0xF4, 0x58, 0x70, 0xBD, 0xB8, 0xAD, 0xB9, 0x02, 0x99,
+ 0x0A, 0xBF, 0x3C, 0xCB, 0x01, 0x7E, 0x2F, 0xB9, 0x0B, 0xFC, 0x02, 0x95, 0x0E, 0xBF, 0x3C, 0xD3,
+ 0x05, 0x7E, 0x2F, 0xB9, 0x0B, 0xFC, 0x02, 0x95, 0x0E, 0xBF, 0x3C, 0xD3, 0x05, 0x7E, 0x2F, 0xB9,
+ 0x0B, 0xFC, 0x02, 0x95, 0x0E, 0xBF, 0x3C, 0xD3, 0x09, 0x7E, 0x81, 0xAC, 0x1D, 0x7E, 0x81, 0x82,
+ 0xC2, 0x2F, 0xFE, 0xA0, 0xC3, 0x2F, 0x27, 0xB8, 0xEA, 0x04, 0xD7, 0x86, 0x20, 0x60, 0x1C, 0x5A,
+ 0xE0, 0x97, 0xE4, 0x32, 0x3E, 0x2D, 0xF0, 0x4B, 0x5A, 0x9B, 0x71, 0xB3, 0xC3, 0x2F, 0xC9, 0x64,
+ 0xCC, 0xEC, 0xF0, 0x4B, 0x7C, 0xA2, 0xE6, 0xA5, 0xC3, 0x2F, 0xA7, 0x61, 0x9C, 0x6C, 0xF0, 0x4B,
+ 0xDC, 0x87, 0xF1, 0xB1, 0xC3, 0x2F, 0xC9, 0x64, 0x9C, 0xEC, 0xF0, 0x4B, 0x9C, 0xAC, 0xE6, 0xA5,
+ 0xC3, 0x2F, 0xA7, 0x61, 0x9C, 0xDC, 0xE1, 0x57, 0xDA, 0x22, 0x74, 0xC4, 0xDF, 0x1E, 0x00, 0xDC,
+ 0x07, 0x81, 0x1D, 0x20, 0xB8, 0x0F, 0x06, 0xF7, 0x01, 0x61, 0x07, 0x14, 0xEE, 0x03, 0xC3, 0x7D,
+ 0x70, 0xD8, 0x01, 0x88, 0x7B, 0x21, 0x71, 0x2F, 0x28, 0x76, 0xC5, 0xE2, 0x36, 0x30, 0x76, 0x43,
+ 0x63, 0x47, 0x38, 0x6E, 0xC7, 0x63, 0x47, 0x40, 0x76, 0x44, 0xE4, 0x76, 0x48, 0x76, 0xC4, 0x64,
+ 0x47, 0x50, 0x6E, 0x47, 0x65, 0x57, 0x58, 0x76, 0xC0, 0x65, 0x0E, 0xCC, 0x56, 0x64, 0xE6, 0xD0,
+ 0x6C, 0xC5, 0x66, 0x0E, 0xCE, 0x1D, 0xE8, 0xCC, 0xE1, 0xB9, 0x03, 0x9F, 0x39, 0x40, 0xB7, 0x23,
+ 0x34, 0x87, 0xE8, 0x76, 0x8C, 0xE6, 0x20, 0xDD, 0x86, 0xD2, 0x1C, 0xA6, 0xDB, 0x70, 0x9A, 0x03,
+ 0x75, 0x3B, 0x52, 0x73, 0xA8, 0x6E, 0xC7, 0x6A, 0x0E, 0xD6, 0x6D, 0x68, 0xCD, 0xE1, 0xDA, 0x8A,
+ 0xD7, 0x23, 0xF2, 0x46, 0x28, 0xCD, 0x22, 0x1F, 0xFD, 0xF5, 0x56, 0x3C, 0x17, 0xE2, 0x41, 0xC8,
+ 0x89, 0x93, 0x04, 0x52, 0x5A, 0x08, 0xE0, 0x9D, 0xEF, 0xEC, 0x88, 0xD4, 0x97, 0x5D, 0x75, 0x02,
+ 0xCC, 0x82, 0x9C, 0xE4, 0x31, 0xF6, 0x06, 0xEC, 0xDF, 0x68, 0x3C, 0x1D, 0xCA, 0x75, 0xE1, 0x57,
+ 0xB6, 0x65, 0x4E, 0x7A, 0xBE, 0x89, 0x51, 0x20, 0xF1, 0x21, 0xEF, 0x49, 0xCB, 0x6C, 0xB4, 0x6C,
+ 0xA3, 0x38, 0x73, 0x90, 0x87, 0xE3, 0x94, 0xE9, 0xA9, 0x7B, 0x89, 0xA9, 0xFA, 0xFA, 0x38, 0x3C,
+ 0x18, 0xFA, 0x20, 0xBD, 0x9B, 0xCA, 0x1E, 0x77, 0x67, 0x06, 0x7F, 0x95, 0xBC, 0x9D, 0xC0, 0x9E,
+ 0x3B, 0x1F, 0xBE, 0xFA, 0xE2, 0xAB, 0x2F, 0xF2, 0xA7, 0xE8, 0xC9, 0xFB, 0x1A, 0x23, 0xE3, 0xA3,
+ 0xF4, 0xF5, 0xB3, 0xA6, 0xBC, 0xAD, 0x86, 0xAC, 0x16, 0x26, 0x59, 0xDD, 0xE7, 0x1E, 0x2F, 0x90,
+ 0x8E, 0x9F, 0x8F, 0x8D, 0xD3, 0xC3, 0x77, 0xF0, 0x88, 0x3C, 0x3E, 0x21, 0x69, 0x4C, 0xFE, 0x2E,
+ 0xE1, 0x0F, 0x3C, 0x83, 0x4F, 0x19, 0xE9, 0x4D, 0x3B, 0xAC, 0x05, 0x24, 0xEF, 0xDC, 0x36, 0xAF,
+ 0x50, 0xC9, 0x2F, 0xEC, 0xD6, 0x34, 0xE4, 0x2D, 0x5C, 0x95, 0x86, 0x0A, 0x52, 0x93, 0x68, 0x6F,
+ 0x61, 0xBC, 0x2A, 0x7C, 0x01, 0xAD, 0x65, 0xE9, 0xF5, 0xA3, 0x45, 0x1E, 0x47, 0xD4, 0x48, 0xE4,
+ 0x51, 0x5B, 0xF9, 0xC5, 0x65, 0x48, 0x60, 0xB5, 0xC0, 0x4E, 0x48, 0x9B, 0x41, 0xE9, 0x2B, 0x38,
+ 0xF4, 0x57, 0x5D, 0x82, 0xD6, 0x8B, 0x52, 0xB9, 0x5C, 0x23, 0x10, 0xB7, 0xE3, 0x60, 0xDC, 0x55,
+ 0x44, 0x2D, 0x34, 0x1E, 0x40, 0x31, 0xB9, 0x50, 0x0E, 0xAF, 0xA3, 0x19, 0x8B, 0xE9, 0x4D, 0x47,
+ 0xAB, 0xE3, 0xB1, 0x42, 0x1B, 0xE2, 0xD5, 0x53, 0xB9, 0xC2, 0x2F, 0x2B, 0x1E, 0x36, 0x3E, 0x7E,
+ 0x6D, 0xF0, 0x85, 0x43, 0x0A, 0xEF, 0xB3, 0x98, 0xDA, 0x8F, 0x7B, 0x88, 0xC4, 0x85, 0x78, 0x0A,
+ 0x77, 0x21, 0x5B, 0xCC, 0x92, 0xD6, 0xE7, 0xE1, 0xE5, 0x54, 0x03, 0x25, 0xE5, 0x15, 0x98, 0xF3,
+ 0x39, 0x7F, 0x1E, 0x77, 0x5C, 0x35, 0xF2, 0xD5, 0xA9, 0x36, 0x42, 0xC9, 0x2B, 0x33, 0x66, 0xCB,
+ 0x75, 0xD5, 0x87, 0x47, 0x29, 0x18, 0xF7, 0x6C, 0xB2, 0x41, 0xE1, 0x66, 0x63, 0x40, 0x27, 0x9A,
+ 0x3B, 0x4D, 0xE7, 0x9B, 0xB0, 0x05, 0xDC, 0xC6, 0xE3, 0xB1, 0x19, 0x8F, 0x9E, 0x85, 0xA0, 0x5D,
+ 0xA3, 0x99, 0x9E, 0xA9, 0x97, 0xE4, 0x22, 0xD3, 0x82, 0x9B, 0x34, 0x40, 0x89, 0x96, 0x27, 0x96,
+ 0xE3, 0x1F, 0x25, 0x74, 0xA9, 0x2B, 0x65, 0x2A, 0xD7, 0x47, 0x63, 0xE9, 0x4A, 0x03, 0x04, 0x45,
+ 0x68, 0x6A, 0x53, 0x3A, 0x9D, 0xA7, 0x8B, 0x34, 0xBE, 0x4B, 0xE9, 0x64, 0x9D, 0x40, 0x93, 0xDD,
+ 0xA1, 0x74, 0x1A, 0xA4, 0x61, 0x3A, 0xB9, 0x4F, 0x69, 0x5A, 0x29, 0x53, 0xBA, 0x3E, 0xF8, 0x4B,
+ 0x55, 0x19, 0x6C, 0x3A, 0x4F, 0x53, 0x6B, 0x3B, 0xCF, 0xD3, 0x75, 0x7A, 0x67, 0x3B, 0x27, 0xE9,
+ 0x38, 0x99, 0xDF, 0xA1, 0x72, 0x12, 0xA4, 0xB3, 0xE4, 0xDE, 0x76, 0x26, 0x95, 0x32, 0x95, 0xC9,
+ 0xB1, 0x66, 0xBA, 0x5F, 0x6F, 0x26, 0x9B, 0xC4, 0xEE, 0xD7, 0x68, 0x81, 0x36, 0xF7, 0xE9, 0x1B,
+ 0xA7, 0x09, 0x8A, 0xEE, 0xD0, 0x77, 0xBD, 0x01, 0xB7, 0x5B, 0xDE, 0xA9, 0x2F, 0xA9, 0x94, 0xE9,
+ 0x5B, 0x1F, 0xDA, 0x26, 0xAB, 0x4C, 0xDE, 0x2F, 0x8B, 0x12, 0x6B, 0x13, 0x6F, 0x42, 0x34, 0x4F,
+ 0xA2, 0xBB, 0x54, 0x46, 0xB3, 0x74, 0xBD, 0x5E, 0xDE, 0xA1, 0x32, 0x4A, 0x50, 0xB0, 0x46, 0xF7,
+ 0xA9, 0x4C, 0x2B, 0x65, 0x2A, 0xB3, 0x23, 0xE9, 0x34, 0x8D, 0x17, 0xE9, 0xDC, 0x18, 0xA9, 0xAC,
+ 0x76, 0xF8, 0x73, 0x5F, 0x23, 0xA7, 0x9B, 0x24, 0x4C, 0x26, 0xF7, 0x68, 0x3C, 0x4D, 0xE6, 0x49,
+ 0x7C, 0x9F, 0xC6, 0xB4, 0x52, 0xA6, 0x31, 0x3D, 0x70, 0x4F, 0x56, 0x98, 0x1D, 0xBF, 0xD7, 0xA2,
+ 0x30, 0x82, 0xE1, 0xD5, 0x9D, 0x0A, 0xA3, 0x31, 0x0A, 0xEE, 0x52, 0x18, 0xCD, 0xD0, 0xFC, 0x5E,
+ 0x85, 0x71, 0xA5, 0xBC, 0x89, 0xCB, 0xAF, 0x65, 0x7D, 0x5B, 0xC6, 0xCE, 0x34, 0x37, 0x89, 0xC6,
+ 0xD1, 0xC4, 0xAA, 0x2F, 0x89, 0x08, 0x8B, 0xBE, 0xD1, 0x3C, 0x5A, 0x47, 0x36, 0xA0, 0xE6, 0x25,
+ 0x75, 0x7D, 0xA3, 0x30, 0x9A, 0x46, 0x7A, 0x14, 0x8B, 0xE5, 0xF8, 0x47, 0x49, 0xDF, 0xBA, 0xD2,
+ 0x5A, 0x5F, 0x78, 0x39, 0xFF, 0x08, 0x67, 0x07, 0xE2, 0x01, 0x46, 0x7D, 0xFA, 0x8F, 0x7F, 0x21,
+ 0xAB, 0x3F, 0xCD, 0x51, 0x01, 0x4D, 0x3A, 0x3D, 0x7B, 0x0F, 0x0F, 0xE3, 0x4E, 0x87, 0x73, 0xB2,
+ 0xE3, 0xEB, 0x4B, 0xFC, 0xCD, 0xC3, 0xF9, 0x74, 0xB4, 0x24, 0x4F, 0xB7, 0xA8, 0xEC, 0xF1, 0xB8,
+ 0xB0, 0x7F, 0x0D, 0x86, 0x2A, 0xE6, 0xB3, 0xB9, 0xB5, 0x8A, 0x22, 0xFD, 0x5D, 0xAA, 0x58, 0x2E,
+ 0x03, 0x6B, 0x15, 0xF9, 0xF6, 0x77, 0xA9, 0x22, 0x08, 0x96, 0x4B, 0x6B, 0x1D, 0x97, 0xFC, 0xF7,
+ 0xA9, 0x23, 0x6A, 0xAB, 0xE3, 0xAE, 0x4A, 0x46, 0xF8, 0xE5, 0x6B, 0x9F, 0x1C, 0xD8, 0x63, 0x7A,
+ 0xA5, 0x97, 0x2E, 0x33, 0x71, 0x9A, 0x7A, 0xD8, 0xCF, 0x5F, 0x50, 0x14, 0x5E, 0x81, 0x07, 0xB9,
+ 0x94, 0x53, 0x8B, 0x8C, 0x24, 0xCA, 0x6B, 0xD9, 0xED, 0xA7, 0xF1, 0xE0, 0x69, 0xB9, 0x2A, 0x04,
+ 0x6E, 0x32, 0x5D, 0x8E, 0x4E, 0x29, 0x04, 0x02, 0xFD, 0x25, 0x72, 0xAD, 0x8E, 0xAA, 0x30, 0xD4,
+ 0x11, 0x76, 0x56, 0x22, 0x52, 0xC8, 0x6F, 0x8E, 0xE3, 0x0C, 0x66, 0x6E, 0xFC, 0xEA, 0xBB, 0xF8,
+ 0xEA, 0x39, 0x2E, 0xE5, 0xF0, 0xA2, 0x39, 0x29, 0x8B, 0x5F, 0x43, 0x85, 0x36, 0x94, 0xCF, 0x01,
+ 0x33, 0x9D, 0x59, 0x53, 0x5B, 0x7D, 0x44, 0x7E, 0xFD, 0x11, 0x47, 0x63, 0x8A, 0xA9, 0x79, 0x76,
+ 0x5C, 0x71, 0x83, 0x5C, 0x0C, 0x2F, 0xA4, 0x43, 0x27, 0x32, 0x81, 0x4E, 0xD5, 0x72, 0x7A, 0xCA,
+ 0x03, 0x3E, 0xFE, 0x47, 0x4B, 0x54, 0xBE, 0x9B, 0xDF, 0x66, 0xC7, 0xCB, 0x25, 0xEC, 0x30, 0x3A,
+ 0x11, 0x17, 0x07, 0x70, 0xA6, 0x69, 0x35, 0x40, 0x71, 0x85, 0x07, 0x72, 0xFE, 0x01, 0x0E, 0x8B,
+ 0xE2, 0x73, 0x76, 0x2D, 0xAF, 0xEB, 0xE4, 0x52, 0xFA, 0x75, 0x78, 0x93, 0xDB, 0x80, 0xD6, 0xCC,
+ 0x4F, 0x83, 0x93, 0x9B, 0x88, 0x1E, 0x41, 0x83, 0x8F, 0x6F, 0xF9, 0xB2, 0x09, 0xCC, 0x15, 0x3D,
+ 0x1C, 0xC5, 0x46, 0xA8, 0x9E, 0x9D, 0x44, 0xBE, 0x7E, 0x51, 0x22, 0x90, 0x79, 0x9F, 0x5F, 0xBF,
+ 0x54, 0x4F, 0x4D, 0x92, 0xD9, 0xD0, 0x23, 0xB3, 0x6E, 0x4E, 0x2D, 0x27, 0xF5, 0x1F, 0x8B, 0xD9,
+ 0x7A, 0xBE, 0x41, 0x0F, 0xCD, 0x11, 0x5B, 0xE2, 0xE2, 0x06, 0x5D, 0xD6, 0x60, 0x6E, 0x4D, 0x96,
+ 0x71, 0x82, 0xC8, 0x0B, 0x82, 0xB1, 0x17, 0x4E, 0x23, 0x0F, 0x92, 0x87, 0xB2, 0x0C, 0xF2, 0x59,
+ 0x3E, 0x7E, 0x0C, 0xD5, 0x92, 0x03, 0x7D, 0xBE, 0x8D, 0xE1, 0x60, 0xE5, 0x1B, 0xF7, 0x2F, 0x54,
+ 0xA8, 0xE5, 0x48, 0xFB, 0x83, 0x47, 0x27, 0x68, 0x47, 0x0E, 0x32, 0xBC, 0xC9, 0x47, 0x9D, 0x35,
+ 0xA7, 0x64, 0xA9, 0xE5, 0xFA, 0x17, 0xA9, 0xAD, 0xEB, 0x49, 0xC9, 0xDC, 0xCA, 0x37, 0xDD, 0x66,
+ 0x68, 0x09, 0x63, 0xA0, 0x8D, 0x8D, 0xA3, 0xE5, 0x3C, 0x1F, 0x73, 0x20, 0xD6, 0x47, 0x50, 0xF9,
+ 0x75, 0xB2, 0xCF, 0xD2, 0x6B, 0xC3, 0x31, 0x5C, 0x80, 0x23, 0x9F, 0x56, 0x52, 0x89, 0xFA, 0xF4,
+ 0x0D, 0x9E, 0xD1, 0x19, 0xA4, 0x4C, 0x6C, 0xE6, 0x2D, 0x3E, 0xFA, 0x16, 0x66, 0xFD, 0x15, 0x8D,
+ 0xA4, 0xFB, 0x4E, 0x76, 0xAB, 0xBF, 0x72, 0x51, 0x84, 0x25, 0x31, 0xE5, 0x4C, 0x37, 0x31, 0x2E,
+ 0x6D, 0x01, 0xC9, 0x25, 0xB6, 0x50, 0xFC, 0xB9, 0x01, 0x6D, 0x6E, 0xC8, 0x8E, 0x30, 0xA7, 0xCB,
+ 0x2B, 0x1D, 0x81, 0x6B, 0xE1, 0xAC, 0x37, 0x19, 0xA0, 0xC3, 0x18, 0x45, 0x72, 0x0D, 0xC6, 0xB3,
+ 0xA0, 0xFE, 0xF7, 0x32, 0xE2, 0x65, 0x8D, 0x42, 0x7F, 0xA9, 0xB7, 0xFD, 0x23, 0x7D, 0xBD, 0xC5,
+ 0xA9, 0x74, 0xA3, 0xD2, 0xF6, 0xFC, 0x5D, 0x22, 0xC1, 0x50, 0x6F, 0xBF, 0x78, 0xC0, 0xDD, 0x04,
+ 0xF6, 0xB5, 0xCB, 0xC9, 0x7D, 0x14, 0xA4, 0x9E, 0xE0, 0xD7, 0x7B, 0xC4, 0x23, 0x6C, 0xCA, 0xD4,
+ 0x83, 0x1B, 0x3A, 0xB0, 0x11, 0x33, 0xF4, 0xE5, 0x6D, 0x9B, 0xE4, 0x52, 0x32, 0x3E, 0x89, 0xC4,
+ 0x89, 0x4E, 0x18, 0x94, 0x96, 0xEC, 0x60, 0x5D, 0xF9, 0x7C, 0xA7, 0x47, 0x8D, 0x1E, 0x1E, 0x15,
+ 0x60, 0x5A, 0x36, 0x07, 0x87, 0x91, 0x01, 0x6A, 0x33, 0x1E, 0xC6, 0x0F, 0xCF, 0x0E, 0x1F, 0xD4,
+ 0xE5, 0x7C, 0xFC, 0x53, 0x1F, 0xBF, 0x9A, 0x8E, 0x56, 0xD5, 0xAA, 0x6C, 0xED, 0x0E, 0x79, 0x0D,
+ 0x32, 0x4C, 0x85, 0x0C, 0xA5, 0xAC, 0x20, 0x65, 0xC3, 0xA8, 0xA9, 0x49, 0x04, 0x37, 0xC4, 0xFC,
+ 0xC3, 0x05, 0xC9, 0xB7, 0x76, 0xF3, 0x07, 0x46, 0xEB, 0x93, 0xCA, 0xF5, 0x63, 0xAE, 0x4C, 0xB6,
+ 0x8F, 0x4C, 0xF5, 0x75, 0xD8, 0x9E, 0xF3, 0xE7, 0x0A, 0x53, 0x7D, 0x03, 0x8B, 0xBA, 0x81, 0x59,
+ 0xDB, 0xC0, 0x54, 0xB9, 0x9B, 0xD5, 0x7F, 0x57, 0x11, 0xEA, 0x73, 0x40, 0x25, 0x51, 0x5A, 0x7C,
+ 0x7E, 0x2E, 0x3A, 0x7D, 0x53, 0xFA, 0xCE, 0xA0, 0x69, 0xCA, 0xDF, 0xD7, 0xEA, 0xB2, 0xFD, 0x28,
+ 0xCC, 0x08, 0xE7, 0x0A, 0xE1, 0xF6, 0x15, 0x8F, 0xE6, 0x52, 0x21, 0xCD, 0x54, 0xFC, 0x49, 0xA6,
+ 0x0F, 0x94, 0x03, 0x1D, 0x88, 0x4B, 0x27, 0x58, 0x8A, 0xC3, 0x76, 0x1D, 0x01, 0x6C, 0xA2, 0x34,
+ 0xCE, 0x70, 0x1F, 0x2F, 0xE2, 0xC1, 0x3D, 0xF0, 0x3D, 0x64, 0x81, 0xAB, 0x0C, 0x83, 0x88, 0x32,
+ 0x35, 0x44, 0x56, 0xD0, 0x58, 0xAC, 0x41, 0xC6, 0xCC, 0x13, 0xFC, 0x41, 0x74, 0xBC, 0xFC, 0x21,
+ 0xF7, 0x46, 0x08, 0xA9, 0xB0, 0x41, 0x06, 0xD7, 0xBE, 0x9C, 0xCB, 0xFC, 0x85, 0x67, 0xF0, 0xA1,
+ 0xFB, 0x2B, 0xF2, 0xFD, 0x65, 0x38, 0x46, 0xFA, 0xA5, 0x4B, 0x91, 0x7B, 0xCF, 0x45, 0x09, 0x7C,
+ 0x1C, 0xC0, 0xC7, 0x7D, 0xF5, 0xCA, 0xF3, 0xF8, 0x0E, 0x1B, 0xB8, 0xC2, 0xE6, 0xBB, 0xEF, 0xBE,
+ 0x1B, 0x7D, 0x17, 0x8D, 0x0E, 0xE5, 0xF6, 0xE5, 0x10, 0x16, 0x38, 0x31, 0xF1, 0xF3, 0x83, 0x6F,
+ 0x33, 0xF4, 0xDD, 0x1B, 0x87, 0xCB, 0x2B, 0xCF, 0x93, 0xE3, 0xA8, 0x67, 0xF0, 0xEF, 0xF9, 0xE7,
+ 0x22, 0x04, 0xE5, 0x8F, 0xF1, 0x69, 0x37, 0x80, 0x98, 0xCB, 0x5F, 0x79, 0x1E, 0x77, 0xCB, 0xCF,
+ 0x0F, 0xF0, 0xF1, 0xF0, 0x5F, 0xA3, 0x57, 0x9E, 0x7F, 0x2E, 0x8C, 0xE8, 0x45, 0x2F, 0x75, 0x92,
+ 0x8F, 0x55, 0x81, 0x5D, 0xD2, 0x57, 0x9E, 0x27, 0xA2, 0x49, 0xC9, 0x5F, 0x81, 0x57, 0xA8, 0xE9,
+ 0xC4, 0xE2, 0xAF, 0x3C, 0x1F, 0x3E, 0x3F, 0x48, 0x5F, 0x79, 0xFE, 0x83, 0x70, 0x30, 0xCD, 0x67,
+ 0x03, 0xF8, 0xEB, 0xCF, 0x9E, 0x7F, 0x99, 0x56, 0x8D, 0x25, 0x83, 0x4F, 0xCF, 0x0C, 0x45, 0x85,
+ 0x4B, 0x04, 0xD3, 0xF6, 0x13, 0x78, 0x1F, 0xFB, 0x24, 0xE6, 0x35, 0x07, 0xC7, 0xD3, 0x2B, 0x7E,
+ 0x58, 0x3B, 0x24, 0x08, 0x3B, 0xA4, 0x40, 0xC7, 0x5A, 0x63, 0x06, 0x7D, 0x68, 0x10, 0x1E, 0xED,
+ 0xEB, 0x09, 0x7F, 0xDC, 0x92, 0xC0, 0x13, 0xAD, 0x54, 0xF4, 0x1B, 0x92, 0x51, 0x67, 0xD7, 0xE7,
+ 0x19, 0x62, 0x2E, 0x9B, 0xDD, 0xFF, 0xFE, 0x33, 0x77, 0xCA, 0xFE, 0x8B, 0xE2, 0x9C, 0x9F, 0xB2,
+ 0x23, 0xBE, 0x00, 0x42, 0x4C, 0xA6, 0xD7, 0x8B, 0xF0, 0x9B, 0x46, 0x5E, 0x79, 0x26, 0x78, 0xE6,
+ 0xCB, 0xA1, 0x32, 0x08, 0xA1, 0x6D, 0xA8, 0x79, 0x3C, 0x51, 0x42, 0xAA, 0xA4, 0x41, 0x26, 0xEB,
+ 0x14, 0x5B, 0x26, 0xE7, 0x47, 0x42, 0x97, 0x78, 0x5F, 0x4A, 0x1F, 0x88, 0x61, 0x00, 0x96, 0xF5,
+ 0x65, 0x41, 0x29, 0xF1, 0x51, 0xD7, 0xF2, 0xA8, 0x4D, 0xD4, 0xF5, 0x3B, 0x9E, 0xCA, 0xC7, 0x53,
+ 0x96, 0x61, 0x90, 0xCC, 0x3D, 0xDF, 0xBA, 0x9E, 0xDE, 0x2E, 0xF1, 0x56, 0x7B, 0x79, 0x19, 0x4A,
+ 0x77, 0x28, 0xF9, 0x5A, 0x41, 0x3F, 0xA1, 0x53, 0x09, 0x34, 0x7E, 0x35, 0xA4, 0x2A, 0x6B, 0xB8,
+ 0x81, 0xC6, 0x74, 0x20, 0x7C, 0xF6, 0xC9, 0x21, 0xEA, 0xE2, 0xD9, 0xD7, 0xE2, 0xA3, 0x69, 0x3E,
+ 0x5D, 0xA7, 0xD1, 0xE9, 0x19, 0x0C, 0x37, 0xDD, 0x13, 0xF9, 0x28, 0x2F, 0x67, 0x42, 0x82, 0xFE,
+ 0xC8, 0x4E, 0x37, 0x52, 0x3A, 0x01, 0x87, 0x05, 0x2B, 0xD8, 0xF9, 0x7C, 0x3A, 0x52, 0xB0, 0x87,
+ 0x8F, 0xE0, 0x2F, 0x71, 0xFB, 0x27, 0x8A, 0xEB, 0xA6, 0xF0, 0x11, 0x3C, 0x92, 0x75, 0x6D, 0xF5,
+ 0x85, 0x40, 0xE8, 0x02, 0x7B, 0x3D, 0x0F, 0x7A, 0x92, 0x6E, 0x41, 0xBA, 0xE0, 0x47, 0x12, 0x20,
+ 0x60, 0xBF, 0x94, 0x4F, 0xC3, 0xA7, 0xE6, 0xB3, 0x15, 0xC2, 0x34, 0x07, 0xA5, 0x04, 0x7E, 0xEE,
+ 0x51, 0xA7, 0x5F, 0xB1, 0xA7, 0x4C, 0xA0, 0x5F, 0xC0, 0x67, 0x92, 0xAE, 0x49, 0xAC, 0xEE, 0x61,
+ 0x0B, 0xFF, 0x85, 0xE5, 0xF8, 0xB9, 0xA1, 0x46, 0xFF, 0xC7, 0x01, 0x8C, 0x58, 0x07, 0xF9, 0xAC,
+ 0xC7, 0x3F, 0xBF, 0xDF, 0x83, 0x57, 0xCF, 0x13, 0xED, 0x7C, 0x54, 0x43, 0xFE, 0xD1, 0x9D, 0x6E,
+ 0x88, 0xFF, 0x39, 0x74, 0xBA, 0xE0, 0xD6, 0x4F, 0xD8, 0xE3, 0x46, 0xB4, 0xC7, 0x85, 0x4E, 0x7E,
+ 0x9C, 0x47, 0x83, 0x28, 0x37, 0x75, 0xB9, 0x2D, 0x76, 0xE1, 0xBE, 0xF2, 0xC7, 0x1A, 0xC5, 0x9F,
+ 0x0C, 0xE0, 0xDF, 0x62, 0xB0, 0x60, 0x46, 0x49, 0xB2, 0x32, 0x81, 0xFB, 0xD2, 0x4A, 0x3C, 0x64,
+ 0x20, 0xC6, 0xA9, 0xCD, 0xE1, 0x20, 0xBC, 0xD2, 0x98, 0xB0, 0x5E, 0x94, 0x22, 0xF0, 0x5C, 0x80,
+ 0x3D, 0x58, 0x4A, 0xEE, 0xE7, 0x31, 0x7F, 0xF8, 0xE8, 0xEB, 0x2F, 0x71, 0x84, 0xDD, 0xC2, 0xC1,
+ 0x8A, 0xBC, 0x8F, 0x35, 0x2D, 0xC8, 0x51, 0x30, 0x20, 0x9F, 0xF9, 0x6D, 0x62, 0xD3, 0x16, 0x2E,
+ 0x3F, 0x08, 0x59, 0x74, 0x67, 0xCC, 0xD3, 0x1B, 0xAE, 0x26, 0xFE, 0x52, 0xA7, 0xBE, 0x69, 0xB5,
+ 0x54, 0xDF, 0x65, 0x30, 0x11, 0x50, 0x6E, 0x5E, 0x62, 0x3D, 0x0D, 0xCF, 0x1F, 0xD8, 0xBA, 0x9D,
+ 0x50, 0xB9, 0x28, 0xC9, 0x27, 0x65, 0xFF, 0xD0, 0x06, 0xB7, 0x3B, 0x79, 0x54, 0x3B, 0x39, 0x86,
+ 0xBF, 0xE7, 0xC2, 0x85, 0xF0, 0x88, 0x2B, 0x3C, 0xF7, 0x1E, 0x2E, 0xB5, 0xF6, 0x32, 0x76, 0x68,
+ 0x58, 0x8F, 0x7A, 0x04, 0x2C, 0x03, 0x7B, 0xA8, 0x8C, 0x65, 0xF5, 0xC2, 0x4F, 0xB8, 0x18, 0x68,
+ 0x33, 0xB6, 0x3E, 0x16, 0xD5, 0xE9, 0xF5, 0xCE, 0xE3, 0x2F, 0x82, 0x9A, 0x48, 0x80, 0x1A, 0xDA,
+ 0x61, 0x69, 0x76, 0x97, 0xC5, 0x77, 0xEA, 0x97, 0xA4, 0x89, 0x4A, 0xDD, 0x3E, 0x7F, 0xB5, 0x9F,
+ 0xB9, 0x81, 0x29, 0x40, 0x8A, 0xF9, 0x04, 0x6F, 0xE9, 0xD4, 0x60, 0xB6, 0x88, 0xB3, 0x3E, 0xED,
+ 0xD9, 0x70, 0xB3, 0xD1, 0x38, 0x5E, 0xC3, 0xC8, 0x09, 0xEE, 0xF6, 0x78, 0x20, 0xDB, 0xBA, 0xF8,
+ 0x72, 0x2A, 0x36, 0x7E, 0x1A, 0x0F, 0x4D, 0xCB, 0xFC, 0x02, 0x8F, 0x06, 0x37, 0x5E, 0xC2, 0x69,
+ 0x1E, 0xCF, 0xE0, 0x18, 0x41, 0x72, 0xDC, 0xC1, 0x69, 0x56, 0xE3, 0x06, 0xB9, 0x3C, 0x55, 0x3C,
+ 0xC2, 0xDB, 0x38, 0x16, 0x5E, 0x8D, 0xDB, 0x96, 0x6E, 0x9F, 0x70, 0x52, 0x27, 0x08, 0xC2, 0x1C,
+ 0xBE, 0x19, 0x18, 0xE9, 0x79, 0xC2, 0x15, 0x22, 0x79, 0x06, 0x31, 0x4D, 0x8F, 0x74, 0xBF, 0x69,
+ 0x03, 0x28, 0x3C, 0x50, 0xC5, 0xE3, 0x60, 0xCF, 0x75, 0x3C, 0x25, 0xD5, 0x81, 0xE5, 0xA6, 0x29,
+ 0xBF, 0x33, 0x7F, 0x61, 0xF2, 0x85, 0x61, 0x05, 0xD5, 0x97, 0x41, 0xC9, 0xAA, 0x9A, 0x95, 0xAC,
+ 0x5B, 0x89, 0xAF, 0x60, 0xD1, 0xCF, 0xE2, 0x69, 0xFB, 0x6C, 0xBA, 0x65, 0xED, 0xD6, 0xEB, 0xDA,
+ 0x64, 0x44, 0x24, 0x7C, 0x4C, 0x7B, 0x3E, 0x7F, 0xE0, 0xF6, 0xCA, 0xC3, 0x9F, 0x54, 0x0D, 0xD3,
+ 0xAA, 0xD3, 0x0B, 0x7B, 0x01, 0x7C, 0x4B, 0x2B, 0xF5, 0xDD, 0xE5, 0x69, 0x61, 0x55, 0xCF, 0x35,
+ 0xF4, 0xD6, 0x5C, 0xCF, 0xD2, 0xC9, 0x06, 0xB5, 0x72, 0x28, 0xCF, 0xFB, 0x3D, 0x86, 0x06, 0x90,
+ 0x03, 0x8A, 0x1B, 0x22, 0x9C, 0x06, 0xB8, 0x1E, 0xCB, 0xF2, 0x6A, 0xA6, 0xEE, 0x4A, 0xCA, 0x1D,
+ 0x08, 0x3A, 0x03, 0xDD, 0xB5, 0xF4, 0x10, 0xA0, 0x9F, 0xED, 0x7E, 0x7D, 0xBF, 0x13, 0x03, 0xF3,
+ 0x7F, 0x9B, 0x07, 0xBB, 0x80, 0xE8, 0x5D, 0xEE, 0xAB, 0x36, 0x04, 0x4E, 0xE8, 0xEB, 0xB7, 0x0D,
+ 0x8F, 0xBB, 0x7C, 0xB6, 0x2E, 0xFD, 0xF7, 0x73, 0xD3, 0xB6, 0x01, 0xBF, 0x91, 0xD0, 0x02, 0x07,
+ 0xBA, 0x60, 0x71, 0xBA, 0x9E, 0xAE, 0x53, 0x0B, 0x0F, 0x6E, 0x11, 0xB7, 0xF2, 0x64, 0x95, 0x09,
+ 0x7A, 0x68, 0xFD, 0xEA, 0x50, 0x85, 0xE0, 0x55, 0x69, 0x37, 0xC1, 0xD3, 0x33, 0xF9, 0x8A, 0xA9,
+ 0xB8, 0xC7, 0x12, 0x89, 0x7B, 0x33, 0xEA, 0xC5, 0x3B, 0x53, 0xB5, 0x0E, 0x3A, 0x39, 0xD1, 0x47,
+ 0x3C, 0xF4, 0x26, 0x4D, 0x76, 0x59, 0x0E, 0x2D, 0x2F, 0x6F, 0x38, 0x04, 0xC2, 0xEE, 0x82, 0xC1,
+ 0xE0, 0xFA, 0xF2, 0x93, 0xD8, 0x92, 0xCD, 0x75, 0x37, 0xEC, 0xD6, 0x5D, 0xDC, 0x45, 0x8B, 0x6E,
+ 0xCB, 0x46, 0x3D, 0x10, 0x59, 0x72, 0xD0, 0x35, 0xE5, 0x20, 0xE7, 0x09, 0x46, 0xFB, 0x8A, 0xF6,
+ 0x6A, 0xBC, 0xB4, 0xB6, 0xC3, 0xCD, 0x60, 0x00, 0xB5, 0x80, 0xDB, 0x23, 0x5D, 0x82, 0x41, 0x54,
+ 0x06, 0x9D, 0xCF, 0x76, 0x39, 0x97, 0xC5, 0x2B, 0xD6, 0x9A, 0x00, 0x00, 0x5C, 0x87, 0xEF, 0xF6,
+ 0x43, 0x69, 0xB9, 0x16, 0xDE, 0xBF, 0x35, 0x2E, 0x07, 0x43, 0x72, 0xB7, 0x8A, 0x64, 0x78, 0xE4,
+ 0x75, 0x0B, 0xF2, 0x47, 0xCA, 0x50, 0xC7, 0x32, 0xDE, 0x23, 0xC4, 0x93, 0x84, 0x27, 0xE7, 0x4C,
+ 0x83, 0xEB, 0xC9, 0xF9, 0xB8, 0xB4, 0xC5, 0x0F, 0xCA, 0x2A, 0xC1, 0x6C, 0xCA, 0x83, 0x64, 0x55,
+ 0xE1, 0x1B, 0x30, 0x5F, 0x18, 0x2D, 0xA6, 0x43, 0x1A, 0x45, 0x00, 0x17, 0xE8, 0xF3, 0x17, 0xE8,
+ 0x7E, 0xB1, 0x90, 0xF4, 0xD9, 0x0B, 0xA3, 0x80, 0x24, 0x39, 0x34, 0x16, 0xAD, 0xEF, 0xAE, 0x26,
+ 0x63, 0x45, 0x5B, 0x4C, 0xF6, 0x27, 0x2A, 0xA3, 0xB6, 0xFA, 0xEF, 0x5C, 0x35, 0x99, 0x15, 0xFB,
+ 0xE4, 0x6A, 0x55, 0x1D, 0xAF, 0x5B, 0x2F, 0xB0, 0x21, 0xAB, 0xFD, 0xE4, 0x8A, 0xB0, 0x6A, 0x05,
+ 0x2B, 0x59, 0x08, 0xE6, 0xDB, 0xC2, 0xBE, 0xAD, 0xC4, 0x59, 0x01, 0x7A, 0x25, 0x8B, 0x9B, 0xD5,
+ 0x20, 0x01, 0x3B, 0x08, 0x3F, 0x90, 0x4E, 0x3C, 0x78, 0xEE, 0x41, 0xB8, 0xF4, 0x53, 0xAD, 0x49,
+ 0x0F, 0x5B, 0x99, 0x40, 0xDF, 0x6A, 0xFB, 0x1E, 0x5F, 0xB8, 0x0A, 0xD5, 0x44, 0x12, 0xAB, 0x01,
+ 0x99, 0xBE, 0xEA, 0x12, 0xD5, 0xD4, 0xA1, 0x4E, 0xDD, 0xCA, 0x8F, 0x3E, 0x0C, 0x2E, 0x5A, 0x54,
+ 0x34, 0x21, 0xDB, 0xEC, 0xF8, 0x43, 0x1E, 0xEA, 0xE6, 0xF7, 0x02, 0xD7, 0xB5, 0x7C, 0xB7, 0x83,
+ 0x4A, 0x7D, 0xC0, 0x57, 0x32, 0x56, 0x23, 0xCD, 0x69, 0xDB, 0xA8, 0xEB, 0xB7, 0x21, 0x2B, 0x6B,
+ 0x9C, 0x6F, 0x5F, 0x25, 0xAB, 0x03, 0x4A, 0xA2, 0xDC, 0x48, 0x6A, 0xAE, 0xD4, 0x42, 0x5A, 0xA6,
+ 0x6E, 0xD1, 0xFB, 0x1F, 0xA9, 0x11, 0x59, 0x55, 0x05, 0x15, 0x55, 0x4B, 0xD4, 0x45, 0xE5, 0xB9,
+ 0x92, 0xA8, 0x5A, 0xA6, 0x2E, 0xEA, 0x93, 0x3C, 0x7B, 0xE5, 0x6C, 0x24, 0x45, 0x2C, 0x65, 0x0B,
+ 0x57, 0xD3, 0x9C, 0x80, 0xE0, 0x68, 0x17, 0x57, 0xF8, 0xD1, 0xED, 0x2C, 0x8D, 0xE9, 0x6B, 0xD5,
+ 0xA3, 0xB4, 0x3C, 0x1C, 0x61, 0xE8, 0x8F, 0xE7, 0xF0, 0xDB, 0x6D, 0x8E, 0xC8, 0x5B, 0xD3, 0xFC,
+ 0x8D, 0xF3, 0x17, 0xF6, 0x2F, 0x45, 0x43, 0xAF, 0x9B, 0x8D, 0xF6, 0xDA, 0x3A, 0x49, 0x50, 0x99,
+ 0xAB, 0xA9, 0x05, 0xDA, 0x9F, 0x87, 0x6C, 0x15, 0x02, 0x77, 0x4A, 0x54, 0xF4, 0xE6, 0x49, 0x50,
+ 0xF9, 0x55, 0x7D, 0x25, 0x53, 0xD2, 0x4D, 0x91, 0xC7, 0x41, 0xAB, 0xC9, 0xD0, 0x6B, 0x63, 0x60,
+ 0x30, 0xC3, 0x9F, 0xAC, 0x12, 0x33, 0xE9, 0x26, 0x2B, 0x6B, 0x21, 0x4C, 0x55, 0xB1, 0x34, 0x22,
+ 0x39, 0x54, 0x77, 0xC0, 0x3B, 0xFE, 0x52, 0xDA, 0x06, 0xA1, 0x14, 0x47, 0x3C, 0x4D, 0x84, 0x0A,
+ 0x34, 0x52, 0x9E, 0xCA, 0x89, 0x6F, 0xD2, 0x7A, 0x3C, 0x7E, 0x48, 0x97, 0x6B, 0x84, 0x13, 0x6D,
+ 0x0A, 0xC9, 0x79, 0x8F, 0x8A, 0x0C, 0xD2, 0xFD, 0xB1, 0xE2, 0xA3, 0x3F, 0xEE, 0x2F, 0xCE, 0x04,
+ 0x4B, 0xF8, 0x3A, 0x79, 0x94, 0x15, 0x36, 0x0F, 0xFA, 0x09, 0xEB, 0x06, 0xC4, 0xA7, 0x0F, 0x62,
+ 0xE5, 0xFA, 0xA5, 0x67, 0x7A, 0xBC, 0x4A, 0x52, 0x05, 0x86, 0x00, 0xEE, 0xB8, 0xBB, 0x9E, 0xAC,
+ 0xC7, 0x85, 0x53, 0x2F, 0x88, 0xA6, 0xDE, 0x02, 0x0E, 0x29, 0x59, 0x0E, 0x6D, 0x40, 0xCA, 0x3C,
+ 0xEF, 0x07, 0xC5, 0x5C, 0x1E, 0xCF, 0x91, 0xF5, 0xF5, 0x46, 0xDF, 0x71, 0x6F, 0x45, 0xE9, 0x60,
+ 0x65, 0x2E, 0xAE, 0x53, 0xE9, 0xAC, 0xE4, 0x07, 0x12, 0xE4, 0xA7, 0xBE, 0x9A, 0xEA, 0x55, 0x4E,
+ 0x12, 0x15, 0x65, 0x5B, 0x7B, 0xBC, 0xD4, 0x4C, 0xFC, 0x2E, 0x39, 0xFD, 0xB9, 0x39, 0x76, 0xCD,
+ 0xE2, 0x1F, 0xBA, 0xFA, 0x0E, 0x53, 0x31, 0xB2, 0xF6, 0xCE, 0x37, 0xF5, 0xF8, 0x9A, 0x3B, 0x95,
+ 0x90, 0x3D, 0x04, 0x35, 0x8A, 0x06, 0xB3, 0xD1, 0x3C, 0x7A, 0x7F, 0x34, 0xC3, 0xD7, 0x0F, 0x47,
+ 0x89, 0x3F, 0x9A, 0xC0, 0xA3, 0x12, 0xE3, 0xC9, 0x68, 0x32, 0x83, 0xDF, 0x13, 0xB8, 0x0A, 0x36,
+ 0xF0, 0x47, 0x8B, 0x1C, 0x7E, 0x0D, 0xF0, 0xD7, 0x08, 0xB2, 0xA3, 0xD1, 0x22, 0x19, 0xCD, 0xFC,
+ 0xD1, 0x2C, 0x82, 0x24, 0xF8, 0x1D, 0xCE, 0xE1, 0x37, 0x5C, 0x9B, 0x0B, 0xA7, 0xD0, 0x00, 0x8F,
+ 0x19, 0x66, 0x01, 0xD3, 0x57, 0x28, 0x45, 0x58, 0xC1, 0xDF, 0xEF, 0x7F, 0xAF, 0xE7, 0xAB, 0xEA,
+ 0x17, 0xF0, 0x98, 0x1D, 0x83, 0x05, 0xB5, 0xA4, 0xED, 0x81, 0x2B, 0x4A, 0x5E, 0x53, 0x47, 0x8C,
+ 0xD8, 0x98, 0x6A, 0x69, 0x7E, 0x3A, 0xD8, 0x71, 0x70, 0x02, 0x4A, 0x68, 0x74, 0x85, 0xD6, 0xA7,
+ 0x0C, 0x78, 0x94, 0xD0, 0x45, 0x6B, 0xA5, 0xA6, 0xE6, 0x99, 0x4C, 0xBD, 0x46, 0xAF, 0xCE, 0x33,
+ 0x4B, 0x7E, 0xEB, 0xE7, 0x80, 0x8D, 0xA1, 0x21, 0xE4, 0xAD, 0x66, 0x6E, 0x6B, 0x04, 0xF9, 0x01,
+ 0xAC, 0x96, 0xF8, 0xE1, 0x44, 0x2D, 0xE1, 0x63, 0x66, 0x46, 0x10, 0x9B, 0x3F, 0xDB, 0x35, 0x6C,
+ 0x9E, 0xE4, 0x82, 0xCF, 0x9E, 0x53, 0x09, 0xFE, 0xD8, 0x57, 0xB7, 0x60, 0x6D, 0xB5, 0xF5, 0x2F,
+ 0xCD, 0x6B, 0x56, 0x5A, 0x66, 0x42, 0x1E, 0x6B, 0xFA, 0x83, 0x37, 0x7D, 0xFF, 0x8E, 0xCF, 0x58,
+ 0x7A, 0xBF, 0xB3, 0x7E, 0xFF, 0x10, 0xB8, 0x6B, 0x7B, 0x64, 0xD4, 0xA3, 0xBF, 0x68, 0xA0, 0xD5,
+ 0x4F, 0x01, 0xDB, 0x9F, 0x23, 0xF5, 0xFA, 0x03, 0x1C, 0x75, 0x51, 0x27, 0x7C, 0xA3, 0xA4, 0xBF,
+ 0x33, 0xBC, 0x29, 0x7B, 0xD5, 0x5D, 0x40, 0xC1, 0x29, 0x1D, 0xD0, 0xC2, 0xC4, 0x96, 0xED, 0x85,
+ 0xBB, 0xB2, 0x37, 0x6C, 0x9D, 0xBB, 0x57, 0x45, 0x6C, 0xE4, 0x5A, 0x11, 0x35, 0xE8, 0xEF, 0x63,
+ 0x3B, 0xFD, 0x41, 0x15, 0x47, 0x29, 0xF4, 0x82, 0xB7, 0x36, 0x6D, 0x71, 0xCC, 0xEB, 0xAC, 0xD4,
+ 0xC1, 0x97, 0x34, 0x80, 0xC6, 0x3D, 0x8E, 0xBC, 0x86, 0x60, 0x19, 0x67, 0x49, 0x34, 0x6E, 0x5D,
+ 0x89, 0x95, 0xEB, 0xCA, 0x95, 0x5E, 0x74, 0xF1, 0x66, 0x45, 0x23, 0x70, 0x91, 0x97, 0xB6, 0x9F,
+ 0x83, 0xD4, 0x94, 0xD0, 0x41, 0x96, 0x5A, 0xF6, 0x7E, 0xA5, 0x44, 0x0D, 0x4C, 0x2B, 0x33, 0xBF,
+ 0xFB, 0x2C, 0x24, 0x4D, 0xA2, 0xE9, 0x64, 0x8A, 0x59, 0xFF, 0x63, 0xE6, 0x21, 0xE1, 0xD8, 0x83,
+ 0x67, 0x02, 0x66, 0xCB, 0xF6, 0x79, 0x08, 0x53, 0xE8, 0x07, 0xCD, 0x68, 0x9E, 0x92, 0xDB, 0x3E,
+ 0x1F, 0xB1, 0xB2, 0xE9, 0xA6, 0x74, 0x9C, 0x97, 0x64, 0x7B, 0x87, 0x99, 0x09, 0x21, 0xE2, 0x70,
+ 0x29, 0x35, 0xDE, 0xDF, 0x61, 0x6E, 0x12, 0x84, 0xF0, 0xEF, 0xF9, 0x01, 0x1B, 0x2D, 0xE0, 0x8F,
+ 0x74, 0x65, 0x8F, 0x7C, 0xB6, 0x8D, 0x4C, 0xA8, 0xF8, 0xF2, 0xF3, 0x44, 0x09, 0x30, 0x9C, 0x3D,
+ 0x3F, 0x48, 0xAE, 0xF8, 0x17, 0x7E, 0xBA, 0x08, 0xBA, 0xF5, 0xE7, 0x5F, 0xE6, 0xE3, 0x00, 0xCB,
+ 0x78, 0x85, 0x0E, 0x07, 0xA6, 0xA3, 0x05, 0x74, 0xF1, 0xB3, 0xDD, 0x68, 0xF2, 0xFE, 0x0C, 0xC6,
+ 0x05, 0xD3, 0xA6, 0xE7, 0xD6, 0x99, 0x2F, 0x46, 0x21, 0x61, 0x3F, 0x9A, 0xD5, 0xE2, 0x71, 0x81,
+ 0x58, 0x1D, 0x54, 0xE2, 0x7F, 0xE2, 0x5C, 0x27, 0xDB, 0x77, 0xF7, 0x60, 0xDC, 0xB1, 0x38, 0xA9,
+ 0xD9, 0xBD, 0xDA, 0x3A, 0x35, 0x1E, 0x8D, 0x3D, 0xE6, 0x3B, 0xB5, 0xC7, 0x37, 0xB9, 0xBA, 0xFC,
+ 0x7F, 0x93, 0x39, 0x4F, 0xB6, 0xEF, 0x9E, 0xF5, 0xB4, 0x85, 0xA6, 0x81, 0xA1, 0xC3, 0x5C, 0xC4,
+ 0xAD, 0x8C, 0xE3, 0xDC, 0xC7, 0xA9, 0xC6, 0xFE, 0xE5, 0xFF, 0x9F, 0xFF, 0xFC, 0xD1, 0xF3, 0x9F,
+ 0xFF, 0x28, 0xA4, 0xFE, 0x5D, 0xE6, 0x53, 0x0E, 0x18, 0x4A, 0x89, 0x7F, 0x77, 0x08, 0x55, 0x06,
+ 0xE4, 0xDD, 0x40, 0xC4, 0x69, 0x9D, 0xD0, 0x48, 0x63, 0xDD, 0x73, 0x66, 0x55, 0x17, 0xD0, 0xE7,
+ 0x56, 0x7D, 0xAA, 0x73, 0x9C, 0x5D, 0x65, 0x7B, 0xF7, 0xF9, 0x95, 0xBB, 0x1D, 0xEF, 0x99, 0x61,
+ 0x59, 0x8B, 0xDE, 0x24, 0xAD, 0x1D, 0xE7, 0x58, 0xD9, 0xFE, 0xC9, 0x66, 0x59, 0xD9, 0xDE, 0x61,
+ 0x9E, 0x95, 0xED, 0x5D, 0x67, 0x4E, 0x75, 0xD7, 0xEC, 0x5E, 0x42, 0xE9, 0xFD, 0x9A, 0x1D, 0x66,
+ 0x37, 0xB9, 0x5D, 0xE6, 0x5B, 0xD9, 0xBE, 0xDF, 0xDC, 0xA9, 0xD6, 0xA1, 0x6F, 0xB9, 0x46, 0x13,
+ 0xF3, 0xAC, 0x0B, 0x6F, 0xB1, 0x1A, 0x9F, 0x57, 0x7F, 0xE2, 0x1D, 0x6D, 0x92, 0x92, 0xC2, 0x79,
+ 0xBD, 0x65, 0xDC, 0x3C, 0xE7, 0xA4, 0xBE, 0x99, 0x58, 0xC0, 0x91, 0x5C, 0x39, 0x52, 0x1E, 0xD3,
+ 0x6B, 0x9E, 0xC6, 0x3D, 0x57, 0xA8, 0x64, 0xF2, 0xF3, 0x27, 0xC5, 0xF5, 0x54, 0x35, 0xC1, 0xE9,
+ 0x00, 0x11, 0xE3, 0x43, 0x62, 0x8E, 0x7B, 0xFD, 0xA6, 0x59, 0xD9, 0x3F, 0xE4, 0x18, 0x1A, 0x68,
+ 0x6D, 0xFD, 0xD9, 0x33, 0x9C, 0xCA, 0x4E, 0xAA, 0x96, 0x1A, 0x55, 0x78, 0xB9, 0x80, 0xFA, 0x0D,
+ 0x7F, 0xB3, 0x80, 0xF9, 0x91, 0xF5, 0xE5, 0xC4, 0x85, 0x84, 0x5D, 0xB3, 0xD0, 0x0B, 0x66, 0x63,
+ 0x2F, 0x98, 0xD3, 0xD7, 0x40, 0x09, 0xDF, 0x51, 0xFD, 0x38, 0x23, 0xE5, 0xD7, 0x7C, 0xDB, 0x64,
+ 0x28, 0x4F, 0xE1, 0x54, 0xF7, 0x26, 0x65, 0x60, 0x78, 0x69, 0x41, 0x7F, 0x51, 0x01, 0x13, 0x35,
+ 0x07, 0x62, 0xB7, 0xCD, 0xB2, 0x9F, 0x8D, 0xDE, 0x7E, 0x7D, 0xFC, 0x46, 0xA0, 0xBC, 0xBB, 0xB6,
+ 0x8C, 0xDE, 0x9A, 0xBE, 0x1D, 0x4A, 0x5C, 0xA8, 0x45, 0x3A, 0x78, 0xC1, 0x4B, 0x2F, 0xCB, 0x29,
+ 0xE3, 0x25, 0x57, 0xA0, 0x1B, 0x4F, 0x64, 0xEE, 0x89, 0x5F, 0x68, 0xFE, 0xDD, 0x52, 0x6B, 0xFD,
+ 0x6F, 0xA7, 0xED, 0xA9, 0x58, 0xEC, 0x29, 0x5C, 0x59, 0x2E, 0x9E, 0x5D, 0xF7, 0x80, 0x76, 0xB9,
+ 0x47, 0x94, 0x81, 0xAC, 0x4B, 0x9D, 0x86, 0x1F, 0xDC, 0x7A, 0x55, 0xCC, 0x51, 0x37, 0xE4, 0xEF,
+ 0xD6, 0xB7, 0x45, 0x05, 0x6E, 0xCD, 0x76, 0x45, 0x38, 0x95, 0xAE, 0x8E, 0x21, 0x67, 0x25, 0xE6,
+ 0x38, 0xA8, 0xC6, 0x1A, 0x54, 0xBB, 0xA8, 0xC0, 0xB1, 0x75, 0x18, 0xD7, 0x26, 0x42, 0x64, 0x59,
+ 0xEA, 0xD4, 0xDA, 0x7E, 0x8B, 0xF9, 0x7A, 0x99, 0x04, 0xA6, 0xA7, 0x85, 0x02, 0x94, 0x36, 0x4F,
+ 0x0B, 0xC9, 0x89, 0xB4, 0x1E, 0x7E, 0x98, 0x3A, 0xCB, 0x9C, 0xBC, 0x85, 0xFF, 0x1A, 0x58, 0xBD,
+ 0xF3, 0xCE, 0xEB, 0x6F, 0x4C, 0x17, 0x8C, 0x15, 0x4F, 0x7C, 0x6B, 0xFC, 0x7A, 0x24, 0xB2, 0x6A,
+ 0xA2, 0xA6, 0x93, 0xE1, 0xDB, 0x6F, 0x2C, 0x97, 0x93, 0x39, 0x63, 0x28, 0xD7, 0xA2, 0x07, 0x8E,
+ 0x5C, 0x83, 0x27, 0x7F, 0xA5, 0x34, 0x4F, 0x52, 0x61, 0xD7, 0x8B, 0xD5, 0xD0, 0x3C, 0xD1, 0xC2,
+ 0x0B, 0x26, 0x70, 0xE3, 0x88, 0x3D, 0x7E, 0xB8, 0x78, 0xBA, 0xE3, 0xB5, 0x2B, 0xC0, 0xA3, 0x88,
+ 0xA7, 0xEA, 0x71, 0xC4, 0xF3, 0x74, 0x77, 0x7B, 0x12, 0xE5, 0x3B, 0xF5, 0xA1, 0xF6, 0xED, 0xD6,
+ 0x8A, 0xD3, 0xE9, 0xBA, 0xE9, 0x79, 0x6A, 0x50, 0x39, 0xE9, 0xE9, 0x32, 0x4C, 0xD6, 0x9A, 0x8B,
+ 0x73, 0xD4, 0x43, 0x8A, 0xCB, 0x52, 0xA7, 0xB7, 0x80, 0x12, 0x3F, 0xDD, 0x92, 0xDB, 0x91, 0x27,
+ 0x92, 0x8A, 0xEA, 0x93, 0xFA, 0x5B, 0xB9, 0xF0, 0x9D, 0x28, 0x7D, 0x6F, 0x48, 0xE2, 0xE2, 0xD2,
+ 0x01, 0x05, 0xD3, 0x79, 0xA4, 0xB5, 0x6C, 0x30, 0x99, 0x25, 0x93, 0x48, 0x8F, 0x23, 0x91, 0xB9,
+ 0x27, 0x7E, 0x71, 0xE9, 0x80, 0x5A, 0x6A, 0xEA, 0xE8, 0x80, 0xA0, 0x41, 0xA6, 0xE4, 0x6D, 0x37,
+ 0x7B, 0xFC, 0x70, 0xB1, 0x74, 0x3F, 0xB3, 0x8B, 0xCD, 0x23, 0x87, 0xA7, 0xA9, 0x71, 0xC3, 0x73,
+ 0xFA, 0xF5, 0x3F, 0x54, 0x33, 0x55, 0xDD, 0x68, 0x36, 0x8D, 0x36, 0x76, 0x15, 0xB8, 0x31, 0x3B,
+ 0x15, 0xA9, 0xA9, 0x74, 0x75, 0x0C, 0x39, 0x52, 0xA8, 0x38, 0xA8, 0xE6, 0x12, 0x28, 0x5A, 0xB3,
+ 0x70, 0x76, 0x3C, 0x4A, 0xC4, 0x54, 0xC7, 0x18, 0xE9, 0xF4, 0x6E, 0x72, 0xB5, 0x03, 0x3F, 0x40,
+ 0xDE, 0xF8, 0x06, 0x57, 0x12, 0x6F, 0xEA, 0x07, 0xD6, 0xE4, 0x44, 0xCE, 0x82, 0x46, 0x47, 0x3B,
+ 0xA3, 0x08, 0xAE, 0xE0, 0xD8, 0x84, 0x0A, 0xA3, 0x70, 0x0A, 0x17, 0x49, 0x84, 0x7A, 0x74, 0x34,
+ 0x9C, 0xBD, 0xE6, 0x13, 0xCD, 0xB9, 0xBB, 0x8E, 0x8E, 0x8E, 0x25, 0x80, 0x6E, 0x3F, 0xF4, 0xC2,
+ 0xF1, 0xC4, 0x1A, 0x17, 0x5C, 0x20, 0xDD, 0x97, 0x74, 0x69, 0x99, 0xFF, 0xF0, 0x04, 0x39, 0x16,
+ 0x9A, 0x64, 0xD5, 0x5B, 0x3A, 0xF4, 0x4B, 0xD3, 0xE9, 0x26, 0xEA, 0xB4, 0xA1, 0x2A, 0x73, 0x47,
+ 0x14, 0x70, 0x12, 0x4D, 0x7E, 0x35, 0xD9, 0xE4, 0xFC, 0x4D, 0xE6, 0x3D, 0x9E, 0xAF, 0x18, 0xBE,
+ 0xE1, 0xC5, 0xDD, 0x9E, 0xD7, 0x5D, 0x27, 0x3D, 0xA1, 0xC3, 0xB2, 0xEB, 0x3D, 0xDA, 0xB9, 0x6C,
+ 0x36, 0x49, 0x30, 0xAE, 0x11, 0x56, 0x4E, 0x94, 0xB8, 0xB8, 0x78, 0x3E, 0x14, 0x8B, 0xC3, 0x44,
+ 0xE7, 0x35, 0x0F, 0xC7, 0xBA, 0xE7, 0x8B, 0xCC, 0x3D, 0xF1, 0x8B, 0x8B, 0xFF, 0xB7, 0xD4, 0xD4,
+ 0xB1, 0x30, 0x18, 0xCC, 0x61, 0xEC, 0x3B, 0xF1, 0x66, 0x76, 0xF7, 0xE7, 0x52, 0xE9, 0x7E, 0x64,
+ 0x97, 0x9A, 0xC7, 0x01, 0x4F, 0x53, 0x43, 0x81, 0xE7, 0xF4, 0x8B, 0x06, 0x50, 0x2C, 0x8D, 0x96,
+ 0x9D, 0x76, 0x35, 0xA8, 0xD0, 0x16, 0x10, 0x3A, 0x95, 0xAE, 0x8E, 0x21, 0x47, 0x8A, 0x0C, 0x07,
+ 0xD5, 0x5C, 0x82, 0x43, 0x6F, 0x15, 0xCE, 0x8D, 0x87, 0x07, 0x4F, 0x75, 0x8C, 0x90, 0x4E, 0xDF,
+ 0xA6, 0xB7, 0xC1, 0xB4, 0x76, 0x2D, 0x7C, 0xB9, 0x59, 0x5F, 0xFC, 0x15, 0x99, 0xB8, 0x8C, 0x9B,
+ 0xD6, 0xEB, 0x30, 0x8D, 0xD6, 0xEA, 0x95, 0x3C, 0xE3, 0x30, 0x8E, 0xE6, 0x7A, 0x7C, 0x08, 0xBC,
+ 0x3D, 0xE1, 0xB3, 0xCB, 0xA8, 0xA9, 0xA5, 0x9E, 0xAE, 0x65, 0x73, 0x78, 0x96, 0x26, 0xF2, 0x96,
+ 0x73, 0x6B, 0x74, 0x70, 0x99, 0x74, 0x9F, 0xB2, 0x89, 0xDC, 0x84, 0x06, 0x4F, 0x52, 0x22, 0x83,
+ 0x67, 0xF4, 0x1B, 0x2F, 0x51, 0x9D, 0x14, 0x45, 0xE3, 0x69, 0xB8, 0x88, 0x26, 0xED, 0xD2, 0xDB,
+ 0xE3, 0x42, 0x27, 0xD2, 0x35, 0xD1, 0x33, 0xA4, 0xA0, 0xE8, 0xD6, 0xCA, 0x29, 0x26, 0x94, 0xB6,
+ 0xE0, 0xCC, 0x78, 0x48, 0xF0, 0x44, 0xC7, 0x71, 0x52, 0xA7, 0x33, 0x93, 0xDB, 0x82, 0xDA, 0xA2,
+ 0xAA, 0xB9, 0x3B, 0x88, 0xF3, 0xE0, 0x89, 0x02, 0x0F, 0xA7, 0xFE, 0x02, 0x8A, 0x6C, 0x6A, 0x3F,
+ 0x95, 0x13, 0xF5, 0x78, 0xE0, 0xAC, 0x3D, 0xFE, 0xD1, 0xA9, 0xAF, 0xB0, 0xD7, 0xD2, 0xD5, 0x57,
+ 0x04, 0x1E, 0xAC, 0x49, 0xC2, 0xFF, 0xA8, 0x2D, 0x1E, 0xB8, 0x4C, 0xBA, 0x2B, 0xE9, 0x12, 0xD7,
+ 0xC1, 0xC0, 0x53, 0xE4, 0x58, 0xE0, 0xE9, 0x3D, 0xFB, 0x08, 0x37, 0x5B, 0x6A, 0x82, 0x5B, 0x23,
+ 0x41, 0xA3, 0xD1, 0x95, 0xD0, 0xD2, 0xA5, 0x30, 0xE8, 0x54, 0xC8, 0x29, 0x0A, 0x94, 0x36, 0xE0,
+ 0xDC, 0x78, 0x18, 0xF0, 0x34, 0xD7, 0x7E, 0xA1, 0xC3, 0x83, 0xC9, 0x15, 0x52, 0x2D, 0x81, 0xC4,
+ 0x37, 0x20, 0x38, 0x0B, 0x9E, 0xC8, 0x59, 0xB8, 0xF4, 0x09, 0x41, 0x12, 0x6C, 0x42, 0x6D, 0xCA,
+ 0x17, 0x07, 0x28, 0x34, 0x2C, 0xE6, 0x36, 0x9C, 0xBD, 0xE6, 0x93, 0x4B, 0x7F, 0xD0, 0x52, 0x47,
+ 0xC7, 0x2C, 0x7A, 0xE6, 0xC1, 0x1A, 0xE1, 0xDC, 0xEE, 0xFE, 0x5C, 0x1C, 0xDD, 0x7D, 0x74, 0x59,
+ 0x99, 0xDB, 0xF0, 0x04, 0xD9, 0xF5, 0x9B, 0xE4, 0x7E, 0x9D, 0x00, 0x55, 0x44, 0x9B, 0xFB, 0x61,
+ 0xA5, 0xDB, 0x64, 0xEE, 0xEC, 0x02, 0x6A, 0x12, 0x4D, 0x7E, 0x35, 0xD9, 0xE4, 0xF5, 0x4D, 0xE6,
+ 0x3D, 0x4E, 0x2F, 0x99, 0xBD, 0xE1, 0xC4, 0x1D, 0xBE, 0x49, 0x72, 0x44, 0xFD, 0x4E, 0x67, 0x65,
+ 0x3B, 0x2A, 0xCD, 0x56, 0x86, 0x6D, 0xA3, 0x81, 0x27, 0x1A, 0xCB, 0x71, 0x7F, 0xE7, 0x84, 0x4E,
+ 0x17, 0xD2, 0x50, 0x52, 0xDD, 0xDF, 0x4D, 0x95, 0x78, 0xA6, 0xC4, 0xF6, 0xB5, 0x6F, 0x1F, 0xCE,
+ 0x7C, 0x56, 0x56, 0xBE, 0xED, 0x23, 0x1C, 0xCE, 0xBC, 0x73, 0x7B, 0xC2, 0x45, 0x32, 0xEE, 0xF3,
+ 0x7A, 0x9E, 0xEA, 0x1B, 0xC4, 0x75, 0x8C, 0x94, 0x4C, 0xBC, 0x3B, 0xF6, 0x85, 0xD8, 0x6E, 0xCD,
+ 0x40, 0x71, 0x38, 0xBF, 0xD9, 0x2B, 0xE3, 0xA7, 0xA5, 0xF6, 0x30, 0x09, 0xB5, 0xB7, 0xAB, 0x61,
+ 0x1A, 0x6A, 0x3D, 0x4B, 0x0A, 0x29, 0x77, 0x23, 0xB5, 0xD0, 0x4B, 0xD1, 0x78, 0x73, 0xDB, 0x21,
+ 0xE4, 0xDE, 0xA0, 0xD7, 0xCE, 0x83, 0x4E, 0xCF, 0xE5, 0xF1, 0x27, 0x1D, 0x03, 0xD4, 0xBA, 0x17,
+ 0x2C, 0x55, 0x63, 0xDD, 0x0D, 0xE1, 0x8D, 0xC8, 0x97, 0xD2, 0xA5, 0x92, 0xBD, 0x37, 0x3F, 0x68,
+ 0xD4, 0xE9, 0x5C, 0x3B, 0xA2, 0x8E, 0x6F, 0x20, 0x18, 0x92, 0x3B, 0x23, 0xAF, 0x09, 0xBC, 0x70,
+ 0x3A, 0x85, 0xB5, 0x8F, 0x05, 0xD8, 0x3B, 0x72, 0x71, 0xB3, 0xEE, 0x8D, 0x0D, 0x57, 0xF9, 0x9A,
+ 0xF8, 0xD3, 0x73, 0xDD, 0x22, 0x90, 0xD2, 0xBA, 0xC6, 0x60, 0xE7, 0x62, 0xBD, 0x83, 0xCE, 0x8E,
+ 0xC1, 0xA5, 0xD0, 0xB7, 0xEB, 0x4F, 0x69, 0xFA, 0x58, 0xA1, 0xB5, 0x04, 0x55, 0xC0, 0xA9, 0x33,
+ 0x0B, 0x70, 0xAB, 0xE3, 0xF9, 0x7D, 0xC8, 0xF6, 0x46, 0x74, 0x86, 0xD6, 0x38, 0x33, 0xEF, 0x95,
+ 0x70, 0x9B, 0xF6, 0x8A, 0x34, 0xB6, 0x4B, 0xE2, 0xBA, 0x62, 0xCC, 0xCA, 0xF5, 0xDA, 0x17, 0xB1,
+ 0xF3, 0xEC, 0x88, 0x32, 0xBE, 0xD1, 0xA0, 0x27, 0xBA, 0x18, 0xB9, 0x79, 0x0F, 0xC6, 0x29, 0xB8,
+ 0x08, 0x5F, 0x07, 0x07, 0x6B, 0x11, 0x8A, 0x87, 0x95, 0x9E, 0xE7, 0x12, 0x54, 0x94, 0xD2, 0x29,
+ 0xA4, 0x1C, 0xAD, 0x6A, 0xD0, 0xD4, 0x71, 0x73, 0xC4, 0x44, 0xDD, 0xAA, 0x35, 0xA5, 0x70, 0xD7,
+ 0xBD, 0x85, 0xBE, 0x47, 0x20, 0xA9, 0x6D, 0xAC, 0x33, 0x33, 0x07, 0x91, 0xBE, 0x95, 0x22, 0xD9,
+ 0xB0, 0x47, 0x08, 0x91, 0x4D, 0x14, 0xD7, 0xD5, 0x67, 0x56, 0xC8, 0x79, 0xDB, 0xA4, 0x93, 0xA1,
+ 0x2D, 0x78, 0xE8, 0x86, 0x84, 0x96, 0xE2, 0x62, 0xD2, 0x20, 0x82, 0x4E, 0x09, 0x26, 0x97, 0x13,
+ 0xA7, 0xB1, 0x0F, 0xE6, 0xDA, 0xE9, 0x44, 0x66, 0x61, 0x98, 0xE3, 0xE8, 0x19, 0x0E, 0xB1, 0x22,
+ 0x6C, 0x47, 0xFC, 0x2E, 0x16, 0x34, 0xA8, 0xE6, 0xB2, 0x79, 0xA2, 0x93, 0x5A, 0xD5, 0xA4, 0xD9,
+ 0x8E, 0xCA, 0xDA, 0x88, 0x7B, 0x44, 0x86, 0xD2, 0x8C, 0x1A, 0x2B, 0x73, 0x5C, 0xE8, 0x7B, 0x2D,
+ 0xDC, 0x5E, 0x3D, 0x82, 0xA2, 0xDE, 0x65, 0x71, 0x5D, 0x73, 0x66, 0xE5, 0x7A, 0xEE, 0xAB, 0xD8,
+ 0x78, 0x76, 0x84, 0x06, 0xDF, 0xA9, 0xD0, 0x13, 0x5D, 0x2C, 0x8B, 0x87, 0x6D, 0xC1, 0x32, 0xF2,
+ 0x9C, 0xA6, 0x4B, 0x8C, 0x6F, 0xA7, 0x07, 0xB5, 0x09, 0xC5, 0x03, 0x45, 0xCF, 0x73, 0x88, 0x15,
+ 0x46, 0xE9, 0x14, 0x2E, 0x8E, 0x56, 0x35, 0x68, 0xEA, 0xB8, 0xBB, 0x62, 0xA4, 0x6E, 0xD3, 0x9A,
+ 0x52, 0xB8, 0xEB, 0xDE, 0x42, 0xDF, 0x23, 0x7A, 0xD4, 0x36, 0xD6, 0x99, 0x99, 0xE3, 0x47, 0xDF,
+ 0x8C, 0x91, 0x6C, 0xD8, 0x23, 0x84, 0xD8, 0x36, 0x8C, 0xEB, 0x1A, 0x35, 0x2B, 0xD6, 0x63, 0xE3,
+ 0xA5, 0x93, 0xA5, 0x2D, 0x80, 0x9A, 0xDD, 0x0C, 0x3D, 0xCD, 0xC9, 0xB4, 0xF5, 0xE3, 0xF3, 0x2E,
+ 0xE1, 0x43, 0xD9, 0x76, 0x7A, 0x93, 0x5D, 0xA2, 0x26, 0x76, 0xF4, 0x2C, 0x87, 0xD0, 0xA1, 0x84,
+ 0x4E, 0x23, 0x32, 0x47, 0x73, 0x1A, 0x94, 0x74, 0xDB, 0x7E, 0x31, 0x11, 0xB7, 0x28, 0x4C, 0x09,
+ 0x9C, 0xD5, 0xB6, 0x93, 0xF7, 0x09, 0x1A, 0xB9, 0x65, 0x75, 0x5E, 0xE6, 0x98, 0xD1, 0x76, 0x6B,
+ 0x24, 0xE3, 0xF5, 0x08, 0x19, 0xBA, 0x4F, 0xE3, 0xBA, 0x9E, 0xCD, 0x4A, 0xF5, 0xD8, 0x99, 0xB1,
+ 0x73, 0x6C, 0x0F, 0x18, 0xB6, 0xDF, 0xA1, 0x27, 0x39, 0x19, 0x75, 0xB2, 0x80, 0x4E, 0x7C, 0x09,
+ 0xE7, 0x93, 0x3B, 0x8D, 0xC7, 0x08, 0xDF, 0x4E, 0x27, 0xB2, 0x09, 0x54, 0x47, 0x8B, 0x9E, 0xE3,
+ 0x10, 0x2C, 0x84, 0xCE, 0xAD, 0x97, 0x71, 0xB3, 0xA4, 0x41, 0x3F, 0xA7, 0xFD, 0x19, 0x03, 0xAD,
+ 0x5D, 0x57, 0x9A, 0xEF, 0xAA, 0xB1, 0x95, 0xBA, 0x4F, 0x98, 0x28, 0x2D, 0xAA, 0x33, 0xB3, 0xC4,
+ 0x89, 0xBE, 0x9D, 0xC3, 0xED, 0xD6, 0x23, 0x4C, 0xC8, 0x46, 0x4E, 0xDF, 0x55, 0x70, 0xD7, 0xAD,
+ 0x9B, 0x4E, 0x86, 0xB6, 0x20, 0xA1, 0xDB, 0x22, 0x5A, 0x8A, 0x8B, 0x41, 0xA3, 0xC8, 0x8B, 0xE6,
+ 0xDE, 0x24, 0x70, 0x89, 0x0F, 0xCC, 0xB3, 0xD3, 0x71, 0xCC, 0xA2, 0x30, 0x87, 0xD1, 0x33, 0x1C,
+ 0x62, 0x03, 0x93, 0x39, 0x75, 0x23, 0x8E, 0xF6, 0x33, 0xA8, 0xE6, 0xB2, 0x81, 0xA3, 0x93, 0x5A,
+ 0xD5, 0xA4, 0xD9, 0x8E, 0xCA, 0xDA, 0x88, 0x7B, 0x44, 0x85, 0xD4, 0x88, 0x1A, 0x23, 0x73, 0x44,
+ 0x28, 0xFB, 0x3D, 0xEE, 0x97, 0x17, 0xD7, 0x5B, 0xA6, 0xFB, 0xAF, 0x6F, 0xEA, 0xCB, 0x55, 0xD2,
+ 0xA2, 0xB5, 0xF6, 0xEA, 0x14, 0x70, 0x43, 0x25, 0xAE, 0x9A, 0x73, 0x50, 0xBA, 0x8E, 0x78, 0xBA,
+ 0x48, 0x62, 0x9E, 0xCB, 0x05, 0x6F, 0x92, 0x2C, 0xCB, 0x76, 0xA4, 0x8C, 0x7C, 0x38, 0x28, 0x2B,
+ 0xB3, 0x7D, 0x82, 0x73, 0x3C, 0x39, 0xCF, 0xAA, 0xE0, 0x3C, 0xE1, 0xCB, 0x13, 0x1D, 0xB8, 0xB9,
+ 0x89, 0x53, 0x74, 0xD3, 0x8F, 0xEC, 0xA6, 0x6F, 0x43, 0x61, 0xF3, 0xC4, 0xA5, 0xF3, 0x8B, 0x50,
+ 0x98, 0x97, 0xFE, 0x26, 0x14, 0x49, 0x26, 0x2F, 0x83, 0x13, 0x07, 0x1B, 0x36, 0x87, 0xFA, 0xC2,
+ 0xB4, 0x1D, 0xCC, 0x96, 0xC7, 0xC7, 0x4A, 0xCC, 0x96, 0x4E, 0x28, 0x69, 0x28, 0x40, 0xBD, 0xFA,
+ 0x74, 0xF4, 0xF1, 0x03, 0x6E, 0xA5, 0x4D, 0x0E, 0xBE, 0xB7, 0x83, 0x57, 0xDD, 0xD0, 0x5E, 0x7C,
+ 0x4D, 0x8C, 0xD2, 0xC0, 0x3B, 0xBA, 0xF4, 0x5D, 0x2E, 0xF7, 0xE3, 0xF9, 0x79, 0x3D, 0xBA, 0x06,
+ 0x4D, 0xA0, 0x78, 0xE4, 0x13, 0xDC, 0xD6, 0x4B, 0x3F, 0x90, 0x0B, 0x58, 0xE9, 0x47, 0xD3, 0xB9,
+ 0xC2, 0x8F, 0xDA, 0x46, 0xAF, 0x7E, 0x20, 0xAD, 0x46, 0xB3, 0x5A, 0xC5, 0x1B, 0x78, 0x27, 0xCB,
+ 0xFC, 0xFA, 0xA0, 0xF4, 0xC6, 0x67, 0x38, 0xD5, 0xAF, 0x25, 0xAC, 0x53, 0xF1, 0x8B, 0x8D, 0x10,
+ 0x1C, 0xAB, 0x67, 0x9E, 0x11, 0xCE, 0xAE, 0x04, 0x47, 0x02, 0xEF, 0x10, 0x6F, 0xA8, 0xA7, 0x2F,
+ 0xE0, 0xD3, 0x74, 0xDB, 0xED, 0xDF, 0xCD, 0x0D, 0xE3, 0xEC, 0x3B, 0xAE, 0xDD, 0x56, 0x44, 0xD7,
+ 0x06, 0x15, 0xC7, 0xD3, 0xB5, 0xD6, 0x49, 0x14, 0x7F, 0xFC, 0x28, 0x9F, 0xE5, 0x69, 0x38, 0x3E,
+ 0xA6, 0x39, 0x8E, 0x07, 0x0F, 0x3D, 0x44, 0xAF, 0xE0, 0xE7, 0x22, 0x43, 0x9E, 0x78, 0x59, 0xCE,
+ 0x48, 0xBA, 0x1B, 0x7D, 0x35, 0x56, 0x5E, 0x32, 0xB4, 0xBF, 0x5E, 0x89, 0x45, 0x82, 0x97, 0x31,
+ 0xAB, 0x13, 0xBD, 0xC6, 0x9F, 0xD4, 0xD2, 0x7D, 0xBD, 0x23, 0xB9, 0x4E, 0x88, 0x9F, 0x20, 0x7E,
+ 0x69, 0xBF, 0xAB, 0x31, 0x98, 0x9A, 0xCF, 0x9E, 0x51, 0x0C, 0xF1, 0x05, 0x3E, 0x0F, 0xC0, 0x5F,
+ 0x57, 0xF0, 0x5A, 0x3B, 0xDC, 0x34, 0x51, 0x7E, 0x79, 0x6B, 0x4E, 0xD1, 0xA1, 0x76, 0x93, 0x4F,
+ 0xC4, 0x31, 0x71, 0xA0, 0x17, 0x03, 0xDF, 0x7C, 0xCC, 0xA4, 0xB1, 0x2A, 0x49, 0x33, 0x12, 0x6A,
+ 0x15, 0x96, 0xFC, 0x8E, 0x66, 0x63, 0x53, 0xE1, 0xEB, 0xAA, 0x15, 0xEE, 0x90, 0x62, 0x20, 0x32,
+ 0x73, 0xA6, 0xF7, 0x11, 0xD0, 0x0A, 0x9A, 0xF0, 0xE4, 0x2D, 0x3A, 0x9D, 0xC3, 0xCB, 0xF8, 0xC3,
+ 0x9B, 0x2A, 0x69, 0xD1, 0x43, 0xAB, 0xE2, 0x5E, 0xC5, 0xAA, 0xC2, 0x4D, 0xB7, 0xAA, 0x70, 0x54,
+ 0xCF, 0xA0, 0xDF, 0x7C, 0xB6, 0x30, 0xE8, 0x57, 0xA4, 0xEE, 0xFA, 0x15, 0xE9, 0xBD, 0xFA, 0x15,
+ 0xA9, 0x9B, 0x7E, 0x45, 0x7A, 0xBF, 0x7E, 0xCB, 0x65, 0x68, 0xD0, 0x2F, 0xDF, 0xBA, 0xEB, 0x97,
+ 0x6F, 0xEF, 0xD5, 0x2F, 0xDF, 0xBA, 0xE9, 0x97, 0x6F, 0xEF, 0xD7, 0x2F, 0x80, 0xC3, 0x37, 0x0C,
+ 0x0A, 0x5E, 0x72, 0x77, 0x05, 0x2F, 0xF9, 0xBD, 0x0A, 0x5E, 0x72, 0x37, 0x05, 0x2F, 0xF9, 0x13,
+ 0x28, 0x38, 0x31, 0x2B, 0xD8, 0x4B, 0xC3, 0x27, 0x50, 0xD1, 0x59, 0x47, 0x77, 0x25, 0x59, 0x17,
+ 0x3D, 0x70, 0x40, 0x5A, 0x22, 0x19, 0xED, 0xF2, 0xB4, 0x83, 0xD1, 0xC6, 0xE6, 0xEB, 0x88, 0x1B,
+ 0xF6, 0x7F, 0x66, 0x5F, 0x3E, 0xBE, 0xA3, 0x0B, 0xE7, 0x44, 0xCE, 0x7D, 0x39, 0x68, 0xD5, 0xBB,
+ 0x4F, 0x87, 0x46, 0x71, 0xB1, 0xF4, 0xF8, 0x41, 0xF1, 0x05, 0xBB, 0xBD, 0x71, 0x6E, 0x6D, 0xED,
+ 0xA6, 0x8E, 0x3F, 0xD5, 0xDC, 0xED, 0xF6, 0xAD, 0x5D, 0x4E, 0x37, 0xB7, 0x5E, 0xC6, 0x62, 0xFA,
+ 0x46, 0xAD, 0x27, 0xB5, 0xB7, 0x6C, 0x0B, 0x59, 0xC7, 0xBA, 0x04, 0x09, 0xCD, 0x3E, 0x6D, 0x84,
+ 0x5B, 0x86, 0x07, 0x94, 0xB1, 0x8D, 0x30, 0x21, 0x6F, 0x24, 0x5E, 0xCD, 0x1F, 0xDE, 0x4C, 0x7D,
+ 0xEA, 0xC2, 0xE3, 0xBA, 0x76, 0xFA, 0x35, 0x82, 0x13, 0x3E, 0x5C, 0x6E, 0x9B, 0x74, 0x94, 0xAE,
+ 0xAF, 0x13, 0x71, 0x1A, 0x27, 0x6F, 0x12, 0x94, 0xE9, 0xEF, 0x38, 0xED, 0x36, 0xD0, 0x7D, 0x87,
+ 0x53, 0xA6, 0xD9, 0xB7, 0xF8, 0xA6, 0x2D, 0x3E, 0x25, 0xA3, 0xDC, 0xD9, 0x00, 0x5C, 0x9F, 0xA1,
+ 0x71, 0x23, 0xD8, 0x07, 0xC7, 0x0D, 0x7B, 0x7A, 0x57, 0x88, 0x72, 0x5B, 0xBC, 0xFD, 0x9C, 0x48,
+ 0x3A, 0xB2, 0xCF, 0x61, 0xA2, 0xBA, 0x02, 0x53, 0xED, 0xD4, 0x33, 0x56, 0xEC, 0x83, 0xFE, 0x6C,
+ 0xBF, 0x43, 0x65, 0x76, 0x32, 0x1E, 0xAA, 0xE2, 0x74, 0x77, 0x88, 0xE1, 0x08, 0x94, 0xB1, 0xA2,
+ 0x05, 0x5B, 0x4E, 0x91, 0x13, 0xE5, 0x75, 0x86, 0x00, 0x61, 0xD1, 0xAC, 0x37, 0x93, 0x28, 0x0C,
+ 0xEB, 0xA5, 0x2B, 0x39, 0x55, 0x5F, 0x90, 0x32, 0x6A, 0x65, 0xBD, 0xD9, 0x4F, 0xA9, 0x84, 0x2F,
+ 0x75, 0xC8, 0xE9, 0x7C, 0xBD, 0x43, 0xBA, 0x11, 0x4D, 0xB9, 0x38, 0xAC, 0xFB, 0xC0, 0x18, 0xA5,
+ 0x2B, 0x27, 0x13, 0x7F, 0xB9, 0xBD, 0x05, 0x8A, 0x1D, 0x8A, 0xC1, 0xB2, 0x8A, 0x3B, 0xE8, 0xCB,
+ 0x28, 0x4A, 0xDF, 0x3C, 0x36, 0x2C, 0x80, 0x48, 0x8B, 0x34, 0x86, 0x16, 0x56, 0xAC, 0xD0, 0x5C,
+ 0x65, 0xC3, 0xEB, 0x35, 0xF9, 0x9E, 0xEC, 0x60, 0x8A, 0x6A, 0x7C, 0x89, 0x96, 0x5F, 0x8C, 0xA7,
+ 0x37, 0x02, 0x3D, 0x70, 0x4E, 0x5E, 0x2A, 0xD4, 0xA2, 0x43, 0xE7, 0x3B, 0x90, 0xE5, 0x95, 0xAB,
+ 0x71, 0x28, 0xC0, 0x9C, 0xD3, 0x81, 0xB0, 0x7B, 0xB9, 0xB8, 0xD9, 0x6B, 0x65, 0xFF, 0x5D, 0x85,
+ 0x66, 0xFE, 0xEC, 0x22, 0x84, 0xCB, 0xA2, 0x2B, 0x75, 0x67, 0x97, 0x7A, 0x7F, 0x63, 0xEF, 0x48,
+ 0x77, 0x9C, 0xA7, 0x81, 0xAF, 0x52, 0x90, 0x10, 0x14, 0x36, 0x25, 0x47, 0xD3, 0xB2, 0x20, 0x10,
+ 0x02, 0xF1, 0x8F, 0x07, 0x00, 0x21, 0x90, 0xD2, 0x26, 0xED, 0x16, 0x92, 0xEE, 0xAA, 0xE9, 0x07,
+ 0x85, 0x0A, 0x9E, 0x9D, 0xF1, 0x91, 0x8C, 0x3D, 0x63, 0x27, 0x4E, 0xBB, 0x5C, 0x12, 0xDA, 0xEF,
+ 0xDB, 0x6D, 0x3D, 0xA7, 0xC7, 0xE3, 0xDB, 0x1E, 0x77, 0xAE, 0x1C, 0x22, 0xD9, 0xE9, 0xF6, 0x6E,
+ 0x29, 0xBC, 0x8D, 0xBC, 0xB3, 0x54, 0xA5, 0x17, 0x4E, 0x2A, 0x5A, 0x55, 0x61, 0x88, 0xAE, 0xB8,
+ 0x7C, 0xF8, 0x80, 0x1F, 0xA3, 0xAE, 0x89, 0xF7, 0x3D, 0x8A, 0x85, 0x3D, 0xA0, 0x78, 0xC9, 0xC9,
+ 0x1D, 0x69, 0xEA, 0x77, 0x07, 0x3F, 0xB5, 0x3E, 0x89, 0x00, 0xF9, 0x7D, 0xEC, 0xDD, 0x2B, 0x27,
+ 0x23, 0xCF, 0xFA, 0xF7, 0xC3, 0x30, 0xAE, 0xF2, 0xEC, 0x61, 0x4C, 0xED, 0x79, 0x5E, 0xF8, 0xC7,
+ 0x23, 0x70, 0x25, 0xC3, 0x0B, 0x56, 0x75, 0xC6, 0x00, 0x07, 0x64, 0x65, 0x34, 0x07, 0x1E, 0xC5,
+ 0x3D, 0xFA, 0xBA, 0xD5, 0x34, 0xB4, 0x33, 0x23, 0x5E, 0x43, 0xA2, 0x8C, 0x3F, 0xBC, 0x29, 0x4E,
+ 0xD7, 0xA1, 0x07, 0xD1, 0x7E, 0x7C, 0xD3, 0x9E, 0x0F, 0xBB, 0x5F, 0x23, 0x3D, 0xC4, 0x91, 0x38,
+ 0x6A, 0xAE, 0x67, 0xF1, 0xB0, 0xC2, 0xA6, 0xE9, 0xD7, 0x45, 0x49, 0x21, 0x7F, 0x86, 0x1F, 0xF9,
+ 0x73, 0x3F, 0x54, 0x61, 0x86, 0xC1, 0x9F, 0xE9, 0x71, 0xB3, 0x66, 0x8F, 0x33, 0x39, 0x7D, 0x33,
+ 0xF0, 0x09, 0xA7, 0x9B, 0xDF, 0x36, 0x0A, 0xCB, 0xB4, 0x47, 0xB5, 0xF3, 0x93, 0xF5, 0x1E, 0x15,
+ 0x82, 0x25, 0x07, 0xF4, 0x99, 0xB9, 0x7A, 0x8B, 0xFC, 0xB6, 0xD7, 0x8A, 0x48, 0x56, 0x23, 0xF0,
+ 0x80, 0x03, 0x7D, 0x56, 0x6B, 0x91, 0x93, 0x67, 0x13, 0x81, 0x07, 0xA6, 0x7A, 0x78, 0xE8, 0x01,
+ 0xE8, 0x43, 0x3F, 0x57, 0x09, 0x40, 0x7B, 0xF3, 0xE2, 0xC1, 0x9A, 0x3C, 0x9A, 0x55, 0x64, 0xFD,
+ 0x98, 0xD6, 0x1A, 0xC2, 0xC7, 0x8E, 0x4D, 0x95, 0x0F, 0xDC, 0x0C, 0xBA, 0xBD, 0x96, 0x0F, 0xC2,
+ 0xEC, 0x94, 0xAD, 0xB9, 0x99, 0x74, 0xA2, 0xBD, 0x39, 0x34, 0x2E, 0xB3, 0xDE, 0x07, 0xCA, 0x74,
+ 0x89, 0x44, 0x89, 0xB4, 0xC1, 0x97, 0xD5, 0xB6, 0x3C, 0x9C, 0xAA, 0x6D, 0x17, 0x5E, 0xEF, 0x4D,
+ 0x73, 0xB4, 0x9E, 0xEA, 0xC3, 0x8A, 0x4D, 0xEB, 0xBC, 0x8E, 0x46, 0x18, 0xD2, 0xF0, 0x23, 0x00,
+ 0xD3, 0x8C, 0xB7, 0x86, 0x5D, 0x2C, 0x42, 0x9B, 0x05, 0x24, 0x18, 0x6E, 0x1F, 0xA0, 0x1E, 0xC8,
+ 0xE6, 0x21, 0x48, 0x16, 0x6F, 0x27, 0x38, 0xD1, 0x2D, 0x0D, 0x86, 0xD1, 0x2C, 0x84, 0x56, 0xC8,
+ 0x00, 0x6D, 0x21, 0xBB, 0x81, 0xEA, 0xFE, 0x11, 0xD4, 0x32, 0x00, 0x80, 0xB5, 0x5C, 0xC7, 0xE2,
+ 0xE7, 0xA1, 0xFE, 0xC0, 0x76, 0xB8, 0x98, 0x8E, 0xC2, 0xE9, 0xA6, 0x8A, 0xE4, 0x27, 0xF7, 0x4E,
+ 0xC7, 0x07, 0xF4, 0x83, 0xFB, 0xB7, 0x92, 0xDB, 0x5F, 0x1F, 0x20, 0x32, 0x78, 0xFF, 0xB0, 0xCB,
+ 0x16, 0xDF, 0x3D, 0xEC, 0x41, 0xBA, 0x27, 0x16, 0xDF, 0xBD, 0xDB, 0xCD, 0x88, 0xBD, 0x70, 0xEE,
+ 0x2D, 0xBB, 0xE7, 0x5A, 0x3A, 0xD6, 0x67, 0x59, 0xED, 0x8A, 0x37, 0xF5, 0x59, 0xF1, 0x38, 0x17,
+ 0x9B, 0xD6, 0xF6, 0x3E, 0x63, 0xEA, 0xAD, 0x07, 0x92, 0x88, 0x39, 0xC3, 0x0C, 0x58, 0x25, 0x28,
+ 0x6B, 0x8E, 0x31, 0xCE, 0x16, 0xDB, 0xFD, 0xC3, 0xE1, 0x3E, 0xDD, 0x3E, 0xA6, 0x27, 0x4C, 0xD4,
+ 0xD1, 0x08, 0x98, 0xE9, 0x83, 0x56, 0x43, 0x08, 0xB5, 0x9F, 0x62, 0x69, 0x4F, 0x9A, 0x67, 0xFD,
+ 0x5F, 0x3D, 0xE7, 0x3A, 0x80, 0xA2, 0xCA, 0x6F, 0xD4, 0xA7, 0xCA, 0x10, 0x35, 0x6A, 0xF2, 0x80,
+ 0x89, 0x3F, 0x07, 0x10, 0x01, 0xB2, 0x51, 0x95, 0x13, 0x5D, 0x94, 0xF7, 0xE0, 0xD2, 0xC1, 0x9E,
+ 0xE5, 0x2C, 0x1F, 0xF3, 0x38, 0x5F, 0x07, 0xDD, 0x77, 0x53, 0x39, 0x9D, 0xF5, 0x7F, 0x01, 0x03,
+ 0x75, 0x20, 0x8B, 0x7D, 0x46, 0xDB, 0x18, 0xF6, 0xC2, 0xA1, 0xA7, 0x65, 0x88, 0x5E, 0x0E, 0x75,
+ 0x6D, 0x3A, 0x8F, 0xDB, 0x53, 0x62, 0xE7, 0xC6, 0x27, 0x72, 0xE0, 0xF9, 0x7F, 0x30, 0x60, 0xEA,
+ 0xCA, 0x71, 0x8F, 0x11, 0x36, 0x01, 0x14, 0xD8, 0x3B, 0x20, 0x47, 0xE3, 0x3F, 0xF4, 0x69, 0x06,
+ 0x2F, 0x6B, 0x02, 0xC2, 0x03, 0xEE, 0x2A, 0x3E, 0xAA, 0xFB, 0x3B, 0x54, 0x25, 0x61, 0x86, 0x00,
+ 0xC2, 0x31, 0xDA, 0x14, 0xED, 0x41, 0x58, 0x4F, 0x7E, 0x01, 0x05, 0x7F, 0xF9, 0x38, 0xE1, 0xCC,
+ 0x5D, 0x4A, 0xA2, 0x21, 0x88, 0x04, 0x17, 0x8A, 0xD5, 0x91, 0x42, 0x29, 0x77, 0xFD, 0xF3, 0x67,
+ 0xF2, 0xCB, 0x4B, 0x71, 0xAC, 0xAC, 0x85, 0x4F, 0x1B, 0x47, 0x1B, 0xDA, 0x6E, 0x86, 0xA5, 0x4A,
+ 0x30, 0x7A, 0xBF, 0xE3, 0x91, 0x64, 0x95, 0x37, 0x36, 0x64, 0x90, 0xEB, 0x2C, 0xD1, 0xA6, 0x3A,
+ 0xFF, 0x52, 0x55, 0xC7, 0xAE, 0xB5, 0x57, 0xAB, 0xA2, 0x8E, 0x87, 0xB8, 0x55, 0x62, 0xA7, 0xCD,
+ 0x67, 0x0B, 0xC1, 0xA5, 0x38, 0x1C, 0xAB, 0xD3, 0x03, 0x4F, 0x82, 0xA9, 0xE9, 0x9B, 0x43, 0xE9,
+ 0x02, 0xD4, 0x7B, 0x57, 0x6A, 0xE3, 0xC4, 0x6D, 0x1B, 0x57, 0xEA, 0xA5, 0x76, 0xA6, 0x5E, 0x6A,
+ 0x5F, 0x97, 0xA8, 0xD7, 0x14, 0x27, 0x5B, 0xA4, 0xCB, 0x6A, 0xB4, 0x81, 0x16, 0xA4, 0xBC, 0x5A,
+ 0xF6, 0xC9, 0x92, 0xD4, 0x61, 0x22, 0x4C, 0xB7, 0xC6, 0xB4, 0xEE, 0x13, 0x45, 0xA1, 0x6B, 0x9C,
+ 0xBD, 0x1A, 0xAC, 0xD7, 0x77, 0x8E, 0x17, 0x6F, 0xE9, 0xFC, 0x35, 0x77, 0xF4, 0x62, 0x32, 0x8E,
+ 0x8D, 0x09, 0x53, 0x8B, 0xC8, 0x77, 0x66, 0xA4, 0x3D, 0x43, 0xB6, 0xB6, 0x1D, 0xAA, 0x5A, 0x32,
+ 0x99, 0xE6, 0x63, 0x51, 0x77, 0x14, 0xC9, 0xA8, 0xC0, 0xB2, 0x66, 0x59, 0x75, 0x98, 0x17, 0x6B,
+ 0x47, 0xAF, 0x07, 0x7C, 0x27, 0x76, 0x18, 0x6B, 0x9D, 0x3B, 0x4B, 0xC4, 0x0E, 0xFD, 0x1D, 0xD2,
+ 0xC5, 0xB8, 0xFB, 0xDB, 0xD1, 0xC0, 0xD9, 0xAF, 0x11, 0xE1, 0xDA, 0xCE, 0x22, 0x1F, 0xE2, 0x10,
+ 0x04, 0xDD, 0x31, 0xBB, 0x7C, 0x8E, 0xA2, 0xEA, 0xF3, 0x84, 0x4E, 0xF7, 0xC4, 0x40, 0xD8, 0x9E,
+ 0xE3, 0x86, 0x94, 0x59, 0x74, 0xD8, 0x3E, 0x7B, 0x02, 0xAF, 0xEB, 0x96, 0x72, 0x21, 0x36, 0x67,
+ 0x94, 0xD1, 0xD5, 0x17, 0xF7, 0x92, 0xD6, 0xE4, 0x07, 0x7B, 0xDC, 0x4F, 0xF2, 0xE8, 0x96, 0x19,
+ 0xFD, 0x37, 0x6A, 0xB7, 0x27, 0x70, 0x33, 0xE8, 0x7D, 0x2F, 0x5D, 0xD1, 0xFF, 0x5C, 0x9C, 0xDE,
+ 0x13, 0x5B, 0xDD, 0x1A, 0xA4, 0x93, 0x1F, 0xD6, 0xF9, 0xCF, 0x4F, 0xF3, 0x7E, 0x1B, 0x25, 0xFA,
+ 0x75, 0xEC, 0xB4, 0x8C, 0x96, 0x51, 0x5D, 0x5E, 0x0A, 0x21, 0xBC, 0xB9, 0x62, 0x93, 0x74, 0x7C,
+ 0x1E, 0x5D, 0xB7, 0xA1, 0xD4, 0x33, 0xB3, 0x19, 0x20, 0x35, 0x1F, 0x2A, 0x82, 0x83, 0x20, 0xA4,
+ 0x92, 0x76, 0x07, 0xBB, 0x1C, 0xE4, 0x21, 0x0D, 0xC3, 0x82, 0xCE, 0x6F, 0x75, 0x92, 0x8B, 0x1D,
+ 0x37, 0x79, 0xBF, 0x23, 0xF5, 0xF3, 0xA1, 0x3D, 0x6C, 0x6A, 0xBF, 0x12, 0xD8, 0x12, 0x58, 0x0D,
+ 0xE0, 0x5B, 0x87, 0xE6, 0xE5, 0xF9, 0x74, 0x2E, 0xA0, 0xC6, 0x61, 0x03, 0xA1, 0x4A, 0x85, 0x73,
+ 0x22, 0x7E, 0x69, 0xF7, 0xC2, 0xFE, 0x33, 0x41, 0x84, 0x51, 0x53, 0xDE, 0x53, 0x8A, 0x4D, 0x39,
+ 0xB1, 0x14, 0x9B, 0xF2, 0xAE, 0x52, 0x6C, 0xCA, 0x57, 0x2D, 0xC5, 0xA6, 0xBC, 0xA5, 0x14, 0x9B,
+ 0xF2, 0xB5, 0x4A, 0xB1, 0x29, 0x27, 0x97, 0xA2, 0x3E, 0xF9, 0x44, 0x18, 0xD5, 0xFB, 0x7B, 0x4A,
+ 0xB1, 0xDE, 0x4F, 0x2C, 0xC5, 0x7A, 0x7F, 0x57, 0x29, 0xD6, 0xFB, 0x57, 0x2D, 0xC5, 0x7A, 0x7F,
+ 0x4B, 0x29, 0xD6, 0xFB, 0xD7, 0x2A, 0xC5, 0x7A, 0x3F, 0xB9, 0x14, 0xBB, 0xF3, 0x5D, 0x84, 0xD3,
+ 0xA5, 0xBE, 0xA7, 0x18, 0x2F, 0xF5, 0xC4, 0x62, 0xBC, 0xD4, 0x77, 0x15, 0xE3, 0xA5, 0x7E, 0xD5,
+ 0x62, 0xBC, 0xD4, 0xB7, 0x14, 0xE3, 0xA5, 0x7E, 0xAD, 0x62, 0xBC, 0xD4, 0xD3, 0x8B, 0x71, 0xE9,
+ 0x2E, 0xC6, 0x3B, 0xCB, 0x71, 0x7A, 0x41, 0xDE, 0x5B, 0x92, 0xAF, 0x5D, 0x94, 0x37, 0x96, 0xE5,
+ 0x2B, 0x16, 0xE6, 0x68, 0x69, 0x5A, 0x04, 0xF7, 0x74, 0x82, 0xD3, 0xCA, 0xEA, 0x9E, 0x72, 0x7A,
+ 0xCD, 0x32, 0xBA, 0xA1, 0x7C, 0x5E, 0xA9, 0x6C, 0x06, 0xCB, 0xA5, 0xC7, 0xAD, 0x45, 0x66, 0x66,
+ 0xF6, 0x84, 0x95, 0xEF, 0x6E, 0x3F, 0xCE, 0x87, 0x08, 0x70, 0x55, 0xCF, 0x87, 0xA1, 0x26, 0x11,
+ 0x53, 0x18, 0xDB, 0xA6, 0xE7, 0x84, 0x79, 0x1E, 0x44, 0xA9, 0x55, 0x0B, 0xC1, 0xF4, 0xA9, 0xB8,
+ 0x0E, 0x12, 0x84, 0xCB, 0x8D, 0x9C, 0x43, 0x16, 0xC6, 0x41, 0x2F, 0x96, 0x79, 0x51, 0xE9, 0xDA,
+ 0xD9, 0x14, 0x73, 0x6A, 0x1F, 0x70, 0x1B, 0x72, 0xE0, 0xAC, 0x0A, 0xE5, 0xE7, 0x9C, 0xA4, 0xFD,
+ 0xD5, 0x4F, 0x3D, 0x66, 0xE2, 0x9F, 0xF1, 0xD4, 0x63, 0xFF, 0x9A, 0xA0, 0x50, 0xF5, 0x9D, 0xF4,
+ 0xA3, 0xF8, 0x61, 0xA6, 0xFF, 0x41, 0x6E, 0xDE, 0x49, 0x1F, 0xC7, 0x1E, 0x7B, 0x6C, 0x0E, 0x67,
+ 0x71, 0x5B, 0xAC, 0x39, 0x88, 0xC7, 0x14, 0x63, 0xCF, 0x5B, 0x8F, 0xCB, 0xD9, 0xFA, 0x29, 0x4D,
+ 0xE1, 0x4F, 0x92, 0xAB, 0xBF, 0x69, 0x06, 0x7F, 0xE9, 0xDB, 0x85, 0x5E, 0xFB, 0xC0, 0x1C, 0x78,
+ 0x92, 0xD7, 0x0A, 0x82, 0x59, 0xF1, 0x30, 0x04, 0x44, 0x5F, 0xF6, 0xA2, 0x84, 0xD4, 0x33, 0x71,
+ 0xC0, 0x84, 0x54, 0x78, 0x5C, 0x84, 0x1D, 0xC0, 0xD2, 0xE2, 0xFD, 0x08, 0x4A, 0xF8, 0x28, 0x33,
+ 0x5E, 0xB3, 0xF9, 0x81, 0x23, 0x69, 0xA9, 0x00, 0x72, 0xAD, 0x53, 0x00, 0xA2, 0xD4, 0xCD, 0x2B,
+ 0x6D, 0x1D, 0x24, 0x0D, 0xEB, 0xB8, 0x8F, 0x4F, 0x1A, 0xC6, 0x47, 0xD7, 0x74, 0x13, 0x33, 0xA0,
+ 0xA2, 0x33, 0x93, 0xDA, 0xB5, 0xD1, 0x9F, 0x3D, 0x5E, 0xC7, 0x39, 0x4A, 0x62, 0x2B, 0xFE, 0xEF,
+ 0xAC, 0xE8, 0x42, 0xD3, 0x59, 0xF7, 0xEB, 0x9F, 0xAD, 0xEE, 0xDA, 0x48, 0x58, 0xF9, 0xA6, 0x7B,
+ 0x32, 0x56, 0x79, 0x84, 0x79, 0x6A, 0xBC, 0x1F, 0x83, 0xD7, 0xB9, 0x6D, 0x71, 0x2A, 0x83, 0x96,
+ 0xFC, 0xD9, 0x02, 0x30, 0x0E, 0xB8, 0xE3, 0x4F, 0x7E, 0x01, 0x77, 0x51, 0xE3, 0xB4, 0xCD, 0xA9,
+ 0x2A, 0x7E, 0x8A, 0xC4, 0xF7, 0xC0, 0x4B, 0x74, 0xCA, 0xD3, 0x02, 0xEE, 0xD0, 0xA5, 0x9E, 0x4B,
+ 0x74, 0x64, 0xE9, 0x39, 0x89, 0x61, 0x4F, 0x4B, 0xE5, 0xEB, 0xB3, 0xA7, 0x93, 0x75, 0xEA, 0x83,
+ 0xDC, 0x2A, 0x88, 0x35, 0xD6, 0xA2, 0x3E, 0xB4, 0xFA, 0xB4, 0x12, 0x6E, 0x93, 0xE3, 0x7A, 0xBE,
+ 0xBD, 0x91, 0xAA, 0x53, 0x39, 0xA9, 0xB9, 0x23, 0x6F, 0xB0, 0xE9, 0x4C, 0xE4, 0xD9, 0x66, 0x53,
+ 0xEF, 0x54, 0xEB, 0x05, 0xE3, 0x68, 0x06, 0x59, 0x9F, 0xB3, 0x7D, 0x37, 0x3F, 0xAA, 0x43, 0x8B,
+ 0xBA, 0x40, 0x25, 0x4C, 0xC5, 0x51, 0x0F, 0x76, 0x2A, 0x21, 0x40, 0x17, 0x3C, 0xA6, 0x10, 0xA0,
+ 0x8D, 0xF8, 0xAD, 0x4F, 0x22, 0x7E, 0x60, 0xA8, 0xF6, 0xC0, 0x94, 0xFD, 0x40, 0xA1, 0xEE, 0x9E,
+ 0x9F, 0xA1, 0xCA, 0x99, 0xA6, 0xD7, 0x25, 0x03, 0x62, 0xCB, 0x5F, 0xC9, 0x3E, 0x5D, 0xB7, 0xC6,
+ 0x9E, 0x74, 0x67, 0x05, 0x34, 0xEA, 0xF9, 0x70, 0xAE, 0x2B, 0xB2, 0x81, 0xBD, 0xC8, 0x11, 0xDE,
+ 0xBE, 0xD9, 0x48, 0x14, 0x6B, 0xFB, 0xD3, 0xE9, 0x40, 0x5A, 0xB8, 0xAC, 0x32, 0xA6, 0x39, 0x9D,
+ 0x58, 0xB2, 0xC5, 0x1D, 0x5A, 0xF4, 0x46, 0xAC, 0x0F, 0xF0, 0xA3, 0x75, 0x86, 0x49, 0x67, 0x02,
+ 0x8D, 0x76, 0x0D, 0x38, 0xE1, 0xCC, 0xEA, 0x96, 0x59, 0x55, 0xE2, 0x8C, 0x14, 0xDC, 0x40, 0x95,
+ 0xB2, 0x24, 0x3B, 0x7C, 0xD8, 0x5F, 0xDC, 0x33, 0x47, 0x12, 0x34, 0xCB, 0xBF, 0x63, 0xA1, 0x3A,
+ 0xB3, 0x12, 0xAC, 0x3A, 0x94, 0xD0, 0xB8, 0xDE, 0x4A, 0x0C, 0x14, 0x93, 0x47, 0x6D, 0xD1, 0x4F,
+ 0x04, 0xA9, 0x6E, 0xD9, 0x41, 0xEE, 0x9B, 0xDB, 0x4D, 0x47, 0xB4, 0xE0, 0xAE, 0x82, 0x89, 0x58,
+ 0x9A, 0x2A, 0x89, 0x5E, 0xA0, 0xB6, 0xB8, 0xCB, 0xBD, 0x6D, 0x9B, 0xBD, 0x9F, 0x93, 0xA6, 0x3C,
+ 0x34, 0xFB, 0x48, 0x38, 0x19, 0xB4, 0xC9, 0x8E, 0xEB, 0xD2, 0xC6, 0x7D, 0x1D, 0xF8, 0xDB, 0x3B,
+ 0x89, 0x6A, 0xE5, 0xAC, 0x2A, 0xF3, 0xC9, 0x58, 0xA9, 0xA2, 0xBC, 0x07, 0x94, 0xAC, 0x38, 0x1A,
+ 0x09, 0x20, 0xD0, 0xD8, 0x81, 0x46, 0x1A, 0x82, 0xF2, 0xD7, 0xB5, 0x79, 0x4E, 0x05, 0xAF, 0x7F,
+ 0x75, 0xDB, 0xA6, 0x9A, 0x2D, 0xD5, 0xC2, 0xD1, 0xB6, 0x66, 0x2D, 0xB0, 0xFD, 0xDB, 0x34, 0x06,
+ 0xBD, 0x63, 0x77, 0x55, 0xCE, 0xEF, 0x4F, 0xB0, 0x4F, 0x27, 0xFA, 0x50, 0x2E, 0x4B, 0xB7, 0x81,
+ 0xA2, 0x7A, 0xBD, 0x43, 0x9B, 0x02, 0x8E, 0xAD, 0x1A, 0x1A, 0xB3, 0x8D, 0xB1, 0x6F, 0xF0, 0xBB,
+ 0x68, 0xE8, 0x99, 0xB6, 0xDB, 0x8F, 0xBB, 0x8E, 0x72, 0x9E, 0x99, 0x75, 0xE1, 0x21, 0x18, 0x9F,
+ 0xFB, 0xD4, 0xCD, 0xA2, 0x45, 0xA3, 0x31, 0x55, 0xF4, 0xB8, 0x87, 0x39, 0x14, 0xE0, 0x47, 0xF5,
+ 0x46, 0xCF, 0xE1, 0xA1, 0x37, 0x4E, 0x63, 0x1C, 0x64, 0x55, 0x20, 0x08, 0x30, 0xEB, 0xAD, 0xB2,
+ 0x85, 0x59, 0x27, 0xCB, 0x46, 0xBB, 0x7A, 0x73, 0xFF, 0x3B, 0xCC, 0x80, 0xB6, 0x00, 0x3F, 0x3C,
+ 0x83, 0xD7, 0xBF, 0x01, 0x84, 0xE3, 0xC8, 0x80, 0xD5, 0x75, 0x04, 0x03, 0x5B, 0x2B, 0x32, 0x84,
+ 0x50, 0xD5, 0x7C, 0x5A, 0xB8, 0x08, 0xE7, 0xC8, 0xD6, 0x77, 0xE2, 0x29, 0xC6, 0xBD, 0xDC, 0xE2,
+ 0xB8, 0x7D, 0x7A, 0x3E, 0xFD, 0x4D, 0xE7, 0x17, 0x87, 0x1E, 0xB8, 0x7E, 0xB0, 0x54, 0x04, 0xF0,
+ 0xD4, 0x98, 0x29, 0xB4, 0x44, 0xF8, 0xC1, 0x00, 0x86, 0x22, 0xDD, 0xA0, 0x0F, 0xFB, 0x52, 0xCE,
+ 0xFB, 0xD3, 0x8F, 0xDB, 0x55, 0x56, 0x2D, 0x1D, 0x36, 0xAD, 0xD6, 0xBB, 0x04, 0xCC, 0xEA, 0x08,
+ 0xC0, 0x1E, 0x89, 0x71, 0x41, 0xCC, 0xC6, 0x04, 0xE3, 0x32, 0xF5, 0x19, 0xF2, 0xBF, 0x7C, 0x6A,
+ 0x9A, 0xAC, 0xE0, 0xDF, 0xBB, 0x33, 0x71, 0xC0, 0xEB, 0xD3, 0x77, 0xDF, 0x49, 0x33, 0x95, 0x47,
+ 0x63, 0xAE, 0x2A, 0x41, 0xD1, 0xE9, 0x4D, 0x0D, 0xD3, 0x55, 0x71, 0x9C, 0xF3, 0xB9, 0x2C, 0xD5,
+ 0xCC, 0x32, 0x59, 0xAC, 0x96, 0xAB, 0xD9, 0x52, 0xFC, 0x2E, 0x16, 0xF9, 0x22, 0x9F, 0x49, 0x76,
+ 0xB3, 0xC5, 0x3A, 0xFE, 0x68, 0x16, 0x7F, 0xFD, 0xD1, 0x2C, 0x89, 0x17, 0xE9, 0x63, 0x56, 0xE7,
+ 0x02, 0x21, 0x12, 0xBF, 0xD7, 0x14, 0x4D, 0xFC, 0xAF, 0xA3, 0xD5, 0xCC, 0xA4, 0x8F, 0x14, 0x3D,
+ 0x24, 0x47, 0x16, 0xDB, 0x58, 0x02, 0x7E, 0x63, 0x13, 0x57, 0xE5, 0x9D, 0xE2, 0xC1, 0x7B, 0xE8,
+ 0x95, 0xCE, 0xC5, 0xB9, 0x7A, 0x2F, 0x4A, 0x3E, 0x8A, 0xCB, 0x6A, 0xEF, 0xB2, 0xB1, 0xB6, 0xA9,
+ 0x5A, 0x8A, 0x7F, 0x3A, 0x89, 0x01, 0x71, 0xAC, 0xEB, 0x9B, 0xAA, 0x5D, 0x78, 0xEE, 0xC2, 0x1E,
+ 0x70, 0xE3, 0xAD, 0x62, 0xE3, 0xD2, 0xEC, 0xDF, 0x5D, 0x34, 0xAA, 0x8A, 0xFF, 0x87, 0x8A, 0x26,
+ 0xF0, 0x80, 0x8A, 0x6A, 0xCB, 0xF8, 0xA1, 0xA0, 0xBE, 0x64, 0x67, 0x8B, 0xF4, 0xB6, 0x23, 0x41,
+ 0x1E, 0x07, 0x08, 0x69, 0x02, 0xEC, 0xAB, 0x47, 0x29, 0xC7, 0x50, 0x6B, 0x18, 0x3D, 0x06, 0x7D,
+ 0xDA, 0xE7, 0xA3, 0xD5, 0x66, 0xBD, 0x0B, 0x38, 0x1E, 0xA4, 0xA3, 0x27, 0xAB, 0xF7, 0x58, 0xD3,
+ 0x3C, 0x7B, 0x58, 0xD8, 0xCD, 0x83, 0xEE, 0x27, 0xE9, 0x9C, 0x0E, 0x31, 0xF4, 0xB5, 0x4A, 0x67,
+ 0x43, 0x3F, 0xBE, 0x4E, 0x41, 0xF9, 0xE8, 0xCE, 0xEF, 0x79, 0x17, 0x9D, 0x7F, 0x7D, 0xA9, 0xAE,
+ 0xF7, 0x9D, 0x93, 0x1E, 0x62, 0x3D, 0x63, 0x06, 0xFD, 0x8B, 0xC6, 0xE0, 0xB6, 0x12, 0xB2, 0x99,
+ 0xB5, 0x35, 0x99, 0x5B, 0xF3, 0x79, 0x46, 0x50, 0x17, 0x88, 0xEA, 0x1D, 0x54, 0xA1, 0x4D, 0x6C,
+ 0x0C, 0x6E, 0x34, 0x27, 0x7F, 0xBF, 0x51, 0xB0, 0x47, 0xF8, 0x4B, 0xA7, 0x0C, 0x81, 0x3A, 0x69,
+ 0x65, 0x5E, 0xDB, 0x0E, 0x72, 0xF1, 0xC4, 0x1E, 0xEB, 0x70, 0xA4, 0x5D, 0xFD, 0xA6, 0x7D, 0x1A,
+ 0x52, 0x46, 0x2F, 0x1B, 0x71, 0x1A, 0x5A, 0x55, 0x9C, 0xF1, 0x30, 0xA4, 0x8A, 0x74, 0x44, 0x14,
+ 0xC0, 0xCD, 0xB3, 0x98, 0x16, 0x42, 0xE9, 0x5B, 0x00, 0x0B, 0xA0, 0xF5, 0xD7, 0x1E, 0x54, 0x5C,
+ 0xAC, 0x6F, 0x96, 0xDB, 0xD3, 0x9B, 0x66, 0x13, 0x72, 0x35, 0x46, 0x34, 0x4A, 0xF6, 0x84, 0x4D,
+ 0x96, 0x04, 0x3F, 0x1D, 0x8B, 0x6C, 0xA5, 0x26, 0x1F, 0xD0, 0x84, 0x2B, 0xDF, 0x3A, 0x1E, 0x27,
+ 0xEA, 0xAF, 0xBE, 0xC1, 0xE0, 0xB3, 0x38, 0xAB, 0xA1, 0x2B, 0xDD, 0x96, 0xE6, 0x97, 0xE3, 0x75,
+ 0x5F, 0x8C, 0xC7, 0x13, 0x91, 0x6B, 0x77, 0xB1, 0xF9, 0x61, 0xF6, 0xF6, 0x87, 0x6F, 0xCF, 0x99,
+ 0x02, 0x7A, 0xA7, 0x82, 0x46, 0x44, 0x7C, 0x29, 0x20, 0xFB, 0x72, 0x49, 0xCC, 0x36, 0x99, 0x95,
+ 0xA5, 0x98, 0xDB, 0x04, 0xE8, 0x2A, 0xB5, 0x5C, 0xC6, 0x87, 0xFB, 0xD6, 0xD1, 0xF5, 0x90, 0xEB,
+ 0x42, 0x81, 0x2D, 0xB9, 0xBE, 0x42, 0xF1, 0xCF, 0x8E, 0xCE, 0x83, 0x7B, 0x62, 0xB4, 0x10, 0xEB,
+ 0x7B, 0x11, 0x46, 0x3B, 0xDD, 0x4F, 0xAC, 0x5B, 0x47, 0xBE, 0xE0, 0x13, 0xEE, 0x2B, 0x26, 0x06,
+ 0x5B, 0xDA, 0x53, 0x87, 0xB2, 0xBD, 0xB5, 0xEF, 0x16, 0x82, 0x95, 0x57, 0xF3, 0xE9, 0x24, 0x6A,
+ 0xC5, 0x6F, 0x06, 0x4B, 0x98, 0xE9, 0x9E, 0x26, 0x36, 0x6A, 0x1F, 0x70, 0xB3, 0x44, 0x5B, 0xC4,
+ 0x4E, 0x44, 0xFE, 0x18, 0x57, 0xC0, 0x90, 0x10, 0x70, 0x83, 0x2B, 0xFC, 0x82, 0x8F, 0x61, 0x7D,
+ 0x5C, 0x58, 0xCD, 0xD6, 0xC6, 0x61, 0x73, 0x85, 0x41, 0x9B, 0x50, 0x53, 0xA1, 0xB0, 0xC1, 0x87,
+ 0xB3, 0x83, 0xA1, 0x02, 0xEA, 0x62, 0x98, 0x7F, 0x40, 0x0F, 0xE6, 0xC0, 0x30, 0xDB, 0x0B, 0x71,
+ 0x88, 0xCF, 0x95, 0x67, 0x95, 0xE5, 0x64, 0x41, 0x67, 0xEE, 0x6E, 0x0E, 0xB7, 0xDB, 0x24, 0x1B,
+ 0x31, 0x49, 0xE6, 0x90, 0x76, 0xBB, 0x81, 0x32, 0x8F, 0x7D, 0xFC, 0xE2, 0xDA, 0xC6, 0x60, 0x1C,
+ 0x18, 0x0B, 0x96, 0x30, 0xB8, 0xC3, 0x3A, 0xE9, 0x98, 0xC3, 0x70, 0x69, 0x77, 0x58, 0x27, 0x75,
+ 0x5B, 0x87, 0x8A, 0xDB, 0x14, 0xE5, 0xDE, 0x13, 0xC3, 0x09, 0xAB, 0x4C, 0x2E, 0xCC, 0xB3, 0xCA,
+ 0x6D, 0xF3, 0xAC, 0xE1, 0xBB, 0x15, 0x41, 0x68, 0x1D, 0xC7, 0xE4, 0xAA, 0x06, 0xD6, 0x4F, 0x7E,
+ 0xA5, 0xCB, 0x71, 0xA5, 0x86, 0xDE, 0x30, 0xD8, 0x40, 0x13, 0x2F, 0x18, 0xF2, 0x6D, 0x4C, 0x54,
+ 0x5D, 0x45, 0x6E, 0x22, 0xA7, 0xAF, 0x36, 0xE7, 0xE3, 0x4C, 0x81, 0x1D, 0x5D, 0x21, 0x5E, 0x72,
+ 0x2E, 0x6A, 0x90, 0xC7, 0x31, 0xF8, 0x9E, 0x99, 0x63, 0x48, 0x32, 0xFD, 0xA6, 0x89, 0x96, 0x27,
+ 0xA7, 0x52, 0xF8, 0x4C, 0x8C, 0xDE, 0x1F, 0xD5, 0x40, 0x55, 0xA6, 0xC4, 0xA8, 0x1D, 0x0C, 0xF2,
+ 0xD8, 0x1C, 0x5A, 0x79, 0xA6, 0x8D, 0x9C, 0x96, 0x93, 0x8E, 0xCE, 0xB1, 0x66, 0x2A, 0xDE, 0x41,
+ 0xFD, 0xDC, 0x56, 0xA3, 0x9B, 0x20, 0x7D, 0x77, 0x87, 0xB9, 0xD7, 0xF5, 0x22, 0x41, 0xE6, 0xF4,
+ 0x59, 0xD0, 0xF8, 0xA3, 0x65, 0xFA, 0xF8, 0x91, 0xA3, 0x45, 0xDE, 0xEE, 0xAA, 0x94, 0x35, 0xCA,
+ 0x9B, 0x55, 0xB9, 0xDC, 0x55, 0x84, 0xD5, 0xCC, 0xC8, 0x78, 0xCF, 0x76, 0x95, 0xE5, 0xEB, 0x42,
+ 0x23, 0x3A, 0x1E, 0x46, 0x4C, 0x96, 0xAB, 0xE5, 0xC6, 0xD5, 0x63, 0xA6, 0x55, 0x56, 0xB1, 0xD7,
+ 0x28, 0xB2, 0x72, 0x55, 0x7E, 0x64, 0x33, 0xF3, 0xC8, 0xCD, 0x96, 0xD9, 0x47, 0xD9, 0x56, 0xA3,
+ 0xD2, 0x67, 0xE2, 0xE2, 0x5D, 0x9E, 0x64, 0xA9, 0x43, 0x6A, 0x99, 0x54, 0xEB, 0xB2, 0xA4, 0x79,
+ 0x2D, 0xCA, 0xCD, 0x96, 0xB2, 0x72, 0xE6, 0x75, 0xBB, 0x4C, 0xD2, 0x4E, 0x3D, 0xFB, 0x5D, 0xAD,
+ 0x3C, 0x4F, 0x56, 0xB1, 0xD3, 0xB8, 0xBB, 0xE5, 0x6E, 0xCB, 0x8C, 0x5B, 0xED, 0x76, 0x1B, 0x93,
+ 0x8F, 0x53, 0xDA, 0x12, 0x8C, 0x57, 0x6A, 0x2C, 0xFA, 0x60, 0xD1, 0x6A, 0xB5, 0x2C, 0xE3, 0xCC,
+ 0xDD, 0xBF, 0x66, 0x5B, 0x92, 0x43, 0x48, 0xAC, 0xB6, 0x9B, 0x9C, 0xB0, 0x72, 0xC9, 0xCC, 0xD3,
+ 0xAC, 0x8A, 0x53, 0x8D, 0x48, 0x5E, 0x78, 0x01, 0xFF, 0x89, 0xD3, 0x47, 0x97, 0xC8, 0x8F, 0xCA,
+ 0x75, 0x59, 0x50, 0x91, 0xF9, 0x36, 0xDD, 0xAE, 0x6D, 0x4E, 0x2E, 0x89, 0xAB, 0x22, 0x29, 0xD2,
+ 0x44, 0xE3, 0xD9, 0x0F, 0x64, 0xAC, 0xB2, 0x15, 0x38, 0x8F, 0x4B, 0x60, 0x25, 0x7E, 0xA8, 0xC0,
+ 0x12, 0x7E, 0x2A, 0x8B, 0x91, 0x4B, 0xDE, 0x72, 0x97, 0xC7, 0x39, 0x56, 0x53, 0x23, 0x8C, 0x55,
+ 0xB2, 0x4C, 0x56, 0x89, 0x2B, 0x7F, 0x65, 0x06, 0x3F, 0x4B, 0x5A, 0x86, 0xDB, 0x4D, 0xB5, 0xD9,
+ 0x99, 0x7C, 0x5C, 0xD2, 0x92, 0x38, 0x49, 0x93, 0xE5, 0xEF, 0x9F, 0x43, 0x0B, 0xB1, 0xF9, 0xE9,
+ 0x70, 0x8E, 0x7E, 0xAA, 0x7E, 0xDD, 0x9D, 0x8A, 0xA6, 0x6A, 0x67, 0x2F, 0xA7, 0xE7, 0xFD, 0x09,
+ 0xFC, 0x2C, 0x12, 0x27, 0x58, 0xDA, 0xF3, 0xE9, 0xF0, 0x52, 0xB5, 0xD7, 0xF8, 0x9D, 0xAB, 0xE3,
+ 0x1A, 0x56, 0x74, 0x91, 0xAD, 0x18, 0x1C, 0x07, 0xBF, 0x8F, 0x7C, 0xD1, 0x11, 0xD9, 0x53, 0x15,
+ 0xD5, 0x03, 0x48, 0x1C, 0x1A, 0xE0, 0xCE, 0xEE, 0x42, 0x00, 0x61, 0x74, 0x44, 0x4D, 0x5A, 0x51,
+ 0x53, 0xCD, 0x90, 0x8B, 0x97, 0xEE, 0x88, 0x1C, 0x54, 0xAD, 0xC9, 0x7D, 0x95, 0x6F, 0xD4, 0x6B,
+ 0x4E, 0x85, 0xE4, 0x62, 0x00, 0xF4, 0x9C, 0x13, 0x37, 0x0B, 0xAC, 0x1C, 0xF2, 0x99, 0x8A, 0xA3,
+ 0x9C, 0x4A, 0xBE, 0x42, 0xAF, 0x02, 0xD2, 0x47, 0x7B, 0x61, 0x3D, 0xC8, 0xC4, 0x7B, 0xCB, 0xBC,
+ 0xAC, 0xF6, 0x0F, 0xCE, 0xD8, 0x63, 0xB3, 0x34, 0x7F, 0xE7, 0xC1, 0xE8, 0xBE, 0xD8, 0xF7, 0x3C,
+ 0x7E, 0xC7, 0x43, 0xE9, 0x87, 0xAC, 0x6D, 0x1E, 0xF4, 0xFB, 0x9C, 0xAE, 0xBD, 0x62, 0x6F, 0x4B,
+ 0x72, 0x58, 0x1C, 0x21, 0x43, 0x67, 0xC8, 0xA2, 0x76, 0x78, 0x95, 0x20, 0x0D, 0x92, 0x74, 0x71,
+ 0xF7, 0x67, 0xD0, 0xE0, 0x1D, 0x8E, 0x50, 0x46, 0x4E, 0x27, 0xFE, 0x64, 0x32, 0xC5, 0x4D, 0x45,
+ 0xE5, 0xD5, 0x54, 0x17, 0x9D, 0x56, 0x03, 0x4B, 0xD2, 0x38, 0xDA, 0x13, 0xE2, 0xC8, 0xC3, 0x37,
+ 0x88, 0x3D, 0xB5, 0x05, 0x45, 0x44, 0x47, 0x58, 0x77, 0xA8, 0x4E, 0xA0, 0x1E, 0xAE, 0x1C, 0xC8,
+ 0xB5, 0x35, 0xA5, 0xDC, 0xF6, 0xF9, 0x8D, 0x9C, 0x6A, 0x41, 0x6E, 0xAA, 0xF3, 0xC7, 0xAD, 0x12,
+ 0xED, 0xA4, 0xFF, 0xAC, 0x3E, 0xF4, 0x2B, 0x25, 0x5D, 0xA5, 0xD2, 0xD4, 0xED, 0x7B, 0x9A, 0x10,
+ 0x56, 0x3C, 0x16, 0x6F, 0xCF, 0xE1, 0xD7, 0xEC, 0xED, 0x9E, 0xF3, 0xE1, 0xB8, 0x05, 0x95, 0xAA,
+ 0xA3, 0x93, 0xBB, 0x0A, 0xE0, 0x56, 0xC8, 0xF4, 0xAB, 0xB1, 0xF9, 0x68, 0x07, 0x77, 0xE0, 0x21,
+ 0x29, 0x7D, 0x3C, 0xF4, 0xE1, 0x3E, 0x37, 0x94, 0xC5, 0xF4, 0xB2, 0xC5, 0x04, 0xAF, 0x88, 0xC8,
+ 0x47, 0x68, 0xBC, 0x1A, 0xE0, 0xEA, 0x0E, 0x6E, 0x8D, 0x7A, 0x1B, 0x3B, 0xC6, 0x65, 0x74, 0x11,
+ 0xC7, 0x1D, 0x06, 0x06, 0x45, 0x05, 0xE7, 0x22, 0x6C, 0x85, 0x1E, 0xD5, 0x63, 0x93, 0x1F, 0xDF,
+ 0x8C, 0x47, 0x17, 0x91, 0x6F, 0xA1, 0xDC, 0x57, 0x84, 0xFE, 0x35, 0x49, 0x27, 0x39, 0x9F, 0x52,
+ 0x8D, 0x0A, 0xC0, 0x88, 0x86, 0x4C, 0xF4, 0x84, 0x50, 0x32, 0x4E, 0x53, 0x72, 0x59, 0xCA, 0x0D,
+ 0x70, 0x3D, 0xE9, 0x8E, 0xA5, 0x13, 0xC2, 0xFA, 0x03, 0x9A, 0xE0, 0x38, 0x47, 0x39, 0x4E, 0xA4,
+ 0x15, 0x64, 0x71, 0x4E, 0x18, 0x2F, 0x48, 0xB4, 0xB8, 0xC1, 0xD6, 0xFC, 0xE1, 0x37, 0xA8, 0xFF,
+ 0x45, 0xED, 0xB8, 0x30, 0xE4, 0xC1, 0xFC, 0x2C, 0xC4, 0x8F, 0x54, 0x49, 0x4E, 0xD9, 0xEB, 0x89,
+ 0x83, 0xC5, 0xA1, 0x6B, 0x4D, 0x5B, 0x79, 0x01, 0x65, 0xA6, 0x4B, 0xE3, 0xA6, 0x0D, 0xA7, 0x0D,
+ 0x28, 0x5C, 0x2C, 0x25, 0xA9, 0x9E, 0x4A, 0xBD, 0x43, 0x82, 0x56, 0xD7, 0x5E, 0x13, 0x74, 0x0B,
+ 0x81, 0x54, 0xFF, 0xD9, 0x2D, 0xB7, 0xFC, 0xA8, 0x6D, 0xC2, 0xFD, 0x04, 0x90, 0xFF, 0x66, 0x57,
+ 0x01, 0x89, 0x7F, 0xAF, 0xB7, 0x80, 0xC0, 0x7B, 0x1C, 0x06, 0xC8, 0xC7, 0x4B, 0xF4, 0x5E, 0x9F,
+ 0x09, 0x12, 0x32, 0xD1, 0x6D, 0xFC, 0x97, 0xFA, 0x3D, 0x3A, 0x34, 0xE5, 0x04, 0xBF, 0x69, 0xCA,
+ 0xBF, 0xDB, 0x6F, 0x9A, 0xF2, 0x6F, 0xF6, 0x9B, 0xA6, 0xBC, 0xCB, 0x6F, 0x9A, 0x72, 0xBC, 0x48,
+ 0xEF, 0xF6, 0x9B, 0x10, 0x21, 0xF7, 0xFB, 0xCD, 0xE3, 0x63, 0x3A, 0xE0, 0x37, 0xF5, 0x7E, 0x82,
+ 0xDF, 0xD4, 0xFB, 0xBF, 0xDB, 0x6F, 0xEA, 0xFD, 0xDF, 0xEC, 0x37, 0xF5, 0xFE, 0x2E, 0xBF, 0xA9,
+ 0xF7, 0xE3, 0x45, 0x7A, 0xB7, 0xDF, 0x84, 0x08, 0xB9, 0xDF, 0x6F, 0xBA, 0xC0, 0x05, 0x1E, 0x25,
+ 0x2E, 0x13, 0x06, 0x34, 0xD1, 0xE5, 0xEF, 0x1E, 0xD3, 0x44, 0x97, 0xBF, 0x79, 0x58, 0x13, 0x5D,
+ 0xEE, 0x1A, 0xD9, 0x44, 0x97, 0x80, 0xA1, 0xC7, 0xDD, 0x8E, 0x13, 0x22, 0xE4, 0x15, 0x1C, 0x67,
+ 0x39, 0xEC, 0x38, 0xD3, 0x3C, 0xE7, 0x1F, 0x70, 0x9D, 0xBF, 0xDF, 0x77, 0xEE, 0x75, 0x9E, 0x90,
+ 0x82, 0xBD, 0xDF, 0x7B, 0x42, 0xA4, 0x4C, 0x75, 0x1F, 0x83, 0x5E, 0x9D, 0x9F, 0xA1, 0xA7, 0x63,
+ 0x38, 0x06, 0x53, 0x82, 0x1C, 0x2B, 0x82, 0x4D, 0x7F, 0x60, 0x1D, 0x40, 0x37, 0x7A, 0xAF, 0x8D,
+ 0xCD, 0x0B, 0x27, 0xEE, 0xED, 0x78, 0xE9, 0x27, 0xAE, 0xC7, 0x20, 0x95, 0x7F, 0x9D, 0x66, 0x54,
+ 0x25, 0x48, 0xD9, 0x54, 0xAB, 0xA9, 0x2A, 0x2D, 0x82, 0x5E, 0x49, 0x50, 0x32, 0x95, 0x0D, 0xED,
+ 0x44, 0x26, 0x6F, 0xF2, 0x56, 0x95, 0x8F, 0xC3, 0x74, 0x33, 0x8E, 0xD2, 0x49, 0x43, 0x8E, 0xAA,
+ 0xB5, 0xDD, 0x6C, 0xB7, 0xDB, 0x6A, 0xBA, 0x5A, 0x41, 0xA6, 0xD4, 0x52, 0xD1, 0x94, 0x98, 0xC8,
+ 0x24, 0x4E, 0xDC, 0x7D, 0xF3, 0xD2, 0x4F, 0x35, 0xA3, 0xA2, 0x0A, 0x32, 0xA2, 0x57, 0xA5, 0xCD,
+ 0xB6, 0x8C, 0xB7, 0xEB, 0xA9, 0x2A, 0x05, 0x99, 0x50, 0xCB, 0x44, 0x13, 0x62, 0x22, 0x93, 0x37,
+ 0x65, 0x33, 0xD1, 0x4D, 0x3C, 0xD1, 0x78, 0x82, 0x24, 0xCC, 0x72, 0x3E, 0x65, 0x36, 0x45, 0xB9,
+ 0xAD, 0xB2, 0x49, 0xCA, 0x84, 0x99, 0x4D, 0x09, 0x44, 0xB3, 0x61, 0x22, 0x13, 0x36, 0x71, 0x57,
+ 0xD4, 0x4B, 0x3F, 0xD1, 0x78, 0x9A, 0x2A, 0xC8, 0x7E, 0x5E, 0x95, 0xAA, 0x55, 0xB9, 0xD9, 0x3C,
+ 0x4E, 0x55, 0x29, 0xC8, 0x84, 0x5A, 0x26, 0x9A, 0x10, 0x13, 0x99, 0xBC, 0x69, 0x9B, 0xBC, 0x3E,
+ 0xF2, 0x89, 0x06, 0x54, 0x44, 0x41, 0xF6, 0xF3, 0x2A, 0x54, 0xEE, 0xB6, 0xE9, 0x76, 0x39, 0x51,
+ 0xA1, 0x20, 0xF3, 0x69, 0x91, 0x68, 0x3E, 0x4C, 0x64, 0xE2, 0x26, 0xED, 0x58, 0x7B, 0xA8, 0x27,
+ 0x1A, 0x4F, 0xD2, 0x04, 0xD9, 0xCE, 0xAB, 0x4E, 0x95, 0x8B, 0x9F, 0x69, 0xEA, 0x04, 0x99, 0x4E,
+ 0x4B, 0x44, 0xD3, 0x61, 0x22, 0x93, 0x36, 0x65, 0xF3, 0xDD, 0x4D, 0x3C, 0xD1, 0x70, 0x82, 0x24,
+ 0xC8, 0x6E, 0x5E, 0x65, 0x36, 0x15, 0xFC, 0xEC, 0x26, 0x29, 0x13, 0x64, 0x36, 0x2D, 0x10, 0xCD,
+ 0x86, 0x89, 0xBF, 0xE3, 0xB9, 0x20, 0xF5, 0x3C, 0xFD, 0xE1, 0x37, 0xB1, 0x91, 0xA4, 0xF7, 0xF2,
+ 0x22, 0x48, 0xEA, 0xAE, 0x23, 0x19, 0x57, 0x91, 0xCC, 0xB7, 0xA3, 0x53, 0x79, 0x20, 0x0C, 0x7E,
+ 0xE3, 0xCB, 0x03, 0xB1, 0xD9, 0x9E, 0x7F, 0x6C, 0x6E, 0xFC, 0xFE, 0xC5, 0xF7, 0x91, 0x00, 0xCB,
+ 0xB8, 0x8C, 0x24, 0x2F, 0x1E, 0x89, 0x6B, 0x45, 0xE2, 0x7F, 0x91, 0xCC, 0xE4, 0x1D, 0xE0, 0x24,
+ 0x59, 0xC0, 0x30, 0x43, 0xDE, 0x39, 0x5A, 0x2D, 0xF2, 0x8F, 0x80, 0xC1, 0x92, 0xA0, 0x24, 0x1A,
+ 0x45, 0xFE, 0xFE, 0xFA, 0x51, 0x7E, 0xFE, 0xA8, 0x5E, 0x09, 0x0C, 0x20, 0x31, 0x59, 0x45, 0x06,
+ 0x22, 0xF0, 0x93, 0xA8, 0x75, 0xE4, 0xC7, 0x8C, 0x14, 0xA6, 0x92, 0xFB, 0xD1, 0x4C, 0x60, 0x00,
+ 0xF1, 0x3A, 0x5E, 0xF7, 0x78, 0xB1, 0x42, 0xE1, 0x37, 0x97, 0x66, 0xEA, 0xF0, 0xC1, 0x87, 0x60,
+ 0x7A, 0x19, 0xCB, 0x61, 0x86, 0xF7, 0x96, 0x06, 0x83, 0xC5, 0x7F, 0xD2, 0xBD, 0x1A, 0xBF, 0xC8,
+ 0x8D, 0x92, 0xB6, 0xDD, 0x11, 0xAC, 0xE6, 0xDC, 0x1F, 0x44, 0xDA, 0xB5, 0x49, 0xAC, 0x0F, 0x2F,
+ 0xDF, 0x76, 0x16, 0xB9, 0xE7, 0x99, 0x18, 0x1C, 0x71, 0x0B, 0x0E, 0xD3, 0x70, 0xF3, 0xCD, 0xB5,
+ 0xDD, 0xD6, 0x6D, 0xAA, 0xBF, 0x69, 0x01, 0xD0, 0x56, 0x75, 0xB5, 0x3D, 0x6B, 0x40, 0xF3, 0xFC,
+ 0x1B, 0x4F, 0xA5, 0x09, 0x98, 0xB3, 0xD4, 0xCC, 0x59, 0x24, 0x8F, 0x76, 0x5C, 0xC1, 0x9D, 0x40,
+ 0x1E, 0x6C, 0x18, 0x82, 0x8D, 0xCE, 0xEF, 0x25, 0xF3, 0xD9, 0xFE, 0x54, 0xFC, 0xDA, 0x6E, 0x8B,
+ 0xBA, 0x7A, 0x4F, 0x6C, 0x43, 0xCF, 0x67, 0x1B, 0x39, 0x1F, 0x3E, 0x56, 0x6D, 0xFB, 0x5E, 0x2A,
+ 0x12, 0x20, 0x42, 0xFC, 0x33, 0x4C, 0xB1, 0xF4, 0x56, 0x75, 0x96, 0x8B, 0x28, 0x2A, 0x22, 0x56,
+ 0x31, 0x6E, 0x5D, 0x3B, 0x5E, 0xD5, 0x23, 0xD9, 0x12, 0x85, 0x1A, 0xF2, 0x3C, 0xDC, 0x47, 0xF9,
+ 0xFC, 0x8E, 0xB7, 0xD4, 0xE7, 0x76, 0x61, 0xE1, 0x76, 0x71, 0xE0, 0x93, 0xEB, 0x32, 0x9F, 0xF2,
+ 0x10, 0xB9, 0x0C, 0x9C, 0x04, 0x42, 0xE7, 0xF8, 0x6D, 0x7E, 0xED, 0xCC, 0x1A, 0x6B, 0xCC, 0xC5,
+ 0xD3, 0xA1, 0xB4, 0x83, 0xE9, 0x6B, 0x40, 0xD4, 0x47, 0x85, 0xD7, 0x46, 0xEB, 0x0A, 0x54, 0x98,
+ 0x4D, 0xB7, 0x3D, 0xBA, 0xDD, 0x91, 0x25, 0xCA, 0x93, 0x8D, 0x14, 0x62, 0x6B, 0x62, 0x59, 0xB7,
+ 0xD8, 0xCF, 0xD8, 0x7D, 0x75, 0x77, 0x4C, 0x02, 0x4D, 0xA7, 0xAF, 0xBC, 0x8D, 0x5C, 0x94, 0x26,
+ 0xDB, 0xF0, 0x0B, 0xC7, 0xF3, 0x89, 0xAF, 0x57, 0xC4, 0xC3, 0xE1, 0x49, 0xE2, 0x7C, 0xFE, 0x17,
+ 0x45, 0xCD, 0x31, 0x0D, 0x62, 0x1C, 0x2A, 0xA5, 0x21, 0x39, 0xB2, 0x35, 0xBD, 0xAD, 0x6A, 0x9B,
+ 0xD4, 0xBA, 0x62, 0xA5, 0x41, 0xCE, 0x50, 0x48, 0xBF, 0x2F, 0x9A, 0xE7, 0xD2, 0x7C, 0x6A, 0x6F,
+ 0x77, 0xB8, 0x54, 0xA5, 0x3E, 0xB1, 0x0A, 0x7C, 0xCD, 0x03, 0xAB, 0x49, 0x0C, 0x23, 0x7B, 0xD3,
+ 0xDD, 0x8C, 0xAB, 0xEB, 0xBA, 0xF3, 0x92, 0x9F, 0xF1, 0x56, 0xF9, 0x45, 0x1F, 0xF9, 0xA2, 0x31,
+ 0xC3, 0xF1, 0xAA, 0x85, 0x96, 0x1F, 0x95, 0x87, 0xA2, 0x7E, 0xDE, 0x3B, 0x4E, 0x62, 0xE0, 0x23,
+ 0x6D, 0xD6, 0x73, 0xAE, 0x2E, 0x37, 0xD4, 0xBC, 0x16, 0x3B, 0x30, 0xDE, 0xCC, 0xE6, 0xEB, 0xBE,
+ 0x65, 0x9A, 0xE9, 0xBB, 0x2D, 0xA0, 0xCD, 0x27, 0x7D, 0xB2, 0x42, 0x00, 0xE9, 0x15, 0x94, 0x76,
+ 0x24, 0x5A, 0x9B, 0x79, 0xF0, 0x19, 0xA5, 0x10, 0xF9, 0x52, 0x53, 0xAD, 0xAA, 0xAC, 0xD8, 0x0C,
+ 0x55, 0x2B, 0x62, 0xE6, 0x49, 0xA3, 0xA8, 0x17, 0x05, 0xBC, 0x04, 0xBA, 0x15, 0x5D, 0xC4, 0xE9,
+ 0xDC, 0xB6, 0xAB, 0x0E, 0xD8, 0x29, 0x5A, 0x7C, 0xFD, 0x3A, 0xAE, 0x74, 0x3E, 0xD9, 0xE0, 0xCE,
+ 0x22, 0xD9, 0x44, 0xF9, 0x29, 0xB4, 0xB8, 0xAE, 0x3D, 0xD0, 0x71, 0xE2, 0x79, 0x69, 0xEB, 0xB2,
+ 0x1E, 0xE5, 0x23, 0x5D, 0x93, 0x38, 0x04, 0x21, 0x52, 0x15, 0xBE, 0x2A, 0x47, 0x9B, 0x04, 0xB1,
+ 0x5E, 0xEC, 0xCD, 0x10, 0xD1, 0xFB, 0xA6, 0x50, 0x62, 0xE8, 0xE1, 0x61, 0x5D, 0x8A, 0x18, 0x22,
+ 0x06, 0x34, 0x2F, 0x9E, 0x76, 0x25, 0x65, 0x3D, 0x43, 0x06, 0x39, 0xE1, 0xB5, 0x45, 0x08, 0x10,
+ 0xA1, 0x60, 0x03, 0xAB, 0xED, 0x32, 0xC6, 0x7C, 0xFC, 0xFC, 0x8B, 0x51, 0x55, 0x7F, 0x7E, 0x72,
+ 0x4D, 0xE1, 0x63, 0x26, 0x47, 0xBA, 0x34, 0xF6, 0x3F, 0x14, 0xAC, 0x9E, 0xCA, 0xC5, 0xB1, 0x90,
+ 0x86, 0x3B, 0xDB, 0x75, 0xEB, 0x2E, 0xFE, 0xE4, 0x17, 0x49, 0x1C, 0x17, 0x06, 0x46, 0x5E, 0x93,
+ 0x1A, 0x6E, 0xA7, 0xB3, 0xE0, 0x66, 0xDA, 0xC0, 0xC4, 0xEC, 0xD1, 0x56, 0x9A, 0xF4, 0x50, 0x46,
+ 0x1B, 0xFD, 0x71, 0xA4, 0xD2, 0xEC, 0x3F, 0x86, 0xEB, 0x3B, 0xC3, 0x84, 0x91, 0x5B, 0x1E, 0x60,
+ 0x59, 0xB3, 0x0E, 0x71, 0x87, 0xF6, 0x07, 0x24, 0xD3, 0x84, 0x2A, 0x12, 0xCA, 0xE0, 0x5B, 0x9A,
+ 0xD3, 0x4B, 0x48, 0x12, 0x54, 0xC7, 0xF2, 0x13, 0xD2, 0xDF, 0xA0, 0x41, 0x79, 0xB1, 0x8C, 0x07,
+ 0x7B, 0xCB, 0x26, 0x5C, 0x6E, 0x36, 0x70, 0xED, 0x8C, 0x7E, 0xF6, 0xFE, 0xB5, 0xEB, 0x35, 0xD2,
+ 0xE1, 0x28, 0x48, 0x76, 0x9B, 0x8A, 0x63, 0x9E, 0x5C, 0x6C, 0x43, 0x75, 0x85, 0x08, 0xD3, 0x89,
+ 0xBE, 0xDC, 0x26, 0xB5, 0xB0, 0x99, 0x2C, 0xEF, 0xB9, 0xAF, 0x99, 0x13, 0xDA, 0x8C, 0x53, 0xB5,
+ 0x8D, 0xA1, 0x17, 0xCC, 0xC6, 0xFC, 0x3B, 0xF5, 0x98, 0x9F, 0x7A, 0xFF, 0xA0, 0x3F, 0x5D, 0x6A,
+ 0x83, 0xFA, 0x23, 0xA0, 0x1E, 0xDC, 0xAF, 0x75, 0x11, 0x25, 0xC9, 0x52, 0x50, 0xF5, 0x16, 0x7E,
+ 0x53, 0xD7, 0x90, 0x6B, 0xA8, 0x95, 0x57, 0xB3, 0x75, 0x41, 0x7C, 0xD1, 0x7F, 0x59, 0xE3, 0x02,
+ 0x6D, 0xC5, 0x98, 0x73, 0xA0, 0x7D, 0x8C, 0x41, 0xE5, 0x99, 0x81, 0x79, 0xB9, 0x60, 0xBB, 0x13,
+ 0x8C, 0xEF, 0xEE, 0x91, 0x38, 0x36, 0x7A, 0x16, 0xE7, 0x8E, 0xB6, 0x44, 0xD7, 0x59, 0xE7, 0x8B,
+ 0x47, 0x79, 0xDC, 0x86, 0xF1, 0x82, 0xB2, 0x8C, 0x60, 0x92, 0xF0, 0x0A, 0x96, 0xD3, 0x9C, 0x5E,
+ 0xC9, 0x82, 0x9A, 0xDB, 0x64, 0x4B, 0x6A, 0xBA, 0x89, 0x16, 0xD5, 0x54, 0x63, 0x96, 0x75, 0x98,
+ 0x76, 0xBD, 0x5A, 0x7B, 0x4D, 0xDB, 0x94, 0xAF, 0x65, 0xDA, 0xA6, 0x7C, 0x4D, 0xD3, 0x36, 0xE5,
+ 0x6D, 0xA6, 0x6D, 0xCA, 0x5B, 0x4C, 0xDB, 0x94, 0x37, 0x9A, 0xF6, 0xF1, 0x31, 0xF1, 0x9A, 0xB6,
+ 0xDE, 0xBF, 0x96, 0x69, 0xEB, 0xFD, 0x6B, 0x9A, 0xB6, 0xDE, 0xDF, 0x66, 0xDA, 0x7A, 0x7F, 0x8B,
+ 0x69, 0xEB, 0xFD, 0x8D, 0xA6, 0x4D, 0x92, 0xC7, 0x47, 0xAF, 0x6D, 0x2F, 0xF5, 0x6B, 0xD9, 0xF6,
+ 0x52, 0xBF, 0xA6, 0x6D, 0x2F, 0xF5, 0x6D, 0xB6, 0xBD, 0xD4, 0xB7, 0xD8, 0xF6, 0x52, 0xDF, 0x6A,
+ 0xDB, 0x6C, 0xC8, 0xB6, 0xAF, 0x68, 0xDC, 0x57, 0xB6, 0xEE, 0xCD, 0xE6, 0xBD, 0xD1, 0xBE, 0x81,
+ 0x06, 0x5E, 0x88, 0x17, 0xD3, 0xCF, 0x87, 0x17, 0x7E, 0x9B, 0xD5, 0x98, 0x57, 0x7C, 0x14, 0xDB,
+ 0x17, 0x25, 0x7A, 0xBB, 0xA9, 0x05, 0xB9, 0x5D, 0xD1, 0x1C, 0xEA, 0x5F, 0x31, 0x5A, 0x87, 0x4C,
+ 0x6C, 0x61, 0xB2, 0x1A, 0xB5, 0xD5, 0xE9, 0xB0, 0x9B, 0x0B, 0x2C, 0x8C, 0xAB, 0x71, 0x6A, 0x8A,
+ 0xDA, 0xBA, 0xE4, 0xBC, 0x8C, 0xD9, 0xF0, 0x97, 0x86, 0xBB, 0x33, 0xBF, 0xCB, 0xA7, 0x36, 0x9C,
+ 0x2B, 0xAD, 0x2A, 0x51, 0x2F, 0xCB, 0x61, 0x82, 0x3D, 0xD1, 0x86, 0x59, 0xD3, 0x19, 0x2C, 0x21,
+ 0x6F, 0x9E, 0xC1, 0x18, 0x56, 0x2B, 0xA4, 0xD6, 0x4C, 0xE4, 0x72, 0x89, 0x99, 0xC2, 0xB0, 0xF0,
+ 0xD2, 0x9A, 0x4E, 0x92, 0x9A, 0x2B, 0x42, 0x59, 0x14, 0x7C, 0x89, 0xD2, 0x1D, 0x99, 0x1A, 0xE7,
+ 0x58, 0xBA, 0x04, 0xE8, 0xE4, 0xEA, 0xB1, 0x07, 0xCC, 0xBA, 0x0F, 0x51, 0x71, 0x3A, 0x01, 0x0A,
+ 0x2B, 0x29, 0x52, 0x38, 0xD2, 0xD7, 0x41, 0xBA, 0x11, 0xF5, 0x6C, 0xB1, 0x84, 0x2F, 0x3E, 0x86,
+ 0xFD, 0xCD, 0x23, 0xCE, 0x18, 0x83, 0xA2, 0x79, 0x5F, 0x9A, 0xD5, 0x00, 0x5D, 0xBC, 0x72, 0xEC,
+ 0xFF, 0xFB, 0x02, 0x9C, 0xA0, 0x17, 0x01, 0x46, 0xF9, 0x4E, 0xEC, 0x55, 0x44, 0x2F, 0xCF, 0x2F,
+ 0x2F, 0x80, 0x08, 0x9A, 0x6E, 0xE5, 0x9D, 0xA5, 0x1F, 0x3E, 0x85, 0x19, 0xC3, 0xF7, 0x0F, 0x26,
+ 0x32, 0x24, 0xE0, 0xDC, 0x4A, 0xE8, 0x3C, 0x8B, 0x27, 0x31, 0x23, 0x39, 0x23, 0xBC, 0x99, 0x25,
+ 0xF5, 0xEC, 0xEB, 0x2E, 0x19, 0x9D, 0xF5, 0x88, 0x2C, 0xAF, 0x91, 0xC9, 0x65, 0x10, 0xD5, 0x32,
+ 0xE9, 0xCC, 0xEA, 0x2C, 0x1B, 0x13, 0x2A, 0x9C, 0xAB, 0x87, 0x2B, 0x29, 0xE7, 0x58, 0xB6, 0x5D,
+ 0xAB, 0x63, 0xD9, 0xDB, 0x35, 0x56, 0x62, 0x26, 0xB2, 0x23, 0xD9, 0xA1, 0xDC, 0xA9, 0x65, 0xF5,
+ 0xD2, 0x04, 0x66, 0x0E, 0x5D, 0xF1, 0xA3, 0xBB, 0x85, 0xA3, 0xC9, 0x6D, 0x25, 0x7C, 0x26, 0x97,
+ 0x4C, 0xBC, 0x46, 0x77, 0x9A, 0x5E, 0x92, 0xDC, 0x62, 0x7C, 0xE5, 0x52, 0xB6, 0xF5, 0x55, 0xDA,
+ 0xCD, 0x8E, 0xAD, 0x59, 0x92, 0xDC, 0x39, 0x24, 0x10, 0x0C, 0xE1, 0x6A, 0x13, 0x7C, 0x5B, 0xF1,
+ 0x08, 0xB2, 0xB5, 0x42, 0xF5, 0x99, 0x5B, 0x41, 0x1D, 0xF6, 0x8E, 0xD1, 0xD6, 0x74, 0x3D, 0xE7,
+ 0x16, 0x53, 0x83, 0x8F, 0x11, 0x37, 0x97, 0x5D, 0xC3, 0xCD, 0x8E, 0x2E, 0xF8, 0xD1, 0xEC, 0x73,
+ 0xF6, 0x04, 0x41, 0x3A, 0xD7, 0xAB, 0xB8, 0x3A, 0x88, 0x0F, 0xF3, 0x74, 0x54, 0x83, 0x23, 0x8A,
+ 0x8A, 0xE7, 0x77, 0x75, 0x67, 0x01, 0x00, 0x85, 0xE5, 0xE9, 0x3D, 0xE3, 0xC3, 0xF1, 0x58, 0x9D,
+ 0x8C, 0x65, 0x81, 0x54, 0xAE, 0x90, 0xB8, 0x42, 0xBC, 0x0C, 0xDE, 0xF4, 0x76, 0xAF, 0x3C, 0x7A,
+ 0x76, 0xC6, 0xC0, 0x2C, 0x62, 0x6C, 0xC3, 0xBB, 0x22, 0xDF, 0x92, 0xE7, 0x9A, 0x0D, 0x4D, 0x50,
+ 0x5F, 0xB1, 0xE0, 0xF3, 0xFF, 0x08, 0xE5, 0xD6, 0x11, 0xCA, 0xDF, 0xB2, 0xE4, 0xDD, 0x97, 0xF8,
+ 0xAC, 0xFB, 0x30, 0x69, 0x90, 0x93, 0x98, 0xB5, 0x2D, 0x1F, 0xE0, 0xA7, 0x03, 0x84, 0x3E, 0x30,
+ 0xF0, 0xE8, 0x10, 0xC8, 0x12, 0x7B, 0xCF, 0x80, 0x48, 0x0B, 0x1C, 0x1D, 0x5F, 0x7C, 0x66, 0xAB,
+ 0xF6, 0x60, 0xD2, 0x02, 0x9C, 0x80, 0x75, 0x6B, 0xAB, 0x96, 0x13, 0xA3, 0x85, 0xB1, 0xC7, 0x78,
+ 0x8F, 0xD0, 0xCE, 0x1E, 0x5E, 0xE1, 0xEE, 0x26, 0x3F, 0x26, 0x8D, 0x8E, 0x6E, 0x21, 0x3C, 0x83,
+ 0x1A, 0xCB, 0x3B, 0xF2, 0x3B, 0xF5, 0x95, 0xC5, 0x3B, 0xAA, 0xAE, 0xC4, 0xBA, 0xE2, 0x16, 0xC2,
+ 0x24, 0x7D, 0x85, 0xFF, 0x07, 0x2B, 0x29, 0xBB, 0x85, 0xC1, 0xB2, 0xAC, 0x8E, 0x25, 0x01, 0xAB,
+ 0xC6, 0x9B, 0x97, 0xE4, 0x27, 0x86, 0x7A, 0x66, 0x1C, 0x90, 0xFB, 0x94, 0xC1, 0x32, 0xF6, 0x29,
+ 0xC5, 0x7B, 0x16, 0x6F, 0x09, 0x73, 0xBB, 0x49, 0x99, 0x77, 0x96, 0xB4, 0xE4, 0x11, 0x52, 0xD6,
+ 0xA0, 0xB6, 0x1B, 0x4B, 0x69, 0x0D, 0x46, 0xBC, 0x49, 0xEF, 0x49, 0x25, 0xAE, 0xBC, 0x6A, 0xB0,
+ 0xC8, 0x15, 0x4A, 0x8F, 0x81, 0xC3, 0xB4, 0x7B, 0xAA, 0xAF, 0x62, 0x1A, 0x54, 0xBA, 0x54, 0x3E,
+ 0x9F, 0x99, 0xC4, 0x74, 0xC4, 0x66, 0x5B, 0x8A, 0x8D, 0xDB, 0xEE, 0x2C, 0x60, 0xC5, 0x24, 0xA4,
+ 0x84, 0x15, 0xA6, 0xA7, 0x90, 0xF5, 0xCE, 0xD3, 0x6D, 0xBA, 0xDF, 0x52, 0xC8, 0xD8, 0x7D, 0xA8,
+ 0x75, 0x9C, 0x01, 0x6B, 0xFB, 0x50, 0x87, 0x87, 0x38, 0x10, 0x04, 0xC6, 0xDF, 0xE1, 0xF1, 0x27,
+ 0x5A, 0x8C, 0x7E, 0xC9, 0xBF, 0x4F, 0xBA, 0x8B, 0xC5, 0x4F, 0x70, 0x56, 0x81, 0xFD, 0x60, 0x03,
+ 0xA6, 0xC6, 0xA1, 0x88, 0x80, 0xC3, 0xE1, 0xBF, 0xA4, 0x0D, 0x03, 0x75, 0xC2, 0x9A, 0x30, 0xA2,
+ 0x16, 0x9F, 0x0A, 0x7A, 0x5A, 0x31, 0xF4, 0x17, 0x7B, 0x70, 0x7C, 0x6F, 0x23, 0x06, 0x2C, 0x82,
+ 0xDA, 0x30, 0xD4, 0x9B, 0xE1, 0x69, 0xB5, 0xC1, 0x8E, 0xB7, 0x28, 0xAE, 0x1C, 0x5C, 0xF3, 0x9D,
+ 0xF0, 0xC0, 0x13, 0x8E, 0x13, 0xDD, 0x8F, 0x26, 0x69, 0x87, 0x0A, 0x3A, 0x48, 0x95, 0xCE, 0xFF,
+ 0x92, 0xFD, 0x79, 0x3B, 0x53, 0x8E, 0xC0, 0x85, 0x88, 0xC2, 0x63, 0x51, 0xF3, 0xF8, 0x2E, 0xE2,
+ 0xA5, 0x11, 0xC8, 0x61, 0x5B, 0xD5, 0x7C, 0xCF, 0x1D, 0x61, 0x0B, 0xEB, 0x80, 0x08, 0x34, 0x3E,
+ 0x6F, 0xB6, 0x4F, 0xDD, 0x01, 0xE5, 0x97, 0xE2, 0x18, 0xFD, 0x8A, 0xA8, 0x6A, 0x0E, 0xE5, 0x3B,
+ 0xED, 0xE4, 0x3C, 0x56, 0x43, 0x89, 0x3B, 0x17, 0x20, 0xE3, 0xD0, 0xBA, 0x2A, 0x4E, 0x1F, 0x83,
+ 0xC9, 0x9F, 0x8C, 0xAA, 0x8F, 0xA4, 0x23, 0xE1, 0x6E, 0xD4, 0x34, 0x03, 0x43, 0x32, 0x9B, 0xEA,
+ 0xD8, 0x07, 0xD0, 0x44, 0x52, 0x7F, 0x4C, 0x54, 0x78, 0xC0, 0x0E, 0xFC, 0x3A, 0x92, 0xCF, 0x00,
+ 0x1F, 0x6A, 0x58, 0xA7, 0xD4, 0x4A, 0x7F, 0xE2, 0x07, 0x79, 0x02, 0xFF, 0xAF, 0x6E, 0x0B, 0x37,
+ 0x6C, 0x65, 0x90, 0x9F, 0xB6, 0xB2, 0xE1, 0xD1, 0x11, 0x26, 0x57, 0x0F, 0x24, 0x0D, 0x24, 0xFC,
+ 0x8C, 0x69, 0xE6, 0x2D, 0x44, 0xDB, 0xC4, 0xBF, 0xEB, 0x54, 0x42, 0x5E, 0x1D, 0xCB, 0x07, 0x2E,
+ 0x45, 0x1D, 0xE6, 0xB4, 0xD3, 0x55, 0x85, 0x9E, 0x5F, 0x1D, 0x07, 0xCE, 0xBE, 0x51, 0x27, 0x60,
+ 0x51, 0x04, 0xA3, 0x73, 0xAA, 0x2D, 0x85, 0x70, 0x7D, 0x3C, 0x22, 0x22, 0x25, 0x03, 0xF1, 0xE5,
+ 0x71, 0x35, 0x9B, 0x1C, 0x8F, 0xFA, 0x18, 0xC5, 0x04, 0xB2, 0x9E, 0xA1, 0x0D, 0x83, 0x54, 0x0D,
+ 0xFD, 0xC4, 0x9E, 0x9F, 0x32, 0x96, 0xDC, 0x1C, 0xFE, 0x1C, 0x21, 0x99, 0x23, 0x83, 0x3C, 0x6F,
+ 0x5E, 0x22, 0x67, 0x20, 0xA0, 0x04, 0xCF, 0x45, 0x53, 0x25, 0x43, 0x4A, 0xD3, 0x46, 0xE4, 0x39,
+ 0xE8, 0xE4, 0x80, 0xB5, 0x5C, 0x76, 0xEB, 0xCC, 0x35, 0x8B, 0x5B, 0xF0, 0xEF, 0xF0, 0x80, 0x67,
+ 0x21, 0xF2, 0x27, 0x2A, 0x3A, 0x50, 0x31, 0x44, 0x73, 0x71, 0x7A, 0xAE, 0x55, 0xDD, 0x60, 0xC9,
+ 0xB2, 0x18, 0xBC, 0x43, 0x93, 0xBE, 0x6B, 0xE8, 0x0D, 0x3E, 0xFA, 0xE8, 0x91, 0x27, 0x54, 0xA0,
+ 0x6E, 0x70, 0x72, 0x7C, 0x0A, 0x29, 0x0E, 0x5D, 0x44, 0x12, 0xA7, 0xE3, 0x71, 0xE7, 0x0D, 0x0F,
+ 0xA3, 0x39, 0x4A, 0xE2, 0x86, 0x57, 0x85, 0x26, 0xD9, 0x29, 0xD0, 0xCC, 0xFA, 0xA6, 0x8B, 0x1B,
+ 0xA8, 0x2E, 0x11, 0x78, 0x24, 0x28, 0x4A, 0x0F, 0x90, 0x3D, 0xA5, 0xEA, 0x5E, 0x72, 0xC2, 0x1B,
+ 0x06, 0xC6, 0xDE, 0x92, 0x9B, 0xA3, 0x9E, 0xE3, 0x79, 0xB2, 0x21, 0x47, 0x21, 0x7E, 0xB0, 0x7C,
+ 0x71, 0xD7, 0xA3, 0xAA, 0x84, 0xB9, 0x43, 0x22, 0xEB, 0xB5, 0x3A, 0x1C, 0x16, 0xCA, 0xCF, 0xA1,
+ 0x4F, 0xCC, 0xF4, 0x9E, 0x0A, 0x03, 0x66, 0x16, 0xFE, 0x50, 0x9E, 0xAE, 0x12, 0xBF, 0x3C, 0xD9,
+ 0xFD, 0x7B, 0xDE, 0x08, 0xA6, 0xB7, 0x6B, 0xA0, 0xA0, 0xE8, 0xED, 0x9A, 0x24, 0x59, 0x64, 0xF9,
+ 0x72, 0x96, 0xD0, 0x77, 0x7D, 0x62, 0xF9, 0x64, 0xCF, 0xD7, 0x39, 0xFC, 0x5E, 0xCF, 0x3E, 0xAA,
+ 0xE5, 0x83, 0x3E, 0xB3, 0x9C, 0x60, 0x45, 0xF8, 0xAC, 0x8F, 0xF3, 0xFD, 0x9E, 0x1A, 0x52, 0xF9,
+ 0x63, 0x41, 0xFC, 0x6E, 0x8C, 0xA7, 0x58, 0xFF, 0x35, 0x46, 0x5A, 0x8A, 0x7C, 0x2B, 0x1B, 0xF1,
+ 0xEC, 0xD4, 0xAB, 0x19, 0xB1, 0x9C, 0xF7, 0xA1, 0xA3, 0x48, 0xDA, 0x54, 0xBD, 0x94, 0x34, 0xFB,
+ 0x68, 0xA6, 0xD8, 0xA6, 0xA2, 0x00, 0x42, 0xDE, 0x3E, 0xB2, 0x86, 0x67, 0xE5, 0x61, 0x5B, 0x9C,
+ 0x9F, 0x4F, 0x2D, 0x6F, 0x30, 0x7D, 0x2F, 0x61, 0x62, 0x50, 0x68, 0xAB, 0xE5, 0xF4, 0x34, 0x93,
+ 0xD8, 0x38, 0xDA, 0xC3, 0xB2, 0x24, 0x27, 0x6F, 0x30, 0xF2, 0x79, 0x21, 0xA0, 0xD0, 0xE7, 0x2C,
+ 0x9C, 0xAA, 0xCF, 0xD4, 0xB4, 0x45, 0x6C, 0x2F, 0x00, 0x75, 0x75, 0xFE, 0xDE, 0x77, 0x7D, 0x4D,
+ 0xA8, 0xF9, 0x71, 0xDC, 0x1D, 0x39, 0xED, 0xCE, 0x26, 0xBE, 0xF4, 0x81, 0x69, 0x33, 0xF8, 0xE8,
+ 0x51, 0x17, 0x40, 0x96, 0x6E, 0xF0, 0x5D, 0x35, 0x55, 0xA0, 0x86, 0xC8, 0x6C, 0xF4, 0xF8, 0xF8,
+ 0x08, 0x49, 0xDB, 0x37, 0xA7, 0x16, 0x5A, 0x32, 0x3D, 0xBA, 0xBE, 0x7D, 0xFD, 0xD9, 0x5C, 0xBA,
+ 0x83, 0x36, 0x60, 0x20, 0x9A, 0xB8, 0xB6, 0x9E, 0x07, 0x69, 0xB8, 0x6F, 0x99, 0x1E, 0x83, 0x36,
+ 0xC8, 0xFA, 0xAC, 0x4B, 0x71, 0x92, 0x75, 0x03, 0x1F, 0xD7, 0x70, 0x67, 0x5B, 0xBC, 0x08, 0x72,
+ 0x87, 0x4F, 0xA2, 0xF3, 0xA8, 0x7C, 0xF7, 0x6F, 0x73, 0xF5, 0x1E, 0xA3, 0xAD, 0x29, 0x2D, 0xA7,
+ 0x81, 0x7D, 0x1A, 0xD2, 0x8C, 0x6E, 0xF9, 0xA0, 0x36, 0x2A, 0xE4, 0x72, 0x58, 0x87, 0xA1, 0x50,
+ 0xFD, 0x2D, 0xF5, 0xC8, 0x8D, 0xB2, 0x39, 0x95, 0x1A, 0x66, 0x70, 0xE7, 0xEE, 0x14, 0x65, 0xC5,
+ 0xAD, 0x8B, 0xB8, 0xAE, 0xB0, 0xD1, 0xED, 0x8B, 0x9C, 0xBB, 0x45, 0xCA, 0xCD, 0x60, 0x8A, 0x78,
+ 0x65, 0xAF, 0xD9, 0x65, 0x2B, 0xF5, 0x98, 0xDD, 0xEF, 0x9F, 0xDF, 0x46, 0xB6, 0xB0, 0x91, 0x27,
+ 0x75, 0xAE, 0xE4, 0x51, 0x80, 0x48, 0xC4, 0x23, 0xC5, 0xF8, 0xFB, 0x0B, 0xF1, 0x45, 0x57, 0x07,
+ 0xA8, 0x93, 0xA2, 0x2A, 0x7C, 0x29, 0x72, 0xEB, 0x5A, 0x21, 0xF5, 0x46, 0xE8, 0x97, 0x1D, 0x32,
+ 0x0F, 0xDA, 0xBB, 0x58, 0xE7, 0x2C, 0x5C, 0x30, 0xC9, 0xF6, 0x27, 0x93, 0xB0, 0xA9, 0x1D, 0xC4,
+ 0xE1, 0x69, 0xB6, 0x37, 0x24, 0x3F, 0x93, 0xA5, 0x93, 0xB4, 0x6A, 0x86, 0x4A, 0x6E, 0x2F, 0x16,
+ 0xB3, 0xE2, 0x77, 0xD8, 0xA5, 0x1B, 0x70, 0x33, 0xC8, 0x19, 0x56, 0x3C, 0x32, 0x37, 0x82, 0xE2,
+ 0x7C, 0x45, 0x5E, 0x0B, 0x93, 0xC3, 0xAB, 0x94, 0x30, 0x75, 0x76, 0xB3, 0x7C, 0x79, 0x01, 0xE2,
+ 0x34, 0x67, 0x6A, 0x51, 0x0A, 0x85, 0x43, 0x0B, 0x52, 0xE0, 0xDA, 0x59, 0xF5, 0x17, 0x62, 0x70,
+ 0x63, 0x6B, 0x7B, 0xC5, 0x83, 0x6D, 0x49, 0x96, 0x99, 0xA8, 0x7C, 0xA3, 0x87, 0xCC, 0xB0, 0xB5,
+ 0xDB, 0x7E, 0xE2, 0x49, 0x87, 0x02, 0x79, 0xDE, 0xED, 0xB6, 0xC5, 0xF1, 0xE7, 0xA2, 0xA5, 0x37,
+ 0x72, 0xF8, 0xD4, 0x28, 0xCE, 0xE3, 0x90, 0x8B, 0x47, 0xE4, 0xD6, 0x25, 0x5B, 0x3F, 0xB9, 0xA1,
+ 0x1F, 0xC4, 0x11, 0xBF, 0xE7, 0x3A, 0xDC, 0x6D, 0x6B, 0x2F, 0x98, 0x77, 0xD6, 0x49, 0x21, 0x2C,
+ 0xF4, 0x8E, 0xE7, 0xC4, 0xDB, 0x3F, 0x86, 0x84, 0x49, 0xD7, 0x71, 0x54, 0xEC, 0x5D, 0x95, 0x12,
+ 0xFA, 0x98, 0xBA, 0x21, 0x2B, 0xF4, 0xB2, 0x0E, 0x12, 0xC8, 0x65, 0x46, 0x91, 0x69, 0xE9, 0x69,
+ 0x1F, 0x27, 0x3C, 0x2B, 0xF4, 0x8E, 0xA4, 0x41, 0xAD, 0x67, 0xEF, 0xE6, 0x51, 0x07, 0x5D, 0xC1,
+ 0x65, 0x90, 0x34, 0xAB, 0x01, 0xF6, 0x2F, 0xB4, 0x0E, 0xAD, 0x06, 0xA1, 0x2C, 0x79, 0x26, 0x0C,
+ 0x1F, 0x2C, 0xF1, 0x88, 0x02, 0x25, 0xA6, 0x4A, 0x62, 0x82, 0x40, 0x88, 0x2D, 0x48, 0x67, 0xAD,
+ 0x1F, 0x39, 0x8A, 0xEB, 0x69, 0xE4, 0xB2, 0x61, 0xE8, 0xA2, 0xB2, 0x43, 0x83, 0x6F, 0x79, 0x5E,
+ 0x35, 0x9B, 0xEB, 0x74, 0xF9, 0xA0, 0xF7, 0x44, 0xE1, 0x54, 0xB6, 0x3A, 0xD6, 0x49, 0x57, 0xD0,
+ 0xE4, 0xFA, 0x2D, 0xB4, 0x1C, 0x93, 0x56, 0x77, 0xC5, 0x93, 0x15, 0x18, 0x53, 0x8C, 0xC6, 0xB1,
+ 0x46, 0x98, 0x5E, 0x35, 0xB0, 0xD2, 0x70, 0xB1, 0x00, 0x5F, 0x48, 0x53, 0x54, 0x18, 0x89, 0xCA,
+ 0x0E, 0xD0, 0x4D, 0xA0, 0xC8, 0x15, 0x53, 0x19, 0xDF, 0x7C, 0x95, 0x57, 0xAB, 0xA5, 0xA2, 0xA4,
+ 0xF1, 0xA6, 0x92, 0xC7, 0x8F, 0xD6, 0x39, 0x81, 0x21, 0x4F, 0x4C, 0xA3, 0x41, 0x3E, 0x56, 0xDB,
+ 0x65, 0xA6, 0xA8, 0xEC, 0xF0, 0x4B, 0xE5, 0xB6, 0xD8, 0xC5, 0x06, 0x40, 0xF3, 0xC2, 0x04, 0x9B,
+ 0x51, 0x56, 0x96, 0xF9, 0x4E, 0x33, 0xA2, 0x01, 0x89, 0xA0, 0xAC, 0x92, 0x78, 0x6D, 0xC3, 0x90,
+ 0x1D, 0xA6, 0x69, 0x8E, 0x48, 0x55, 0x66, 0x8F, 0x8A, 0x8A, 0xC4, 0xE7, 0x29, 0xB7, 0x30, 0x9D,
+ 0xCC, 0x2D, 0x10, 0xF2, 0xC3, 0x24, 0x8B, 0xDD, 0x26, 0x4E, 0x8B, 0x4C, 0x2B, 0x81, 0xF1, 0x6A,
+ 0x30, 0x28, 0xBE, 0x09, 0xD1, 0xCC, 0x8C, 0x14, 0x9B, 0x97, 0xC0, 0xDF, 0x6D, 0x7A, 0xF9, 0xA7,
+ 0x9F, 0xEC, 0x90, 0xF9, 0x06, 0x40, 0x73, 0xC2, 0x04, 0x62, 0xFE, 0x22, 0xA9, 0xC4, 0xBB, 0x2F,
+ 0xB2, 0x17, 0x1C, 0xDC, 0xC7, 0xD0, 0x38, 0xDD, 0x4E, 0x9B, 0xED, 0xD1, 0xE6, 0xC4, 0x02, 0x4F,
+ 0x61, 0x15, 0xED, 0x0B, 0xF4, 0x85, 0x91, 0xA4, 0x9B, 0x5B, 0x9E, 0x2E, 0x93, 0x3E, 0x7B, 0x7F,
+ 0xE4, 0x18, 0x98, 0xE7, 0x5A, 0xBA, 0x26, 0x8F, 0x92, 0x4B, 0x72, 0x65, 0x72, 0x2C, 0x8C, 0xE5,
+ 0x25, 0xE3, 0x18, 0x6A, 0x9F, 0x29, 0x9B, 0x7D, 0x38, 0x5B, 0xCE, 0xDE, 0x9F, 0xE9, 0xEA, 0xAC,
+ 0x39, 0xAE, 0x2E, 0x8F, 0x3E, 0x82, 0x47, 0x20, 0x48, 0x56, 0x94, 0x22, 0x4D, 0x86, 0x29, 0xD2,
+ 0x04, 0x29, 0xE4, 0x40, 0x22, 0x3A, 0xF3, 0xDB, 0xBE, 0xBC, 0xF1, 0xB4, 0xC6, 0x19, 0x59, 0xAC,
+ 0x69, 0x75, 0x33, 0x47, 0xC8, 0x47, 0x97, 0x22, 0x14, 0x07, 0xE8, 0x8E, 0xB7, 0x3F, 0xFD, 0x6A,
+ 0x8B, 0xEF, 0xC6, 0x49, 0x0A, 0xF6, 0x49, 0x9F, 0xAE, 0xBF, 0x03, 0xAE, 0xC5, 0x26, 0x8D, 0xFD,
+ 0xD7, 0x2E, 0x3B, 0xFE, 0x6D, 0x73, 0xAF, 0x08, 0x5F, 0xB0, 0x6B, 0x94, 0xD1, 0x94, 0x7F, 0x81,
+ 0x8C, 0xC7, 0xC7, 0xD4, 0x94, 0x51, 0xEF, 0xFF, 0x02, 0x19, 0xEA, 0x52, 0x26, 0x0A, 0xB9, 0xD4,
+ 0x7F, 0x85, 0x90, 0x25, 0x11, 0xF2, 0x0A, 0x52, 0x16, 0x30, 0x6C, 0x7D, 0x53, 0xD4, 0xF5, 0xAF,
+ 0x91, 0x1A, 0xB4, 0x3E, 0xD0, 0x84, 0x48, 0x34, 0x33, 0x32, 0x12, 0x8D, 0x8A, 0x18, 0x22, 0xBF,
+ 0xCE, 0x8D, 0xCF, 0xA0, 0xDC, 0xF9, 0xE9, 0x70, 0x9C, 0xF3, 0x2A, 0xFF, 0xD6, 0xA1, 0x79, 0x79,
+ 0x3E, 0x9D, 0x8B, 0x3E, 0x66, 0x09, 0xF4, 0xC7, 0x98, 0xA6, 0x2B, 0xBE, 0x9D, 0x88, 0x0B, 0x51,
+ 0x98, 0xD6, 0xDF, 0xCF, 0xB6, 0x30, 0xE9, 0xEE, 0xAB, 0x01, 0x91, 0xC3, 0x6A, 0x31, 0x58, 0xD7,
+ 0x3D, 0x7E, 0x3C, 0x47, 0xA0, 0xE3, 0xF5, 0x22, 0x04, 0xF6, 0xEB, 0x50, 0x98, 0x24, 0xAA, 0xD7,
+ 0xA9, 0x3A, 0x6F, 0x9F, 0xA0, 0x92, 0x8A, 0x96, 0x56, 0x77, 0xFB, 0xA3, 0x4F, 0xC8, 0x79, 0xAB,
+ 0xAC, 0xD5, 0x5A, 0xEA, 0x33, 0xA4, 0x6F, 0x8E, 0xDB, 0xE2, 0x5C, 0x5D, 0x49, 0x8E, 0xD4, 0xEA,
+ 0x4C, 0x9F, 0x58, 0xD5, 0x90, 0xAD, 0xF6, 0xD0, 0x3A, 0x72, 0xF0, 0xFB, 0x42, 0x8D, 0xD3, 0xBB,
+ 0x37, 0x02, 0xAF, 0x9E, 0xB7, 0x03, 0x31, 0x5B, 0x1D, 0x05, 0x28, 0x4D, 0x91, 0x21, 0x89, 0xE3,
+ 0x35, 0xA0, 0x51, 0xCD, 0xF8, 0xAA, 0x54, 0x8E, 0xAD, 0x32, 0x4F, 0xB1, 0x55, 0x2A, 0xC7, 0x16,
+ 0xD9, 0xF4, 0x90, 0x68, 0x90, 0x9F, 0xCE, 0x99, 0x01, 0x9D, 0x6E, 0x52, 0xC8, 0x1D, 0x73, 0x3D,
+ 0x48, 0xC7, 0xDD, 0x73, 0x8E, 0x51, 0x1D, 0x4B, 0x0D, 0x97, 0x05, 0xC9, 0x11, 0xC4, 0x20, 0x50,
+ 0x63, 0x88, 0x8F, 0x26, 0x42, 0x57, 0x52, 0xF2, 0x4C, 0x09, 0x16, 0xA6, 0xF8, 0xE6, 0x42, 0xD3,
+ 0xF5, 0x8B, 0x96, 0xBA, 0x13, 0x55, 0xCE, 0x2D, 0x6B, 0xC3, 0x43, 0x74, 0x82, 0x0B, 0x59, 0xDF,
+ 0x73, 0x47, 0x5C, 0xF5, 0xDD, 0x44, 0x2D, 0x23, 0xB5, 0x12, 0x41, 0x16, 0x26, 0x28, 0x0A, 0x2E,
+ 0x56, 0x38, 0x57, 0x30, 0x2C, 0x74, 0x82, 0xE7, 0x40, 0xD8, 0x9F, 0x0E, 0x65, 0x0F, 0x17, 0x5F,
+ 0x6C, 0xF0, 0x19, 0x9A, 0x18, 0x54, 0x48, 0x7E, 0x73, 0x20, 0x44, 0xE6, 0x72, 0x4A, 0x9F, 0xE2,
+ 0x42, 0xDC, 0x42, 0x7D, 0xE9, 0x31, 0x31, 0xC9, 0x46, 0x15, 0xD3, 0x3D, 0x6B, 0xC2, 0xEB, 0x32,
+ 0x81, 0x8D, 0xA5, 0xD3, 0x38, 0xF2, 0xF1, 0x19, 0x0D, 0xCA, 0x7C, 0x43, 0x1D, 0x1C, 0xBF, 0x06,
+ 0x06, 0x9F, 0xE2, 0x94, 0xD0, 0xFB, 0x12, 0xE2, 0x44, 0x9F, 0xE2, 0x4F, 0x73, 0xCA, 0x20, 0x5E,
+ 0x3B, 0x39, 0xD4, 0x7B, 0x8B, 0x83, 0x12, 0x9C, 0x31, 0xE9, 0x4E, 0x62, 0x95, 0x39, 0x24, 0x67,
+ 0xF9, 0xEB, 0x9A, 0x43, 0x1D, 0xFF, 0x06, 0x9B, 0x47, 0xF5, 0xDD, 0x89, 0xDA, 0x0D, 0x45, 0xF9,
+ 0xE0, 0xD4, 0x89, 0xDE, 0x35, 0xB4, 0xAC, 0xE9, 0x75, 0xA3, 0xCB, 0x11, 0x14, 0x19, 0x50, 0xB9,
+ 0x10, 0x75, 0x67, 0xE9, 0xEB, 0x44, 0x91, 0x84, 0x76, 0xA7, 0x26, 0x33, 0x68, 0x6D, 0xA2, 0x58,
+ 0x4D, 0x95, 0x69, 0x72, 0xAE, 0xD2, 0x61, 0x4D, 0x8E, 0x42, 0x92, 0x58, 0x81, 0x92, 0xD8, 0x86,
+ 0xA9, 0xF6, 0x0E, 0xF8, 0x75, 0x7D, 0x88, 0x03, 0x98, 0xF7, 0xD0, 0xDC, 0x49, 0x0C, 0x3C, 0x35,
+ 0x02, 0x63, 0x2F, 0x1B, 0x41, 0xE0, 0xAE, 0x3A, 0x26, 0x0E, 0xC9, 0x35, 0x28, 0x77, 0x90, 0x01,
+ 0x5F, 0x05, 0x65, 0x5C, 0xA1, 0xE1, 0x04, 0x9E, 0xBA, 0xF7, 0xA3, 0x80, 0xBC, 0x83, 0xE4, 0x8C,
+ 0x08, 0x38, 0x2A, 0x20, 0x67, 0xD9, 0x4F, 0xCD, 0xBB, 0xDE, 0xC7, 0x15, 0x00, 0x2A, 0x02, 0x96,
+ 0x0F, 0xE2, 0xD7, 0x7C, 0x88, 0x34, 0xBA, 0xB8, 0x4F, 0xDA, 0x8C, 0x13, 0xFE, 0xEA, 0x22, 0xFC,
+ 0x96, 0x13, 0xEA, 0xB5, 0x7C, 0xDF, 0x9B, 0xF0, 0x1C, 0x15, 0xAC, 0xE5, 0x1A, 0x6D, 0xE0, 0xEA,
+ 0xC5, 0x15, 0x3F, 0x06, 0xF1, 0x53, 0x3E, 0x88, 0x5F, 0x5C, 0x7C, 0x65, 0xFF, 0x66, 0xAE, 0x45,
+ 0x05, 0x71, 0x96, 0x65, 0x6B, 0x91, 0x39, 0x79, 0xEB, 0x2E, 0xDC, 0xFA, 0x16, 0xC4, 0x1F, 0x1D,
+ 0xDE, 0xFC, 0xEE, 0x92, 0xA1, 0x7B, 0x70, 0x5C, 0xE2, 0x0A, 0xCB, 0x80, 0x76, 0x79, 0x93, 0xCE,
+ 0xC5, 0xBD, 0x5F, 0xA1, 0x71, 0x3D, 0x38, 0xC6, 0xD1, 0x71, 0x29, 0x05, 0x09, 0x70, 0x15, 0xC6,
+ 0x49, 0xA0, 0xD7, 0x55, 0x10, 0x1D, 0x97, 0x57, 0x38, 0xBA, 0x5A, 0x34, 0x41, 0x5C, 0x5C, 0x3B,
+ 0xE1, 0xB8, 0xFD, 0xBA, 0x08, 0xA2, 0xE3, 0xF2, 0x08, 0x47, 0xD7, 0xCB, 0x18, 0x88, 0x6D, 0xAC,
+ 0x7D, 0x70, 0x6C, 0xBD, 0xA6, 0x81, 0xC8, 0xB8, 0xB4, 0xE1, 0x64, 0x7D, 0xFA, 0x09, 0x71, 0x8D,
+ 0xB5, 0x0B, 0x07, 0xAE, 0x1C, 0xCA, 0x32, 0x9D, 0x77, 0x0E, 0xCC, 0xA4, 0xC3, 0xC2, 0x99, 0x04,
+ 0x47, 0x4A, 0x6D, 0xA4, 0xD4, 0x89, 0x94, 0xD9, 0x48, 0x99, 0x13, 0x69, 0x69, 0x23, 0x2D, 0x9D,
+ 0x48, 0xB9, 0x8D, 0x94, 0xDB, 0x48, 0xBF, 0x44, 0x69, 0x7E, 0xD5, 0x8A, 0xE4, 0xEF, 0xD8, 0x10,
+ 0x68, 0x10, 0x35, 0x4D, 0x4C, 0x20, 0xEB, 0x8E, 0x66, 0x4D, 0x69, 0x92, 0x58, 0x13, 0xB1, 0x86,
+ 0x52, 0x8D, 0x38, 0x15, 0x90, 0x0D, 0x37, 0x1B, 0x45, 0x89, 0x9B, 0x15, 0x94, 0xFA, 0x67, 0xC2,
+ 0xFA, 0x67, 0x6B, 0x38, 0x25, 0x66, 0x9F, 0x1A, 0x43, 0x7C, 0xF4, 0x60, 0x3D, 0x89, 0xBC, 0xAA,
+ 0xF9, 0x1C, 0xCD, 0xEC, 0x13, 0x64, 0x56, 0x83, 0x20, 0xB7, 0x04, 0xB4, 0xEE, 0xA9, 0xD6, 0x84,
+ 0x4A, 0x4A, 0xC4, 0xA5, 0x21, 0x1B, 0x28, 0xF3, 0xAB, 0xA1, 0x3C, 0xC3, 0x4F, 0x7D, 0x86, 0x3D,
+ 0xF4, 0x3F, 0x53, 0xEE, 0x3F, 0x3F, 0xB1, 0x2C, 0x3F, 0xF5, 0x59, 0xF6, 0xA1, 0xC9, 0xAD, 0x02,
+ 0x71, 0xEC, 0xE5, 0x6A, 0xC5, 0xF4, 0x62, 0x28, 0x30, 0x3E, 0x75, 0xBC, 0x83, 0xC2, 0xD0, 0xD4,
+ 0xA6, 0x12, 0xC5, 0x54, 0xA9, 0x2E, 0x9E, 0xD1, 0xA9, 0x82, 0x01, 0x7E, 0x5B, 0x31, 0xDE, 0x08,
+ 0xF2, 0xC8, 0xF0, 0x51, 0x2A, 0xA8, 0x8F, 0x58, 0x6D, 0xEF, 0xC5, 0xC6, 0x0E, 0x49, 0xEC, 0x46,
+ 0x49, 0x0C, 0x94, 0xC4, 0x42, 0xC1, 0xA8, 0x65, 0xC0, 0xC7, 0x8A, 0x61, 0xE6, 0x43, 0x4B, 0x2C,
+ 0x34, 0xCE, 0x4D, 0x4C, 0x7E, 0xAF, 0x76, 0x70, 0x34, 0x86, 0x73, 0x7C, 0x26, 0x58, 0x2A, 0xC1,
+ 0xC9, 0xAB, 0xCB, 0x3C, 0xE1, 0xE9, 0xB2, 0xC9, 0xBE, 0x10, 0xDD, 0xED, 0x1E, 0x30, 0x62, 0x9A,
+ 0x9C, 0xC8, 0x64, 0x35, 0x32, 0xA7, 0xB0, 0x54, 0xC1, 0x5C, 0xA0, 0x4C, 0x82, 0x12, 0x07, 0x64,
+ 0xA9, 0x20, 0x4E, 0xAA, 0x5C, 0xC2, 0x32, 0x02, 0x21, 0x9B, 0x74, 0xBA, 0xDF, 0x74, 0x85, 0x85,
+ 0x53, 0x20, 0x3F, 0xAD, 0x9A, 0x13, 0xFB, 0x02, 0xCA, 0x0D, 0xD1, 0xA9, 0xFD, 0x42, 0x46, 0xAA,
+ 0x92, 0x87, 0x08, 0xF5, 0x5E, 0xE2, 0x75, 0x70, 0xA7, 0x71, 0x88, 0x41, 0x21, 0xB7, 0x57, 0xDD,
+ 0xF4, 0x1A, 0x38, 0x40, 0x2E, 0x6F, 0x07, 0xD4, 0xBF, 0xBA, 0xC9, 0x35, 0xD0, 0x24, 0x37, 0xF6,
+ 0x48, 0xB5, 0x9D, 0x8D, 0x14, 0x66, 0x63, 0x4E, 0x03, 0x66, 0xE4, 0x14, 0x90, 0xE8, 0xC3, 0xD7,
+ 0x76, 0xE5, 0x5B, 0xB3, 0x1E, 0x02, 0x5C, 0x16, 0x32, 0x49, 0xBA, 0x44, 0x1F, 0x91, 0x5E, 0xF6,
+ 0xB2, 0x68, 0x74, 0x1A, 0x23, 0x21, 0x6E, 0x66, 0xA5, 0x0D, 0x19, 0xC0, 0x72, 0x31, 0x2B, 0xC5,
+ 0x6F, 0x04, 0xEA, 0x5E, 0x56, 0xA2, 0xCF, 0x10, 0xCC, 0xB5, 0x64, 0xEA, 0xB8, 0x63, 0x21, 0x1A,
+ 0xBA, 0x95, 0xA2, 0x1D, 0x75, 0x2A, 0x0B, 0x0B, 0xAD, 0x69, 0xA6, 0xFA, 0xED, 0x09, 0x45, 0xB3,
+ 0x53, 0x9D, 0x1C, 0x7E, 0xC7, 0x8E, 0x8E, 0xA1, 0x6A, 0xBB, 0x63, 0x82, 0xC7, 0xE8, 0x88, 0x80,
+ 0x16, 0x47, 0x7C, 0x91, 0xE6, 0xC6, 0xD6, 0xB6, 0x36, 0x09, 0x54, 0x8A, 0x07, 0x1D, 0x1D, 0x0E,
+ 0x09, 0xBA, 0x34, 0x37, 0x49, 0x6F, 0x20, 0x4C, 0x72, 0x59, 0x47, 0x0D, 0xB7, 0xE4, 0xB3, 0x60,
+ 0x57, 0x35, 0x79, 0x8A, 0x12, 0x0E, 0x8F, 0x35, 0x2C, 0xE6, 0xA0, 0x44, 0x83, 0x1C, 0x54, 0xA9,
+ 0x06, 0xA5, 0x1C, 0x94, 0x69, 0x50, 0xC6, 0x41, 0x4B, 0x0D, 0x5A, 0x72, 0x50, 0xAE, 0x41, 0x39,
+ 0x07, 0xD5, 0x45, 0x9F, 0x01, 0x6B, 0xBE, 0x22, 0x26, 0x41, 0x5D, 0x84, 0x26, 0x3B, 0x3D, 0xB1,
+ 0xA3, 0x64, 0xDA, 0xC0, 0xB4, 0x07, 0x72, 0x58, 0xD6, 0xC1, 0x12, 0x06, 0x5A, 0xF6, 0x20, 0x07,
+ 0x5D, 0xDE, 0x01, 0x33, 0x0A, 0x92, 0x8E, 0xA9, 0xA1, 0x7C, 0xF4, 0x75, 0xE9, 0xB3, 0xC0, 0xA7,
+ 0x8A, 0xD6, 0x59, 0xD2, 0x98, 0x90, 0x25, 0x36, 0x19, 0xE6, 0x93, 0xD3, 0x6A, 0x20, 0x61, 0x90,
+ 0x12, 0x06, 0x43, 0xF4, 0x0E, 0xF2, 0xCC, 0x26, 0x4F, 0xFC, 0xD4, 0x09, 0x27, 0x5E, 0x12, 0xE2,
+ 0x21, 0xE1, 0x89, 0x4B, 0x7A, 0x6E, 0x33, 0xC8, 0xFC, 0xE4, 0x19, 0x23, 0x96, 0x05, 0x62, 0xD3,
+ 0x8B, 0x14, 0x37, 0x3D, 0x2F, 0xB1, 0x5F, 0xB1, 0xC4, 0xC8, 0xB2, 0x01, 0xBD, 0x58, 0x48, 0xC8,
+ 0x12, 0x83, 0x4C, 0x17, 0x89, 0x8F, 0x56, 0x83, 0x09, 0x83, 0xD4, 0x62, 0x30, 0x4C, 0xEF, 0x20,
+ 0xCF, 0x4C, 0xF2, 0x64, 0x88, 0x3A, 0xE1, 0xC4, 0x4B, 0x8B, 0x78, 0x58, 0x78, 0xE2, 0x92, 0x9E,
+ 0x9B, 0x0C, 0xB2, 0x21, 0xF2, 0x8C, 0x11, 0xEB, 0x12, 0x43, 0x7A, 0xF1, 0xDD, 0x47, 0xCF, 0x4B,
+ 0xEC, 0x4C, 0x4A, 0xCC, 0x06, 0xFA, 0xCA, 0x05, 0x31, 0x88, 0xE1, 0x39, 0x02, 0x31, 0x2D, 0x83,
+ 0x33, 0xEB, 0x31, 0x0C, 0x62, 0x1E, 0x06, 0xF7, 0x59, 0x00, 0x51, 0x2A, 0xD6, 0x90, 0xD8, 0x60,
+ 0x7F, 0x83, 0x81, 0x38, 0xAC, 0x4D, 0xE0, 0x28, 0xAC, 0xDE, 0x73, 0x0C, 0x5E, 0xB9, 0x19, 0x0E,
+ 0xAB, 0xBF, 0x0C, 0x63, 0xB4, 0x92, 0x02, 0xD2, 0x06, 0xB2, 0x3C, 0x54, 0xE5, 0x36, 0x90, 0xE7,
+ 0xD1, 0x6A, 0xB5, 0x89, 0x52, 0x8A, 0xE4, 0xC0, 0xC9, 0x08, 0x4E, 0xC2, 0x50, 0x20, 0xDB, 0xA3,
+ 0xB5, 0x60, 0x03, 0xF9, 0x1E, 0xF1, 0xF5, 0x8D, 0xCA, 0xF8, 0x88, 0x47, 0xB7, 0x90, 0x73, 0x7F,
+ 0xE7, 0xD0, 0x42, 0xBE, 0x79, 0xF3, 0x4F, 0x50, 0x52, 0x1B, 0xC5, 0x81, 0x91, 0x59, 0x18, 0x09,
+ 0x47, 0x58, 0xDA, 0x08, 0x0E, 0x1E, 0x90, 0xDF, 0xC1, 0xB6, 0xB8, 0xC5, 0xDC, 0xFA, 0xDA, 0x5B,
+ 0x31, 0x6D, 0xC4, 0x5D, 0x6E, 0x0B, 0x90, 0xF4, 0x00, 0xCC, 0x21, 0x42, 0x53, 0x84, 0x72, 0x60,
+ 0xD6, 0x03, 0x13, 0x06, 0x5B, 0x22, 0xCC, 0x41, 0x99, 0xF7, 0xD0, 0x8C, 0xC2, 0x2E, 0xA8, 0xAA,
+ 0xA3, 0x33, 0xD7, 0x10, 0x5E, 0x60, 0x40, 0x88, 0x59, 0xE1, 0xB5, 0x93, 0x53, 0x6B, 0x28, 0x61,
+ 0x91, 0x52, 0x16, 0x83, 0x1C, 0x1C, 0x0C, 0x32, 0xC2, 0x20, 0x19, 0xA0, 0x4F, 0x38, 0xF9, 0x92,
+ 0x92, 0x0F, 0x2A, 0x90, 0xB8, 0x34, 0xC8, 0x09, 0x8B, 0xCC, 0xCF, 0x80, 0x5B, 0xFF, 0x57, 0xB4,
+ 0x3E, 0x6D, 0xE6, 0xE9, 0xD5, 0x8E, 0x98, 0x10, 0x26, 0x26, 0xA1, 0xDF, 0xF6, 0xBC, 0x11, 0x41,
+ 0x16, 0xA9, 0xCD, 0x62, 0x84, 0x83, 0x83, 0x41, 0x66, 0x31, 0x48, 0x06, 0xE9, 0x13, 0x4E, 0xBE,
+ 0xB4, 0xC9, 0x47, 0x14, 0x48, 0x5C, 0x1A, 0xE4, 0x16, 0x8B, 0x6C, 0x88, 0x01, 0xB7, 0xFE, 0x99,
+ 0x5A, 0xDF, 0x86, 0x7A, 0x4D, 0x8C, 0x28, 0xD4, 0x84, 0x1C, 0x83, 0xDA, 0x88, 0x21, 0x70, 0x2B,
+ 0x30, 0x14, 0x9A, 0x4B, 0x8A, 0x50, 0xF1, 0x3A, 0x6C, 0xC3, 0x47, 0xAB, 0xAA, 0x40, 0xE2, 0x95,
+ 0x91, 0xE3, 0xF0, 0xFA, 0xC6, 0x50, 0x46, 0xEB, 0x94, 0x40, 0xE2, 0xB5, 0x86, 0xA2, 0x88, 0x7E,
+ 0x72, 0xB0, 0x06, 0x88, 0x8E, 0x72, 0xDC, 0xC9, 0x45, 0x4F, 0x39, 0xEA, 0xC8, 0xA2, 0xAB, 0x1C,
+ 0xF3, 0x56, 0xD1, 0x57, 0x8E, 0x3B, 0xA4, 0xE8, 0x2C, 0xC7, 0xBC, 0x4E, 0xF4, 0x83, 0x03, 0xED,
+ 0xAA, 0xE8, 0x08, 0xC7, 0x1A, 0x4E, 0xD1, 0x13, 0x8E, 0x34, 0x8D, 0xA2, 0x2B, 0x1C, 0x6E, 0xFD,
+ 0x44, 0x5F, 0x38, 0xD6, 0xBC, 0x89, 0xCE, 0x70, 0xB0, 0x05, 0x93, 0x61, 0x48, 0x9A, 0xE7, 0xE3,
+ 0xB3, 0x5C, 0x23, 0xB9, 0x7A, 0xE3, 0xF9, 0x21, 0xCE, 0xDC, 0x22, 0x17, 0xB9, 0xC5, 0x50, 0x26,
+ 0xEA, 0xD9, 0x05, 0xFD, 0xAE, 0xD2, 0xEC, 0x83, 0x19, 0x68, 0xF4, 0xF3, 0x2F, 0x94, 0x20, 0x75,
+ 0x10, 0xA4, 0x9A, 0x60, 0xF1, 0xC8, 0xF1, 0x33, 0x07, 0xBE, 0xC6, 0x5E, 0x71, 0xEC, 0x25, 0xC7,
+ 0x4E, 0xD7, 0x1D, 0xF7, 0x8C, 0xE3, 0xE7, 0x06, 0x7E, 0xC2, 0x8B, 0x0A, 0x30, 0x56, 0x26, 0x06,
+ 0x03, 0x9F, 0xA3, 0xC3, 0xB9, 0xA8, 0x0F, 0xDB, 0xAB, 0x11, 0xE4, 0x50, 0xA5, 0x50, 0x3C, 0x15,
+ 0x30, 0xD0, 0xC4, 0x53, 0x29, 0x16, 0xDE, 0x2F, 0x7A, 0x23, 0xCF, 0x0C, 0x90, 0x98, 0xC5, 0xB1,
+ 0x0B, 0xA7, 0x3A, 0x59, 0x58, 0x3A, 0xCD, 0xC6, 0x44, 0xA1, 0x88, 0xB8, 0x64, 0xEC, 0x36, 0xCF,
+ 0x75, 0x69, 0xA1, 0xAC, 0x9D, 0x28, 0x44, 0xA0, 0x4A, 0x32, 0xF1, 0xEA, 0x27, 0xF0, 0x06, 0xEB,
+ 0xFA, 0x06, 0x81, 0xB6, 0x8D, 0x0D, 0x06, 0x7B, 0xDB, 0x18, 0x72, 0x59, 0x8A, 0xE0, 0x50, 0x94,
+ 0x7A, 0x6F, 0x21, 0xA4, 0x26, 0xF8, 0x5C, 0x5D, 0xBA, 0x15, 0x4E, 0x12, 0x42, 0x92, 0x61, 0x55,
+ 0xC7, 0xD2, 0xC4, 0x11, 0xCD, 0x18, 0x47, 0xD2, 0x8B, 0x6A, 0x88, 0xE7, 0x58, 0x54, 0x23, 0x71,
+ 0x01, 0xD4, 0x39, 0x1C, 0x57, 0xB0, 0x80, 0x21, 0xA2, 0x37, 0x47, 0xB0, 0x65, 0x7D, 0x70, 0x50,
+ 0x6A, 0xC8, 0x30, 0xB9, 0x34, 0xC8, 0xF9, 0x09, 0x56, 0x38, 0xF7, 0x4F, 0x8C, 0x83, 0x09, 0x64,
+ 0x4C, 0xE0, 0xF4, 0x59, 0x75, 0xDA, 0x0A, 0xAB, 0x93, 0x60, 0x99, 0x3D, 0x80, 0x91, 0xBC, 0x79,
+ 0x79, 0x71, 0x93, 0xF4, 0x00, 0x6E, 0xC7, 0xE2, 0x45, 0x56, 0x8A, 0xDF, 0x18, 0x0D, 0x42, 0x28,
+ 0x91, 0xDA, 0x39, 0xE2, 0x21, 0x37, 0x19, 0xDE, 0xF1, 0x99, 0x61, 0xAA, 0x24, 0x8A, 0xA9, 0xE2,
+ 0x74, 0x5E, 0x5D, 0x01, 0x39, 0x11, 0xD5, 0x0C, 0x05, 0x8A, 0x70, 0xCA, 0xCA, 0x79, 0x53, 0x84,
+ 0x61, 0xF1, 0xDB, 0x1F, 0x5F, 0x7D, 0xF1, 0xF8, 0xB8, 0x5C, 0x53, 0x3C, 0xF7, 0x6D, 0x0E, 0x86,
+ 0xC5, 0x6F, 0x68, 0x50, 0x14, 0xF7, 0xDD, 0x0B, 0x8A, 0xE5, 0xBC, 0x4F, 0x41, 0x91, 0x5C, 0xB7,
+ 0x24, 0x18, 0x0E, 0xBF, 0xFD, 0xC0, 0x50, 0x64, 0xB1, 0xA0, 0x3E, 0x3B, 0x82, 0xA0, 0x1E, 0xD9,
+ 0x1B, 0xE1, 0xD1, 0xBC, 0x39, 0x57, 0xA5, 0x7D, 0x81, 0x86, 0xB3, 0xA9, 0xE1, 0xD2, 0x9E, 0xD8,
+ 0xC8, 0xE6, 0x01, 0xC5, 0xF2, 0xB9, 0x5B, 0x2B, 0xC4, 0xA6, 0x0F, 0x1C, 0x3A, 0x69, 0x4E, 0x55,
+ 0x5B, 0x75, 0x06, 0x39, 0x1C, 0x9F, 0xAA, 0xD3, 0xC1, 0x6A, 0x30, 0x36, 0xFB, 0xDE, 0x29, 0xF8,
+ 0xAD, 0x42, 0xEE, 0x1F, 0x80, 0x8E, 0xDE, 0xC1, 0x09, 0x78, 0x2E, 0x81, 0x40, 0xBB, 0x09, 0x47,
+ 0xE7, 0x1E, 0x03, 0xE8, 0xCA, 0x5F, 0x38, 0x2E, 0x77, 0x1D, 0xC0, 0xD5, 0x8E, 0xC3, 0xD1, 0xB9,
+ 0x0F, 0x01, 0xBA, 0xF6, 0x20, 0x8E, 0xCD, 0x9D, 0x09, 0xB0, 0xB5, 0x2B, 0x71, 0x64, 0xF4, 0x2A,
+ 0x44, 0x56, 0x3E, 0xC5, 0x71, 0xD1, 0x35, 0x10, 0x57, 0xFA, 0x8E, 0x53, 0xE5, 0x9D, 0x8D, 0xA8,
+ 0xCA, 0x3B, 0x08, 0xD3, 0xB8, 0x74, 0xCD, 0xF1, 0x0D, 0x20, 0x21, 0xDB, 0x8B, 0x8B, 0xBD, 0x90,
+ 0xCC, 0x82, 0x79, 0xE0, 0x38, 0xAA, 0x43, 0xB1, 0x3C, 0x0B, 0x9F, 0x7D, 0x15, 0x3D, 0x4C, 0x7D,
+ 0x75, 0xBD, 0x1A, 0x0B, 0xE9, 0x48, 0xA2, 0x9E, 0x8F, 0xF5, 0x83, 0x09, 0xC4, 0x2B, 0x0B, 0x96,
+ 0x3C, 0xDC, 0xC2, 0x00, 0xC0, 0xA4, 0xF9, 0xE1, 0x14, 0xE4, 0x91, 0xA7, 0x7A, 0x45, 0xDF, 0x93,
+ 0xB8, 0x4C, 0x9E, 0x1F, 0x4E, 0x41, 0x64, 0x5E, 0x22, 0x92, 0xE8, 0x6B, 0xBC, 0x1C, 0x4D, 0xE6,
+ 0x9E, 0xA0, 0x31, 0xED, 0x65, 0x19, 0x42, 0xB3, 0xE3, 0x8A, 0xA5, 0xED, 0xC0, 0x8B, 0x62, 0x82,
+ 0x19, 0xBB, 0x90, 0x12, 0xC6, 0xCE, 0xC3, 0x2D, 0x0D, 0x96, 0x9B, 0x51, 0xCC, 0xCC, 0x83, 0xB8,
+ 0x3D, 0x9C, 0xB6, 0x75, 0x75, 0x65, 0x17, 0xD1, 0x5D, 0xB8, 0x2F, 0x87, 0xBA, 0x66, 0x98, 0x6E,
+ 0xBE, 0xE4, 0x20, 0xA4, 0x9C, 0x67, 0xF8, 0xB4, 0x66, 0xE1, 0x07, 0xC7, 0xB3, 0x27, 0xC6, 0x69,
+ 0xA1, 0x54, 0xF6, 0xC5, 0xD6, 0x50, 0x09, 0x0A, 0xFB, 0x3A, 0x81, 0x96, 0x3F, 0xA5, 0x17, 0x20,
+ 0x46, 0x0D, 0x4B, 0xC3, 0x29, 0x3F, 0x09, 0xB4, 0xE9, 0xEF, 0x8B, 0xEE, 0xEA, 0x01, 0x5E, 0x6F,
+ 0x77, 0x5D, 0x3E, 0x38, 0x1C, 0x39, 0x1E, 0xBF, 0xD0, 0xE0, 0xBF, 0x63, 0xA6, 0xEF, 0x67, 0x34,
+ 0x01, 0x57, 0x34, 0xDA, 0x66, 0xFC, 0x96, 0x46, 0xDB, 0x0C, 0x5D, 0xD4, 0x28, 0x01, 0x1E, 0x72,
+ 0x03, 0xA2, 0x6D, 0xA6, 0x5E, 0x82, 0x68, 0x9B, 0xF1, 0x7B, 0x10, 0x6D, 0xC3, 0xAF, 0x42, 0x50,
+ 0x8C, 0xF1, 0xDB, 0x10, 0x6D, 0x13, 0x7E, 0x21, 0xA2, 0x6D, 0xA6, 0xDC, 0x89, 0x68, 0x1B, 0x7E,
+ 0x2D, 0x82, 0x62, 0x4C, 0xBB, 0x19, 0xD1, 0x36, 0xC3, 0x97, 0x23, 0x04, 0x05, 0x20, 0x85, 0x1C,
+ 0xA6, 0x6B, 0x9B, 0xD0, 0xF3, 0x74, 0x6D, 0x33, 0xED, 0x48, 0x5D, 0xDB, 0xDC, 0x7A, 0xAA, 0xAE,
+ 0x6D, 0xEE, 0x3B, 0x58, 0xD7, 0x36, 0x21, 0x67, 0xEB, 0xDA, 0x26, 0xE8, 0x78, 0x5D, 0x13, 0x7C,
+ 0xC2, 0xAE, 0x09, 0x3D, 0x64, 0xD7, 0x36, 0x41, 0xE7, 0xEC, 0xDA, 0x26, 0xF8, 0xA8, 0x5D, 0xDB,
+ 0xDC, 0x72, 0xDA, 0xAE, 0x6D, 0xF0, 0xC0, 0x1D, 0x85, 0xD8, 0x67, 0xEE, 0x38, 0x38, 0x55, 0x60,
+ 0x0F, 0xD4, 0x7F, 0xF2, 0x0E, 0x80, 0x4B, 0x05, 0xF4, 0xD0, 0x86, 0x9E, 0xBF, 0x6B, 0xEE, 0x3A,
+ 0x82, 0xD7, 0x36, 0x37, 0x9F, 0xC2, 0x6B, 0x9B, 0xDB, 0x0F, 0xE2, 0xB5, 0xCD, 0xFD, 0x67, 0xF1,
+ 0xDA, 0xE6, 0xDE, 0xE3, 0x78, 0x6D, 0x73, 0xD7, 0x89, 0xBC, 0xE6, 0xA6, 0x43, 0x79, 0x6D, 0x33,
+ 0xF9, 0x5C, 0xDE, 0x9F, 0xAD, 0xDD, 0x4D, 0xCA, 0x16, 0x31, 0x10, 0x04, 0xE0, 0xBD, 0xA7, 0x10,
+ 0xDC, 0x09, 0xFE, 0x0B, 0x82, 0xE0, 0x0D, 0x74, 0x23, 0x78, 0x00, 0xB5, 0xA5, 0x11, 0xAA, 0x51,
+ 0x8C, 0x42, 0xA3, 0x78, 0x77, 0x13, 0x15, 0x47, 0x3B, 0xAF, 0xD3, 0x95, 0xF4, 0xBB, 0x13, 0xBA,
+ 0xE6, 0xC3, 0xC9, 0x98, 0x08, 0xF3, 0x94, 0x63, 0xB3, 0xF5, 0x6A, 0x5E, 0xB3, 0xCD, 0x76, 0x9E,
+ 0x6D, 0x16, 0xF4, 0x6C, 0xBB, 0xA3, 0xD7, 0x6C, 0xAB, 0xA6, 0xD7, 0x6C, 0xAF, 0xA9, 0xD7, 0xAC,
+ 0x56, 0xD6, 0x6B, 0x56, 0xEA, 0xEB, 0xD9, 0x56, 0x65, 0xAF, 0xD9, 0x52, 0x6B, 0xCF, 0x36, 0x8A,
+ 0x7B, 0xCD, 0x16, 0xBB, 0x7B, 0xCD, 0x56, 0xEB, 0x7B, 0xCD, 0xB6, 0x1A, 0x7C, 0xB6, 0x54, 0xE2,
+ 0x6B, 0x96, 0xF6, 0xF8, 0x9A, 0x85, 0x2A, 0x5F, 0x9C, 0x3E, 0xF8, 0x3D, 0xBD, 0x78, 0x6D, 0x28,
+ 0xF4, 0x85, 0x69, 0xE8, 0xF4, 0x85, 0x69, 0xA8, 0xF5, 0x85, 0x69, 0x68, 0xF6, 0xC5, 0xE9, 0xFF,
+ 0xCB, 0x7D, 0xED, 0xFF, 0xFD, 0xBE, 0x96, 0x54, 0xFC, 0xDA, 0x79, 0xCB, 0xAF, 0x9D, 0x16, 0xFD,
+ 0x5A, 0xD2, 0xF5, 0x6B, 0xA7, 0x75, 0xBF, 0x96, 0x36, 0xFE, 0x9A, 0xED, 0x96, 0xFE, 0xFA, 0x95,
+ 0xE5, 0xDE, 0x5F, 0xB3, 0x6A, 0xF5, 0xAF, 0x59, 0xAD, 0xFD, 0xD7, 0xAF, 0x2F, 0x17, 0x00, 0x9B,
+ 0xD5, 0x3A, 0x80, 0xCD, 0xD2, 0x86, 0x51, 0x56, 0x03, 0x6C, 0xB6, 0xDB, 0x04, 0x6C, 0x56, 0x2F,
+ 0x03, 0x36, 0xAB, 0xF6, 0x01, 0x9B, 0xD5, 0x2A, 0x81, 0xCD, 0xEA, 0xAD, 0xC0, 0x66, 0xB5, 0x62,
+ 0x60, 0xB3, 0x72, 0x37, 0x70, 0x7A, 0x8C, 0xFF, 0xCE, 0xD3, 0x87, 0xF5, 0x3B, 0x94, 0x97, 0x04,
+ 0x9B, 0xA5, 0x3D, 0xC1, 0x66, 0x44, 0x55, 0xB0, 0x59, 0xDA, 0x16, 0x4C, 0x97, 0xA5, 0xA7, 0xDE,
+ 0xFD, 0xEF, 0x1C, 0x3A, 0x12, 0xC4, 0x79, 0x33, 0x62, 0xF9, 0x91, 0x32, 0x52, 0xE9, 0xB1, 0x31,
+ 0x42, 0xC4, 0xD9, 0x30, 0x62, 0xE9, 0xF6, 0x1F, 0x21, 0x66, 0x8F, 0xF7, 0xDC, 0x9B, 0x9E, 0x4B,
+ 0x8B, 0x84, 0xCD, 0xB8, 0x2E, 0x61, 0x33, 0xAA, 0x4E, 0xD8, 0x8C, 0x69, 0x14, 0x36, 0xE3, 0x4A,
+ 0x85, 0xCD, 0x98, 0x5E, 0x61, 0x33, 0xB2, 0x5A, 0x78, 0x2C, 0xC7, 0xF1, 0xB7, 0x50, 0x08, 0x30,
+ 0x05, 0xC3, 0x66, 0x44, 0xC7, 0xB0, 0x59, 0x5E, 0x33, 0x6C, 0xC6, 0x34, 0x0D, 0x9B, 0xE5, 0x65,
+ 0xC3, 0x66, 0x4C, 0xDF, 0xB0, 0xD9, 0x5C, 0x39, 0x3C, 0x66, 0xE7, 0xAD, 0xC3, 0x9F, 0x77, 0x7D,
+ 0x04, 0x2E, 0xCD, 0xCF, 0xBA, 0x87, 0x3F, 0xEF, 0xF5, 0x18, 0x5F, 0xBC, 0xFE, 0xB4, 0x81, 0xD8,
+ 0xE7, 0xFB, 0x25, 0xC4, 0x66, 0x69, 0xB9, 0x89, 0xE8, 0x21, 0xF6, 0x9F, 0x52, 0xAE, 0x22, 0x36,
+ 0xAB, 0xB6, 0x11, 0x9B, 0xA5, 0xE5, 0x29, 0xA2, 0x90, 0xD8, 0xAC, 0xDA, 0x49, 0x6C, 0xB6, 0x5F,
+ 0x4B, 0x6C, 0x76, 0x8D, 0x66, 0x62, 0xB3, 0x7A, 0x39, 0xB1, 0x59, 0xB5, 0x9F, 0xD8, 0xEC, 0x1A,
+ 0x15, 0xC5, 0x66, 0xD5, 0x96, 0xE2, 0xFC, 0x3C, 0xFE, 0x0D, 0xE4, 0x8B, 0x3E, 0x52, 0xE9, 0xA2,
+ 0xFE, 0x0E, 0xE5, 0x8D, 0xC5, 0x74, 0x5D, 0x7E, 0xA7, 0xF2, 0xDE, 0x62, 0xB3, 0xB4, 0xBA, 0x48,
+ 0x6C, 0xF0, 0x91, 0x23, 0xB6, 0xF0, 0x88, 0xE5, 0xBB, 0x74, 0xA4, 0x98, 0x9D, 0x38, 0x72, 0xF9,
+ 0x5E, 0xEB, 0xA9, 0xF1, 0xB7, 0x58, 0x5E, 0x66, 0x6C, 0x46, 0xF6, 0x19, 0x9B, 0x71, 0x95, 0xC6,
+ 0x66, 0x54, 0xAB, 0xB1, 0x19, 0x59, 0x6C, 0x6C, 0x46, 0x75, 0x1B, 0x8F, 0x7B, 0x3D, 0x4E, 0xEC,
+ 0x98, 0xA0, 0x1A, 0x8E, 0xCD, 0x98, 0x92, 0x63, 0x33, 0xA2, 0xE7, 0xD8, 0x8C, 0xAA, 0x3A, 0x1E,
+ 0x77, 0x18, 0xCE, 0xC6, 0xD0, 0xB5, 0x31, 0xB2, 0x21, 0xD6, 0x8C, 0x2B, 0x89, 0x35, 0x63, 0x7A,
+ 0x62, 0xFF, 0xF9, 0xB2, 0xE2, 0x21, 0x6B, 0x26, 0x84, 0xAC, 0x99, 0xE4, 0xB2, 0x66, 0x72, 0x2E,
+ 0x6B, 0x46, 0x7D, 0x5B, 0xCC, 0x96, 0x3F, 0x2F, 0x66, 0xC4, 0x17, 0xC6, 0x2C, 0xFD, 0xC8, 0x98,
+ 0x11, 0xDF, 0x19, 0xB3, 0x85, 0x4F, 0x8D, 0xD9, 0xD2, 0xD7, 0xC6, 0x2C, 0xFD, 0xE0, 0x98, 0x2D,
+ 0x7E, 0x73, 0xCC, 0x84, 0x90, 0x35, 0x13, 0x46, 0xD6, 0x4C, 0x58, 0x59, 0x33, 0x59, 0x93, 0x35,
+ 0x93, 0x5D, 0x59, 0x33, 0xA9, 0xC9, 0x9A, 0x09, 0x23, 0x6B, 0x26, 0x8C, 0xAC, 0x99, 0xB0, 0xB2,
+ 0x66, 0xC2, 0xCA, 0x9A, 0x09, 0x25, 0x6B, 0x26, 0xB4, 0xAC, 0x99, 0xEC, 0xC8, 0x9A, 0xC9, 0x21,
+ 0x6B, 0x71, 0x12, 0x64, 0x6D, 0x1A, 0x3F, 0xFC, 0x35, 0xFE, 0xCF, 0xF4, 0x90, 0xB5, 0x79, 0x18,
+ 0x64, 0x2D, 0x8E, 0x49, 0x59, 0x33, 0x29, 0xC9, 0x9A, 0xC9, 0xB6, 0xAC, 0x99, 0xEC, 0xCB, 0x9A,
+ 0x49, 0x5D, 0xD6, 0x4C, 0xAA, 0xB2, 0x66, 0x52, 0x91, 0x35, 0x93, 0x2D, 0x59, 0x33, 0x59, 0x96,
+ 0x35, 0x93, 0x75, 0x59, 0x33, 0xD9, 0x93, 0x35, 0x93, 0x3D, 0x59, 0x33, 0xD9, 0x96, 0x35, 0x93,
+ 0x2D, 0x59, 0x33, 0xD9, 0x93, 0x35, 0x93, 0x9A, 0xAC, 0x99, 0x54, 0x64, 0xCD, 0x64, 0x4B, 0xD6,
+ 0x4C, 0x56, 0x64, 0xCD, 0x64, 0x43, 0xD6, 0x4C, 0x16, 0x65, 0xCD, 0x64, 0x55, 0xD6, 0x4C, 0x76,
+ 0x64, 0xCD, 0x64, 0x49, 0xD6, 0x4C, 0x52, 0x59, 0x33, 0x09, 0xB2, 0x16, 0xA7, 0x0F, 0x7E, 0x4F,
+ 0x2F, 0x5E, 0x1B, 0x64, 0x2D, 0x4C, 0x83, 0xAC, 0x85, 0x69, 0x90, 0xB5, 0x30, 0x0D, 0xB2, 0x16,
+ 0xA7, 0xFF, 0x97, 0xB5, 0x3E, 0x3C, 0x64, 0x2D, 0x8E, 0xA2, 0xAC, 0x4D, 0xF3, 0x87, 0x7F, 0xE6,
+ 0x97, 0xC6, 0x41, 0xD6, 0xC2, 0x34, 0xC8, 0xDA, 0x34, 0x0F, 0xB2, 0x16, 0xA6, 0x89, 0xAC, 0xF5,
+ 0xC4, 0xAE, 0xAC, 0xF5, 0x2B, 0xCB, 0xB2, 0x66, 0x52, 0x95, 0x35, 0x93, 0x9A, 0xAC, 0xF5, 0xEB,
+ 0xCB, 0xB2, 0x66, 0x52, 0x93, 0x35, 0x93, 0xF4, 0xAD, 0x7B, 0x26, 0x6B, 0x26, 0xBB, 0xB2, 0x66,
+ 0x52, 0x97, 0x35, 0x93, 0xAA, 0xAC, 0x99, 0xD4, 0x64, 0xCD, 0xA4, 0x2E, 0x6B, 0x26, 0x35, 0x59,
+ 0x33, 0x29, 0xCB, 0xDA, 0xF4, 0x18, 0xFF, 0x9D, 0xA7, 0x0F, 0xEB, 0x77, 0x28, 0x97, 0x35, 0x93,
+ 0x54, 0xD6, 0x4C, 0x08, 0x59, 0x33, 0x49, 0x65, 0x2D, 0x5D, 0x96, 0x9E, 0x7A, 0xF7, 0xBF, 0x73,
+ 0xE8, 0x48, 0x10, 0xE7, 0xCD, 0x88, 0xE5, 0x47, 0xCA, 0x48, 0xA5, 0xC7, 0xC6, 0x08, 0x11, 0x67,
+ 0xC3, 0x88, 0xA5, 0xDB, 0x7F, 0x84, 0x98, 0x3D, 0xDE, 0x73, 0x6F, 0x7A, 0x2E, 0x95, 0x35, 0x13,
+ 0x4E, 0xD6, 0x4C, 0x28, 0x59, 0x33, 0x61, 0x64, 0xCD, 0x84, 0x93, 0x35, 0x13, 0x46, 0xD6, 0x4C,
+ 0x48, 0x59, 0x33, 0x89, 0xB2, 0x36, 0x05, 0x18, 0x59, 0x33, 0x21, 0x64, 0xCD, 0x24, 0x97, 0x35,
+ 0x13, 0x46, 0xD6, 0x4C, 0x72, 0x59, 0x33, 0x61, 0x64, 0xCD, 0x64, 0x96, 0xB5, 0x63, 0x76, 0x2E,
+ 0x6B, 0x3F, 0xEF, 0xFA, 0x08, 0x5C, 0x9A, 0x9F, 0xC9, 0xDA, 0xCF, 0x7B, 0x3D, 0xC6, 0x17, 0xAF,
+ 0x3F, 0x95, 0xB5, 0x3E, 0xDF, 0x97, 0x35, 0x93, 0xF4, 0xC5, 0x3B, 0x21, 0x6B, 0xFD, 0xA7, 0x94,
+ 0x65, 0xCD, 0xA4, 0x2A, 0x6B, 0x26, 0xE9, 0xFB, 0x7C, 0x42, 0xD6, 0x4C, 0xAA, 0xB2, 0x66, 0xB2,
+ 0x2F, 0x6B, 0x26, 0xD7, 0x90, 0x35, 0x93, 0xBA, 0xAC, 0x99, 0x54, 0x65, 0xCD, 0xE4, 0x1A, 0xB2,
+ 0x66, 0x52, 0x95, 0xB5, 0xF9, 0x79, 0xFC, 0x1B, 0xC8, 0x17, 0x7D, 0xA4, 0xD2, 0x45, 0xFD, 0x1D,
+ 0xCA, 0x65, 0x2D, 0x5D, 0x97, 0xDF, 0xA9, 0x5C, 0xD6, 0x4C, 0x52, 0x59, 0x23, 0x36, 0xF8, 0xC8,
+ 0x11, 0x5B, 0x78, 0xC4, 0xF2, 0x5D, 0x3A, 0x52, 0xCC, 0x4E, 0x1C, 0xB9, 0x7C, 0xAF, 0xF5, 0xD4,
+ 0xF8, 0x5B, 0x2C, 0x97, 0x35, 0x13, 0x52, 0xD6, 0x4C, 0x38, 0x59, 0x33, 0xA1, 0x64, 0xCD, 0x84,
+ 0x94, 0x35, 0x13, 0x4A, 0xD6, 0x4C, 0x26, 0x59, 0x9B, 0x12, 0x94, 0xAC, 0x99, 0x30, 0xB2, 0x66,
+ 0x42, 0xC8, 0x9A, 0x09, 0x25, 0x6B, 0x26, 0x94, 0xAC, 0x99, 0x90, 0xB2, 0x66, 0xC2, 0xC9, 0x9A,
+ 0xC9, 0xA6, 0xAC, 0x8D, 0xFF, 0x4F, 0xEC, 0x90, 0x35, 0x28, 0x21, 0x6B, 0xD0, 0x5C, 0xD6, 0x7A,
+ 0xE6, 0x4C, 0xD6, 0xFA, 0x9C, 0x91, 0x35, 0xE8, 0xAA, 0xAC, 0x41, 0x73, 0x59, 0xEB, 0x99, 0x49,
+ 0xD6, 0x62, 0x22, 0x97, 0x35, 0x28, 0x2F, 0x6B, 0xD0, 0x15, 0x59, 0xEB, 0xE9, 0x49, 0xD6, 0x62,
+ 0x62, 0x4D, 0xD6, 0xA0, 0x84, 0xAC, 0x41, 0x19, 0x59, 0x83, 0xB2, 0xB2, 0x06, 0x5D, 0x93, 0x35,
+ 0xE8, 0xAE, 0xAC, 0x41, 0x6B, 0xB2, 0x06, 0x65, 0x64, 0x0D, 0xCA, 0xC8, 0x1A, 0x94, 0x95, 0x35,
+ 0x28, 0x2B, 0x6B, 0x50, 0x4A, 0xD6, 0xA0, 0xB4, 0xAC, 0x41, 0x77, 0x64, 0xAD, 0x5F, 0x75, 0xC8,
+ 0x5A, 0x98, 0x04, 0x59, 0x9B, 0xC6, 0x0F, 0x7F, 0x8D, 0xFF, 0x33, 0x3D, 0x64, 0x6D, 0x1E, 0x06,
+ 0x59, 0x8B, 0x63, 0x52, 0xD6, 0xA0, 0x25, 0x59, 0x83, 0x6E, 0xCB, 0x1A, 0x74, 0x5F, 0xD6, 0xA0,
+ 0x75, 0x59, 0x83, 0x56, 0x65, 0x0D, 0x5A, 0x91, 0x35, 0xE8, 0x96, 0xAC, 0x41, 0x97, 0x65, 0x0D,
+ 0xBA, 0x2E, 0x6B, 0xD0, 0x3D, 0x59, 0x83, 0xEE, 0xC9, 0x1A, 0x74, 0x5B, 0xD6, 0xA0, 0x5B, 0xB2,
+ 0x06, 0xDD, 0x93, 0x35, 0x68, 0x4D, 0xD6, 0xA0, 0x15, 0x59, 0x83, 0x6E, 0xC9, 0x1A, 0x74, 0x45,
+ 0xD6, 0xA0, 0x1B, 0xB2, 0x06, 0x5D, 0x94, 0x35, 0xE8, 0xAA, 0xAC, 0x41, 0x77, 0x64, 0x0D, 0xBA,
+ 0x24, 0x6B, 0xD0, 0x54, 0xD6, 0x7A, 0x24, 0xC8, 0x5A, 0x98, 0x3E, 0xF8, 0x3D, 0xBD, 0x78, 0x6D,
+ 0x90, 0xB5, 0x30, 0x0D, 0xB2, 0x16, 0xA6, 0x41, 0xD6, 0xC2, 0xF4, 0xF4, 0x6B, 0xF4, 0xFA, 0x7F,
+ 0x59, 0xEB, 0xC3, 0x49, 0xD6, 0x8E, 0x51, 0x94, 0xB5, 0x69, 0xFE, 0xF0, 0xCF, 0xFC, 0xD2, 0x38,
+ 0xC8, 0x5A, 0x98, 0x06, 0x59, 0x9B, 0xE6, 0x41, 0xD6, 0xC2, 0x34, 0x91, 0xB5, 0x9E, 0xD8, 0x95,
+ 0xB5, 0x7E, 0x65, 0x59, 0xD6, 0xA0, 0x55, 0x59, 0x83, 0xD6, 0x64, 0xAD, 0x5F, 0x5F, 0x96, 0x35,
+ 0x68, 0x4D, 0xD6, 0xA0, 0xE9, 0x5B, 0xF7, 0x44, 0xD6, 0xFA, 0x8F, 0xD8, 0x95, 0x35, 0x68, 0x5D,
+ 0xD6, 0xA0, 0x55, 0x59, 0x83, 0xD6, 0x64, 0x0D, 0x5A, 0x97, 0x35, 0x68, 0x4D, 0xD6, 0xA0, 0x65,
+ 0x59, 0x9B, 0x1E, 0xE3, 0xBF, 0xF3, 0xF4, 0x61, 0xFD, 0x0E, 0xE5, 0xB2, 0x06, 0x4D, 0x65, 0x0D,
+ 0x4A, 0xC8, 0x1A, 0x34, 0x95, 0xB5, 0x74, 0x59, 0x7A, 0xEA, 0xDD, 0xFF, 0xCE, 0xA1, 0x23, 0x41,
+ 0x9C, 0x37, 0x23, 0x96, 0x1F, 0x29, 0x23, 0x95, 0x1E, 0x1B, 0x23, 0x44, 0x9C, 0x0D, 0x23, 0x96,
+ 0x6E, 0xFF, 0x11, 0x62, 0xF6, 0x78, 0xCF, 0xBD, 0xE9, 0xB9, 0x54, 0xD6, 0xA0, 0x9C, 0xAC, 0x41,
+ 0x29, 0x59, 0x83, 0x32, 0xB2, 0x06, 0xE5, 0x64, 0x0D, 0xCA, 0xC8, 0x1A, 0x94, 0x94, 0xB5, 0x1E,
+ 0x0C, 0xB2, 0x36, 0x05, 0x18, 0x59, 0x83, 0x12, 0xB2, 0x06, 0xCD, 0x65, 0x0D, 0xCA, 0xC8, 0x1A,
+ 0x34, 0x97, 0x35, 0x28, 0x23, 0x6B, 0x3D, 0x35, 0xC9, 0xDA, 0x31, 0x3B, 0x97, 0xB5, 0x9F, 0x77,
+ 0x7D, 0x04, 0x2E, 0xCD, 0xCF, 0x64, 0xED, 0xE7, 0xBD, 0x1E, 0xE3, 0x8B, 0xD7, 0x9F, 0xCA, 0x5A,
+ 0x9F, 0xEF, 0xCB, 0x1A, 0x34, 0x7D, 0xF1, 0x4E, 0xC8, 0x5A, 0xFF, 0x29, 0x65, 0x59, 0x83, 0x56,
+ 0x65, 0x0D, 0x9A, 0xBE, 0xCF, 0x27, 0x64, 0x0D, 0x5A, 0x95, 0x35, 0xE8, 0xBE, 0xAC, 0x41, 0xAF,
+ 0x21, 0x6B, 0xD0, 0xBA, 0xAC, 0x41, 0xAB, 0xB2, 0x06, 0xBD, 0x86, 0xAC, 0x41, 0xAB, 0xB2, 0x36,
+ 0x3F, 0x8F, 0x7F, 0x03, 0xF9, 0xA2, 0x8F, 0x54, 0xBA, 0xA8, 0xBF, 0x43, 0xB9, 0xAC, 0xA5, 0xEB,
+ 0xF2, 0x3B, 0x95, 0xCB, 0x1A, 0x34, 0x95, 0x35, 0x62, 0x83, 0x8F, 0x1C, 0xB1, 0x85, 0x47, 0x2C,
+ 0xDF, 0xA5, 0x23, 0xC5, 0xEC, 0xC4, 0x91, 0xCB, 0xF7, 0x5A, 0x4F, 0x8D, 0xBF, 0xC5, 0x72, 0x59,
+ 0x83, 0x92, 0xB2, 0x06, 0xE5, 0x64, 0x0D, 0x4A, 0xC9, 0x1A, 0x94, 0x94, 0x35, 0x28, 0x25, 0x6B,
+ 0x3D, 0x16, 0x65, 0x6D, 0x4A, 0x50, 0xB2, 0x06, 0x65, 0x64, 0x0D, 0x4A, 0xC8, 0x1A, 0x94, 0x92,
+ 0x35, 0x28, 0x25, 0x6B, 0x50, 0x52, 0xD6, 0xA0, 0x9C, 0xAC, 0x41, 0x37, 0x65, 0xED, 0xC1, 0xC3,
+ 0xFB, 0xF7, 0xFF, 0xA2, 0x35, 0x07, 0x41, 0x6B, 0x8E, 0x9C, 0xD6, 0x7A, 0xE6, 0x8C, 0xD6, 0xFA,
+ 0x9C, 0xA1, 0x35, 0xC7, 0x2A, 0xAD, 0x39, 0x72, 0x5A, 0xEB, 0x99, 0x89, 0xD6, 0x62, 0x22, 0xA7,
+ 0x35, 0x07, 0x4F, 0x6B, 0x8E, 0x15, 0x5A, 0xEB, 0xE9, 0x89, 0xD6, 0x62, 0x62, 0x8D, 0xD6, 0x1C,
+ 0x04, 0xAD, 0x39, 0x18, 0x5A, 0x73, 0xB0, 0xB4, 0xE6, 0x58, 0xA3, 0x35, 0xC7, 0x2E, 0xAD, 0x39,
+ 0x6A, 0xB4, 0xE6, 0x60, 0x68, 0xCD, 0xC1, 0xD0, 0x9A, 0x83, 0xA5, 0x35, 0x07, 0x4B, 0x6B, 0x0E,
+ 0x8A, 0xD6, 0x1C, 0x34, 0xAD, 0x39, 0x76, 0x68, 0xAD, 0x5F, 0x75, 0xD0, 0x5A, 0x98, 0x04, 0x5A,
+ 0x9B, 0xC6, 0x0F, 0x7F, 0x8D, 0xFF, 0x33, 0x3D, 0x68, 0x6D, 0x1E, 0x06, 0x5A, 0x8B, 0x63, 0x92,
+ 0xD6, 0x1C, 0x25, 0x5A, 0x73, 0x6C, 0xD3, 0x9A, 0x63, 0x9F, 0xD6, 0x1C, 0x75, 0x5A, 0x73, 0x54,
+ 0x69, 0xCD, 0x51, 0xA1, 0x35, 0xC7, 0x16, 0xAD, 0x39, 0x96, 0x69, 0xCD, 0xB1, 0x4E, 0x6B, 0x8E,
+ 0x3D, 0x5A, 0x73, 0xEC, 0xD1, 0x9A, 0x63, 0x9B, 0xD6, 0x1C, 0x5B, 0xB4, 0xE6, 0xD8, 0xA3, 0x35,
+ 0x47, 0x8D, 0xD6, 0x1C, 0x15, 0x5A, 0x73, 0x6C, 0xD1, 0x9A, 0x63, 0x85, 0xD6, 0x1C, 0x1B, 0xB4,
+ 0xE6, 0x58, 0xA4, 0x35, 0xC7, 0x2A, 0xAD, 0x39, 0x76, 0x68, 0xCD, 0xB1, 0x44, 0x6B, 0x8E, 0x94,
+ 0xD6, 0x7A, 0x24, 0xD0, 0x5A, 0x98, 0x3E, 0xF8, 0x3D, 0xBD, 0x78, 0x6D, 0xA0, 0xB5, 0x30, 0x0D,
+ 0xB4, 0x16, 0xA6, 0x81, 0xD6, 0xC2, 0x34, 0xD0, 0x5A, 0x9C, 0xFE, 0x9F, 0xD6, 0xFA, 0xF0, 0xA0,
+ 0xB5, 0x38, 0x8A, 0xB4, 0x36, 0xCD, 0x1F, 0xFE, 0x99, 0x5F, 0x1A, 0x07, 0x5A, 0x0B, 0xD3, 0x40,
+ 0x6B, 0xD3, 0x3C, 0xD0, 0x5A, 0x98, 0x26, 0xB4, 0xD6, 0x13, 0xBB, 0xB4, 0xD6, 0xAF, 0x2C, 0xD3,
+ 0x9A, 0xA3, 0x4A, 0x6B, 0x8E, 0x1A, 0xAD, 0xF5, 0xEB, 0xCB, 0xB4, 0xE6, 0xA8, 0xD1, 0x9A, 0x23,
+ 0x7D, 0xED, 0x9E, 0xD0, 0x5A, 0xFF, 0x11, 0xBB, 0xB4, 0xE6, 0xA8, 0xD3, 0x9A, 0xA3, 0x4A, 0x6B,
+ 0x8E, 0x1A, 0xAD, 0x39, 0xEA, 0xB4, 0xE6, 0xA8, 0xD1, 0x9A, 0xA3, 0x4C, 0x6B, 0xD3, 0x63, 0xFC,
+ 0x77, 0x9E, 0x3E, 0xAC, 0xDF, 0xA1, 0x9C, 0xD6, 0x1C, 0x29, 0xAD, 0x39, 0x08, 0x5A, 0x73, 0xA4,
+ 0xB4, 0x96, 0x2E, 0x4B, 0x4F, 0xBD, 0xFB, 0xDF, 0x39, 0x74, 0x24, 0x88, 0xF3, 0x66, 0xC4, 0xF2,
+ 0x23, 0x65, 0xA4, 0xD2, 0x63, 0x63, 0x84, 0x88, 0xB3, 0x61, 0xC4, 0xD2, 0xED, 0x3F, 0x42, 0xCC,
+ 0x1E, 0xEF, 0xB9, 0x37, 0x3D, 0x97, 0xD2, 0x9A, 0x83, 0xA3, 0x35, 0x07, 0x45, 0x6B, 0x0E, 0x86,
+ 0xD6, 0x1C, 0x1C, 0xAD, 0x39, 0x18, 0x5A, 0x73, 0x90, 0xB4, 0xD6, 0x83, 0x81, 0xD6, 0xA6, 0x00,
+ 0x43, 0x6B, 0x0E, 0x82, 0xD6, 0x1C, 0x39, 0xAD, 0x39, 0x18, 0x5A, 0x73, 0xE4, 0xB4, 0xE6, 0x60,
+ 0x68, 0xAD, 0xA7, 0x26, 0x5A, 0x3B, 0x66, 0xE7, 0xB4, 0xF6, 0xF3, 0xAE, 0x8F, 0xC0, 0xA5, 0xF9,
+ 0x19, 0xAD, 0xFD, 0xBC, 0xD7, 0x63, 0x7C, 0xF1, 0xFA, 0x53, 0x5A, 0xEB, 0xF3, 0x7D, 0x5A, 0x73,
+ 0xA4, 0x6F, 0xDE, 0x09, 0x5A, 0xEB, 0x3F, 0xA5, 0x4C, 0x6B, 0x8E, 0x2A, 0xAD, 0x39, 0xD2, 0x17,
+ 0xFA, 0x04, 0xAD, 0x39, 0xAA, 0xB4, 0xE6, 0xD8, 0xA7, 0x35, 0xC7, 0x35, 0x68, 0xCD, 0x51, 0xA7,
+ 0x35, 0x47, 0x95, 0xD6, 0x1C, 0xD7, 0xA0, 0x35, 0x47, 0x95, 0xD6, 0xE6, 0xE7, 0xF1, 0x6F, 0x20,
+ 0x5F, 0xF4, 0x91, 0x4A, 0x17, 0xF5, 0x77, 0x28, 0xA7, 0xB5, 0x74, 0x5D, 0x7E, 0xA7, 0x72, 0x5A,
+ 0x73, 0xA4, 0xB4, 0x46, 0x6C, 0xF0, 0x91, 0x23, 0xB6, 0xF0, 0x88, 0xE5, 0xBB, 0x74, 0xA4, 0x98,
+ 0x9D, 0x38, 0x72, 0xF9, 0x5E, 0xEB, 0xA9, 0xF1, 0xB7, 0x58, 0x4E, 0x6B, 0x0E, 0x92, 0xD6, 0x1C,
+ 0x1C, 0xAD, 0x39, 0x28, 0x5A, 0x73, 0x90, 0xB4, 0xE6, 0xA0, 0x68, 0xAD, 0xC7, 0x22, 0xAD, 0x4D,
+ 0x09, 0x8A, 0xD6, 0x1C, 0x0C, 0xAD, 0x39, 0x08, 0x5A, 0x73, 0x50, 0xB4, 0xE6, 0xA0, 0x68, 0xCD,
+ 0x41, 0xD2, 0x9A, 0x83, 0xA3, 0x35, 0xC7, 0x2E, 0xAD, 0x3D, 0xFE, 0x97, 0xD6, 0x38, 0x5B, 0xA3,
+ 0x70, 0x2D, 0xD5, 0x35, 0x92, 0xD7, 0x36, 0x7C, 0x8D, 0x02, 0x36, 0x42, 0xD8, 0x28, 0x62, 0x5B,
+ 0x32, 0xB6, 0x45, 0x64, 0x23, 0x94, 0x6D, 0x99, 0xD9, 0x38, 0x67, 0x23, 0xA1, 0x6D, 0x41, 0xDA,
+ 0x96, 0xA9, 0xAD, 0x60, 0x6D, 0x65, 0x6C, 0x23, 0xB5, 0x8D, 0xE4, 0xB6, 0x05, 0x6F, 0x5B, 0x00,
+ 0x37, 0x56, 0xDC, 0x56, 0xC8, 0x6D, 0xD3, 0xDC, 0x4E, 0xD0, 0x2D, 0x53, 0xB7, 0x84, 0xDD, 0xCE,
+ 0xDD, 0x2D, 0x83, 0x37, 0x5E, 0xDE, 0xAA, 0xF4, 0x56, 0xB1, 0xB7, 0x12, 0xBE, 0x5D, 0x45, 0xDF,
+ 0xAE, 0xC0, 0x6F, 0x45, 0x7F, 0xDB, 0x05, 0xB8, 0x1D, 0x81, 0xDB, 0x22, 0xB8, 0x6D, 0x83, 0xDB,
+ 0x46, 0xB8, 0x8A, 0xC2, 0xED, 0x32, 0xDC, 0xB6, 0xC3, 0x95, 0x21, 0xAE, 0x28, 0x71, 0xBB, 0x14,
+ 0xB7, 0x68, 0x71, 0x7B, 0x18, 0xB7, 0xAE, 0x71, 0x1B, 0x1C, 0xB7, 0xE9, 0x71, 0xAB, 0x20, 0xC7,
+ 0x88, 0x5C, 0x42, 0x72, 0x89, 0xC9, 0x25, 0x28, 0x97, 0xA8, 0x5C, 0xC2, 0x72, 0x89, 0xCB, 0x9D,
+ 0xC3, 0xDC, 0x99, 0xCC, 0xA5, 0x34, 0x97, 0xD9, 0x5C, 0x82, 0x73, 0xA9, 0xCE, 0x25, 0x3C, 0x47,
+ 0xF8, 0x5C, 0x01, 0xE8, 0xF2, 0x37, 0xE6, 0x84, 0xD0, 0xA5, 0xEF, 0xD3, 0x73, 0xA2, 0xF3, 0xAA,
+ 0xD1, 0xE5, 0x2F, 0xE2, 0x09, 0xA4, 0xF3, 0xAA, 0xD2, 0x11, 0xAF, 0xF0, 0x53, 0xA6, 0x2B, 0x38,
+ 0x9D, 0x5F, 0x03, 0xEA, 0xAE, 0x20, 0x75, 0x65, 0xAA, 0xF3, 0x6B, 0x58, 0x5D, 0x19, 0xEB, 0x52,
+ 0x96, 0x22, 0xB4, 0x2E, 0xE5, 0x3A, 0xA7, 0xBC, 0x8E, 0x02, 0x3B, 0x46, 0xEC, 0x9C, 0x22, 0x3B,
+ 0xC6, 0xEC, 0xD2, 0xD5, 0xF9, 0x85, 0x5A, 0x84, 0xDA, 0xE5, 0x87, 0xD0, 0xC8, 0x11, 0xE7, 0xCC,
+ 0x88, 0xE5, 0x67, 0xC9, 0x48, 0x31, 0x07, 0xC6, 0xC8, 0xE5, 0x67, 0xC2, 0x48, 0x51, 0x1B, 0xFF,
+ 0x97, 0x6A, 0x31, 0x78, 0x47, 0xEB, 0x1D, 0xCB, 0x77, 0xA4, 0xDF, 0xD1, 0x80, 0x47, 0x0A, 0x1E,
+ 0x4F, 0x78, 0xB9, 0xE1, 0x91, 0x88, 0xC7, 0x29, 0x1E, 0xC5, 0x78, 0xA4, 0xE3, 0x51, 0x90, 0x47,
+ 0x4A, 0xDE, 0x29, 0xE5, 0xE5, 0x96, 0x97, 0x62, 0x5E, 0xA6, 0x79, 0x39, 0xE7, 0xA5, 0x9E, 0x57,
+ 0x02, 0x3D, 0xE2, 0x85, 0x3F, 0x23, 0x7A, 0xB9, 0x07, 0x10, 0xA4, 0x97, 0x6A, 0x41, 0x6E, 0x7A,
+ 0x04, 0x25, 0x30, 0xA8, 0x97, 0x4A, 0x43, 0xAE, 0x7A, 0x25, 0xD6, 0x4B, 0x89, 0x89, 0x73, 0x3D,
+ 0xBF, 0x06, 0xEC, 0x5D, 0x41, 0xF6, 0x52, 0xC2, 0xE2, 0x68, 0xEF, 0x0A, 0xB6, 0x97, 0xE3, 0x5E,
+ 0xBA, 0xF4, 0x23, 0x96, 0x2F, 0xED, 0x48, 0xA5, 0x6B, 0x37, 0x42, 0xC4, 0xEA, 0x8C, 0x58, 0x7A,
+ 0xF7, 0xBF, 0x84, 0x8C, 0x21, 0x3E, 0x62, 0xCB, 0x8F, 0x20, 0xB3, 0xA9, 0x47, 0x8E, 0xD8, 0xB7,
+ 0x23, 0x46, 0xED, 0xCD, 0x11, 0x24, 0x76, 0xDF, 0x2F, 0x22, 0xA3, 0xA0, 0x8F, 0x97, 0x3E, 0x9A,
+ 0xFA, 0x58, 0xEB, 0xE3, 0xB1, 0x8F, 0xD5, 0x3E, 0x82, 0xFB, 0x58, 0xEF, 0x23, 0xC1, 0x8F, 0x13,
+ 0x3F, 0x96, 0xFC, 0x58, 0xF3, 0xE3, 0xD1, 0x8F, 0x56, 0xBF, 0x7D, 0xF6, 0xFB, 0xF3, 0x2F, 0xEA,
+ 0xDA, 0x70, 0x86, 0xFE, 0xAA, 0xEC, 0x4E, 0x7B, 0xFF, 0xF5, 0xDD, 0xD3, 0x87, 0xD3, 0x1D, 0xF6,
+ 0xC0, 0xC3, 0xBF, 0x03, 0xF3, 0xF8, 0xD1, 0x31, 0xEE, 0x2B, 0xF4, 0xE4, 0xC2, 0x0F, 0x78, 0xFC,
+ 0x4F, 0xE2, 0x67, 0x60, 0xFE, 0xDD, 0x7D, 0xEC, 0xCC, 0xF1, 0xF9, 0xDB, 0x5D, 0xB9, 0xF3, 0xF3,
+ 0x17, 0x8C, 0x0C, 0xFE, 0x0C, 0xAE, 0xDA, 0xE0, 0xCF, 0x8B, 0x72, 0x1D, 0xFC, 0x15, 0x9B, 0x7D,
+ 0x70, 0x0E, 0xE5, 0x42, 0xF8, 0x33, 0xC6, 0x1B, 0xE1, 0xCF, 0xF8, 0x8A, 0x12, 0xFE, 0xBA, 0x60,
+ 0x76, 0xC2, 0x39, 0xB4, 0x26, 0x85, 0x3F, 0x2F, 0x39, 0xB5, 0xC2, 0xEF, 0x37, 0xEE, 0xDD, 0xBE,
+ 0x75, 0xB3, 0x7D, 0xF8, 0xF2, 0xE9, 0xED, 0xBB, 0x17, 0xAF, 0x3F, 0x7E, 0xEC, 0x3B, 0xE0, 0xD5,
+ 0xCB, 0xE7, 0xCF, 0xDE, 0x7C, 0xF8, 0xF0, 0xB9, 0x7D, 0xEE, 0x5E, 0x74, 0xB7, 0xFF, 0x89, 0xBB,
+ 0xFB, 0xB6, 0xB5, 0xBB, 0xF6, 0xFA, 0xE3, 0xCD, 0xDB, 0xF7, 0x7E, 0x00, 0x22, 0x7E, 0x14, 0x4B,
+ 0x30, 0x61, 0x02, 0x00
+}; //bootstrap.min.css
+
+//Content of jquery-3.6.0.min.js.gz with gzip compression
+static const uint8_t jquery_js[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x82, 0x8A, 0x0C, 0x61, 0x04, 0x00, 0x6A, 0x71, 0x75, 0x65, 0x72, 0x79,
+ 0x2D, 0x33, 0x2E, 0x36, 0x2E, 0x30, 0x2E, 0x6D, 0x69, 0x6E, 0x2E, 0x6A, 0x73, 0x00, 0xBC, 0x5D,
+ 0x69, 0x7B, 0xDB, 0xC6, 0x11, 0xFE, 0xDE, 0x5F, 0x21, 0xA2, 0xAE, 0x0A, 0x98, 0x2B, 0x5A, 0x74,
+ 0x9A, 0xB4, 0x85, 0x04, 0xF3, 0x71, 0x64, 0x27, 0x71, 0x9B, 0xD3, 0x72, 0xDB, 0xA4, 0x14, 0x9D,
+ 0x07, 0x22, 0x41, 0x09, 0x31, 0x05, 0x30, 0x00, 0xA8, 0xA3, 0x02, 0xFB, 0xDB, 0xFB, 0xCE, 0xEC,
+ 0x89, 0x83, 0xB2, 0xD3, 0x2B, 0x87, 0xB8, 0xD8, 0xFB, 0x98, 0x9D, 0x9D, 0x6B, 0x67, 0x9F, 0x3C,
+ 0x1E, 0xEC, 0xFD, 0xF4, 0xDD, 0x26, 0x29, 0xEE, 0xF6, 0xAE, 0x3F, 0x1A, 0x7D, 0x32, 0x3A, 0xDC,
+ 0xAB, 0xF7, 0xFC, 0x79, 0xB0, 0xF7, 0xCD, 0x3A, 0xC9, 0xFE, 0x74, 0xBA, 0xF7, 0x59, 0xBE, 0xC9,
+ 0x16, 0x71, 0x95, 0xE6, 0xD9, 0x5E, 0x9C, 0x2D, 0xF6, 0xF2, 0xEA, 0x32, 0x29, 0xF6, 0xE6, 0x79,
+ 0x56, 0x15, 0xE9, 0xF9, 0xA6, 0xCA, 0x8B, 0x12, 0xD9, 0x7F, 0xFA, 0x99, 0x8A, 0x8F, 0xF2, 0xE2,
+ 0xE2, 0xC9, 0x2A, 0x9D, 0x27, 0x59, 0x99, 0xEC, 0x3D, 0x7E, 0xF2, 0xAB, 0xC1, 0x72, 0x93, 0xCD,
+ 0xA9, 0xA0, 0x9F, 0x88, 0x2A, 0xB8, 0xF7, 0x36, 0x88, 0x2E, 0x51, 0x6C, 0x5E, 0x79, 0x47, 0x5E,
+ 0x7E, 0xFE, 0x53, 0x82, 0x40, 0x14, 0x55, 0x77, 0xEB, 0x24, 0x5F, 0xEE, 0x5D, 0xE5, 0x8B, 0xCD,
+ 0x2A, 0xD9, 0xDF, 0xDF, 0x91, 0x30, 0x4A, 0x6E, 0xD7, 0x79, 0x51, 0x95, 0x93, 0xE6, 0x67, 0x94,
+ 0x8C, 0x16, 0xF9, 0x7C, 0x73, 0x95, 0x64, 0xD5, 0xA4, 0x42, 0x33, 0x83, 0xC3, 0x20, 0xB4, 0xAD,
+ 0x06, 0xF7, 0xE9, 0xD2, 0x1F, 0xD8, 0x2C, 0x41, 0x75, 0x59, 0xE4, 0x37, 0x7B, 0x59, 0x72, 0xB3,
+ 0xF7, 0xB2, 0x28, 0xF2, 0xC2, 0xF7, 0xD4, 0xB8, 0x8B, 0xE4, 0xE7, 0x4D, 0x5A, 0x24, 0xE5, 0x5E,
+ 0xBC, 0x77, 0x93, 0x66, 0x8B, 0xFC, 0x06, 0x3F, 0xD5, 0x25, 0xBE, 0x74, 0x49, 0x2F, 0x38, 0x2A,
+ 0x92, 0x6A, 0x53, 0x64, 0x7B, 0x15, 0xAA, 0xDD, 0x86, 0xFC, 0xD7, 0xF7, 0x30, 0x33, 0xC9, 0x32,
+ 0xCD, 0x92, 0x85, 0x37, 0x50, 0xDD, 0x55, 0xE5, 0x27, 0xF2, 0x27, 0xAC, 0x2E, 0xD3, 0x52, 0x98,
+ 0x0E, 0x9D, 0x88, 0xA4, 0x35, 0x0D, 0xD7, 0x71, 0xB1, 0x57, 0x45, 0xD3, 0x99, 0x28, 0xA2, 0x6F,
+ 0x78, 0xDC, 0xA3, 0x8B, 0xA4, 0xFA, 0xB6, 0xC8, 0xAB, 0x9C, 0xAA, 0xFB, 0x66, 0x29, 0xCA, 0xA8,
+ 0x1A, 0x95, 0x34, 0xA7, 0xE2, 0x02, 0xA1, 0xE5, 0x2A, 0xAE, 0x26, 0xEE, 0xF8, 0x54, 0xA7, 0x64,
+ 0xCA, 0x68, 0x1E, 0xAF, 0x56, 0xDC, 0xBD, 0xDE, 0x2C, 0x58, 0xB3, 0x39, 0x32, 0xC5, 0xEB, 0xF5,
+ 0xEA, 0xCE, 0x47, 0x93, 0xC8, 0x28, 0x36, 0xA8, 0x74, 0xBD, 0x29, 0x2F, 0x45, 0x8A, 0x40, 0x8A,
+ 0xD1, 0xDC, 0xA2, 0xCD, 0x2C, 0xBA, 0xDF, 0x8A, 0x3C, 0xCA, 0x46, 0x55, 0x7E, 0x8A, 0x7E, 0x66,
+ 0x17, 0xE2, 0x1A, 0x1F, 0x97, 0x71, 0xF9, 0xCD, 0x4D, 0x86, 0xBE, 0xAD, 0x93, 0xA2, 0xBA, 0x13,
+ 0x71, 0x74, 0x6D, 0xD3, 0x57, 0x51, 0x2C, 0x1B, 0x97, 0x83, 0x08, 0xC4, 0x1D, 0x55, 0x71, 0x15,
+ 0x75, 0xFB, 0xE1, 0xE9, 0x28, 0xBB, 0xC0, 0xB4, 0xE8, 0xD9, 0xE6, 0xEA, 0x3C, 0x29, 0xEC, 0x2C,
+ 0x26, 0xA3, 0x2C, 0x5F, 0x24, 0x6F, 0xF0, 0x81, 0x44, 0x5D, 0xC4, 0x4D, 0x4E, 0xAB, 0xE4, 0x6A,
+ 0x2B, 0x6E, 0x7B, 0x5A, 0xD8, 0xCB, 0x36, 0xAB, 0xD5, 0x20, 0x42, 0xC1, 0x24, 0x8A, 0x00, 0x1F,
+ 0x72, 0x25, 0xB6, 0xE2, 0x65, 0x74, 0x62, 0x00, 0x41, 0xCC, 0xA3, 0x7B, 0xAA, 0x2A, 0x1C, 0x1C,
+ 0x8A, 0xB2, 0x98, 0xD3, 0x4F, 0x86, 0xD9, 0x49, 0x64, 0xE0, 0x2B, 0x02, 0x31, 0x0A, 0x6F, 0x8F,
+ 0x74, 0xF5, 0x7B, 0xE7, 0x04, 0xC3, 0x22, 0x0B, 0xEE, 0x69, 0xC5, 0x0A, 0x91, 0x62, 0x7E, 0xFC,
+ 0x2C, 0xCA, 0xEA, 0xFA, 0x65, 0x30, 0x9A, 0x17, 0x49, 0x5C, 0x25, 0x2F, 0x57, 0x09, 0x55, 0xED,
+ 0x7B, 0xE5, 0xBC, 0x48, 0xD7, 0x04, 0x31, 0x00, 0xBE, 0x7C, 0x54, 0x25, 0xB7, 0x55, 0x44, 0xF0,
+ 0xBF, 0x04, 0xBC, 0x15, 0x7B, 0x69, 0xB6, 0x37, 0x0F, 0x7C, 0xCC, 0xF6, 0xB4, 0x98, 0xD5, 0x35,
+ 0x2F, 0xF7, 0xF3, 0x4A, 0x6E, 0x25, 0xF4, 0xB8, 0xF9, 0xED, 0x17, 0x41, 0xB0, 0xBF, 0x9F, 0x8F,
+ 0x4A, 0x37, 0x0E, 0x6D, 0x07, 0x47, 0x58, 0x8D, 0x24, 0x5E, 0xD0, 0x52, 0x26, 0xD9, 0xE2, 0xE4,
+ 0x32, 0x5D, 0x2D, 0xFC, 0x3C, 0x18, 0xAD, 0xE3, 0x02, 0x1D, 0xF8, 0x1A, 0x13, 0x37, 0x2A, 0x92,
+ 0xAB, 0xFC, 0x3A, 0xD1, 0x29, 0x5B, 0x33, 0x8C, 0x9B, 0xD6, 0x3C, 0x61, 0x82, 0x26, 0xC9, 0xD0,
+ 0xF3, 0xC2, 0xCE, 0xB6, 0x4B, 0xEA, 0xBA, 0x6F, 0xAD, 0x26, 0xD9, 0x34, 0xD7, 0x70, 0x86, 0x01,
+ 0xE8, 0x62, 0xA1, 0x4E, 0xDF, 0xD2, 0xFC, 0x2C, 0x23, 0x8F, 0x11, 0x89, 0x27, 0x4E, 0x23, 0x17,
+ 0x07, 0xE8, 0xA6, 0x79, 0x07, 0x9E, 0x8E, 0x96, 0x19, 0x40, 0x2E, 0xAD, 0x38, 0xC5, 0x99, 0xEA,
+ 0x35, 0xF5, 0x51, 0x6E, 0x8C, 0xC1, 0x80, 0xD6, 0x7F, 0x95, 0x64, 0x17, 0xD5, 0xA5, 0x87, 0xA9,
+ 0xA3, 0x45, 0x1D, 0xC9, 0x4F, 0x40, 0x29, 0x06, 0xA3, 0xB7, 0xE5, 0xE0, 0x0A, 0xE1, 0xFD, 0xFD,
+ 0xC1, 0x2D, 0xFF, 0xF8, 0x5E, 0x5C, 0x14, 0xF1, 0x1D, 0xBA, 0x4D, 0x2B, 0x74, 0x88, 0x9F, 0xAA,
+ 0xAE, 0x15, 0x8C, 0xD9, 0xB1, 0x54, 0xFB, 0xFB, 0x87, 0xC7, 0xF8, 0x53, 0x1D, 0x8C, 0x69, 0x59,
+ 0xB0, 0x1D, 0xA8, 0x4B, 0xD1, 0xE9, 0x68, 0xAD, 0xF7, 0x5F, 0x74, 0x2F, 0x11, 0x5B, 0xB8, 0x14,
+ 0xD8, 0x3C, 0xD8, 0xB3, 0x9B, 0x39, 0x10, 0x5E, 0x78, 0x2A, 0x64, 0x17, 0xC2, 0x43, 0x51, 0xE5,
+ 0xCF, 0xA9, 0x25, 0xBB, 0xE1, 0xCC, 0x10, 0x4B, 0x39, 0x4B, 0x84, 0x00, 0xB0, 0xCF, 0xB0, 0xAC,
+ 0x36, 0x4F, 0x77, 0x09, 0xDC, 0xCC, 0x61, 0x72, 0x7C, 0x38, 0xA1, 0xD0, 0x34, 0x19, 0xD2, 0x8F,
+ 0x1A, 0xEF, 0x2C, 0x94, 0x71, 0xB3, 0xAD, 0xA0, 0x0D, 0x7B, 0x5A, 0xC5, 0xF3, 0x77, 0xA6, 0x4A,
+ 0x33, 0x63, 0xE8, 0xFD, 0x55, 0x52, 0x5C, 0x24, 0x3E, 0x97, 0x74, 0x3A, 0xED, 0x07, 0x22, 0xB1,
+ 0x38, 0x0C, 0x43, 0x4C, 0xAE, 0xE5, 0x5E, 0x8D, 0x18, 0x45, 0x55, 0x5B, 0x91, 0xC4, 0xF3, 0xCB,
+ 0xBE, 0x3E, 0x9E, 0x8E, 0x28, 0x85, 0x2B, 0x64, 0x94, 0x71, 0x15, 0xAF, 0x6D, 0xB6, 0xCC, 0x64,
+ 0xE3, 0x06, 0x4D, 0xCF, 0xFC, 0xD3, 0x11, 0xF2, 0xC9, 0x42, 0xFD, 0x10, 0xC0, 0x23, 0xA6, 0x28,
+ 0xAA, 0x34, 0x40, 0xBD, 0x8C, 0xE5, 0x6C, 0xCD, 0xBB, 0x2A, 0x2E, 0x25, 0x02, 0x93, 0x55, 0xC7,
+ 0xC5, 0x05, 0xEF, 0xE7, 0x92, 0x2A, 0x58, 0xA6, 0x45, 0x59, 0xED, 0xAA, 0x20, 0xF9, 0xD9, 0x3F,
+ 0x44, 0x9E, 0x55, 0xFC, 0x60, 0x96, 0x83, 0x31, 0xF2, 0x24, 0xD7, 0x49, 0xF6, 0xFE, 0x7E, 0x9C,
+ 0x8E, 0x2E, 0x8A, 0xE4, 0x81, 0x11, 0xFA, 0xD5, 0x70, 0x1C, 0xFC, 0xE6, 0x29, 0x0F, 0x2D, 0x5F,
+ 0x2C, 0xFE, 0xF3, 0x0A, 0xF7, 0x2A, 0x55, 0x5B, 0xF2, 0x73, 0xCF, 0xBA, 0x53, 0x31, 0xBB, 0x33,
+ 0x86, 0xC9, 0xD0, 0x67, 0x30, 0x0A, 0x0F, 0x83, 0xA3, 0xFE, 0xE6, 0x0E, 0x8F, 0xA3, 0x6C, 0x7F,
+ 0x3F, 0x3B, 0xAE, 0x26, 0x53, 0x06, 0xAC, 0x6C, 0x36, 0x0B, 0xA7, 0x33, 0xAA, 0x3E, 0xDB, 0xD9,
+ 0x59, 0x0B, 0x35, 0x75, 0xDD, 0x05, 0x30, 0x09, 0x98, 0xE1, 0x46, 0x94, 0x38, 0x99, 0xC3, 0x6A,
+ 0x44, 0x3F, 0xA2, 0x5C, 0xD3, 0xB2, 0xE2, 0x4B, 0x06, 0xB6, 0x02, 0xE0, 0x74, 0x5B, 0xA1, 0x8D,
+ 0x88, 0x36, 0x9B, 0x0E, 0x9B, 0xF6, 0xE4, 0x70, 0x18, 0xD5, 0x0A, 0xC6, 0xB2, 0x38, 0x67, 0xCC,
+ 0x22, 0x4F, 0x0F, 0x81, 0x71, 0xEE, 0x01, 0x29, 0xD1, 0x58, 0x6C, 0x4C, 0xB4, 0x19, 0xF6, 0x2A,
+ 0x1A, 0x8C, 0x8F, 0x96, 0x74, 0xAA, 0x9F, 0xE7, 0xF9, 0x2A, 0x89, 0x1D, 0xB4, 0x15, 0x03, 0x27,
+ 0xE0, 0x8C, 0x6A, 0x54, 0x56, 0xAA, 0xCA, 0x86, 0xC3, 0x40, 0x74, 0xB0, 0x5F, 0x5C, 0xD7, 0x57,
+ 0x7E, 0x1C, 0xD4, 0xB5, 0x1F, 0xE3, 0x24, 0x0B, 0xD0, 0x64, 0x14, 0x6D, 0x50, 0x49, 0x2C, 0xB7,
+ 0x4B, 0x79, 0x70, 0x10, 0x1C, 0x95, 0xC7, 0x9B, 0x23, 0x2A, 0x0D, 0x34, 0x2F, 0x8F, 0x1C, 0x3F,
+ 0x69, 0x54, 0x1F, 0x30, 0xCA, 0xAF, 0x24, 0x6E, 0x29, 0xA2, 0x64, 0x5A, 0xCD, 0x84, 0xF7, 0xE3,
+ 0x8F, 0x8C, 0x5D, 0x7E, 0xFC, 0x11, 0x87, 0x59, 0x04, 0xE4, 0x13, 0xE3, 0xA7, 0xA0, 0xDE, 0xED,
+ 0xEF, 0xD3, 0xCF, 0xE9, 0x28, 0x2D, 0xBF, 0x5D, 0xC5, 0x69, 0x26, 0xA7, 0xD9, 0x2F, 0xA8, 0x0B,
+ 0x69, 0xC4, 0x48, 0x06, 0x49, 0xFC, 0x4B, 0xC7, 0x42, 0x30, 0xC1, 0xE9, 0x13, 0x53, 0x8D, 0x79,
+ 0x94, 0x02, 0xEF, 0x35, 0x33, 0x64, 0xC1, 0x64, 0x3A, 0x0B, 0xD3, 0xBA, 0x6E, 0x55, 0x47, 0x09,
+ 0x59, 0x88, 0x41, 0xA7, 0x98, 0x2A, 0x41, 0xC5, 0x23, 0xBD, 0x1C, 0xFE, 0x0A, 0x73, 0x8D, 0x8A,
+ 0xC3, 0xEB, 0x3C, 0x5D, 0xEC, 0x1D, 0xAA, 0x5E, 0x71, 0x16, 0xC4, 0x6A, 0x18, 0x8A, 0xED, 0xFA,
+ 0xF9, 0xF7, 0x20, 0xC1, 0x62, 0x1C, 0xAD, 0xA1, 0x22, 0xA2, 0xBC, 0xA1, 0xBF, 0x1C, 0x7E, 0x15,
+ 0x57, 0x97, 0xA3, 0x82, 0xA2, 0xAF, 0xFC, 0x20, 0x18, 0x01, 0xA0, 0x57, 0xF1, 0x3C, 0xF1, 0x9F,
+ 0x9C, 0xBD, 0x78, 0x72, 0x21, 0x3C, 0x2F, 0x10, 0x69, 0xF9, 0x1A, 0x27, 0xD7, 0x1D, 0x1D, 0xB4,
+ 0x09, 0x91, 0x60, 0x0D, 0x50, 0x6E, 0x93, 0x67, 0x84, 0x6E, 0xB2, 0x3C, 0x5F, 0x3B, 0xF0, 0x88,
+ 0xCE, 0x37, 0x86, 0xD4, 0xDD, 0x0A, 0x22, 0xD3, 0x67, 0x82, 0x3F, 0xA0, 0x43, 0x6C, 0x2A, 0xD7,
+ 0x76, 0x4F, 0xE6, 0x9F, 0xD1, 0xBC, 0x9B, 0x13, 0x8C, 0xCE, 0x8A, 0x81, 0x5F, 0x45, 0x05, 0x85,
+ 0xFB, 0x0E, 0x3C, 0xCC, 0xF2, 0xB5, 0xC2, 0xCD, 0xC2, 0x73, 0xA0, 0xDD, 0x43, 0xC9, 0xCA, 0x05,
+ 0x7F, 0x7C, 0x2B, 0x12, 0x28, 0x0B, 0x00, 0x2B, 0xAB, 0x80, 0xFA, 0xF9, 0xF2, 0x6A, 0x5D, 0xDD,
+ 0xED, 0xEA, 0xE7, 0x91, 0x03, 0x1D, 0xAA, 0xC3, 0x63, 0xDD, 0xF3, 0x43, 0x9C, 0x18, 0xAB, 0xFC,
+ 0x3C, 0x5E, 0xBD, 0xBC, 0x8E, 0x57, 0xA6, 0xA8, 0x26, 0x41, 0x88, 0x16, 0xB9, 0x97, 0xF4, 0x4A,
+ 0x45, 0xDD, 0xE0, 0xE0, 0x16, 0x29, 0x06, 0x89, 0xDB, 0xFC, 0xB2, 0x31, 0x6C, 0xA6, 0xE8, 0x90,
+ 0xE8, 0x91, 0x35, 0x0D, 0xF4, 0x1E, 0x2D, 0x63, 0x60, 0xFA, 0x24, 0x3D, 0x2A, 0x8E, 0x31, 0x65,
+ 0x12, 0x90, 0x07, 0x63, 0x74, 0x5E, 0x13, 0x92, 0x20, 0x51, 0xB0, 0x09, 0xE9, 0x27, 0x08, 0xCE,
+ 0x41, 0xE3, 0xBC, 0xDB, 0x26, 0x2B, 0xD0, 0xAE, 0x86, 0x90, 0x49, 0xDE, 0x5F, 0x42, 0x03, 0x4E,
+ 0x42, 0xE7, 0xC6, 0xBB, 0x84, 0xC1, 0xB3, 0xAF, 0x7F, 0x74, 0x40, 0x4F, 0x67, 0x47, 0x6D, 0x0A,
+ 0x0E, 0xFD, 0x95, 0xD3, 0x47, 0xDD, 0x9E, 0xE8, 0xB3, 0x2D, 0x13, 0x5E, 0xC9, 0x74, 0xA7, 0x4B,
+ 0x97, 0xE0, 0x64, 0x0C, 0x93, 0x20, 0xDC, 0xA8, 0x45, 0x10, 0x28, 0x20, 0x32, 0x2C, 0x42, 0x66,
+ 0xDB, 0xB4, 0x73, 0xD8, 0x3A, 0x80, 0xAB, 0xC9, 0xC1, 0x38, 0x4C, 0xF5, 0x3A, 0x27, 0x3C, 0x93,
+ 0xDC, 0x54, 0xA3, 0x98, 0x9C, 0x37, 0xD9, 0xDD, 0x61, 0xA5, 0x26, 0x8F, 0x26, 0x56, 0xA4, 0xDD,
+ 0xB9, 0x4C, 0xA6, 0xE9, 0x70, 0x38, 0x23, 0x32, 0xCF, 0x8C, 0x4A, 0xE7, 0x89, 0x52, 0x81, 0xE9,
+ 0x20, 0x64, 0xDF, 0xEA, 0x95, 0x6D, 0xA0, 0x20, 0x96, 0x20, 0x45, 0xCD, 0xB9, 0xA9, 0x19, 0xB8,
+ 0x6B, 0x90, 0x1D, 0xA5, 0xC7, 0xF9, 0x51, 0x8A, 0xEA, 0x07, 0x95, 0x8F, 0x16, 0x90, 0x27, 0x00,
+ 0x38, 0xC7, 0xC0, 0x1D, 0x8C, 0xD9, 0x39, 0xCE, 0x6C, 0xD6, 0xA2, 0x79, 0x56, 0x77, 0x08, 0x58,
+ 0x54, 0x1F, 0xA3, 0x1D, 0x03, 0x16, 0xBC, 0xAE, 0x76, 0x24, 0xF9, 0x71, 0x71, 0x94, 0xA3, 0x29,
+ 0x85, 0xDB, 0xD2, 0x88, 0x9A, 0xCC, 0x81, 0x70, 0x50, 0x09, 0x41, 0xBB, 0x6C, 0x11, 0xC4, 0xA8,
+ 0x01, 0x8A, 0x9C, 0x81, 0xE2, 0xFD, 0x05, 0x54, 0xFF, 0x2E, 0xFC, 0x98, 0x28, 0xA3, 0x4D, 0xBA,
+ 0x08, 0xC7, 0xA2, 0xDC, 0xAC, 0xD7, 0x74, 0x6C, 0xDC, 0x01, 0xD9, 0xF6, 0xD0, 0x9D, 0xA7, 0x77,
+ 0x57, 0xE7, 0xF9, 0x8A, 0x11, 0xE4, 0x32, 0x9B, 0xCA, 0x2F, 0xE2, 0x01, 0x8A, 0x18, 0x7B, 0x0F,
+ 0xD3, 0xDC, 0x89, 0x0A, 0x84, 0xA2, 0x5B, 0xBC, 0x4F, 0xE5, 0x61, 0xB0, 0xF7, 0x35, 0x93, 0x7F,
+ 0x7B, 0x92, 0x63, 0xD9, 0xFB, 0x4C, 0x53, 0x9B, 0x0C, 0x1E, 0x7B, 0x2F, 0x40, 0xC0, 0xEF, 0xBD,
+ 0x4E, 0x2E, 0x5E, 0xDE, 0xAE, 0x15, 0xA2, 0x90, 0x28, 0x48, 0x35, 0xEC, 0xF1, 0xF1, 0x05, 0xDA,
+ 0x7E, 0xCF, 0x0B, 0x5A, 0x87, 0x73, 0x36, 0x35, 0x18, 0xC6, 0x1B, 0x56, 0x43, 0x6F, 0xE6, 0xA1,
+ 0x3B, 0xE0, 0x8C, 0xBE, 0xCC, 0x6F, 0x92, 0xE2, 0x24, 0x2E, 0x13, 0x1C, 0x8C, 0x01, 0xF3, 0x79,
+ 0xCE, 0x31, 0x97, 0xE9, 0x73, 0x6E, 0x21, 0xCE, 0x45, 0x8E, 0xC5, 0xB8, 0x14, 0x4B, 0x71, 0x21,
+ 0x6E, 0xC4, 0x46, 0xAC, 0xC4, 0x1B, 0x71, 0x22, 0x62, 0xF1, 0x52, 0x5C, 0x8B, 0x52, 0xCC, 0xC5,
+ 0x1D, 0x68, 0x68, 0xAF, 0x4C, 0xFF, 0xF1, 0x8F, 0x55, 0xE2, 0x0D, 0xC7, 0x8F, 0x81, 0x1C, 0xB9,
+ 0xB3, 0x62, 0x1D, 0x65, 0x96, 0x9D, 0x79, 0x17, 0x1D, 0x32, 0x20, 0x5E, 0x45, 0x1B, 0xB4, 0x27,
+ 0x6E, 0xE5, 0xCF, 0x73, 0xF9, 0xF3, 0xB5, 0xFC, 0xF9, 0xA9, 0x9F, 0x14, 0x4F, 0x68, 0xFB, 0xF2,
+ 0xF1, 0x08, 0xDE, 0x59, 0x00, 0xF1, 0xBC, 0xC0, 0x81, 0xD7, 0x66, 0xF6, 0x98, 0x45, 0xFD, 0x99,
+ 0x78, 0xC5, 0x7C, 0x2D, 0xBE, 0xD4, 0x3C, 0xE3, 0x17, 0x3A, 0xF0, 0x8D, 0x61, 0x52, 0xBF, 0x8D,
+ 0x76, 0xEC, 0x18, 0xEA, 0xA0, 0x85, 0xAD, 0x0C, 0xB0, 0x95, 0x49, 0x8C, 0x93, 0x80, 0xF2, 0xA0,
+ 0x2E, 0x04, 0xAA, 0x3F, 0x1A, 0x7B, 0x1F, 0x8C, 0xB7, 0xE2, 0x75, 0xE4, 0xCD, 0x2F, 0x93, 0xF9,
+ 0xBB, 0x64, 0x51, 0x97, 0xC9, 0x0A, 0x53, 0x8C, 0x40, 0x5C, 0xDE, 0x65, 0xF3, 0x3A, 0x86, 0xE4,
+ 0x61, 0x89, 0xD1, 0x97, 0x1C, 0xC2, 0x21, 0x73, 0x57, 0xB3, 0x48, 0x22, 0x5F, 0x95, 0x35, 0x58,
+ 0xF2, 0xA4, 0xA8, 0x17, 0x69, 0x19, 0x9F, 0xAF, 0x50, 0xE0, 0x32, 0x5D, 0x2C, 0x92, 0xAC, 0x4E,
+ 0x4B, 0x6C, 0x86, 0x7A, 0x85, 0xD3, 0xA4, 0xBE, 0xDA, 0xAC, 0xAA, 0x74, 0xBD, 0x4A, 0xEA, 0x7C,
+ 0x8D, 0x84, 0x02, 0xE7, 0x51, 0x9E, 0xAD, 0xEE, 0x6A, 0x29, 0x04, 0xA0, 0xB6, 0xE6, 0x48, 0x58,
+ 0x78, 0xE2, 0xAB, 0xC8, 0x9B, 0x9E, 0x9D, 0xDD, 0x3E, 0x3D, 0x3C, 0x3B, 0xAB, 0xCE, 0xCE, 0x8A,
+ 0xB3, 0xB3, 0xEC, 0xEC, 0x6C, 0x39, 0xF3, 0xC4, 0xAB, 0xC8, 0xF3, 0x27, 0xE1, 0x19, 0xFE, 0x41,
+ 0xF2, 0x22, 0x3E, 0x58, 0x3E, 0x3F, 0xF8, 0x6C, 0x76, 0x3F, 0x16, 0x9F, 0x6C, 0xBD, 0xE1, 0x57,
+ 0x43, 0x6F, 0x52, 0x73, 0xD2, 0x5B, 0x5B, 0xA4, 0x46, 0xBE, 0x9B, 0x03, 0xFC, 0xBC, 0x3D, 0x3B,
+ 0x3C, 0x40, 0x8D, 0xBF, 0x5F, 0xCE, 0x82, 0xA1, 0x27, 0xFE, 0x16, 0x79, 0xC8, 0xC7, 0x65, 0x1E,
+ 0xFB, 0xDE, 0xF0, 0xD5, 0xD0, 0x0B, 0x50, 0xAF, 0xFA, 0x9E, 0x3E, 0x7E, 0xFB, 0xA8, 0x1E, 0xFC,
+ 0x73, 0x36, 0x89, 0x02, 0x19, 0x83, 0xA4, 0xDF, 0xFA, 0xAA, 0xDD, 0x11, 0x55, 0x85, 0x7F, 0x7E,
+ 0x3B, 0x0B, 0x1E, 0x07, 0xBF, 0xAD, 0xCF, 0xBC, 0x76, 0xC2, 0x99, 0x47, 0x29, 0x67, 0x5E, 0xAD,
+ 0xEA, 0x0D, 0x6A, 0x55, 0xCB, 0xD9, 0x19, 0x06, 0xF0, 0x59, 0xE4, 0x85, 0x2A, 0x81, 0xCB, 0xF9,
+ 0xBE, 0xFF, 0xCB, 0xAB, 0x0E, 0xEA, 0x76, 0x8A, 0x1F, 0x4C, 0x51, 0xFD, 0xAC, 0xF6, 0x86, 0x7F,
+ 0x1B, 0x7A, 0xC8, 0x53, 0x8F, 0x90, 0xEF, 0x8C, 0x9A, 0x16, 0x9F, 0x46, 0x00, 0x5C, 0xB5, 0xC1,
+ 0x7C, 0xF4, 0x03, 0xA3, 0xF7, 0x2E, 0xB0, 0x97, 0x1E, 0xB9, 0xF1, 0xDE, 0x5B, 0xEE, 0xE3, 0x90,
+ 0x2B, 0x7E, 0xAB, 0x2A, 0x9D, 0x05, 0xBA, 0x15, 0xD4, 0x28, 0xD3, 0x1F, 0xA9, 0xC2, 0x3F, 0xF6,
+ 0x14, 0x7E, 0x2C, 0xE4, 0x0F, 0x92, 0xFF, 0xD1, 0x4C, 0xD6, 0xF3, 0xFA, 0x6C, 0xF8, 0xCF, 0x59,
+ 0xCD, 0x1F, 0x81, 0xC9, 0xFA, 0x97, 0x56, 0xF7, 0xEA, 0x67, 0x88, 0xFC, 0xDE, 0x8D, 0xFC, 0x2C,
+ 0x10, 0x7F, 0x6D, 0xD6, 0xC7, 0xF3, 0xF7, 0x08, 0xF9, 0x3E, 0x8F, 0xEE, 0x5F, 0xBD, 0x08, 0x1B,
+ 0x69, 0xBF, 0x56, 0xB3, 0x8B, 0xD4, 0x93, 0x2F, 0x9F, 0x9F, 0x9E, 0x36, 0x53, 0x31, 0x16, 0x9B,
+ 0xFE, 0xE6, 0xF9, 0xE7, 0xCD, 0x54, 0x99, 0x54, 0x4F, 0x1F, 0xCF, 0x28, 0xF9, 0xF9, 0x9B, 0x37,
+ 0xAF, 0x9B, 0xE9, 0x98, 0xDD, 0x40, 0x7C, 0x7B, 0xFA, 0xF2, 0x2F, 0x2F, 0xBE, 0x69, 0x27, 0x7C,
+ 0x86, 0xE6, 0xBE, 0x78, 0xF5, 0x65, 0xAB, 0x33, 0xA1, 0xCF, 0xE0, 0xCD, 0xEC, 0x51, 0xBD, 0x8A,
+ 0xF1, 0x27, 0xAB, 0x2E, 0xE9, 0xFF, 0x03, 0xFA, 0x08, 0x0E, 0xFC, 0x39, 0x89, 0x09, 0xEA, 0x7C,
+ 0x79, 0x40, 0xC8, 0x56, 0x41, 0x84, 0x9A, 0x2D, 0xE2, 0x85, 0x6A, 0xB0, 0x2F, 0x58, 0x92, 0xE9,
+ 0x10, 0x10, 0x1C, 0xF8, 0x80, 0xF8, 0xC7, 0x41, 0x56, 0x5B, 0xA0, 0xE4, 0x04, 0xFD, 0x4D, 0xC9,
+ 0x43, 0xAC, 0xB8, 0xFA, 0x54, 0xAB, 0xEF, 0xA5, 0x18, 0x09, 0x11, 0xE5, 0xCD, 0x8E, 0x31, 0xB0,
+ 0xBF, 0xC6, 0x34, 0x3C, 0x52, 0x59, 0xB2, 0x24, 0x59, 0x94, 0x27, 0x79, 0x46, 0x32, 0x93, 0xB0,
+ 0x67, 0xF1, 0xE4, 0xDA, 0x85, 0xB6, 0x57, 0xC9, 0xCF, 0xF5, 0x05, 0xC6, 0x24, 0x47, 0x64, 0x07,
+ 0xD8, 0x1C, 0x03, 0x3E, 0x0E, 0xD0, 0xAD, 0x60, 0xC2, 0x5D, 0xB7, 0x1D, 0x43, 0xAE, 0x68, 0xFA,
+ 0x16, 0x7D, 0x7F, 0xA4, 0xBA, 0xB8, 0x15, 0x3F, 0x44, 0x4F, 0xBE, 0x78, 0xF3, 0xD5, 0x97, 0x8F,
+ 0x9E, 0xA4, 0xE2, 0xBB, 0xE8, 0x09, 0x75, 0x30, 0xCD, 0xD6, 0x9B, 0x4A, 0x61, 0x9F, 0x9A, 0xFA,
+ 0x15, 0x03, 0x5F, 0xD4, 0x90, 0xC4, 0x54, 0x79, 0x16, 0x50, 0xBE, 0x3F, 0x21, 0xDF, 0xE5, 0xD9,
+ 0x82, 0x82, 0x7F, 0x46, 0x70, 0xFA, 0xF6, 0x7E, 0x36, 0x3C, 0xBB, 0x3F, 0x2B, 0x1F, 0x9F, 0x4D,
+ 0x33, 0x48, 0x4D, 0xAF, 0x93, 0xBD, 0xB3, 0x9B, 0x27, 0xE2, 0xEF, 0xB2, 0xB6, 0x5F, 0xFB, 0x53,
+ 0x42, 0x04, 0x98, 0x21, 0xFF, 0xEC, 0x06, 0x7F, 0xCF, 0x46, 0x3A, 0x02, 0x75, 0x89, 0x24, 0x89,
+ 0x9E, 0x4C, 0x31, 0xC2, 0x27, 0xA2, 0x4A, 0x1A, 0xB0, 0xF6, 0x1E, 0x54, 0xE3, 0xBB, 0xB8, 0x86,
+ 0xC6, 0x72, 0xC1, 0x73, 0x19, 0xF5, 0xD1, 0x59, 0xDE, 0xE1, 0xAD, 0x37, 0x4C, 0x24, 0xB6, 0xF6,
+ 0xC7, 0xC1, 0xC1, 0x27, 0x1F, 0x7F, 0xFC, 0xD1, 0x27, 0x86, 0x45, 0x04, 0xB3, 0x91, 0x81, 0x6D,
+ 0x94, 0x67, 0xE4, 0x68, 0x59, 0xE4, 0x57, 0x27, 0x97, 0x71, 0x71, 0x92, 0x2F, 0x40, 0x74, 0x0D,
+ 0x39, 0x6B, 0x10, 0xF6, 0x26, 0x3E, 0x7B, 0x36, 0x3E, 0xAC, 0x3F, 0xFE, 0xF8, 0xE9, 0x1F, 0x3F,
+ 0x11, 0xE3, 0xC3, 0xA7, 0x1F, 0xED, 0x67, 0xF5, 0xC7, 0x9F, 0x7C, 0xF4, 0xF4, 0x90, 0xD8, 0xD5,
+ 0x02, 0xA3, 0xF2, 0xA7, 0x84, 0xF8, 0x6E, 0xC7, 0x4B, 0xC6, 0x7D, 0xF5, 0xDB, 0x83, 0x09, 0xD6,
+ 0x03, 0x3F, 0x8F, 0x68, 0x93, 0xDB, 0x94, 0x83, 0xB3, 0xCD, 0x67, 0xF8, 0x87, 0x66, 0x04, 0xEC,
+ 0x42, 0xDA, 0x1E, 0x81, 0xEE, 0xE5, 0xC4, 0x3B, 0x3B, 0xF4, 0x48, 0xB6, 0x87, 0xC0, 0x66, 0xB9,
+ 0x5C, 0x2E, 0xBC, 0x50, 0x8F, 0xE8, 0x50, 0x80, 0x85, 0x1F, 0x62, 0xC2, 0x68, 0x90, 0x73, 0xD5,
+ 0xBD, 0xE7, 0x95, 0xAF, 0x4F, 0x1E, 0xA4, 0x1A, 0xA9, 0xA5, 0x3F, 0xFE, 0x04, 0x59, 0xF7, 0xBC,
+ 0x50, 0x66, 0x07, 0x9B, 0x9E, 0xB8, 0x8C, 0xE8, 0x1B, 0xE2, 0x68, 0xE3, 0x24, 0x3A, 0x4F, 0xFC,
+ 0xAE, 0x54, 0x64, 0x70, 0xC8, 0xB2, 0x45, 0x7D, 0xC8, 0x90, 0x90, 0x32, 0x4D, 0x56, 0x0B, 0x48,
+ 0xEA, 0xA8, 0x63, 0x52, 0x7A, 0xF9, 0x75, 0x7C, 0x95, 0xB4, 0x08, 0x01, 0x71, 0xBF, 0x48, 0x8B,
+ 0xD0, 0xB3, 0x82, 0x3A, 0x4F, 0x64, 0x04, 0xEB, 0x10, 0x71, 0x5D, 0x80, 0xA7, 0xF2, 0x40, 0x29,
+ 0x54, 0xC5, 0xDD, 0xFD, 0x17, 0x5A, 0xC6, 0x11, 0x7D, 0x23, 0x89, 0xD2, 0xF5, 0x88, 0xF7, 0x28,
+ 0x95, 0x28, 0x03, 0xD1, 0xFC, 0xAA, 0xA6, 0xCE, 0xB7, 0x91, 0x16, 0x19, 0xF9, 0xE9, 0x16, 0x32,
+ 0xDF, 0xF9, 0x25, 0xF5, 0xFC, 0x8B, 0xE8, 0x9E, 0xAB, 0x0D, 0x35, 0xED, 0x3A, 0x69, 0x4E, 0xEF,
+ 0x97, 0xB2, 0x55, 0x7C, 0xA8, 0x56, 0x2B, 0x2C, 0x5F, 0x2F, 0xB1, 0xAE, 0x67, 0x93, 0xB9, 0x8A,
+ 0x1B, 0xB4, 0x9D, 0xD0, 0x31, 0xAE, 0xE8, 0x5D, 0xFC, 0x04, 0x47, 0x86, 0xD6, 0xA5, 0x73, 0x7C,
+ 0x6B, 0xE5, 0x8F, 0x65, 0x22, 0x09, 0x6C, 0x51, 0xC8, 0xBA, 0x98, 0xC3, 0x17, 0x25, 0xD3, 0x3D,
+ 0x73, 0xB1, 0x8C, 0x58, 0xC0, 0x97, 0xDF, 0x64, 0x49, 0xF1, 0x42, 0xD3, 0x36, 0x6B, 0x12, 0x51,
+ 0x9A, 0xE1, 0x84, 0x7F, 0x04, 0xBD, 0x2A, 0x25, 0xAF, 0xD3, 0x99, 0xE6, 0x03, 0x8C, 0x60, 0x98,
+ 0x20, 0x78, 0x80, 0xFF, 0xC7, 0xA0, 0x8A, 0xD7, 0xFB, 0xFB, 0x7F, 0x94, 0x3F, 0x63, 0xFE, 0xB4,
+ 0x04, 0x06, 0x71, 0x2D, 0xC4, 0xDD, 0xBE, 0xC1, 0xAC, 0x88, 0x24, 0x02, 0x93, 0x78, 0x22, 0x5E,
+ 0x06, 0xAC, 0x2C, 0x90, 0x59, 0x91, 0xB6, 0x89, 0xFE, 0x0E, 0x46, 0x37, 0x99, 0xD3, 0x24, 0x10,
+ 0x99, 0x92, 0x46, 0x9B, 0xE9, 0x78, 0xC6, 0x79, 0xFE, 0x18, 0x21, 0x0B, 0x87, 0x06, 0x90, 0x07,
+ 0x24, 0x24, 0xB2, 0x55, 0xB2, 0xDF, 0x4F, 0xEF, 0x5E, 0x2D, 0x40, 0xE0, 0x06, 0x8D, 0xA6, 0xE2,
+ 0x51, 0xBA, 0x40, 0x89, 0xD4, 0x44, 0x4A, 0x3A, 0x38, 0xC6, 0x16, 0x95, 0xFC, 0x14, 0xF2, 0x2C,
+ 0x59, 0xB4, 0xB0, 0xEC, 0xA9, 0x6A, 0x7F, 0x9F, 0x16, 0x24, 0xC6, 0xEF, 0xFB, 0xEA, 0xA1, 0x0E,
+ 0x6D, 0xA6, 0x4F, 0x67, 0x3A, 0x5D, 0x03, 0x51, 0x26, 0xDC, 0x2E, 0x96, 0x9F, 0xDE, 0xBD, 0x89,
+ 0x2F, 0x08, 0x34, 0x69, 0x64, 0x82, 0x7B, 0xC8, 0x83, 0xFB, 0x68, 0x86, 0x36, 0x16, 0xCD, 0x9C,
+ 0x27, 0x40, 0xA6, 0x25, 0xF2, 0xD2, 0xAA, 0xEC, 0x48, 0x79, 0x6F, 0x6B, 0x26, 0x27, 0x46, 0x43,
+ 0x5D, 0x45, 0x7B, 0x8B, 0xD1, 0xCF, 0x25, 0x78, 0x96, 0xC1, 0xD7, 0xD3, 0x8A, 0xF6, 0xDF, 0x8C,
+ 0x98, 0xF0, 0x6B, 0x2C, 0xDC, 0x35, 0x84, 0xE5, 0x65, 0x45, 0xFD, 0x42, 0x0C, 0x2F, 0x84, 0x15,
+ 0x30, 0x0F, 0x76, 0xEF, 0x2A, 0xB9, 0x72, 0xF3, 0xA8, 0x22, 0xF8, 0x11, 0xE3, 0x48, 0x2E, 0xE0,
+ 0x5F, 0x74, 0x65, 0x75, 0xFD, 0x0F, 0x15, 0xA4, 0x9C, 0x3E, 0xF2, 0x24, 0xEA, 0x9B, 0x26, 0x17,
+ 0x90, 0xEB, 0xC8, 0xCD, 0x91, 0x39, 0x09, 0x68, 0x03, 0xD3, 0x54, 0x30, 0x19, 0x58, 0xE3, 0xE4,
+ 0x83, 0x96, 0xA9, 0x29, 0x97, 0xF7, 0xD2, 0x85, 0x07, 0x7E, 0xB4, 0x8C, 0x4A, 0x23, 0xE1, 0x28,
+ 0x12, 0xE0, 0xAB, 0x80, 0xF0, 0x50, 0x3B, 0x23, 0x64, 0x47, 0xA7, 0x18, 0x79, 0x1E, 0x81, 0xC4,
+ 0xBE, 0xA4, 0x4E, 0xA8, 0xAD, 0xA1, 0x36, 0x4E, 0x0E, 0x51, 0xD2, 0x0A, 0x6C, 0x52, 0xE4, 0x97,
+ 0x13, 0xEF, 0xD7, 0xDE, 0xB0, 0x0C, 0xBD, 0x90, 0x5B, 0xF6, 0x18, 0x39, 0x0D, 0x6F, 0x13, 0x9F,
+ 0x92, 0x83, 0xA3, 0x79, 0xB4, 0x1A, 0xFD, 0x94, 0xA7, 0x99, 0xEF, 0x09, 0x1C, 0x55, 0x84, 0x26,
+ 0x3A, 0x53, 0xBF, 0x1C, 0xB1, 0xB8, 0xFA, 0x94, 0x4F, 0xAB, 0xBC, 0x78, 0x8E, 0x3D, 0x3C, 0xA7,
+ 0x49, 0xB7, 0x38, 0xE0, 0x6B, 0xBF, 0x22, 0x1D, 0xD9, 0x16, 0x4A, 0x2B, 0xEC, 0xF0, 0xBB, 0x7B,
+ 0x92, 0x6B, 0x9D, 0xEE, 0xEF, 0x6B, 0x85, 0x41, 0x6B, 0x88, 0xD8, 0xB9, 0x86, 0x57, 0xAB, 0xCC,
+ 0x50, 0x1F, 0x09, 0xEF, 0xD1, 0xD8, 0x0B, 0xD4, 0x36, 0xB6, 0x7B, 0x9B, 0xB8, 0x8C, 0x7B, 0xCD,
+ 0xBD, 0xEA, 0x23, 0xC5, 0xA4, 0x26, 0xBE, 0xCB, 0x77, 0x2B, 0x7E, 0x95, 0xD7, 0x3F, 0x78, 0x76,
+ 0x0E, 0x7C, 0x03, 0x42, 0xFF, 0x4B, 0x9E, 0x17, 0x4C, 0x3D, 0xFA, 0x0F, 0x76, 0x0C, 0x62, 0x84,
+ 0x51, 0x79, 0x99, 0x2E, 0x2B, 0x3F, 0x80, 0xB2, 0x4A, 0xC1, 0x4A, 0x94, 0x39, 0xD8, 0x84, 0xF0,
+ 0x8E, 0x65, 0x64, 0xA6, 0xA7, 0xB3, 0x88, 0xC4, 0x4A, 0x36, 0x7D, 0x9E, 0x58, 0x01, 0xE9, 0x49,
+ 0x4B, 0x4D, 0x63, 0x11, 0x36, 0xE3, 0x5C, 0x8D, 0xD7, 0x07, 0xB4, 0x2F, 0xEC, 0x7C, 0x69, 0xC1,
+ 0x8C, 0x99, 0xB0, 0xCA, 0x42, 0x0B, 0xE9, 0x6B, 0x76, 0xE8, 0x5C, 0x50, 0x07, 0x78, 0x26, 0x62,
+ 0x84, 0x9D, 0xDE, 0x2E, 0x93, 0x06, 0x0E, 0xD5, 0x2C, 0x65, 0x8D, 0xA9, 0x2C, 0xA2, 0xAC, 0x09,
+ 0x15, 0x05, 0xA0, 0xE2, 0x7C, 0x14, 0x63, 0x39, 0xBE, 0x80, 0x30, 0x6D, 0x05, 0xEC, 0x0A, 0x41,
+ 0x02, 0xD0, 0xAB, 0xAD, 0x6D, 0xDD, 0xA8, 0xAD, 0xC2, 0x1A, 0x52, 0x2D, 0xFB, 0xFB, 0x04, 0xFF,
+ 0x16, 0x5D, 0xCA, 0xEF, 0xCA, 0xF9, 0x46, 0xBB, 0xF9, 0xA6, 0x98, 0x27, 0xAF, 0x48, 0xD9, 0x77,
+ 0x50, 0xB9, 0x5F, 0x84, 0x0B, 0x8A, 0x40, 0xAF, 0x10, 0xE3, 0xD9, 0x40, 0x76, 0x27, 0x43, 0xFF,
+ 0xE8, 0xA0, 0x3A, 0x4D, 0xCF, 0x57, 0xC0, 0xB7, 0x2C, 0xF2, 0x74, 0x98, 0xB6, 0x83, 0xB1, 0x91,
+ 0x71, 0x4C, 0xC6, 0xE1, 0xC1, 0xD8, 0xF6, 0x12, 0xB4, 0x81, 0x3D, 0xB8, 0x7B, 0xB4, 0x81, 0x44,
+ 0x5E, 0x3D, 0x78, 0x64, 0x52, 0x87, 0x09, 0xB7, 0x53, 0x6B, 0xCE, 0x5C, 0x5E, 0xA2, 0x4F, 0x9D,
+ 0x7A, 0xCD, 0x62, 0xEF, 0xAC, 0x4D, 0x75, 0xD3, 0x57, 0xED, 0x2A, 0xC5, 0x90, 0xA4, 0xE6, 0xF8,
+ 0xCB, 0x69, 0xAF, 0x01, 0x69, 0x17, 0xEF, 0x19, 0x07, 0xF8, 0xDC, 0x2B, 0x56, 0x50, 0x4D, 0x92,
+ 0x06, 0x80, 0x0C, 0xC6, 0x0D, 0x3A, 0x61, 0xE2, 0xAD, 0xE2, 0xF3, 0x64, 0x25, 0x73, 0xDA, 0xB0,
+ 0x53, 0xA6, 0x51, 0x81, 0x2D, 0x48, 0x7D, 0x0B, 0x3B, 0x9F, 0x69, 0xF9, 0x42, 0x45, 0xC8, 0x91,
+ 0xB8, 0x31, 0x40, 0x98, 0x03, 0x92, 0x2F, 0xD3, 0x16, 0xE8, 0x2B, 0x6D, 0x5B, 0x47, 0x37, 0x6D,
+ 0x5A, 0x6B, 0x9E, 0xAF, 0x13, 0x3F, 0x36, 0xE3, 0x5E, 0x39, 0x94, 0x50, 0x6E, 0x62, 0xF3, 0x68,
+ 0x98, 0x0B, 0x95, 0xD4, 0x95, 0x3A, 0xC6, 0xA4, 0x69, 0x36, 0xF4, 0x42, 0x1E, 0x40, 0xAA, 0x55,
+ 0x34, 0x41, 0x3D, 0x05, 0xA8, 0x03, 0xBE, 0xA3, 0x02, 0xB2, 0x2B, 0x1C, 0x05, 0x52, 0x20, 0x00,
+ 0xA1, 0x2C, 0xFD, 0x50, 0x18, 0x44, 0x08, 0xFE, 0xB5, 0x5D, 0xBA, 0xB3, 0x9B, 0x9E, 0xFB, 0xDE,
+ 0xA7, 0x84, 0xEF, 0x3D, 0xF1, 0x68, 0x9C, 0x5B, 0x12, 0x48, 0xE0, 0xC8, 0x05, 0x64, 0x46, 0x65,
+ 0x32, 0x52, 0x52, 0xA7, 0x88, 0x45, 0xE2, 0x25, 0x4D, 0xDF, 0xF7, 0x5F, 0x7D, 0x19, 0x75, 0xE1,
+ 0x89, 0xA7, 0x28, 0x43, 0x15, 0xE5, 0x1A, 0x18, 0xF0, 0x2F, 0xAF, 0x5F, 0x89, 0x8C, 0xC5, 0x93,
+ 0x2D, 0x5A, 0x85, 0x4E, 0x0F, 0x2D, 0x94, 0xD1, 0xCD, 0x6B, 0x91, 0xEE, 0x0F, 0xF2, 0xD0, 0x41,
+ 0x1E, 0x52, 0xBB, 0x18, 0xF8, 0x04, 0xF8, 0x11, 0xC7, 0x41, 0xCC, 0xC7, 0x1B, 0xEE, 0x51, 0x52,
+ 0xE9, 0xDA, 0xBA, 0xFD, 0x20, 0x84, 0x0B, 0x6A, 0xA8, 0xDB, 0x6A, 0xB8, 0x36, 0x42, 0xBF, 0x41,
+ 0x74, 0xB2, 0xBF, 0x4F, 0x24, 0x4A, 0xE1, 0x6C, 0xFA, 0xA2, 0xDD, 0x2B, 0xA6, 0x37, 0xFC, 0x93,
+ 0xA8, 0xE8, 0xF4, 0x17, 0x7A, 0xF2, 0x41, 0xEA, 0x9F, 0x80, 0xCA, 0xE4, 0x9A, 0xB0, 0xCF, 0x4F,
+ 0x46, 0x98, 0xDF, 0x18, 0x32, 0x93, 0xBF, 0xA6, 0xC9, 0x0D, 0x36, 0x08, 0xD9, 0x05, 0x20, 0x31,
+ 0xCA, 0x28, 0x75, 0x14, 0x2F, 0x16, 0x2F, 0xAF, 0x51, 0xEE, 0xCB, 0xB4, 0xAC, 0x12, 0xF4, 0x6A,
+ 0xD2, 0x8D, 0x22, 0x3B, 0x89, 0x55, 0x1E, 0xE3, 0x28, 0xCC, 0x61, 0xA1, 0x31, 0x0E, 0xC2, 0x8C,
+ 0x30, 0x1B, 0x30, 0x3E, 0xE7, 0x42, 0x85, 0xEE, 0xA7, 0xEF, 0xE5, 0x99, 0xCD, 0x8E, 0xD3, 0x4B,
+ 0x1D, 0xC4, 0xD1, 0xBC, 0x8F, 0x0E, 0xDF, 0x8B, 0x5D, 0x25, 0x38, 0xE2, 0x1B, 0x9F, 0x1D, 0x9C,
+ 0xBF, 0x48, 0xAF, 0x3D, 0x54, 0xD9, 0x0B, 0x32, 0x9D, 0xB3, 0x13, 0xBB, 0xB7, 0x1B, 0xE9, 0xAB,
+ 0xD3, 0x79, 0x4F, 0x1F, 0x1F, 0x7B, 0x5C, 0xA7, 0x02, 0xE9, 0x2D, 0x75, 0x37, 0xD6, 0x87, 0x68,
+ 0xB9, 0xA3, 0xCF, 0xE0, 0x4C, 0x34, 0x51, 0x14, 0x81, 0xEF, 0x14, 0x83, 0x36, 0x79, 0x61, 0x92,
+ 0xBD, 0x80, 0x6A, 0xEC, 0x05, 0xE6, 0x5D, 0x75, 0xF7, 0x4E, 0xC0, 0x49, 0x7E, 0x25, 0x27, 0x80,
+ 0x46, 0x3F, 0xD8, 0x41, 0x0F, 0x7A, 0x8F, 0xDD, 0x71, 0xEC, 0x22, 0x05, 0xA3, 0x3F, 0x4B, 0x40,
+ 0x3E, 0xD9, 0x91, 0x2E, 0x4B, 0x32, 0xFD, 0xFA, 0xA1, 0x4B, 0x06, 0xBA, 0xF6, 0x54, 0x0C, 0x5A,
+ 0x15, 0xCA, 0xCD, 0xD1, 0x17, 0xEB, 0x9F, 0xDA, 0x6E, 0xDA, 0xC6, 0x26, 0xFE, 0xF9, 0x68, 0x99,
+ 0xAE, 0x20, 0xFF, 0x1D, 0xBD, 0x7A, 0xD1, 0xB7, 0x85, 0x0D, 0xFD, 0x52, 0x81, 0x70, 0xB1, 0xBA,
+ 0xF0, 0x9E, 0x0E, 0xF6, 0x51, 0x7B, 0x12, 0x2F, 0x0A, 0x6A, 0x23, 0x5B, 0x34, 0x5B, 0x10, 0x15,
+ 0x13, 0xA0, 0x7D, 0x40, 0x55, 0xB5, 0x28, 0xFA, 0xFD, 0xFD, 0x97, 0xE6, 0xC4, 0x6E, 0x13, 0xFB,
+ 0xB6, 0x4B, 0xD9, 0x64, 0x9A, 0x91, 0x32, 0x76, 0xBB, 0x0D, 0xC2, 0x07, 0x47, 0x95, 0x7D, 0xD0,
+ 0xA8, 0xE4, 0xF8, 0x7B, 0xBA, 0xD7, 0x1A, 0xA8, 0x3C, 0xB1, 0x3A, 0x71, 0x72, 0x02, 0x4C, 0xCD,
+ 0xAC, 0x71, 0x82, 0x56, 0x6A, 0xC3, 0x47, 0xE4, 0x7F, 0x73, 0x4A, 0xA4, 0xC2, 0xB7, 0x67, 0x62,
+ 0xA4, 0x25, 0x0D, 0xD7, 0x08, 0x74, 0x94, 0xEF, 0xE8, 0x20, 0x61, 0x26, 0xD3, 0x31, 0xCD, 0x97,
+ 0x80, 0x72, 0x3E, 0x82, 0x22, 0xA2, 0x0B, 0x44, 0xA8, 0xD5, 0xE1, 0x5E, 0xA1, 0x44, 0x95, 0x8C,
+ 0xEB, 0xBF, 0xD7, 0x84, 0xA2, 0x92, 0x79, 0xC5, 0xF4, 0x7C, 0x40, 0xAE, 0x17, 0xF5, 0xEF, 0xDC,
+ 0x49, 0x9F, 0x54, 0x63, 0xE7, 0x54, 0x75, 0x8B, 0x77, 0xA3, 0xD5, 0x80, 0x42, 0x66, 0xAC, 0x90,
+ 0xDE, 0x41, 0x5A, 0x89, 0xD6, 0xEB, 0xF6, 0xF2, 0xF3, 0xC2, 0x51, 0x37, 0xF5, 0x57, 0x2E, 0xD7,
+ 0x80, 0x30, 0x04, 0x0F, 0xFC, 0x5E, 0x53, 0x85, 0x39, 0x6B, 0xB9, 0x82, 0x31, 0x81, 0x82, 0x39,
+ 0x6E, 0x8C, 0x36, 0x2A, 0x73, 0x54, 0x51, 0x9A, 0x58, 0x30, 0xF0, 0xC2, 0x72, 0xD1, 0x68, 0x37,
+ 0xC3, 0xF9, 0x8B, 0x81, 0xA9, 0x59, 0xFC, 0xA5, 0x5A, 0x9D, 0x9D, 0x59, 0x58, 0xB9, 0x5C, 0xD2,
+ 0xC0, 0xAF, 0xE9, 0x8F, 0x64, 0x4A, 0x0D, 0x72, 0xEB, 0x4C, 0x21, 0xF3, 0xA4, 0xD8, 0x66, 0x5D,
+ 0x35, 0x6E, 0x17, 0x9D, 0x65, 0x38, 0xF3, 0xE8, 0x50, 0x8F, 0xBC, 0xE3, 0x78, 0x0F, 0xC8, 0xED,
+ 0xB7, 0xDE, 0xF0, 0x74, 0xE8, 0xFD, 0xF6, 0xD9, 0xF1, 0x93, 0xF8, 0xD9, 0xB1, 0x14, 0x25, 0xDA,
+ 0xE8, 0x03, 0x92, 0xDC, 0xFD, 0x76, 0xEF, 0xAA, 0x04, 0x7B, 0x91, 0xDF, 0xCC, 0xE3, 0x35, 0x7A,
+ 0x9D, 0x44, 0xBF, 0x45, 0xEE, 0x7C, 0xCD, 0xF4, 0x8E, 0xD6, 0x7C, 0x70, 0xDC, 0x13, 0x19, 0x89,
+ 0x80, 0x8C, 0x7E, 0xE6, 0x89, 0xBE, 0x33, 0x6A, 0xDA, 0xAC, 0xEE, 0x2D, 0xCA, 0xCE, 0x0C, 0x72,
+ 0xDF, 0xDF, 0xBF, 0x96, 0xEB, 0xE3, 0x91, 0x8E, 0x61, 0x16, 0x59, 0xF5, 0x02, 0x89, 0xFB, 0xCF,
+ 0x48, 0x2A, 0xDD, 0x5F, 0xA9, 0xEE, 0x89, 0xAD, 0xAA, 0xAE, 0x55, 0x55, 0xA4, 0xC8, 0x30, 0xF5,
+ 0xF0, 0x0E, 0xA9, 0x49, 0xB4, 0xBB, 0xBB, 0xAE, 0x74, 0xF1, 0xCF, 0x48, 0x8E, 0xBF, 0xAF, 0x36,
+ 0xA4, 0x05, 0xC2, 0xEF, 0x30, 0x6D, 0x9A, 0x6E, 0x0F, 0x82, 0x16, 0x9B, 0x9D, 0xD1, 0x59, 0xC9,
+ 0xF6, 0x05, 0xCD, 0xF3, 0xAF, 0xDA, 0xD1, 0x3A, 0xF2, 0xD3, 0x1C, 0x3F, 0x38, 0x10, 0xCA, 0x23,
+ 0x43, 0x1F, 0x3C, 0x43, 0xA1, 0xD2, 0x56, 0x75, 0xAA, 0xB5, 0x49, 0x3B, 0x4A, 0xC6, 0xBF, 0xE6,
+ 0xC9, 0x18, 0x3E, 0xEE, 0x29, 0x3A, 0xFA, 0xF5, 0x68, 0x48, 0x62, 0xE1, 0x1D, 0x45, 0xCF, 0x20,
+ 0xF5, 0x45, 0x92, 0x59, 0x53, 0x2B, 0x0A, 0x66, 0xEA, 0xA1, 0x05, 0xB3, 0x49, 0x0B, 0x3C, 0x2F,
+ 0x8B, 0x64, 0x89, 0x99, 0xD8, 0x33, 0xF4, 0xFF, 0x6F, 0x75, 0xA8, 0x09, 0xAF, 0xDD, 0x74, 0x03,
+ 0xA1, 0x4F, 0x2C, 0x34, 0x4A, 0x83, 0xD6, 0xDD, 0xEB, 0x76, 0x54, 0xB5, 0x16, 0x0E, 0xFB, 0x98,
+ 0x16, 0x4E, 0xEA, 0xE8, 0xBA, 0xCB, 0xB7, 0x63, 0x9D, 0x5F, 0x78, 0x0F, 0xAD, 0xEB, 0xA2, 0x0F,
+ 0xD4, 0xED, 0x6A, 0x5A, 0xC5, 0x1A, 0x6A, 0x79, 0x0A, 0x3A, 0xB6, 0x77, 0x25, 0x93, 0x8C, 0x07,
+ 0xD9, 0xA9, 0xC9, 0x26, 0xA1, 0x1F, 0xA1, 0x9E, 0x0B, 0xD4, 0xD4, 0x41, 0x03, 0x66, 0xC6, 0x06,
+ 0x87, 0xBB, 0x9B, 0x41, 0xA6, 0x5F, 0xD8, 0x4E, 0x5F, 0x35, 0x8F, 0x45, 0x78, 0xEB, 0x80, 0x80,
+ 0x18, 0x3D, 0x0E, 0x69, 0xED, 0x03, 0xC2, 0x6A, 0x57, 0x24, 0xCA, 0x48, 0x4A, 0x9D, 0x5F, 0x63,
+ 0xB8, 0x79, 0x14, 0xEB, 0xA4, 0xBA, 0x8E, 0x47, 0x37, 0xC9, 0xF9, 0xBB, 0xB4, 0xFA, 0xAA, 0x99,
+ 0x97, 0x12, 0xAE, 0xF2, 0x7F, 0xF4, 0xC4, 0xE6, 0x7D, 0x39, 0xCB, 0x56, 0x24, 0xA1, 0xCC, 0x16,
+ 0xF4, 0x2D, 0x68, 0x56, 0xA0, 0xA8, 0xCD, 0x18, 0x8F, 0x70, 0xFE, 0x68, 0xAE, 0x2D, 0x01, 0x59,
+ 0x13, 0x67, 0xBF, 0xA6, 0xE5, 0x80, 0xB6, 0x28, 0x8F, 0xAC, 0x54, 0x23, 0x1B, 0x44, 0x50, 0x5E,
+ 0x12, 0x54, 0x5F, 0x47, 0xD7, 0x66, 0xC2, 0x1C, 0x4D, 0xC9, 0xB5, 0x92, 0x90, 0xD5, 0x44, 0xE7,
+ 0x42, 0x3C, 0xD7, 0x97, 0xA7, 0x74, 0xF3, 0x54, 0x7A, 0x3E, 0x62, 0x18, 0xF3, 0x5C, 0x11, 0xE3,
+ 0xAD, 0xB9, 0xAA, 0x6F, 0xF3, 0x32, 0xA5, 0x6E, 0xC3, 0x96, 0x99, 0x58, 0x6B, 0x27, 0x5B, 0x56,
+ 0xC1, 0xFA, 0xA8, 0x0C, 0x26, 0x7D, 0x92, 0xF1, 0x3F, 0x36, 0xA4, 0x2F, 0x93, 0xA4, 0xCD, 0x5D,
+ 0x85, 0x24, 0xA5, 0xA9, 0x9A, 0x82, 0xA3, 0x23, 0x47, 0x13, 0x8F, 0x99, 0x84, 0xD9, 0x12, 0xFE,
+ 0x8E, 0x07, 0x2E, 0x07, 0x47, 0xB1, 0x99, 0x69, 0x7A, 0x62, 0x83, 0x7E, 0x01, 0x99, 0xE3, 0x8E,
+ 0xAE, 0x93, 0x30, 0xE8, 0x93, 0xFD, 0x9D, 0xA9, 0x28, 0x4A, 0xF2, 0xFD, 0xCE, 0x39, 0x5B, 0x29,
+ 0x99, 0x4F, 0x15, 0xB9, 0x9D, 0x0C, 0x28, 0xC5, 0xA1, 0x7A, 0x06, 0x87, 0x47, 0x46, 0x36, 0x26,
+ 0x7E, 0x82, 0x05, 0x4D, 0xA7, 0x9E, 0xC4, 0xD5, 0xEA, 0xAF, 0x68, 0x17, 0x1C, 0x1E, 0xC9, 0x49,
+ 0x1A, 0xEC, 0xEC, 0xD3, 0xC1, 0xA0, 0xDA, 0x95, 0x64, 0x88, 0x63, 0x48, 0x64, 0xC7, 0xC4, 0x96,
+ 0xF6, 0x31, 0xDF, 0x51, 0xE4, 0x57, 0xED, 0xD8, 0x2A, 0x98, 0xEC, 0x6C, 0x0F, 0x83, 0x0D, 0xC7,
+ 0x01, 0x66, 0x77, 0xC1, 0xD6, 0x8A, 0x2F, 0x12, 0x62, 0x43, 0x49, 0x9D, 0xB3, 0xB3, 0x1B, 0x52,
+ 0x9A, 0x92, 0x4D, 0x30, 0xBA, 0x13, 0xB4, 0xD8, 0x6C, 0x8C, 0x25, 0xCF, 0x77, 0xFE, 0x1A, 0xC6,
+ 0x47, 0x64, 0x51, 0x54, 0x71, 0x9E, 0x6A, 0x47, 0x1E, 0xF4, 0x6B, 0x1C, 0x6E, 0x26, 0xDF, 0xFA,
+ 0x1B, 0x64, 0x3F, 0xA0, 0x1F, 0x74, 0xE6, 0x30, 0xFC, 0xDD, 0x7E, 0x46, 0x65, 0xC7, 0x58, 0x9A,
+ 0x0F, 0x9F, 0x52, 0x63, 0x85, 0x64, 0x17, 0x8C, 0x69, 0x39, 0xFB, 0x49, 0xA6, 0x3E, 0xC9, 0x8C,
+ 0x08, 0x9E, 0x6A, 0xC6, 0x0A, 0x90, 0x14, 0xA3, 0xCE, 0x75, 0x5D, 0x34, 0x1A, 0xDD, 0x63, 0xF4,
+ 0x2A, 0xA5, 0x70, 0xDE, 0xD7, 0x3D, 0x2A, 0x9A, 0xA2, 0x17, 0xAA, 0xA4, 0x11, 0x43, 0x1E, 0x81,
+ 0x15, 0x39, 0x32, 0xB2, 0x42, 0x17, 0x6E, 0xE2, 0xD1, 0x26, 0x93, 0x52, 0xDC, 0x8C, 0x72, 0x55,
+ 0xFD, 0xB9, 0x4A, 0x27, 0x97, 0xCA, 0x11, 0x43, 0xE2, 0x89, 0x96, 0x4A, 0xFC, 0x04, 0xA0, 0xCD,
+ 0x0D, 0x29, 0x39, 0x59, 0xCB, 0x34, 0xC1, 0x29, 0xA1, 0xCC, 0xB6, 0xA6, 0x1E, 0x97, 0x32, 0x88,
+ 0x7E, 0x1F, 0x02, 0x45, 0x9C, 0x80, 0xBC, 0x4B, 0x34, 0x8E, 0xEB, 0xD7, 0x24, 0x96, 0xD4, 0x79,
+ 0x92, 0xD2, 0xF2, 0x1F, 0x24, 0x50, 0x91, 0x0E, 0xC6, 0x6C, 0x2F, 0x83, 0xD4, 0x1B, 0x75, 0x30,
+ 0x2B, 0x68, 0xCE, 0x96, 0x4E, 0xA3, 0xC4, 0x14, 0x97, 0x46, 0xF7, 0xD0, 0xD5, 0x72, 0x04, 0x24,
+ 0x77, 0xE6, 0xF5, 0xB3, 0x48, 0xB0, 0x62, 0x42, 0x1B, 0x40, 0xDE, 0x83, 0x2A, 0xEB, 0xDA, 0xE2,
+ 0x13, 0xA5, 0xD5, 0xB2, 0x11, 0x06, 0x57, 0xE8, 0x95, 0xE9, 0x91, 0xFA, 0xAB, 0x94, 0xC3, 0x63,
+ 0x56, 0xC3, 0x9D, 0xC8, 0x51, 0x03, 0x2C, 0xF4, 0xE1, 0x43, 0xC3, 0xD7, 0xA8, 0xA5, 0x7F, 0xCA,
+ 0xFA, 0x36, 0x1D, 0xCB, 0x89, 0x78, 0x4E, 0xEE, 0x38, 0x2B, 0xD5, 0xC2, 0x42, 0x90, 0x56, 0x0D,
+ 0x0F, 0x96, 0x55, 0xA8, 0xA1, 0x21, 0xF3, 0x6E, 0x99, 0x51, 0xCD, 0xA4, 0x80, 0xFB, 0x85, 0x9C,
+ 0x2A, 0x37, 0xA7, 0x68, 0xE5, 0x0C, 0x26, 0xD2, 0xDA, 0x6D, 0xF0, 0x52, 0xF3, 0x3D, 0x1A, 0x7A,
+ 0xAC, 0x75, 0xEB, 0xA4, 0x08, 0x1D, 0x51, 0x0D, 0x2D, 0xD5, 0xCB, 0x49, 0x8B, 0xF1, 0x07, 0xC0,
+ 0xC3, 0x20, 0xAE, 0xCB, 0x10, 0xCB, 0xD5, 0x2C, 0x20, 0xC0, 0x4F, 0xE6, 0xE9, 0x32, 0x85, 0x58,
+ 0xB7, 0x90, 0x5C, 0x61, 0x48, 0x13, 0xCA, 0xC3, 0x4F, 0x4A, 0x90, 0xDE, 0xAE, 0xFE, 0xDA, 0x4E,
+ 0xE0, 0x10, 0x64, 0x6A, 0x4B, 0x6D, 0xC4, 0x45, 0xD8, 0x1C, 0xB6, 0x51, 0xA2, 0x73, 0x5B, 0xE9,
+ 0xF4, 0x0E, 0x4B, 0x73, 0xBB, 0xC7, 0x39, 0xC5, 0xDE, 0x26, 0x2B, 0x92, 0x79, 0x7E, 0x91, 0xA5,
+ 0xFF, 0x48, 0x16, 0x7B, 0x30, 0xC6, 0xC5, 0xFD, 0xA5, 0x12, 0x25, 0x43, 0x28, 0x8D, 0x54, 0x95,
+ 0x9B, 0x2C, 0x05, 0xB1, 0x70, 0x9A, 0x17, 0xBD, 0xB2, 0x44, 0xB0, 0x3D, 0x0A, 0x6F, 0xF0, 0xB6,
+ 0x06, 0x3E, 0x01, 0xCC, 0x25, 0x15, 0xE0, 0xED, 0xC5, 0x86, 0x8C, 0xB5, 0x63, 0x4C, 0x0B, 0xEC,
+ 0xAC, 0x15, 0x6E, 0x84, 0xD5, 0xF8, 0x39, 0xDD, 0xCF, 0x32, 0x9A, 0x76, 0x90, 0x22, 0x9C, 0xE0,
+ 0xFF, 0x14, 0x88, 0x95, 0x66, 0x09, 0x21, 0x67, 0x91, 0x2C, 0x21, 0x30, 0x09, 0x05, 0x69, 0x1F,
+ 0x60, 0xD5, 0xB4, 0xE9, 0x5F, 0xE0, 0xA8, 0x37, 0x48, 0x01, 0xC2, 0x35, 0x65, 0xB4, 0x99, 0xC7,
+ 0x06, 0x3A, 0x37, 0x11, 0x83, 0x25, 0xE9, 0xE3, 0xA3, 0x92, 0x27, 0xFF, 0x4D, 0x72, 0xDB, 0x3F,
+ 0x00, 0xCF, 0x53, 0x03, 0xB0, 0x47, 0x2E, 0x23, 0x28, 0xA9, 0xCA, 0x45, 0x0F, 0x80, 0xE4, 0xFE,
+ 0x28, 0x7F, 0xC6, 0xF4, 0x29, 0x13, 0xBA, 0xC6, 0xA3, 0x7C, 0x49, 0x87, 0xED, 0x4E, 0x32, 0x8D,
+ 0x5E, 0x9B, 0x91, 0x47, 0x2C, 0x36, 0x46, 0x33, 0x6C, 0x63, 0xC2, 0x24, 0xDE, 0x51, 0x72, 0x84,
+ 0x88, 0x86, 0x56, 0x24, 0x1B, 0x46, 0x39, 0xBA, 0x67, 0xB4, 0xBA, 0x1F, 0xC9, 0xA6, 0x7F, 0xE7,
+ 0x6A, 0x6D, 0x65, 0x4F, 0xFF, 0x4A, 0xD0, 0x22, 0xF3, 0x99, 0x79, 0x63, 0x01, 0x84, 0xAC, 0x03,
+ 0x48, 0xC0, 0x6C, 0x62, 0xE1, 0x9F, 0xB3, 0x5C, 0x58, 0x61, 0x99, 0x32, 0xBA, 0x77, 0x74, 0x65,
+ 0xE1, 0xC7, 0x87, 0x42, 0x52, 0xDA, 0xDF, 0x96, 0xC9, 0x06, 0xB6, 0xD8, 0xD8, 0x0B, 0x8C, 0x96,
+ 0xC2, 0xCF, 0x85, 0xDD, 0x1E, 0x64, 0xF1, 0x4D, 0x3C, 0x37, 0xFD, 0x16, 0xC9, 0x8A, 0xAC, 0x52,
+ 0x10, 0xE7, 0x3D, 0xF3, 0xC2, 0x8E, 0x39, 0x82, 0xBA, 0x44, 0x41, 0x06, 0xC8, 0xDE, 0x5E, 0x4F,
+ 0x3A, 0xA2, 0x87, 0x3A, 0x9A, 0x6E, 0x03, 0xA4, 0xF9, 0xA6, 0x54, 0xC3, 0x6F, 0x94, 0xFD, 0xE7,
+ 0xAE, 0x4C, 0x10, 0x18, 0x21, 0xEA, 0x33, 0x16, 0x69, 0x85, 0xF7, 0x6C, 0xDD, 0xD4, 0x27, 0x81,
+ 0x83, 0xAE, 0x3D, 0xA2, 0x3F, 0x2D, 0xF1, 0x16, 0xD4, 0x80, 0x1F, 0x41, 0x53, 0x4A, 0x7F, 0x81,
+ 0x3D, 0xA6, 0xBF, 0xE3, 0xBF, 0x1F, 0xE3, 0xAF, 0xD9, 0x53, 0x36, 0x2B, 0x31, 0x91, 0x0C, 0x84,
+ 0x4F, 0x09, 0x08, 0xB9, 0x20, 0xE9, 0x53, 0x29, 0xC0, 0x5A, 0x47, 0x61, 0x6D, 0x46, 0x7E, 0x17,
+ 0x6C, 0xA5, 0xE1, 0xD4, 0x83, 0x7D, 0x69, 0xE0, 0x18, 0xD4, 0x0F, 0x9B, 0x23, 0x34, 0xC0, 0x49,
+ 0xA6, 0xA6, 0x8F, 0x82, 0x89, 0xEA, 0x9D, 0xDE, 0xD1, 0xF8, 0x3C, 0x84, 0x35, 0x2A, 0x75, 0x36,
+ 0x1A, 0xFA, 0xF4, 0x33, 0xA1, 0x2E, 0x53, 0xF0, 0x13, 0x64, 0x83, 0x34, 0xFC, 0x29, 0x8C, 0xFF,
+ 0xC8, 0xA2, 0x89, 0x2A, 0x93, 0x65, 0x3D, 0xD8, 0x36, 0xE9, 0xAF, 0x80, 0xCA, 0x7E, 0x2C, 0xCB,
+ 0xFE, 0x7E, 0x86, 0xEE, 0xFF, 0xA1, 0x93, 0x21, 0xC4, 0x0F, 0xC6, 0xD8, 0x6E, 0x71, 0xAB, 0xAD,
+ 0xC4, 0xFA, 0xB6, 0xCE, 0x00, 0xCD, 0xA3, 0x0C, 0xCD, 0x8E, 0x86, 0xB5, 0xCF, 0x47, 0x3C, 0x07,
+ 0x7C, 0x3C, 0xC9, 0x3A, 0x26, 0xB4, 0x13, 0x43, 0x1E, 0xD0, 0x84, 0x72, 0x46, 0xCD, 0x29, 0x0F,
+ 0x81, 0x8D, 0xBF, 0x97, 0xD9, 0x33, 0x3A, 0xDE, 0xAA, 0x08, 0x32, 0x1F, 0x3A, 0x68, 0xE4, 0x47,
+ 0xA6, 0x2F, 0x14, 0xFA, 0x60, 0x98, 0x85, 0x56, 0x70, 0x1E, 0xC0, 0xE2, 0x4C, 0x87, 0x91, 0x8F,
+ 0x1B, 0x8A, 0xF0, 0xC7, 0xCE, 0x21, 0x98, 0x76, 0x6E, 0x2C, 0x73, 0x62, 0xDC, 0xD5, 0xFA, 0x08,
+ 0x64, 0x2B, 0x00, 0x5A, 0x81, 0x10, 0xD9, 0xCF, 0xBD, 0x5F, 0xDA, 0xDB, 0xAB, 0x06, 0x54, 0xE2,
+ 0x2C, 0x4B, 0xBA, 0x5A, 0xEB, 0x9E, 0xDE, 0x2B, 0x94, 0x56, 0xAB, 0xB8, 0xBF, 0xBF, 0x43, 0xC3,
+ 0xA8, 0xE5, 0xC4, 0x2C, 0xDD, 0xEA, 0xE9, 0xD7, 0xD5, 0x34, 0x61, 0xF2, 0xC0, 0xB5, 0xB8, 0xAA,
+ 0x1A, 0xB6, 0x5E, 0xFE, 0x5B, 0x63, 0x9C, 0x88, 0xAC, 0x3E, 0x87, 0xC9, 0x42, 0x8D, 0x26, 0xF5,
+ 0x0A, 0xA7, 0x5A, 0xDF, 0xD5, 0x4E, 0x5E, 0x83, 0x3E, 0xBC, 0x36, 0xB7, 0x02, 0x31, 0xE7, 0xA3,
+ 0xAE, 0xDF, 0x2F, 0x0C, 0x46, 0x81, 0x3E, 0xA5, 0x84, 0x17, 0xF0, 0x5E, 0xDB, 0x62, 0xC3, 0x34,
+ 0xF7, 0x2E, 0x4B, 0x6F, 0x1F, 0xD0, 0xC8, 0x2A, 0x92, 0x00, 0x03, 0x00, 0x81, 0xD7, 0x32, 0xBE,
+ 0x27, 0xB6, 0x8E, 0x78, 0x9F, 0x70, 0x50, 0xD0, 0x74, 0x0C, 0x09, 0xAF, 0x7B, 0x32, 0x6A, 0x52,
+ 0x11, 0xF2, 0x0C, 0x91, 0x43, 0x7D, 0x0E, 0xF8, 0xF3, 0xAD, 0xFA, 0xC4, 0xD5, 0x17, 0xBE, 0xDD,
+ 0x67, 0x20, 0x2D, 0x0D, 0x42, 0xEF, 0xB1, 0x4D, 0x3C, 0x18, 0x1F, 0x37, 0xD3, 0x1E, 0xD9, 0xB4,
+ 0x4A, 0x01, 0xD4, 0x41, 0xAA, 0xA1, 0x51, 0x36, 0xF5, 0x4F, 0x95, 0x05, 0x65, 0x7D, 0x42, 0x18,
+ 0xD6, 0xF0, 0xE1, 0x53, 0x41, 0x78, 0x83, 0x91, 0x47, 0xA3, 0xD2, 0x5A, 0x96, 0x20, 0xA0, 0x97,
+ 0x18, 0xBF, 0x32, 0xB0, 0xAA, 0xEB, 0x1E, 0x8E, 0xB9, 0x76, 0x88, 0xBB, 0xB0, 0x90, 0xDB, 0x0E,
+ 0xB6, 0xB9, 0x14, 0x44, 0xAE, 0xE0, 0x9E, 0xAD, 0x9C, 0xAE, 0xBB, 0x88, 0x91, 0x0B, 0xC6, 0x7A,
+ 0xE9, 0x40, 0x3D, 0xAC, 0xBB, 0x3D, 0xB2, 0x6E, 0x74, 0xE3, 0x0F, 0x7E, 0x47, 0xD6, 0xDE, 0x9E,
+ 0xB2, 0xDD, 0x64, 0x70, 0xD6, 0xB3, 0x4B, 0x07, 0xDE, 0x85, 0x9C, 0x9F, 0xEB, 0x49, 0x8F, 0x05,
+ 0xDB, 0xC0, 0xE5, 0x16, 0xB6, 0xBB, 0xAE, 0x09, 0x58, 0xFB, 0xAC, 0xE8, 0x0E, 0xED, 0x5E, 0x4D,
+ 0x3C, 0xE7, 0xC4, 0xF3, 0x7A, 0x0E, 0x81, 0x79, 0x93, 0x0B, 0x59, 0x46, 0xB7, 0xBB, 0x37, 0x8B,
+ 0x58, 0xE3, 0x4E, 0x03, 0xDD, 0xDA, 0x14, 0x0B, 0xBA, 0xBB, 0x45, 0x56, 0x3C, 0x7C, 0x42, 0xDF,
+ 0x69, 0x72, 0x02, 0x74, 0x45, 0x6C, 0x18, 0x8B, 0x18, 0x17, 0x9E, 0x56, 0x2C, 0xA2, 0xBF, 0x9D,
+ 0xC4, 0xBB, 0xB7, 0xDF, 0x32, 0xA4, 0x91, 0xC7, 0x6D, 0x7A, 0x18, 0xF5, 0x6F, 0xA2, 0x15, 0xA6,
+ 0x2A, 0x5B, 0xF1, 0xC5, 0xD0, 0x4B, 0xB4, 0x8B, 0x1B, 0x5D, 0x8D, 0xE1, 0x6C, 0xCD, 0xF6, 0x27,
+ 0x5B, 0xAA, 0x68, 0x7A, 0x35, 0x99, 0x3B, 0x27, 0x7E, 0x38, 0x1F, 0xAD, 0x62, 0x15, 0x9E, 0x89,
+ 0xAB, 0xFD, 0xFD, 0x35, 0x04, 0x1A, 0x11, 0xAC, 0x83, 0xFC, 0x82, 0x6E, 0x3B, 0x40, 0x89, 0x80,
+ 0x2E, 0xCE, 0x03, 0x98, 0xA1, 0xD0, 0xB5, 0x31, 0xFC, 0xD0, 0xCD, 0xB1, 0x60, 0x1A, 0x2B, 0x32,
+ 0xEC, 0xD5, 0x0B, 0x8A, 0xCF, 0x9D, 0x6F, 0x95, 0xE1, 0x12, 0xF1, 0xB8, 0x79, 0x07, 0x14, 0x88,
+ 0x6E, 0xBD, 0x03, 0x61, 0x49, 0x86, 0x66, 0xF4, 0xF3, 0x74, 0x06, 0xAE, 0xAD, 0x84, 0x10, 0xC5,
+ 0xDA, 0xF7, 0xD1, 0xBD, 0x32, 0x33, 0x1D, 0xC3, 0x21, 0x12, 0x63, 0xFC, 0x37, 0x5D, 0x51, 0xD5,
+ 0xD0, 0x68, 0x47, 0x87, 0xD8, 0x9F, 0x1B, 0xB2, 0xD2, 0xF7, 0xD9, 0x6A, 0xAD, 0x39, 0x13, 0xFB,
+ 0xFB, 0xC3, 0xE1, 0x02, 0xD9, 0xA5, 0x0C, 0x3F, 0x45, 0xC3, 0xD1, 0xF4, 0x1D, 0x16, 0x77, 0x31,
+ 0x3B, 0x92, 0xD7, 0x7B, 0x0C, 0xE5, 0x02, 0x16, 0x15, 0xD5, 0x35, 0x47, 0x96, 0xFC, 0x97, 0x46,
+ 0x26, 0xD8, 0x26, 0x62, 0x11, 0x7C, 0xF8, 0x28, 0x7E, 0xE1, 0x7A, 0xAB, 0x61, 0xF2, 0x20, 0x64,
+ 0xEF, 0xE3, 0x7F, 0xA3, 0xE7, 0x34, 0x35, 0x0B, 0xF4, 0x96, 0x67, 0xAB, 0x79, 0x9B, 0xC9, 0x5F,
+ 0x1C, 0x44, 0xD7, 0xD4, 0xF4, 0x05, 0xD8, 0xB4, 0xDF, 0x5C, 0x44, 0xD1, 0x21, 0xDD, 0x24, 0x8E,
+ 0x16, 0x4F, 0x2E, 0xB6, 0xDB, 0x9E, 0x43, 0x57, 0xE4, 0xFA, 0xD8, 0x8D, 0xC1, 0xDF, 0xAC, 0x99,
+ 0x50, 0xC3, 0x45, 0x5E, 0xB4, 0x7C, 0x4E, 0xE2, 0x4D, 0x49, 0x0B, 0x21, 0xA2, 0x39, 0x32, 0x97,
+ 0x84, 0x00, 0xBA, 0x56, 0x96, 0x0A, 0xA0, 0xF4, 0x65, 0x05, 0x92, 0xCA, 0xD7, 0x7B, 0x9D, 0x46,
+ 0x36, 0x89, 0xFD, 0x1C, 0xD2, 0x8C, 0xE3, 0x58, 0x5B, 0x77, 0x02, 0x1B, 0x4D, 0x13, 0x20, 0x16,
+ 0x60, 0x53, 0xDC, 0xC2, 0x71, 0xDB, 0x6A, 0x5D, 0xEF, 0xF0, 0x93, 0x36, 0xFB, 0xF4, 0x80, 0x39,
+ 0x47, 0xF2, 0xA0, 0x11, 0xC7, 0xB7, 0x48, 0x2F, 0xE8, 0x12, 0x92, 0x31, 0xE1, 0xE0, 0xAF, 0x6D,
+ 0xD0, 0x77, 0x9E, 0x52, 0x65, 0x38, 0xDC, 0x29, 0x35, 0xC6, 0xC4, 0xA9, 0x99, 0x09, 0x71, 0xB5,
+ 0xAD, 0x0A, 0x57, 0x5D, 0x55, 0x8B, 0xD4, 0x52, 0xD1, 0x1F, 0xF0, 0xA6, 0x7E, 0xD2, 0xB6, 0x47,
+ 0x33, 0x93, 0x51, 0x62, 0x32, 0xDA, 0x23, 0x68, 0x58, 0x9C, 0x46, 0xA5, 0x66, 0xFB, 0x0B, 0x31,
+ 0xE5, 0x25, 0x4E, 0x9A, 0xA3, 0x89, 0x31, 0x1A, 0x40, 0x0E, 0xE0, 0x62, 0x26, 0x49, 0x94, 0x98,
+ 0x87, 0x43, 0x3F, 0x29, 0x99, 0xA3, 0xEC, 0xBA, 0x4D, 0x56, 0x30, 0x2D, 0x23, 0x20, 0x95, 0x53,
+ 0x52, 0x05, 0x1C, 0x89, 0x82, 0x23, 0xF9, 0x73, 0x90, 0x4A, 0x98, 0x26, 0x2D, 0x21, 0x56, 0xC0,
+ 0x1D, 0xE3, 0x03, 0xD6, 0x45, 0x96, 0x61, 0x4F, 0x34, 0x9F, 0x4E, 0x15, 0x28, 0x36, 0x7D, 0x57,
+ 0x2D, 0x90, 0xDE, 0xB5, 0x09, 0xE2, 0x6E, 0xC5, 0x74, 0xC4, 0x35, 0xF8, 0x9C, 0xBA, 0x26, 0x06,
+ 0xC6, 0x9E, 0x6E, 0x15, 0xF7, 0x75, 0x15, 0x67, 0x17, 0x8D, 0x66, 0xEC, 0x88, 0xFF, 0xAA, 0xC8,
+ 0x3F, 0xA6, 0x0A, 0x76, 0x41, 0x2C, 0x95, 0x27, 0x78, 0x85, 0x2C, 0x15, 0x62, 0x9F, 0x07, 0x29,
+ 0x33, 0xD1, 0xD5, 0xAF, 0x2D, 0xF2, 0x7B, 0x96, 0x3D, 0x12, 0x7B, 0xCE, 0x35, 0xB5, 0x09, 0x93,
+ 0xDB, 0xAB, 0x55, 0x48, 0x09, 0xD4, 0x81, 0x76, 0x9A, 0x8C, 0x57, 0x47, 0x00, 0x2A, 0x69, 0x0B,
+ 0x0A, 0x1C, 0x0F, 0x01, 0x66, 0xD0, 0x19, 0x9F, 0xD2, 0x5B, 0x09, 0x0A, 0xC4, 0xF5, 0xB9, 0x82,
+ 0xAA, 0xB6, 0x81, 0x5D, 0x60, 0xC4, 0xA0, 0x34, 0x53, 0xB8, 0x1D, 0x80, 0xF6, 0x7B, 0xE8, 0x3E,
+ 0x50, 0xBC, 0xF9, 0x3C, 0x96, 0x42, 0x59, 0x1B, 0xA6, 0x7D, 0x78, 0xE9, 0x2A, 0xD6, 0x8D, 0x5D,
+ 0x3E, 0xB7, 0x91, 0x2E, 0xC0, 0xAF, 0xE5, 0x79, 0xB3, 0x3E, 0x47, 0x58, 0x8C, 0xDB, 0xB8, 0x7C,
+ 0x07, 0x6A, 0x57, 0xFA, 0xC9, 0x28, 0x9E, 0x13, 0xAF, 0x67, 0x6D, 0x7E, 0x06, 0x27, 0xD4, 0xE4,
+ 0x67, 0x7C, 0x71, 0xAA, 0xB6, 0x61, 0x4C, 0x04, 0x4E, 0xC3, 0x81, 0x2F, 0x4D, 0xDF, 0x68, 0x16,
+ 0x49, 0x2D, 0x54, 0xD7, 0xFF, 0x44, 0x44, 0x7C, 0xCE, 0xB6, 0x81, 0x20, 0xF8, 0x94, 0x3E, 0x22,
+ 0x84, 0x2D, 0x1C, 0x8C, 0x78, 0x04, 0xC4, 0x53, 0xF6, 0x1B, 0xD2, 0x00, 0xA5, 0xE0, 0x0A, 0x7F,
+ 0xB1, 0x35, 0x9E, 0x63, 0x8C, 0x47, 0xBD, 0x48, 0x46, 0xAA, 0x26, 0xC0, 0x94, 0x54, 0x30, 0x39,
+ 0x49, 0x5A, 0x05, 0xB9, 0x15, 0x3A, 0xD4, 0x37, 0xF8, 0xA6, 0x0D, 0x9E, 0xF3, 0x65, 0x2B, 0xE0,
+ 0x41, 0x09, 0x65, 0xC6, 0x6F, 0x6B, 0x4D, 0xE8, 0xC6, 0x6E, 0xA3, 0xCA, 0x0F, 0xE2, 0xFC, 0x01,
+ 0xA0, 0x16, 0x24, 0x8E, 0x3F, 0xE9, 0xBB, 0xCF, 0x2B, 0xFB, 0xD0, 0xD3, 0xDB, 0x81, 0x39, 0x13,
+ 0x46, 0xDC, 0x3A, 0x2B, 0xA6, 0xC9, 0xBD, 0x47, 0x52, 0xF4, 0x8D, 0xED, 0x4F, 0x72, 0xC7, 0x99,
+ 0x39, 0xA5, 0xDC, 0x3C, 0x81, 0x7D, 0x99, 0xBF, 0xEB, 0xC9, 0x2C, 0xAD, 0x1D, 0xFF, 0xC3, 0x65,
+ 0x72, 0x6C, 0x26, 0x35, 0xD0, 0x38, 0x51, 0xF0, 0x26, 0xC1, 0x57, 0x78, 0xBA, 0x9B, 0xF9, 0xC3,
+ 0xCD, 0x3E, 0x3D, 0xAA, 0xC1, 0xD6, 0x0F, 0xE0, 0x95, 0xEC, 0x82, 0x4F, 0x1D, 0xBD, 0xE8, 0x2A,
+ 0x11, 0xE9, 0xFE, 0xB6, 0x2E, 0xD3, 0xDA, 0xE6, 0xD6, 0x39, 0x04, 0xCC, 0x19, 0x3B, 0xCC, 0x1E,
+ 0xD0, 0x33, 0xE3, 0x38, 0x9D, 0xDC, 0x15, 0x7A, 0x4E, 0xE1, 0x21, 0x84, 0xF2, 0x24, 0x3F, 0xEB,
+ 0x1C, 0x5D, 0xDC, 0x3F, 0xA5, 0x7B, 0x33, 0xD9, 0x10, 0xD7, 0x96, 0x28, 0x27, 0xB1, 0xF3, 0x94,
+ 0x77, 0xE7, 0x65, 0x48, 0xDC, 0x80, 0xAC, 0x8E, 0x20, 0xEB, 0x79, 0x1A, 0x24, 0x6D, 0x5B, 0x89,
+ 0x04, 0xE5, 0xC1, 0xDE, 0x3F, 0x54, 0x7C, 0xFC, 0x9E, 0xE2, 0x2B, 0x1E, 0xCA, 0xEE, 0xCB, 0xC5,
+ 0xBA, 0xAF, 0xD5, 0x71, 0x36, 0x41, 0x8F, 0x8F, 0x40, 0xBC, 0x1C, 0x1C, 0x14, 0x47, 0xBA, 0xB2,
+ 0xA2, 0x51, 0xD9, 0xC5, 0x07, 0x56, 0x96, 0x1D, 0x0D, 0x87, 0xC5, 0x71, 0xD5, 0x5B, 0x0B, 0x1D,
+ 0x23, 0x06, 0xCA, 0xC1, 0xDA, 0x44, 0x0E, 0xCC, 0xFF, 0x2C, 0xEE, 0x8B, 0x78, 0x91, 0xE6, 0x10,
+ 0x1C, 0x49, 0x1C, 0x72, 0x9E, 0xDF, 0x52, 0x78, 0x09, 0xF4, 0x4B, 0xBF, 0x6B, 0xB0, 0x9D, 0x37,
+ 0x79, 0xB1, 0xA0, 0x70, 0x7A, 0x15, 0x5F, 0x50, 0xE4, 0x36, 0x70, 0x09, 0xA9, 0x68, 0xC1, 0x06,
+ 0x2A, 0xDA, 0xB4, 0xF3, 0xBE, 0xDC, 0x9C, 0x5F, 0xA5, 0x15, 0xE5, 0x2F, 0x12, 0x10, 0x3D, 0xDD,
+ 0xFC, 0x97, 0x9C, 0xDF, 0x58, 0x94, 0x5E, 0x91, 0xE1, 0xFA, 0xD6, 0x7C, 0xDE, 0x22, 0xD5, 0x0E,
+ 0xB0, 0x82, 0x80, 0xB1, 0x71, 0xE3, 0xC5, 0xF3, 0x8E, 0x30, 0x6F, 0x47, 0xD5, 0x70, 0x08, 0xB5,
+ 0x04, 0x7B, 0x75, 0x90, 0xC2, 0x5F, 0xA3, 0xA0, 0xB0, 0x35, 0xE1, 0xEA, 0x50, 0x29, 0x0C, 0xFD,
+ 0xB4, 0x61, 0x43, 0xE0, 0x42, 0xAC, 0x14, 0xF2, 0x00, 0xAB, 0xB4, 0x02, 0x9D, 0x2B, 0x96, 0xBC,
+ 0xA1, 0x2C, 0x8E, 0x22, 0x00, 0x9E, 0x83, 0x35, 0x72, 0x94, 0x1E, 0x0A, 0xFD, 0x4C, 0xDA, 0xAB,
+ 0xA0, 0xEE, 0xDE, 0xA0, 0x17, 0x1B, 0x66, 0x8C, 0x9A, 0x67, 0x53, 0x5D, 0x2F, 0x03, 0x4D, 0x13,
+ 0xC9, 0x12, 0xF6, 0xB4, 0x7A, 0x90, 0xE1, 0x23, 0xDA, 0x77, 0xCD, 0x6A, 0xA2, 0x9E, 0x36, 0x7A,
+ 0x1A, 0xD9, 0xDF, 0xD7, 0xF5, 0x1B, 0xED, 0xA0, 0x64, 0x25, 0xDE, 0xDF, 0x3F, 0x44, 0x32, 0x89,
+ 0x9E, 0x48, 0x12, 0x3D, 0x31, 0x24, 0x7A, 0xD2, 0x22, 0xD1, 0xED, 0x37, 0x67, 0x10, 0x30, 0xCF,
+ 0x5C, 0x3D, 0x80, 0x40, 0x02, 0xD9, 0x28, 0xCE, 0xB2, 0x23, 0x7D, 0x39, 0x06, 0xCC, 0x4C, 0x3A,
+ 0x9D, 0x33, 0x6B, 0xD5, 0xE0, 0x48, 0x22, 0xE7, 0x72, 0x50, 0x4C, 0x82, 0xA7, 0x02, 0x7F, 0xF8,
+ 0x5E, 0x0C, 0x65, 0x8F, 0xE2, 0x80, 0xE2, 0xBA, 0x03, 0x34, 0x33, 0xE9, 0x78, 0x57, 0x4A, 0x7C,
+ 0x2B, 0x03, 0x19, 0x1F, 0xA7, 0x9A, 0x12, 0xEF, 0x9B, 0x6A, 0x74, 0xA6, 0x6B, 0xF2, 0xCF, 0x8A,
+ 0x39, 0x88, 0xC3, 0x55, 0xC6, 0x9E, 0x73, 0x24, 0x4C, 0xD1, 0x77, 0xDB, 0xE2, 0x9B, 0xC4, 0x37,
+ 0xCE, 0x52, 0x2C, 0xCC, 0x62, 0x09, 0x25, 0x99, 0x7C, 0x28, 0x00, 0x74, 0xD6, 0x3F, 0x8A, 0xBC,
+ 0x99, 0x5F, 0x19, 0xCF, 0x25, 0x3C, 0xEF, 0x25, 0xD3, 0xB8, 0xC4, 0x86, 0xC3, 0xAA, 0x9B, 0xEB,
+ 0x21, 0x56, 0x49, 0xEE, 0xE1, 0x9C, 0xE7, 0xB9, 0x92, 0x1F, 0x65, 0xE0, 0x38, 0x02, 0xB1, 0x5D,
+ 0x38, 0x49, 0xFC, 0x85, 0xB8, 0x24, 0xE1, 0x05, 0xEE, 0xAA, 0xDB, 0xA3, 0xE7, 0x1A, 0x15, 0x5E,
+ 0x63, 0x35, 0x51, 0xF7, 0x75, 0x84, 0x3C, 0xD7, 0x01, 0xF4, 0x43, 0x88, 0xBB, 0x93, 0x71, 0x77,
+ 0x14, 0x47, 0xF9, 0x83, 0xB6, 0xED, 0x78, 0xE7, 0x72, 0x18, 0x0D, 0x65, 0x83, 0x3F, 0x18, 0x80,
+ 0xF1, 0xBA, 0x30, 0xA7, 0x5B, 0x5B, 0xBB, 0x70, 0x92, 0xD4, 0x09, 0x54, 0xD6, 0x25, 0x43, 0xCA,
+ 0x2E, 0x19, 0x4A, 0x9E, 0x2B, 0xCC, 0xAE, 0x2B, 0x82, 0xDA, 0xFA, 0x50, 0xAB, 0x41, 0x10, 0x28,
+ 0xAC, 0xFD, 0x1A, 0x5B, 0x5F, 0x66, 0xCC, 0x19, 0x2C, 0xA3, 0x01, 0xC8, 0x10, 0x72, 0x42, 0x75,
+ 0x39, 0x99, 0x87, 0x98, 0xEC, 0x39, 0xB1, 0xCB, 0xDC, 0x43, 0x6C, 0xD0, 0x8B, 0xC9, 0x1D, 0x81,
+ 0xEC, 0x64, 0x11, 0x62, 0x1F, 0x5F, 0xB3, 0xCF, 0x15, 0x9C, 0x79, 0x04, 0x39, 0x90, 0xBB, 0x5C,
+ 0xF8, 0x4B, 0xB1, 0x96, 0x39, 0xAF, 0xC1, 0x6C, 0x47, 0x28, 0xBC, 0x16, 0x1B, 0x7C, 0xF8, 0x29,
+ 0xAA, 0x96, 0x09, 0x30, 0x3B, 0xEC, 0x5E, 0x06, 0xF2, 0x63, 0x80, 0x69, 0xCE, 0x8B, 0xB2, 0x9E,
+ 0x6E, 0x10, 0x22, 0xDE, 0x63, 0xA9, 0x42, 0x31, 0xCE, 0x32, 0x22, 0x36, 0xA4, 0x98, 0x04, 0xEC,
+ 0xA7, 0x0C, 0xE0, 0x2F, 0x26, 0x08, 0xF5, 0xAD, 0x7B, 0xEB, 0x5B, 0xCB, 0xFA, 0x52, 0xB9, 0x8E,
+ 0x4B, 0x7C, 0xA1, 0xA2, 0xA3, 0x3B, 0x3E, 0x51, 0xC5, 0x9A, 0xD9, 0x2A, 0xBA, 0xC9, 0xF3, 0x9E,
+ 0xE2, 0xC4, 0x2D, 0xA4, 0xD1, 0xDD, 0xE4, 0x5B, 0xBE, 0x90, 0x16, 0x96, 0x32, 0x96, 0x7C, 0x4E,
+ 0x48, 0xEE, 0x28, 0x95, 0xDD, 0x93, 0x3B, 0x7F, 0xCD, 0x03, 0xA6, 0xA3, 0x78, 0xB2, 0xD6, 0x8A,
+ 0x1E, 0xB4, 0xA5, 0x45, 0x6D, 0xE1, 0x1A, 0xD0, 0x30, 0x51, 0x3D, 0xC0, 0x1D, 0x3F, 0x34, 0x1F,
+ 0xEA, 0x7B, 0x4C, 0xF8, 0x6C, 0x18, 0xFC, 0xBF, 0x6C, 0xE0, 0xE3, 0x54, 0x42, 0x88, 0x05, 0xEB,
+ 0x1C, 0x87, 0x89, 0xD6, 0x6A, 0x4C, 0x49, 0x90, 0xCC, 0xF4, 0x02, 0x89, 0x4E, 0xF2, 0xBA, 0x76,
+ 0x92, 0x20, 0xB6, 0xA3, 0x2D, 0x41, 0x6A, 0x6D, 0xDA, 0x16, 0xC0, 0xCD, 0x3B, 0xC8, 0xE6, 0x74,
+ 0x0B, 0xA8, 0x23, 0x8A, 0x76, 0xD5, 0x9F, 0x09, 0xF3, 0xF0, 0x2D, 0x16, 0x31, 0x09, 0x74, 0xBE,
+ 0x79, 0x34, 0xED, 0xDF, 0xE1, 0x83, 0x9C, 0x76, 0x56, 0x5D, 0x93, 0xD4, 0xF2, 0x86, 0xB6, 0x15,
+ 0x39, 0xC5, 0x08, 0x2C, 0xA0, 0x6D, 0x54, 0xF6, 0x70, 0x25, 0x03, 0x76, 0x7F, 0xA5, 0x91, 0xE4,
+ 0x4F, 0xB7, 0x33, 0x6C, 0xD6, 0x42, 0xBB, 0x19, 0xAA, 0x9A, 0x43, 0x2D, 0xD5, 0x50, 0x03, 0x74,
+ 0x00, 0x3D, 0xBD, 0x01, 0x78, 0x06, 0x38, 0x6C, 0x66, 0x06, 0xE3, 0x71, 0x01, 0x29, 0x28, 0x77,
+ 0xB2, 0xAB, 0x69, 0xE6, 0x06, 0x28, 0x56, 0x2B, 0xB2, 0x03, 0x12, 0xF0, 0x28, 0xE7, 0x30, 0x24,
+ 0x95, 0x71, 0xBD, 0x34, 0x34, 0xDA, 0xCD, 0x54, 0x45, 0xAE, 0x50, 0x84, 0xF1, 0xC0, 0xF8, 0x18,
+ 0xA2, 0x1C, 0xD9, 0x0D, 0x0E, 0xE2, 0x24, 0xB5, 0x62, 0xFB, 0x12, 0x77, 0x6F, 0x95, 0x0F, 0x42,
+ 0xFF, 0x5E, 0xAA, 0x4B, 0xBD, 0x3D, 0xA9, 0xC4, 0x28, 0x0F, 0x9E, 0xCA, 0x2A, 0x27, 0xD8, 0x89,
+ 0xA1, 0xE7, 0x01, 0xFF, 0x77, 0x6E, 0x9B, 0xC1, 0x95, 0xD4, 0x31, 0x90, 0xD4, 0x4B, 0x5B, 0x65,
+ 0x49, 0x13, 0x26, 0xD0, 0x49, 0x19, 0x1B, 0xE9, 0x78, 0x13, 0xCB, 0x07, 0x39, 0x2E, 0x73, 0xA9,
+ 0xBB, 0x66, 0x46, 0xC3, 0x48, 0x3D, 0x34, 0x1F, 0x57, 0x89, 0xF5, 0xF7, 0x66, 0x66, 0xAB, 0xB4,
+ 0x04, 0x4A, 0x43, 0x70, 0x42, 0x92, 0x7A, 0x94, 0x10, 0x97, 0x51, 0x49, 0xE7, 0xCD, 0xBB, 0x84,
+ 0x94, 0xAF, 0x51, 0x9F, 0xA8, 0xA4, 0x29, 0x3A, 0xBD, 0x55, 0x62, 0x7F, 0xD6, 0xB3, 0x06, 0xE6,
+ 0x1E, 0xF3, 0x61, 0xB8, 0x32, 0xDA, 0xD4, 0x23, 0x48, 0x21, 0x14, 0xAE, 0x43, 0xE3, 0x46, 0x47,
+ 0xA6, 0x25, 0x12, 0xC1, 0xBD, 0xF1, 0xB0, 0x42, 0xA8, 0x1A, 0x67, 0xD9, 0x8F, 0x7C, 0x3F, 0x15,
+ 0x29, 0x04, 0x58, 0x18, 0x2D, 0xC9, 0x47, 0x55, 0x6D, 0x74, 0xBE, 0xA9, 0xED, 0x81, 0xC4, 0xD8,
+ 0xD8, 0x4F, 0x11, 0x92, 0xA0, 0xC9, 0x21, 0xEF, 0x4F, 0xA8, 0xE1, 0x1F, 0xA6, 0x06, 0xBE, 0xE7,
+ 0x61, 0x6E, 0xDB, 0x09, 0x89, 0x28, 0xF4, 0x2A, 0x65, 0x82, 0x5D, 0x25, 0x52, 0xA5, 0xEE, 0xAA,
+ 0xEC, 0xB1, 0x79, 0xA1, 0x6D, 0x54, 0x2B, 0x75, 0xD0, 0x80, 0x9E, 0xC6, 0x80, 0xFA, 0xF9, 0x39,
+ 0xF0, 0x84, 0xD3, 0x55, 0xC2, 0x63, 0x72, 0x00, 0x14, 0xF2, 0x0B, 0x8A, 0x7B, 0x7F, 0xE3, 0xB9,
+ 0x50, 0x40, 0x1A, 0x16, 0x3B, 0x1A, 0x65, 0xA3, 0x96, 0x4C, 0xF9, 0x29, 0x32, 0x33, 0xAC, 0x85,
+ 0x61, 0x61, 0x3C, 0xB1, 0xEA, 0xB1, 0x20, 0xBC, 0xF5, 0x31, 0xD7, 0x81, 0x99, 0xFB, 0x2D, 0x10,
+ 0x7D, 0x29, 0x8D, 0x84, 0xD2, 0x55, 0xEF, 0x7A, 0xF2, 0xE1, 0x76, 0x25, 0x6E, 0xB1, 0xAE, 0x0A,
+ 0xD3, 0x4E, 0x09, 0xC3, 0x3C, 0x77, 0xD6, 0x75, 0x80, 0x35, 0x92, 0xEA, 0x1C, 0xD8, 0x5C, 0xF0,
+ 0x2C, 0x57, 0x4D, 0x6C, 0x9A, 0x49, 0x6C, 0xFA, 0x32, 0x61, 0xC1, 0x18, 0xEF, 0xB6, 0x89, 0x1A,
+ 0x2C, 0xD0, 0x69, 0xAE, 0x43, 0x47, 0xC8, 0xF3, 0x1C, 0x4D, 0xE3, 0xD8, 0xC4, 0xA8, 0xA3, 0xC3,
+ 0x63, 0x9C, 0x95, 0x69, 0xA0, 0xB1, 0xDD, 0x2D, 0x22, 0xAE, 0xF5, 0x87, 0x35, 0x17, 0xB0, 0x54,
+ 0x80, 0xEC, 0xB0, 0x02, 0x3D, 0x1C, 0x84, 0x10, 0x79, 0x1F, 0xB2, 0x54, 0x7E, 0x7F, 0x1F, 0x5D,
+ 0x5E, 0x52, 0xBF, 0xD7, 0xD1, 0x8D, 0x58, 0xD0, 0x01, 0x0A, 0xD1, 0xBC, 0xB5, 0x56, 0x27, 0xEB,
+ 0x6E, 0x14, 0x07, 0x6C, 0xBF, 0x1B, 0x32, 0xF6, 0x91, 0xA6, 0x34, 0x0D, 0x8F, 0x5E, 0x75, 0x3D,
+ 0x1A, 0xC3, 0x11, 0xE9, 0x42, 0x0F, 0x8C, 0x60, 0x32, 0x05, 0xF0, 0xDC, 0x44, 0xD2, 0xD6, 0x09,
+ 0xE3, 0x87, 0x7F, 0x9F, 0xD5, 0x80, 0x35, 0x0F, 0xCA, 0x0F, 0x50, 0x1E, 0x2D, 0x20, 0xD9, 0x45,
+ 0xEC, 0x70, 0xC8, 0x87, 0x15, 0x1A, 0xCD, 0x21, 0xD6, 0x47, 0xD7, 0x48, 0xDC, 0xD4, 0x30, 0x09,
+ 0x91, 0x95, 0xC0, 0xBE, 0x26, 0x67, 0x20, 0x7D, 0xA9, 0xCD, 0x05, 0xCA, 0xE8, 0x7A, 0x1A, 0x2B,
+ 0xF3, 0xFC, 0xD2, 0xCF, 0xA9, 0xE0, 0x09, 0x6D, 0xFE, 0xFB, 0x42, 0x93, 0x29, 0x4A, 0x80, 0x4D,
+ 0x7D, 0x79, 0x17, 0x5D, 0x06, 0xDB, 0x2B, 0x04, 0xD0, 0xF2, 0xA0, 0xA4, 0xC6, 0xF6, 0xF7, 0x37,
+ 0x07, 0x07, 0x02, 0x13, 0x30, 0x57, 0xD9, 0xE5, 0x21, 0xBA, 0x19, 0x46, 0x2B, 0x12, 0xE3, 0x53,
+ 0x77, 0x37, 0xDC, 0x23, 0xD3, 0xDC, 0x9D, 0x6C, 0xAE, 0xC4, 0x79, 0xBF, 0x94, 0x74, 0xB3, 0x39,
+ 0x74, 0x0F, 0x8F, 0x37, 0x81, 0x52, 0x4E, 0x60, 0x3D, 0xE7, 0x2C, 0xB4, 0x5E, 0xF2, 0x5F, 0x9F,
+ 0x7E, 0xA2, 0x9F, 0xA5, 0x79, 0x0A, 0x39, 0x4D, 0x5B, 0xD2, 0x19, 0xB8, 0x0C, 0xB6, 0xFA, 0x60,
+ 0x2B, 0xC4, 0x12, 0xE0, 0x4D, 0xD7, 0x8A, 0x48, 0x5E, 0xBC, 0x34, 0xC6, 0x89, 0xE3, 0xE3, 0xCD,
+ 0xF0, 0xCE, 0x7C, 0x35, 0xEC, 0x36, 0x50, 0x8F, 0x06, 0x66, 0x35, 0x36, 0x71, 0x13, 0xAD, 0x71,
+ 0xE4, 0xC0, 0xB7, 0x13, 0xC9, 0x38, 0x71, 0x6C, 0x92, 0x19, 0x9F, 0x31, 0x0F, 0x88, 0x92, 0xAD,
+ 0x75, 0xD5, 0x76, 0x61, 0x0D, 0x07, 0xA2, 0x5D, 0x14, 0x96, 0xC5, 0x51, 0xFD, 0x9E, 0x59, 0x13,
+ 0x80, 0x0E, 0x5D, 0x96, 0x07, 0x4C, 0x47, 0x2B, 0xD3, 0x0C, 0x99, 0xEE, 0xB8, 0xF7, 0xF0, 0xC7,
+ 0xC4, 0xB2, 0xA8, 0x11, 0xF0, 0x2C, 0x3D, 0x3D, 0xC6, 0xEC, 0xCF, 0x81, 0x31, 0xF8, 0x8F, 0xD9,
+ 0x68, 0xD6, 0xCA, 0xD5, 0x7B, 0xF5, 0x02, 0xED, 0x90, 0x5E, 0x21, 0x27, 0x4D, 0xB3, 0xE2, 0xF1,
+ 0xFF, 0xD8, 0xB8, 0xAC, 0xCA, 0x16, 0x55, 0xCE, 0xC9, 0x93, 0x4F, 0xC7, 0xEA, 0xE4, 0xA1, 0x36,
+ 0xD8, 0x71, 0x9B, 0x6F, 0x6E, 0x9F, 0xF8, 0xC6, 0x9E, 0x15, 0xF5, 0xB5, 0x45, 0x9F, 0x55, 0xA0,
+ 0x15, 0x11, 0xCE, 0xDD, 0x7C, 0x50, 0xB6, 0x6D, 0x23, 0x47, 0x61, 0x4F, 0x91, 0x5C, 0x23, 0x24,
+ 0xC9, 0xDD, 0xE9, 0xC1, 0x6D, 0xD3, 0xE8, 0xF3, 0x91, 0xEB, 0xA7, 0x44, 0x49, 0x58, 0x02, 0x20,
+ 0xF3, 0xBC, 0x23, 0x13, 0xA7, 0x7E, 0xD2, 0x10, 0xC9, 0x55, 0x97, 0x33, 0x90, 0x32, 0x8A, 0x1B,
+ 0x27, 0x28, 0x32, 0x41, 0xDD, 0x24, 0x87, 0x22, 0xE9, 0x71, 0x42, 0x8F, 0x0F, 0x8E, 0x48, 0x5F,
+ 0x62, 0xCF, 0x35, 0xC1, 0x23, 0x2F, 0xB3, 0x37, 0x46, 0x83, 0xFD, 0xA8, 0x2E, 0xC7, 0xE7, 0x9A,
+ 0x04, 0x4B, 0x05, 0x24, 0x77, 0x10, 0xF4, 0x19, 0xF9, 0x3D, 0x9D, 0x92, 0xB4, 0x21, 0xBA, 0xF7,
+ 0xF8, 0x0B, 0xEC, 0x41, 0xAD, 0x17, 0x52, 0x40, 0xE5, 0xAF, 0x00, 0xEB, 0x80, 0x20, 0x5C, 0x2A,
+ 0xF7, 0x0B, 0xB6, 0xAF, 0x02, 0x28, 0x91, 0xBF, 0x05, 0xDD, 0x9B, 0x1D, 0xBD, 0x60, 0x4F, 0x6C,
+ 0xAE, 0x19, 0x51, 0x74, 0xCA, 0x1D, 0xE2, 0x0B, 0x69, 0xDA, 0x88, 0x48, 0x99, 0xE5, 0xF2, 0x55,
+ 0xAB, 0x53, 0xD1, 0x35, 0x45, 0x82, 0x33, 0x58, 0xF8, 0xC6, 0xC2, 0x01, 0xD1, 0xB0, 0xD5, 0xDC,
+ 0x75, 0xC9, 0x6C, 0xFC, 0x80, 0xE1, 0xEB, 0x03, 0x57, 0xC1, 0xE9, 0x48, 0xEB, 0xAF, 0xB0, 0xDF,
+ 0x70, 0xFE, 0xD7, 0xD2, 0x4A, 0x1E, 0xFA, 0xEA, 0x5F, 0x03, 0x9C, 0x1B, 0x62, 0xC2, 0x96, 0x90,
+ 0x8A, 0xF2, 0xD3, 0x89, 0x89, 0x19, 0x54, 0x22, 0xAB, 0x9A, 0x85, 0xAC, 0x97, 0x49, 0x7A, 0x71,
+ 0x59, 0xD5, 0x37, 0xE9, 0x02, 0x1A, 0x5F, 0xD1, 0xA6, 0x23, 0xE5, 0xA1, 0xD6, 0x7F, 0x2F, 0xAD,
+ 0x12, 0x5C, 0x4D, 0x8F, 0xBC, 0x0B, 0x68, 0xFB, 0x69, 0xD0, 0xBA, 0x8B, 0xB8, 0xBF, 0xFF, 0x21,
+ 0xE3, 0x62, 0xC1, 0xDC, 0x13, 0x8C, 0xC7, 0x1D, 0x49, 0xCB, 0x08, 0x9F, 0xF6, 0x83, 0xBA, 0x6D,
+ 0xE1, 0x75, 0x07, 0xDD, 0xCD, 0xAA, 0x46, 0xAD, 0x3F, 0x77, 0x0C, 0x72, 0x7F, 0xFF, 0xFD, 0x52,
+ 0x41, 0x33, 0x11, 0xE6, 0x46, 0x2A, 0x55, 0x28, 0x97, 0x6C, 0xB7, 0x77, 0xDD, 0x56, 0x9F, 0xB4,
+ 0xDC, 0xDA, 0x74, 0xEB, 0xB5, 0xE8, 0xA5, 0xDE, 0x15, 0x41, 0x61, 0xFD, 0xBD, 0xB0, 0xAC, 0x67,
+ 0xD2, 0x9C, 0xEA, 0x7F, 0xCF, 0x42, 0x10, 0x04, 0x59, 0xB2, 0xC5, 0x1D, 0xDB, 0xA3, 0x53, 0xDE,
+ 0xF2, 0xD1, 0x82, 0xBD, 0x6A, 0xAE, 0x8B, 0x68, 0x61, 0xED, 0xBC, 0x54, 0xD4, 0x14, 0xF4, 0xF0,
+ 0x2C, 0x92, 0x61, 0x43, 0x92, 0x9E, 0xBA, 0x96, 0x7D, 0xFA, 0x03, 0x85, 0x6D, 0x2C, 0xB2, 0xB0,
+ 0xE3, 0xE9, 0x85, 0xB2, 0x9F, 0x43, 0x84, 0xBC, 0xDC, 0x8C, 0xCD, 0x80, 0x48, 0x0E, 0x22, 0xCA,
+ 0xD8, 0x7A, 0x2E, 0x4C, 0x10, 0xB1, 0xD2, 0x78, 0xD1, 0xD8, 0xBF, 0x2E, 0x54, 0x04, 0x9B, 0x68,
+ 0x5E, 0xB6, 0x4E, 0x92, 0x86, 0x6A, 0xCE, 0x58, 0x57, 0x66, 0x47, 0x5A, 0x7B, 0xC2, 0x93, 0x16,
+ 0x48, 0x57, 0x2C, 0x56, 0xF2, 0xD3, 0x95, 0x05, 0x31, 0x14, 0xE0, 0x88, 0x3B, 0xE5, 0x1B, 0x54,
+ 0xA5, 0x9F, 0x69, 0x95, 0xAB, 0x3A, 0xE2, 0xC1, 0x6D, 0x59, 0xB7, 0x86, 0x6F, 0x76, 0x79, 0x99,
+ 0x43, 0x37, 0x7A, 0xA4, 0xF3, 0x6D, 0x97, 0x08, 0x89, 0x74, 0xBD, 0x9A, 0xA9, 0x9A, 0x5D, 0x63,
+ 0xBB, 0x77, 0x6A, 0xAA, 0x25, 0xE6, 0x6D, 0x20, 0x7A, 0x2B, 0x2F, 0x7C, 0x4E, 0xAD, 0xFE, 0x62,
+ 0xAB, 0xA0, 0x66, 0x0C, 0xFB, 0xC9, 0xFE, 0x1A, 0x5E, 0xA1, 0x8E, 0xFD, 0x69, 0x7C, 0xF0, 0x8F,
+ 0x19, 0xFC, 0x20, 0x3D, 0x39, 0x3B, 0x7C, 0x16, 0xB2, 0xBB, 0xB9, 0x0A, 0x57, 0x78, 0x32, 0x5C,
+ 0xE0, 0x79, 0x1C, 0x4C, 0x9B, 0xDF, 0x67, 0x4F, 0x26, 0xCF, 0x70, 0x0D, 0xE9, 0x18, 0x79, 0xC7,
+ 0xCF, 0x6A, 0xF2, 0x3F, 0x65, 0x7B, 0xF5, 0x93, 0xAF, 0xFC, 0xEA, 0x68, 0xC6, 0x86, 0xBC, 0xBD,
+ 0x2A, 0xA7, 0xC2, 0x49, 0x03, 0xC4, 0xAD, 0x09, 0x48, 0xC3, 0x0B, 0x33, 0x59, 0xC5, 0x42, 0xB7,
+ 0x69, 0x05, 0x21, 0xB6, 0xF4, 0x0E, 0x4E, 0x39, 0x53, 0x45, 0x3A, 0x6E, 0x78, 0x32, 0x5B, 0xB4,
+ 0x97, 0x7F, 0x4E, 0x95, 0x97, 0x4E, 0xDB, 0xEA, 0xA9, 0x62, 0x19, 0x28, 0x8E, 0x64, 0x11, 0xFA,
+ 0x7B, 0x07, 0xB8, 0x55, 0x38, 0xF6, 0xCC, 0xB2, 0x91, 0x10, 0x02, 0x1E, 0xED, 0xA0, 0x36, 0xF6,
+ 0xA1, 0x14, 0x97, 0x97, 0xCE, 0xD8, 0xED, 0x85, 0xA5, 0xB0, 0x1A, 0xF7, 0xE1, 0x27, 0xBC, 0xED,
+ 0x3A, 0x06, 0xDC, 0x7E, 0x41, 0x76, 0xFB, 0xD3, 0x82, 0xAE, 0xDE, 0x86, 0xCD, 0x2C, 0x68, 0x5C,
+ 0xBB, 0x67, 0xEE, 0x9B, 0x8B, 0x26, 0x80, 0xB1, 0x93, 0x66, 0xC7, 0xBB, 0xB1, 0x7F, 0xCF, 0xC6,
+ 0x97, 0xBD, 0xB7, 0xF9, 0x1B, 0x7E, 0x9B, 0x53, 0xFE, 0x22, 0xBC, 0xD3, 0x99, 0xD0, 0x24, 0xD8,
+ 0xE1, 0x34, 0x1A, 0xB5, 0xE9, 0x99, 0x33, 0x0D, 0xC8, 0x0D, 0x51, 0x81, 0x9E, 0xAD, 0xC0, 0xC1,
+ 0x57, 0x92, 0x83, 0xB7, 0x9B, 0xDD, 0x4F, 0xC9, 0x6F, 0x30, 0x55, 0xE4, 0x48, 0x33, 0x41, 0xAE,
+ 0x4A, 0xC6, 0xBF, 0xD5, 0x00, 0x49, 0xC1, 0xDC, 0x9A, 0xE4, 0xBC, 0x60, 0x3E, 0xB8, 0x12, 0x2B,
+ 0x48, 0x1B, 0x1F, 0x17, 0x13, 0x17, 0x2D, 0xF9, 0x19, 0x20, 0x49, 0x5B, 0xE9, 0x35, 0xC6, 0xDE,
+ 0x3F, 0x90, 0x9F, 0x94, 0x83, 0x71, 0x26, 0x27, 0x07, 0x63, 0x9A, 0x42, 0xAC, 0xE7, 0x2F, 0x2E,
+ 0x78, 0x48, 0x05, 0xD3, 0xB2, 0x4F, 0xCD, 0x36, 0x50, 0x39, 0xD5, 0xDC, 0x36, 0xA8, 0xDC, 0x77,
+ 0x86, 0x8A, 0xA3, 0x09, 0x0D, 0x4D, 0x2F, 0xAC, 0xBE, 0x9D, 0x91, 0xDF, 0x0B, 0xF1, 0xB3, 0xF4,
+ 0xE3, 0x06, 0xF7, 0x6E, 0xFE, 0x31, 0x3C, 0xB7, 0x9D, 0xFD, 0x6D, 0x36, 0x7C, 0x16, 0x4C, 0xDF,
+ 0x3E, 0x9B, 0x3D, 0xAE, 0x7F, 0xED, 0xB8, 0x72, 0x3B, 0xF2, 0x8D, 0x77, 0xFB, 0x68, 0x87, 0x9C,
+ 0x9D, 0x8F, 0x97, 0xC6, 0xB2, 0x1A, 0x82, 0xFA, 0x45, 0x4F, 0x1F, 0x15, 0xC5, 0x5B, 0xE0, 0x8C,
+ 0x46, 0x2C, 0x1B, 0x4C, 0xE2, 0xC4, 0x7C, 0x26, 0xC3, 0xD6, 0xBB, 0x19, 0x22, 0x3F, 0x3A, 0x8E,
+ 0xF4, 0xF7, 0x64, 0x2A, 0x85, 0x3C, 0x82, 0x7E, 0x66, 0xE1, 0xCF, 0xCC, 0x86, 0x4B, 0x57, 0xC7,
+ 0x83, 0x62, 0x4A, 0x99, 0x2B, 0x0D, 0x01, 0x15, 0x59, 0xAD, 0x49, 0x6F, 0xF7, 0x13, 0x76, 0x52,
+ 0x11, 0xA8, 0x85, 0x0E, 0xC2, 0x8E, 0xA7, 0xEF, 0xCA, 0xA4, 0x51, 0x97, 0xD9, 0xA8, 0x47, 0xAA,
+ 0xE8, 0x71, 0xAD, 0x15, 0xB9, 0xE2, 0x6C, 0xCE, 0x2E, 0x5B, 0x27, 0xB4, 0x49, 0xC3, 0x4A, 0xB8,
+ 0x2E, 0xE9, 0xF1, 0x01, 0x92, 0xAC, 0x4C, 0x88, 0xDC, 0xE0, 0x92, 0x82, 0xD4, 0xDE, 0x76, 0x67,
+ 0x76, 0x6F, 0xEF, 0x84, 0x2F, 0x79, 0x55, 0xC5, 0xD7, 0x72, 0x89, 0x94, 0x71, 0x54, 0xCB, 0xB1,
+ 0x35, 0xBA, 0x64, 0xDF, 0x57, 0xA8, 0x82, 0x2B, 0x6A, 0x8B, 0xAF, 0x87, 0x4C, 0x54, 0xC0, 0xAF,
+ 0xF0, 0x47, 0x0E, 0x45, 0xDA, 0x21, 0x16, 0x2C, 0x09, 0x6E, 0x38, 0x47, 0xD7, 0x44, 0x6E, 0x1A,
+ 0xBD, 0x6C, 0x5F, 0x41, 0x27, 0x8D, 0x00, 0x9D, 0xE8, 0x5C, 0x2F, 0x71, 0x35, 0xA9, 0x70, 0x36,
+ 0x6E, 0x04, 0x9A, 0xDA, 0xA9, 0x60, 0xCF, 0xB9, 0x00, 0x66, 0x0A, 0x24, 0xCD, 0x02, 0x72, 0xFB,
+ 0x85, 0x57, 0x04, 0x72, 0xF6, 0xD4, 0x1C, 0x15, 0xE4, 0x1B, 0x7B, 0xA2, 0x7E, 0x19, 0x12, 0xFD,
+ 0x53, 0xC2, 0x8D, 0xC6, 0x77, 0xB2, 0x2F, 0xEB, 0x01, 0xE9, 0xE2, 0x48, 0xAD, 0x08, 0xD6, 0xE0,
+ 0xB6, 0xF5, 0xD4, 0x7F, 0x29, 0xC1, 0xF4, 0x4B, 0x09, 0xA4, 0x92, 0x06, 0x2F, 0x6B, 0x32, 0xD1,
+ 0xC3, 0xE7, 0x5F, 0xB2, 0x2A, 0x5D, 0xD5, 0x7C, 0x95, 0xF9, 0x89, 0x80, 0x2F, 0x38, 0xB6, 0x29,
+ 0x43, 0x0E, 0xD6, 0xBB, 0x49, 0xF3, 0x8F, 0x92, 0xC2, 0x74, 0x72, 0xD2, 0x2F, 0x15, 0x6B, 0xBE,
+ 0x81, 0xF1, 0x8D, 0x3C, 0x3C, 0xDA, 0x47, 0xFB, 0xB8, 0x79, 0xB4, 0x5B, 0xAD, 0x5F, 0x03, 0x07,
+ 0xC2, 0xD6, 0xA0, 0xEF, 0xB9, 0x02, 0x35, 0x20, 0x57, 0x5C, 0xE2, 0xAE, 0xC9, 0x0E, 0xDC, 0x46,
+ 0x85, 0x13, 0x60, 0xA5, 0x04, 0x8A, 0xB8, 0xA4, 0x83, 0xDF, 0xE4, 0x8B, 0x06, 0x50, 0xF4, 0xB9,
+ 0xF8, 0x6D, 0x2B, 0xE6, 0xAB, 0xBC, 0x04, 0x08, 0xED, 0x72, 0x8F, 0xAD, 0xD0, 0xAF, 0xEA, 0x84,
+ 0x96, 0xF0, 0x74, 0x31, 0xB1, 0xA4, 0x51, 0x78, 0xFB, 0x6A, 0xB4, 0x21, 0x81, 0xCF, 0x28, 0x1A,
+ 0x2C, 0x22, 0x25, 0xEF, 0xCF, 0x64, 0xF1, 0x41, 0x04, 0xC7, 0x51, 0xEB, 0xCA, 0x13, 0xED, 0x76,
+ 0x6B, 0x33, 0x30, 0x1E, 0x93, 0x04, 0x8F, 0x8C, 0x4E, 0x63, 0x69, 0x88, 0x42, 0x08, 0xB4, 0x7D,
+ 0xAF, 0xBE, 0xF7, 0xDC, 0xE2, 0x23, 0x93, 0xF8, 0xBC, 0x5C, 0xAB, 0x82, 0x15, 0xD7, 0xD6, 0x8F,
+ 0x2F, 0xC7, 0xC7, 0x9A, 0x4D, 0x6D, 0xE2, 0xEB, 0x1C, 0x22, 0x28, 0x4C, 0x12, 0xB7, 0xDD, 0x6B,
+ 0x53, 0x31, 0xE9, 0x71, 0xBA, 0xAD, 0xCE, 0x71, 0x9A, 0x10, 0xA1, 0x20, 0x3D, 0x08, 0x65, 0xA4,
+ 0x42, 0xCB, 0x1A, 0xA7, 0x24, 0x84, 0x0B, 0x14, 0x32, 0x61, 0xDC, 0xA5, 0x02, 0xCE, 0x8C, 0xF0,
+ 0x5E, 0x65, 0x5E, 0x82, 0xB8, 0x6E, 0x02, 0x3F, 0xBA, 0xDC, 0xAA, 0x71, 0x70, 0x48, 0x0E, 0x81,
+ 0xE1, 0x59, 0xA6, 0xB5, 0x7C, 0xFD, 0x83, 0x6C, 0x8C, 0xCC, 0xC5, 0x3F, 0xB4, 0xB1, 0xC1, 0x42,
+ 0x9E, 0x72, 0x61, 0xFC, 0xC3, 0x55, 0x7E, 0x8A, 0x22, 0x3B, 0x4F, 0x1A, 0xA4, 0xFB, 0xFA, 0x99,
+ 0x8E, 0xD6, 0x23, 0x08, 0x61, 0xEB, 0x5B, 0x03, 0x6B, 0x42, 0x5A, 0x10, 0xE3, 0x80, 0xFA, 0x5E,
+ 0x8E, 0xB0, 0xD7, 0xE0, 0xA2, 0x7B, 0x09, 0x54, 0x5F, 0xEC, 0x72, 0x71, 0xA2, 0xBA, 0x56, 0xA4,
+ 0xF6, 0x73, 0x5F, 0x4F, 0x2F, 0x31, 0x1E, 0x57, 0x83, 0x1C, 0x98, 0xDC, 0xBC, 0xEB, 0x5B, 0xBA,
+ 0xDE, 0x9D, 0xC5, 0x90, 0xB4, 0xE5, 0xFD, 0xDF, 0x6D, 0x43, 0xEE, 0xFE, 0x86, 0xB5, 0x2C, 0xF2,
+ 0xD2, 0xD8, 0x77, 0xE5, 0xED, 0x98, 0x06, 0xAB, 0xBA, 0xB1, 0xA8, 0xBD, 0x43, 0xD8, 0x51, 0xFD,
+ 0xAE, 0xEC, 0x0F, 0xB4, 0xF0, 0xFE, 0x31, 0xBB, 0xED, 0x20, 0x4D, 0xB6, 0xF4, 0xFE, 0x62, 0xBA,
+ 0xBD, 0x46, 0xD1, 0x52, 0x7E, 0xF4, 0xAE, 0xCB, 0x1B, 0xBF, 0xE1, 0x5E, 0x90, 0x1E, 0x9C, 0x08,
+ 0x1C, 0x6E, 0x99, 0x75, 0x48, 0x1A, 0x0F, 0xF7, 0x97, 0x77, 0x99, 0x6B, 0x64, 0x36, 0x88, 0x7A,
+ 0xF7, 0xBB, 0x47, 0x23, 0x95, 0xE7, 0x85, 0xB9, 0x29, 0x58, 0xF8, 0x9D, 0xC8, 0x60, 0xD2, 0x89,
+ 0x0A, 0xFD, 0xE7, 0x34, 0xC6, 0x0A, 0x46, 0x48, 0x2B, 0x08, 0x47, 0x3C, 0xD6, 0xED, 0xD9, 0xEA,
+ 0x48, 0xE4, 0xA7, 0x8F, 0x72, 0xE9, 0xD3, 0xCB, 0x1A, 0x25, 0x13, 0xBC, 0x5B, 0xBA, 0x98, 0x85,
+ 0xD0, 0x84, 0xFB, 0x81, 0xFD, 0x7A, 0x7D, 0xD0, 0x3A, 0x6F, 0xC3, 0xA4, 0xF6, 0x55, 0x1A, 0x8F,
+ 0xE7, 0xDF, 0x23, 0x7E, 0x40, 0x1B, 0xB7, 0x7F, 0x2C, 0x2F, 0x92, 0xA0, 0x61, 0x8C, 0xA2, 0x83,
+ 0x7E, 0x2A, 0xD6, 0x5A, 0x18, 0xC6, 0x81, 0x56, 0x8B, 0xF4, 0x4E, 0x0E, 0x12, 0x47, 0x86, 0x2F,
+ 0xF8, 0x9D, 0xA5, 0x16, 0x65, 0x2A, 0xBE, 0x54, 0xC4, 0x04, 0x3B, 0x74, 0xC1, 0xA2, 0x26, 0x05,
+ 0x8B, 0x17, 0x44, 0x13, 0x8D, 0x20, 0xA7, 0xA6, 0xFC, 0xBE, 0x85, 0x67, 0xDE, 0xB7, 0x0D, 0x5E,
+ 0x6C, 0xF8, 0xE4, 0xC2, 0x9E, 0x8C, 0xAF, 0x5D, 0x44, 0x69, 0x35, 0x97, 0x5F, 0x99, 0x5B, 0x7C,
+ 0x6E, 0xEC, 0xAB, 0x96, 0x0C, 0x96, 0xDD, 0x0F, 0x26, 0x74, 0xBB, 0x83, 0x6F, 0xF0, 0xC2, 0xAF,
+ 0x6E, 0x5A, 0x26, 0x81, 0xC6, 0xAD, 0xEC, 0x51, 0x2C, 0x4B, 0x98, 0xE8, 0x8A, 0xD3, 0x15, 0x1D,
+ 0x0B, 0x26, 0x2F, 0x9E, 0x62, 0xCB, 0x74, 0x46, 0xAD, 0x4A, 0xD4, 0x0F, 0x7C, 0x49, 0xAA, 0x82,
+ 0x6E, 0x77, 0x6A, 0xA5, 0x50, 0xE0, 0xF8, 0x35, 0xCC, 0x3A, 0xB9, 0x30, 0xD4, 0xD3, 0xD1, 0x09,
+ 0x2A, 0x3A, 0xC7, 0xC8, 0x9D, 0xCB, 0x9F, 0xAA, 0x97, 0x09, 0x3D, 0xAB, 0x11, 0x75, 0xD6, 0xA0,
+ 0x98, 0x90, 0x2C, 0x91, 0x5F, 0x0C, 0xD3, 0xF8, 0x4E, 0x5D, 0xA0, 0xF5, 0xBF, 0x65, 0x99, 0x6B,
+ 0xDB, 0x5B, 0x3E, 0x3D, 0x28, 0x42, 0xC7, 0x31, 0x75, 0xD5, 0x3E, 0x25, 0xB2, 0xC5, 0x64, 0x1C,
+ 0x29, 0xED, 0x6E, 0x47, 0xE9, 0x7F, 0x30, 0x86, 0x0C, 0xBA, 0x49, 0x00, 0x90, 0xAE, 0xAB, 0xAE,
+ 0x8B, 0x11, 0x3D, 0x7C, 0x41, 0x5A, 0x74, 0x54, 0x79, 0xB4, 0xD1, 0xE4, 0x03, 0x95, 0xC0, 0xB4,
+ 0x47, 0x1B, 0x25, 0xC1, 0xD5, 0x5A, 0x84, 0xE1, 0x70, 0x75, 0xAC, 0x21, 0x23, 0x18, 0x8C, 0xF9,
+ 0x6E, 0xF1, 0x4A, 0x6B, 0x43, 0x89, 0x56, 0x15, 0x95, 0x24, 0x2E, 0x01, 0x7F, 0xF0, 0x70, 0xF6,
+ 0x4D, 0xF6, 0x59, 0x0C, 0x3D, 0x2A, 0xBB, 0xC6, 0xD7, 0xC5, 0xC0, 0x0A, 0x0D, 0xE8, 0xA6, 0x22,
+ 0xB6, 0xC1, 0x55, 0x5E, 0x90, 0xF6, 0x9F, 0x23, 0xD4, 0x7B, 0x2A, 0xC8, 0x8A, 0xE7, 0xDA, 0xC8,
+ 0x0C, 0xC0, 0xF3, 0x58, 0xA7, 0x74, 0x1F, 0xF7, 0x3E, 0xFC, 0x53, 0x22, 0x23, 0xD9, 0x33, 0xA6,
+ 0x8D, 0xBA, 0x31, 0xCC, 0x8D, 0xD6, 0x54, 0x5A, 0x3B, 0xD8, 0x3D, 0xDE, 0xE1, 0x7A, 0x6A, 0x5B,
+ 0xB3, 0x79, 0x85, 0xAC, 0x93, 0x42, 0xC2, 0x36, 0xB9, 0xCC, 0x21, 0x83, 0x4E, 0xF6, 0x78, 0x5A,
+ 0xEA, 0x8A, 0xF8, 0x81, 0x10, 0xD5, 0x80, 0xDD, 0x42, 0xA4, 0x8E, 0x46, 0x22, 0x80, 0x1F, 0x7F,
+ 0x41, 0x1A, 0xF9, 0xF6, 0x1D, 0x25, 0xA1, 0x3A, 0x36, 0xD7, 0xDB, 0x61, 0x2B, 0xA4, 0x5F, 0xCB,
+ 0x9E, 0x71, 0xC8, 0x5E, 0xD9, 0xC2, 0xA2, 0x67, 0xB3, 0xAB, 0xB9, 0x07, 0x5D, 0xC3, 0x7B, 0x55,
+ 0x3D, 0xC7, 0x81, 0xCD, 0xCA, 0xDA, 0xDB, 0xA0, 0x34, 0x97, 0x40, 0xC5, 0x98, 0xB4, 0xB6, 0x11,
+ 0x99, 0xF5, 0x1C, 0x1C, 0x6C, 0x75, 0xDB, 0x18, 0x50, 0x3F, 0x31, 0x82, 0x0A, 0x4D, 0x6D, 0xAC,
+ 0xCC, 0x0B, 0x0F, 0xCD, 0xE2, 0x2A, 0x33, 0xCE, 0x1D, 0x33, 0x4F, 0xA0, 0xA5, 0xAB, 0x57, 0xB2,
+ 0xC0, 0x9E, 0x9C, 0x71, 0xC4, 0xD0, 0x87, 0xF5, 0x84, 0xC1, 0x57, 0x33, 0x77, 0xCF, 0x92, 0x0E,
+ 0x90, 0x0A, 0xA3, 0xDE, 0x77, 0xBB, 0x2B, 0x22, 0xCD, 0x18, 0xC0, 0x45, 0xD6, 0xC7, 0xCD, 0xAB,
+ 0x22, 0xBD, 0xD5, 0x0D, 0x62, 0xB6, 0x58, 0x4C, 0xFE, 0x86, 0x57, 0x08, 0xFB, 0x09, 0x1E, 0x80,
+ 0xBF, 0xB4, 0xEF, 0xC7, 0x5F, 0x7E, 0x3B, 0x45, 0x69, 0x33, 0x27, 0xDA, 0x86, 0x18, 0x4B, 0x3F,
+ 0xB3, 0x00, 0x25, 0xD0, 0xB8, 0x59, 0x52, 0xAE, 0xBB, 0x6F, 0xD0, 0xCB, 0x91, 0x6E, 0xB4, 0xFD,
+ 0xBC, 0x96, 0x2D, 0xD7, 0xDF, 0xDF, 0x7C, 0xBB, 0x35, 0xAE, 0xC7, 0xDC, 0x37, 0x82, 0x5E, 0xD0,
+ 0x5B, 0x0C, 0x45, 0x8F, 0x55, 0x30, 0xC8, 0xEB, 0xA9, 0x07, 0x0E, 0x3F, 0x5D, 0xDE, 0x79, 0x74,
+ 0xB0, 0xE6, 0x17, 0x74, 0x7D, 0xD9, 0x13, 0x0E, 0x0E, 0xF2, 0x3D, 0xB9, 0xCB, 0xBC, 0x60, 0x47,
+ 0x2C, 0xEE, 0xC7, 0x4C, 0x3D, 0x14, 0xCB, 0x57, 0xD7, 0x24, 0x8F, 0x26, 0x34, 0xD9, 0xAA, 0x80,
+ 0xF0, 0xC3, 0x9E, 0xCE, 0xFF, 0x50, 0xD2, 0xA1, 0xD0, 0x15, 0x2D, 0x3C, 0x59, 0x2B, 0x11, 0x74,
+ 0xA8, 0x94, 0x70, 0xEE, 0xBF, 0x5B, 0xE9, 0x58, 0xA8, 0x7A, 0xA8, 0x52, 0x92, 0x8F, 0x7A, 0xE4,
+ 0x20, 0x86, 0x09, 0x07, 0xBC, 0x2A, 0x05, 0x46, 0xB9, 0xEA, 0x5B, 0x04, 0x32, 0x3F, 0x59, 0xDD,
+ 0xC4, 0x77, 0x65, 0x1F, 0xFC, 0xCA, 0xB3, 0xC0, 0xAC, 0x8B, 0x3A, 0x13, 0x3A, 0xEB, 0xE4, 0x31,
+ 0xA2, 0xF7, 0x7A, 0x2F, 0x5D, 0xF0, 0xA9, 0xA1, 0xAC, 0x3F, 0x88, 0xF2, 0x49, 0xD7, 0x4E, 0x2F,
+ 0xD4, 0x91, 0x64, 0xDF, 0xB1, 0x3A, 0x32, 0x7B, 0x5D, 0x2F, 0xA5, 0x6F, 0x8F, 0x06, 0x83, 0x98,
+ 0xF2, 0xBE, 0xAD, 0x1F, 0x5D, 0x91, 0xA4, 0x09, 0x37, 0x46, 0x09, 0xA7, 0xAA, 0xD0, 0x51, 0x39,
+ 0x25, 0x24, 0x3B, 0xF3, 0xDB, 0x2F, 0x7E, 0xF1, 0x63, 0x64, 0xFD, 0x4F, 0xBB, 0x1D, 0xF1, 0x79,
+ 0xE7, 0x9C, 0x8C, 0x26, 0x08, 0xE6, 0x40, 0x43, 0x8E, 0x5F, 0x8C, 0x24, 0x34, 0xC9, 0xE3, 0x12,
+ 0x9F, 0x6A, 0x3D, 0xE5, 0x14, 0xF1, 0x37, 0x3F, 0x4D, 0x09, 0x3B, 0x06, 0xC2, 0xF5, 0x43, 0x8F,
+ 0xC0, 0xDC, 0x9B, 0xC9, 0xC6, 0x32, 0x7E, 0x3E, 0xC8, 0x36, 0xB9, 0xC5, 0xBF, 0x58, 0x2F, 0x29,
+ 0xAC, 0xB7, 0xAD, 0x6D, 0x31, 0xBD, 0x2E, 0xA1, 0xE6, 0x9C, 0xE2, 0x50, 0xB5, 0x5B, 0x72, 0x60,
+ 0xE5, 0x2B, 0xC5, 0x6A, 0xF7, 0x52, 0x87, 0x9E, 0x1B, 0x6E, 0xB6, 0xB0, 0xF3, 0x2C, 0x92, 0x9E,
+ 0x47, 0xD0, 0x98, 0xC9, 0xF4, 0xD3, 0xE3, 0x8D, 0xD4, 0xE2, 0xD1, 0xFB, 0x62, 0x56, 0x3D, 0xC7,
+ 0x97, 0x17, 0x72, 0xDB, 0x39, 0xE7, 0x71, 0x53, 0x62, 0x1B, 0x94, 0xCB, 0x80, 0x37, 0x97, 0xD2,
+ 0x6C, 0x9F, 0xDC, 0x88, 0x2D, 0x0F, 0x78, 0x4E, 0x36, 0xD4, 0x08, 0xF9, 0x62, 0x62, 0x17, 0xA6,
+ 0x1F, 0xFA, 0xF0, 0x23, 0xD6, 0x50, 0x12, 0x1C, 0x82, 0x4F, 0x9D, 0x72, 0x52, 0x69, 0xB2, 0x63,
+ 0xE5, 0x6F, 0x30, 0xDA, 0xD7, 0x18, 0xAD, 0x0A, 0x7E, 0x85, 0x60, 0x10, 0x42, 0xBF, 0x3E, 0x14,
+ 0x0F, 0x67, 0x32, 0xB1, 0xB9, 0x5A, 0x3C, 0x5A, 0x93, 0x80, 0xCA, 0xD2, 0xB3, 0x68, 0xAF, 0x99,
+ 0xBE, 0x53, 0x14, 0x4A, 0x11, 0x11, 0x8D, 0x22, 0xFC, 0x12, 0x36, 0x03, 0x7A, 0x69, 0x39, 0xB7,
+ 0x9C, 0x8A, 0x2D, 0x96, 0x26, 0x2A, 0x27, 0x0D, 0x48, 0x66, 0xB2, 0xCA, 0x77, 0x88, 0x1E, 0x0B,
+ 0xC3, 0xC0, 0x4F, 0xF3, 0x84, 0xEF, 0x16, 0x7C, 0x91, 0xE7, 0x30, 0x03, 0xDD, 0x95, 0x42, 0xD0,
+ 0x3C, 0x2A, 0x89, 0x16, 0x7C, 0x53, 0xC4, 0x73, 0xD0, 0xA1, 0x9B, 0x63, 0x5C, 0xE2, 0x04, 0xAB,
+ 0xCE, 0x1D, 0xFC, 0xAA, 0xA7, 0x83, 0xB9, 0x82, 0x33, 0xEA, 0x9B, 0xEE, 0xDA, 0x51, 0x3A, 0xA9,
+ 0x48, 0x53, 0x64, 0x5B, 0x21, 0x36, 0x94, 0x49, 0x4C, 0xD9, 0xBC, 0xEF, 0x36, 0x12, 0xED, 0xC8,
+ 0x46, 0xD8, 0xFB, 0x84, 0x54, 0x71, 0x6F, 0xD2, 0xAB, 0x24, 0xDF, 0x40, 0xE6, 0x45, 0x75, 0xEF,
+ 0xDE, 0x9E, 0x34, 0x62, 0xD2, 0x0E, 0xE3, 0xF2, 0x36, 0x33, 0xB1, 0x2B, 0xD8, 0x60, 0x25, 0x58,
+ 0xBC, 0x02, 0x24, 0x43, 0xF8, 0x5A, 0x24, 0x8D, 0x29, 0x17, 0xD0, 0xAD, 0x37, 0x73, 0xCA, 0x65,
+ 0xAE, 0xC2, 0xD7, 0x9C, 0xF8, 0xB4, 0x9D, 0x28, 0x9F, 0x92, 0xFB, 0x2A, 0x08, 0x9A, 0xFB, 0x43,
+ 0x05, 0x1F, 0x60, 0x4F, 0x26, 0xFA, 0x70, 0x20, 0x2B, 0x42, 0x79, 0xF1, 0x0B, 0x3E, 0x0D, 0xCC,
+ 0xE9, 0xF1, 0x20, 0x42, 0xA9, 0xE8, 0x4A, 0x64, 0x81, 0x9F, 0x8F, 0x67, 0x47, 0xB1, 0x44, 0x24,
+ 0x11, 0xBB, 0x9B, 0x15, 0xB0, 0x50, 0xE0, 0x80, 0x8B, 0x57, 0x52, 0x28, 0x16, 0xD0, 0xF5, 0x8F,
+ 0x0E, 0x92, 0x19, 0xCA, 0x69, 0x7F, 0x5C, 0x3A, 0xE6, 0x23, 0x37, 0xE6, 0x90, 0x73, 0xD0, 0x71,
+ 0x2C, 0x3F, 0x3E, 0x92, 0x1F, 0x81, 0x90, 0x95, 0x56, 0x88, 0xE0, 0xA3, 0x11, 0x8A, 0x3B, 0xC2,
+ 0x1E, 0x68, 0xB6, 0x07, 0x37, 0x77, 0xF1, 0x0A, 0x91, 0x98, 0x4A, 0xBC, 0x17, 0xF6, 0x1F, 0xA8,
+ 0x8D, 0x42, 0x18, 0x8C, 0x39, 0x80, 0xC9, 0xCA, 0xC9, 0x4C, 0x2C, 0xB2, 0x93, 0x72, 0x49, 0xEE,
+ 0xA5, 0x92, 0x36, 0x0E, 0x8A, 0xDE, 0x10, 0x2A, 0xEA, 0x7A, 0x04, 0x35, 0x4D, 0x58, 0x92, 0x95,
+ 0xC4, 0x5B, 0x8A, 0xEC, 0x22, 0xAC, 0xA6, 0x1E, 0x1B, 0x75, 0xFA, 0x92, 0x47, 0x0E, 0xFC, 0x90,
+ 0x79, 0xD5, 0xFB, 0xAE, 0xA5, 0x11, 0x15, 0x2F, 0xF9, 0x37, 0x0A, 0x8D, 0x8F, 0xDB, 0x8D, 0x4E,
+ 0x3A, 0x6D, 0xC0, 0xF1, 0xD5, 0xC1, 0x41, 0xD6, 0xDA, 0xBC, 0xCC, 0x2A, 0x6E, 0xB7, 0x2C, 0xF8,
+ 0x3E, 0x8E, 0x68, 0x5B, 0x11, 0x6F, 0x94, 0xAB, 0x43, 0x8E, 0x00, 0x5C, 0xE7, 0x36, 0xFB, 0x4A,
+ 0x0C, 0xC0, 0xBE, 0x99, 0x73, 0x94, 0x31, 0x20, 0x1F, 0xA4, 0x64, 0x9B, 0xC4, 0xE7, 0x0D, 0x44,
+ 0x4B, 0xF4, 0x97, 0x51, 0x95, 0x35, 0x6B, 0xC8, 0xF9, 0xDB, 0xB0, 0x02, 0x15, 0x4C, 0x33, 0x5E,
+ 0x49, 0x3D, 0x08, 0x35, 0x63, 0x6A, 0x37, 0xC2, 0x4B, 0x8B, 0x57, 0x0D, 0x0B, 0xF8, 0x37, 0x92,
+ 0xAA, 0xD2, 0x7B, 0x7C, 0xF5, 0xAB, 0x0C, 0xFC, 0x66, 0x86, 0xC0, 0x6B, 0xDC, 0x2E, 0x4B, 0xEA,
+ 0xD7, 0x34, 0x73, 0x09, 0x4E, 0xFE, 0x5A, 0xFA, 0x67, 0xA9, 0xD9, 0xB6, 0x1D, 0x4E, 0xA2, 0x03,
+ 0xC6, 0xC1, 0xD0, 0x06, 0xEC, 0x42, 0x2F, 0x2D, 0xEE, 0xF8, 0x84, 0xA5, 0xEC, 0x39, 0x79, 0x59,
+ 0x31, 0xC1, 0xD1, 0x4D, 0x5C, 0xE0, 0x68, 0x44, 0xD4, 0xDF, 0xCC, 0x5D, 0x1E, 0xBA, 0xC7, 0xD3,
+ 0xCE, 0xA2, 0x9F, 0xB2, 0x36, 0x2D, 0xED, 0x99, 0x96, 0xF8, 0xAA, 0xE8, 0xE8, 0x0A, 0x87, 0x64,
+ 0x7C, 0x81, 0xFB, 0xA0, 0x12, 0xD5, 0xB0, 0x9F, 0x9E, 0x53, 0x29, 0x68, 0x7E, 0xA9, 0x73, 0x36,
+ 0x9C, 0xAC, 0x34, 0x70, 0x8D, 0x49, 0xB0, 0xFC, 0x6C, 0xB0, 0xE5, 0x69, 0xF9, 0xAC, 0x01, 0x3B,
+ 0xF6, 0x0C, 0xFC, 0x14, 0x59, 0x5F, 0x2A, 0x9F, 0xF8, 0x2D, 0x6F, 0xD0, 0x2F, 0xBE, 0xF9, 0x4A,
+ 0xDD, 0x37, 0xFC, 0x12, 0x6E, 0x9E, 0xC9, 0xC3, 0xDD, 0xA7, 0x40, 0x6D, 0xFD, 0x79, 0x57, 0xC8,
+ 0x41, 0xE9, 0xBA, 0xAF, 0xBE, 0x7C, 0xA5, 0x57, 0x7E, 0xF4, 0xBD, 0xFE, 0xFC, 0x19, 0xAD, 0x34,
+ 0x45, 0x4C, 0x15, 0x11, 0x34, 0x6B, 0x60, 0x44, 0x77, 0xC8, 0x2A, 0xCE, 0x30, 0x1B, 0x96, 0x6C,
+ 0x35, 0xAF, 0x54, 0x8E, 0x05, 0x67, 0xFF, 0x5B, 0x8C, 0xFB, 0x24, 0x2A, 0xDC, 0xD8, 0x73, 0xBE,
+ 0x34, 0x0C, 0x98, 0x1C, 0x1C, 0xA0, 0x62, 0x93, 0xF3, 0x74, 0xA4, 0x2A, 0x20, 0xDB, 0x46, 0xF3,
+ 0x01, 0x26, 0x95, 0xB4, 0x9D, 0x6C, 0xFC, 0xD5, 0xC8, 0x5F, 0xD7, 0x9F, 0x35, 0x76, 0xC5, 0x4B,
+ 0x31, 0x3D, 0x25, 0x7E, 0xDC, 0x0C, 0x9A, 0x87, 0x14, 0xC9, 0x91, 0x09, 0x8F, 0xCC, 0x5A, 0xE8,
+ 0x81, 0x04, 0x82, 0xFE, 0x97, 0x32, 0xC3, 0x29, 0x6D, 0x81, 0xBA, 0xE6, 0xD9, 0x62, 0x06, 0xAF,
+ 0x99, 0x02, 0x96, 0xEE, 0x65, 0xDB, 0x13, 0x1D, 0xBE, 0x4F, 0xE7, 0x78, 0x2F, 0x6D, 0x35, 0x69,
+ 0x2C, 0xB4, 0x6A, 0x11, 0xA7, 0xD5, 0x4B, 0x42, 0x7D, 0x1F, 0xB6, 0x72, 0x3A, 0x63, 0x77, 0xD9,
+ 0xE4, 0xD6, 0x79, 0x64, 0x17, 0xAA, 0xF1, 0xB4, 0xAB, 0xC4, 0x59, 0xBD, 0xD7, 0x14, 0xC8, 0x86,
+ 0x20, 0x5D, 0x5A, 0xEA, 0x84, 0x38, 0xD6, 0x4C, 0xCA, 0xD9, 0x4B, 0x52, 0xF2, 0x10, 0xCB, 0x0F,
+ 0x0E, 0xF2, 0x11, 0x2A, 0x64, 0x5E, 0x72, 0x5A, 0x92, 0xF6, 0x8F, 0x2B, 0x3D, 0xD2, 0xB7, 0xD8,
+ 0x1B, 0x4F, 0x99, 0x52, 0x01, 0x3A, 0xFD, 0xE4, 0x9B, 0xAE, 0x6C, 0xDE, 0xCD, 0x12, 0x77, 0xDF,
+ 0x90, 0x28, 0x85, 0x7E, 0xB3, 0x01, 0x83, 0xC7, 0x55, 0x03, 0x7C, 0xEC, 0x90, 0x05, 0xAE, 0x1C,
+ 0xA9, 0x77, 0xC6, 0x7E, 0x16, 0x95, 0xFA, 0xC9, 0x5C, 0xAC, 0xC0, 0x36, 0x45, 0x87, 0x30, 0xD0,
+ 0x18, 0x67, 0x6D, 0xC1, 0xD9, 0x39, 0x06, 0x3D, 0xD5, 0x49, 0x81, 0x63, 0xEC, 0x0D, 0xBA, 0x65,
+ 0x65, 0xE8, 0xA9, 0x00, 0x5E, 0xD7, 0xA4, 0xFF, 0x15, 0x92, 0x91, 0xC0, 0x61, 0xEE, 0x8F, 0x40,
+ 0x3C, 0x07, 0x57, 0xE5, 0xC1, 0x13, 0xBC, 0x68, 0xF6, 0xE4, 0x40, 0x9A, 0x0B, 0x04, 0xAE, 0xF4,
+ 0xE9, 0x2F, 0x0D, 0xCE, 0x90, 0x0C, 0x0D, 0xFE, 0xB2, 0x5E, 0x1B, 0x43, 0x03, 0x93, 0xED, 0x7B,
+ 0x67, 0x97, 0xD8, 0xDB, 0xD3, 0x3F, 0x0A, 0x0F, 0x75, 0x3B, 0xEE, 0x73, 0xFE, 0x21, 0xFE, 0x22,
+ 0xAD, 0x13, 0xFE, 0x1A, 0xBD, 0x57, 0xD5, 0x0D, 0xF7, 0x4A, 0xAD, 0xEF, 0xC1, 0xD0, 0x7E, 0x39,
+ 0xAA, 0xA3, 0xCF, 0x81, 0x0D, 0x58, 0xB8, 0xA6, 0x1E, 0x8D, 0x8D, 0x4E, 0x75, 0x68, 0xF8, 0xF9,
+ 0x08, 0x2F, 0x40, 0x0E, 0x87, 0x5B, 0xFE, 0x85, 0x66, 0xEC, 0x73, 0xAB, 0xD9, 0x52, 0xBE, 0x8E,
+ 0xFA, 0x44, 0xE7, 0x53, 0xB7, 0xB6, 0xA6, 0x3B, 0x15, 0x92, 0x45, 0xFD, 0x55, 0xBE, 0x03, 0xEE,
+ 0x68, 0xE2, 0x5A, 0x25, 0xF0, 0x02, 0x82, 0x12, 0xDA, 0x4B, 0x47, 0x28, 0xF6, 0x2E, 0xBC, 0x70,
+ 0xF3, 0x09, 0x65, 0x86, 0x5C, 0x91, 0xE4, 0x75, 0x99, 0x5E, 0x6C, 0x0A, 0x50, 0x0C, 0xF2, 0x06,
+ 0x1A, 0x2D, 0x3B, 0x5D, 0x43, 0xAD, 0xFA, 0x6F, 0x52, 0x69, 0x75, 0x12, 0x8F, 0x40, 0x29, 0x8C,
+ 0xBA, 0x22, 0xCC, 0x20, 0x9D, 0x7E, 0x8F, 0x03, 0x68, 0x06, 0x68, 0x6F, 0xBC, 0xAD, 0xAA, 0x52,
+ 0x8A, 0xA0, 0xF9, 0x84, 0x68, 0xDA, 0x7C, 0x49, 0xBC, 0xB1, 0xEE, 0x12, 0xDA, 0xF9, 0xC6, 0x45,
+ 0xA3, 0xE1, 0xB0, 0x35, 0xF2, 0xFD, 0xFD, 0x56, 0x84, 0xEC, 0x01, 0xB8, 0xCE, 0xF9, 0x1C, 0x67,
+ 0x44, 0x7B, 0x34, 0xDD, 0xEA, 0xEB, 0x7A, 0x87, 0x34, 0xD6, 0x64, 0xC9, 0x26, 0x5A, 0xD7, 0xC2,
+ 0x3D, 0x0C, 0x7D, 0xFE, 0xC4, 0x54, 0xA9, 0x3A, 0x85, 0x55, 0x79, 0x82, 0x88, 0xAC, 0x02, 0x2D,
+ 0x63, 0xEA, 0x57, 0xCD, 0x75, 0x16, 0xBB, 0xB1, 0xB5, 0x83, 0x7B, 0xF7, 0x93, 0x44, 0x8C, 0xB8,
+ 0x22, 0xDA, 0x7C, 0x30, 0x99, 0x09, 0x59, 0x96, 0x39, 0x7F, 0x4F, 0x7D, 0x89, 0x68, 0xBC, 0x41,
+ 0x4A, 0xAE, 0x04, 0x41, 0x00, 0x84, 0x55, 0x43, 0x5E, 0x19, 0x74, 0x6D, 0xB6, 0xD5, 0x73, 0x34,
+ 0x20, 0x7A, 0xE8, 0x91, 0xEC, 0xAD, 0xEF, 0xCE, 0xC4, 0xE9, 0xA8, 0xF1, 0xCA, 0x2F, 0x16, 0xEC,
+ 0x3D, 0x60, 0xA7, 0xA8, 0x41, 0x55, 0x67, 0x2B, 0x35, 0xD8, 0xB2, 0xC4, 0x0B, 0xEF, 0x7B, 0xC6,
+ 0x1F, 0x0E, 0xF3, 0x76, 0xEC, 0x40, 0xF3, 0xED, 0xFE, 0x60, 0x6E, 0xE5, 0x31, 0xFD, 0x03, 0x5F,
+ 0x5D, 0xF8, 0x5C, 0x7C, 0xA7, 0x7E, 0xFF, 0xA4, 0x0C, 0x19, 0xEE, 0xA5, 0x15, 0xC3, 0xE3, 0xB3,
+ 0x6D, 0x7D, 0x36, 0xD5, 0xE1, 0x19, 0xBD, 0x46, 0xF7, 0x67, 0x88, 0xBC, 0x9F, 0x1F, 0xFC, 0x7D,
+ 0xE6, 0x62, 0x9A, 0xBF, 0xF7, 0x18, 0x31, 0xD8, 0x55, 0xEF, 0xDC, 0x8D, 0x47, 0x2A, 0x64, 0xC6,
+ 0x0B, 0x0C, 0xE7, 0xC0, 0xF5, 0xA3, 0xF3, 0x67, 0xE1, 0x1D, 0x3C, 0xDA, 0xF7, 0xDA, 0x57, 0xFF,
+ 0xDB, 0x20, 0xE5, 0x67, 0x2D, 0xCB, 0x3A, 0x9A, 0x5D, 0xC9, 0xD6, 0xC1, 0xF5, 0x1B, 0x8C, 0x11,
+ 0xF8, 0x04, 0x04, 0x62, 0xCF, 0xF8, 0xE9, 0x66, 0x12, 0xD6, 0xD2, 0xC1, 0x97, 0x12, 0x53, 0x4B,
+ 0x68, 0x9C, 0x52, 0x53, 0xE9, 0xC2, 0x2A, 0x45, 0x70, 0x98, 0xC2, 0xF3, 0xDE, 0x64, 0x98, 0x86,
+ 0xEA, 0x06, 0x75, 0x1A, 0x4C, 0xFE, 0x74, 0xFA, 0xCD, 0xD7, 0xD2, 0x1E, 0x01, 0x5F, 0x61, 0xEA,
+ 0xB0, 0x8A, 0xDB, 0xEF, 0x2C, 0xB8, 0x2A, 0x87, 0x6E, 0x9A, 0xD3, 0xB3, 0xD6, 0x62, 0x96, 0x5E,
+ 0x50, 0x8B, 0xD6, 0x7F, 0x05, 0x5B, 0x25, 0x22, 0xAE, 0xAE, 0x7F, 0x70, 0xBE, 0x20, 0x50, 0x74,
+ 0xCB, 0xA8, 0xC6, 0x6C, 0x39, 0xB9, 0x1F, 0x65, 0xB4, 0xD9, 0x22, 0x54, 0xB6, 0xB5, 0x4D, 0xBE,
+ 0x53, 0x94, 0x13, 0x7F, 0xE1, 0xB8, 0x78, 0xA8, 0xD6, 0x1F, 0xDA, 0xB5, 0xFE, 0xB8, 0xB3, 0xDA,
+ 0x1F, 0x1A, 0xD5, 0x6E, 0x83, 0xA6, 0x49, 0x53, 0xB3, 0x91, 0x4C, 0x68, 0x28, 0x55, 0x4E, 0xFC,
+ 0xA5, 0x76, 0x97, 0x2F, 0x61, 0xC1, 0x02, 0xDF, 0x31, 0x43, 0x6D, 0x42, 0x0C, 0x6F, 0xDE, 0xA6,
+ 0xC2, 0x26, 0x8D, 0xBE, 0x63, 0xC4, 0x91, 0x4B, 0x1B, 0xAE, 0xDC, 0x51, 0x7F, 0x0F, 0x7E, 0x90,
+ 0x29, 0xC2, 0x53, 0x93, 0x48, 0x90, 0x51, 0x7A, 0x0C, 0x14, 0x91, 0xBE, 0xE9, 0xE1, 0x50, 0xF8,
+ 0x31, 0x73, 0x03, 0xD4, 0x10, 0xC0, 0x90, 0x3E, 0x98, 0x74, 0xB6, 0xDE, 0x2D, 0x14, 0x64, 0x4A,
+ 0x73, 0xEB, 0xEF, 0x7D, 0xAD, 0x76, 0xFA, 0x18, 0x98, 0xFD, 0xEF, 0xF2, 0x2A, 0x24, 0x3F, 0x64,
+ 0x7D, 0xF4, 0xC3, 0xA8, 0xEC, 0x69, 0xD6, 0xF5, 0xA8, 0x99, 0xAA, 0x40, 0x47, 0x98, 0x22, 0x31,
+ 0x21, 0xF3, 0xB4, 0x2E, 0x0D, 0x2D, 0xA1, 0x8B, 0x92, 0x98, 0x80, 0x08, 0x1F, 0xC9, 0x70, 0x67,
+ 0xCF, 0xF3, 0xC3, 0x07, 0x0E, 0x62, 0x4D, 0x82, 0xF6, 0xA6, 0xF7, 0x2B, 0x3D, 0x5F, 0xA8, 0x89,
+ 0xF8, 0x75, 0x37, 0xE5, 0xEF, 0xAD, 0xD8, 0xA3, 0xF7, 0x76, 0x46, 0x24, 0xEC, 0xDB, 0x4B, 0x99,
+ 0x0D, 0x75, 0x19, 0x3B, 0x4E, 0xC1, 0xC8, 0x0D, 0x3C, 0xEE, 0x02, 0xFB, 0x1D, 0x2D, 0xC9, 0x42,
+ 0xDC, 0x18, 0x35, 0xC5, 0x70, 0x65, 0xB7, 0x11, 0xB4, 0x18, 0x38, 0x6B, 0x77, 0x5A, 0xE3, 0x5A,
+ 0x6B, 0x29, 0x0C, 0x8E, 0xB6, 0xFC, 0x2D, 0x79, 0xE3, 0xE2, 0x42, 0xE4, 0x08, 0xF2, 0x07, 0x73,
+ 0xE0, 0x08, 0xA0, 0x22, 0xF6, 0xA2, 0xDC, 0x79, 0x16, 0x1F, 0xB9, 0xDC, 0x1D, 0xE0, 0x5A, 0xD6,
+ 0x60, 0xA2, 0x42, 0xF3, 0x70, 0x01, 0xDC, 0xB6, 0xD0, 0x69, 0x80, 0x2D, 0x9A, 0x74, 0x3B, 0x45,
+ 0xE0, 0xA6, 0xDA, 0x3F, 0xD2, 0x1A, 0x4D, 0xCE, 0x45, 0x89, 0xEE, 0x63, 0x5E, 0x24, 0x42, 0x54,
+ 0x4A, 0x28, 0xC9, 0x64, 0xFF, 0xC8, 0xD9, 0x88, 0xF5, 0x2B, 0x39, 0xEF, 0x11, 0x6C, 0x9E, 0x8D,
+ 0xEC, 0x3B, 0x92, 0xB8, 0xCB, 0x2D, 0x43, 0x77, 0x82, 0x05, 0x45, 0x52, 0x5B, 0x91, 0xB4, 0x5A,
+ 0x35, 0x9E, 0x72, 0xDD, 0xB2, 0x81, 0x50, 0x27, 0x4A, 0xCE, 0xBA, 0x2B, 0x61, 0x74, 0x82, 0xCE,
+ 0xEC, 0x9F, 0x8E, 0xE4, 0x60, 0x34, 0xA2, 0xC8, 0x31, 0x4A, 0xBA, 0xDA, 0xC1, 0x3B, 0x94, 0x35,
+ 0x26, 0x24, 0x7D, 0x60, 0x31, 0x8E, 0xD3, 0xCF, 0xFE, 0xD7, 0xDA, 0x87, 0x9E, 0xCD, 0xE1, 0x69,
+ 0x8C, 0xA8, 0x17, 0x80, 0xF0, 0xB1, 0x33, 0xCD, 0x99, 0xB8, 0x97, 0xFA, 0x98, 0xDD, 0xA2, 0xF2,
+ 0xA6, 0xE8, 0xA6, 0x81, 0x7B, 0xA6, 0x95, 0x59, 0xE2, 0x6C, 0x46, 0x10, 0xD3, 0xC5, 0x45, 0xAD,
+ 0x15, 0x32, 0x40, 0x93, 0x44, 0x4F, 0x55, 0xD7, 0x8C, 0xE2, 0xAB, 0xA1, 0x3B, 0x66, 0x1A, 0x9F,
+ 0x66, 0x56, 0x24, 0x34, 0xCF, 0x6D, 0x58, 0x3F, 0x4E, 0x26, 0x7A, 0x5D, 0x15, 0x3A, 0x23, 0x5A,
+ 0xA6, 0x45, 0xE4, 0x84, 0x3D, 0x80, 0xAE, 0x5B, 0x77, 0x4B, 0xCB, 0x5B, 0x44, 0x0D, 0x08, 0x50,
+ 0xF1, 0x81, 0xB0, 0x8B, 0xEB, 0x2E, 0xE9, 0x40, 0x5B, 0xF9, 0x99, 0x75, 0x53, 0x05, 0x78, 0x87,
+ 0xAA, 0xA8, 0x0F, 0xDE, 0x7A, 0xCD, 0x4A, 0xD4, 0x36, 0x9F, 0xE3, 0x61, 0xF4, 0xE2, 0xBB, 0x87,
+ 0xEA, 0xD1, 0x70, 0x2D, 0xA1, 0x1D, 0x17, 0x97, 0x8D, 0x8C, 0x6F, 0x17, 0xB1, 0x36, 0x16, 0x69,
+ 0x43, 0x22, 0xA0, 0x0E, 0x03, 0x11, 0x37, 0xAC, 0xAB, 0x4A, 0x57, 0x80, 0x76, 0x70, 0x80, 0xCD,
+ 0x96, 0x36, 0x58, 0xE0, 0x5C, 0x4C, 0x89, 0x1C, 0x3A, 0xEA, 0x9A, 0x5F, 0x49, 0x8B, 0x00, 0x91,
+ 0xA8, 0xD3, 0x38, 0x40, 0x48, 0xF5, 0xCE, 0x75, 0xDE, 0x94, 0x29, 0x6C, 0x40, 0x2E, 0x9C, 0x44,
+ 0xE2, 0x42, 0xAB, 0x7C, 0xC5, 0x45, 0xC2, 0x3B, 0x21, 0x7E, 0x08, 0xA9, 0xD5, 0x17, 0x03, 0x61,
+ 0xE9, 0xF8, 0x91, 0xA2, 0xCB, 0x88, 0x46, 0x0E, 0x54, 0x19, 0x41, 0x90, 0x7C, 0xA6, 0xF7, 0x60,
+ 0x36, 0x21, 0xFA, 0x69, 0xF1, 0x18, 0xCF, 0x5F, 0x07, 0x78, 0xF4, 0x18, 0x1F, 0xD3, 0xE4, 0xE5,
+ 0x8C, 0x13, 0xF0, 0x59, 0x07, 0x4F, 0xD4, 0xD3, 0x7A, 0xED, 0xC7, 0x7C, 0xDF, 0xEA, 0x37, 0x93,
+ 0x83, 0xA8, 0x0E, 0xC8, 0x60, 0x99, 0x2C, 0x96, 0x99, 0xAB, 0xFB, 0xCD, 0xEC, 0xB1, 0xF3, 0x14,
+ 0x72, 0x34, 0xF5, 0xDE, 0xE4, 0x6B, 0x7C, 0xBE, 0xA6, 0x0B, 0x22, 0xF8, 0xFD, 0x34, 0x87, 0x7F,
+ 0x95, 0x2B, 0x04, 0xBE, 0x4C, 0x96, 0x95, 0x37, 0xA3, 0x87, 0x75, 0x3B, 0x5C, 0x3D, 0x3F, 0x9A,
+ 0xDB, 0x5D, 0x4A, 0xC7, 0x38, 0xAE, 0xFD, 0xBC, 0x6A, 0x42, 0x98, 0x00, 0x9C, 0x16, 0x49, 0x15,
+ 0x60, 0x1F, 0xB7, 0x60, 0x93, 0xBF, 0x82, 0x09, 0xAF, 0xD7, 0x79, 0xAE, 0x5C, 0xE8, 0xF8, 0xFF,
+ 0x46, 0xB5, 0x75, 0xDD, 0xA8, 0xC4, 0xCF, 0xD9, 0x73, 0x7A, 0x2B, 0x9F, 0x9A, 0xD2, 0xB8, 0xFF,
+ 0xA9, 0x5F, 0x28, 0x08, 0x33, 0x49, 0xE4, 0x25, 0x91, 0x7C, 0x5C, 0xAD, 0xAC, 0xEE, 0x56, 0xFC,
+ 0x58, 0x1D, 0x3F, 0xF9, 0x5E, 0xCB, 0xFB, 0x27, 0xCD, 0x58, 0xC8, 0xFF, 0x12, 0xE6, 0xFE, 0x4C,
+ 0x69, 0x74, 0x93, 0x11, 0x91, 0xA7, 0x72, 0x78, 0x00, 0x2C, 0xF7, 0x45, 0xDA, 0xEE, 0x95, 0xB8,
+ 0xE8, 0xE9, 0x21, 0x40, 0xB4, 0x98, 0x74, 0xA5, 0xBC, 0x60, 0xE6, 0x37, 0x85, 0xEF, 0x3A, 0x67,
+ 0x77, 0x67, 0x43, 0x1D, 0x2B, 0xAC, 0xFE, 0xDF, 0x44, 0x04, 0x40, 0x2B, 0x56, 0x6C, 0xB1, 0x57,
+ 0x57, 0x9F, 0x33, 0x7C, 0xBD, 0xB9, 0x3A, 0x4F, 0x48, 0x80, 0x3A, 0xF1, 0xC8, 0x7D, 0xE2, 0x2D,
+ 0xBD, 0x79, 0xD0, 0xB8, 0x9D, 0xD0, 0xCC, 0x86, 0x72, 0x94, 0x69, 0x10, 0x91, 0x66, 0x7C, 0xB8,
+ 0xC1, 0xB0, 0xAA, 0x44, 0x9A, 0xF1, 0x9A, 0xE6, 0xE4, 0xDD, 0xD7, 0x39, 0x74, 0xF6, 0x68, 0x86,
+ 0x32, 0x06, 0xF7, 0x9B, 0x27, 0xD1, 0x53, 0x34, 0x8D, 0xEB, 0x5A, 0x14, 0x87, 0x06, 0x86, 0x1B,
+ 0xB8, 0x9A, 0x75, 0x36, 0xC8, 0x29, 0xCF, 0x99, 0x1C, 0xFA, 0x7C, 0xB8, 0x82, 0x62, 0x65, 0x7C,
+ 0x90, 0x07, 0x8F, 0xF1, 0xD7, 0xCF, 0xA9, 0xDF, 0x4F, 0x90, 0x7F, 0x04, 0x42, 0xE8, 0x38, 0x3A,
+ 0xE4, 0x9B, 0xC5, 0x74, 0xCB, 0xFD, 0x49, 0x94, 0x1F, 0xCD, 0x1F, 0xA3, 0xE2, 0x4E, 0x61, 0x75,
+ 0x0D, 0x70, 0xEB, 0x18, 0xE4, 0xA3, 0xC9, 0x79, 0x5D, 0x53, 0xB3, 0x87, 0x74, 0x0A, 0x42, 0x54,
+ 0x3F, 0x99, 0x0F, 0x7D, 0xFA, 0x85, 0x2B, 0xCC, 0xC7, 0x19, 0xC4, 0xED, 0xE1, 0x90, 0xFE, 0x0A,
+ 0x92, 0xCD, 0xB0, 0x09, 0x43, 0x15, 0xE1, 0xBE, 0x3B, 0x09, 0x27, 0x71, 0xA7, 0x65, 0x8E, 0x10,
+ 0x50, 0x7B, 0x44, 0x4F, 0xC6, 0xA6, 0x5B, 0xD6, 0xAF, 0x01, 0x44, 0x9D, 0x55, 0xA3, 0xD6, 0xED,
+ 0xCD, 0x8F, 0xEE, 0x85, 0xEB, 0x29, 0x0D, 0x1A, 0x3E, 0x64, 0xAC, 0x67, 0xB7, 0xF9, 0xF1, 0xF2,
+ 0x68, 0x4E, 0x3E, 0x2F, 0x88, 0x9F, 0x9C, 0xCF, 0x14, 0x28, 0xC9, 0x8B, 0xCF, 0x4D, 0x00, 0x12,
+ 0xD5, 0xC4, 0x37, 0xB0, 0x43, 0x63, 0x59, 0x21, 0xBF, 0xC2, 0x27, 0x85, 0x03, 0x44, 0x75, 0xCD,
+ 0xC4, 0x10, 0x52, 0x69, 0x71, 0x5B, 0x95, 0x90, 0x96, 0x5F, 0xDD, 0x8F, 0x6A, 0xA5, 0xF0, 0x9B,
+ 0x8C, 0x45, 0xA0, 0xEB, 0xC5, 0xC5, 0x3F, 0x10, 0xC9, 0x5A, 0x8B, 0x14, 0x13, 0x3F, 0x53, 0x04,
+ 0xAD, 0xCD, 0x54, 0x46, 0xA9, 0xB9, 0x58, 0x22, 0x50, 0x60, 0xC3, 0x5E, 0x3B, 0xC8, 0x2D, 0x0A,
+ 0xC8, 0xDE, 0xF3, 0x7C, 0x71, 0xD7, 0x78, 0x7B, 0x24, 0x6E, 0x5D, 0x6F, 0x2B, 0xD1, 0x91, 0x8D,
+ 0xDA, 0x04, 0xB9, 0xD3, 0x7F, 0x91, 0xEF, 0x78, 0xBD, 0x94, 0x68, 0x6F, 0x33, 0x01, 0x1B, 0x7E,
+ 0x0C, 0xD9, 0x3B, 0x27, 0x7D, 0x08, 0xCA, 0x70, 0xD3, 0xB8, 0x2E, 0x8B, 0x7F, 0x42, 0x99, 0x67,
+ 0xE0, 0x4C, 0x92, 0x8C, 0x11, 0x20, 0x98, 0x9B, 0x73, 0x25, 0xC5, 0x5D, 0xB4, 0x58, 0x58, 0x15,
+ 0xB3, 0x14, 0x52, 0x23, 0x44, 0x05, 0x51, 0x9E, 0x16, 0xA5, 0x39, 0x51, 0x9C, 0xB2, 0xCB, 0x9C,
+ 0xB8, 0xBC, 0xCC, 0x6F, 0x7A, 0xF6, 0xE0, 0x4A, 0x9E, 0x6F, 0x92, 0x42, 0xC5, 0xAB, 0x32, 0xC9,
+ 0xEE, 0x3C, 0xA4, 0xCD, 0xCD, 0x2F, 0x2E, 0x56, 0x7D, 0x67, 0x9F, 0x47, 0x8F, 0xD5, 0x27, 0xB1,
+ 0xAB, 0xFF, 0x9C, 0x28, 0xF3, 0x4F, 0x6A, 0xD8, 0x57, 0x96, 0xE4, 0xD4, 0x80, 0x0C, 0x77, 0x0F,
+ 0xDC, 0x58, 0xB5, 0x32, 0x39, 0x95, 0xBF, 0xBA, 0xA0, 0xFA, 0x54, 0x65, 0xB7, 0xE6, 0x54, 0xC1,
+ 0x41, 0xB1, 0x4C, 0xC4, 0x3A, 0x91, 0x7C, 0xB9, 0x76, 0x84, 0x54, 0xB3, 0x6B, 0x24, 0x7E, 0x68,
+ 0x7E, 0x81, 0xA4, 0xE6, 0x65, 0xA1, 0xD6, 0x5D, 0x21, 0xE4, 0xB9, 0xA4, 0xE2, 0x8F, 0xEA, 0xB7,
+ 0x57, 0xF9, 0x62, 0xB3, 0x4A, 0x1E, 0xD5, 0x67, 0x4F, 0x50, 0xD7, 0x4F, 0xF1, 0x75, 0x5C, 0x27,
+ 0xF3, 0xAB, 0x38, 0x28, 0xE7, 0x45, 0xBA, 0xAE, 0x70, 0x69, 0x68, 0x8E, 0xA3, 0x43, 0x41, 0x89,
+ 0x06, 0xB2, 0xCF, 0x8A, 0xF8, 0x82, 0xC1, 0xA5, 0xF9, 0x84, 0xE2, 0xCB, 0x1D, 0x4F, 0x28, 0xFA,
+ 0x4B, 0x54, 0xF1, 0x81, 0xAF, 0x33, 0xA9, 0x47, 0x7E, 0x78, 0x28, 0x5E, 0x80, 0x61, 0xB6, 0xD2,
+ 0xF5, 0xD3, 0x48, 0x42, 0x87, 0x4C, 0xA6, 0xEE, 0xD3, 0x3F, 0x15, 0xE1, 0xCB, 0xE6, 0x2B, 0x41,
+ 0x4B, 0x7A, 0x79, 0x60, 0xC4, 0x45, 0x4F, 0x56, 0x80, 0xBF, 0x08, 0xE9, 0x73, 0x0A, 0xF0, 0xB1,
+ 0x03, 0x48, 0x68, 0x7E, 0x59, 0x27, 0xAE, 0xDA, 0xD1, 0x1C, 0x2A, 0x6C, 0x5C, 0x82, 0xD4, 0xCF,
+ 0xFC, 0x3F, 0xBB, 0x3D, 0x7E, 0x62, 0xC2, 0x1E, 0xDA, 0xC8, 0x72, 0x6E, 0xE0, 0x44, 0x16, 0xC3,
+ 0x85, 0x54, 0x14, 0xDC, 0x51, 0x77, 0xE3, 0x86, 0x62, 0xBB, 0x01, 0xF3, 0x88, 0x97, 0x0C, 0x70,
+ 0xDD, 0x32, 0xC8, 0x75, 0xDA, 0x5A, 0x18, 0x32, 0x2E, 0x80, 0xFA, 0x2A, 0x72, 0x08, 0x17, 0x4E,
+ 0x61, 0x51, 0x72, 0xCC, 0x17, 0x68, 0x9F, 0x79, 0x08, 0x3D, 0x51, 0x41, 0xE0, 0xBA, 0x7C, 0x15,
+ 0x4E, 0x9F, 0x9A, 0xC4, 0x63, 0x7C, 0x5F, 0x14, 0xF9, 0x66, 0x2D, 0xB3, 0x99, 0x2F, 0xA7, 0x44,
+ 0x55, 0x34, 0x0A, 0x54, 0x84, 0x45, 0x64, 0x6E, 0x0E, 0x36, 0xB2, 0xA2, 0xE1, 0x8F, 0x5A, 0x59,
+ 0xF1, 0x53, 0xC8, 0xEC, 0xF8, 0xED, 0x29, 0xF3, 0xA3, 0x1A, 0x7E, 0x38, 0x3D, 0x04, 0x12, 0xC4,
+ 0x7F, 0x33, 0x07, 0x79, 0x5F, 0xBB, 0x4F, 0x17, 0x1B, 0x71, 0x4A, 0xF4, 0xE1, 0x0F, 0xB9, 0x4E,
+ 0x76, 0xBD, 0x68, 0xCE, 0xAE, 0x7C, 0x82, 0xF0, 0xC3, 0xDE, 0xF7, 0x9C, 0x74, 0xA3, 0x4C, 0x0D,
+ 0x38, 0x3F, 0x5A, 0x02, 0x4F, 0x79, 0xE9, 0x6F, 0x62, 0x2C, 0x60, 0x13, 0x16, 0xCF, 0x67, 0xEE,
+ 0xBB, 0xB4, 0x1D, 0x2F, 0x6F, 0x8E, 0x9B, 0x1A, 0xE3, 0x4C, 0x85, 0x71, 0x23, 0x3F, 0x72, 0x2B,
+ 0xBC, 0x8B, 0x55, 0x7E, 0x1E, 0xAF, 0x48, 0xA7, 0xE8, 0xF1, 0x35, 0x6A, 0x79, 0xC6, 0x54, 0xED,
+ 0x34, 0x28, 0xD9, 0x2F, 0x92, 0x11, 0xCF, 0x71, 0x44, 0x81, 0x25, 0x48, 0x2A, 0x0A, 0xE8, 0x75,
+ 0xE5, 0x70, 0xCC, 0xF0, 0x43, 0x41, 0x86, 0x15, 0xC1, 0x01, 0xFE, 0x5C, 0x18, 0xE8, 0xC2, 0x89,
+ 0x81, 0x08, 0x84, 0x75, 0x31, 0x0D, 0x75, 0x04, 0x58, 0xFA, 0xA5, 0xAF, 0x2B, 0x2C, 0x5B, 0x0A,
+ 0x05, 0x53, 0xF4, 0x5B, 0x1D, 0xFA, 0xAD, 0x5C, 0x69, 0x99, 0x8E, 0xD5, 0x95, 0x08, 0xEB, 0x8A,
+ 0xF0, 0x51, 0xBD, 0xFF, 0xEB, 0xC9, 0xD9, 0xCD, 0xF0, 0xE8, 0x89, 0x5D, 0xDD, 0xDB, 0x5E, 0x97,
+ 0x54, 0xCD, 0x67, 0xFE, 0xAB, 0x9D, 0xC8, 0x47, 0xBA, 0x23, 0x5A, 0x60, 0xEE, 0x2E, 0xED, 0xDC,
+ 0x2D, 0x8E, 0xF1, 0xBF, 0xBC, 0xE6, 0xC1, 0x2E, 0xAB, 0x16, 0x74, 0xF8, 0x1D, 0xF2, 0x33, 0x34,
+ 0x6D, 0x1D, 0x11, 0x98, 0x5A, 0xBD, 0x44, 0x6B, 0x91, 0x3B, 0x3E, 0x9D, 0xF2, 0x59, 0x98, 0x5B,
+ 0xD5, 0xD0, 0x95, 0xBE, 0x3E, 0x0F, 0xD9, 0x11, 0xDB, 0x92, 0x2E, 0x9B, 0x0F, 0x8F, 0xED, 0xC0,
+ 0x7A, 0xF0, 0x88, 0xBC, 0x50, 0xE4, 0x57, 0x4E, 0xE2, 0x62, 0x09, 0xDA, 0x41, 0xF7, 0x71, 0x80,
+ 0x4D, 0x74, 0x41, 0x07, 0x65, 0x5D, 0x63, 0x8E, 0xF5, 0x5E, 0x10, 0xB1, 0x83, 0x04, 0x36, 0x44,
+ 0x09, 0x9D, 0x8E, 0x2E, 0xAB, 0xAB, 0xD5, 0xB7, 0x45, 0x22, 0x4D, 0x98, 0x51, 0xE7, 0x70, 0x43,
+ 0x34, 0xD1, 0x1C, 0xC9, 0x87, 0xDA, 0xCF, 0xF3, 0x1C, 0xE4, 0x1A, 0xBA, 0xE8, 0x20, 0x05, 0x3B,
+ 0xC0, 0xD8, 0x35, 0xC2, 0x16, 0xA0, 0xD3, 0x96, 0xAE, 0xB9, 0xB8, 0xEB, 0xD9, 0x15, 0xF4, 0x88,
+ 0x72, 0xCC, 0xC4, 0x22, 0x0E, 0x33, 0x42, 0xDC, 0x0D, 0x56, 0xE4, 0x39, 0x1D, 0xD0, 0xAD, 0x12,
+ 0x62, 0xE1, 0xBC, 0x90, 0xB9, 0x9E, 0x2E, 0x94, 0x0B, 0x0E, 0x90, 0x6C, 0x0D, 0xF3, 0xCB, 0x9C,
+ 0xCC, 0x5C, 0x52, 0xE3, 0x5F, 0xCA, 0x99, 0xE6, 0x55, 0x94, 0x52, 0xCD, 0x20, 0x6B, 0xB0, 0xE7,
+ 0xF5, 0x14, 0x5B, 0x1A, 0x43, 0x9E, 0x47, 0x1E, 0xAB, 0xE2, 0xEE, 0xC8, 0xC9, 0x0D, 0x71, 0xEE,
+ 0x73, 0xA7, 0xCD, 0x78, 0x3A, 0xA7, 0x36, 0x2F, 0xF5, 0x6A, 0x69, 0x57, 0x93, 0x1E, 0xF1, 0x70,
+ 0xBA, 0x35, 0x4D, 0x76, 0x2E, 0x99, 0x50, 0x3C, 0xE7, 0x13, 0x74, 0xFA, 0x76, 0x84, 0x13, 0x91,
+ 0x18, 0xB4, 0x91, 0x3F, 0x1A, 0x06, 0x75, 0xE0, 0x00, 0xE8, 0x4D, 0xE2, 0x3A, 0xFA, 0x37, 0xD1,
+ 0x6F, 0x9C, 0xE8, 0xB1, 0x8D, 0x3E, 0x61, 0x70, 0x6E, 0x5C, 0x7B, 0x6D, 0xDA, 0x18, 0xE9, 0x94,
+ 0x97, 0x4D, 0x6F, 0xAB, 0x8E, 0x2C, 0x79, 0x4B, 0x17, 0x80, 0x7D, 0x8F, 0x7D, 0xB5, 0x32, 0xA3,
+ 0xDF, 0x72, 0x6E, 0x65, 0x55, 0xA2, 0x12, 0x27, 0x62, 0xA7, 0x34, 0x74, 0x9F, 0x56, 0x5B, 0x74,
+ 0xAF, 0xF5, 0x9F, 0xDD, 0x1B, 0xB7, 0x2C, 0xC4, 0x2C, 0x40, 0x93, 0x0A, 0x2D, 0xA8, 0x26, 0xB5,
+ 0x24, 0x57, 0x5F, 0x72, 0xF5, 0x15, 0xA9, 0x1D, 0x73, 0x87, 0x9E, 0x42, 0x13, 0x52, 0xD3, 0x8A,
+ 0xF5, 0x94, 0x81, 0x74, 0xE2, 0xA7, 0x6C, 0x2D, 0x62, 0xAA, 0x08, 0x55, 0x02, 0x6A, 0xEF, 0x68,
+ 0x7C, 0x32, 0xCA, 0x5D, 0x20, 0xB7, 0xCE, 0xAB, 0x3E, 0x9D, 0x1E, 0x28, 0x67, 0xDF, 0x69, 0x40,
+ 0xFE, 0xCF, 0x0C, 0x54, 0x0C, 0xCC, 0x6B, 0x28, 0x0D, 0x67, 0xF5, 0x39, 0x73, 0x1A, 0xA9, 0x40,
+ 0x35, 0x7D, 0xBC, 0xA6, 0x0F, 0xAA, 0x78, 0xB9, 0x24, 0x9D, 0x6B, 0xDC, 0x67, 0x27, 0xC8, 0x77,
+ 0xE2, 0x2E, 0x48, 0x85, 0x18, 0xF3, 0x0F, 0xA9, 0x79, 0xE5, 0xF7, 0x29, 0xFF, 0x0C, 0x87, 0xFC,
+ 0x88, 0x44, 0x57, 0x2E, 0x92, 0x90, 0xE6, 0x9A, 0x78, 0x7F, 0x2D, 0xA7, 0x49, 0x31, 0x59, 0x59,
+ 0xC3, 0x03, 0xD9, 0x29, 0x66, 0x51, 0x2E, 0x4F, 0x3E, 0xF1, 0x19, 0x81, 0xF3, 0x37, 0x59, 0x7E,
+ 0x3B, 0xE5, 0x39, 0xEE, 0xDE, 0xBC, 0x12, 0x4E, 0xC6, 0x03, 0x97, 0xFC, 0xFE, 0x4A, 0xB1, 0xE3,
+ 0xEE, 0x2F, 0xA3, 0x7B, 0x75, 0x0F, 0x82, 0x79, 0xB8, 0xF1, 0x3E, 0x3D, 0x41, 0xFE, 0xA6, 0x48,
+ 0x2F, 0x2E, 0x92, 0x42, 0x5D, 0x91, 0x4A, 0xE5, 0xE5, 0xCA, 0x42, 0x1B, 0xAE, 0xFB, 0xAA, 0x45,
+ 0xE9, 0x5B, 0x20, 0x5E, 0x21, 0x83, 0xBC, 0x56, 0x42, 0x02, 0xC0, 0x8B, 0xB8, 0xD2, 0x77, 0xDC,
+ 0x59, 0x0E, 0x48, 0x5A, 0xCD, 0x18, 0x91, 0xD4, 0xB4, 0xDD, 0x95, 0x45, 0x8F, 0x09, 0x10, 0x46,
+ 0x65, 0xFA, 0x22, 0x35, 0xE1, 0xB9, 0xEE, 0x99, 0x50, 0xFD, 0x20, 0x79, 0x24, 0xC9, 0x95, 0xB3,
+ 0x66, 0xC7, 0xC9, 0xC1, 0xC7, 0xC4, 0x2D, 0x2E, 0x9F, 0xF8, 0x06, 0xBB, 0xC6, 0xD9, 0x8D, 0xF7,
+ 0x0A, 0xD9, 0xA3, 0x57, 0x78, 0x88, 0x7A, 0x91, 0xA2, 0x9B, 0x8D, 0xAE, 0x09, 0x32, 0xE9, 0xE4,
+ 0x61, 0xBD, 0x90, 0xB8, 0x12, 0x51, 0x99, 0x79, 0x6C, 0x57, 0x22, 0xAE, 0xC2, 0xEA, 0x08, 0xDC,
+ 0xD6, 0xB4, 0x86, 0x56, 0x4F, 0x4B, 0x25, 0x67, 0xCF, 0xD7, 0x52, 0x65, 0x76, 0x86, 0x85, 0x65,
+ 0x7A, 0x49, 0xA9, 0x56, 0xB5, 0x8C, 0xB1, 0x18, 0x67, 0xCB, 0xFA, 0x4A, 0xA5, 0x78, 0xA8, 0x8B,
+ 0x44, 0xA8, 0x5B, 0xE9, 0x9F, 0x11, 0x36, 0xA7, 0xC0, 0x44, 0x6D, 0x18, 0xB8, 0x81, 0x4C, 0x45,
+ 0xC5, 0x45, 0xF7, 0xF2, 0x1C, 0x0F, 0xEF, 0x5B, 0x97, 0xD7, 0x20, 0x21, 0xD1, 0x47, 0x64, 0xFB,
+ 0x78, 0x24, 0xBF, 0x81, 0xCA, 0xA3, 0xA2, 0x9E, 0x69, 0x86, 0x8E, 0xBF, 0xE2, 0x97, 0x6E, 0x6E,
+ 0x28, 0xA0, 0x62, 0xD6, 0xD5, 0xCF, 0x31, 0xC1, 0x3A, 0x06, 0x8C, 0x76, 0x6E, 0x1C, 0x48, 0xB0,
+ 0xDC, 0x78, 0xD7, 0x45, 0x77, 0xEA, 0xB7, 0xC8, 0xF4, 0x3E, 0xC9, 0x5A, 0xFB, 0x84, 0x58, 0xCC,
+ 0x6B, 0xD9, 0xFF, 0x12, 0xAB, 0xEB, 0x7C, 0x45, 0xAC, 0x56, 0xD4, 0x67, 0x07, 0xA3, 0x8F, 0x80,
+ 0xCF, 0x9D, 0x6B, 0xD5, 0x07, 0xCA, 0x6E, 0xBF, 0x7A, 0xF6, 0x71, 0x1F, 0x69, 0x76, 0xBA, 0xBF,
+ 0xDF, 0x5A, 0xBD, 0x64, 0x31, 0xD0, 0x8E, 0x7F, 0x27, 0x2A, 0x89, 0xB9, 0x42, 0x1A, 0x87, 0xDE,
+ 0xFC, 0x76, 0xE7, 0xEB, 0x65, 0x21, 0xAF, 0xA0, 0x24, 0x2A, 0xF2, 0xE5, 0x11, 0xE1, 0xEA, 0x75,
+ 0x3D, 0xAF, 0xAD, 0xD9, 0x5D, 0x91, 0x66, 0x37, 0xBA, 0xA0, 0xF7, 0x1B, 0xCE, 0xD5, 0x91, 0x9E,
+ 0xE0, 0x3A, 0x08, 0x65, 0xE6, 0x03, 0x5D, 0x5C, 0x22, 0x09, 0x87, 0x31, 0x57, 0xA5, 0xBD, 0xD5,
+ 0x8C, 0xB4, 0xBB, 0x1A, 0xC8, 0xDB, 0x31, 0xFF, 0xCB, 0xA8, 0xBD, 0x19, 0x17, 0xBC, 0x19, 0x71,
+ 0x6E, 0xFA, 0xE9, 0x64, 0xD9, 0xD8, 0x92, 0xE1, 0x72, 0x74, 0x8E, 0xB5, 0xA0, 0x20, 0xDA, 0x58,
+ 0x88, 0x9D, 0x65, 0xE7, 0x91, 0x55, 0x88, 0x54, 0x54, 0x70, 0x21, 0x72, 0xCC, 0x0A, 0xD7, 0x71,
+ 0x21, 0x55, 0x86, 0x85, 0x41, 0x2C, 0x99, 0xA0, 0x35, 0x0B, 0xE5, 0x0A, 0x0A, 0xBD, 0xF6, 0x61,
+ 0x2A, 0x5C, 0xCF, 0x15, 0x21, 0x20, 0x61, 0xA7, 0x5B, 0x0B, 0xAD, 0x0C, 0x15, 0x16, 0x71, 0x5D,
+ 0xB2, 0x2B, 0x1E, 0x1A, 0x2D, 0x6B, 0x09, 0x04, 0xFC, 0x38, 0x6E, 0x24, 0xCD, 0xE5, 0xAB, 0x20,
+ 0x5D, 0xA1, 0x30, 0xA3, 0x3B, 0xC9, 0x37, 0x59, 0x45, 0xC2, 0x16, 0xDA, 0x95, 0x9B, 0x35, 0xF4,
+ 0x74, 0x74, 0x81, 0x51, 0x7D, 0xA9, 0xBB, 0xA0, 0x02, 0x5D, 0x16, 0x31, 0xA1, 0x8A, 0x8E, 0xB1,
+ 0xD0, 0xFE, 0x7E, 0x37, 0xCE, 0x5F, 0x20, 0x33, 0x98, 0x40, 0x24, 0xD0, 0x3C, 0xF3, 0xAF, 0xAE,
+ 0x69, 0x0E, 0x06, 0x50, 0x43, 0xBC, 0x86, 0xE0, 0x66, 0x44, 0x24, 0xA7, 0x83, 0x64, 0x47, 0xD6,
+ 0xF9, 0xE4, 0xBA, 0xD9, 0x5F, 0x88, 0x7D, 0x0F, 0x51, 0x55, 0xA8, 0xC8, 0xA1, 0xB9, 0xC5, 0xE4,
+ 0x72, 0xC3, 0xD2, 0x20, 0x49, 0x88, 0xD0, 0x6B, 0x9A, 0xF0, 0x61, 0x3B, 0xD7, 0x6A, 0x7A, 0xF7,
+ 0xF7, 0x15, 0xBA, 0xE0, 0x7D, 0x0C, 0xCF, 0xA8, 0xEE, 0x06, 0x0B, 0xEE, 0x01, 0xB4, 0x15, 0xFE,
+ 0xFB, 0x40, 0xA0, 0x45, 0x0D, 0x2D, 0xB8, 0xAD, 0x7E, 0x11, 0xDC, 0x06, 0xF7, 0x3B, 0x41, 0x8F,
+ 0x17, 0x17, 0xCA, 0xD2, 0x07, 0x41, 0x97, 0xF2, 0xF2, 0xCD, 0x98, 0x92, 0x5F, 0xD6, 0x6A, 0xBD,
+ 0x5D, 0x74, 0x76, 0x36, 0xC2, 0xCB, 0x45, 0x1A, 0x82, 0xF0, 0x05, 0x1A, 0x6C, 0xF4, 0x18, 0xBF,
+ 0x75, 0x40, 0xCA, 0x3A, 0x9F, 0x42, 0x8F, 0x10, 0x04, 0x49, 0xD8, 0xEB, 0x6E, 0x74, 0xCE, 0xDE,
+ 0x46, 0x05, 0x5D, 0x41, 0xBA, 0x18, 0x90, 0x53, 0x31, 0x0D, 0xFB, 0x75, 0x8D, 0x03, 0x41, 0x2E,
+ 0x2C, 0xC5, 0xAB, 0x95, 0xC7, 0x85, 0x1E, 0xFD, 0xC4, 0xE2, 0x7C, 0xA4, 0x21, 0x98, 0x7A, 0x09,
+ 0xB0, 0x2A, 0x38, 0x9F, 0xDE, 0x13, 0x44, 0xC1, 0x3C, 0x7E, 0xEC, 0x0D, 0xE4, 0x1B, 0xAB, 0x36,
+ 0x9E, 0xA0, 0xDA, 0x00, 0x49, 0x4E, 0xB7, 0x90, 0xDC, 0x32, 0x2D, 0xA8, 0x81, 0x1B, 0xB9, 0xA5,
+ 0x92, 0x94, 0xED, 0xEF, 0xEB, 0x90, 0x56, 0xB5, 0xCD, 0x41, 0x41, 0xC7, 0xE8, 0x8F, 0x1E, 0x15,
+ 0x03, 0x6E, 0x05, 0x0D, 0xCB, 0x02, 0xD2, 0x3C, 0xB5, 0x2B, 0x6C, 0x84, 0x2E, 0x75, 0x29, 0x1C,
+ 0xDC, 0x79, 0xEA, 0x1A, 0x41, 0x22, 0x71, 0x61, 0x12, 0x8D, 0x82, 0x8F, 0x77, 0xE2, 0xD6, 0x18,
+ 0x0B, 0x2D, 0xC8, 0x58, 0x68, 0x13, 0xA8, 0x05, 0x55, 0xC5, 0xA9, 0xE4, 0x90, 0xA0, 0x82, 0x01,
+ 0x15, 0x60, 0x7C, 0xD4, 0xB6, 0x0B, 0xD9, 0x10, 0x4C, 0xDA, 0xDC, 0x9E, 0x6C, 0x64, 0x4F, 0x82,
+ 0xA4, 0x47, 0x60, 0xAF, 0xD1, 0x6D, 0x1F, 0xED, 0x62, 0x85, 0xAD, 0xAC, 0xD9, 0x90, 0x5C, 0x40,
+ 0x5B, 0x7D, 0x46, 0x4C, 0x90, 0xEE, 0xD6, 0x32, 0xBD, 0x45, 0x71, 0xC2, 0xCE, 0x0E, 0xE9, 0xE0,
+ 0xA9, 0xD6, 0x30, 0xEE, 0xBE, 0x63, 0x65, 0xBA, 0x61, 0xFC, 0x2F, 0xA1, 0x6D, 0xDE, 0x06, 0x59,
+ 0x9B, 0x7A, 0xBF, 0x65, 0xC1, 0x22, 0x7B, 0x59, 0xD8, 0x80, 0x6E, 0x19, 0x1F, 0x55, 0x1D, 0xB5,
+ 0x35, 0xBB, 0x2E, 0x29, 0xC9, 0x56, 0xD9, 0xA4, 0xA8, 0x77, 0x50, 0x37, 0x16, 0xD2, 0xF9, 0xED,
+ 0x86, 0x48, 0x8A, 0x0F, 0xE7, 0x44, 0x8F, 0xBC, 0x50, 0x53, 0x50, 0xD7, 0xBC, 0x76, 0x8D, 0x38,
+ 0xE7, 0x8E, 0xFB, 0x86, 0x78, 0x48, 0xDD, 0x3F, 0x8D, 0x88, 0x4A, 0x37, 0x83, 0x58, 0xB1, 0x13,
+ 0x15, 0xED, 0x14, 0x29, 0x25, 0x63, 0x04, 0xE2, 0x6B, 0xE8, 0x09, 0x22, 0x2C, 0x8C, 0x43, 0x67,
+ 0x9C, 0x82, 0xFE, 0x58, 0x93, 0xDA, 0x0C, 0x32, 0x7C, 0xD2, 0x34, 0x90, 0x78, 0x56, 0x75, 0x2C,
+ 0x1D, 0xA1, 0xA3, 0x57, 0x22, 0xB3, 0xF5, 0xE4, 0x88, 0xD3, 0xCD, 0x4D, 0x33, 0xA7, 0x42, 0x43,
+ 0xC1, 0xF4, 0xD6, 0xBC, 0x19, 0x15, 0x66, 0xA7, 0x28, 0xB0, 0xCC, 0x47, 0x6E, 0x8C, 0x9B, 0x41,
+ 0xEE, 0x2D, 0x27, 0x9D, 0x09, 0x02, 0xD5, 0x2C, 0x96, 0x2D, 0xCA, 0x71, 0xC9, 0x8C, 0x0E, 0x25,
+ 0xD4, 0x41, 0x3F, 0xC6, 0xB4, 0x8B, 0xDF, 0x0E, 0xEA, 0x10, 0xAB, 0xB9, 0xD9, 0xCF, 0x92, 0x6A,
+ 0x55, 0x15, 0xC1, 0x2E, 0x5C, 0x8F, 0x24, 0x50, 0xE7, 0xBB, 0x1A, 0x2E, 0xF0, 0x23, 0xF5, 0x91,
+ 0x74, 0x40, 0x1B, 0xD2, 0x10, 0x82, 0x36, 0x8C, 0x58, 0x78, 0xBE, 0xE9, 0x52, 0x8C, 0x9B, 0x2E,
+ 0xCD, 0x1B, 0x18, 0xEE, 0x10, 0xAB, 0x97, 0x97, 0x95, 0x5E, 0xBE, 0xFD, 0x7D, 0xF3, 0xDD, 0x5D,
+ 0x4E, 0xA1, 0x5B, 0xC2, 0x36, 0xD0, 0xF3, 0xDB, 0xAF, 0xED, 0x14, 0x0D, 0x17, 0xD5, 0x55, 0x13,
+ 0x59, 0xB0, 0xA3, 0x77, 0xF9, 0x22, 0x08, 0x43, 0x1A, 0x6E, 0x36, 0xBA, 0xC6, 0x2C, 0xF4, 0xA2,
+ 0x5A, 0x3A, 0x7F, 0xE7, 0x3E, 0x71, 0x30, 0x3E, 0x46, 0x50, 0x3E, 0xA3, 0xA0, 0x0C, 0x4A, 0x21,
+ 0x1F, 0x97, 0x4E, 0x81, 0xA0, 0xCC, 0x69, 0x5C, 0x24, 0xA7, 0x48, 0xED, 0x45, 0xCB, 0xAD, 0x56,
+ 0xD7, 0x6A, 0xE8, 0x27, 0x00, 0x2F, 0xAD, 0xC6, 0xCA, 0xBC, 0x47, 0x1E, 0x48, 0x2E, 0x52, 0xF9,
+ 0xB7, 0x00, 0xD6, 0x97, 0xCF, 0x11, 0x6C, 0x58, 0x3A, 0x65, 0xE8, 0xDB, 0x78, 0x9A, 0xD2, 0x0A,
+ 0xB2, 0xA7, 0x52, 0x83, 0x0F, 0xF5, 0xCB, 0xB6, 0x48, 0x84, 0x97, 0xF4, 0x06, 0x19, 0xC1, 0x17,
+ 0x32, 0xFD, 0x54, 0x92, 0xD2, 0xCA, 0x83, 0xC5, 0x2A, 0x50, 0x6E, 0x94, 0x54, 0xBC, 0x34, 0x1E,
+ 0xA1, 0xC3, 0xCA, 0xA0, 0x08, 0xAA, 0x09, 0xE6, 0x07, 0xE6, 0xC5, 0x80, 0xDC, 0xBA, 0xC1, 0x94,
+ 0x71, 0xF7, 0x04, 0x05, 0xE1, 0xCA, 0xAE, 0x43, 0xBE, 0x35, 0x4B, 0xBA, 0x8A, 0xE4, 0x82, 0x1D,
+ 0x57, 0xEF, 0x2D, 0xA5, 0x6F, 0x45, 0x6E, 0x88, 0xCC, 0x2F, 0x99, 0x16, 0x27, 0x38, 0x69, 0xD0,
+ 0xE3, 0xC1, 0x7D, 0xBF, 0x45, 0x68, 0x9B, 0x77, 0x20, 0x02, 0xE0, 0x3E, 0xC9, 0x80, 0x46, 0xB4,
+ 0x31, 0x68, 0xDB, 0x38, 0x94, 0x8C, 0x34, 0xA5, 0x6F, 0x17, 0x53, 0xBF, 0xB1, 0x74, 0xE2, 0x0D,
+ 0x90, 0x66, 0x24, 0xDA, 0x73, 0x5F, 0x1B, 0xED, 0x4B, 0xDB, 0x86, 0x1F, 0x5A, 0xBC, 0x9B, 0x02,
+ 0xF4, 0xD6, 0x32, 0x4E, 0xDD, 0x39, 0x3E, 0x2A, 0xFC, 0xFE, 0x21, 0xDD, 0x14, 0x69, 0xA5, 0xC3,
+ 0x92, 0xE3, 0x92, 0xCF, 0x3A, 0xD0, 0xA5, 0xD0, 0xDB, 0xFE, 0x77, 0x33, 0x4F, 0x8D, 0xAD, 0x22,
+ 0x0C, 0x9C, 0xE9, 0xA0, 0x50, 0x33, 0xC9, 0xB6, 0x6F, 0x0A, 0x23, 0x84, 0xF7, 0x64, 0x2A, 0x4E,
+ 0x6F, 0x3E, 0x7D, 0xBA, 0x39, 0x57, 0xA6, 0xB5, 0x82, 0x41, 0x38, 0xBC, 0x67, 0x02, 0xB2, 0x7B,
+ 0xFE, 0xF0, 0xAA, 0x93, 0x63, 0x7F, 0xF3, 0x04, 0xB5, 0x7A, 0x51, 0x59, 0xFB, 0xC1, 0xAC, 0x46,
+ 0x5C, 0x01, 0xC9, 0x6C, 0x2B, 0xA1, 0x94, 0x12, 0xC4, 0xA6, 0x25, 0xF8, 0x54, 0xDB, 0x83, 0x78,
+ 0x34, 0x41, 0x4F, 0x97, 0x2B, 0x86, 0xE3, 0x7F, 0xD3, 0x0A, 0x9A, 0x38, 0xDC, 0x1A, 0x59, 0x78,
+ 0xB7, 0x0D, 0x83, 0x1F, 0x7E, 0x69, 0x23, 0xF2, 0x14, 0xB5, 0xED, 0xD4, 0x35, 0xA7, 0xC7, 0x7C,
+ 0x72, 0x9F, 0x27, 0xD8, 0xDF, 0xC9, 0x26, 0x93, 0x13, 0xEB, 0x62, 0xB9, 0x46, 0x0F, 0x0C, 0xA2,
+ 0x4E, 0x14, 0xB6, 0x83, 0xD0, 0xA0, 0x09, 0x43, 0xD8, 0xE9, 0xAD, 0x98, 0x91, 0xEC, 0x27, 0x2B,
+ 0x35, 0x4C, 0x39, 0xB4, 0x89, 0x56, 0x5D, 0xD2, 0xA5, 0x6B, 0x39, 0xAF, 0xF5, 0x8B, 0x2D, 0xCA,
+ 0xBF, 0x37, 0x9A, 0x0D, 0x78, 0xA8, 0xC2, 0x6E, 0x55, 0xCA, 0x0B, 0x16, 0x2D, 0x8D, 0xEB, 0x6A,
+ 0x4A, 0x65, 0x35, 0xB7, 0x7D, 0x1C, 0x58, 0xA3, 0x52, 0x7C, 0x83, 0x54, 0x71, 0x93, 0x3D, 0x9B,
+ 0x48, 0xF9, 0x66, 0xE2, 0x74, 0x89, 0x34, 0x55, 0x44, 0x5A, 0xAA, 0xB3, 0xE5, 0x5B, 0x79, 0xD2,
+ 0x24, 0x0B, 0x24, 0x2F, 0x5A, 0x51, 0x75, 0x6D, 0x70, 0x66, 0x37, 0x51, 0x1D, 0x5D, 0x89, 0x3B,
+ 0x6F, 0x93, 0x9B, 0x24, 0x7C, 0x23, 0x5B, 0x90, 0xAB, 0x6F, 0xC1, 0x00, 0xCE, 0xBB, 0x90, 0x5B,
+ 0x7F, 0x59, 0xE1, 0xB5, 0x89, 0xB1, 0x27, 0x40, 0xA8, 0xE3, 0xB8, 0xA2, 0x16, 0xA9, 0x90, 0x98,
+ 0x6F, 0x9B, 0x85, 0xBD, 0xCF, 0xA2, 0x4B, 0x3A, 0x4B, 0xF3, 0x3B, 0x08, 0x9D, 0x29, 0x20, 0x1F,
+ 0x00, 0x9A, 0x07, 0x35, 0x46, 0x4A, 0x32, 0x1D, 0xB7, 0x44, 0x70, 0xA3, 0xE4, 0x6A, 0x1D, 0xD1,
+ 0x8C, 0xDA, 0x4F, 0xF8, 0x2C, 0x43, 0x5D, 0xE8, 0xF0, 0x8D, 0x2F, 0x73, 0xDA, 0xBD, 0x4F, 0x6C,
+ 0x94, 0x59, 0xCC, 0x86, 0x69, 0xBF, 0xF5, 0x28, 0x16, 0xAA, 0x64, 0xD1, 0x9D, 0x71, 0x9A, 0xAA,
+ 0x2E, 0x85, 0xA4, 0xE2, 0x2D, 0xA1, 0xB3, 0x2B, 0xC3, 0x69, 0x7A, 0xB5, 0xE1, 0x61, 0x92, 0x34,
+ 0xAE, 0x49, 0x30, 0x84, 0x1D, 0x7B, 0xAD, 0x2E, 0x68, 0x1C, 0xED, 0x82, 0x83, 0x9B, 0x44, 0xD0,
+ 0x19, 0x2E, 0x93, 0x6D, 0x2B, 0xFB, 0xFB, 0x5D, 0x41, 0xD6, 0x56, 0xB4, 0xA8, 0x92, 0x5F, 0xD2,
+ 0x70, 0x7B, 0x5C, 0x0F, 0x37, 0xDD, 0x6A, 0x48, 0xB7, 0x6D, 0x66, 0xE9, 0xDF, 0xEC, 0x84, 0x29,
+ 0xFF, 0xCB, 0x7B, 0xD3, 0x57, 0x54, 0x81, 0x48, 0x4F, 0x6F, 0xB7, 0xC6, 0x67, 0x52, 0xBC, 0xAA,
+ 0xFE, 0x9C, 0xDC, 0xD1, 0x59, 0x73, 0xCE, 0xC7, 0x42, 0xC9, 0x47, 0x12, 0x6D, 0xF7, 0x95, 0x39,
+ 0xA0, 0x70, 0xB8, 0x5F, 0x00, 0x7E, 0xF3, 0xCD, 0xFC, 0x52, 0xA5, 0x57, 0xC5, 0x4A, 0x95, 0x82,
+ 0xF3, 0x60, 0xDC, 0xFA, 0xA6, 0x10, 0x2F, 0xC6, 0xB7, 0xE0, 0xC5, 0xB9, 0xD0, 0x15, 0xE2, 0x55,
+ 0x16, 0xB4, 0x9B, 0x7C, 0xAF, 0x03, 0x3F, 0x50, 0x80, 0xCD, 0x2A, 0x55, 0xEA, 0x75, 0x9A, 0xDC,
+ 0xD0, 0xAF, 0x87, 0x66, 0x0A, 0x4F, 0x1E, 0x88, 0x0B, 0xDD, 0x6E, 0x71, 0xA2, 0xC2, 0xEF, 0x90,
+ 0x59, 0xFE, 0xE8, 0x18, 0x49, 0xBD, 0xD9, 0x90, 0xEC, 0xD8, 0x2A, 0x45, 0x27, 0xBE, 0xB7, 0x41,
+ 0x6E, 0x0E, 0xD2, 0x6C, 0x9C, 0x70, 0xDF, 0x9B, 0xA0, 0x8C, 0x5D, 0xE7, 0x29, 0x5D, 0x2F, 0x7C,
+ 0xB5, 0x70, 0x3E, 0x08, 0x09, 0x70, 0x07, 0xC1, 0x20, 0x25, 0xD9, 0xF7, 0x36, 0xC8, 0x25, 0x24,
+ 0x1E, 0x70, 0xE6, 0xA1, 0xCA, 0x95, 0x0E, 0x82, 0x3F, 0x6C, 0x3C, 0xD8, 0x86, 0xF9, 0x25, 0x1D,
+ 0xB0, 0xAE, 0xD4, 0x9A, 0x56, 0xC0, 0xFA, 0xAA, 0x92, 0xEF, 0xC9, 0x79, 0xFC, 0x03, 0x2D, 0x83,
+ 0x38, 0x5F, 0x6D, 0x0A, 0xF5, 0x99, 0xE3, 0xE8, 0xD9, 0xB6, 0xAE, 0xE5, 0xB6, 0xC8, 0x7B, 0x7A,
+ 0x5B, 0xA9, 0x7D, 0x6A, 0x5B, 0x01, 0xBE, 0x36, 0x3D, 0x14, 0x27, 0xCD, 0xB3, 0xF7, 0xA1, 0xBC,
+ 0xCD, 0x13, 0xB4, 0x9B, 0x93, 0x52, 0x1B, 0xA2, 0x8A, 0xCA, 0xF5, 0xBC, 0x75, 0x05, 0x5F, 0x4D,
+ 0x09, 0x6D, 0x58, 0x8C, 0x81, 0xC3, 0x38, 0x6A, 0x0A, 0x4F, 0x70, 0x10, 0xE6, 0x1D, 0xD7, 0x89,
+ 0x8A, 0xA6, 0xA1, 0xE9, 0xB9, 0x56, 0xD9, 0xD5, 0x97, 0x2C, 0xA0, 0x3E, 0x54, 0x11, 0xF5, 0xD5,
+ 0x99, 0x8F, 0xB4, 0x7F, 0x3E, 0x1A, 0xDD, 0x4B, 0x05, 0x49, 0x52, 0x64, 0x50, 0x51, 0xA7, 0x7D,
+ 0x0C, 0x76, 0x1B, 0x3D, 0x93, 0x22, 0xD9, 0xF2, 0x5C, 0xAE, 0xE3, 0xD3, 0x2C, 0x8A, 0x14, 0xA5,
+ 0xD2, 0x76, 0x80, 0x97, 0xF1, 0xCB, 0x0A, 0x89, 0xC4, 0xE9, 0x85, 0xE1, 0xBB, 0x04, 0xF8, 0x27,
+ 0xCD, 0x6B, 0xF5, 0xEB, 0x51, 0x84, 0x2A, 0x43, 0x92, 0xFF, 0x6D, 0xC7, 0xD8, 0x16, 0xC0, 0xDD,
+ 0xF1, 0x25, 0x6F, 0x54, 0x60, 0x6A, 0xD9, 0x54, 0x3C, 0x49, 0x0F, 0xB3, 0xE4, 0x83, 0xB3, 0x8B,
+ 0x31, 0x15, 0x58, 0x2E, 0xC3, 0x07, 0x3C, 0x66, 0x26, 0x5D, 0x0C, 0x8B, 0x18, 0x3B, 0x35, 0x9A,
+ 0x02, 0x68, 0xCC, 0x17, 0xB9, 0x7B, 0x6B, 0xB1, 0xF9, 0x52, 0x97, 0x54, 0x58, 0xB6, 0x76, 0x62,
+ 0x67, 0x68, 0x08, 0x71, 0xD9, 0xD0, 0x49, 0x0A, 0x9D, 0xC9, 0x2B, 0x0C, 0x2F, 0x24, 0xCC, 0x2C,
+ 0x32, 0x3A, 0xEB, 0xD7, 0xD8, 0xA9, 0x77, 0x6B, 0x52, 0x10, 0x2B, 0x08, 0x53, 0x36, 0x6E, 0x97,
+ 0xBC, 0x1C, 0xE1, 0xD9, 0x9C, 0x5E, 0xF7, 0x93, 0xCC, 0x8F, 0x63, 0x50, 0xD6, 0x07, 0x83, 0x35,
+ 0x5A, 0xAE, 0x6B, 0x65, 0xB4, 0x2C, 0x49, 0x0E, 0xA5, 0x5D, 0x93, 0x60, 0xF0, 0x46, 0xB9, 0xE0,
+ 0xDB, 0xA5, 0xE1, 0x6A, 0x9A, 0xDE, 0x63, 0xBE, 0x2B, 0xC7, 0xF8, 0xE8, 0x1D, 0xE9, 0xF2, 0xA5,
+ 0x36, 0xB6, 0x3E, 0x66, 0x23, 0xAC, 0xFA, 0x18, 0x4E, 0xCD, 0xDE, 0xC1, 0xA4, 0xE8, 0x39, 0x92,
+ 0x94, 0x9D, 0x0C, 0xBC, 0x9E, 0x92, 0x95, 0xEB, 0xDB, 0x68, 0x56, 0x47, 0x08, 0x8F, 0x54, 0xF4,
+ 0x88, 0x2C, 0x8F, 0xBE, 0x26, 0xB5, 0x2B, 0x22, 0x8F, 0x07, 0xA4, 0x74, 0x9D, 0x9E, 0xBC, 0x78,
+ 0xFE, 0xE6, 0xF9, 0xD9, 0xB4, 0x86, 0x44, 0xAF, 0xA6, 0x88, 0xD9, 0xD9, 0x8C, 0xC2, 0xCF, 0x90,
+ 0xE3, 0x91, 0x7B, 0xA1, 0xE8, 0xA7, 0xA6, 0x76, 0x55, 0x7A, 0x20, 0x23, 0x0C, 0xEF, 0x05, 0x44,
+ 0xF3, 0x76, 0x5D, 0xE1, 0x55, 0x8E, 0x82, 0x1B, 0x59, 0x0B, 0xE4, 0x93, 0x3E, 0xA3, 0xB5, 0x1B,
+ 0x35, 0xDF, 0x63, 0x43, 0x09, 0x8F, 0x9C, 0xFD, 0xD7, 0xB5, 0xE3, 0x76, 0xEB, 0x45, 0xE2, 0x30,
+ 0x27, 0x0A, 0xC4, 0x7D, 0x69, 0x82, 0xB6, 0xEB, 0x45, 0xC1, 0xA1, 0xF7, 0xC4, 0x1B, 0x2A, 0x8A,
+ 0xD0, 0xA9, 0xE9, 0x67, 0xA7, 0x26, 0xBE, 0x87, 0xF4, 0x44, 0xDA, 0xA8, 0x5A, 0xED, 0xB4, 0x79,
+ 0xF4, 0xE6, 0xE3, 0x60, 0xA2, 0x9A, 0x92, 0x3F, 0xE6, 0x8A, 0x49, 0xA8, 0x49, 0xDF, 0x4E, 0xAB,
+ 0x6E, 0x4B, 0x5F, 0x26, 0xBD, 0xE2, 0x86, 0x23, 0xC5, 0xF8, 0x57, 0x4D, 0xF7, 0xD9, 0x4D, 0x91,
+ 0x33, 0x04, 0xC4, 0x5A, 0xEA, 0x6C, 0x44, 0xCD, 0x06, 0x18, 0x7F, 0x30, 0xE0, 0xD0, 0x91, 0xFD,
+ 0x89, 0x92, 0xB3, 0x49, 0xF3, 0x15, 0xD2, 0xFB, 0xB5, 0x2D, 0x58, 0x1A, 0x2A, 0x53, 0x74, 0x89,
+ 0xF2, 0x90, 0x98, 0xE0, 0xE8, 0xBB, 0x66, 0xFB, 0x79, 0x64, 0xEF, 0x30, 0x81, 0xD3, 0x77, 0xD4,
+ 0x1A, 0xAC, 0x55, 0x50, 0xD7, 0x4E, 0x48, 0xD0, 0xEF, 0x3C, 0xA1, 0xF6, 0x45, 0xE2, 0x1B, 0x5D,
+ 0x78, 0x11, 0x5D, 0x90, 0x4C, 0x40, 0x39, 0x43, 0xB1, 0x72, 0x76, 0xD2, 0x35, 0xD8, 0x3B, 0x16,
+ 0xEB, 0x68, 0x79, 0x30, 0x86, 0xC6, 0x85, 0xF5, 0x7C, 0x97, 0x70, 0x3A, 0xB3, 0x60, 0x29, 0x3B,
+ 0xA4, 0x76, 0xE3, 0xE3, 0x65, 0xCF, 0x45, 0x45, 0x22, 0xCA, 0x5D, 0x73, 0x31, 0xC0, 0x99, 0xE2,
+ 0xBA, 0x16, 0x96, 0x81, 0xD0, 0xBB, 0xA8, 0xFB, 0x8E, 0x6B, 0xF2, 0x33, 0xBE, 0x8E, 0x48, 0xB8,
+ 0xCB, 0xAF, 0xC8, 0x49, 0xB5, 0x84, 0xC1, 0x63, 0x6C, 0xD0, 0x41, 0x62, 0x27, 0x81, 0x81, 0x54,
+ 0x6A, 0x20, 0x5B, 0xEE, 0xD0, 0x92, 0x2C, 0xD7, 0x49, 0x3D, 0x75, 0x9B, 0xD0, 0xFB, 0xC0, 0x28,
+ 0xDC, 0xB2, 0xF2, 0x04, 0x85, 0x9A, 0x21, 0x77, 0xC3, 0x41, 0x20, 0xAD, 0xB2, 0xEB, 0x69, 0xCF,
+ 0x8A, 0x96, 0x41, 0xB0, 0x05, 0xFC, 0x82, 0x8A, 0x92, 0xE7, 0xC0, 0x34, 0x25, 0x56, 0x9E, 0xF5,
+ 0x58, 0xA4, 0x6B, 0x8D, 0x2B, 0x5E, 0xC0, 0x1F, 0x65, 0xDB, 0x0A, 0x76, 0x03, 0x2A, 0x7F, 0x0E,
+ 0xC0, 0x5F, 0xB3, 0x2E, 0xE2, 0x54, 0x5A, 0xB1, 0xF9, 0x1B, 0x9C, 0xB6, 0xFC, 0xA2, 0x15, 0xC4,
+ 0xEA, 0xDA, 0xC8, 0x04, 0x12, 0x3D, 0x24, 0x98, 0xEA, 0x68, 0x68, 0xDA, 0x7B, 0x37, 0xCC, 0x33,
+ 0xB1, 0x20, 0x73, 0x1E, 0x9D, 0x04, 0x9A, 0x15, 0xE4, 0x46, 0xB1, 0xF1, 0x67, 0xD6, 0x1E, 0x20,
+ 0xF7, 0x0E, 0x15, 0xFE, 0x0C, 0x70, 0x90, 0x76, 0xA0, 0xA5, 0xEA, 0x4C, 0x4C, 0x55, 0x69, 0x2B,
+ 0x8F, 0x8D, 0x6B, 0xE5, 0x31, 0x30, 0x17, 0x49, 0x36, 0xAE, 0x4D, 0x14, 0x25, 0x39, 0x07, 0xDE,
+ 0x4A, 0x6C, 0xA4, 0x4C, 0xB0, 0x2C, 0xE6, 0x58, 0x72, 0x69, 0xFE, 0x48, 0x32, 0x30, 0xB7, 0xB2,
+ 0xD6, 0x1B, 0x09, 0xB8, 0x8B, 0x91, 0xA0, 0xA6, 0xBF, 0x14, 0x2B, 0x16, 0x79, 0x66, 0xF9, 0x57,
+ 0x5C, 0x0A, 0xF5, 0x9A, 0x04, 0x59, 0x21, 0x74, 0xFA, 0x74, 0x59, 0x25, 0x44, 0x1E, 0xFA, 0xC5,
+ 0x13, 0x46, 0x2D, 0x6C, 0xC1, 0xD1, 0xA4, 0x2B, 0x83, 0xC0, 0xEB, 0x1C, 0x65, 0xAC, 0x11, 0x8D,
+ 0xBD, 0xF3, 0xF8, 0x75, 0x42, 0x56, 0xE1, 0x04, 0xBF, 0x56, 0x24, 0xE9, 0x18, 0x8F, 0x7D, 0x93,
+ 0xB8, 0x8F, 0xD8, 0x99, 0x0B, 0xC4, 0x13, 0xC7, 0xBB, 0x21, 0xDD, 0xE4, 0x15, 0x39, 0xA6, 0x4D,
+ 0xBD, 0xAB, 0x53, 0xC8, 0x67, 0xE2, 0x8E, 0x72, 0xB2, 0xA4, 0x05, 0xB0, 0x0F, 0x5C, 0x17, 0xE7,
+ 0x4C, 0x0E, 0x90, 0x05, 0x2B, 0x6F, 0xC7, 0x6B, 0xBE, 0x3A, 0x29, 0x0A, 0x87, 0x7F, 0x94, 0x4F,
+ 0xFE, 0xA5, 0x94, 0x40, 0xB6, 0x37, 0x9C, 0xC5, 0x2C, 0xB2, 0xC9, 0xDB, 0xB5, 0x0D, 0x46, 0x7E,
+ 0xC7, 0x84, 0xC4, 0x6E, 0xE9, 0x86, 0x21, 0x53, 0xAF, 0x38, 0x68, 0x2B, 0x18, 0xC8, 0x1E, 0x78,
+ 0x77, 0xD2, 0x6E, 0xF0, 0x96, 0x59, 0x25, 0xB6, 0x3B, 0xDB, 0xF4, 0x4B, 0x7F, 0x48, 0x6D, 0x3B,
+ 0x4C, 0x1E, 0x7B, 0xC3, 0x84, 0x7E, 0x2C, 0x23, 0xEC, 0x64, 0xD8, 0xC7, 0x0F, 0x50, 0x8B, 0x44,
+ 0x83, 0x6C, 0x92, 0x34, 0x0F, 0x94, 0xE7, 0x5F, 0x3F, 0xC7, 0x27, 0xA5, 0x75, 0x1E, 0x0D, 0x8C,
+ 0x72, 0x7A, 0x31, 0x10, 0x20, 0x4A, 0x3F, 0xCA, 0x4A, 0xDB, 0xBE, 0x54, 0x01, 0x88, 0xDF, 0xEC,
+ 0x7A, 0xAC, 0x62, 0x7F, 0x5F, 0x8B, 0x73, 0x24, 0x63, 0x1D, 0x4C, 0x36, 0xFA, 0xC8, 0x8C, 0x4A,
+ 0x1D, 0x0A, 0x65, 0x5D, 0xCA, 0xC0, 0xDF, 0xD3, 0xE6, 0xA6, 0xFC, 0x4D, 0x92, 0xF6, 0x86, 0x1D,
+ 0x29, 0x8A, 0x39, 0x9F, 0x72, 0x3E, 0x2A, 0x76, 0x24, 0xCC, 0x43, 0xCA, 0xE9, 0xC5, 0x3B, 0x1E,
+ 0x86, 0x60, 0x9B, 0x36, 0x77, 0x80, 0x79, 0x7B, 0x60, 0x38, 0x5A, 0x78, 0x64, 0x34, 0x2E, 0x69,
+ 0x1A, 0xC2, 0xA7, 0x0D, 0x6D, 0x67, 0xF3, 0xEA, 0xB8, 0x9A, 0x24, 0x07, 0x2E, 0x54, 0x2D, 0xD2,
+ 0x5C, 0x4B, 0x0C, 0x80, 0xD3, 0x9A, 0xC8, 0x86, 0x5F, 0x26, 0x32, 0x80, 0xD7, 0x79, 0x48, 0xD9,
+ 0xD1, 0x1F, 0xB5, 0x15, 0x3A, 0x0C, 0xDA, 0x56, 0x79, 0x00, 0x12, 0xD8, 0x40, 0x37, 0xD9, 0x3E,
+ 0x60, 0x88, 0xCA, 0xC9, 0x78, 0x36, 0xFD, 0xC1, 0xC8, 0x16, 0x64, 0x94, 0x3E, 0xDE, 0xCC, 0xE5,
+ 0x79, 0x13, 0x43, 0x17, 0x38, 0x27, 0x2D, 0x02, 0x88, 0x28, 0xD0, 0xB0, 0xA9, 0x68, 0x63, 0xF3,
+ 0x28, 0xAD, 0x68, 0x3B, 0x72, 0x1B, 0x50, 0x94, 0xD6, 0x36, 0x9B, 0x7E, 0x67, 0xE2, 0xB0, 0x75,
+ 0xDC, 0x4F, 0x4D, 0x8C, 0x75, 0x49, 0xE5, 0x05, 0x3F, 0x51, 0xD3, 0xEB, 0x1F, 0x57, 0xD3, 0x61,
+ 0xEE, 0x55, 0xCA, 0x07, 0x33, 0x06, 0xFA, 0x49, 0xE5, 0x6E, 0x9E, 0xCE, 0xE5, 0xD1, 0xEE, 0x9D,
+ 0x7D, 0xB2, 0xA2, 0xA0, 0xE2, 0x9C, 0x93, 0x65, 0x3D, 0xFA, 0xB1, 0xE9, 0xA0, 0x43, 0x2F, 0x8E,
+ 0x95, 0xCA, 0xA1, 0xB1, 0x9F, 0xBA, 0x71, 0x7F, 0x6C, 0x47, 0x01, 0x5A, 0xE9, 0xBB, 0x61, 0x42,
+ 0xD8, 0xB8, 0x4D, 0xDA, 0x56, 0xCA, 0x21, 0x45, 0x1A, 0x04, 0xF6, 0x30, 0x80, 0x5F, 0xA8, 0x71,
+ 0xF7, 0xF9, 0x96, 0xFC, 0x4F, 0xBA, 0xF8, 0x93, 0x9E, 0xCF, 0xA6, 0x95, 0xBB, 0xEC, 0x27, 0xF8,
+ 0x8A, 0x7F, 0xAB, 0x3B, 0x4C, 0x8D, 0x75, 0x9A, 0x1A, 0xF7, 0xC4, 0xFD, 0xB1, 0x15, 0x65, 0xE8,
+ 0x0A, 0xDB, 0xAF, 0x23, 0x7A, 0x88, 0xBE, 0x84, 0x60, 0xFE, 0x53, 0x12, 0xE3, 0xD2, 0xE6, 0x72,
+ 0x49, 0x5E, 0xA2, 0xD8, 0xB5, 0x84, 0xF7, 0x17, 0xF7, 0x93, 0x92, 0x1A, 0x47, 0x40, 0x2B, 0xA2,
+ 0xDD, 0x30, 0x25, 0xF3, 0xC4, 0xC4, 0x4B, 0x20, 0xF5, 0xFF, 0x4B, 0x73, 0xEE, 0xBB, 0x2D, 0x68,
+ 0xBA, 0xE3, 0xC9, 0xD3, 0xA2, 0x11, 0xE4, 0xB7, 0x67, 0xA1, 0x94, 0x5E, 0x91, 0x3B, 0x7A, 0xD2,
+ 0xF4, 0x76, 0x1E, 0x7C, 0xF1, 0x5B, 0x87, 0x61, 0xC2, 0x2F, 0x4C, 0x88, 0xA4, 0x65, 0xEE, 0xDA,
+ 0x60, 0xC7, 0xDA, 0xC7, 0x55, 0xCB, 0x0A, 0x54, 0x36, 0x8C, 0xBA, 0x11, 0xCD, 0x1F, 0x58, 0x58,
+ 0x68, 0x39, 0xA4, 0xAC, 0x95, 0xE9, 0x9C, 0xEE, 0x74, 0x49, 0x32, 0xCB, 0x32, 0xBD, 0x3C, 0x40,
+ 0x3A, 0x38, 0x3F, 0x70, 0x57, 0x5B, 0xBD, 0x04, 0xF3, 0x36, 0x52, 0x75, 0xD7, 0x7C, 0x43, 0xC4,
+ 0xBD, 0x5E, 0x4F, 0xBD, 0x6B, 0x71, 0x09, 0x7A, 0x7C, 0xD6, 0x10, 0xB9, 0xD7, 0x2D, 0x09, 0x49,
+ 0xF4, 0xDE, 0x39, 0x4F, 0x7A, 0x0D, 0x60, 0xCC, 0x6C, 0x0C, 0x9F, 0x93, 0x87, 0x0C, 0x9F, 0x81,
+ 0x91, 0x93, 0xA8, 0x6D, 0xD6, 0x4C, 0x40, 0x5D, 0xDC, 0xF1, 0xCA, 0x19, 0x26, 0x82, 0x35, 0xBA,
+ 0x6A, 0x30, 0x99, 0xD4, 0x05, 0x3F, 0xB0, 0x60, 0x95, 0x5C, 0x30, 0xA7, 0xDF, 0x11, 0x55, 0x1A,
+ 0x1D, 0xBA, 0x56, 0xB6, 0x60, 0x9A, 0x1D, 0xC4, 0xA6, 0x37, 0x38, 0xD2, 0x1E, 0xC2, 0x40, 0x92,
+ 0x3E, 0x53, 0x8E, 0x56, 0xED, 0x8A, 0x99, 0xD7, 0x84, 0x3E, 0x08, 0xD2, 0xED, 0xC2, 0xB8, 0x1E,
+ 0xDD, 0x8D, 0x81, 0xB4, 0x96, 0xC7, 0x1C, 0x1F, 0x76, 0x47, 0xC6, 0x7B, 0x8C, 0xBD, 0xEE, 0x1A,
+ 0x5A, 0x91, 0x37, 0xBA, 0xDE, 0x7E, 0xD4, 0xF9, 0x86, 0x37, 0x79, 0x39, 0xAA, 0x37, 0x79, 0xE8,
+ 0xC9, 0x90, 0x47, 0x68, 0x4B, 0x47, 0x21, 0x28, 0xE3, 0xDC, 0xAD, 0x15, 0x7A, 0x12, 0x5F, 0xE8,
+ 0xD8, 0xE7, 0xB4, 0x9B, 0x51, 0x9C, 0x7E, 0x3C, 0x3D, 0x01, 0xE4, 0x66, 0xDD, 0x73, 0x26, 0xA3,
+ 0x29, 0xDA, 0x8A, 0xA5, 0x43, 0x6F, 0x92, 0x67, 0xF5, 0x1F, 0xE9, 0x34, 0x57, 0x80, 0x44, 0x76,
+ 0x89, 0x04, 0x2B, 0x60, 0xC3, 0x02, 0xF0, 0xA9, 0x9E, 0xC3, 0x69, 0x22, 0x1F, 0xE4, 0x30, 0x2A,
+ 0x25, 0x6B, 0x60, 0x7B, 0x71, 0x9A, 0x37, 0x84, 0x24, 0xF1, 0x4E, 0xFD, 0x82, 0x0E, 0x7C, 0xDC,
+ 0xD7, 0x25, 0x87, 0x6C, 0x1B, 0xE3, 0xE7, 0xB2, 0x62, 0x1E, 0xD6, 0x12, 0x9D, 0xBB, 0x5D, 0x74,
+ 0xB7, 0xAE, 0xD9, 0x9A, 0xAB, 0xB5, 0x93, 0xC1, 0xFA, 0x36, 0x90, 0xF7, 0x6B, 0x87, 0xFA, 0x7A,
+ 0xED, 0xEB, 0x24, 0xEA, 0x2C, 0x60, 0xFB, 0x6E, 0xAA, 0xA1, 0xB5, 0x20, 0x0A, 0xD6, 0xAD, 0xF3,
+ 0x3A, 0x41, 0x6F, 0x8A, 0x6C, 0xEC, 0x6A, 0xE8, 0x24, 0x90, 0x1D, 0x3C, 0xC1, 0xED, 0x59, 0xF0,
+ 0x02, 0x8B, 0x53, 0x92, 0x9D, 0x30, 0xC8, 0x7D, 0x65, 0x5A, 0xE8, 0x52, 0xBA, 0x74, 0x95, 0xD1,
+ 0xB0, 0xE2, 0x55, 0x40, 0xCF, 0x03, 0xEA, 0x5B, 0xAC, 0x08, 0x0A, 0x1B, 0x8C, 0x2A, 0xFC, 0xB1,
+ 0x59, 0x8B, 0x48, 0x9B, 0x0D, 0x91, 0x6D, 0xB7, 0x93, 0x2D, 0xA7, 0x6C, 0xF6, 0x7D, 0xAC, 0x57,
+ 0x8D, 0xA9, 0xC8, 0x12, 0x65, 0x8D, 0x55, 0x7B, 0x01, 0x8F, 0xDE, 0x8A, 0x5F, 0xFE, 0x96, 0xF4,
+ 0x52, 0xE1, 0xBA, 0x33, 0xAA, 0x4A, 0xF9, 0x2A, 0xCD, 0x6B, 0xA6, 0x8F, 0xC9, 0xC2, 0x80, 0x19,
+ 0xAB, 0x38, 0xCA, 0x68, 0xE0, 0x5A, 0x8B, 0xCC, 0x04, 0x29, 0xFB, 0x9E, 0x26, 0x17, 0xDF, 0xF8,
+ 0x01, 0xC1, 0xAE, 0xAC, 0x41, 0x9D, 0xEB, 0xA2, 0x80, 0x74, 0xF0, 0xDE, 0xEB, 0xF4, 0x36, 0x59,
+ 0x7D, 0x9A, 0xDF, 0xF2, 0x64, 0x95, 0x3E, 0xEA, 0xFC, 0x56, 0x21, 0x9B, 0x18, 0xE1, 0x57, 0x2A,
+ 0x5C, 0x21, 0xCC, 0xA6, 0xCA, 0xFC, 0x34, 0x1E, 0xFB, 0x2D, 0xBC, 0x4A, 0xB3, 0xBF, 0xF1, 0x47,
+ 0x4E, 0x1F, 0xF1, 0xAD, 0xFC, 0xB0, 0xF1, 0x4E, 0xAC, 0x2E, 0x17, 0xC5, 0x02, 0x3D, 0x55, 0x75,
+ 0xE8, 0xB8, 0xC2, 0x2D, 0x93, 0x0A, 0xA7, 0x54, 0x1E, 0x38, 0xCE, 0x89, 0xE2, 0x49, 0x3C, 0xC4,
+ 0x25, 0x5D, 0xE7, 0xAD, 0xF3, 0xCF, 0x1A, 0xE2, 0xAA, 0xFB, 0x86, 0x2F, 0x26, 0xA9, 0x9B, 0xB4,
+ 0xAF, 0xD4, 0x99, 0xF7, 0x26, 0xC0, 0xA8, 0xEF, 0x70, 0x6C, 0xAB, 0x4C, 0xBA, 0x74, 0x46, 0x50,
+ 0x90, 0xFF, 0xEA, 0xED, 0xDA, 0x9B, 0xDB, 0x36, 0x8E, 0xF8, 0xFF, 0xFD, 0x14, 0x12, 0x9A, 0x7A,
+ 0x00, 0xF3, 0x24, 0x51, 0x4E, 0xDA, 0x69, 0x21, 0xA3, 0x9C, 0xD4, 0xB5, 0x93, 0xB4, 0x75, 0xEC,
+ 0xDA, 0x4A, 0x9B, 0x96, 0x66, 0x3D, 0xB0, 0x08, 0x49, 0x48, 0x29, 0x40, 0x05, 0x40, 0x4B, 0x8E,
+ 0xC8, 0xEF, 0xDE, 0xDF, 0xEE, 0xED, 0xBD, 0x00, 0x50, 0x76, 0x9F, 0x99, 0x58, 0x04, 0xEE, 0x8D,
+ 0xBB, 0xBD, 0xBD, 0xBD, 0x7D, 0xEE, 0x7B, 0xCD, 0xD9, 0x5E, 0x0B, 0xDD, 0x36, 0xD9, 0xF5, 0xEA,
+ 0xA9, 0x24, 0x53, 0x4E, 0x32, 0xDC, 0xC8, 0xA2, 0x6B, 0x89, 0x69, 0x98, 0xE6, 0xEF, 0xD8, 0xB3,
+ 0x6B, 0x71, 0xB2, 0x2A, 0xCE, 0x3B, 0xC4, 0xCA, 0xA0, 0xFF, 0xAE, 0x6F, 0x4F, 0xF8, 0x7B, 0xD3,
+ 0x5F, 0x4C, 0xF1, 0x78, 0x85, 0x8E, 0xCB, 0xEA, 0x00, 0x42, 0x97, 0x94, 0x72, 0xAE, 0xF3, 0x25,
+ 0xB9, 0x8A, 0x4B, 0xA7, 0x27, 0xEF, 0xEA, 0x66, 0x09, 0x84, 0x30, 0x8D, 0xD4, 0x6A, 0x67, 0xF3,
+ 0x26, 0x74, 0xE5, 0x89, 0xD8, 0x66, 0xA6, 0x6C, 0x0F, 0x8A, 0xAA, 0xB7, 0x07, 0x6D, 0xF9, 0x23,
+ 0xB5, 0xA3, 0x5B, 0x39, 0x40, 0xCA, 0x09, 0x31, 0xCD, 0xCF, 0x57, 0xB0, 0xCF, 0x6C, 0xD9, 0xC1,
+ 0x9C, 0xF4, 0x9C, 0xE6, 0xEB, 0xAE, 0x36, 0x9D, 0xF9, 0x23, 0xF0, 0xC7, 0xF9, 0xB3, 0x13, 0x1E,
+ 0xDF, 0xCF, 0x08, 0x2F, 0x05, 0x14, 0xDA, 0x3A, 0x24, 0xD8, 0x56, 0xC2, 0x16, 0xCA, 0x9E, 0x0C,
+ 0x77, 0x1F, 0xF2, 0xAA, 0x2C, 0x42, 0x1B, 0x7C, 0x09, 0x44, 0x7B, 0x00, 0xE8, 0xE3, 0x47, 0x74,
+ 0x10, 0x82, 0x45, 0xA7, 0xC7, 0x42, 0x16, 0xF4, 0x89, 0xFD, 0xDC, 0x86, 0x0C, 0xEC, 0xB3, 0x08,
+ 0xBD, 0x47, 0x80, 0xAC, 0xCF, 0x7F, 0xC1, 0x45, 0x25, 0x19, 0xF7, 0x27, 0x97, 0xC2, 0xA3, 0x74,
+ 0xF5, 0xCC, 0xEC, 0x64, 0x91, 0x99, 0x7D, 0xA0, 0x58, 0xD3, 0xD5, 0xEA, 0x50, 0x0B, 0x75, 0x18,
+ 0xAE, 0x8E, 0x3E, 0x47, 0x3B, 0xE1, 0x3D, 0x7A, 0x9D, 0x88, 0xA7, 0x3B, 0x8F, 0xF9, 0xD5, 0x79,
+ 0xE7, 0xBF, 0x8E, 0xBB, 0x5B, 0xAF, 0x71, 0x82, 0xB1, 0x87, 0xA2, 0x67, 0x10, 0xE7, 0x53, 0x7E,
+ 0xB2, 0x1D, 0xD8, 0x3B, 0x67, 0xE3, 0x46, 0x9A, 0x6A, 0xB5, 0x23, 0xE3, 0x64, 0x65, 0x8D, 0x9F,
+ 0xCD, 0xA7, 0x90, 0x53, 0x8E, 0x0B, 0xEE, 0xED, 0xC9, 0xAA, 0xBC, 0xCE, 0x22, 0x09, 0xFA, 0x40,
+ 0xCB, 0x09, 0xB0, 0x08, 0x6F, 0xE3, 0x3B, 0xAA, 0x90, 0x11, 0x23, 0x3B, 0x94, 0xE0, 0xBB, 0x39,
+ 0x2F, 0x45, 0xD8, 0x0E, 0xA6, 0x65, 0xBC, 0x3B, 0xE7, 0x01, 0xE6, 0x83, 0xBA, 0x43, 0xC9, 0xD7,
+ 0x0C, 0x51, 0xAF, 0x8A, 0x55, 0xB9, 0xC3, 0x39, 0x3B, 0x59, 0x58, 0x01, 0xCD, 0x85, 0x78, 0x64,
+ 0x47, 0xB9, 0x5A, 0xCA, 0x99, 0xE8, 0x9F, 0x3B, 0x8A, 0x55, 0x20, 0x05, 0xA4, 0xC3, 0xE7, 0x16,
+ 0x44, 0x76, 0x94, 0x05, 0x6D, 0xA8, 0x21, 0x5B, 0x8F, 0x75, 0xD7, 0x08, 0x4B, 0xD7, 0xE4, 0x69,
+ 0xF3, 0xDB, 0x12, 0x0B, 0xD0, 0xA2, 0x4C, 0xDB, 0x27, 0x33, 0x44, 0xFA, 0x71, 0x12, 0x04, 0xAD,
+ 0xCC, 0x89, 0xC3, 0x37, 0x58, 0x3E, 0xC3, 0x1A, 0x57, 0xDD, 0x48, 0x56, 0x83, 0xF4, 0x6A, 0x17,
+ 0x28, 0x14, 0xFF, 0x2A, 0xE2, 0x90, 0xED, 0x7C, 0x86, 0xAF, 0xCC, 0xAF, 0x21, 0x3B, 0x6D, 0x0B,
+ 0x40, 0x61, 0x4E, 0x10, 0xDE, 0xF5, 0x9B, 0x72, 0x5B, 0x7A, 0x0F, 0x4D, 0x95, 0x4B, 0x57, 0xE4,
+ 0xB2, 0xD0, 0x3B, 0x0B, 0x59, 0x91, 0xAA, 0x7A, 0x89, 0xBF, 0xE2, 0xC4, 0xBE, 0xD5, 0x3C, 0xE3,
+ 0x95, 0xC1, 0xEE, 0xEF, 0x5D, 0xD7, 0xBA, 0xF0, 0xB5, 0x02, 0x34, 0x8C, 0x21, 0x82, 0x8E, 0xF8,
+ 0x20, 0xBC, 0x79, 0xBE, 0xC1, 0x4C, 0x34, 0xD2, 0xB3, 0x3A, 0x9E, 0x26, 0x13, 0x2F, 0x55, 0x0F,
+ 0x1F, 0xAE, 0x36, 0x78, 0xA7, 0x8E, 0xE7, 0x6A, 0xD7, 0x1B, 0xB6, 0x00, 0x6D, 0x70, 0xD9, 0xDE,
+ 0x5F, 0x73, 0x9B, 0xFD, 0xCD, 0x5D, 0x24, 0xE8, 0x9A, 0x0C, 0x46, 0xB6, 0xB1, 0x46, 0x54, 0xBF,
+ 0x21, 0x77, 0x1E, 0x7F, 0x2E, 0xDE, 0xFD, 0xBD, 0x24, 0x4F, 0x1E, 0xCF, 0xEB, 0x1F, 0xF1, 0xF7,
+ 0xAA, 0x8D, 0x16, 0xEA, 0xB3, 0x62, 0x7C, 0xC9, 0x64, 0x97, 0xA9, 0xB7, 0xA1, 0xDF, 0x83, 0x1F,
+ 0x0B, 0x47, 0xAE, 0xB0, 0x45, 0x3F, 0x9D, 0xBF, 0xED, 0x9C, 0xB4, 0x29, 0xDF, 0x16, 0xF8, 0xF1,
+ 0x1D, 0x21, 0x16, 0x44, 0x32, 0x7C, 0x56, 0xE0, 0x56, 0xC2, 0x59, 0x63, 0x04, 0x0F, 0xEE, 0x11,
+ 0xA1, 0xB7, 0xC8, 0x49, 0xE1, 0xAC, 0x62, 0xAA, 0xEC, 0x37, 0xC5, 0xD0, 0x1B, 0x1E, 0xFB, 0x21,
+ 0x47, 0x0E, 0xA8, 0xF6, 0x49, 0x97, 0x70, 0x0F, 0x89, 0x65, 0xF1, 0xF1, 0x91, 0x5F, 0x68, 0x14,
+ 0xF5, 0x1D, 0x1B, 0xE2, 0x91, 0x43, 0x80, 0x0D, 0x83, 0x2D, 0xE8, 0xB0, 0x83, 0xB3, 0x79, 0x91,
+ 0x43, 0xFB, 0x6F, 0x92, 0x1C, 0xA9, 0xEF, 0x29, 0xFB, 0x00, 0x4E, 0x2D, 0xFF, 0x84, 0x2F, 0xB4,
+ 0xD0, 0xE8, 0x61, 0xD2, 0xF7, 0x65, 0x5B, 0xBE, 0x2B, 0x57, 0x25, 0xAE, 0x7F, 0x11, 0x8C, 0xE5,
+ 0x97, 0x45, 0x15, 0x29, 0x73, 0xFA, 0x08, 0x98, 0x6C, 0xD5, 0x57, 0xA8, 0x8C, 0x33, 0x14, 0x94,
+ 0xEC, 0x6B, 0x88, 0xEA, 0xE8, 0x34, 0xA1, 0x68, 0xE7, 0xE7, 0xC0, 0x3A, 0x7F, 0xE6, 0x85, 0x49,
+ 0xA3, 0x2F, 0xA6, 0xD3, 0xC8, 0x9B, 0xC0, 0xBF, 0x14, 0xBD, 0x40, 0x92, 0x46, 0x87, 0x3E, 0xB1,
+ 0xF4, 0xD3, 0x8C, 0x51, 0x2F, 0x48, 0x02, 0xC8, 0x6F, 0x28, 0xF0, 0xDB, 0x41, 0x0C, 0xEA, 0x67,
+ 0x0A, 0x69, 0x50, 0xDC, 0xB0, 0x1F, 0x0F, 0x76, 0xDA, 0x91, 0x76, 0x0E, 0x6D, 0xFF, 0x71, 0xCC,
+ 0x86, 0x2F, 0x8B, 0x74, 0x48, 0x5F, 0x76, 0xC8, 0x78, 0x9C, 0x4E, 0x95, 0x76, 0x73, 0x3A, 0xD5,
+ 0x51, 0xFE, 0x32, 0xD2, 0xB2, 0x97, 0x9D, 0x13, 0xA5, 0x06, 0x4D, 0x46, 0x56, 0x00, 0x31, 0x3D,
+ 0xE1, 0xDB, 0x52, 0xFE, 0xF8, 0x8B, 0x93, 0x7C, 0x92, 0x3D, 0x4A, 0x22, 0x7D, 0x6E, 0x19, 0x8F,
+ 0x14, 0xEB, 0x89, 0x75, 0x6A, 0x52, 0x4D, 0xAA, 0x62, 0x9E, 0xB3, 0xF3, 0xD3, 0x92, 0xF8, 0xB6,
+ 0xB3, 0xD8, 0xB4, 0x66, 0x0B, 0x1F, 0xD8, 0xC2, 0x91, 0x9C, 0xB9, 0x51, 0x58, 0xC9, 0xB4, 0xBE,
+ 0x3F, 0xA8, 0x60, 0x86, 0xC8, 0xE5, 0xC9, 0x4F, 0x35, 0x7F, 0x93, 0xD4, 0x63, 0x1F, 0xEF, 0xF7,
+ 0xB7, 0xED, 0x52, 0xD9, 0xD9, 0xE3, 0x7A, 0xF2, 0x49, 0x2D, 0xA7, 0xED, 0xA7, 0x95, 0x33, 0x6B,
+ 0xB6, 0xDF, 0x90, 0x4F, 0xDD, 0xAC, 0xD6, 0x33, 0xE3, 0x2D, 0x1F, 0x3F, 0x9E, 0x15, 0x25, 0x79,
+ 0x5F, 0x8D, 0xF4, 0x86, 0x8D, 0x26, 0xDD, 0x10, 0xEA, 0x3B, 0x0B, 0xF5, 0x8B, 0x83, 0xFA, 0x60,
+ 0x7D, 0xD0, 0x1E, 0x90, 0xF3, 0x13, 0x5A, 0x75, 0xB5, 0x76, 0xEB, 0xFC, 0xBB, 0x1E, 0xEC, 0x30,
+ 0x45, 0x4C, 0xCC, 0x63, 0xD0, 0xB4, 0x83, 0x63, 0x2B, 0x26, 0x52, 0x98, 0xBC, 0xD0, 0x38, 0xC2,
+ 0x28, 0xF0, 0x45, 0x63, 0x2B, 0x44, 0x24, 0xF8, 0x69, 0xC8, 0x4F, 0x52, 0x09, 0x74, 0x25, 0x14,
+ 0x39, 0xDE, 0xDB, 0xEC, 0xD3, 0x46, 0x4C, 0x00, 0xE5, 0xE8, 0xE7, 0x5E, 0xC8, 0xE8, 0xFC, 0x04,
+ 0x80, 0x48, 0x84, 0x57, 0x24, 0x42, 0x87, 0x1D, 0x63, 0x85, 0x28, 0x60, 0xB3, 0x41, 0xD6, 0xD8,
+ 0x81, 0x45, 0xB9, 0x5A, 0x30, 0xDB, 0x90, 0xA6, 0xA1, 0x6E, 0x8E, 0x54, 0x84, 0x51, 0xC3, 0x91,
+ 0x26, 0xE8, 0x9B, 0xFD, 0x57, 0xAD, 0xCA, 0x71, 0xA7, 0x3B, 0xF2, 0x99, 0x28, 0x54, 0x30, 0xA6,
+ 0x66, 0x3D, 0x98, 0x57, 0x90, 0x96, 0xA3, 0x7D, 0xDF, 0xFB, 0xDF, 0x27, 0xCF, 0x17, 0x39, 0xAA,
+ 0xD1, 0x72, 0x75, 0xB6, 0x1B, 0x65, 0x97, 0x28, 0x6C, 0x8B, 0xE6, 0x8F, 0x89, 0xD7, 0x70, 0x62,
+ 0x36, 0x28, 0x90, 0x62, 0x39, 0xB6, 0xEB, 0x14, 0x39, 0xFB, 0xCB, 0x93, 0x09, 0xED, 0x6C, 0xB7,
+ 0xDC, 0xBF, 0x0F, 0x0C, 0xD9, 0x9D, 0x6A, 0x21, 0x32, 0x9C, 0x1E, 0x1B, 0xC7, 0xF8, 0xF4, 0xCA,
+ 0x79, 0x22, 0x11, 0x0C, 0x5C, 0x7B, 0x50, 0xBB, 0xAB, 0x81, 0x9B, 0x08, 0x8F, 0xF1, 0x85, 0x61,
+ 0xA0, 0xCF, 0x68, 0xE4, 0xBB, 0x7A, 0xED, 0x23, 0x29, 0x6C, 0xB9, 0x51, 0x11, 0xEF, 0xE3, 0x59,
+ 0x74, 0x1C, 0xA5, 0x15, 0x6B, 0x5A, 0x5A, 0xEF, 0x40, 0xE9, 0x5D, 0x5E, 0x95, 0x57, 0xAC, 0x3C,
+ 0xF5, 0x0D, 0x30, 0x20, 0x3F, 0xB0, 0xE6, 0xB9, 0x56, 0x57, 0x5A, 0xAD, 0xAF, 0xDC, 0x2B, 0x58,
+ 0x31, 0xAB, 0x17, 0xBA, 0x65, 0x7E, 0x5D, 0x15, 0xB7, 0x5F, 0xC1, 0x8F, 0xB7, 0x79, 0x7E, 0x7D,
+ 0xD9, 0x40, 0xDC, 0x8F, 0x37, 0x1F, 0x77, 0xE2, 0xED, 0xA2, 0x29, 0x97, 0x5F, 0xE2, 0x74, 0x32,
+ 0xCF, 0x4F, 0xB8, 0xD5, 0xF0, 0xED, 0x69, 0xB5, 0x0C, 0x13, 0x70, 0x33, 0x6F, 0x6C, 0xED, 0x57,
+ 0xE8, 0xC4, 0x3D, 0xA2, 0xAC, 0xF7, 0x66, 0x0B, 0x12, 0xD0, 0x7C, 0x6D, 0x3B, 0xAD, 0xDD, 0x38,
+ 0x79, 0xA9, 0xF4, 0xC3, 0x35, 0xF8, 0xF3, 0x5A, 0x8D, 0xA9, 0x84, 0xFD, 0x0C, 0x3F, 0xFD, 0xF8,
+ 0x0D, 0xE9, 0x99, 0xF3, 0x53, 0x5D, 0x5F, 0xB1, 0x66, 0x93, 0x39, 0x12, 0xC9, 0xE0, 0x92, 0x4E,
+ 0xD0, 0x31, 0xF5, 0x13, 0xAD, 0x47, 0xF2, 0x79, 0x4F, 0x68, 0xF4, 0xCB, 0xDE, 0xBB, 0x90, 0x4C,
+ 0x9E, 0x57, 0x28, 0xD5, 0xB2, 0xEB, 0x58, 0x60, 0xF1, 0xEF, 0xED, 0x1D, 0x55, 0xAD, 0xCC, 0x65,
+ 0x99, 0x75, 0xFC, 0x99, 0x29, 0x80, 0x43, 0xBA, 0x4D, 0xB4, 0xC4, 0xDB, 0xC0, 0x00, 0x3B, 0x72,
+ 0xF2, 0x5E, 0x5B, 0xCF, 0xBF, 0x84, 0xDB, 0xAC, 0xD8, 0x40, 0x80, 0x90, 0xA8, 0xE4, 0x47, 0x27,
+ 0x0B, 0x29, 0xB3, 0x5C, 0x0B, 0xF2, 0x65, 0x0B, 0xCD, 0xCA, 0x74, 0x45, 0xE6, 0x2B, 0x8E, 0x6F,
+ 0x07, 0xF9, 0x95, 0xB5, 0xCA, 0x4E, 0x68, 0x1B, 0xD9, 0x73, 0xAD, 0x4A, 0x38, 0xF8, 0xCA, 0x31,
+ 0x4B, 0x2D, 0x32, 0x71, 0x76, 0x55, 0x12, 0xB6, 0x89, 0x2A, 0x06, 0x22, 0x40, 0x3F, 0xF3, 0x32,
+ 0xD9, 0x4F, 0x95, 0xC6, 0xFF, 0x26, 0x07, 0x7D, 0x43, 0xA0, 0x44, 0xDF, 0x54, 0x4D, 0xB2, 0x12,
+ 0xCD, 0x0C, 0xDD, 0x58, 0xB5, 0xCE, 0x8D, 0x55, 0x32, 0xA4, 0xFF, 0x81, 0x2C, 0xA8, 0x11, 0x3A,
+ 0x3E, 0xF1, 0xD3, 0x39, 0xC7, 0x9B, 0x8E, 0xFE, 0x27, 0x8C, 0x12, 0xD3, 0xD7, 0x64, 0xF8, 0xEC,
+ 0xCB, 0xA2, 0x29, 0x69, 0x3B, 0xD2, 0x44, 0xB4, 0xBD, 0x89, 0xC8, 0x58, 0x28, 0x94, 0x6B, 0xE3,
+ 0x6B, 0x5E, 0x47, 0xAA, 0xB9, 0x9E, 0xAD, 0x28, 0xC5, 0xE9, 0xB2, 0x93, 0x53, 0x0E, 0x6E, 0x0E,
+ 0x1F, 0xAE, 0xF7, 0xCA, 0x00, 0x00, 0xFA, 0x2B, 0xEA, 0x8E, 0x7E, 0xB7, 0xAC, 0xFE, 0x3A, 0xC6,
+ 0xF7, 0x2F, 0x64, 0x12, 0xAC, 0x9A, 0xBF, 0x58, 0x53, 0x55, 0x59, 0x76, 0x82, 0x71, 0xEE, 0xE8,
+ 0x10, 0x3C, 0xFB, 0x58, 0x6A, 0xAE, 0x20, 0xCB, 0xD6, 0x79, 0x1D, 0x21, 0xB3, 0xAF, 0x0A, 0x2E,
+ 0xF4, 0x15, 0x87, 0x56, 0x55, 0x7A, 0xEB, 0xE3, 0x1C, 0x99, 0xC5, 0xB5, 0x8F, 0xD6, 0xCA, 0x04,
+ 0x8D, 0xEB, 0xAC, 0xB2, 0x7D, 0x46, 0x18, 0x88, 0xBC, 0x2C, 0xCC, 0x6A, 0xCC, 0x73, 0x4A, 0x8E,
+ 0x6C, 0x3D, 0x7E, 0xE1, 0x3C, 0xD2, 0x54, 0x6F, 0xA4, 0x84, 0x1E, 0x09, 0x22, 0xB2, 0xAD, 0x93,
+ 0x3B, 0xEF, 0x5B, 0xD6, 0x8B, 0x8C, 0x31, 0xD4, 0x48, 0x7C, 0x7E, 0x1B, 0x27, 0xF8, 0x3B, 0x99,
+ 0xA2, 0x01, 0x7A, 0xA7, 0xE5, 0xB8, 0x07, 0xB3, 0x73, 0xD6, 0x6F, 0x68, 0xC5, 0x01, 0xB1, 0xAE,
+ 0x08, 0x4A, 0xF0, 0xB0, 0x66, 0x7C, 0xB6, 0xAE, 0x69, 0xF5, 0x9E, 0xE3, 0x09, 0x44, 0xA1, 0x1A,
+ 0x5E, 0xAA, 0x6C, 0x99, 0x6D, 0x72, 0xAF, 0xE3, 0x6D, 0x39, 0x98, 0xEB, 0x0C, 0x07, 0x9A, 0x7F,
+ 0x51, 0xE3, 0x73, 0xCE, 0x52, 0x98, 0x3C, 0xED, 0xF6, 0x0E, 0xAF, 0x72, 0xEC, 0xA1, 0x7F, 0xE5,
+ 0xC4, 0xC6, 0x1A, 0xB4, 0x40, 0xCB, 0x7F, 0x94, 0x31, 0xA9, 0x1C, 0x29, 0x60, 0xA6, 0xB8, 0xAD,
+ 0x4C, 0xD4, 0x48, 0x7B, 0x90, 0x8D, 0x92, 0x20, 0xEB, 0xE1, 0x81, 0xBE, 0xF6, 0x48, 0x10, 0x7F,
+ 0xA9, 0xB1, 0x2A, 0xC9, 0x81, 0xF4, 0x22, 0x23, 0x93, 0xFE, 0x99, 0x46, 0x51, 0x2D, 0xBA, 0xF1,
+ 0xC9, 0x58, 0x7C, 0x80, 0x38, 0x8C, 0xF3, 0xC9, 0x55, 0x14, 0x12, 0x34, 0x85, 0xF6, 0x48, 0x85,
+ 0xCC, 0x7E, 0xD6, 0x1A, 0x6D, 0x80, 0x28, 0x9E, 0x92, 0x1B, 0x7D, 0x2D, 0xC3, 0x74, 0x10, 0xE1,
+ 0x71, 0x4B, 0x32, 0xB0, 0xBE, 0x3E, 0x1C, 0x0E, 0xAF, 0xC8, 0xA1, 0x42, 0xA8, 0x0F, 0x2A, 0x3E,
+ 0xDB, 0x42, 0x9F, 0x6C, 0xAE, 0x35, 0x86, 0x96, 0xDD, 0x20, 0x41, 0x57, 0xD1, 0x03, 0x86, 0x83,
+ 0x3B, 0x57, 0x27, 0x9D, 0x6E, 0x47, 0x60, 0xE2, 0xFE, 0x46, 0xB6, 0x89, 0x3E, 0xD4, 0xDD, 0x7E,
+ 0x90, 0x06, 0xD3, 0x28, 0x52, 0x42, 0x99, 0xD2, 0xA3, 0x5C, 0x61, 0x85, 0xB8, 0xF4, 0xFA, 0x01,
+ 0x96, 0x08, 0xB6, 0x48, 0x39, 0xA9, 0xB1, 0x49, 0xB4, 0xE8, 0x77, 0x5C, 0xBA, 0x0D, 0x2A, 0x5F,
+ 0xFB, 0x3E, 0xC8, 0x46, 0xE2, 0xE5, 0x16, 0xC6, 0x20, 0x77, 0x0F, 0x37, 0x08, 0xBA, 0xAE, 0x75,
+ 0x20, 0xF0, 0x49, 0x62, 0x55, 0xA1, 0xE5, 0x8A, 0x36, 0x3D, 0xB5, 0xAF, 0xDD, 0xFE, 0xE1, 0xEF,
+ 0xC1, 0x23, 0xFA, 0xF5, 0x43, 0xDD, 0x6F, 0xB7, 0x8E, 0x58, 0xD7, 0x18, 0xA5, 0x37, 0x38, 0xC2,
+ 0x86, 0xD9, 0x5F, 0x8A, 0xA4, 0x2F, 0x8B, 0xEE, 0x23, 0xC2, 0xA1, 0x9C, 0x69, 0x37, 0xAF, 0x5A,
+ 0xE5, 0xFA, 0xDA, 0xD2, 0x77, 0xAA, 0xAE, 0xBF, 0xDA, 0xD2, 0xC1, 0x2E, 0x80, 0x73, 0x0E, 0x1D,
+ 0x83, 0x1C, 0x5F, 0x55, 0xCF, 0x3B, 0xD0, 0xEC, 0x0B, 0x0B, 0x6B, 0xF4, 0xC6, 0x67, 0x98, 0x8B,
+ 0xB3, 0xB2, 0x95, 0x07, 0xCF, 0x23, 0xBC, 0xE3, 0x17, 0x33, 0x36, 0xB7, 0x95, 0xB1, 0xE9, 0x29,
+ 0x69, 0xE8, 0x94, 0x98, 0xE5, 0x23, 0x64, 0x6B, 0x78, 0x7A, 0x53, 0x14, 0x55, 0xF6, 0xFB, 0x22,
+ 0xD9, 0x65, 0x67, 0xF0, 0xFB, 0x42, 0x11, 0xB6, 0x4C, 0xC7, 0xC2, 0x51, 0x98, 0x00, 0x05, 0xB8,
+ 0x85, 0x1B, 0x8B, 0x10, 0xB4, 0x72, 0x9D, 0x55, 0x46, 0xAD, 0xB2, 0xC5, 0x62, 0x66, 0x25, 0xB0,
+ 0xBF, 0x3C, 0x5B, 0xC7, 0x3E, 0x5C, 0x40, 0x5C, 0x29, 0xB5, 0x99, 0xBC, 0xB2, 0x23, 0x43, 0x23,
+ 0xEC, 0xBD, 0xE1, 0x07, 0xED, 0x3A, 0x92, 0xB3, 0xD9, 0xBB, 0x61, 0xC3, 0x8F, 0xDA, 0xF7, 0x61,
+ 0xDD, 0x3B, 0x58, 0x2B, 0x77, 0xB0, 0x6E, 0x15, 0xEA, 0x0D, 0xF5, 0xF5, 0x35, 0x15, 0x7A, 0xAD,
+ 0x97, 0xDE, 0x0E, 0xD7, 0x82, 0x4A, 0x21, 0x38, 0x77, 0x46, 0x7F, 0x8D, 0xE0, 0xDF, 0xAF, 0x63,
+ 0x87, 0xEF, 0xF2, 0x01, 0xB5, 0xEB, 0x6A, 0x4C, 0x1F, 0xF9, 0x63, 0x9D, 0xF9, 0x13, 0x70, 0xB8,
+ 0x5C, 0x6B, 0x52, 0x74, 0xA6, 0xCB, 0xD5, 0x98, 0x92, 0xCC, 0xCC, 0xD9, 0xDC, 0x9B, 0xCB, 0x05,
+ 0x66, 0x7F, 0xB4, 0xE2, 0xC3, 0x42, 0x4D, 0xD5, 0xF1, 0x78, 0x5E, 0x92, 0x7A, 0xAD, 0x16, 0xCA,
+ 0xCE, 0x6F, 0x6C, 0x66, 0xF5, 0xC0, 0xCD, 0x7E, 0xF2, 0xB0, 0x9B, 0xB8, 0x37, 0xBF, 0x3D, 0x4A,
+ 0x2A, 0xA0, 0xFB, 0x36, 0x48, 0xD2, 0xE2, 0x15, 0x0B, 0x08, 0xB6, 0x7D, 0x7E, 0x10, 0x9F, 0x24,
+ 0x2D, 0x26, 0x15, 0x7F, 0xEF, 0x9F, 0x54, 0x97, 0xCF, 0x55, 0x01, 0xA1, 0x7C, 0x53, 0xF0, 0x00,
+ 0xD3, 0xBF, 0x45, 0xA8, 0xD8, 0x6F, 0x24, 0xBB, 0x33, 0xAD, 0xF0, 0x8D, 0x61, 0xB8, 0x20, 0x66,
+ 0xDA, 0x59, 0xCD, 0x89, 0x07, 0xEA, 0xE9, 0x18, 0x30, 0x35, 0x27, 0xC9, 0x73, 0xDD, 0xEA, 0x42,
+ 0x1C, 0xFC, 0x48, 0xAA, 0xDE, 0x5F, 0x26, 0x6F, 0x16, 0x16, 0x4D, 0x63, 0x7B, 0x36, 0xE8, 0x29,
+ 0xD0, 0xE9, 0x2A, 0x62, 0xC7, 0xB9, 0xFA, 0xF2, 0x47, 0xC4, 0xDC, 0x0C, 0x08, 0xB9, 0x7F, 0x0A,
+ 0xB3, 0x94, 0xF0, 0x96, 0x27, 0xD2, 0xB6, 0x3E, 0x48, 0x41, 0xB1, 0x74, 0x74, 0xE0, 0xFB, 0x0E,
+ 0x95, 0xDD, 0x3B, 0x6E, 0x9C, 0xE3, 0x3A, 0x3B, 0xE9, 0x8F, 0x9D, 0xE9, 0xF7, 0x9B, 0x54, 0x10,
+ 0x48, 0xEF, 0x03, 0x38, 0x6F, 0x52, 0xF0, 0x76, 0xE3, 0x83, 0x4E, 0x68, 0x03, 0x70, 0xFE, 0x02,
+ 0xF0, 0x96, 0x64, 0x3E, 0xF4, 0xEE, 0xFA, 0xDF, 0xD7, 0x1B, 0x34, 0xF6, 0x18, 0x27, 0x84, 0x8A,
+ 0x75, 0x63, 0x83, 0x62, 0xE3, 0x16, 0x83, 0x44, 0xEE, 0xE8, 0xBE, 0x93, 0xEF, 0x52, 0x93, 0x6B,
+ 0x6F, 0x50, 0x66, 0x24, 0xEF, 0xF0, 0xE7, 0x07, 0x9A, 0x98, 0xA8, 0xB1, 0x36, 0x0F, 0xF9, 0xF1,
+ 0xE5, 0x37, 0xC9, 0xD1, 0x23, 0xCF, 0x3E, 0x22, 0xE2, 0xBA, 0x38, 0xC0, 0x68, 0xD6, 0xB3, 0xC1,
+ 0x3D, 0x55, 0xD9, 0xC5, 0x00, 0x56, 0x67, 0xBE, 0xE4, 0x5F, 0x0B, 0x45, 0xF6, 0x61, 0xD8, 0xE2,
+ 0x20, 0x49, 0x3B, 0xED, 0x7B, 0x52, 0x7B, 0xC5, 0xDC, 0x90, 0xBF, 0xCA, 0x0D, 0x79, 0xA9, 0xA4,
+ 0x60, 0x10, 0x25, 0xF2, 0x9C, 0x0F, 0xE5, 0xCF, 0x3C, 0x6F, 0x5A, 0x75, 0x17, 0x63, 0x5A, 0x3A,
+ 0x72, 0xB8, 0x7E, 0xCC, 0x01, 0x89, 0x34, 0xAF, 0x8E, 0xE2, 0x53, 0x35, 0x05, 0xAA, 0xB4, 0xDD,
+ 0x97, 0xE6, 0x92, 0xFA, 0xAC, 0x21, 0xDF, 0x7D, 0x3B, 0xD2, 0xE3, 0xBA, 0x4B, 0xD2, 0x20, 0x14,
+ 0x51, 0x2D, 0xE3, 0x65, 0x6B, 0x0B, 0x28, 0x64, 0x26, 0xFA, 0xB5, 0x2B, 0x21, 0xF6, 0x4D, 0x3C,
+ 0xEF, 0x4C, 0x79, 0xE7, 0xC8, 0x81, 0x5D, 0x41, 0xAB, 0xFE, 0x5A, 0x64, 0xD6, 0x47, 0x0C, 0x9E,
+ 0x9D, 0xB5, 0x9A, 0x6B, 0xA6, 0xED, 0x3C, 0x5D, 0x6B, 0xD1, 0x9A, 0xBB, 0xD3, 0xC4, 0x73, 0x5A,
+ 0x68, 0x79, 0x6D, 0xA7, 0x59, 0x79, 0x27, 0x0D, 0x4E, 0xED, 0x06, 0x6C, 0xB9, 0x03, 0x8A, 0xD2,
+ 0x62, 0xCE, 0xE3, 0x09, 0x89, 0x49, 0x0B, 0xD2, 0xA6, 0x5B, 0x64, 0x48, 0x75, 0xDC, 0x30, 0x40,
+ 0x80, 0x27, 0x3E, 0x8E, 0xCB, 0x43, 0xB9, 0xDF, 0x66, 0xA5, 0x88, 0x23, 0xE9, 0xF0, 0x74, 0xE3,
+ 0x58, 0x77, 0x63, 0xAA, 0xA0, 0x31, 0x50, 0x4A, 0x47, 0xC7, 0x5B, 0xD1, 0xF0, 0x1D, 0x84, 0x43,
+ 0x95, 0xE0, 0x68, 0x83, 0xFE, 0x43, 0x90, 0x05, 0xEF, 0x85, 0x0B, 0x22, 0x7D, 0xC9, 0x95, 0x6C,
+ 0x69, 0xCE, 0xE3, 0xFA, 0x71, 0x2E, 0x8A, 0x74, 0xA2, 0x35, 0x2A, 0x0A, 0xBC, 0xE8, 0xA6, 0xB0,
+ 0x2C, 0xC7, 0xC6, 0x0D, 0x61, 0x85, 0xC9, 0x57, 0xDE, 0x64, 0xE4, 0x32, 0x1D, 0xE8, 0xE8, 0xDA,
+ 0x68, 0x75, 0x38, 0x57, 0xDE, 0x81, 0xBB, 0xEF, 0x43, 0x1D, 0xBB, 0xD4, 0x9F, 0x7B, 0xE3, 0xB8,
+ 0x82, 0x77, 0xC5, 0x96, 0xAE, 0xCF, 0xA1, 0xCC, 0x34, 0x37, 0x77, 0x8B, 0x63, 0x9E, 0x64, 0x7D,
+ 0xBA, 0xFD, 0x15, 0x08, 0x81, 0x16, 0x56, 0x75, 0x3E, 0x3F, 0x6F, 0xC5, 0xA8, 0x9C, 0x17, 0x78,
+ 0xB2, 0xB2, 0x67, 0xC2, 0x41, 0x41, 0xCC, 0x6A, 0xB8, 0x2E, 0xEE, 0x8E, 0x6C, 0x22, 0xB3, 0x6C,
+ 0xCD, 0xB0, 0xF5, 0xF4, 0xB4, 0x7D, 0x15, 0x48, 0x93, 0x8E, 0x35, 0x3B, 0xC4, 0xC1, 0x17, 0x57,
+ 0xCE, 0xF5, 0xB7, 0x0B, 0x8E, 0xC8, 0xBE, 0xC8, 0x57, 0x0A, 0x73, 0xB5, 0x40, 0x2F, 0x8F, 0x8F,
+ 0x71, 0x0D, 0x06, 0xEA, 0x8B, 0x41, 0x07, 0x0C, 0x0B, 0x1D, 0xAB, 0x29, 0x0A, 0xB5, 0x7D, 0x4F,
+ 0xE6, 0x2B, 0x24, 0x52, 0xD8, 0x68, 0xF0, 0x0A, 0x5A, 0xE3, 0x4F, 0x5C, 0x4C, 0xD4, 0x6B, 0x45,
+ 0x38, 0xA2, 0x0D, 0x42, 0x61, 0xE3, 0x6B, 0x70, 0x24, 0x79, 0x69, 0xB8, 0x48, 0xDE, 0x89, 0x96,
+ 0xE4, 0x53, 0x46, 0x20, 0x29, 0x95, 0xD2, 0x4F, 0x03, 0x62, 0x64, 0x8B, 0x65, 0x53, 0xC6, 0x8C,
+ 0x4F, 0xEE, 0xC5, 0x25, 0xA4, 0x63, 0x85, 0x4D, 0x7C, 0xA1, 0xCF, 0xBB, 0xB4, 0x53, 0x76, 0x32,
+ 0x53, 0x3B, 0xDD, 0x66, 0xFA, 0xD2, 0xCE, 0xCE, 0xA4, 0xD2, 0xB3, 0x44, 0x3E, 0x31, 0xC5, 0x67,
+ 0x20, 0xBD, 0xA7, 0xE3, 0x91, 0xDD, 0x39, 0x2F, 0xAE, 0xB1, 0x52, 0xF4, 0x0D, 0x04, 0x44, 0xF2,
+ 0x78, 0x18, 0x7C, 0x01, 0x4B, 0x2C, 0x74, 0x86, 0x7C, 0x80, 0x9D, 0x7B, 0xB3, 0x2A, 0x26, 0x14,
+ 0x83, 0xAA, 0xB4, 0x15, 0x63, 0x80, 0x11, 0x1D, 0xA9, 0x5D, 0xCC, 0x7A, 0xCB, 0x8B, 0xFD, 0xC9,
+ 0x40, 0xE5, 0x91, 0x26, 0x04, 0x57, 0x1C, 0xF4, 0x0B, 0xF4, 0x76, 0x75, 0xD2, 0xF9, 0x4B, 0xDF,
+ 0xE9, 0xA5, 0x3F, 0x76, 0x4A, 0xCD, 0xB3, 0xF8, 0xD3, 0x17, 0x56, 0x15, 0xE0, 0x39, 0xA6, 0xAD,
+ 0x8D, 0xF9, 0xE9, 0x92, 0xED, 0xB1, 0xAF, 0xCE, 0x00, 0x7F, 0xBC, 0xC8, 0x3C, 0x8A, 0xFD, 0x7B,
+ 0xFC, 0x48, 0x70, 0x81, 0x4A, 0xF3, 0x34, 0xF1, 0x09, 0x20, 0xA8, 0xE7, 0x14, 0x97, 0xA4, 0x4A,
+ 0x16, 0xD8, 0xD0, 0xEC, 0x31, 0x34, 0xA4, 0xC0, 0x6B, 0xCD, 0xEA, 0xA9, 0xE7, 0xC7, 0xA6, 0x00,
+ 0x9E, 0x69, 0xA0, 0x95, 0xC4, 0x3C, 0x03, 0x36, 0x42, 0x92, 0xF1, 0x1C, 0x43, 0x05, 0xFA, 0xDC,
+ 0x8C, 0x86, 0xD9, 0x17, 0xFA, 0x3E, 0xC3, 0x1C, 0x8C, 0xC4, 0x8E, 0x81, 0x7C, 0x4B, 0xEB, 0x0C,
+ 0x74, 0xE4, 0xDA, 0x68, 0xD0, 0x57, 0xC2, 0x05, 0x48, 0xE3, 0x53, 0x3A, 0xC5, 0xD0, 0x3A, 0x7A,
+ 0x2A, 0x45, 0x93, 0x18, 0x03, 0xC7, 0xCB, 0x36, 0x3E, 0x1B, 0x5D, 0xFC, 0xC4, 0xEE, 0x40, 0x92,
+ 0x85, 0x84, 0x58, 0x05, 0x15, 0x35, 0x72, 0x5A, 0x61, 0x46, 0x4C, 0x75, 0x8B, 0xA3, 0xAE, 0xE2,
+ 0x8A, 0xCD, 0x46, 0xE9, 0xC3, 0xC3, 0x58, 0x0A, 0x2B, 0xC6, 0x2E, 0xA6, 0x3B, 0xCE, 0x48, 0xB8,
+ 0x68, 0xA6, 0x6B, 0xB0, 0x07, 0x23, 0x0E, 0xEC, 0x61, 0x3D, 0xCE, 0x6A, 0x73, 0x84, 0x33, 0xB5,
+ 0x06, 0x88, 0x26, 0xEA, 0x2A, 0x96, 0xBA, 0xBC, 0x29, 0xD0, 0x81, 0xFF, 0xAA, 0x87, 0x54, 0x53,
+ 0xB9, 0x95, 0x0D, 0x39, 0x2C, 0x15, 0xEC, 0xBB, 0x8E, 0x3C, 0x2C, 0xA9, 0xFC, 0x6C, 0x86, 0x23,
+ 0x11, 0xFB, 0x4C, 0x2C, 0x62, 0x9D, 0xCA, 0xCF, 0x06, 0x5F, 0x4A, 0x9A, 0xBC, 0xD9, 0x93, 0xEE,
+ 0xCA, 0x77, 0x64, 0xB7, 0x56, 0x06, 0x67, 0x10, 0xDB, 0x37, 0x5D, 0x29, 0xFE, 0xCA, 0xD4, 0xFF,
+ 0xE4, 0x6D, 0x82, 0x01, 0x82, 0x05, 0x6D, 0x8F, 0xD6, 0xCC, 0xD6, 0xC6, 0x35, 0xE5, 0xCE, 0x9C,
+ 0x12, 0xE9, 0x1D, 0x8E, 0x89, 0x74, 0x3E, 0x02, 0x8A, 0xFA, 0x9E, 0xE2, 0xB6, 0x39, 0xE7, 0x39,
+ 0xB4, 0x08, 0x11, 0xA0, 0xD0, 0x54, 0xCA, 0x71, 0x1C, 0x14, 0xEF, 0xD1, 0xC5, 0x56, 0x49, 0xF3,
+ 0x3D, 0xCC, 0xC0, 0x7E, 0x30, 0x24, 0x66, 0x83, 0x3E, 0x9E, 0xD2, 0x22, 0x2B, 0xAC, 0x9F, 0x2B,
+ 0x06, 0x7A, 0xFF, 0xCC, 0x2D, 0xFA, 0x68, 0xBA, 0xCA, 0x18, 0xEA, 0xFC, 0x53, 0x0E, 0xA0, 0x16,
+ 0xBE, 0xE2, 0x3C, 0xEC, 0x97, 0xB0, 0x71, 0x52, 0xE8, 0xE2, 0xE8, 0x00, 0x2C, 0x9D, 0x7F, 0x82,
+ 0x01, 0x82, 0x3A, 0x37, 0xE2, 0xBB, 0x92, 0x65, 0xA9, 0xC2, 0x44, 0xE3, 0x37, 0x75, 0xCD, 0x93,
+ 0xA4, 0x96, 0x74, 0x33, 0xBE, 0x34, 0xDC, 0x5F, 0x75, 0x11, 0x30, 0x8F, 0x73, 0xBA, 0x12, 0x5B,
+ 0xFF, 0x7D, 0x05, 0x85, 0xF8, 0x20, 0x7A, 0x0A, 0x4C, 0x7D, 0xAB, 0xA4, 0x5E, 0xE9, 0x25, 0xDB,
+ 0x6C, 0xC4, 0x0B, 0x27, 0x36, 0x66, 0x08, 0xD2, 0x5C, 0x0B, 0x54, 0x37, 0x3E, 0x84, 0x53, 0x97,
+ 0x24, 0xE7, 0xB0, 0x2F, 0x19, 0x49, 0x14, 0x73, 0x2F, 0x6E, 0x8B, 0xF2, 0x5F, 0xFC, 0x23, 0xD6,
+ 0xD5, 0xC1, 0x99, 0x15, 0x13, 0x67, 0xC0, 0xA5, 0x4C, 0x26, 0xEA, 0x7A, 0xE4, 0xC8, 0x1E, 0x4B,
+ 0x73, 0x95, 0x0E, 0x0E, 0x00, 0x9F, 0xFC, 0x68, 0xC6, 0x28, 0x4B, 0x86, 0x33, 0x24, 0x0C, 0x24,
+ 0xA3, 0x83, 0x23, 0x5A, 0x64, 0xB6, 0x50, 0x8D, 0xF5, 0x37, 0xC7, 0x47, 0xBF, 0x60, 0x15, 0xCE,
+ 0x62, 0xEB, 0x85, 0x88, 0x89, 0x4F, 0xCD, 0x9B, 0x53, 0x25, 0xFE, 0xC6, 0x17, 0xB3, 0x88, 0x88,
+ 0x50, 0xDC, 0x85, 0x79, 0x02, 0x75, 0x3D, 0xFD, 0x0C, 0x2C, 0x47, 0x52, 0xAB, 0xF7, 0x9E, 0x0B,
+ 0x86, 0xF7, 0x84, 0xD1, 0x48, 0xAE, 0x53, 0x56, 0xEB, 0xE2, 0xE4, 0x82, 0x7C, 0x0F, 0x2C, 0x09,
+ 0x17, 0xC1, 0xE7, 0x1A, 0x65, 0x6D, 0x36, 0x8E, 0xB9, 0xD0, 0x24, 0x5B, 0x92, 0x65, 0xAF, 0xB3,
+ 0x61, 0xD0, 0x34, 0xE2, 0x4E, 0x0D, 0x52, 0x97, 0x89, 0x33, 0x30, 0x80, 0xDD, 0xC3, 0x40, 0xC7,
+ 0xB8, 0x3A, 0x34, 0x7A, 0x4F, 0xD9, 0xFC, 0xD2, 0x3E, 0x2B, 0xF7, 0xF8, 0xBD, 0xF7, 0xFC, 0x97,
+ 0x85, 0x92, 0x55, 0x5F, 0xF1, 0xD8, 0x8C, 0x02, 0x04, 0x87, 0x05, 0x70, 0x50, 0xE3, 0x98, 0xAB,
+ 0xCA, 0xC5, 0xC2, 0x38, 0x1B, 0x09, 0x68, 0xC1, 0xF5, 0x66, 0x38, 0x6A, 0xD2, 0x78, 0xA5, 0x9D,
+ 0x53, 0x73, 0x84, 0xCB, 0x7E, 0x6C, 0x0C, 0x9C, 0xB9, 0x6A, 0xAC, 0xBA, 0xD2, 0x95, 0x12, 0xE2,
+ 0x76, 0x7B, 0x32, 0xBC, 0xB3, 0xCD, 0x46, 0xDE, 0x0E, 0x58, 0xE0, 0xCE, 0x69, 0x0F, 0x1E, 0x88,
+ 0xDF, 0xFE, 0xD1, 0x10, 0x1B, 0xF8, 0xB8, 0x9C, 0x5D, 0x94, 0xB0, 0x2C, 0xE4, 0x9A, 0xD1, 0xA0,
+ 0x0F, 0x45, 0x97, 0xCE, 0xBB, 0xFF, 0x36, 0x91, 0x39, 0x58, 0x71, 0xBC, 0x08, 0x9B, 0x83, 0x61,
+ 0xDB, 0x76, 0xCF, 0x88, 0x0B, 0x72, 0x86, 0x61, 0xB9, 0x7A, 0xE1, 0x88, 0x90, 0xE5, 0x26, 0x1E,
+ 0xED, 0xB8, 0x29, 0xCE, 0x8C, 0xD6, 0xC0, 0x28, 0x84, 0x7B, 0xE5, 0x5C, 0x7D, 0x1C, 0x9E, 0xFE,
+ 0x7A, 0xF9, 0x39, 0xC7, 0x7E, 0xCE, 0x5F, 0xFC, 0x9C, 0x47, 0x0B, 0x02, 0x72, 0x80, 0xD1, 0xB1,
+ 0x5A, 0x26, 0xF4, 0xD1, 0xEF, 0x67, 0xA6, 0x67, 0x40, 0x0A, 0x39, 0xFB, 0xBB, 0xC8, 0xDE, 0xCB,
+ 0xAD, 0x08, 0x9E, 0x2A, 0x5D, 0x00, 0x2A, 0x87, 0x15, 0xD4, 0x9D, 0x51, 0x6C, 0xA0, 0x49, 0xA9,
+ 0x51, 0xC5, 0x54, 0xC8, 0xF6, 0x2F, 0x12, 0x75, 0xF1, 0xE0, 0x81, 0xB7, 0xA8, 0xC3, 0x39, 0xB5,
+ 0x60, 0x79, 0xB1, 0xD9, 0xC8, 0x42, 0x2A, 0xE7, 0xDC, 0xCD, 0xE1, 0x1E, 0x8C, 0xCF, 0x03, 0x7F,
+ 0x45, 0x1B, 0x23, 0xD1, 0x63, 0x5F, 0x77, 0xD8, 0x67, 0xB4, 0x41, 0xD2, 0x29, 0x32, 0xAE, 0x13,
+ 0xC5, 0xCD, 0xBD, 0xA7, 0x8F, 0xA1, 0xDD, 0xB3, 0x16, 0x0E, 0xCA, 0x05, 0x2D, 0x2B, 0x31, 0x59,
+ 0x6C, 0x8A, 0xFC, 0x66, 0x53, 0x5C, 0xC8, 0x16, 0x0E, 0xCB, 0xF6, 0xC3, 0x65, 0xCD, 0x82, 0x33,
+ 0xDE, 0x62, 0x66, 0x70, 0x02, 0xFC, 0x0C, 0x21, 0xF9, 0x0A, 0xD1, 0x77, 0x06, 0xCD, 0x50, 0x2C,
+ 0xC7, 0x95, 0x68, 0x49, 0xB5, 0x7D, 0x68, 0x6C, 0x3D, 0x0B, 0xA9, 0xE8, 0xF4, 0xCE, 0x9C, 0xBB,
+ 0x69, 0x85, 0xFD, 0x8C, 0x2B, 0x69, 0x47, 0x31, 0xA2, 0x39, 0x92, 0xBD, 0xA3, 0x73, 0x0B, 0x43,
+ 0x49, 0xEB, 0x7C, 0x0A, 0xA2, 0x48, 0x91, 0xCF, 0xF1, 0xE2, 0xC5, 0x24, 0x3F, 0xBF, 0x25, 0x75,
+ 0x9F, 0x59, 0x63, 0x69, 0xE2, 0x6C, 0x9A, 0x5A, 0x29, 0x99, 0xE9, 0xDF, 0xE5, 0x62, 0x9A, 0xDC,
+ 0xCB, 0x5E, 0x29, 0x2D, 0xF0, 0xF7, 0xB4, 0x7E, 0x23, 0x5E, 0xF2, 0xDC, 0x25, 0x2F, 0xD2, 0xF1,
+ 0x22, 0x96, 0xC4, 0x37, 0x12, 0xBB, 0x46, 0x23, 0x61, 0x8C, 0x98, 0x23, 0x61, 0xDA, 0xF3, 0x44,
+ 0x9E, 0x32, 0x46, 0xCD, 0xAA, 0x39, 0xAC, 0x57, 0x4B, 0xE4, 0x9A, 0xC9, 0x50, 0xEE, 0xD1, 0x3F,
+ 0x25, 0xAE, 0x62, 0x2E, 0x88, 0x0F, 0xE7, 0x5F, 0xC7, 0x08, 0x4B, 0x94, 0xB4, 0xD7, 0x8F, 0x44,
+ 0x65, 0xD2, 0x89, 0x6D, 0xB8, 0x0D, 0xB9, 0xCD, 0xE7, 0x39, 0xD0, 0x62, 0xBD, 0xDB, 0xF2, 0x9F,
+ 0xAA, 0x1B, 0xCB, 0xC6, 0xBC, 0x48, 0x18, 0x79, 0x58, 0xC1, 0xB7, 0x9A, 0x9A, 0x60, 0x19, 0x04,
+ 0x6A, 0xF8, 0xAB, 0xA5, 0xDC, 0x45, 0x6C, 0xE5, 0xE8, 0x1D, 0x96, 0x57, 0x1C, 0x0C, 0x48, 0x5E,
+ 0xE8, 0x58, 0xD7, 0x09, 0xF9, 0xB2, 0x21, 0x6E, 0xC7, 0x41, 0x23, 0xB0, 0x25, 0x72, 0x44, 0xDC,
+ 0x94, 0x07, 0xCC, 0xD5, 0x95, 0xF8, 0x28, 0xF4, 0x21, 0x8A, 0xAA, 0x26, 0x27, 0x74, 0xF3, 0xF3,
+ 0x9D, 0x18, 0x9E, 0x83, 0xB1, 0xD2, 0x5E, 0x02, 0x0F, 0x19, 0x97, 0x27, 0xA4, 0x22, 0x69, 0x41,
+ 0x27, 0x3F, 0xD4, 0xF9, 0x59, 0xAE, 0x50, 0x8F, 0x39, 0x24, 0xB5, 0x9E, 0x35, 0x2F, 0x30, 0x60,
+ 0x9E, 0xA4, 0x5E, 0xAC, 0x2D, 0xC9, 0x57, 0x79, 0xD2, 0xBF, 0xF7, 0x94, 0xAA, 0x70, 0xEA, 0x45,
+ 0xC3, 0xDB, 0x90, 0xEE, 0xDE, 0x28, 0x15, 0xEB, 0x37, 0xD5, 0x91, 0x83, 0xF4, 0x5D, 0x51, 0xD0,
+ 0x48, 0x7E, 0x50, 0x33, 0x61, 0x86, 0xC3, 0xD6, 0x3A, 0x15, 0xC0, 0x42, 0x7B, 0xC3, 0x29, 0x5D,
+ 0xE8, 0x2F, 0x75, 0x4F, 0xAC, 0x33, 0xF2, 0x34, 0xA2, 0x8D, 0x62, 0x58, 0x52, 0x1C, 0x44, 0xDC,
+ 0x52, 0x00, 0x63, 0x4D, 0xCC, 0xB6, 0x81, 0xD7, 0x6B, 0xB1, 0x5F, 0x6C, 0x38, 0x4C, 0x23, 0xFD,
+ 0xE5, 0x21, 0x83, 0x78, 0x8A, 0xE9, 0x25, 0x71, 0x11, 0x71, 0x59, 0x34, 0xDA, 0x0C, 0x0B, 0x96,
+ 0x9D, 0x91, 0xD7, 0xBA, 0x4A, 0x5C, 0xDE, 0x5A, 0x87, 0x9F, 0xC0, 0x65, 0xE7, 0x49, 0x52, 0x21,
+ 0x87, 0xE9, 0xD6, 0x7D, 0xE3, 0x2C, 0xC3, 0x8E, 0x94, 0xB3, 0x78, 0xAC, 0x9A, 0x9A, 0x88, 0x29,
+ 0x81, 0x21, 0x4E, 0x2F, 0x66, 0x8D, 0xF9, 0x20, 0xD4, 0x5E, 0x19, 0x17, 0xA1, 0x10, 0x40, 0x40,
+ 0xEF, 0x68, 0x1F, 0x93, 0x04, 0x72, 0xA5, 0xB7, 0x21, 0x4A, 0x96, 0x4C, 0xEA, 0x15, 0x77, 0xCB,
+ 0x96, 0x3B, 0xB7, 0xF2, 0xE8, 0x23, 0x67, 0x6D, 0x95, 0x5C, 0xCF, 0xEA, 0x7D, 0x53, 0xAA, 0x3A,
+ 0x7F, 0xAA, 0x30, 0x87, 0x90, 0x9E, 0xC8, 0xB4, 0x52, 0xF0, 0x2F, 0x7A, 0xF5, 0x67, 0x19, 0x69,
+ 0xA5, 0x9B, 0xE6, 0x9A, 0x22, 0xEB, 0xBA, 0x0B, 0x31, 0x4F, 0x8A, 0x01, 0x45, 0x2C, 0x55, 0x10,
+ 0x99, 0x2E, 0xE7, 0xB5, 0x25, 0x87, 0xA9, 0xFC, 0xC9, 0xE6, 0xD7, 0x61, 0x00, 0x3E, 0x76, 0x0A,
+ 0xC7, 0x4E, 0x2A, 0x68, 0x4E, 0xCB, 0x79, 0xA1, 0xE7, 0x54, 0x4C, 0xDA, 0xB0, 0x1A, 0x48, 0x11,
+ 0xCC, 0xA3, 0x3F, 0x13, 0x09, 0xFE, 0x54, 0x52, 0x2B, 0xA5, 0x99, 0xC6, 0x82, 0xA6, 0x91, 0xC7,
+ 0x55, 0x40, 0xB8, 0x54, 0x3C, 0xAE, 0x4F, 0x0A, 0x16, 0x83, 0x15, 0x58, 0x61, 0xFA, 0x2B, 0x83,
+ 0x0D, 0x5E, 0xDC, 0x88, 0x9C, 0xEA, 0xBC, 0x64, 0x6D, 0x13, 0x5F, 0xE6, 0x6D, 0xA8, 0x48, 0xA5,
+ 0xE9, 0x44, 0xC5, 0x14, 0x64, 0x20, 0xF9, 0xF6, 0x11, 0xC4, 0x79, 0x45, 0x11, 0x96, 0xE5, 0x77,
+ 0x57, 0xCC, 0x6F, 0xE1, 0x43, 0x6F, 0x36, 0x63, 0xA1, 0x81, 0xCA, 0x71, 0xFD, 0x7E, 0xDE, 0xD5,
+ 0x16, 0x7B, 0x01, 0x4E, 0x1B, 0x3D, 0x93, 0xAA, 0xEB, 0xD9, 0xF4, 0xB4, 0x2B, 0x8C, 0xEF, 0xB7,
+ 0x30, 0x3E, 0x49, 0x51, 0x48, 0xE8, 0x5C, 0xC5, 0x89, 0xDF, 0x5D, 0x53, 0x12, 0x8F, 0xDF, 0x24,
+ 0x9D, 0xF2, 0xB7, 0x71, 0x32, 0x7F, 0x26, 0x65, 0x10, 0xBE, 0xFD, 0xA6, 0x72, 0x8A, 0x46, 0xBA,
+ 0x8D, 0x2D, 0xA7, 0xBF, 0x58, 0x77, 0x2E, 0x83, 0x5B, 0xE2, 0x0C, 0xD3, 0x90, 0xCB, 0x33, 0xCD,
+ 0x6D, 0xB7, 0xE1, 0x44, 0x39, 0xD3, 0x9F, 0xF1, 0xA9, 0x09, 0xBE, 0xB2, 0xF1, 0x3E, 0xCF, 0x40,
+ 0x23, 0x6C, 0x83, 0x1C, 0xEB, 0x36, 0x1B, 0x81, 0xF3, 0xA9, 0x87, 0x21, 0x18, 0x26, 0x02, 0x16,
+ 0x2D, 0x31, 0x6D, 0x7C, 0x3F, 0xAE, 0x80, 0x18, 0xB6, 0x31, 0x89, 0xC5, 0xD8, 0x64, 0x9F, 0xD7,
+ 0xC5, 0x6D, 0x50, 0x5C, 0x4F, 0xC0, 0xCE, 0xA9, 0xEC, 0x9D, 0x84, 0xBB, 0x66, 0x08, 0x4C, 0x94,
+ 0xE3, 0x03, 0x7B, 0x57, 0xEC, 0x00, 0x97, 0xF2, 0x38, 0x3C, 0x5A, 0x84, 0xCB, 0x69, 0x2A, 0x07,
+ 0x17, 0x9A, 0x90, 0x23, 0x9D, 0x1D, 0x7F, 0xEE, 0x65, 0xFB, 0x5F, 0x56, 0xB0, 0x02, 0x6E, 0x47,
+ 0x7B, 0xAD, 0x46, 0x3D, 0x54, 0xB4, 0xA3, 0x08, 0x8B, 0x69, 0x13, 0x00, 0xE5, 0x1D, 0xF3, 0x10,
+ 0x3A, 0x80, 0xAC, 0x84, 0x2D, 0xC4, 0x14, 0xAB, 0xD4, 0x76, 0xE9, 0x23, 0x3C, 0x58, 0x06, 0x3F,
+ 0xB4, 0x59, 0xE5, 0xA8, 0x5D, 0x16, 0xA0, 0x17, 0x5D, 0x5B, 0x8D, 0x72, 0x32, 0x84, 0x86, 0xA9,
+ 0x86, 0x07, 0x0F, 0x02, 0xF2, 0x82, 0x24, 0xC9, 0x2E, 0x56, 0xA2, 0xF2, 0x83, 0x3B, 0xAA, 0x31,
+ 0x46, 0x80, 0xCF, 0x49, 0x67, 0x30, 0x38, 0xE9, 0x06, 0xC3, 0x7F, 0xA2, 0x15, 0x7E, 0x4C, 0x29,
+ 0x5E, 0x75, 0x9C, 0x93, 0xDD, 0xCE, 0xC8, 0x50, 0xAA, 0x1A, 0xC9, 0xD3, 0x1E, 0x75, 0xA2, 0x8F,
+ 0xC4, 0x9D, 0xD2, 0x52, 0x3A, 0x9C, 0xB3, 0x68, 0x5F, 0x7B, 0x54, 0x89, 0x4C, 0x90, 0xAC, 0xC8,
+ 0x84, 0x7F, 0x7A, 0x51, 0x65, 0xAC, 0x70, 0x84, 0x12, 0xEC, 0xA2, 0x52, 0x07, 0xBE, 0xD1, 0x6E,
+ 0xEF, 0x41, 0x4A, 0x56, 0xF8, 0x02, 0x79, 0x56, 0xF1, 0x3D, 0xA3, 0x4C, 0xB8, 0x36, 0x3A, 0xE8,
+ 0x22, 0xD7, 0x19, 0x47, 0xAF, 0xA2, 0x9E, 0xF8, 0xE1, 0x4F, 0xA6, 0x40, 0xE6, 0x7A, 0xD3, 0x61,
+ 0xBC, 0x3A, 0x05, 0xB9, 0x91, 0xF6, 0xB6, 0xCE, 0x61, 0x8F, 0xBF, 0x66, 0xAB, 0xF2, 0x93, 0x80,
+ 0x3C, 0xA2, 0xF4, 0x7B, 0xA5, 0xF1, 0xAF, 0xB9, 0xEA, 0x4E, 0x71, 0xB7, 0x72, 0xAE, 0x63, 0xFE,
+ 0x85, 0x00, 0xA0, 0xAE, 0xD2, 0x78, 0xFC, 0x5D, 0x37, 0xAE, 0x71, 0xF6, 0x88, 0x77, 0xDD, 0xA5,
+ 0x83, 0x9B, 0xF4, 0xE7, 0x6A, 0xA8, 0xCD, 0xE9, 0x9F, 0x47, 0xF4, 0x93, 0xF4, 0x7D, 0xFC, 0x3B,
+ 0x24, 0x19, 0x7A, 0xD8, 0x99, 0xBD, 0x66, 0x96, 0xA8, 0x11, 0xF1, 0xC7, 0x12, 0xFE, 0x23, 0x70,
+ 0xD3, 0x40, 0xCA, 0x9A, 0x99, 0x9E, 0x08, 0x11, 0x3B, 0xF7, 0x4C, 0x4D, 0x51, 0x20, 0xF0, 0x6A,
+ 0x4F, 0x88, 0xD9, 0xD0, 0x03, 0xB3, 0x33, 0x13, 0xF3, 0x38, 0x09, 0xA3, 0xCD, 0x33, 0x1A, 0xC7,
+ 0x03, 0xA7, 0xF9, 0x73, 0xC2, 0xCB, 0x90, 0x96, 0x4E, 0xF3, 0x0C, 0x8F, 0xBE, 0x2F, 0xE3, 0xD2,
+ 0x6A, 0x9E, 0x75, 0xD0, 0xC0, 0x6B, 0xD2, 0xB8, 0x17, 0xA0, 0x0C, 0x1F, 0x32, 0x89, 0x08, 0xC2,
+ 0x75, 0x23, 0x17, 0xB6, 0x11, 0xEA, 0xD2, 0x34, 0xC1, 0xD7, 0x7A, 0x69, 0x80, 0xD2, 0x39, 0x59,
+ 0xFB, 0xC8, 0xE5, 0x0F, 0x95, 0x4C, 0xDD, 0x6F, 0x4A, 0xF4, 0xAD, 0xFD, 0xFC, 0x54, 0x07, 0x03,
+ 0x60, 0x89, 0xE4, 0xD0, 0x25, 0xA6, 0x0F, 0x96, 0xE8, 0x9D, 0x5F, 0x18, 0x36, 0xB5, 0xEA, 0xAE,
+ 0x81, 0x6C, 0xB3, 0xB9, 0x0B, 0x0D, 0xB1, 0x96, 0xE7, 0x3D, 0x16, 0xB0, 0xCD, 0xC4, 0x51, 0x96,
+ 0xB2, 0x59, 0xA5, 0xDD, 0x81, 0x8D, 0x02, 0xDF, 0x50, 0x72, 0x86, 0x8E, 0x3B, 0xC7, 0xE3, 0x23,
+ 0x06, 0xD0, 0x20, 0x3A, 0xBC, 0xD8, 0x14, 0x40, 0x20, 0xD5, 0x90, 0xEF, 0xEA, 0xA1, 0x43, 0x24,
+ 0x7D, 0x8A, 0x9C, 0x89, 0x18, 0x76, 0xFC, 0xF4, 0x61, 0x92, 0xBA, 0x9B, 0xF5, 0x56, 0x12, 0x8B,
+ 0xD0, 0xFB, 0xA6, 0x8A, 0xB9, 0x94, 0xD6, 0xC3, 0xE0, 0x10, 0x72, 0x24, 0x5A, 0xAB, 0x8C, 0xF9,
+ 0x08, 0x41, 0xAD, 0x8E, 0x70, 0xF5, 0xF6, 0x3B, 0xB5, 0x94, 0x77, 0xC7, 0x02, 0x38, 0x6F, 0xD9,
+ 0x4E, 0x38, 0xE9, 0x1E, 0x7B, 0xCF, 0x1E, 0xE8, 0x5A, 0x75, 0x1E, 0x86, 0x71, 0x54, 0xAE, 0x17,
+ 0x8A, 0xFF, 0x72, 0xFC, 0x1C, 0x0D, 0x30, 0xB9, 0xB4, 0x32, 0xAB, 0x19, 0x52, 0x24, 0xBF, 0x04,
+ 0xA1, 0x66, 0x0C, 0x5E, 0xAF, 0x45, 0x5E, 0xCB, 0x6B, 0xBB, 0xD1, 0x98, 0x6D, 0x63, 0x1C, 0x88,
+ 0x6C, 0xC4, 0x8D, 0x34, 0x47, 0x0E, 0x94, 0x82, 0xF9, 0x86, 0x72, 0x28, 0xC9, 0x89, 0x6F, 0x2F,
+ 0x3D, 0x9B, 0xB3, 0xD8, 0x71, 0x65, 0x59, 0xBE, 0x28, 0x66, 0xA3, 0x7B, 0x91, 0x27, 0x25, 0xBD,
+ 0xF0, 0xCA, 0x87, 0x9B, 0x1A, 0x70, 0x17, 0xBE, 0x93, 0x2B, 0xEA, 0xBC, 0x6D, 0x59, 0x67, 0x3C,
+ 0x72, 0x2D, 0xBC, 0xF7, 0x5B, 0x08, 0x05, 0x1A, 0x60, 0x14, 0x17, 0xE9, 0x98, 0x35, 0x7A, 0x38,
+ 0xB0, 0x30, 0x12, 0x24, 0x21, 0x92, 0x8F, 0x20, 0x53, 0x2A, 0xF2, 0x31, 0x64, 0x4A, 0xA2, 0xB2,
+ 0x4F, 0x46, 0xA6, 0x9E, 0xE1, 0xE6, 0x5C, 0x37, 0xFF, 0xAC, 0xBC, 0x65, 0x99, 0x56, 0xB1, 0xE8,
+ 0x23, 0x55, 0xCA, 0xFD, 0x2F, 0x20, 0xD5, 0xBD, 0x1D, 0x38, 0xB2, 0xCB, 0xEC, 0x00, 0x18, 0x2C,
+ 0x3B, 0x55, 0x4A, 0x8A, 0xD1, 0x57, 0x0D, 0x10, 0xE0, 0xA7, 0x23, 0xB8, 0x82, 0xB5, 0x68, 0x3F,
+ 0x11, 0x99, 0x51, 0xE1, 0xAD, 0xB2, 0xBD, 0x02, 0x59, 0xE5, 0xEF, 0x58, 0x39, 0x7B, 0x87, 0x06,
+ 0x4B, 0x0F, 0xE9, 0x45, 0x28, 0x5E, 0x52, 0x71, 0xCF, 0x07, 0xC3, 0xCC, 0x5A, 0x61, 0xB1, 0xC9,
+ 0x56, 0x7A, 0x2D, 0x97, 0xBD, 0xC2, 0xFA, 0xDB, 0xC1, 0xE7, 0x2F, 0x87, 0x89, 0xEC, 0x0F, 0x10,
+ 0x2C, 0xA5, 0xD9, 0x14, 0xD6, 0x6C, 0x84, 0xAF, 0x64, 0x76, 0x20, 0xF4, 0x38, 0xAF, 0x49, 0xED,
+ 0x9F, 0x9C, 0x13, 0x3C, 0xC3, 0x93, 0x12, 0xF8, 0x4C, 0xF5, 0x2F, 0x55, 0x8E, 0xB0, 0x74, 0x21,
+ 0xD1, 0x80, 0x19, 0xF6, 0x66, 0xD3, 0xD2, 0x0F, 0xD9, 0x8E, 0xCF, 0x2A, 0x3C, 0x4D, 0x10, 0xDF,
+ 0x66, 0xDC, 0x4B, 0x0E, 0xDF, 0x6C, 0x8B, 0x3C, 0x5B, 0x4A, 0x53, 0x83, 0xED, 0xC7, 0xDB, 0x46,
+ 0xA3, 0x71, 0xD7, 0xAB, 0xFC, 0xA9, 0xBD, 0x84, 0x77, 0x25, 0xB3, 0x52, 0x98, 0x0F, 0x60, 0x87,
+ 0xE5, 0x8B, 0x6A, 0xF5, 0x01, 0x8F, 0x90, 0xB7, 0xFF, 0x81, 0x37, 0x08, 0x4D, 0x53, 0xB1, 0x5A,
+ 0x89, 0x79, 0x95, 0xBC, 0xBD, 0x14, 0xE5, 0x06, 0x54, 0xA9, 0x6F, 0x90, 0x55, 0x51, 0x7A, 0xBD,
+ 0x92, 0xA7, 0x75, 0x0B, 0x05, 0xD1, 0x6B, 0x3C, 0x9C, 0x93, 0x62, 0xC7, 0x6F, 0x44, 0x67, 0xD5,
+ 0x98, 0x5B, 0x3C, 0x5D, 0xB2, 0x23, 0xF1, 0xE0, 0x3E, 0x96, 0xDC, 0x39, 0x20, 0xA6, 0x4D, 0xD5,
+ 0x3B, 0xE9, 0xF9, 0x82, 0xD9, 0xD7, 0x68, 0xC4, 0x10, 0x9E, 0xD0, 0xBA, 0x79, 0xAC, 0x1F, 0x73,
+ 0xA3, 0x08, 0xAD, 0x63, 0x59, 0x1E, 0x4C, 0x3C, 0xBD, 0x64, 0xF7, 0x96, 0xC6, 0x24, 0x9B, 0xC0,
+ 0xAC, 0xA6, 0xDD, 0xB8, 0x0B, 0x5C, 0xBB, 0x5D, 0xC8, 0x45, 0x3C, 0x11, 0x77, 0x6E, 0x31, 0x6E,
+ 0x12, 0x48, 0xB2, 0x2E, 0x91, 0xEC, 0x71, 0xC6, 0x48, 0x61, 0x2D, 0xA1, 0xF2, 0xCA, 0x0C, 0xF5,
+ 0xD8, 0x3E, 0x91, 0x3D, 0x35, 0x7A, 0xF2, 0x02, 0xA0, 0xD5, 0xC9, 0x25, 0x09, 0x42, 0x26, 0x78,
+ 0x82, 0x74, 0xC5, 0x8B, 0x7A, 0x57, 0xCC, 0x73, 0xAA, 0xDE, 0x38, 0x7D, 0x79, 0x94, 0xAD, 0xB9,
+ 0x1C, 0xBB, 0x90, 0x80, 0xEA, 0x09, 0xBF, 0x61, 0x1C, 0xB4, 0x17, 0xDB, 0x0C, 0xED, 0x34, 0x3A,
+ 0xAC, 0x79, 0x3B, 0x82, 0x7F, 0x49, 0xAF, 0xD7, 0xFB, 0x72, 0x83, 0xF0, 0xFE, 0x27, 0x93, 0xA7,
+ 0x9B, 0xFE, 0x94, 0xF9, 0xDB, 0xEF, 0x63, 0x62, 0xBF, 0x03, 0xC6, 0x0B, 0x76, 0xFC, 0x51, 0xF4,
+ 0x7F, 0x9A, 0x71, 0xFD, 0x8A, 0x48, 0x06, 0x63, 0x53, 0x9F, 0x34, 0x59, 0x63, 0x5C, 0x74, 0xB8,
+ 0x64, 0xF5, 0x1F, 0xAC, 0x03, 0xDF, 0xB7, 0xFB, 0xEB, 0x50, 0x1A, 0x42, 0xA3, 0xB6, 0x5C, 0x3A,
+ 0x95, 0x7B, 0xBA, 0xC4, 0x24, 0x25, 0x0B, 0x4F, 0x4D, 0x08, 0xDD, 0x77, 0x45, 0x2D, 0x06, 0x96,
+ 0xC8, 0x67, 0x1D, 0xB3, 0x1A, 0x1D, 0x5C, 0x97, 0xC2, 0x98, 0x70, 0x6B, 0xC5, 0x69, 0x57, 0xF8,
+ 0x33, 0xBB, 0x7F, 0x75, 0xDD, 0x80, 0x51, 0x63, 0x74, 0x75, 0x55, 0x47, 0xFF, 0x6F, 0x07, 0x31,
+ 0x91, 0x87, 0xE6, 0xCE, 0xAC, 0xA3, 0x71, 0x27, 0x77, 0x7F, 0xC3, 0x68, 0xA6, 0x05, 0x2E, 0x13,
+ 0x59, 0x96, 0x22, 0x6B, 0x74, 0x98, 0x94, 0x8A, 0xFC, 0x4A, 0x72, 0xAF, 0x44, 0x21, 0x54, 0x6E,
+ 0xE0, 0x9C, 0x90, 0x56, 0xEE, 0xD3, 0x0A, 0x89, 0x8C, 0x63, 0xCF, 0x35, 0x1C, 0x59, 0x66, 0x56,
+ 0xC4, 0x9C, 0x85, 0xC0, 0xC8, 0x8C, 0x16, 0x4B, 0xE5, 0x85, 0x92, 0x8B, 0xDE, 0xBE, 0xB5, 0x07,
+ 0xC1, 0xDB, 0xB7, 0x91, 0x12, 0x87, 0xAA, 0xFE, 0x5A, 0x0A, 0x6B, 0x74, 0x7C, 0x79, 0x0B, 0x61,
+ 0xF0, 0x96, 0x24, 0xB3, 0x02, 0xEB, 0x6E, 0xBC, 0x5D, 0x4D, 0x0E, 0x25, 0xCC, 0x29, 0x34, 0x9F,
+ 0xB5, 0x23, 0x3E, 0x20, 0xD4, 0x55, 0x32, 0x02, 0xB4, 0x82, 0x00, 0xED, 0x24, 0x80, 0xF6, 0x46,
+ 0xA0, 0xBD, 0x0F, 0xE3, 0x80, 0xDD, 0x58, 0xE0, 0x9C, 0xB7, 0x81, 0x86, 0x75, 0x03, 0xCD, 0x6E,
+ 0x1B, 0x43, 0x15, 0xC6, 0xD0, 0xCF, 0x86, 0x9C, 0xFC, 0x00, 0x2A, 0xF1, 0x4D, 0x73, 0x74, 0x11,
+ 0x5E, 0x55, 0x41, 0xFB, 0xBB, 0xD1, 0x59, 0xC2, 0xA5, 0x50, 0x00, 0x53, 0x1E, 0x89, 0xA7, 0x9C,
+ 0xDE, 0xDF, 0xD0, 0x14, 0x1E, 0x12, 0x0A, 0x1B, 0x3E, 0x8F, 0x73, 0xA8, 0xC9, 0x7A, 0x9C, 0x0D,
+ 0xDC, 0x76, 0x19, 0x79, 0x78, 0x87, 0x99, 0xAC, 0x02, 0x28, 0x33, 0x70, 0x88, 0x41, 0xC5, 0x09,
+ 0x16, 0x1E, 0xF0, 0x8A, 0x0B, 0xBF, 0x95, 0xC4, 0x38, 0xB0, 0x9F, 0x75, 0x13, 0xCA, 0xE8, 0x2B,
+ 0xAC, 0xD3, 0x99, 0x29, 0x3E, 0x32, 0x7D, 0xC3, 0x85, 0x3E, 0x93, 0x8F, 0xD6, 0x0F, 0x73, 0x1E,
+ 0x91, 0x6C, 0x8C, 0xEF, 0x67, 0xE8, 0xCF, 0x69, 0x3E, 0x9B, 0xB8, 0x43, 0xFD, 0xE4, 0x71, 0x2F,
+ 0x80, 0x8B, 0xC4, 0x11, 0x5C, 0x8D, 0x47, 0x70, 0x35, 0x0E, 0xEE, 0x3A, 0x15, 0xF1, 0x0D, 0x8B,
+ 0x20, 0x83, 0x53, 0xE4, 0xC2, 0xD5, 0x11, 0x90, 0x60, 0x27, 0xCD, 0x7A, 0x63, 0x18, 0x1B, 0xC0,
+ 0x7D, 0xBD, 0x5F, 0x0C, 0x7B, 0xA7, 0xBD, 0xC5, 0x00, 0xEA, 0xFA, 0x1E, 0x23, 0xBD, 0x51, 0x4C,
+ 0x78, 0x1B, 0xC8, 0xB5, 0xB8, 0xEF, 0x43, 0x47, 0x18, 0x39, 0xF5, 0x27, 0x4B, 0xEE, 0xDA, 0x21,
+ 0xE1, 0x6B, 0x46, 0x97, 0xDE, 0x69, 0xDE, 0xCD, 0x27, 0x92, 0x82, 0x32, 0x1E, 0xDF, 0x1F, 0xC2,
+ 0x3E, 0xAB, 0x1B, 0x03, 0xA6, 0xC5, 0x8B, 0x5D, 0xC1, 0xB6, 0x5D, 0x9A, 0xA6, 0x19, 0xB6, 0xEA,
+ 0x3B, 0x19, 0x34, 0x11, 0x8E, 0x89, 0x23, 0xDE, 0xA7, 0xB5, 0x80, 0x55, 0x75, 0xC2, 0x81, 0x08,
+ 0x9B, 0xC5, 0xD3, 0x6F, 0x9B, 0xE5, 0xCC, 0x27, 0x20, 0x85, 0xB7, 0x35, 0x9E, 0xEB, 0xC9, 0x71,
+ 0x6A, 0x79, 0xDF, 0xDA, 0xE2, 0xA1, 0x7E, 0x3C, 0x9D, 0xAD, 0x53, 0x64, 0xB1, 0x32, 0xE8, 0xDA,
+ 0xE8, 0x32, 0xC5, 0xFA, 0x36, 0xBB, 0x48, 0x6C, 0x4F, 0x60, 0xBA, 0x11, 0xCA, 0x26, 0x1F, 0x5A,
+ 0x95, 0x8D, 0xC0, 0x03, 0x30, 0xC4, 0x9B, 0xA3, 0xCE, 0x6C, 0x06, 0x10, 0xC8, 0x97, 0xB1, 0x9F,
+ 0xA3, 0x22, 0x13, 0xAC, 0x39, 0x4A, 0x8C, 0xFF, 0xC3, 0xD7, 0x71, 0x25, 0x3B, 0x40, 0x39, 0x25,
+ 0xB7, 0x13, 0xE1, 0x57, 0x76, 0xF6, 0x88, 0x69, 0x99, 0x8C, 0x1C, 0xB9, 0xA1, 0x0F, 0x26, 0x86,
+ 0x77, 0xC4, 0xDF, 0x0B, 0xB3, 0x49, 0x3C, 0xC5, 0x51, 0xC1, 0x39, 0x39, 0xCC, 0xFE, 0x63, 0xD6,
+ 0x1C, 0xCD, 0xBD, 0x4F, 0xCB, 0x82, 0x18, 0xC0, 0x0E, 0x18, 0xA5, 0x61, 0x06, 0x2F, 0x32, 0x6B,
+ 0x66, 0xDD, 0x84, 0x0A, 0x9C, 0xD0, 0xE0, 0xDA, 0xDB, 0x5B, 0x0C, 0x34, 0x86, 0xA2, 0xDB, 0xAD,
+ 0x4F, 0x99, 0x1A, 0x96, 0x9B, 0xE5, 0xF3, 0x85, 0x14, 0x63, 0x6F, 0xFF, 0x2D, 0xB2, 0x71, 0xD6,
+ 0x48, 0x1F, 0x07, 0xB8, 0x70, 0xA4, 0xDC, 0xEC, 0xE0, 0x3B, 0xE2, 0xC2, 0xCC, 0x2E, 0x69, 0x89,
+ 0x3B, 0xCE, 0x22, 0xC6, 0xDC, 0xEF, 0x91, 0x9D, 0xFB, 0xEC, 0x42, 0x23, 0x03, 0xA7, 0xD2, 0x06,
+ 0xB0, 0x67, 0x51, 0x5D, 0x61, 0xCB, 0x48, 0x68, 0xD3, 0x84, 0x2F, 0x1C, 0xE2, 0x6F, 0x3F, 0x43,
+ 0x96, 0x3C, 0xD2, 0x8E, 0x7D, 0xA2, 0xE3, 0x6C, 0xCB, 0xED, 0x5D, 0x32, 0xF8, 0x67, 0x63, 0x3C,
+ 0xF2, 0x93, 0x7F, 0x7E, 0xD2, 0xCF, 0xBE, 0x0D, 0x07, 0x32, 0x16, 0x0A, 0xE2, 0xC4, 0xEE, 0x4C,
+ 0x71, 0x88, 0xA9, 0xEE, 0x9C, 0x03, 0xFE, 0x71, 0x0B, 0xC9, 0x5E, 0x6C, 0xC3, 0x6C, 0x8E, 0x59,
+ 0x78, 0x4A, 0xD1, 0xB9, 0xDF, 0x9B, 0xE8, 0x75, 0x9A, 0x55, 0x64, 0x9C, 0x5C, 0xA7, 0x85, 0xBA,
+ 0xF4, 0xF2, 0xAC, 0x07, 0x75, 0x2E, 0x60, 0xDF, 0xBC, 0xA0, 0x84, 0xD8, 0x62, 0x44, 0x08, 0xD4,
+ 0xD9, 0x79, 0x96, 0x67, 0x55, 0x46, 0xCD, 0x2B, 0xBA, 0x02, 0x57, 0xBE, 0x5D, 0x6E, 0xEF, 0x7D,
+ 0xFF, 0x4A, 0xEE, 0x7C, 0xCB, 0xC9, 0x20, 0x6A, 0x28, 0x81, 0x1A, 0x96, 0x73, 0xE9, 0xC8, 0x37,
+ 0x74, 0x82, 0xB4, 0x65, 0x16, 0x5F, 0x66, 0x4B, 0xAF, 0xE3, 0xE4, 0x50, 0x6B, 0x20, 0x40, 0xB1,
+ 0x44, 0x07, 0x46, 0x64, 0x35, 0x08, 0xAF, 0x5E, 0xAA, 0x69, 0x6D, 0x5A, 0xAB, 0xC9, 0x52, 0x01,
+ 0x11, 0xDE, 0x13, 0xCE, 0x68, 0xA9, 0x86, 0xBA, 0x08, 0xB8, 0x85, 0x26, 0x89, 0x0B, 0xE6, 0x9B,
+ 0x35, 0xB3, 0x47, 0xE9, 0xE7, 0xCA, 0x9B, 0x85, 0xCC, 0x8B, 0xB2, 0x89, 0xF4, 0xC6, 0x65, 0x78,
+ 0x85, 0x66, 0xFF, 0x66, 0x74, 0x45, 0xCD, 0x32, 0x32, 0x01, 0x7A, 0x44, 0xE4, 0xA1, 0x4C, 0xC8,
+ 0x18, 0x80, 0xB2, 0x79, 0x04, 0x57, 0xCF, 0xF3, 0x5F, 0x08, 0x0E, 0x46, 0x1A, 0x20, 0x03, 0xC5,
+ 0xBA, 0x24, 0x67, 0xBB, 0xE2, 0x45, 0x72, 0x24, 0x45, 0x33, 0xFB, 0x12, 0x25, 0xCF, 0xBE, 0x3B,
+ 0x0F, 0x6E, 0x82, 0xBA, 0xC8, 0x4B, 0x03, 0x8A, 0x9B, 0x48, 0x4F, 0x78, 0xB9, 0x8D, 0x2B, 0xEB,
+ 0xBD, 0xFA, 0x2C, 0x08, 0x39, 0x49, 0x11, 0x52, 0xCD, 0x3A, 0xB7, 0x93, 0x25, 0x9D, 0x8C, 0x75,
+ 0x56, 0x7B, 0x98, 0x11, 0x9E, 0x61, 0x4F, 0x7A, 0x29, 0x12, 0xCA, 0xB3, 0xE6, 0xD8, 0x92, 0x27,
+ 0x39, 0x1B, 0x0C, 0x87, 0x1E, 0xDD, 0x00, 0x5E, 0x80, 0x06, 0x29, 0x97, 0xFB, 0xFE, 0xDD, 0x36,
+ 0x9B, 0x5C, 0x9A, 0xFA, 0x73, 0x89, 0x15, 0xC6, 0xFB, 0x13, 0xA8, 0x87, 0xF9, 0xA1, 0xF6, 0xAE,
+ 0xE7, 0xA5, 0x44, 0xD8, 0x2B, 0x76, 0x85, 0xEC, 0x3B, 0xCF, 0x6A, 0x13, 0x3B, 0xE1, 0xF8, 0x71,
+ 0x39, 0x6B, 0xD3, 0x33, 0xD6, 0x3A, 0x35, 0xDF, 0x13, 0xDB, 0xF0, 0x87, 0xF5, 0x47, 0x63, 0x1F,
+ 0x72, 0x33, 0x24, 0x0B, 0x95, 0x0A, 0xC6, 0x4B, 0x3B, 0xEB, 0x19, 0xAC, 0x64, 0x6A, 0x6B, 0x4C,
+ 0x2D, 0xB5, 0xBA, 0x86, 0x2C, 0x1A, 0x66, 0x9D, 0x2E, 0xE7, 0xC1, 0x83, 0x3F, 0x69, 0xE5, 0x60,
+ 0x0B, 0x01, 0x41, 0x15, 0xA6, 0x52, 0x4D, 0xD6, 0x58, 0xC8, 0x1A, 0x8B, 0xA9, 0xE5, 0x6B, 0x96,
+ 0xB4, 0xD2, 0xC5, 0x48, 0x1C, 0x1C, 0x12, 0xC1, 0x9D, 0x59, 0xBD, 0x14, 0x09, 0x26, 0xE8, 0x12,
+ 0xA4, 0x53, 0x4C, 0x38, 0x0B, 0xDF, 0x3A, 0x94, 0xDE, 0x87, 0x27, 0x5F, 0xFC, 0x60, 0xC8, 0x20,
+ 0x0D, 0x01, 0x47, 0x89, 0x80, 0x01, 0x06, 0x4B, 0x2E, 0xE5, 0xF4, 0x67, 0xC4, 0xF4, 0x9B, 0xF1,
+ 0x54, 0xA8, 0xC1, 0x1E, 0xC7, 0x68, 0x76, 0xAC, 0x00, 0x6A, 0x9E, 0x8F, 0x85, 0x92, 0xBD, 0xC5,
+ 0x37, 0x53, 0x5F, 0x71, 0x72, 0x5F, 0x4D, 0xB9, 0x56, 0x0C, 0x2B, 0x8F, 0x8C, 0xC1, 0xEC, 0xA8,
+ 0xDC, 0x8C, 0x35, 0x4F, 0x12, 0xBB, 0xDD, 0x88, 0x34, 0x91, 0x38, 0x38, 0xE3, 0x7C, 0x3F, 0xA7,
+ 0x94, 0xEB, 0xB0, 0x08, 0x72, 0x95, 0xE6, 0xF0, 0xF7, 0xC2, 0x16, 0x4D, 0x41, 0x9E, 0xF7, 0xFA,
+ 0x8F, 0x1B, 0xE6, 0x13, 0x61, 0x3E, 0xFB, 0x2E, 0x87, 0x47, 0x31, 0xFA, 0xFD, 0xAC, 0xCC, 0x7E,
+ 0xDB, 0xA8, 0xE0, 0x1C, 0xBF, 0xEA, 0x44, 0x91, 0x61, 0x8D, 0xB2, 0xFB, 0xED, 0x25, 0x80, 0xBD,
+ 0x50, 0x4B, 0x47, 0x63, 0x6D, 0x56, 0xEC, 0xEA, 0xD8, 0x3F, 0xED, 0x36, 0x9B, 0x7F, 0x3D, 0xEA,
+ 0x8C, 0xA7, 0xD9, 0xE3, 0x06, 0xE3, 0x3E, 0xC2, 0xCC, 0x7B, 0xDC, 0x58, 0x64, 0xA7, 0xC2, 0x18,
+ 0xA4, 0x38, 0x07, 0xFB, 0xC8, 0xAC, 0x31, 0x41, 0x6B, 0xC6, 0x63, 0x22, 0xF5, 0x51, 0x08, 0xA7,
+ 0x2D, 0x83, 0x57, 0xD5, 0x59, 0xAD, 0x3E, 0x11, 0x95, 0x62, 0xBB, 0x0C, 0x21, 0xB1, 0x52, 0x25,
+ 0x2B, 0x00, 0xF8, 0x65, 0x55, 0xDC, 0xB1, 0x8B, 0x12, 0x32, 0x0F, 0x31, 0xA1, 0x61, 0xFF, 0x6B,
+ 0xE3, 0x38, 0x40, 0x2C, 0xD4, 0x99, 0x9F, 0x42, 0x22, 0xAE, 0xD8, 0x08, 0x5A, 0x76, 0x0C, 0x4E,
+ 0x72, 0xB9, 0x81, 0x64, 0x6B, 0x6E, 0x87, 0xEF, 0x3A, 0x48, 0x84, 0xA1, 0x71, 0xA9, 0x2D, 0x42,
+ 0x6E, 0x20, 0x93, 0xE1, 0xC0, 0xD3, 0x9E, 0x21, 0x95, 0x3A, 0xA5, 0x0B, 0xE4, 0xEC, 0x08, 0xF3,
+ 0xCB, 0x9C, 0x5B, 0x30, 0xA9, 0xB3, 0x11, 0xB2, 0x9C, 0x79, 0x3F, 0xC0, 0x87, 0x03, 0x65, 0xA4,
+ 0x22, 0xF1, 0xC8, 0x27, 0x76, 0x18, 0xDB, 0x65, 0xBC, 0x41, 0x9E, 0x1C, 0xFE, 0xF6, 0xC5, 0xF3,
+ 0x97, 0xD4, 0x64, 0x93, 0xE8, 0xA6, 0x9F, 0xC1, 0x86, 0xE6, 0x35, 0x57, 0x67, 0x12, 0x04, 0xC0,
+ 0x7F, 0x74, 0x7B, 0xB5, 0x8A, 0x12, 0xCF, 0x1B, 0xAC, 0x69, 0x4B, 0x0B, 0xA2, 0x00, 0x07, 0x22,
+ 0xE4, 0x6D, 0x7F, 0xF3, 0xE1, 0x34, 0xBF, 0xA0, 0x9B, 0x52, 0x1C, 0x71, 0x5B, 0x4D, 0xD1, 0x34,
+ 0x75, 0xC3, 0xE1, 0x42, 0x14, 0x61, 0x30, 0x06, 0x4B, 0x4E, 0x8B, 0xA3, 0x6F, 0x2A, 0x10, 0x69,
+ 0xD8, 0xE8, 0xF8, 0x94, 0x74, 0x8F, 0x0C, 0xBC, 0x66, 0xFA, 0xF2, 0x58, 0x79, 0x11, 0x19, 0xC6,
+ 0xEE, 0x91, 0x81, 0x63, 0xE1, 0x6D, 0x62, 0x8E, 0xEB, 0x0A, 0x07, 0x73, 0x41, 0x02, 0x6C, 0x6D,
+ 0x7B, 0xF7, 0x84, 0xE6, 0x6B, 0xFE, 0x66, 0x01, 0xF2, 0xED, 0x29, 0x3D, 0x36, 0xB3, 0x37, 0xD5,
+ 0xD1, 0x85, 0x7A, 0x2D, 0xF4, 0x5E, 0xBB, 0x7E, 0x77, 0x55, 0x76, 0x22, 0xC7, 0xD9, 0x40, 0xBB,
+ 0xE2, 0xA2, 0xD8, 0x00, 0xC1, 0xE0, 0xF8, 0x3E, 0xC7, 0x91, 0xC4, 0x82, 0x9D, 0xBF, 0xDF, 0x2B,
+ 0x01, 0x42, 0x54, 0xAA, 0x8B, 0xA2, 0x4A, 0x7C, 0x69, 0xCF, 0x97, 0xE4, 0xEB, 0xBC, 0x70, 0xC1,
+ 0xAB, 0xBB, 0xA1, 0x59, 0x37, 0x06, 0xA8, 0xB7, 0xE5, 0x40, 0x2F, 0xA0, 0xC1, 0xA1, 0x28, 0x47,
+ 0x32, 0x6E, 0xDE, 0x25, 0x9F, 0xEC, 0x29, 0xB5, 0x38, 0x89, 0xE6, 0x98, 0x9C, 0x01, 0x0D, 0xD4,
+ 0x19, 0x71, 0x41, 0x47, 0x57, 0x4B, 0xA2, 0x4F, 0x16, 0x91, 0xEA, 0xB8, 0xF3, 0xAD, 0x28, 0x77,
+ 0xA1, 0x77, 0xB4, 0x6A, 0x6A, 0xE2, 0xEC, 0xB8, 0xA1, 0xFE, 0xA9, 0xE9, 0xA2, 0xAF, 0xFE, 0x55,
+ 0x24, 0xB6, 0xAB, 0x8E, 0x5B, 0x22, 0x91, 0x83, 0x6E, 0x8C, 0x01, 0x2E, 0xBF, 0xCA, 0xC6, 0xA5,
+ 0x91, 0xB8, 0xC5, 0x95, 0x63, 0x59, 0x19, 0x71, 0x35, 0x67, 0x38, 0xED, 0x52, 0xD8, 0xD3, 0xCE,
+ 0x8D, 0xCB, 0xDA, 0x45, 0x56, 0x54, 0x67, 0x35, 0x14, 0x6F, 0x5E, 0x7D, 0x43, 0xCE, 0xE4, 0xEA,
+ 0x8A, 0x63, 0x08, 0x62, 0xEC, 0x59, 0x34, 0x19, 0xE6, 0x08, 0xAB, 0xA2, 0xA2, 0x4B, 0x30, 0xA4,
+ 0x95, 0x98, 0x4D, 0x49, 0x31, 0xC0, 0x1C, 0x45, 0x63, 0x33, 0x4C, 0xD8, 0xE1, 0x07, 0x28, 0x5F,
+ 0x34, 0x38, 0xA9, 0x59, 0xCF, 0xFC, 0xE5, 0x2A, 0x2F, 0x2B, 0x4D, 0x08, 0x8C, 0x2F, 0x00, 0x08,
+ 0x28, 0x66, 0x0D, 0x30, 0x69, 0xA8, 0x1C, 0x93, 0xC0, 0xCE, 0xA4, 0x67, 0x3A, 0xA4, 0x17, 0x99,
+ 0x6D, 0x72, 0x54, 0xE9, 0xDC, 0x9A, 0x09, 0x14, 0x3E, 0x88, 0x92, 0x9E, 0xB2, 0x26, 0xF6, 0x00,
+ 0xD0, 0xE0, 0xB8, 0x1F, 0x43, 0x99, 0x5A, 0xDD, 0xB7, 0x2D, 0xC8, 0xDF, 0xC1, 0x5A, 0x2D, 0x61,
+ 0xD2, 0x48, 0x03, 0xD6, 0x1D, 0x76, 0x1F, 0xA7, 0x89, 0x90, 0xCA, 0x84, 0x82, 0x96, 0xDD, 0x19,
+ 0x79, 0x76, 0x58, 0x3E, 0x25, 0x0A, 0x36, 0x9F, 0x96, 0x01, 0x88, 0xD6, 0xE8, 0xB0, 0x39, 0xCB,
+ 0x9A, 0x31, 0x0D, 0xD8, 0xE9, 0xC2, 0x1C, 0x1B, 0xC6, 0x51, 0xD9, 0x82, 0xB2, 0x37, 0xB7, 0x6D,
+ 0xBA, 0x19, 0xFC, 0x5D, 0x00, 0x3A, 0xE0, 0xE0, 0x24, 0x54, 0x43, 0x32, 0xE8, 0x85, 0x73, 0xCD,
+ 0x75, 0x91, 0x9C, 0x5E, 0x15, 0x26, 0x0F, 0xD3, 0xCF, 0x1F, 0x37, 0x06, 0x5E, 0x3E, 0xB3, 0x2A,
+ 0xE0, 0x66, 0x18, 0x3D, 0x84, 0x1E, 0x7B, 0xAA, 0x4A, 0x0C, 0x76, 0x19, 0x41, 0x29, 0x77, 0xF4,
+ 0x21, 0x69, 0xC7, 0xDF, 0x23, 0xE1, 0x57, 0x1D, 0x43, 0xE6, 0x69, 0xA7, 0xA2, 0x37, 0x0D, 0xA1,
+ 0x17, 0x60, 0xEB, 0x74, 0xA4, 0x68, 0x35, 0x5E, 0x94, 0x49, 0x4B, 0xEB, 0x3F, 0xF9, 0x5B, 0x20,
+ 0x93, 0x9F, 0x3D, 0x9A, 0x02, 0x03, 0xFD, 0x80, 0xA7, 0x9F, 0x1E, 0x3E, 0x04, 0x5E, 0xFA, 0x2D,
+ 0x9E, 0xE2, 0xF9, 0xEC, 0xC1, 0x22, 0x79, 0x9B, 0xCD, 0xFF, 0xF6, 0x60, 0xF1, 0xF0, 0x48, 0xFD,
+ 0x83, 0x71, 0xCE, 0xE1, 0xC3, 0x19, 0x2E, 0x6F, 0x7B, 0x6F, 0xBA, 0xC5, 0xC3, 0x78, 0xFE, 0x37,
+ 0x6A, 0x71, 0xF1, 0x10, 0x28, 0xE9, 0xE2, 0x4A, 0xFD, 0x41, 0x70, 0xD2, 0x57, 0x4F, 0x4F, 0x37,
+ 0x5F, 0x3F, 0xFD, 0xF2, 0xB7, 0x48, 0x55, 0x5F, 0x53, 0xDA, 0x9B, 0xA3, 0x37, 0x47, 0x47, 0xEA,
+ 0x45, 0x47, 0x66, 0x33, 0x2F, 0xF9, 0xEF, 0x2B, 0x70, 0xEF, 0x1E, 0x1E, 0x45, 0xC6, 0x8C, 0x15,
+ 0xB6, 0x41, 0x89, 0x7A, 0x3E, 0xA2, 0x96, 0x93, 0xFB, 0x2E, 0x8B, 0xBF, 0x01, 0x35, 0x6C, 0xA1,
+ 0x2B, 0x9C, 0xF7, 0xE1, 0x91, 0x42, 0x6B, 0xC7, 0xFA, 0xAD, 0xD4, 0xF6, 0x89, 0xAF, 0x9F, 0xD0,
+ 0x63, 0x94, 0x05, 0xF2, 0x65, 0x23, 0xF2, 0xE8, 0x6B, 0x28, 0x44, 0x13, 0xF6, 0xC7, 0x83, 0x93,
+ 0x62, 0x86, 0xC4, 0xCA, 0x38, 0x6C, 0x41, 0x1D, 0x34, 0xAF, 0xE2, 0xDA, 0xD8, 0xC2, 0xB1, 0xF0,
+ 0xDC, 0x33, 0x3F, 0x4A, 0xD2, 0x5E, 0x9E, 0xE5, 0xD5, 0x6C, 0x9D, 0x27, 0x66, 0x14, 0xD4, 0x62,
+ 0x17, 0x0D, 0x3F, 0x2D, 0x4D, 0xD0, 0x3A, 0xEB, 0xD0, 0xE1, 0xCB, 0xCE, 0x7D, 0xFD, 0xCA, 0x9C,
+ 0xA4, 0xD6, 0x5F, 0x28, 0x39, 0x7F, 0x04, 0x73, 0xC5, 0x70, 0x4D, 0xBA, 0xB9, 0x44, 0x4C, 0x1F,
+ 0xA5, 0xD5, 0x62, 0xDD, 0xC3, 0x2E, 0x7D, 0xE0, 0x8A, 0xBD, 0x3D, 0xB5, 0xE4, 0x7C, 0x62, 0x3D,
+ 0xDB, 0x8F, 0x1B, 0xDC, 0x0E, 0x85, 0xB3, 0x97, 0xC6, 0x25, 0x87, 0xF7, 0xA6, 0xBB, 0x8C, 0xD3,
+ 0xDF, 0xC7, 0xE5, 0x71, 0x85, 0x3F, 0x6C, 0xDC, 0x9A, 0xA8, 0xC6, 0xC6, 0x65, 0xF6, 0x0B, 0x63,
+ 0xB6, 0x88, 0xEC, 0xD7, 0x96, 0xC9, 0xB8, 0xA1, 0xF0, 0x32, 0x7B, 0x1E, 0x95, 0xBB, 0x3E, 0x67,
+ 0x0A, 0x2A, 0x3F, 0x3F, 0xE4, 0xB7, 0xAF, 0x8B, 0xAE, 0xC3, 0xD8, 0xA0, 0x25, 0x0E, 0x7A, 0x4E,
+ 0x2C, 0x56, 0x6D, 0xAC, 0xF7, 0x4A, 0xFB, 0xCD, 0xB6, 0xEC, 0xCC, 0x0E, 0x23, 0xA6, 0x0B, 0x44,
+ 0x49, 0x23, 0x2F, 0xD2, 0x86, 0x74, 0xE0, 0x31, 0x7F, 0x49, 0x82, 0x77, 0xCE, 0x74, 0x58, 0xF0,
+ 0xC1, 0x03, 0xDF, 0xAC, 0x96, 0x68, 0x1C, 0x55, 0x6C, 0x9F, 0x77, 0x2C, 0x31, 0xCE, 0xDE, 0xE9,
+ 0x5F, 0x5F, 0x23, 0xEA, 0x8C, 0xBC, 0x1E, 0xA7, 0x53, 0x05, 0x36, 0x7D, 0x87, 0x00, 0x41, 0xE5,
+ 0x79, 0x59, 0x2C, 0xD9, 0xFC, 0xB6, 0xCB, 0x2F, 0xE8, 0xD7, 0x1F, 0x6B, 0x7A, 0xB7, 0x6E, 0x56,
+ 0xA9, 0x69, 0x84, 0x89, 0xF9, 0x08, 0xDB, 0x20, 0x02, 0x41, 0xFF, 0x87, 0x1A, 0x4C, 0x91, 0x54,
+ 0x6B, 0x61, 0xBC, 0xAB, 0x71, 0x58, 0xE3, 0x66, 0x44, 0xFF, 0x0E, 0xC0, 0xA7, 0x69, 0xE8, 0x64,
+ 0x3F, 0x9C, 0x1C, 0x70, 0x9F, 0x2D, 0x3E, 0x94, 0x0E, 0x78, 0x3E, 0xEB, 0x37, 0xB0, 0x41, 0xE3,
+ 0xE0, 0xAC, 0x9F, 0x1D, 0x69, 0x8C, 0xF3, 0x4E, 0x1C, 0x48, 0x40, 0xD6, 0x9A, 0x28, 0x1D, 0x0D,
+ 0x29, 0xC5, 0x67, 0x20, 0x0D, 0x14, 0x1E, 0x87, 0xDE, 0xA2, 0xD7, 0xBC, 0xFD, 0x50, 0x9D, 0xD1,
+ 0x83, 0x48, 0x5C, 0x4F, 0x79, 0x24, 0x74, 0x17, 0x2B, 0x35, 0xFD, 0x76, 0x74, 0x7B, 0x70, 0x73,
+ 0x73, 0x73, 0x80, 0xA9, 0xBC, 0x3A, 0xC0, 0x90, 0xF5, 0xB9, 0xB6, 0x3C, 0xD9, 0xA3, 0xD0, 0x96,
+ 0xE4, 0x3F, 0xE6, 0xBB, 0xD3, 0x67, 0x07, 0xBF, 0x8C, 0x14, 0xD1, 0x8D, 0xD7, 0x9D, 0x58, 0x0B,
+ 0xBE, 0xEA, 0x74, 0x00, 0x12, 0x4D, 0x6E, 0x5D, 0xD3, 0x81, 0x15, 0x71, 0xF0, 0x02, 0x49, 0xA1,
+ 0xC7, 0x48, 0x81, 0x0C, 0x0B, 0x7B, 0x42, 0x82, 0xDA, 0xB3, 0x14, 0x9A, 0xFA, 0xA1, 0xAD, 0xAB,
+ 0xB0, 0x00, 0xA5, 0x48, 0x89, 0x1F, 0xF2, 0xF7, 0xB9, 0x04, 0x92, 0xD9, 0x9A, 0xB1, 0xA3, 0x77,
+ 0x6A, 0xF3, 0xE8, 0xCD, 0x3B, 0xFC, 0xBC, 0x79, 0x77, 0x84, 0x2E, 0xF5, 0x2B, 0xFD, 0x1E, 0x71,
+ 0x7B, 0xF4, 0x46, 0xBF, 0xC8, 0x25, 0xD9, 0x66, 0x8B, 0xA3, 0x19, 0x24, 0x62, 0x59, 0xAC, 0x96,
+ 0x52, 0x39, 0x32, 0x89, 0xA0, 0xE3, 0x22, 0xFE, 0x0A, 0x97, 0x74, 0x8A, 0x37, 0x3D, 0x2C, 0x97,
+ 0xF6, 0xBB, 0xD7, 0x2F, 0xBE, 0xD5, 0x23, 0x80, 0x8D, 0x4F, 0x27, 0xF6, 0x92, 0x3C, 0xC4, 0x28,
+ 0xD5, 0x64, 0xA7, 0x26, 0x3A, 0xF7, 0xF8, 0x9B, 0x31, 0xD1, 0xF2, 0x4A, 0xAD, 0x44, 0x29, 0xD5,
+ 0xD6, 0x64, 0xAA, 0x24, 0xDF, 0x52, 0x21, 0x47, 0x14, 0x6F, 0x95, 0x03, 0x69, 0x0D, 0x32, 0x66,
+ 0xA9, 0x50, 0x18, 0x8F, 0x5B, 0x0B, 0x54, 0xEB, 0xEB, 0x1D, 0xF7, 0xB8, 0x19, 0x76, 0x0D, 0x6F,
+ 0x9C, 0x70, 0xAF, 0x24, 0x44, 0x90, 0x21, 0x3D, 0x4C, 0x05, 0x29, 0xA5, 0x5B, 0x74, 0xE1, 0x99,
+ 0x80, 0x43, 0x5F, 0x74, 0x09, 0x27, 0x9E, 0x36, 0x79, 0x85, 0xAF, 0x6E, 0x3A, 0x4A, 0x7C, 0x29,
+ 0x89, 0xBD, 0x6E, 0x2D, 0x65, 0x37, 0xC0, 0xAB, 0xD6, 0x66, 0xA0, 0xCB, 0x70, 0x05, 0x11, 0x9F,
+ 0x12, 0xCC, 0x2B, 0x54, 0x95, 0x5A, 0x62, 0x3B, 0x5F, 0xAA, 0x0B, 0xC6, 0x6A, 0xEF, 0xB1, 0xAD,
+ 0xED, 0x57, 0x89, 0x31, 0xC5, 0x07, 0xB0, 0x0A, 0xE5, 0xB3, 0x61, 0xC1, 0xA7, 0xAE, 0xDC, 0x2B,
+ 0x9A, 0xFF, 0xE0, 0x39, 0x06, 0xF9, 0x20, 0xC4, 0x12, 0x0E, 0xC8, 0xF8, 0x43, 0x92, 0x1A, 0xF6,
+ 0xE5, 0x6D, 0xE0, 0x78, 0x40, 0xBD, 0xC3, 0xEB, 0x13, 0x70, 0x1E, 0xC9, 0xB7, 0x1C, 0x8E, 0x79,
+ 0x8A, 0xC9, 0xB5, 0x77, 0x85, 0xAB, 0x4C, 0x43, 0xF6, 0x6D, 0x37, 0x68, 0xBC, 0xED, 0xF2, 0x6E,
+ 0xDD, 0x3E, 0x41, 0xB3, 0x34, 0x54, 0x1D, 0xFB, 0x5E, 0xD0, 0x6C, 0xC4, 0xA1, 0x65, 0x89, 0x26,
+ 0x50, 0xA7, 0xD9, 0x1D, 0xA9, 0x41, 0x7C, 0x78, 0x8D, 0xD2, 0xB4, 0xF1, 0xB1, 0xFF, 0x5E, 0x09,
+ 0x5C, 0x7C, 0x8D, 0xF4, 0xB1, 0x50, 0xE1, 0x1C, 0x6E, 0xCE, 0x38, 0xDE, 0xBC, 0xAB, 0xD0, 0xA4,
+ 0xB0, 0x8D, 0xBA, 0xEC, 0x1F, 0x9D, 0xB6, 0x92, 0xBD, 0x4E, 0xC8, 0x08, 0x61, 0x10, 0x2E, 0x83,
+ 0x03, 0xEA, 0x67, 0xF1, 0xAE, 0xAC, 0xD0, 0xCD, 0x43, 0x07, 0x53, 0xB3, 0x64, 0x4B, 0x01, 0x90,
+ 0x8A, 0x91, 0xA2, 0xDB, 0x80, 0xC2, 0xE8, 0x34, 0x85, 0xD1, 0x09, 0xD1, 0xA7, 0x48, 0x87, 0x8B,
+ 0x3E, 0x05, 0x61, 0x1E, 0xC2, 0xAF, 0x19, 0x73, 0x9B, 0x7D, 0x39, 0xBB, 0x4E, 0xAD, 0x22, 0xCA,
+ 0x2B, 0xED, 0xAC, 0x43, 0x97, 0x1E, 0xC2, 0xA3, 0xEB, 0x52, 0x07, 0xA5, 0x6B, 0xFB, 0x83, 0x5B,
+ 0x8C, 0x24, 0x81, 0xFE, 0x55, 0x39, 0x9D, 0x5B, 0x9D, 0x98, 0xC9, 0x2B, 0xB2, 0xA6, 0x6B, 0xA0,
+ 0x9A, 0xFE, 0xBC, 0xBC, 0xE2, 0x45, 0xF7, 0x3A, 0x1A, 0xE9, 0xE6, 0xFD, 0xE1, 0x95, 0x94, 0xCB,
+ 0x0A, 0xD3, 0x82, 0x5B, 0xDD, 0xD1, 0x05, 0xC2, 0x33, 0xAF, 0xD2, 0xA9, 0xB1, 0x05, 0x2C, 0xE6,
+ 0xA7, 0x02, 0x11, 0x8B, 0xE1, 0x55, 0xE3, 0x86, 0x74, 0x9F, 0xE6, 0xF4, 0x97, 0xAF, 0x19, 0x8B,
+ 0x30, 0x30, 0x0C, 0x30, 0x78, 0x33, 0xAA, 0xA0, 0x83, 0xE3, 0xD3, 0x94, 0x84, 0x99, 0xE4, 0xD9,
+ 0x21, 0x17, 0x64, 0x27, 0x91, 0xF1, 0x54, 0x99, 0x6F, 0xE5, 0x5B, 0xC2, 0xAD, 0xF5, 0x03, 0x71,
+ 0x0A, 0xF5, 0xAC, 0x43, 0x20, 0x81, 0x2C, 0x8E, 0x51, 0x9F, 0x1F, 0x37, 0x1B, 0x39, 0x43, 0x12,
+ 0xD2, 0x28, 0xB5, 0xB4, 0xDB, 0xD7, 0x9D, 0xF2, 0xF0, 0x3F, 0x62, 0x53, 0x1E, 0x45, 0xA8, 0xAB,
+ 0x39, 0x74, 0xD0, 0xB5, 0x2C, 0xBA, 0xCB, 0x1A, 0xD4, 0x69, 0xC7, 0x09, 0x68, 0xC9, 0xA6, 0xE8,
+ 0x22, 0x28, 0x69, 0xCF, 0xE4, 0x2C, 0x76, 0x2F, 0x4C, 0xBC, 0x24, 0xBB, 0xA9, 0xA1, 0x28, 0x32,
+ 0xA6, 0xAA, 0xD8, 0x9E, 0x4D, 0x8D, 0x53, 0xA6, 0xBE, 0x02, 0xEE, 0xC7, 0xAA, 0x8C, 0x12, 0x6A,
+ 0x7C, 0x71, 0x6F, 0xF4, 0x71, 0xCA, 0x1F, 0xA3, 0xE4, 0x45, 0xFF, 0xA8, 0xA0, 0x91, 0xEC, 0x79,
+ 0xEF, 0x7B, 0x26, 0x74, 0x12, 0xD7, 0x6D, 0xB7, 0x9F, 0x35, 0xBD, 0x8C, 0x86, 0xD3, 0xDD, 0x05,
+ 0x3F, 0x6C, 0x87, 0xB0, 0x66, 0x79, 0x2E, 0x5F, 0x45, 0xE6, 0xB4, 0xEE, 0x44, 0xC4, 0xEB, 0x80,
+ 0xCE, 0x31, 0xE5, 0xA4, 0x42, 0x26, 0xB7, 0x1C, 0x79, 0xA5, 0x39, 0x25, 0xD1, 0x13, 0x2D, 0x6E,
+ 0x0E, 0x66, 0xAC, 0xFA, 0x33, 0xA1, 0x4B, 0xF5, 0x5E, 0x75, 0x0A, 0x8B, 0x65, 0xF5, 0x54, 0x4E,
+ 0x6D, 0x5C, 0x8E, 0xF8, 0xC2, 0x30, 0xC8, 0xA9, 0x6B, 0x7D, 0x2C, 0xE3, 0xDA, 0x30, 0xCD, 0x90,
+ 0xAC, 0x09, 0x88, 0xC9, 0xE4, 0xC1, 0x83, 0x3E, 0x53, 0x2C, 0x22, 0x7C, 0xC8, 0x1E, 0x50, 0xDD,
+ 0x32, 0xEA, 0x9F, 0xD0, 0x33, 0x20, 0xF2, 0xA0, 0x27, 0x60, 0xA2, 0x14, 0xED, 0xFF, 0x41, 0x2E,
+ 0x26, 0xBA, 0x68, 0xA2, 0x64, 0x9A, 0x2D, 0x94, 0xFC, 0x40, 0xD2, 0xD9, 0xB0, 0xD2, 0x6C, 0x74,
+ 0x5E, 0x78, 0x80, 0x04, 0x09, 0x96, 0x4A, 0x60, 0x60, 0xF0, 0xB4, 0x05, 0x3E, 0x4E, 0x32, 0x44,
+ 0x89, 0x9B, 0x44, 0xFD, 0xE3, 0x02, 0x1E, 0x62, 0x1C, 0x93, 0x88, 0x69, 0x5F, 0x3D, 0x42, 0x21,
+ 0x97, 0xCF, 0x8D, 0xF6, 0x8E, 0x72, 0xAB, 0xE0, 0x0F, 0xCB, 0x71, 0x86, 0xB2, 0x70, 0xB9, 0xA8,
+ 0xAB, 0xF3, 0x49, 0x16, 0x9F, 0xCA, 0x04, 0x9C, 0x27, 0x33, 0x5C, 0x66, 0xD3, 0x68, 0x06, 0xCE,
+ 0x82, 0x2E, 0x61, 0x9C, 0x54, 0xE8, 0x37, 0xCD, 0xE7, 0x66, 0x71, 0x14, 0xAE, 0x6E, 0x54, 0x39,
+ 0x3B, 0xB7, 0xA3, 0xFB, 0x2D, 0x46, 0xF7, 0xD9, 0x31, 0x79, 0xFE, 0x1D, 0x6F, 0x30, 0x7A, 0x8B,
+ 0xFB, 0xFE, 0x4D, 0x77, 0x48, 0x4C, 0xAE, 0xC9, 0x64, 0x52, 0x9B, 0x4D, 0x7A, 0xAE, 0x1F, 0xCB,
+ 0x73, 0x43, 0x0B, 0xB2, 0x23, 0x0A, 0x9F, 0x38, 0x9C, 0x9F, 0x83, 0x14, 0x3D, 0x3D, 0xEC, 0x63,
+ 0x4E, 0x30, 0x93, 0xCE, 0x0F, 0x4C, 0x99, 0x83, 0xD7, 0x25, 0x45, 0x8C, 0x54, 0x83, 0x9A, 0x09,
+ 0x92, 0x88, 0xB2, 0xBC, 0xAF, 0x91, 0x6F, 0x6B, 0x18, 0x3F, 0x3F, 0xA7, 0x7D, 0x10, 0xB9, 0xD2,
+ 0x80, 0x52, 0x07, 0xFD, 0x6E, 0xE9, 0x0D, 0x4B, 0xBE, 0xB7, 0xC8, 0x9D, 0xFF, 0x9A, 0x8C, 0xF7,
+ 0x24, 0x0D, 0x1C, 0x50, 0x91, 0x48, 0x05, 0x0D, 0x24, 0x6A, 0xAC, 0xC2, 0x97, 0x4C, 0x2D, 0xA2,
+ 0x68, 0x40, 0xFB, 0xD3, 0x70, 0x84, 0x8E, 0x9C, 0x87, 0x39, 0x8B, 0xD9, 0xCE, 0x9C, 0x09, 0x5D,
+ 0x12, 0x30, 0xEC, 0x30, 0x79, 0x46, 0xC7, 0xD8, 0xE4, 0x15, 0xB8, 0x3F, 0x27, 0x7B, 0xFF, 0xC8,
+ 0xA6, 0x87, 0xD3, 0xE3, 0x88, 0xD8, 0x4A, 0xA9, 0x6D, 0x86, 0xAF, 0x18, 0x0C, 0xFA, 0xFA, 0x68,
+ 0x4B, 0x46, 0x86, 0x59, 0xBA, 0x6C, 0x0E, 0xC0, 0xCC, 0x58, 0x43, 0xC7, 0x47, 0x7A, 0x0D, 0x8A,
+ 0xDF, 0xB8, 0xB1, 0xF2, 0xD3, 0xB4, 0x3C, 0xF3, 0x83, 0x3A, 0x55, 0xEF, 0x81, 0x10, 0x2F, 0x13,
+ 0x8B, 0x00, 0x04, 0xBF, 0x73, 0x23, 0xA0, 0x21, 0xF8, 0x0D, 0xCC, 0x6B, 0x0E, 0x8C, 0xFB, 0xDE,
+ 0x39, 0xFD, 0x50, 0xA7, 0x6C, 0xB1, 0x8D, 0xA4, 0x76, 0xCD, 0x9C, 0x58, 0xA4, 0x68, 0x47, 0x20,
+ 0xEF, 0x35, 0xA3, 0x91, 0x24, 0x6C, 0xC0, 0x31, 0x2F, 0x0D, 0x8E, 0x61, 0x52, 0xE2, 0xF4, 0xD0,
+ 0x91, 0x21, 0xD9, 0x31, 0x99, 0x5E, 0x5F, 0xF5, 0x50, 0x07, 0x07, 0x7A, 0x9A, 0x63, 0x54, 0x8B,
+ 0x00, 0x2B, 0xBD, 0x3F, 0x64, 0xFA, 0x9F, 0x3C, 0xA2, 0x03, 0x47, 0x68, 0xD3, 0x1C, 0x16, 0x81,
+ 0xEE, 0xF2, 0x8D, 0x65, 0xBE, 0x23, 0x92, 0xC2, 0x44, 0x2A, 0xD8, 0x9A, 0x89, 0xC6, 0xE8, 0x97,
+ 0x64, 0x03, 0x79, 0x86, 0x06, 0x70, 0x29, 0xCA, 0xD5, 0xCA, 0xE3, 0xB4, 0xF2, 0xA9, 0xDA, 0x5D,
+ 0x42, 0x0F, 0x74, 0x0F, 0x31, 0x24, 0x20, 0x7D, 0x25, 0x7A, 0x53, 0x6B, 0x61, 0xF1, 0x5B, 0xF4,
+ 0x6D, 0xBD, 0x67, 0x89, 0x4C, 0xFF, 0x22, 0xBF, 0xDA, 0x29, 0x6D, 0xCE, 0xBA, 0x93, 0x4B, 0x5C,
+ 0xE1, 0xD8, 0x52, 0x11, 0x2B, 0xD2, 0xB3, 0x32, 0x5A, 0x62, 0xBA, 0x8C, 0xFC, 0xE5, 0x3A, 0x6B,
+ 0x08, 0x65, 0xA9, 0x60, 0xB2, 0xA6, 0x8F, 0x8B, 0xD9, 0x17, 0x29, 0x5D, 0xF2, 0x61, 0x3A, 0xF5,
+ 0x98, 0x63, 0xB4, 0x3D, 0xFE, 0x7C, 0x3A, 0xDD, 0x6C, 0x3E, 0x9F, 0x7E, 0x41, 0x82, 0x2F, 0x55,
+ 0x71, 0x70, 0xE4, 0x7B, 0xDD, 0x80, 0x64, 0x85, 0x01, 0x78, 0x8C, 0x08, 0x2F, 0x16, 0x0E, 0x85,
+ 0xB2, 0x03, 0xA4, 0xA1, 0xA9, 0x35, 0x5D, 0x6E, 0xD7, 0x46, 0x94, 0xEC, 0x3C, 0xFD, 0x36, 0xEC,
+ 0x93, 0xB5, 0x60, 0x2A, 0xC5, 0xEC, 0x38, 0x8F, 0x8E, 0x1C, 0xDD, 0x62, 0x09, 0x83, 0x51, 0xE3,
+ 0x42, 0x34, 0xB7, 0x09, 0xDE, 0x01, 0xA4, 0xD8, 0x42, 0xF4, 0x57, 0xE3, 0xA8, 0x06, 0xC0, 0xB1,
+ 0xB6, 0xF7, 0x6F, 0x70, 0xF7, 0xDE, 0xE1, 0xC3, 0xFF, 0x4E, 0x67, 0x1F, 0x8D, 0x05, 0xB5, 0xAA,
+ 0xA4, 0xE6, 0x61, 0x31, 0x49, 0xE3, 0x82, 0x8F, 0x57, 0x9A, 0x3C, 0xA5, 0x1C, 0x62, 0x3E, 0xBA,
+ 0xAB, 0x0D, 0xA4, 0x9A, 0xA4, 0x2F, 0x46, 0x19, 0x88, 0xDD, 0x56, 0x67, 0xA5, 0xB4, 0x98, 0x73,
+ 0xE8, 0x26, 0x30, 0x57, 0xD9, 0x2D, 0x47, 0x4E, 0x3D, 0x58, 0x4D, 0xF4, 0x7A, 0x9F, 0x3F, 0x1D,
+ 0x03, 0x73, 0x43, 0xA9, 0x49, 0xC4, 0x56, 0x2F, 0xB6, 0xF1, 0x7B, 0x75, 0xCA, 0x6E, 0x8F, 0xF7,
+ 0xA1, 0x7A, 0x17, 0x68, 0x56, 0x70, 0x8C, 0xD0, 0x1E, 0x82, 0x00, 0xEE, 0xF1, 0x0A, 0xF0, 0xFD,
+ 0xC9, 0xCF, 0xD6, 0xEA, 0xAE, 0xEF, 0xFD, 0xE1, 0xEA, 0x2B, 0x95, 0x34, 0xB5, 0xF0, 0x8D, 0xD1,
+ 0xB6, 0xE4, 0x90, 0xF7, 0xA3, 0x9A, 0x0C, 0x44, 0xEF, 0x9F, 0xF9, 0x0B, 0x2A, 0xC7, 0x12, 0xCF,
+ 0xFE, 0x19, 0xA8, 0x70, 0x5E, 0x80, 0x9C, 0xE6, 0xCC, 0x9F, 0xA6, 0x64, 0x35, 0xCF, 0xFB, 0x74,
+ 0x6D, 0x30, 0x8D, 0xF9, 0x02, 0xE2, 0xE8, 0x33, 0x03, 0x0A, 0x46, 0xB1, 0x94, 0x96, 0xB0, 0x38,
+ 0x0C, 0xEF, 0xA4, 0x73, 0x89, 0x20, 0x3A, 0x92, 0xCE, 0x04, 0xF1, 0x3E, 0x64, 0xA3, 0x00, 0x20,
+ 0x3D, 0xC4, 0x67, 0x7C, 0x61, 0xE3, 0xEB, 0x96, 0x9F, 0x10, 0x77, 0xCA, 0x7D, 0x02, 0xAB, 0x2E,
+ 0xD4, 0xCA, 0xEB, 0x1E, 0xDD, 0x0A, 0x84, 0xD6, 0x04, 0x0E, 0x86, 0xD7, 0x2E, 0x58, 0x15, 0xED,
+ 0xAF, 0xF1, 0x53, 0x6B, 0x90, 0xC0, 0x2A, 0xAF, 0xE6, 0x6B, 0x06, 0x82, 0x9A, 0x7C, 0x72, 0x01,
+ 0x89, 0xF2, 0x63, 0xE2, 0x20, 0x71, 0x45, 0xED, 0x61, 0xC7, 0x94, 0x9E, 0xF7, 0xDA, 0x04, 0x53,
+ 0xA5, 0x0D, 0x12, 0xBC, 0x06, 0x08, 0x4B, 0xBB, 0x36, 0xF8, 0x0D, 0xF0, 0xCA, 0xAE, 0xAA, 0xF3,
+ 0x19, 0x15, 0x2B, 0x17, 0x29, 0xF9, 0x2A, 0xE0, 0x27, 0xB6, 0x3D, 0xA7, 0x32, 0xEA, 0xCC, 0x42,
+ 0x51, 0x4B, 0x0B, 0xE0, 0x01, 0x35, 0x17, 0xCE, 0xA9, 0xFB, 0x1C, 0x33, 0x32, 0x8F, 0x18, 0xCF,
+ 0xB4, 0x40, 0xF2, 0x5D, 0x96, 0xC7, 0x9D, 0xD0, 0xED, 0x84, 0x9F, 0xF8, 0xDD, 0x60, 0x25, 0xC7,
+ 0x35, 0x6D, 0xF9, 0x12, 0x17, 0x08, 0x7A, 0x14, 0xFF, 0x40, 0x77, 0x0B, 0xE9, 0x40, 0x4D, 0x7A,
+ 0x09, 0x5B, 0x42, 0x48, 0xE7, 0xA0, 0xC8, 0x31, 0x6C, 0xFA, 0x94, 0xAE, 0xC6, 0x03, 0xA9, 0x23,
+ 0x85, 0xED, 0x08, 0xFE, 0x8E, 0x14, 0x4D, 0x7D, 0xDA, 0x6D, 0x09, 0xDA, 0x5B, 0xC0, 0x7B, 0x99,
+ 0xA8, 0x72, 0x16, 0xF7, 0xC8, 0x02, 0x1C, 0x09, 0xA7, 0x63, 0xBB, 0xFE, 0x0F, 0x38, 0xF2, 0x2D,
+ 0x25, 0x80, 0x89, 0x1C, 0xA3, 0x20, 0xB2, 0x75, 0xA2, 0x76, 0xD5, 0xA7, 0x13, 0xDF, 0x54, 0xA3,
+ 0x67, 0x5D, 0x3C, 0x51, 0x8F, 0x34, 0x76, 0x03, 0x3E, 0x24, 0xB6, 0x2B, 0x16, 0x5F, 0xA8, 0xC9,
+ 0x19, 0xBB, 0x53, 0x31, 0xB1, 0x0E, 0x52, 0x8D, 0x04, 0x25, 0xB5, 0xBB, 0x32, 0xE3, 0x48, 0x63,
+ 0x72, 0x4E, 0xC7, 0xDF, 0xA9, 0xB0, 0x2C, 0x9A, 0x9E, 0x2A, 0x33, 0x02, 0x8F, 0x56, 0x4E, 0x29,
+ 0xD0, 0x72, 0xB4, 0xD6, 0x8A, 0x6C, 0xDF, 0x29, 0x36, 0x31, 0x5A, 0x30, 0x53, 0xCA, 0xFB, 0x14,
+ 0x98, 0x17, 0x65, 0x94, 0xB9, 0x56, 0x65, 0x85, 0x7D, 0xE4, 0x38, 0x41, 0x24, 0xE8, 0x5C, 0xD1,
+ 0x8D, 0x06, 0x73, 0x75, 0x1B, 0x78, 0x52, 0xFB, 0xA0, 0xE6, 0xB5, 0x5A, 0xA9, 0xD3, 0x45, 0x92,
+ 0xDE, 0x3A, 0x57, 0x6A, 0x9C, 0x7E, 0x8A, 0xF4, 0x7C, 0x81, 0x46, 0xBD, 0xDB, 0x7B, 0x7C, 0xC3,
+ 0xF7, 0x79, 0x46, 0xB6, 0xE1, 0xB9, 0x58, 0xCE, 0xF4, 0xC9, 0x28, 0xCB, 0x94, 0xF2, 0xDB, 0x53,
+ 0x1E, 0x23, 0x1F, 0x94, 0xE8, 0xB9, 0x4E, 0xA9, 0xB9, 0x77, 0xEC, 0xA0, 0xC8, 0xEB, 0x04, 0x69,
+ 0x68, 0x29, 0xEE, 0x1D, 0xB1, 0x4F, 0xE4, 0x08, 0xB7, 0xC7, 0xEC, 0xC1, 0x81, 0xA1, 0xE8, 0x37,
+ 0x9B, 0x1D, 0xF4, 0x7C, 0xCD, 0xAA, 0x7A, 0x5B, 0x73, 0x1C, 0xF3, 0xAD, 0x9B, 0x58, 0x42, 0x7D,
+ 0x55, 0x00, 0x27, 0x61, 0x41, 0x01, 0x41, 0x57, 0x8C, 0x01, 0xE5, 0xA6, 0xFE, 0x9A, 0x51, 0x5C,
+ 0x50, 0x6B, 0x50, 0x47, 0xE6, 0xA0, 0x73, 0xF1, 0x97, 0x03, 0xFD, 0x39, 0x14, 0x8A, 0x14, 0x45,
+ 0x7A, 0xEA, 0x42, 0xDB, 0xF7, 0x12, 0x02, 0x73, 0x8A, 0x1A, 0xB8, 0xD3, 0x3D, 0xC7, 0x95, 0x09,
+ 0xBE, 0x87, 0xD3, 0xB5, 0x52, 0x15, 0xB9, 0x34, 0x36, 0x9C, 0x1E, 0x61, 0x39, 0x39, 0xAF, 0x64,
+ 0xCC, 0xC0, 0x2A, 0x34, 0xB7, 0xB3, 0x54, 0x06, 0x27, 0xA5, 0x8D, 0xEC, 0x11, 0x25, 0x7B, 0x26,
+ 0xAD, 0xB6, 0x6A, 0x28, 0xE9, 0x22, 0x2D, 0x29, 0x19, 0x73, 0xC0, 0xA1, 0x1A, 0x51, 0x14, 0x76,
+ 0xB7, 0x73, 0x4B, 0xDE, 0xD9, 0xE8, 0x62, 0xD4, 0x39, 0x00, 0xBE, 0x6F, 0x66, 0x47, 0x30, 0xE9,
+ 0xD3, 0xAD, 0x99, 0xAD, 0x8A, 0x3B, 0x3D, 0x5F, 0x75, 0xD0, 0xB3, 0x8B, 0xF6, 0xBE, 0xCB, 0xBE,
+ 0x5D, 0x8F, 0xCD, 0x7C, 0xA8, 0x63, 0xEB, 0xBA, 0x8F, 0xB5, 0x47, 0x1B, 0x5F, 0x35, 0x3C, 0x16,
+ 0xEC, 0xB1, 0x65, 0xD2, 0x1E, 0x87, 0xEC, 0x44, 0xFF, 0x14, 0x4B, 0xFD, 0x43, 0x6C, 0xAB, 0x1C,
+ 0x96, 0xEF, 0x79, 0xD6, 0x75, 0xD1, 0xEF, 0x63, 0x63, 0x63, 0xDF, 0x17, 0xE5, 0xDD, 0x34, 0xF9,
+ 0x35, 0x58, 0x3C, 0xBB, 0x3D, 0x06, 0x8B, 0xB6, 0x06, 0x81, 0x3A, 0xF2, 0x78, 0xD3, 0x16, 0x9A,
+ 0xBE, 0x95, 0x9C, 0x24, 0x21, 0xDF, 0xF0, 0xE8, 0x40, 0xDE, 0x43, 0xAD, 0x83, 0xE4, 0xB0, 0xF8,
+ 0x47, 0x3C, 0x4D, 0x5C, 0xD8, 0x4E, 0x5B, 0xCC, 0xB7, 0x3C, 0xEA, 0x45, 0x30, 0x96, 0x22, 0x28,
+ 0x3B, 0x2E, 0x08, 0xA4, 0x7C, 0xA3, 0xF2, 0xCF, 0xA1, 0x8E, 0x45, 0x56, 0x2F, 0x11, 0x8F, 0x8B,
+ 0x6C, 0x24, 0xD5, 0x85, 0xA0, 0x37, 0x66, 0xE3, 0x5A, 0xDA, 0x66, 0xB8, 0x41, 0x34, 0x11, 0xDF,
+ 0x54, 0x81, 0x33, 0xBA, 0xCA, 0x03, 0xEF, 0xEA, 0x63, 0x96, 0x0E, 0xB6, 0x7E, 0x1C, 0x68, 0xA0,
+ 0x27, 0xF7, 0x9B, 0x36, 0x58, 0x43, 0x86, 0xCE, 0xA3, 0x1F, 0xC9, 0x81, 0x81, 0xD1, 0x88, 0xEF,
+ 0x0E, 0x65, 0x85, 0x30, 0x82, 0xB4, 0x30, 0x23, 0xAF, 0xD0, 0xAA, 0x1E, 0xB2, 0x1B, 0x6D, 0x20,
+ 0xA5, 0x3E, 0xF9, 0x24, 0xDB, 0x1B, 0xDB, 0x34, 0xBA, 0xF1, 0xC7, 0x9C, 0x76, 0xDC, 0xFE, 0xBA,
+ 0x42, 0x81, 0x9D, 0x26, 0x8E, 0xB2, 0x80, 0x48, 0x25, 0xE7, 0x97, 0x31, 0x8C, 0x26, 0x96, 0xE0,
+ 0x9B, 0xF6, 0x3A, 0x73, 0x7D, 0x79, 0x91, 0x62, 0x39, 0xC5, 0x53, 0x88, 0x48, 0xB6, 0xCE, 0xF7,
+ 0xA5, 0x58, 0xD9, 0x5E, 0xB7, 0xC5, 0x7A, 0x59, 0xB7, 0xC6, 0xA7, 0xD5, 0x70, 0x08, 0xFB, 0xBD,
+ 0x82, 0x1C, 0xFB, 0x4B, 0x02, 0xA9, 0x8E, 0x67, 0x8D, 0x35, 0xB2, 0x1F, 0x17, 0x7E, 0xD0, 0x45,
+ 0x10, 0xBA, 0x41, 0x90, 0xB6, 0x7B, 0xE2, 0x53, 0xA0, 0x9B, 0x9E, 0xD8, 0xE9, 0xF6, 0xB2, 0xF1,
+ 0x89, 0x4B, 0xD0, 0x18, 0x66, 0xB6, 0xB4, 0x4A, 0x0A, 0x18, 0xF9, 0x5F, 0x77, 0xDD, 0xB5, 0x5C,
+ 0x2E, 0xFD, 0x30, 0xC4, 0x3A, 0xDE, 0x1B, 0x04, 0x9C, 0x53, 0x76, 0xDD, 0x70, 0xFC, 0xE8, 0xD1,
+ 0xE7, 0x78, 0xF8, 0x62, 0xAB, 0x3E, 0xEB, 0xB2, 0x61, 0x27, 0x80, 0x8E, 0x0F, 0x00, 0x95, 0xA6,
+ 0xCD, 0xF6, 0xF7, 0x3F, 0xC3, 0x3D, 0x2D, 0xBA, 0xC1, 0x94, 0x3E, 0x01, 0x47, 0x1B, 0x63, 0x84,
+ 0x64, 0xBD, 0x8D, 0x28, 0xC2, 0x5A, 0xA7, 0x3E, 0x70, 0xC5, 0x0C, 0x4D, 0x50, 0x31, 0x19, 0xAC,
+ 0xBD, 0x56, 0xB9, 0x15, 0x12, 0xBD, 0x0E, 0x90, 0xBB, 0x44, 0xCF, 0xEA, 0x96, 0x37, 0x1B, 0x6A,
+ 0x78, 0xBF, 0x0C, 0xD8, 0x77, 0x86, 0xB0, 0x01, 0xFC, 0xED, 0xB0, 0xE2, 0x2E, 0x79, 0x78, 0xFA,
+ 0x56, 0xC2, 0x21, 0x6F, 0xE3, 0x52, 0x33, 0x12, 0x4B, 0x66, 0xEB, 0x95, 0xFA, 0x9A, 0x49, 0x6F,
+ 0x6D, 0xC1, 0xDA, 0xAB, 0x78, 0xBC, 0xCE, 0xDB, 0xF6, 0xA6, 0x6E, 0x70, 0x45, 0xE3, 0xDA, 0x9A,
+ 0xB0, 0x75, 0x2E, 0x45, 0xFD, 0xC4, 0x86, 0x64, 0x73, 0x5E, 0x02, 0x5E, 0x4F, 0x5C, 0x41, 0x7B,
+ 0x75, 0x22, 0x6F, 0x34, 0x8E, 0x39, 0xBC, 0x3B, 0x2D, 0x76, 0x55, 0x12, 0x15, 0x7C, 0x2A, 0x96,
+ 0x7D, 0x1E, 0x7D, 0x7F, 0x20, 0x2B, 0x05, 0xE6, 0x0B, 0x01, 0x2D, 0xB1, 0xD6, 0xE3, 0xD1, 0xF4,
+ 0x2C, 0x0A, 0x97, 0x96, 0xB4, 0x73, 0x93, 0x66, 0xC0, 0x48, 0x10, 0x15, 0x0B, 0xA8, 0xA1, 0x8E,
+ 0x69, 0x80, 0x7B, 0x90, 0x53, 0x33, 0x9D, 0x9B, 0x67, 0x18, 0x72, 0xB5, 0xAA, 0xF3, 0x25, 0x3F,
+ 0x30, 0xDD, 0xC4, 0x4F, 0x7C, 0xF5, 0xE6, 0x27, 0xB9, 0x70, 0xF3, 0x33, 0xDF, 0x66, 0x99, 0x18,
+ 0x83, 0x48, 0xAE, 0xBA, 0xD0, 0xE1, 0xC6, 0x95, 0x70, 0x18, 0x98, 0x7C, 0x6B, 0x0C, 0xF3, 0x21,
+ 0x15, 0x22, 0x8C, 0x53, 0x87, 0x3E, 0xC9, 0x84, 0x6E, 0x9A, 0x75, 0xE0, 0x40, 0x4B, 0x49, 0x20,
+ 0x84, 0xD8, 0xA4, 0xAB, 0xC6, 0x23, 0xD1, 0x28, 0xE3, 0x37, 0xDD, 0xDC, 0x24, 0x61, 0x8A, 0x46,
+ 0x8B, 0x69, 0xB1, 0x96, 0x8E, 0x30, 0x62, 0xEE, 0x30, 0x72, 0xCD, 0xD5, 0x39, 0xC9, 0x88, 0x76,
+ 0x97, 0x57, 0x12, 0x45, 0x66, 0x77, 0x50, 0x8C, 0xCD, 0x9B, 0x0F, 0xA9, 0x4B, 0xDE, 0xA6, 0x77,
+ 0x2C, 0x00, 0x0B, 0x0B, 0x6E, 0xD1, 0xF3, 0xA8, 0xD8, 0x22, 0x4E, 0xD8, 0x06, 0xC4, 0xCE, 0x6A,
+ 0x4D, 0xE6, 0x10, 0x6E, 0x6E, 0xFD, 0x19, 0x45, 0x9E, 0x7C, 0xB9, 0xB3, 0x84, 0x76, 0x73, 0x3F,
+ 0x73, 0xAB, 0x90, 0xA7, 0xA3, 0x73, 0xEF, 0x2D, 0x26, 0x91, 0xCE, 0x8D, 0xE3, 0x35, 0x00, 0x12,
+ 0x77, 0x31, 0x55, 0x6A, 0xF2, 0x48, 0xC5, 0x78, 0xB7, 0xA6, 0xFE, 0xB9, 0x03, 0xCB, 0x1D, 0x67,
+ 0x36, 0x4A, 0x19, 0xB0, 0xE9, 0xB4, 0xC4, 0x9C, 0x1D, 0x53, 0xF5, 0xB8, 0x2B, 0xB5, 0xE1, 0xAE,
+ 0x6C, 0x9D, 0xE4, 0xC1, 0xEF, 0xA6, 0x46, 0x37, 0x1F, 0x23, 0xA1, 0x0A, 0x7F, 0x43, 0xF8, 0x64,
+ 0x51, 0x7B, 0xA8, 0x89, 0x90, 0x4C, 0x8B, 0xF0, 0x03, 0x21, 0x9D, 0x15, 0xFF, 0xEA, 0x22, 0x69,
+ 0xD4, 0x13, 0xD2, 0xAA, 0x3D, 0x8F, 0x3F, 0xBC, 0x33, 0xBD, 0x38, 0xBB, 0x1A, 0x4D, 0xBF, 0x3D,
+ 0x40, 0xCE, 0x98, 0xB8, 0x57, 0x27, 0x91, 0x50, 0x17, 0xC2, 0x72, 0x6A, 0x75, 0x43, 0x05, 0x13,
+ 0x9D, 0x4C, 0x32, 0xDE, 0x4F, 0xA1, 0xA4, 0xDC, 0x56, 0x0C, 0xE9, 0x26, 0x6C, 0xE6, 0xD1, 0xC9,
+ 0xB2, 0x14, 0x9C, 0xDF, 0x82, 0x0B, 0xA4, 0x6F, 0x79, 0xC8, 0xF2, 0x44, 0xD3, 0xA5, 0x86, 0x93,
+ 0xCA, 0x3C, 0x7C, 0xA6, 0x0D, 0x31, 0x99, 0x03, 0x24, 0x3D, 0xEC, 0xC3, 0x71, 0x9D, 0x58, 0x73,
+ 0x2C, 0xC4, 0x5A, 0x95, 0x5E, 0x1A, 0xB6, 0x20, 0x69, 0xEF, 0xC1, 0xD7, 0x14, 0x75, 0x3D, 0x7A,
+ 0xAC, 0xCB, 0xFE, 0x3A, 0x4A, 0xB4, 0x1D, 0x55, 0x50, 0x99, 0xA3, 0xEB, 0x6B, 0x6D, 0xAB, 0x3B,
+ 0x11, 0xF1, 0xA7, 0xA6, 0xC0, 0x13, 0xFD, 0xAE, 0xDA, 0xE6, 0x0C, 0x69, 0x40, 0xEC, 0x28, 0x89,
+ 0x96, 0x23, 0xDA, 0x54, 0x7B, 0x72, 0xBD, 0x2B, 0x43, 0x14, 0x67, 0x14, 0x44, 0x81, 0x67, 0x35,
+ 0x5E, 0x22, 0x92, 0x2F, 0xF6, 0xD0, 0x90, 0xBE, 0x76, 0x7E, 0x31, 0xFD, 0x82, 0x0F, 0x40, 0xFD,
+ 0x4A, 0x13, 0xF2, 0x94, 0xC9, 0xEF, 0xC0, 0xCD, 0x4F, 0x43, 0x84, 0xE1, 0x08, 0x5C, 0x93, 0x0F,
+ 0xB9, 0xD8, 0x69, 0x9F, 0xBE, 0xED, 0xD4, 0x8F, 0x1D, 0x69, 0xF2, 0x7D, 0x47, 0x0A, 0x49, 0x59,
+ 0xF2, 0x66, 0x16, 0xCF, 0xB2, 0x07, 0x30, 0x75, 0xD8, 0xBC, 0x99, 0xB1, 0xCA, 0xA9, 0x0F, 0xB7,
+ 0x74, 0x89, 0xBA, 0x4E, 0xA3, 0x33, 0x91, 0x05, 0x47, 0x8A, 0x13, 0x8C, 0x68, 0x78, 0xA8, 0x5C,
+ 0xFB, 0x63, 0xA7, 0xF5, 0xD3, 0x37, 0x1B, 0x6B, 0xE1, 0x01, 0xFE, 0xBF, 0xC7, 0xFE, 0xF7, 0xC8,
+ 0x2F, 0xA3, 0x6D, 0x53, 0x8C, 0x02, 0x11, 0x75, 0xB4, 0xC7, 0xBD, 0x45, 0xEA, 0x1E, 0xF6, 0x62,
+ 0xB6, 0xAF, 0xE3, 0x5F, 0x70, 0x49, 0x00, 0xCE, 0x77, 0x46, 0xD7, 0x8C, 0xE6, 0x1F, 0x42, 0x08,
+ 0xFC, 0x8D, 0x46, 0xDC, 0x60, 0x1C, 0x2E, 0x9D, 0xD4, 0xA6, 0xF8, 0x4F, 0xA5, 0x36, 0xB6, 0x4F,
+ 0x23, 0x5B, 0x89, 0xE8, 0x57, 0x1B, 0x5A, 0x93, 0x0C, 0x86, 0xC7, 0xC6, 0xAB, 0x19, 0xAA, 0xF2,
+ 0x18, 0x3D, 0x1A, 0xA4, 0x07, 0xD3, 0x0A, 0x3A, 0xB5, 0x97, 0x92, 0xCC, 0x7A, 0x09, 0x38, 0xAB,
+ 0x7A, 0x29, 0x2A, 0x9F, 0x51, 0x50, 0xCF, 0x8C, 0xFE, 0x58, 0xD1, 0xCC, 0x77, 0x5A, 0x34, 0x33,
+ 0x69, 0x92, 0xB4, 0x37, 0x4F, 0x3C, 0x3F, 0x4E, 0x02, 0xA4, 0xDF, 0x3D, 0xA1, 0x8D, 0x94, 0x84,
+ 0x96, 0x16, 0xD5, 0x56, 0x01, 0x7F, 0x4E, 0x36, 0x1F, 0xAF, 0x4E, 0xC8, 0x37, 0x34, 0x4C, 0x4D,
+ 0xA7, 0xF7, 0xDB, 0x80, 0x03, 0x74, 0x93, 0xB7, 0x7B, 0x20, 0x84, 0xF7, 0x08, 0x8A, 0x68, 0xC6,
+ 0x54, 0x8D, 0xAF, 0xDF, 0xAA, 0x70, 0x36, 0x32, 0x61, 0x59, 0x96, 0xD9, 0x13, 0xF2, 0x60, 0x8C,
+ 0x3F, 0x41, 0xCB, 0x75, 0x66, 0x4D, 0x62, 0xB7, 0xAA, 0x1A, 0x71, 0x42, 0xEB, 0x42, 0xC3, 0x41,
+ 0xB3, 0xE1, 0x49, 0x72, 0xE8, 0x3C, 0x91, 0xC4, 0xF8, 0x7A, 0x6E, 0xAE, 0x54, 0x05, 0x7E, 0xF0,
+ 0xF1, 0xFD, 0xF9, 0xEE, 0xC2, 0x77, 0xEC, 0x0F, 0xD6, 0x2A, 0x43, 0x45, 0xF6, 0x20, 0x4B, 0xD6,
+ 0xDC, 0xB4, 0x87, 0x68, 0xD8, 0x1C, 0x5B, 0xD4, 0xC5, 0xBF, 0x30, 0x78, 0x08, 0x8F, 0x1F, 0x44,
+ 0x82, 0xFB, 0xF5, 0x29, 0x7B, 0x14, 0xE1, 0xB1, 0x42, 0x10, 0xFD, 0x96, 0x94, 0xF0, 0x4A, 0xE2,
+ 0x7D, 0x50, 0x02, 0x43, 0xD2, 0x48, 0xC1, 0x98, 0xA0, 0x8E, 0xEE, 0x09, 0x80, 0x3D, 0xBA, 0x2A,
+ 0x51, 0x5E, 0x16, 0x3D, 0x26, 0x60, 0xFB, 0xF5, 0xE3, 0x23, 0xFE, 0x09, 0x5E, 0x22, 0x45, 0x91,
+ 0xD1, 0xDF, 0x76, 0xDE, 0x5D, 0xC1, 0xCA, 0x02, 0x45, 0x3D, 0x86, 0x9B, 0x18, 0xBF, 0x86, 0x0F,
+ 0x95, 0xFB, 0x66, 0xF3, 0x45, 0x1A, 0x8F, 0xDA, 0xBC, 0xC7, 0xCC, 0xB2, 0x60, 0x24, 0x4D, 0xC6,
+ 0x4E, 0x63, 0x5F, 0x39, 0x23, 0x83, 0xC6, 0xF8, 0x93, 0xBF, 0x33, 0xE9, 0x8B, 0xBA, 0xDF, 0x81,
+ 0xC3, 0x80, 0x64, 0x2D, 0xDB, 0x7E, 0x6A, 0x35, 0xE3, 0x45, 0xF5, 0x6B, 0x04, 0xD1, 0x81, 0x7B,
+ 0x86, 0xDE, 0x68, 0x29, 0xC8, 0x13, 0x2D, 0x50, 0x59, 0x5C, 0x66, 0xDF, 0x6A, 0xE5, 0x10, 0x5C,
+ 0x2B, 0x67, 0xF3, 0xAE, 0xD7, 0x43, 0x49, 0xAC, 0xD0, 0x45, 0x8A, 0x52, 0xB7, 0xDA, 0xF7, 0x6F,
+ 0xA7, 0x6A, 0x5E, 0xD9, 0xDA, 0xC6, 0xD4, 0x7B, 0x0D, 0xDA, 0xC0, 0x21, 0x63, 0x28, 0x8F, 0x16,
+ 0xCD, 0x05, 0xCA, 0x2E, 0x54, 0xE9, 0x5F, 0xC7, 0xC0, 0x4F, 0xB5, 0xB8, 0x47, 0xD8, 0x05, 0x4C,
+ 0x31, 0xDD, 0x87, 0xA0, 0x08, 0xD3, 0x91, 0x10, 0xC4, 0x77, 0x83, 0x60, 0x6E, 0xA1, 0x60, 0xEA,
+ 0xEB, 0x98, 0x73, 0xE4, 0x3A, 0x48, 0xB8, 0xE7, 0x74, 0xF1, 0x2E, 0x32, 0xF3, 0x36, 0x55, 0xF4,
+ 0xCE, 0xEA, 0xD5, 0x71, 0xC0, 0x3C, 0x4A, 0xB1, 0x36, 0x63, 0x6A, 0xE2, 0xF8, 0xC8, 0xE8, 0xE5,
+ 0x8B, 0xD7, 0x38, 0x36, 0xD5, 0xF4, 0x71, 0xEE, 0xBE, 0x6F, 0xC8, 0x78, 0x29, 0x37, 0x9B, 0x3E,
+ 0xEF, 0x85, 0x94, 0xB0, 0x2C, 0x47, 0x36, 0xE9, 0x39, 0x47, 0x2E, 0x82, 0x5D, 0xA8, 0xF2, 0x43,
+ 0x2A, 0x1D, 0x37, 0x33, 0x3A, 0x32, 0x11, 0x66, 0xFB, 0xD7, 0x91, 0x65, 0x1F, 0x78, 0x10, 0x48,
+ 0x0B, 0xC2, 0x36, 0xC9, 0xB4, 0x0B, 0xE9, 0xE4, 0x32, 0x5B, 0x17, 0x0B, 0x17, 0x9E, 0xBD, 0xF9,
+ 0xE0, 0x82, 0x5C, 0xF9, 0xAE, 0x20, 0x81, 0x53, 0xE6, 0x45, 0x40, 0xCF, 0x62, 0xAA, 0x0B, 0x1C,
+ 0x78, 0xE6, 0x8E, 0xDC, 0xBB, 0xDD, 0x5A, 0x37, 0x8A, 0xDE, 0xEA, 0x04, 0x5C, 0x3F, 0xE0, 0xC8,
+ 0xD8, 0xFA, 0xF8, 0x1C, 0xBB, 0xC9, 0x67, 0x26, 0x56, 0xD2, 0xD6, 0x5C, 0x6D, 0xD1, 0x85, 0xDC,
+ 0x82, 0xD9, 0xCA, 0xE5, 0x05, 0x3F, 0xA6, 0x1F, 0xF5, 0x81, 0x6F, 0x3D, 0x7A, 0xDB, 0x48, 0xF1,
+ 0x11, 0x1B, 0x02, 0x12, 0x09, 0x75, 0x4E, 0x1A, 0x4E, 0x51, 0x4B, 0x1B, 0xE6, 0x8C, 0xA3, 0xFA,
+ 0xBB, 0xE8, 0x82, 0x87, 0xA6, 0x74, 0x16, 0x35, 0xC5, 0x0A, 0x05, 0xDE, 0xB3, 0xDF, 0xCA, 0xEC,
+ 0x4C, 0xC6, 0x10, 0x03, 0x84, 0x5D, 0xD3, 0xCC, 0x2C, 0x55, 0x6B, 0x97, 0x40, 0x31, 0xFB, 0x90,
+ 0x12, 0x07, 0x41, 0x1A, 0x57, 0x58, 0x73, 0x18, 0xEF, 0x14, 0x4B, 0x7E, 0x49, 0xB4, 0x93, 0x83,
+ 0x7A, 0xB2, 0x76, 0x47, 0x9E, 0x8E, 0x4A, 0x05, 0x68, 0xCB, 0xC9, 0x33, 0xD1, 0x99, 0x1D, 0x44,
+ 0x8C, 0x75, 0x44, 0x1F, 0xAA, 0x04, 0xD1, 0x4E, 0x4D, 0x27, 0x69, 0x18, 0x1E, 0xB8, 0xA6, 0xF0,
+ 0xC0, 0xAA, 0xF4, 0x93, 0xD6, 0x94, 0xA4, 0x81, 0x97, 0xA5, 0x2E, 0x9D, 0xB1, 0x66, 0xAD, 0x7C,
+ 0x5F, 0xC0, 0x04, 0xE0, 0xC6, 0x0B, 0x73, 0x47, 0x5D, 0xA0, 0xEC, 0x39, 0xFD, 0xEA, 0xB7, 0x83,
+ 0x96, 0xFE, 0x4E, 0x72, 0x5B, 0x84, 0x7B, 0xA7, 0x32, 0xFC, 0x20, 0xEF, 0x28, 0x45, 0x3F, 0x93,
+ 0x32, 0x21, 0x97, 0x39, 0x84, 0xDB, 0x48, 0xAB, 0x15, 0xCC, 0x1B, 0x7E, 0x31, 0xFD, 0x9E, 0x27,
+ 0xE9, 0x19, 0x4F, 0xCF, 0x79, 0xB2, 0xED, 0x71, 0xFB, 0xEA, 0xDE, 0x5A, 0x6A, 0x83, 0xE7, 0x5D,
+ 0x7E, 0x55, 0xEC, 0x61, 0xA3, 0xFD, 0x81, 0xA4, 0xE3, 0xCC, 0x24, 0x03, 0x2C, 0x87, 0x02, 0x2B,
+ 0xCE, 0x49, 0x01, 0xDB, 0x1A, 0x58, 0x17, 0x31, 0x7D, 0xE7, 0x0F, 0xCD, 0xAC, 0xD9, 0xC5, 0x5C,
+ 0x99, 0x91, 0xB7, 0x81, 0x5D, 0x61, 0x1A, 0x55, 0x85, 0xBC, 0x80, 0xC7, 0xE8, 0x9B, 0x65, 0xAA,
+ 0x3B, 0x72, 0x80, 0x5C, 0xF0, 0x6C, 0x92, 0x5D, 0xFC, 0x45, 0xF1, 0x17, 0x3D, 0x28, 0x45, 0x33,
+ 0x87, 0x0C, 0xFA, 0x91, 0x9C, 0xEF, 0x75, 0x0E, 0x18, 0x74, 0x5C, 0x69, 0xAA, 0x8B, 0x4C, 0xB7,
+ 0xC6, 0x37, 0x81, 0xB2, 0x41, 0xE6, 0xC3, 0xF0, 0x43, 0xF2, 0x21, 0xD6, 0x3B, 0x89, 0xFF, 0x75,
+ 0x80, 0x8D, 0xB0, 0x31, 0x22, 0x8F, 0x2C, 0x30, 0x0A, 0xDC, 0x36, 0xFE, 0x1E, 0x49, 0xBA, 0xDD,
+ 0xDF, 0xAA, 0x65, 0xB2, 0x1D, 0x37, 0xAE, 0xA7, 0x79, 0x74, 0x02, 0x14, 0xA6, 0x4B, 0xB2, 0x5F,
+ 0x32, 0x53, 0x8E, 0xEE, 0x02, 0x4B, 0xC9, 0x94, 0xE3, 0xC1, 0xF0, 0x4B, 0x69, 0xE7, 0x91, 0x4E,
+ 0x3A, 0x9F, 0xC3, 0x9B, 0x0D, 0x3D, 0x0F, 0xCA, 0x02, 0x90, 0xDD, 0x66, 0x1D, 0xDB, 0xD7, 0x49,
+ 0x11, 0xFA, 0x96, 0x22, 0x89, 0x39, 0x5D, 0x91, 0x47, 0x82, 0x1C, 0xC4, 0x25, 0xA3, 0x00, 0x3B,
+ 0x7A, 0xDE, 0x5F, 0x83, 0x60, 0xED, 0x08, 0xA9, 0x66, 0xC3, 0xB4, 0xE3, 0x96, 0xC0, 0xAB, 0x34,
+ 0x28, 0xF4, 0x07, 0x24, 0xDA, 0x52, 0x56, 0x62, 0xC2, 0xD3, 0xAD, 0x37, 0x51, 0xC9, 0x7F, 0x65,
+ 0x8E, 0x4D, 0xD8, 0x4B, 0x34, 0xCD, 0xE5, 0xF5, 0x82, 0xC8, 0x46, 0xD2, 0x3D, 0xF4, 0x8B, 0x52,
+ 0x07, 0x5C, 0x96, 0x9C, 0x80, 0xF9, 0xF3, 0xF9, 0x2F, 0x99, 0xA9, 0x70, 0x8E, 0xAB, 0xED, 0xA6,
+ 0xFE, 0x93, 0x66, 0xD5, 0xAF, 0x68, 0x79, 0xD6, 0xE0, 0xAB, 0x14, 0xDB, 0xC0, 0x01, 0xB0, 0x0B,
+ 0x39, 0x97, 0x46, 0x1E, 0x2C, 0x47, 0xCA, 0x86, 0xA8, 0x43, 0xBA, 0x83, 0x7E, 0xDF, 0x22, 0xB2,
+ 0x53, 0x86, 0xE7, 0x97, 0x05, 0x45, 0x88, 0xAA, 0x64, 0x57, 0x2E, 0xA1, 0x67, 0xC2, 0x4F, 0x8A,
+ 0xFB, 0xC9, 0x1A, 0x8A, 0x28, 0x3B, 0x03, 0x99, 0x9F, 0xFE, 0xAA, 0x0F, 0x03, 0x48, 0xF4, 0xF7,
+ 0x68, 0xE2, 0x47, 0xB2, 0x76, 0xD8, 0x80, 0x64, 0xBD, 0x05, 0xFA, 0x3E, 0xC1, 0xB3, 0x8D, 0xB4,
+ 0x17, 0xD7, 0x78, 0xF1, 0x3E, 0x30, 0xAD, 0x54, 0x3D, 0xAB, 0xD2, 0xC6, 0xDF, 0xDA, 0x89, 0x78,
+ 0x9E, 0xDB, 0x12, 0xCE, 0x51, 0x7D, 0x6C, 0xD6, 0xF7, 0xF3, 0x7C, 0x1D, 0xF1, 0xA9, 0xD1, 0x93,
+ 0x70, 0x55, 0x41, 0xD0, 0xD6, 0x6A, 0xA1, 0xA3, 0xD7, 0x5E, 0x63, 0xDF, 0xAE, 0x5E, 0xCA, 0x02,
+ 0xED, 0x0E, 0x5C, 0x8B, 0x53, 0xF4, 0xCF, 0x05, 0x37, 0xA2, 0x5E, 0x16, 0xD6, 0xA1, 0x28, 0x83,
+ 0xBD, 0x3B, 0x58, 0xD0, 0x28, 0x47, 0x96, 0x25, 0x9A, 0xC3, 0x5B, 0x48, 0x09, 0x62, 0x6E, 0x83,
+ 0x2E, 0x33, 0x80, 0xA7, 0xD1, 0x4D, 0x3F, 0xAE, 0x6C, 0x8E, 0x13, 0xE4, 0xCE, 0x54, 0xB2, 0x81,
+ 0x68, 0x99, 0x8C, 0x8E, 0x26, 0xB9, 0xE1, 0x86, 0xA4, 0xAD, 0x22, 0xF7, 0x36, 0x60, 0x2D, 0x71,
+ 0xB2, 0x57, 0xBF, 0x51, 0xB5, 0xF8, 0x65, 0xAE, 0x17, 0xA3, 0xC6, 0x6D, 0xFD, 0x79, 0xA3, 0x75,
+ 0x73, 0xCE, 0xAB, 0x3D, 0x2A, 0x9A, 0x6E, 0xF1, 0xC8, 0x81, 0x30, 0x47, 0x4B, 0x8F, 0xF9, 0xB7,
+ 0x9B, 0x99, 0xA8, 0x76, 0xA9, 0xEC, 0x56, 0x90, 0x7F, 0x9F, 0x02, 0x38, 0x52, 0x86, 0x81, 0x87,
+ 0x1A, 0xAA, 0xED, 0x19, 0x2D, 0x5F, 0x91, 0xE0, 0xC6, 0x67, 0xBF, 0x13, 0x10, 0x62, 0x91, 0x55,
+ 0x1F, 0x6B, 0xCD, 0xA3, 0x33, 0x46, 0x9D, 0x54, 0xAC, 0x07, 0x83, 0x33, 0x02, 0xC1, 0x7E, 0x79,
+ 0x65, 0xC3, 0xC4, 0x15, 0x8C, 0x0C, 0xE7, 0x91, 0x06, 0x3A, 0xAA, 0xAF, 0x9A, 0xE0, 0xCD, 0x14,
+ 0xD0, 0x9B, 0xD3, 0x14, 0xF0, 0xDF, 0xFC, 0xCE, 0x41, 0xB8, 0x3B, 0x00, 0x9F, 0x99, 0x8D, 0x4E,
+ 0xDB, 0x2E, 0x0D, 0x22, 0xD5, 0x22, 0x01, 0x74, 0x81, 0xAA, 0x66, 0x85, 0x94, 0x17, 0x09, 0x9D,
+ 0x03, 0x57, 0x4F, 0xE3, 0x53, 0x39, 0x69, 0xB1, 0xEA, 0x89, 0x9A, 0x7D, 0xA1, 0x75, 0x20, 0xCE,
+ 0x56, 0x4E, 0xED, 0xAB, 0x6F, 0xDE, 0x33, 0xBE, 0xCF, 0x1D, 0x66, 0xD3, 0x71, 0x11, 0x86, 0xE6,
+ 0xE2, 0xE4, 0x9E, 0xA0, 0x47, 0x0B, 0x0E, 0x2A, 0x16, 0x4C, 0xC4, 0x50, 0x16, 0x0B, 0x91, 0x06,
+ 0x55, 0x7A, 0x15, 0xCE, 0xCF, 0x6D, 0x0D, 0x94, 0x37, 0x3E, 0x1D, 0x3E, 0x12, 0x0A, 0xC2, 0xC5,
+ 0x6D, 0xE0, 0x3E, 0x76, 0xD4, 0xB2, 0x75, 0xE8, 0x58, 0x1A, 0x78, 0x9B, 0xF2, 0xBA, 0x8F, 0x1E,
+ 0x42, 0xA5, 0x39, 0xB5, 0x09, 0x68, 0x7A, 0xB3, 0xA1, 0x34, 0xFE, 0x84, 0x4B, 0x12, 0x1D, 0xDC,
+ 0xF3, 0x05, 0x57, 0x35, 0x04, 0x1A, 0x68, 0xB8, 0x68, 0x30, 0x8B, 0xFA, 0x0D, 0x5B, 0x06, 0x57,
+ 0x2C, 0x12, 0x20, 0xF9, 0xE8, 0x27, 0x22, 0x33, 0xF5, 0x3D, 0xB6, 0x52, 0xD7, 0x7F, 0x41, 0xBE,
+ 0x19, 0x9B, 0xF5, 0x3D, 0x10, 0xFD, 0xB0, 0x8A, 0xDC, 0xD3, 0x50, 0xB7, 0x07, 0x68, 0x3A, 0xFB,
+ 0xFB, 0xDE, 0xF2, 0xDD, 0x4A, 0x3F, 0x70, 0xA3, 0x64, 0xE9, 0xAD, 0x9F, 0xD6, 0xD7, 0xFC, 0xCB,
+ 0x17, 0x39, 0xFD, 0x44, 0x43, 0x94, 0x27, 0xB4, 0xE5, 0x46, 0xB4, 0xE7, 0x86, 0xB3, 0xA7, 0xB9,
+ 0xD3, 0x7B, 0xDA, 0xEA, 0x77, 0x4F, 0x5B, 0x0B, 0xEF, 0xC1, 0xE6, 0x97, 0xDB, 0xC5, 0xEF, 0x35,
+ 0x86, 0xD0, 0xD2, 0x03, 0x9A, 0x17, 0xBB, 0x0A, 0xCC, 0xD7, 0x3A, 0xF2, 0x94, 0x78, 0x06, 0x28,
+ 0xF3, 0xBC, 0x22, 0x64, 0x39, 0x3A, 0x37, 0xD3, 0xC7, 0xE3, 0x33, 0x8E, 0x62, 0x95, 0x5E, 0x6F,
+ 0x94, 0xD6, 0x93, 0x6E, 0x55, 0x22, 0x2A, 0x6B, 0x5C, 0xF8, 0x3D, 0x59, 0x00, 0xCE, 0xDF, 0xB4,
+ 0x6F, 0xD6, 0xCF, 0x9E, 0x3E, 0x7B, 0xF6, 0xE6, 0xF6, 0xCB, 0xE9, 0x62, 0xB2, 0xE9, 0xBD, 0x7F,
+ 0xC6, 0xBE, 0xC7, 0xC0, 0xA6, 0xBC, 0xFD, 0x30, 0x6A, 0xCA, 0x2B, 0x6C, 0x52, 0x61, 0x04, 0xF4,
+ 0x2F, 0xFC, 0x6C, 0x0E, 0xAC, 0x8D, 0x52, 0x2A, 0xA2, 0xDF, 0xBD, 0xC0, 0x96, 0x59, 0xAB, 0x89,
+ 0x69, 0x77, 0x0F, 0x7C, 0x94, 0xD0, 0xF5, 0x7B, 0x24, 0x9A, 0xB8, 0xDC, 0xDE, 0x8C, 0x3D, 0x7D,
+ 0x63, 0x6C, 0x2F, 0xFA, 0x2D, 0xB0, 0x1B, 0x43, 0x66, 0x0F, 0x66, 0x85, 0xFF, 0x03, 0x26, 0x92,
+ 0x30, 0x0D, 0x55, 0x49, 0xD4, 0xFA, 0x65, 0xBD, 0x5A, 0xBE, 0x22, 0x71, 0x41, 0xE8, 0x9E, 0x87,
+ 0xFD, 0x15, 0x23, 0xF5, 0xCF, 0x79, 0x09, 0x0F, 0x77, 0xA9, 0xBC, 0x91, 0x40, 0x1D, 0x95, 0x8C,
+ 0x45, 0x68, 0x16, 0xD8, 0x87, 0x1A, 0x16, 0x09, 0xE9, 0x92, 0x64, 0x9E, 0x8D, 0xD1, 0x6B, 0x6B,
+ 0xB6, 0x9A, 0x7D, 0xC9, 0x75, 0x9F, 0x49, 0x47, 0xD9, 0x15, 0xBF, 0x6A, 0x7F, 0x26, 0xD9, 0x2D,
+ 0x5E, 0xCE, 0x50, 0x68, 0x45, 0xAA, 0x11, 0xD9, 0xF7, 0x78, 0x63, 0x66, 0xF5, 0x0D, 0xD7, 0xBF,
+ 0xB1, 0xDE, 0xF6, 0xB9, 0xCA, 0xB7, 0xF8, 0xC6, 0xA6, 0x3C, 0x1B, 0x09, 0x31, 0xA2, 0x6B, 0xE1,
+ 0x5D, 0x10, 0x7D, 0xEC, 0x5C, 0xAF, 0x65, 0x9D, 0x95, 0xFA, 0xF0, 0x1B, 0x19, 0xD1, 0xA2, 0xA9,
+ 0xFC, 0xDB, 0xB8, 0xF0, 0xE2, 0xD9, 0xA3, 0x2A, 0x7F, 0x22, 0xCA, 0x5D, 0x65, 0xF7, 0x7B, 0x60,
+ 0x8B, 0x8B, 0xC0, 0xFA, 0xE1, 0x7B, 0xD2, 0x6B, 0x47, 0xDD, 0xC8, 0xD4, 0x72, 0x00, 0xC0, 0xCE,
+ 0xC6, 0x41, 0x99, 0xE8, 0xDF, 0xC3, 0xFC, 0x6A, 0x69, 0x9E, 0xE3, 0x48, 0x5B, 0x0E, 0x51, 0xF8,
+ 0x12, 0x35, 0x62, 0xDD, 0x2C, 0xD0, 0xF9, 0x27, 0x72, 0x6E, 0xF0, 0xC3, 0x1F, 0xA9, 0xA4, 0xFA,
+ 0x8A, 0x9E, 0x3F, 0x33, 0x07, 0x19, 0xCD, 0x0E, 0x64, 0x34, 0xE7, 0xD8, 0xB6, 0xA3, 0xAE, 0x9E,
+ 0x50, 0x94, 0xA8, 0x3F, 0x40, 0x20, 0x3D, 0x7D, 0xD5, 0x71, 0xF4, 0x14, 0xD3, 0x96, 0xE4, 0xD8,
+ 0xD7, 0x3F, 0x21, 0xFB, 0xF5, 0x56, 0x8D, 0xBA, 0x47, 0xF7, 0xCB, 0x51, 0x53, 0xAF, 0x13, 0x45,
+ 0xA3, 0xFB, 0xC9, 0x3F, 0x01, 0x0E, 0xC6, 0x32, 0x5D, 0x9D, 0x5D, 0x01, 0x00
+}; //jquery-3.6.0.min.js
+
+//Content of bootstrap.min.js with gzip compression
+static const uint8_t bootstrap_min_js[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xDC, 0x8A, 0x0C, 0x61, 0x04, 0x00, 0x62, 0x6F, 0x6F, 0x74, 0x73, 0x74,
+ 0x72, 0x61, 0x70, 0x2E, 0x6D, 0x69, 0x6E, 0x2E, 0x6A, 0x73, 0x00, 0xDD, 0x1D, 0x6B, 0x57, 0xDB,
+ 0xC8, 0xF5, 0x7B, 0x7F, 0x05, 0xA8, 0x29, 0x91, 0xCA, 0x60, 0xA0, 0xD9, 0xBE, 0x6C, 0xB4, 0x3E,
+ 0x04, 0xC8, 0x86, 0xDD, 0x04, 0x58, 0x1E, 0xC9, 0x66, 0x29, 0xE5, 0x08, 0x7B, 0x30, 0x4A, 0xCC,
+ 0xC8, 0x91, 0xC6, 0x21, 0x04, 0xBB, 0xBF, 0xBD, 0xF7, 0xCE, 0x53, 0x23, 0x8D, 0x6C, 0x93, 0x66,
+ 0x7B, 0x7A, 0xDA, 0xD3, 0x25, 0xD6, 0xBC, 0xE7, 0xCE, 0x9D, 0x3B, 0x77, 0xEE, 0x6B, 0xD6, 0xFF,
+ 0xB8, 0xFC, 0xBB, 0xA5, 0xA5, 0x3F, 0x2E, 0x3D, 0xCF, 0x32, 0x5E, 0xF0, 0x3C, 0x19, 0x2D, 0x7D,
+ 0xFA, 0xAE, 0xF5, 0xAC, 0xB5, 0xB9, 0x14, 0xDE, 0x70, 0x3E, 0x2A, 0xDA, 0xEB, 0xEB, 0x03, 0xCA,
+ 0xAF, 0x74, 0x66, 0xAB, 0x97, 0xDD, 0xAE, 0x47, 0xA2, 0xC2, 0x4E, 0x36, 0xBA, 0xCF, 0xD3, 0xC1,
+ 0x0D, 0x5F, 0xFA, 0xD3, 0xC6, 0xE6, 0xE6, 0x1A, 0xFC, 0xF9, 0xFB, 0xD2, 0xE9, 0x0D, 0x2D, 0x35,
+ 0xB4, 0x3D, 0xE6, 0x37, 0x59, 0x5E, 0x94, 0x5A, 0x4A, 0xF9, 0xCD, 0xF8, 0x4A, 0xB4, 0xC1, 0xEF,
+ 0xAE, 0x8A, 0x75, 0xD3, 0xEC, 0xFA, 0x00, 0xFE, 0xDC, 0x14, 0xEB, 0xBD, 0x8C, 0xF1, 0x3C, 0xBD,
+ 0x1A, 0x73, 0xA8, 0x26, 0x7B, 0x79, 0x95, 0xF6, 0x28, 0x2B, 0x68, 0x7F, 0x69, 0xCC, 0xFA, 0x34,
+ 0x5F, 0x7A, 0xBD, 0x7F, 0xBA, 0x48, 0x73, 0x57, 0xC3, 0xEC, 0x6A, 0xFD, 0x36, 0x29, 0x38, 0xCD,
+ 0xD7, 0x5F, 0xED, 0xEF, 0xEC, 0x1D, 0x9C, 0xEC, 0x89, 0xE6, 0xD6, 0x7F, 0xB7, 0x7C, 0x3D, 0x66,
+ 0x3D, 0x9E, 0x66, 0x2C, 0xE4, 0x84, 0x46, 0x0F, 0x41, 0x76, 0xF5, 0x9E, 0xF6, 0x78, 0x10, 0xC7,
+ 0xFC, 0x7E, 0x44, 0xB3, 0xEB, 0x25, 0xFA, 0x79, 0x94, 0xE5, 0xBC, 0x58, 0x59, 0x09, 0xB0, 0xC3,
+ 0xEB, 0x94, 0xD1, 0x7E, 0xB0, 0xAC, 0x33, 0x6F, 0xB3, 0xFE, 0x78, 0x48, 0xBB, 0x34, 0x54, 0xA5,
+ 0x48, 0x4E, 0x3F, 0x8E, 0xD3, 0x9C, 0x86, 0xC1, 0xFB, 0x8F, 0x63, 0x9A, 0xDF, 0x07, 0x91, 0x4D,
+ 0x19, 0x65, 0xA3, 0x11, 0xCD, 0x5B, 0xEF, 0x8B, 0x20, 0x8A, 0xDA, 0x81, 0xEE, 0xD5, 0x76, 0x24,
+ 0x1B, 0x5F, 0x59, 0x91, 0xFF, 0xB6, 0x92, 0xDB, 0x7E, 0x57, 0xFE, 0x0C, 0xCF, 0x03, 0xD5, 0x7C,
+ 0x40, 0x74, 0xBB, 0xA4, 0xD4, 0xDC, 0x05, 0x8C, 0xBB, 0x4D, 0xC3, 0x90, 0xC7, 0x7C, 0x32, 0x29,
+ 0xE8, 0xF0, 0x3A, 0x6A, 0x99, 0x89, 0xC7, 0x0F, 0x53, 0xC2, 0x5B, 0xEF, 0x7F, 0xC6, 0x4A, 0xF0,
+ 0xE3, 0x48, 0x54, 0x8A, 0xA6, 0x21, 0xBF, 0x49, 0x0B, 0x52, 0x9A, 0xF9, 0x80, 0x8C, 0x61, 0xEE,
+ 0xE3, 0x82, 0x2E, 0x41, 0xB5, 0x14, 0xE6, 0xDF, 0xD1, 0x99, 0x4B, 0xA9, 0x04, 0xCC, 0x75, 0x96,
+ 0x87, 0x9F, 0x92, 0x7C, 0x89, 0xC5, 0x1B, 0x1D, 0xB6, 0x45, 0x5B, 0x43, 0xCA, 0x06, 0xFC, 0xA6,
+ 0xC3, 0x56, 0x57, 0xA3, 0x07, 0x4C, 0x4F, 0x63, 0x7A, 0xCE, 0x2E, 0x3A, 0x69, 0x8B, 0xB2, 0xF1,
+ 0x2D, 0xCD, 0x93, 0xAB, 0x21, 0x8D, 0xCB, 0x1F, 0x93, 0xC9, 0xF2, 0x26, 0x49, 0x5B, 0xB0, 0xA0,
+ 0xD7, 0xE9, 0x60, 0x2C, 0xF3, 0x97, 0x37, 0x48, 0xF0, 0x29, 0x19, 0x8E, 0x69, 0x90, 0x42, 0x3F,
+ 0x2B, 0x2B, 0x61, 0xDA, 0xBA, 0xCB, 0x53, 0xAE, 0xF2, 0x22, 0x72, 0x28, 0xD6, 0xA2, 0x25, 0xC1,
+ 0x70, 0x94, 0x67, 0x30, 0x76, 0x7E, 0x1F, 0x72, 0x68, 0xE6, 0x03, 0xBD, 0x27, 0x69, 0x34, 0x9D,
+ 0x9A, 0x51, 0x16, 0x38, 0x4A, 0xC2, 0xA2, 0x87, 0x9C, 0xF2, 0x71, 0xCE, 0x96, 0x00, 0x90, 0x30,
+ 0xF0, 0xD6, 0x28, 0xCF, 0x78, 0x86, 0x00, 0x86, 0x29, 0x10, 0x26, 0xD2, 0xA0, 0x10, 0xE1, 0xB6,
+ 0xE2, 0x30, 0xCC, 0xEC, 0xE4, 0x78, 0xBC, 0xD9, 0xE1, 0x5B, 0x49, 0x3E, 0x80, 0x51, 0x33, 0x5E,
+ 0xE8, 0x49, 0x72, 0x3D, 0xC9, 0x3C, 0x66, 0xE3, 0xE1, 0x70, 0x39, 0x36, 0x25, 0xCE, 0xF9, 0x45,
+ 0xB7, 0xFC, 0xD1, 0x06, 0x78, 0xD3, 0x58, 0x8E, 0x1B, 0x07, 0x59, 0x84, 0x79, 0xD4, 0xF1, 0x2C,
+ 0xB6, 0x2A, 0x01, 0x3B, 0xE9, 0xF0, 0x8E, 0xE9, 0x99, 0x9D, 0xDC, 0xDF, 0x5E, 0x65, 0xC3, 0x02,
+ 0xE0, 0x40, 0x63, 0x8A, 0x90, 0xEA, 0x25, 0x3C, 0x9C, 0x51, 0x12, 0x1B, 0x6F, 0x5D, 0xA7, 0x43,
+ 0x40, 0xEA, 0xD0, 0xAE, 0xA5, 0x01, 0x81, 0xB7, 0xE6, 0x2E, 0x2D, 0x7A, 0x79, 0x3A, 0xE2, 0x30,
+ 0xE1, 0x9C, 0xF0, 0xA8, 0xB4, 0x40, 0xD3, 0x28, 0x8A, 0x08, 0x6D, 0x01, 0x28, 0xF6, 0x92, 0xDE,
+ 0x4D, 0xA9, 0x41, 0x39, 0x75, 0x00, 0x2E, 0x49, 0x3B, 0x34, 0xCE, 0x48, 0x1A, 0xE7, 0xE7, 0x2C,
+ 0xE6, 0x17, 0x04, 0xD6, 0x0C, 0x00, 0xDD, 0xF5, 0xAF, 0x12, 0x96, 0x7F, 0x10, 0x8B, 0xDB, 0x4E,
+ 0x89, 0xED, 0xA5, 0x0D, 0x6B, 0xAE, 0x70, 0xC0, 0x7C, 0xE3, 0x9A, 0xAB, 0xDF, 0xD3, 0xA8, 0x8D,
+ 0x68, 0x14, 0xA7, 0xD3, 0x68, 0xAA, 0xA6, 0x91, 0x4D, 0x07, 0xF1, 0x60, 0x65, 0x65, 0xD0, 0xBA,
+ 0x49, 0x8A, 0xD2, 0x44, 0xC2, 0x00, 0x3A, 0x4C, 0xC6, 0x43, 0x1E, 0x44, 0xDD, 0x41, 0x4B, 0xFD,
+ 0x6E, 0x03, 0x1E, 0xC7, 0xE3, 0x95, 0x95, 0xF1, 0x8C, 0xC2, 0x63, 0x53, 0x78, 0xDC, 0x11, 0x13,
+ 0x8B, 0x03, 0xD8, 0x29, 0xAC, 0x48, 0x71, 0xB6, 0x94, 0xF5, 0x4B, 0xA8, 0x6F, 0x27, 0x1F, 0x8B,
+ 0x0D, 0xC3, 0xE2, 0xE5, 0xCD, 0x8E, 0x1A, 0xD6, 0x40, 0xEC, 0xA1, 0xA8, 0x05, 0x75, 0xC2, 0xCB,
+ 0xD6, 0xE9, 0xF1, 0xF6, 0xC1, 0xC9, 0xFE, 0xE9, 0xFE, 0xE1, 0xC1, 0xE5, 0xDE, 0xC1, 0xAE, 0xDD,
+ 0x59, 0xD1, 0x03, 0xD4, 0x81, 0x59, 0x91, 0x82, 0xF2, 0xD3, 0xF4, 0x96, 0x66, 0x63, 0x1E, 0x96,
+ 0x33, 0x27, 0x93, 0xCB, 0x16, 0x6C, 0xB8, 0xC1, 0x80, 0xE6, 0xA7, 0x66, 0x10, 0x7B, 0xAC, 0x1F,
+ 0xD2, 0x68, 0x0A, 0xCB, 0x43, 0xB0, 0x8F, 0x29, 0x8E, 0xE0, 0x32, 0x7E, 0x70, 0xFB, 0x68, 0x07,
+ 0x57, 0x85, 0x53, 0x25, 0x20, 0xB0, 0xD2, 0x67, 0xFB, 0xBB, 0x6D, 0xBB, 0x72, 0x12, 0xB1, 0x01,
+ 0x7D, 0xE3, 0x7F, 0xFD, 0x2B, 0xDC, 0xA4, 0x7F, 0xF9, 0xE3, 0xEB, 0x84, 0xDF, 0xB4, 0xA0, 0x52,
+ 0x3F, 0xBB, 0x0D, 0x61, 0xB5, 0xFB, 0x59, 0x0F, 0x31, 0x57, 0xE0, 0xC8, 0xDE, 0x90, 0xE2, 0xCF,
+ 0xE7, 0xF7, 0xFB, 0x7D, 0xA8, 0xD8, 0x89, 0xF4, 0x3C, 0xF9, 0x14, 0xDB, 0x3D, 0xA1, 0x43, 0x58,
+ 0xE3, 0x2C, 0x7F, 0x91, 0x67, 0xB7, 0xAA, 0xA4, 0xE9, 0xC7, 0x02, 0x49, 0x34, 0xB4, 0xCD, 0x25,
+ 0xC5, 0xA6, 0x00, 0xF4, 0x84, 0x27, 0x6B, 0x1C, 0xF6, 0x07, 0x05, 0xC0, 0x77, 0xD2, 0xEB, 0x70,
+ 0x19, 0xC8, 0x40, 0xF0, 0x7B, 0xD8, 0x06, 0x31, 0x95, 0x75, 0x58, 0xAD, 0xCE, 0x4D, 0x4E, 0xAF,
+ 0xA1, 0x30, 0x8D, 0x61, 0xB7, 0x42, 0xD1, 0xE5, 0x38, 0x66, 0x5D, 0x86, 0x30, 0x82, 0x11, 0xB7,
+ 0x83, 0x60, 0xCA, 0xF3, 0x7B, 0x8D, 0xE0, 0x66, 0xF8, 0x82, 0x26, 0xEA, 0x21, 0x02, 0xE8, 0xBA,
+ 0xB4, 0x8D, 0xFB, 0x74, 0x0A, 0xFB, 0xA7, 0x77, 0x53, 0xDA, 0x11, 0x22, 0x51, 0xCC, 0xC7, 0x42,
+ 0x6E, 0x17, 0xF0, 0x10, 0xFF, 0x6D, 0x9A, 0x19, 0x8E, 0x9A, 0x47, 0xAA, 0x81, 0x0D, 0x85, 0x31,
+ 0x03, 0xC8, 0x69, 0xF5, 0x8A, 0x22, 0x2C, 0xA1, 0xCE, 0x5A, 0x5F, 0xB5, 0x14, 0x00, 0xA5, 0x69,
+ 0x28, 0x41, 0x87, 0x09, 0x9E, 0x09, 0x69, 0x3C, 0x4A, 0xF2, 0x82, 0xBE, 0x18, 0x66, 0xB0, 0xC1,
+ 0x81, 0x30, 0x65, 0xE5, 0x6F, 0x66, 0x40, 0x9F, 0x4E, 0x26, 0x59, 0x57, 0xD0, 0x82, 0x62, 0x34,
+ 0x4C, 0x79, 0x08, 0x54, 0x3F, 0x3A, 0xDF, 0xB8, 0x80, 0xD6, 0x59, 0x25, 0x65, 0x93, 0x3E, 0xFB,
+ 0x63, 0xE8, 0xB4, 0xB9, 0xEA, 0xB4, 0x08, 0x27, 0xCE, 0xC6, 0x94, 0x00, 0x64, 0x87, 0xD9, 0x9D,
+ 0x33, 0x3B, 0xBD, 0xC8, 0xAD, 0xEC, 0xFA, 0x1A, 0x50, 0xF4, 0x25, 0xC5, 0xF3, 0x7B, 0x4A, 0x7C,
+ 0x28, 0xE9, 0xD4, 0x13, 0xD3, 0x53, 0xA5, 0x04, 0xAE, 0x16, 0xE3, 0x91, 0x38, 0xA2, 0x1A, 0xAA,
+ 0xE8, 0x9E, 0x90, 0x19, 0x18, 0xD2, 0x84, 0x89, 0x3A, 0x69, 0x51, 0x07, 0xB8, 0x2A, 0x08, 0xBF,
+ 0x60, 0x5E, 0x93, 0x09, 0xF4, 0xC2, 0xB2, 0x3E, 0x3D, 0x05, 0xAA, 0x09, 0xA3, 0x82, 0xBF, 0x3B,
+ 0x37, 0xB4, 0xF7, 0x61, 0x47, 0x10, 0x10, 0x53, 0x4B, 0x92, 0x7E, 0x4B, 0xC5, 0x53, 0xA4, 0x4B,
+ 0x2C, 0x4A, 0xAF, 0x15, 0xF1, 0xB4, 0xA7, 0x40, 0x85, 0x2C, 0xB4, 0x7A, 0xC9, 0x70, 0x18, 0x32,
+ 0x38, 0x49, 0x24, 0x36, 0x66, 0x31, 0x3B, 0x4F, 0x2F, 0x48, 0x1E, 0x53, 0xFC, 0xA7, 0x88, 0xF3,
+ 0x95, 0x95, 0xCB, 0x96, 0x19, 0x25, 0x50, 0xDB, 0x6E, 0x40, 0xE5, 0xEF, 0xA0, 0x1D, 0x26, 0x71,
+ 0x4E, 0x1E, 0xA6, 0x2D, 0x9E, 0x9D, 0x00, 0x1C, 0xD8, 0x40, 0xB6, 0x95, 0x44, 0xAD, 0x5B, 0x81,
+ 0x74, 0xEB, 0xFF, 0x28, 0xC2, 0xF3, 0x64, 0xED, 0xCB, 0xC5, 0x6A, 0xB4, 0x9E, 0x46, 0xE7, 0x9B,
+ 0x17, 0x50, 0xF0, 0x55, 0x76, 0x47, 0xF3, 0x9D, 0xA4, 0xA0, 0x61, 0x24, 0x77, 0x03, 0xA3, 0x77,
+ 0x4B, 0xC7, 0x74, 0xB0, 0xF7, 0x79, 0x14, 0x66, 0x00, 0x4E, 0x5A, 0xF0, 0xB0, 0x88, 0x22, 0x7E,
+ 0x93, 0x67, 0x77, 0x4B, 0x98, 0xB7, 0x97, 0xE7, 0x30, 0x25, 0x0E, 0x55, 0xCF, 0x46, 0x23, 0x5D,
+ 0x75, 0xF5, 0x69, 0x7B, 0xE9, 0x70, 0x24, 0xE8, 0x53, 0xF0, 0x74, 0x35, 0x5D, 0x7D, 0x1A, 0x2C,
+ 0xC1, 0xFC, 0x3E, 0xA5, 0x7D, 0xE0, 0x89, 0x70, 0x8E, 0x98, 0x5A, 0x60, 0x2A, 0xEC, 0x27, 0xE4,
+ 0x5E, 0x00, 0x00, 0xA5, 0x9C, 0x0C, 0x72, 0x5A, 0x4F, 0x23, 0x41, 0x53, 0x92, 0x29, 0x01, 0x92,
+ 0xDD, 0x3F, 0xB9, 0x49, 0xFA, 0xD9, 0xDD, 0x71, 0x96, 0xD5, 0x11, 0xDF, 0xEC, 0x30, 0xFD, 0x43,
+ 0x81, 0xA2, 0x95, 0x70, 0x0E, 0x87, 0x84, 0xAC, 0x19, 0x95, 0x76, 0x17, 0x4E, 0xCB, 0x1C, 0x77,
+ 0x96, 0x4F, 0x12, 0x9B, 0x1C, 0x7B, 0x38, 0x80, 0xC5, 0xD4, 0xE5, 0x39, 0x2C, 0x53, 0xC1, 0x13,
+ 0xD6, 0xC3, 0x12, 0x76, 0x10, 0x5D, 0xDE, 0x86, 0x15, 0x4B, 0x72, 0xE8, 0x06, 0x4B, 0x77, 0x2F,
+ 0x5B, 0xEE, 0x20, 0xC3, 0x72, 0x6E, 0x24, 0x36, 0xBA, 0xDA, 0x95, 0x4E, 0x2F, 0xA1, 0xD9, 0x46,
+ 0xD4, 0xDF, 0x8F, 0x26, 0x12, 0xD3, 0xCE, 0xA0, 0x75, 0xCD, 0x5A, 0xF4, 0x76, 0x3C, 0x4C, 0x38,
+ 0x75, 0xF0, 0x37, 0x66, 0x64, 0xD0, 0xA2, 0x9F, 0x70, 0xC2, 0x05, 0xC0, 0x31, 0x4D, 0x86, 0xE7,
+ 0x55, 0x52, 0x7F, 0x11, 0x3F, 0x5C, 0xC1, 0xF0, 0x10, 0x41, 0xDB, 0x94, 0xF4, 0x01, 0x3D, 0x06,
+ 0xD0, 0x8A, 0xFA, 0xBC, 0x01, 0x4A, 0x3B, 0xA4, 0x55, 0xA8, 0x0E, 0x60, 0x06, 0x92, 0x28, 0x46,
+ 0x80, 0x5C, 0xF2, 0x18, 0x31, 0x30, 0x69, 0xC9, 0x3A, 0x80, 0xB8, 0xEA, 0x57, 0xDE, 0x4A, 0x46,
+ 0xA3, 0xE1, 0xBD, 0xE4, 0xD8, 0x0C, 0xAB, 0x11, 0xC1, 0xB0, 0x25, 0xC6, 0x06, 0x09, 0x94, 0xE1,
+ 0x01, 0x20, 0x2D, 0x1C, 0x08, 0x2D, 0xF5, 0x91, 0xC4, 0x41, 0x2B, 0x58, 0xCD, 0x49, 0x2F, 0xC6,
+ 0xB9, 0x9D, 0x67, 0x17, 0xE4, 0x26, 0x7E, 0xD8, 0x79, 0x75, 0x78, 0xB2, 0xD7, 0x0E, 0x7A, 0xC3,
+ 0xAC, 0xA0, 0xC1, 0x6A, 0x42, 0xC4, 0xF7, 0xAE, 0x4A, 0xE8, 0xCB, 0x94, 0xFD, 0x9D, 0x9F, 0x2E,
+ 0x77, 0xB7, 0x4F, 0xB7, 0x2F, 0xB7, 0x8F, 0xF6, 0x31, 0x27, 0xED, 0x7D, 0x80, 0x8C, 0xD5, 0xA0,
+ 0x25, 0x48, 0x79, 0x32, 0x4A, 0x03, 0xC0, 0x19, 0xD3, 0x67, 0x3F, 0x0E, 0xAE, 0x93, 0x3E, 0x0D,
+ 0xC8, 0x6D, 0x1C, 0x14, 0x37, 0xD9, 0x5D, 0x40, 0x46, 0x71, 0x69, 0xCB, 0x5B, 0x16, 0x12, 0x67,
+ 0x8E, 0x13, 0x68, 0x5D, 0xAA, 0xFD, 0x13, 0xF3, 0xA9, 0xE4, 0xB7, 0x52, 0xBB, 0x39, 0x3B, 0x06,
+ 0x06, 0x62, 0x44, 0x71, 0xFD, 0x7C, 0x71, 0x9A, 0xE8, 0x70, 0x60, 0x94, 0x74, 0x9A, 0x5A, 0x78,
+ 0xBD, 0x53, 0x39, 0x1C, 0x6E, 0x32, 0x43, 0xD1, 0xA8, 0x1D, 0x6C, 0x71, 0xEF, 0x13, 0xE6, 0x51,
+ 0x84, 0xFA, 0xAE, 0x64, 0x01, 0x8E, 0x72, 0xB1, 0xBA, 0xB4, 0x1F, 0x46, 0x93, 0x89, 0xAC, 0x90,
+ 0xD3, 0xDB, 0xEC, 0x13, 0x55, 0x0D, 0xC9, 0x63, 0xB8, 0xD5, 0x4F, 0x8B, 0x91, 0x33, 0x22, 0x20,
+ 0x80, 0x2D, 0x59, 0x70, 0x17, 0xE0, 0x12, 0x3A, 0xE3, 0x22, 0xB9, 0xE8, 0xDB, 0x26, 0x08, 0x9E,
+ 0x11, 0x9A, 0xA9, 0x8E, 0xD2, 0x33, 0xC1, 0xCB, 0x96, 0xFF, 0xAC, 0x85, 0x12, 0x0E, 0xF3, 0x41,
+ 0x61, 0xEA, 0x2C, 0x6E, 0x3C, 0xFF, 0xA0, 0xF0, 0x64, 0x12, 0xEA, 0x43, 0x08, 0xA6, 0x8E, 0x94,
+ 0x05, 0x31, 0xE2, 0x1A, 0x4F, 0x0B, 0xC8, 0x85, 0xD1, 0x54, 0x41, 0xE3, 0x1F, 0xD0, 0xA0, 0x25,
+ 0x81, 0x76, 0xD3, 0x12, 0xD8, 0x62, 0xF6, 0x54, 0x85, 0xFE, 0x13, 0xA4, 0xCE, 0x15, 0xD8, 0xD9,
+ 0xD6, 0xF4, 0x59, 0x2F, 0xD6, 0xAA, 0x83, 0x3B, 0x00, 0x92, 0x14, 0xFC, 0x76, 0x86, 0x09, 0x1C,
+ 0x92, 0xB7, 0x11, 0xC1, 0x34, 0x24, 0xCF, 0x32, 0xA1, 0xAF, 0x28, 0x32, 0x07, 0x90, 0xCC, 0x3B,
+ 0xAE, 0xA1, 0x62, 0x07, 0x6A, 0xCF, 0x61, 0xC5, 0xCA, 0x6C, 0x40, 0xEB, 0xB2, 0x0F, 0x10, 0xC9,
+ 0xB3, 0x7B, 0xD3, 0x02, 0xF0, 0x5A, 0xD3, 0xC8, 0x4B, 0x03, 0x42, 0xC8, 0xA1, 0x43, 0xB8, 0x18,
+ 0xE1, 0xE0, 0x4D, 0x45, 0x07, 0x3F, 0xAA, 0xA9, 0x71, 0xED, 0xA4, 0xEC, 0x53, 0x24, 0x97, 0xA1,
+ 0x01, 0x99, 0x86, 0xE7, 0xAE, 0x06, 0x43, 0x88, 0xC7, 0x61, 0xEB, 0x52, 0x5E, 0xD4, 0xF6, 0x01,
+ 0x23, 0xF3, 0xEB, 0xA4, 0x57, 0x42, 0x38, 0x73, 0xB1, 0x91, 0xC3, 0xA0, 0x0E, 0x87, 0xAE, 0x41,
+ 0xA5, 0x58, 0x52, 0x02, 0x9B, 0x42, 0xEC, 0x57, 0xBC, 0x7B, 0x00, 0xC3, 0x05, 0x9B, 0x04, 0x0F,
+ 0x93, 0x54, 0xE5, 0xEA, 0x3C, 0x82, 0x78, 0xA2, 0xA8, 0x41, 0x1C, 0x23, 0xBB, 0x85, 0x9C, 0xB7,
+ 0x2C, 0x34, 0x95, 0xC3, 0x91, 0x14, 0x68, 0x37, 0x2D, 0x6E, 0xD3, 0xA2, 0x28, 0xAD, 0xA7, 0x19,
+ 0x4C, 0x79, 0xA2, 0xB0, 0x1D, 0x81, 0x32, 0xCB, 0x0D, 0xA5, 0xB6, 0x57, 0x08, 0x63, 0x91, 0xF8,
+ 0xA7, 0x9A, 0x05, 0x3E, 0x21, 0x4C, 0x09, 0xEE, 0x07, 0x72, 0xFE, 0x00, 0xD7, 0xA3, 0x76, 0xF0,
+ 0x66, 0xEF, 0xF8, 0x04, 0x96, 0x4A, 0x30, 0xAF, 0x75, 0x76, 0x21, 0x10, 0x92, 0x87, 0x60, 0x3A,
+ 0x05, 0xA4, 0x4D, 0xA7, 0x21, 0xAE, 0xB3, 0x46, 0x7A, 0x5C, 0x6E, 0x00, 0xA3, 0x4B, 0xB2, 0xC8,
+ 0xD3, 0x73, 0x41, 0xA8, 0xFA, 0x72, 0xC8, 0x9A, 0x50, 0x5D, 0x3C, 0x25, 0xA3, 0xCA, 0x6C, 0x42,
+ 0x04, 0xC9, 0x08, 0x20, 0xA0, 0x08, 0x64, 0x3C, 0xAA, 0x41, 0x5F, 0x67, 0xB5, 0x80, 0xDD, 0x80,
+ 0xE5, 0x1D, 0xE3, 0xE6, 0x8A, 0x47, 0x26, 0x95, 0x65, 0xC8, 0x86, 0x00, 0x85, 0x2C, 0xAD, 0xB7,
+ 0x01, 0x8C, 0x6E, 0xB5, 0x47, 0xEA, 0xED, 0x4A, 0xCA, 0xFD, 0x29, 0x0E, 0xE0, 0x80, 0xE6, 0x70,
+ 0x4A, 0x92, 0x7B, 0x41, 0xBA, 0xF5, 0xD7, 0x9E, 0xA0, 0xDD, 0xF7, 0x64, 0x27, 0x2E, 0x51, 0x5D,
+ 0x72, 0x2A, 0x29, 0xF9, 0xA7, 0x0B, 0x72, 0x02, 0xB3, 0x82, 0xDE, 0x3E, 0x01, 0xD9, 0xBD, 0x82,
+ 0x8A, 0x1C, 0xAA, 0xEC, 0x03, 0x1D, 0x06, 0xB0, 0x80, 0x7C, 0x60, 0x37, 0x56, 0x10, 0xE0, 0xD9,
+ 0x60, 0x30, 0xA4, 0xFF, 0xD4, 0x9D, 0x20, 0x08, 0xEE, 0xDC, 0x3C, 0x9D, 0x55, 0x60, 0xDE, 0x76,
+ 0xFC, 0x34, 0x65, 0xA3, 0x31, 0x6F, 0x33, 0x38, 0x60, 0xCF, 0x81, 0x1A, 0x43, 0xF6, 0x4D, 0xDA,
+ 0xEF, 0x53, 0xA8, 0x19, 0x3D, 0x25, 0x07, 0x30, 0x16, 0xDD, 0xE9, 0x21, 0xFC, 0x16, 0xBD, 0x7E,
+ 0x88, 0x1F, 0x1A, 0x0E, 0x8C, 0xBD, 0xD5, 0x1D, 0xF2, 0xE2, 0x70, 0xE7, 0xEC, 0xE4, 0xF2, 0xF9,
+ 0xAB, 0xB3, 0x63, 0x9B, 0x2F, 0x47, 0x29, 0xF2, 0x57, 0x81, 0x3F, 0x19, 0x8E, 0x73, 0xF1, 0x7B,
+ 0x4A, 0x8E, 0xBC, 0xC7, 0x06, 0x6B, 0x3E, 0x36, 0x98, 0xEF, 0xD8, 0x50, 0xF3, 0xAA, 0xED, 0x0B,
+ 0xB8, 0x62, 0x52, 0xFC, 0x83, 0x04, 0xD1, 0x69, 0xCE, 0x12, 0xC7, 0x3B, 0x24, 0x8C, 0x48, 0x9A,
+ 0x98, 0x96, 0x66, 0xD8, 0x82, 0x75, 0x0A, 0xBB, 0x2D, 0xD8, 0xB7, 0x54, 0x9C, 0xE6, 0x41, 0x9E,
+ 0xF4, 0xD3, 0x0C, 0x77, 0x50, 0xDA, 0xC2, 0xE1, 0x44, 0x98, 0xD3, 0xEA, 0x21, 0x9B, 0x4A, 0xFB,
+ 0x2B, 0x2B, 0x6E, 0x33, 0x3D, 0x24, 0x6E, 0xAF, 0xD2, 0x02, 0x7E, 0x81, 0x60, 0x2B, 0x01, 0xBE,
+ 0x24, 0x3C, 0x01, 0x86, 0x0F, 0x29, 0x3B, 0xD2, 0x18, 0xCD, 0x84, 0x56, 0xBA, 0x3B, 0x88, 0x3A,
+ 0x19, 0x5C, 0x80, 0xC3, 0xAC, 0x4C, 0x34, 0xB1, 0xE6, 0x14, 0xFA, 0x92, 0x3C, 0x45, 0x8A, 0xA4,
+ 0xB3, 0x7C, 0xF3, 0x4A, 0x0B, 0xBC, 0x51, 0xF7, 0x03, 0x38, 0xD7, 0xD8, 0x8C, 0xBC, 0xD4, 0x33,
+ 0xA4, 0x4A, 0xED, 0xD9, 0x25, 0x34, 0xE7, 0xD2, 0x31, 0x73, 0x8E, 0x97, 0x17, 0x98, 0x33, 0x90,
+ 0xFB, 0xD4, 0x52, 0xC3, 0xA0, 0x07, 0x7B, 0x73, 0x40, 0x03, 0x98, 0x50, 0x4B, 0xA0, 0x08, 0x12,
+ 0x0E, 0x00, 0xCA, 0x74, 0x4A, 0xAB, 0x10, 0x2C, 0x9C, 0xCB, 0x62, 0x92, 0xA7, 0xC9, 0x1A, 0xD0,
+ 0x9C, 0x02, 0x19, 0x18, 0xB2, 0x48, 0xC7, 0x11, 0x01, 0x3A, 0x55, 0x45, 0x03, 0x89, 0x39, 0x1A,
+ 0xAC, 0x5F, 0x71, 0xE4, 0xDF, 0xFB, 0x8F, 0x7C, 0x36, 0x83, 0xA2, 0xD3, 0x47, 0x50, 0x74, 0x49,
+ 0xB1, 0xEF, 0xA3, 0x0E, 0x07, 0x6A, 0xCE, 0x05, 0x35, 0x67, 0x8A, 0x9A, 0xBB, 0x25, 0x08, 0x47,
+ 0x9A, 0x2E, 0x67, 0x83, 0x28, 0x89, 0xE0, 0x3B, 0xA7, 0x17, 0xA1, 0x20, 0xE8, 0x40, 0xF3, 0xBE,
+ 0x92, 0xF4, 0x32, 0x0F, 0xE9, 0xFD, 0x50, 0x25, 0xBD, 0xEE, 0x51, 0x5B, 0x3F, 0x0A, 0x14, 0x93,
+ 0xAE, 0x79, 0xDF, 0x8E, 0x7B, 0xE0, 0x5F, 0x45, 0xE2, 0xA0, 0x82, 0x44, 0xBB, 0x29, 0x0F, 0x61,
+ 0x32, 0x47, 0x35, 0x10, 0xCA, 0x8B, 0x16, 0x14, 0xB4, 0x33, 0x8D, 0xA6, 0x6A, 0x48, 0x96, 0xE8,
+ 0x34, 0x8D, 0x4B, 0x0E, 0xC2, 0xB2, 0xE0, 0xA5, 0xDE, 0x04, 0x09, 0x80, 0x76, 0x5D, 0x74, 0xD8,
+ 0x27, 0xEB, 0xFF, 0x94, 0x38, 0x99, 0xB2, 0xA8, 0xFB, 0x64, 0x5D, 0x5E, 0xD3, 0xB8, 0xDC, 0xEB,
+ 0xD0, 0x31, 0x51, 0x74, 0x39, 0xAE, 0x8F, 0x54, 0x65, 0xB9, 0x07, 0xC8, 0x91, 0x4A, 0x5D, 0xE4,
+ 0x00, 0x81, 0x56, 0x4F, 0xC9, 0x51, 0xC3, 0x01, 0xF2, 0x2A, 0x0E, 0x7A, 0x49, 0x9E, 0x8D, 0x41,
+ 0xE2, 0x1B, 0x90, 0xF7, 0xE2, 0x08, 0xB1, 0xDF, 0x2F, 0xC5, 0x21, 0xF2, 0x9E, 0x1C, 0x3B, 0x87,
+ 0xC8, 0x67, 0x79, 0x88, 0xBC, 0xBA, 0x20, 0x2F, 0xE2, 0x87, 0x14, 0x9B, 0x03, 0x01, 0x5D, 0xFB,
+ 0xCF, 0xF4, 0x19, 0x01, 0x84, 0xB8, 0xCA, 0x92, 0xBC, 0x8F, 0xE2, 0xB8, 0x62, 0x08, 0x77, 0xC9,
+ 0x36, 0x48, 0x6B, 0x47, 0x09, 0x34, 0xD6, 0x0E, 0x6E, 0x00, 0xE7, 0xF3, 0x80, 0xDC, 0x81, 0x38,
+ 0x19, 0xB3, 0x79, 0x36, 0xEE, 0xDD, 0xC0, 0x8F, 0x29, 0x39, 0x2B, 0x35, 0x12, 0x84, 0x20, 0xE2,
+ 0xBB, 0xA2, 0xF9, 0xE4, 0x4A, 0x5E, 0xF0, 0xA3, 0xC0, 0xB6, 0x19, 0xA8, 0xB4, 0x40, 0x36, 0x0D,
+ 0x65, 0x55, 0xC2, 0xA4, 0x10, 0x57, 0x67, 0x28, 0xAB, 0xBA, 0x0A, 0x65, 0x82, 0x69, 0x44, 0xF6,
+ 0x6A, 0x1B, 0x90, 0x9D, 0xDB, 0xEF, 0x29, 0x79, 0x1B, 0x07, 0x8C, 0x7E, 0x86, 0xBB, 0xC8, 0xC7,
+ 0x38, 0x40, 0x8C, 0x0B, 0xC8, 0xEB, 0x38, 0x18, 0xD2, 0x6B, 0x48, 0xF9, 0x29, 0x0E, 0x50, 0x1D,
+ 0x01, 0xBF, 0x7E, 0x8E, 0x1F, 0x4E, 0x5E, 0xED, 0xEF, 0xC2, 0xE5, 0x47, 0x0C, 0x20, 0x58, 0x7D,
+ 0x49, 0xF0, 0x5B, 0x7E, 0xE2, 0xD7, 0x4F, 0x7B, 0xEF, 0x76, 0x0F, 0xDF, 0x1E, 0xB4, 0x03, 0x18,
+ 0x33, 0x5C, 0x0F, 0x19, 0xA6, 0xBD, 0x3E, 0x3C, 0x3B, 0xD9, 0xDB, 0x3B, 0x38, 0xDD, 0x3B, 0x6E,
+ 0x07, 0xB7, 0x08, 0x56, 0x8A, 0x73, 0x35, 0x39, 0xAF, 0xF6, 0xB6, 0xDF, 0xEC, 0xA9, 0x1C, 0x18,
+ 0xCB, 0x27, 0xD1, 0xEA, 0xE9, 0xE1, 0xD9, 0xCE, 0xCB, 0x93, 0xD3, 0xED, 0xE3, 0xD3, 0x76, 0x20,
+ 0x86, 0x5A, 0x00, 0x96, 0x71, 0x93, 0xF3, 0xFA, 0x10, 0xAA, 0xC8, 0x8C, 0xDB, 0xAC, 0x54, 0x03,
+ 0xF8, 0x54, 0x95, 0x4C, 0x99, 0x18, 0xCF, 0xD1, 0xE1, 0x3E, 0x76, 0x2C, 0xC7, 0x34, 0xCA, 0x04,
+ 0x94, 0xC5, 0xB8, 0x6C, 0xDE, 0xD9, 0x91, 0xC9, 0x19, 0x8F, 0x30, 0x7D, 0xF7, 0x78, 0xFB, 0x87,
+ 0x4B, 0xD5, 0x77, 0x3F, 0x4F, 0x06, 0xA6, 0xEB, 0x57, 0x87, 0xDB, 0xBB, 0xA5, 0x33, 0x18, 0x44,
+ 0x42, 0xD8, 0xC7, 0xEA, 0xB1, 0xFF, 0xBA, 0x27, 0xB2, 0xA6, 0xE4, 0x79, 0x19, 0xBB, 0xDE, 0x58,
+ 0x96, 0xE3, 0x5D, 0xAC, 0x60, 0x48, 0xBE, 0xD8, 0x12, 0x6B, 0x29, 0xA7, 0xB7, 0x6B, 0x0A, 0xD8,
+ 0xBF, 0x54, 0xD3, 0xE5, 0x6A, 0x3C, 0xA9, 0x26, 0xCB, 0x65, 0xFB, 0xA1, 0x9A, 0x2C, 0x17, 0xF1,
+ 0xC7, 0x58, 0x4F, 0x6E, 0x0D, 0xA9, 0x08, 0x14, 0xFC, 0xB5, 0xC4, 0x82, 0x70, 0x6E, 0x3E, 0x5A,
+ 0x4E, 0xED, 0x80, 0x50, 0xC8, 0xAA, 0xA6, 0xB1, 0x5A, 0xDA, 0x52, 0x7A, 0x3B, 0x08, 0x48, 0x8A,
+ 0xE9, 0xF5, 0x41, 0x91, 0xA5, 0x96, 0x6F, 0x48, 0x99, 0x5B, 0x9A, 0xF5, 0xD3, 0x5E, 0x82, 0x6A,
+ 0x29, 0xB8, 0x7A, 0x43, 0x8E, 0xE4, 0xAC, 0x04, 0x68, 0x2E, 0xC8, 0x52, 0xE9, 0x0B, 0xB8, 0xAD,
+ 0x0B, 0x40, 0x7C, 0xAE, 0x99, 0xAF, 0x1C, 0xD2, 0xEC, 0x9C, 0x91, 0xF7, 0x4A, 0x78, 0xFC, 0x20,
+ 0xD0, 0x40, 0xE1, 0x40, 0x40, 0x8E, 0xF6, 0x70, 0xD9, 0x29, 0xE2, 0xF7, 0x90, 0x7B, 0x59, 0xA3,
+ 0x5C, 0x2A, 0x65, 0xE4, 0xA1, 0x83, 0x83, 0x2C, 0xC4, 0x91, 0xA3, 0x4E, 0x21, 0xBD, 0x2B, 0xCB,
+ 0x69, 0x08, 0x2E, 0x73, 0x37, 0x73, 0x0A, 0x17, 0x47, 0xB8, 0xF9, 0xFA, 0x70, 0xDE, 0x9A, 0x94,
+ 0x13, 0x18, 0x39, 0xEC, 0x43, 0x93, 0x24, 0xC6, 0x25, 0xE5, 0xD7, 0xAA, 0xAE, 0x4D, 0x3E, 0x41,
+ 0x3C, 0xFB, 0x25, 0xDE, 0x28, 0x25, 0xED, 0xD2, 0x21, 0x4F, 0x4C, 0xD2, 0xA5, 0x94, 0xF7, 0xDB,
+ 0x9B, 0xFA, 0x0E, 0x7E, 0x0B, 0x02, 0x5E, 0x61, 0xEE, 0xD4, 0xB7, 0x85, 0xAD, 0x1A, 0xEE, 0x4C,
+ 0x66, 0x2C, 0xE3, 0xBA, 0x1D, 0x39, 0x1C, 0x29, 0x88, 0x84, 0xF9, 0x04, 0x19, 0xB3, 0x7B, 0x10,
+ 0x35, 0x4C, 0x4D, 0xD2, 0xAC, 0xC9, 0x64, 0x63, 0x8B, 0x25, 0x9F, 0xD2, 0x01, 0xF6, 0x09, 0xF2,
+ 0xBB, 0xCF, 0xA7, 0x58, 0xED, 0x08, 0xF1, 0xAF, 0x50, 0x4D, 0x2B, 0x64, 0x94, 0xD7, 0x64, 0x2D,
+ 0xC3, 0xBC, 0x83, 0x81, 0x66, 0x77, 0xAD, 0x23, 0x9D, 0x27, 0x9A, 0x52, 0x89, 0xAF, 0x4F, 0x4A,
+ 0xC9, 0x66, 0x84, 0x49, 0xBF, 0x2F, 0xBE, 0x91, 0x2B, 0xA1, 0x8C, 0xE6, 0xC0, 0xE8, 0x28, 0x86,
+ 0x36, 0xF7, 0x31, 0xB4, 0x80, 0x8D, 0xCE, 0xF2, 0x57, 0x96, 0x47, 0x8B, 0x2C, 0x04, 0x9E, 0x85,
+ 0x6F, 0xE1, 0x88, 0x97, 0x55, 0xDE, 0x02, 0x19, 0x79, 0x93, 0x16, 0xE9, 0x95, 0xCB, 0x0C, 0x5B,
+ 0x69, 0x9E, 0xE4, 0xEC, 0x3D, 0x7C, 0x50, 0x0A, 0xAC, 0x5D, 0xFB, 0x93, 0xAC, 0x1A, 0x44, 0x20,
+ 0x80, 0x97, 0x25, 0x51, 0x0A, 0xEF, 0x96, 0xD5, 0xC2, 0x6D, 0x51, 0x36, 0x05, 0x51, 0xF4, 0x3D,
+ 0x16, 0xC7, 0x12, 0x62, 0x04, 0x78, 0x9D, 0x95, 0x1C, 0xC0, 0xC2, 0xC3, 0xFF, 0x28, 0xAB, 0x24,
+ 0xE3, 0x8A, 0xE4, 0x07, 0x59, 0x1E, 0x17, 0x51, 0x85, 0x6A, 0x70, 0x16, 0x46, 0xA4, 0x1C, 0xC6,
+ 0x12, 0x36, 0x28, 0x56, 0x9C, 0x8A, 0xAA, 0x9D, 0xDE, 0x7D, 0x6F, 0x48, 0x43, 0x68, 0x36, 0x22,
+ 0xF0, 0x23, 0xC9, 0xF7, 0xD5, 0xFE, 0x09, 0xDD, 0xED, 0x14, 0xF9, 0xB6, 0x17, 0x8C, 0x5A, 0xD4,
+ 0x9F, 0x3F, 0xEA, 0xCD, 0x6A, 0x7D, 0x18, 0xE4, 0xA3, 0xBB, 0x8B, 0x9C, 0x3D, 0xD5, 0xB2, 0x4D,
+ 0x2D, 0xBB, 0xDD, 0x41, 0xDB, 0x95, 0xDA, 0xC0, 0x39, 0x9B, 0x8E, 0x42, 0x83, 0x0A, 0x76, 0x01,
+ 0x61, 0x1F, 0x73, 0xDA, 0xC5, 0x4A, 0x55, 0x24, 0x6A, 0x9B, 0x44, 0xD0, 0x2C, 0xA7, 0x0A, 0x86,
+ 0x0D, 0x03, 0x89, 0xC4, 0x32, 0xF2, 0xAC, 0x41, 0x7A, 0xD7, 0x29, 0x13, 0xA3, 0x45, 0x76, 0x37,
+ 0xE7, 0x92, 0x71, 0x64, 0x96, 0x7C, 0xEC, 0x03, 0xB9, 0xDB, 0x07, 0x5D, 0xFC, 0xE7, 0xD0, 0xD3,
+ 0x98, 0x14, 0xAC, 0x87, 0xFC, 0x7B, 0x4B, 0x1A, 0xB5, 0x1A, 0x77, 0x6D, 0x13, 0x30, 0x6E, 0x0B,
+ 0x16, 0x19, 0x4A, 0x54, 0x50, 0x31, 0xAA, 0x60, 0xB7, 0x94, 0x25, 0xFD, 0xDC, 0x42, 0x2E, 0x81,
+ 0xD4, 0x99, 0x33, 0x0A, 0x13, 0x0C, 0x51, 0x62, 0x24, 0x6F, 0x6D, 0x78, 0x6D, 0x04, 0x66, 0xDB,
+ 0x68, 0x89, 0xB0, 0x29, 0x89, 0xC9, 0x70, 0x8F, 0xF9, 0x94, 0xA5, 0xFD, 0xA5, 0x12, 0x96, 0xC9,
+ 0xE9, 0xA4, 0x31, 0xDB, 0xE2, 0xDD, 0xB7, 0xED, 0x8F, 0x9D, 0xF2, 0x1E, 0x48, 0x89, 0x1D, 0x35,
+ 0x6A, 0x93, 0xA3, 0x69, 0xC3, 0x55, 0xA4, 0x3E, 0xDC, 0xEB, 0xEB, 0xF0, 0x65, 0x44, 0x66, 0x5C,
+ 0x51, 0xDE, 0x47, 0xA4, 0x7A, 0x5A, 0x38, 0x2B, 0x68, 0x52, 0x6C, 0x9D, 0x39, 0x47, 0x8A, 0x45,
+ 0x6D, 0x9B, 0x66, 0x41, 0xBA, 0xD8, 0xD9, 0x53, 0xA5, 0xF4, 0x66, 0x4B, 0xD9, 0x73, 0x22, 0xF6,
+ 0xE9, 0xAB, 0xE2, 0x61, 0x08, 0x9A, 0xF6, 0x17, 0xA8, 0x22, 0xBD, 0x6C, 0x55, 0xF4, 0x42, 0xE1,
+ 0x2B, 0xC2, 0xC9, 0x19, 0x4C, 0x17, 0xDA, 0xD1, 0x52, 0x9F, 0x93, 0xBB, 0x74, 0xE4, 0x91, 0x0C,
+ 0x08, 0x7D, 0x68, 0x72, 0x55, 0x84, 0xD5, 0xB3, 0x4B, 0xA3, 0xD1, 0x56, 0xFC, 0xDD, 0x46, 0x64,
+ 0x10, 0x78, 0xBD, 0x5A, 0xAC, 0xB3, 0xB1, 0xA5, 0x6E, 0xA8, 0x82, 0xD8, 0xE1, 0xB5, 0x75, 0x6B,
+ 0xC3, 0xA5, 0x83, 0x38, 0x88, 0x3A, 0xDD, 0xB7, 0x43, 0xA9, 0xEE, 0x0E, 0xBB, 0xA5, 0x34, 0xFF,
+ 0xEC, 0xA1, 0xD3, 0x50, 0xEF, 0xE7, 0x96, 0x62, 0x5A, 0xCB, 0xD7, 0x1B, 0x8B, 0xA0, 0x97, 0x8A,
+ 0x93, 0x15, 0x68, 0x4A, 0x14, 0x0F, 0x0F, 0x48, 0x5A, 0xEE, 0x41, 0xE2, 0x68, 0x53, 0xF3, 0x96,
+ 0xFF, 0xF5, 0xF6, 0x20, 0x2B, 0x63, 0xF3, 0xAA, 0xBC, 0xE5, 0x8A, 0xBD, 0xE5, 0x05, 0xFA, 0xCB,
+ 0xF2, 0x0E, 0xDE, 0x49, 0x70, 0x4A, 0x98, 0x09, 0x40, 0x89, 0xC3, 0xB7, 0x76, 0x4A, 0x12, 0x6E,
+ 0x33, 0x67, 0x83, 0xD2, 0x8A, 0x99, 0x7D, 0x5C, 0x81, 0x2C, 0xE3, 0x12, 0x6B, 0xE6, 0x9E, 0xEF,
+ 0x2B, 0x2B, 0x09, 0x3F, 0x07, 0x8D, 0x28, 0x9C, 0x1E, 0x29, 0x4B, 0x86, 0x22, 0xAD, 0xA5, 0x0A,
+ 0xA0, 0x62, 0xC7, 0x55, 0xC7, 0x5D, 0x74, 0x99, 0xC3, 0x07, 0x55, 0x2B, 0x02, 0x53, 0x0D, 0xFF,
+ 0xFC, 0xD2, 0x76, 0x3B, 0xC1, 0xF3, 0x61, 0x76, 0x3D, 0x79, 0x1D, 0x28, 0xE0, 0x4E, 0xAA, 0x9B,
+ 0x40, 0x99, 0xEC, 0x37, 0x1C, 0x37, 0xAA, 0x10, 0x4A, 0xB8, 0xDC, 0x38, 0xF2, 0x35, 0x67, 0x9C,
+ 0x20, 0x0C, 0x70, 0xF6, 0x54, 0x58, 0x46, 0x2E, 0xE6, 0x62, 0x16, 0xF6, 0x60, 0xE8, 0x20, 0x2B,
+ 0x33, 0x91, 0x30, 0x56, 0x71, 0xF2, 0xA9, 0xAF, 0xD0, 0xCD, 0xAC, 0x16, 0x8E, 0x1D, 0xE3, 0x89,
+ 0xBA, 0x38, 0xDF, 0xA2, 0x16, 0xF9, 0xF3, 0xC6, 0xC6, 0x2A, 0xF3, 0x1D, 0x4A, 0x70, 0xB1, 0x9F,
+ 0x71, 0xCC, 0x6C, 0xA3, 0x1A, 0x98, 0x47, 0x0A, 0x93, 0xED, 0x4D, 0xCA, 0x87, 0xC9, 0x35, 0xC9,
+ 0x86, 0xC6, 0x68, 0x77, 0x35, 0xBA, 0xA1, 0x7F, 0x5B, 0x95, 0xAE, 0x76, 0xBE, 0xD6, 0xD5, 0x0E,
+ 0x99, 0x59, 0xF7, 0xEC, 0xC8, 0x57, 0x33, 0x95, 0x35, 0x9B, 0x64, 0x60, 0xB0, 0x79, 0xC2, 0x1F,
+ 0xC1, 0x0E, 0xA0, 0x61, 0x58, 0xF6, 0xE6, 0xFA, 0xE8, 0x51, 0x99, 0xAB, 0x6D, 0x5D, 0xD0, 0xD2,
+ 0x41, 0x05, 0x5D, 0xE4, 0xC7, 0xEC, 0x95, 0x95, 0xCD, 0x2D, 0xEA, 0xCD, 0xD2, 0x07, 0x76, 0xD7,
+ 0xC5, 0xCF, 0x8D, 0xB6, 0xFB, 0x4D, 0xE7, 0xEE, 0x18, 0x17, 0x75, 0x1B, 0x27, 0xA0, 0xEF, 0xE0,
+ 0xCD, 0x50, 0x15, 0x64, 0xDC, 0xD0, 0xD4, 0xB8, 0xAA, 0x26, 0x5F, 0x17, 0xB2, 0xF3, 0x09, 0xA7,
+ 0x9F, 0xA1, 0x23, 0x9A, 0xAC, 0xA7, 0x42, 0x48, 0xA4, 0x65, 0x4C, 0xF8, 0xCF, 0xE0, 0x20, 0xB9,
+ 0x05, 0x71, 0x51, 0x71, 0x97, 0x0A, 0x8B, 0x94, 0xD6, 0xDD, 0x4D, 0xDA, 0xBB, 0x89, 0x1E, 0x7A,
+ 0xB0, 0x15, 0x97, 0x9E, 0xFD, 0xB5, 0xED, 0x51, 0x9C, 0x94, 0x0E, 0x95, 0xCE, 0x15, 0x34, 0xFA,
+ 0xA1, 0x23, 0x0B, 0xFF, 0xBD, 0xB1, 0xB0, 0x3D, 0x70, 0x5C, 0x36, 0xC9, 0x77, 0x80, 0x96, 0x59,
+ 0x01, 0xD8, 0x8C, 0x8E, 0xC2, 0xFD, 0xFC, 0xA2, 0x05, 0xEC, 0x88, 0x16, 0xB4, 0x95, 0xB3, 0xEA,
+ 0x9B, 0x86, 0x72, 0x40, 0xA9, 0xF3, 0x0B, 0x52, 0xE6, 0xB6, 0x52, 0xEC, 0xF4, 0x10, 0x25, 0xD3,
+ 0xE5, 0xA1, 0x3C, 0xBF, 0xDF, 0x05, 0x33, 0x48, 0x31, 0x8E, 0xD2, 0x80, 0x88, 0xD5, 0x0D, 0x02,
+ 0xFD, 0x78, 0x4B, 0x52, 0xF1, 0xEF, 0x47, 0x92, 0x79, 0xD8, 0x3D, 0xBC, 0x30, 0xE6, 0xB1, 0x8F,
+ 0xAF, 0x43, 0x62, 0x1F, 0x82, 0xFD, 0xE0, 0x06, 0xD4, 0xCD, 0x26, 0x13, 0xB8, 0xD9, 0x64, 0xF0,
+ 0x2B, 0x8F, 0x24, 0x57, 0x6C, 0x8F, 0x1A, 0x14, 0x26, 0x45, 0x1A, 0xA5, 0x05, 0x1F, 0x56, 0xC4,
+ 0x61, 0xB6, 0x1A, 0x8A, 0x3E, 0xBB, 0x6B, 0x9B, 0xED, 0xCD, 0x28, 0xFA, 0x43, 0xBD, 0x03, 0x75,
+ 0x21, 0x5B, 0xDB, 0x84, 0x62, 0x45, 0xB7, 0xCC, 0xA2, 0xF9, 0x06, 0x73, 0xD1, 0x2E, 0x97, 0x28,
+ 0x2E, 0xCA, 0xBA, 0x56, 0xE4, 0x8D, 0xA4, 0xAE, 0xB5, 0x02, 0x83, 0x66, 0x16, 0x17, 0x14, 0x5F,
+ 0xBE, 0xF4, 0x39, 0x3C, 0x33, 0xD8, 0x14, 0x69, 0xDD, 0xAD, 0x62, 0x63, 0xF7, 0x08, 0xAC, 0x3D,
+ 0x6A, 0x37, 0xFB, 0xA7, 0x02, 0x29, 0xDB, 0x9C, 0xF4, 0xF5, 0x82, 0x80, 0xA9, 0xC2, 0x35, 0xE8,
+ 0x53, 0xDB, 0xC0, 0x7F, 0x66, 0x6D, 0x36, 0xB5, 0x5A, 0x5E, 0xB7, 0x1B, 0x2B, 0xB0, 0xCF, 0xA0,
+ 0x7D, 0x31, 0x2F, 0x14, 0xC7, 0x0B, 0xE6, 0x6E, 0x5F, 0x73, 0x72, 0x8A, 0x91, 0xAB, 0xEE, 0x91,
+ 0x06, 0x7E, 0x4F, 0x1F, 0xC7, 0x0E, 0xCE, 0x35, 0x15, 0xAE, 0xA3, 0xDF, 0xAF, 0x60, 0x48, 0x53,
+ 0x53, 0x26, 0xBF, 0xD1, 0x37, 0x86, 0xA6, 0x66, 0x7A, 0x37, 0xE9, 0xB0, 0x0F, 0x38, 0x7D, 0xEE,
+ 0x85, 0xF7, 0x45, 0x47, 0xDC, 0x8C, 0x59, 0x84, 0x24, 0x53, 0xB5, 0x28, 0x76, 0x95, 0xE2, 0xD2,
+ 0x7D, 0x2B, 0x47, 0x52, 0x92, 0x29, 0xDC, 0x24, 0xC5, 0xBC, 0xFB, 0x0C, 0x49, 0x7C, 0x0B, 0x0A,
+ 0x17, 0xAA, 0x61, 0x0C, 0xDA, 0xDA, 0x42, 0xB2, 0x42, 0x95, 0x7D, 0xA3, 0x3B, 0x84, 0x52, 0x3D,
+ 0x5F, 0x6D, 0xB8, 0x17, 0xDE, 0x68, 0xB9, 0x44, 0xF5, 0x1A, 0x89, 0xBB, 0x23, 0x93, 0xFB, 0xAB,
+ 0x0B, 0x17, 0x95, 0x5F, 0x00, 0xA1, 0x9E, 0x90, 0xD7, 0x51, 0x1B, 0x7E, 0x7F, 0x81, 0xDF, 0x3F,
+ 0x90, 0x9F, 0xA0, 0x6B, 0x9C, 0xF2, 0x50, 0xC8, 0xE3, 0xF5, 0x94, 0xA3, 0x0A, 0x43, 0xAF, 0x95,
+ 0x54, 0x4B, 0x48, 0xF1, 0x44, 0x5E, 0x1D, 0xAB, 0xC3, 0x21, 0xC9, 0xFC, 0xE6, 0x15, 0x2B, 0x2B,
+ 0x30, 0xB1, 0x61, 0x4D, 0x06, 0x80, 0x6A, 0x39, 0xC9, 0xFC, 0xD9, 0x1B, 0x13, 0x7E, 0x34, 0x61,
+ 0x96, 0xE8, 0x43, 0xAE, 0xEF, 0xB8, 0x82, 0xE0, 0x55, 0xFC, 0x1E, 0x96, 0xF0, 0x3B, 0x93, 0xF8,
+ 0x9D, 0x20, 0x7E, 0xF7, 0xA6, 0x02, 0x22, 0x55, 0xDC, 0xB6, 0x53, 0x7F, 0x17, 0xE1, 0x05, 0x6B,
+ 0x58, 0x5A, 0xFF, 0x14, 0x6F, 0x19, 0xD2, 0x7A, 0x0E, 0xD2, 0xE1, 0x14, 0x29, 0x4A, 0x99, 0x0C,
+ 0x13, 0x86, 0xE5, 0x04, 0x39, 0xBE, 0x6B, 0x69, 0xD4, 0xB7, 0x8F, 0x23, 0xF6, 0xD9, 0x45, 0xEA,
+ 0xF5, 0x01, 0x7B, 0xC0, 0xCD, 0x8D, 0xA8, 0xE3, 0xBD, 0x4D, 0xC7, 0xD7, 0xDD, 0xB0, 0x94, 0x61,
+ 0xEC, 0x5C, 0xF7, 0x75, 0xFE, 0xAC, 0xCC, 0xC9, 0xC4, 0xDB, 0x26, 0xB9, 0x8E, 0xDA, 0x5F, 0x51,
+ 0x4D, 0xCC, 0xA9, 0xBF, 0x88, 0x39, 0x46, 0x81, 0x7B, 0x72, 0x01, 0xCB, 0x58, 0x80, 0x9A, 0xBB,
+ 0x71, 0x19, 0x28, 0x85, 0x83, 0xD5, 0xB4, 0xBC, 0xF1, 0x04, 0xB0, 0x2B, 0xDB, 0x5B, 0x96, 0x12,
+ 0x7F, 0x01, 0xF6, 0xB9, 0x8B, 0xA2, 0x0D, 0x86, 0xB6, 0x86, 0xA0, 0xE5, 0x1E, 0x6A, 0x36, 0x86,
+ 0x83, 0x6A, 0xA3, 0xD1, 0xFE, 0xA3, 0xAF, 0xEC, 0x3F, 0xEA, 0x03, 0xB1, 0x0B, 0x6F, 0x12, 0xEA,
+ 0x7B, 0xC6, 0xF2, 0x1C, 0xF5, 0x6E, 0x3B, 0x0A, 0xF5, 0x25, 0x03, 0x2B, 0xCE, 0xEE, 0x7C, 0x86,
+ 0xCA, 0x30, 0x7D, 0xB4, 0xCA, 0xF0, 0x3D, 0x5C, 0x48, 0xF5, 0x55, 0xD9, 0xC9, 0x00, 0xBA, 0x59,
+ 0xF5, 0x74, 0x10, 0x06, 0xF8, 0xAA, 0x34, 0x05, 0x93, 0x48, 0x4D, 0x45, 0x03, 0xA9, 0x93, 0x29,
+ 0x95, 0xEB, 0xA6, 0x6D, 0xDA, 0x12, 0xB4, 0x10, 0x37, 0x92, 0xD5, 0x49, 0xE6, 0xA2, 0x07, 0xB4,
+ 0xB4, 0x77, 0x07, 0x21, 0xB5, 0x92, 0x52, 0x3F, 0x54, 0x6A, 0x26, 0x42, 0x96, 0x0D, 0x26, 0x65,
+ 0xA8, 0x4A, 0xAD, 0x27, 0x26, 0x8E, 0x0E, 0xEB, 0x78, 0x61, 0x73, 0x38, 0x98, 0xAA, 0x94, 0x0C,
+ 0x25, 0xF1, 0x7A, 0x23, 0x8D, 0x25, 0x9F, 0x1E, 0x64, 0x4B, 0xB7, 0x14, 0xDC, 0x4C, 0xFA, 0x4B,
+ 0x0C, 0x58, 0xAE, 0x3E, 0xDA, 0x3C, 0x32, 0xB0, 0x79, 0x7C, 0x1A, 0x75, 0xB0, 0x4E, 0xA8, 0x16,
+ 0x93, 0x96, 0x24, 0x67, 0xB4, 0x85, 0x32, 0x79, 0x14, 0x98, 0x59, 0x12, 0xA4, 0x57, 0x04, 0xB0,
+ 0x42, 0xAC, 0x09, 0x4E, 0x64, 0x7B, 0x94, 0xEE, 0xA0, 0x86, 0xE4, 0xA5, 0xB4, 0xCB, 0x7B, 0x94,
+ 0xF9, 0x16, 0x42, 0x03, 0x81, 0x65, 0x8E, 0x7A, 0x38, 0xB5, 0xB4, 0xC9, 0x81, 0x3A, 0x6E, 0x2C,
+ 0x01, 0x7A, 0xAE, 0x24, 0x0E, 0xA9, 0x5C, 0x0C, 0xC8, 0xD4, 0xAB, 0x56, 0x5D, 0x44, 0xC5, 0x27,
+ 0xD5, 0xC9, 0x8B, 0x55, 0x3E, 0x04, 0xC2, 0x78, 0x20, 0x4C, 0xCD, 0x84, 0x85, 0x20, 0x32, 0x6F,
+ 0x54, 0xAB, 0xC2, 0x96, 0x4A, 0xA1, 0x5D, 0x31, 0x26, 0x83, 0x45, 0xB0, 0x50, 0x78, 0xE6, 0x7B,
+ 0xAE, 0x3E, 0xC2, 0xA4, 0x27, 0xFF, 0x1A, 0xBD, 0x32, 0x91, 0xA5, 0x55, 0x53, 0x0D, 0xA5, 0x97,
+ 0x5E, 0x08, 0x05, 0x74, 0xEE, 0x51, 0x40, 0xFF, 0x5C, 0x55, 0x40, 0xE7, 0x1C, 0xB4, 0x25, 0xDE,
+ 0xA5, 0x02, 0xB8, 0x29, 0x61, 0xBD, 0xAA, 0xE9, 0xA8, 0xC4, 0x2C, 0x4D, 0x2A, 0x39, 0x82, 0xB8,
+ 0x1C, 0x89, 0xE9, 0xB9, 0xCE, 0x82, 0x14, 0x3C, 0xC2, 0x4D, 0x86, 0xD6, 0x25, 0x5C, 0xF3, 0x8B,
+ 0x74, 0x8B, 0x75, 0xA8, 0x75, 0x8C, 0x19, 0x84, 0xA8, 0x84, 0x8F, 0x3A, 0x43, 0xDE, 0x00, 0xF5,
+ 0x94, 0xA4, 0x7A, 0x45, 0xA7, 0x5A, 0xA3, 0xFC, 0xEA, 0x22, 0xF6, 0x94, 0x57, 0x79, 0x8E, 0x4A,
+ 0x19, 0xCA, 0xA9, 0xE4, 0x45, 0x74, 0xCA, 0xD0, 0xEE, 0x67, 0x80, 0x53, 0x83, 0x52, 0x19, 0xEA,
+ 0x05, 0xBD, 0x6C, 0x38, 0x4C, 0x46, 0x05, 0xE8, 0xD9, 0x6E, 0xB8, 0x50, 0x2B, 0xDB, 0x84, 0x31,
+ 0x17, 0x7A, 0xE5, 0x1B, 0x4E, 0xAE, 0xB9, 0xD4, 0x25, 0xF7, 0xC0, 0x0F, 0xA4, 0x0F, 0x0A, 0x2C,
+ 0xA9, 0x32, 0x47, 0xFD, 0xB0, 0xBC, 0x2E, 0xA0, 0xA9, 0x3E, 0x19, 0x98, 0x0C, 0xAB, 0xB2, 0x55,
+ 0xF9, 0x56, 0xD7, 0xAB, 0xE9, 0x22, 0x94, 0xBF, 0x84, 0xF2, 0x27, 0x2F, 0x0F, 0xDF, 0xB6, 0xA5,
+ 0x35, 0xE9, 0xEA, 0x98, 0x13, 0xFC, 0x3C, 0x90, 0xDF, 0x4C, 0x24, 0xBC, 0x14, 0x7A, 0xDC, 0x1B,
+ 0x40, 0x6E, 0xFD, 0xB9, 0x0B, 0xDA, 0x32, 0xA5, 0xA8, 0xC0, 0x24, 0xBF, 0x5A, 0x13, 0x73, 0x5C,
+ 0x33, 0xD6, 0x5B, 0x6E, 0x8C, 0x56, 0x9D, 0x49, 0x7F, 0xB2, 0x5F, 0x48, 0x8C, 0xC8, 0xBD, 0xF9,
+ 0x16, 0x46, 0x26, 0x7B, 0xF0, 0x79, 0x97, 0xF6, 0x39, 0xA8, 0xE9, 0x76, 0xE0, 0xE7, 0x0D, 0x95,
+ 0x5A, 0xCF, 0x53, 0x84, 0x0C, 0x36, 0x07, 0xDA, 0xC3, 0x72, 0xED, 0x13, 0xEE, 0x5A, 0x5D, 0xD9,
+ 0xB6, 0x50, 0xF5, 0x77, 0xE5, 0xD7, 0xEE, 0x25, 0x68, 0x90, 0x68, 0x79, 0x25, 0x7B, 0x1E, 0xC9,
+ 0x23, 0xA5, 0x22, 0xA5, 0xA5, 0xB3, 0xD5, 0x6C, 0xBC, 0x62, 0x0A, 0xBB, 0x9D, 0xE7, 0xC9, 0xFD,
+ 0xC2, 0xE8, 0xDD, 0x38, 0xF8, 0x73, 0x74, 0xD4, 0x88, 0x83, 0xDF, 0x3F, 0x5D, 0x05, 0x5A, 0xDA,
+ 0x07, 0x0A, 0x7B, 0x41, 0x1A, 0x8B, 0x96, 0xFC, 0x40, 0x9C, 0x1A, 0x4F, 0xE1, 0x90, 0x31, 0x7E,
+ 0x65, 0x0B, 0x0F, 0xE9, 0x04, 0x77, 0x5C, 0x0A, 0x3B, 0x0E, 0x2C, 0xA5, 0xF4, 0x8E, 0x4B, 0xB7,
+ 0xB2, 0x4E, 0x5A, 0xF2, 0xD2, 0x12, 0xF6, 0xFB, 0xCD, 0xF4, 0x18, 0x28, 0x42, 0xB2, 0x70, 0x7F,
+ 0x45, 0x34, 0xCB, 0xCB, 0x0A, 0xF9, 0x6A, 0x0A, 0x2C, 0xA5, 0x74, 0x0C, 0x8B, 0x0B, 0xB8, 0x85,
+ 0x6E, 0x25, 0x6A, 0x58, 0x46, 0x09, 0x53, 0xA8, 0xE6, 0xE2, 0xC2, 0xB3, 0x16, 0xAD, 0xD1, 0xB8,
+ 0xB8, 0x81, 0x21, 0x45, 0x53, 0x99, 0x27, 0x37, 0x88, 0xCB, 0xDA, 0xC9, 0xB4, 0xAE, 0x59, 0xDB,
+ 0x23, 0xF1, 0x1D, 0x4A, 0xF3, 0x77, 0xE2, 0x29, 0x3A, 0x99, 0x18, 0x81, 0xEA, 0x36, 0x18, 0x49,
+ 0x6D, 0xB3, 0xFE, 0x8E, 0x5A, 0x0F, 0xC5, 0xAC, 0x38, 0x38, 0xE4, 0x19, 0x55, 0x45, 0x4A, 0x2B,
+ 0x57, 0x55, 0xB2, 0x2B, 0xEA, 0xC3, 0xE8, 0x2E, 0x93, 0xC5, 0x8C, 0xF1, 0x9A, 0x39, 0xEE, 0x5B,
+ 0x1E, 0xC9, 0xA9, 0xE1, 0xB6, 0x0E, 0x25, 0x7B, 0x2A, 0x36, 0x93, 0x10, 0xFC, 0x8A, 0x5F, 0x35,
+ 0xD9, 0x3D, 0xF0, 0x28, 0x56, 0xD2, 0xBB, 0xEC, 0xDD, 0x2A, 0x70, 0xFD, 0x9F, 0xD9, 0xA7, 0x58,
+ 0x1E, 0x0B, 0x71, 0x29, 0x3F, 0x08, 0x2B, 0xA4, 0xDF, 0x16, 0xF1, 0x22, 0xC7, 0x29, 0x9F, 0x85,
+ 0x1D, 0x75, 0x8E, 0xA6, 0xB6, 0xA2, 0xBE, 0xB3, 0x5B, 0xE6, 0x05, 0x11, 0x0C, 0xA7, 0x5A, 0xA1,
+ 0xED, 0xB5, 0x6B, 0x1B, 0x09, 0x21, 0x55, 0x09, 0xEB, 0x94, 0xAE, 0x10, 0x34, 0x18, 0xF0, 0xA5,
+ 0xFC, 0x95, 0xC0, 0xB4, 0xB3, 0x82, 0x8E, 0xEA, 0x7C, 0xBF, 0x81, 0x39, 0x00, 0x1B, 0x54, 0x83,
+ 0x5F, 0xA4, 0xF9, 0x10, 0x73, 0xD3, 0xBA, 0xE4, 0x2D, 0x24, 0xC8, 0x82, 0x95, 0x69, 0xE4, 0x6B,
+ 0x53, 0xE8, 0x37, 0xF5, 0x5E, 0x02, 0x85, 0x91, 0x70, 0x98, 0x34, 0x9C, 0x82, 0x4D, 0x83, 0x24,
+ 0x92, 0xDA, 0xC3, 0x29, 0x3B, 0x99, 0x40, 0x19, 0x33, 0x68, 0xC1, 0x74, 0x28, 0x36, 0xB5, 0x24,
+ 0x2F, 0xDA, 0x4D, 0x61, 0x38, 0x85, 0xC0, 0x94, 0xAA, 0xD0, 0xD7, 0xE5, 0xDF, 0x47, 0xBC, 0xC4,
+ 0xBB, 0x7F, 0xE2, 0x55, 0xD1, 0x69, 0xC1, 0xEF, 0x87, 0x14, 0x6D, 0x78, 0x37, 0x3C, 0xBB, 0xC3,
+ 0x80, 0x1A, 0x7A, 0xA8, 0x65, 0xBA, 0xDD, 0xDC, 0x63, 0x37, 0xB0, 0xC0, 0xCA, 0x5A, 0x11, 0x3C,
+ 0x72, 0x80, 0x29, 0xC1, 0x93, 0xC4, 0xA8, 0xB1, 0xE1, 0xCA, 0xE2, 0xC0, 0x1D, 0x35, 0xD1, 0x1D,
+ 0x49, 0xC8, 0x02, 0xF0, 0xD6, 0x84, 0x7D, 0x1B, 0xAC, 0x86, 0x19, 0x70, 0x8B, 0x15, 0x47, 0xA0,
+ 0x4C, 0xA2, 0x69, 0xB8, 0x09, 0xF4, 0xB0, 0x58, 0xE4, 0x66, 0xE6, 0x4C, 0xB0, 0x06, 0x9D, 0x45,
+ 0x6E, 0x6C, 0xAC, 0x01, 0x96, 0x9F, 0x14, 0x2C, 0xEB, 0x80, 0xBD, 0x15, 0xE2, 0xFC, 0x3A, 0x54,
+ 0x83, 0x00, 0x92, 0x6B, 0x33, 0x47, 0xDE, 0xD4, 0xED, 0x45, 0x01, 0x56, 0xA3, 0xDE, 0x41, 0xE3,
+ 0x3D, 0xAD, 0x68, 0x5C, 0x40, 0x27, 0xF9, 0x3C, 0xBF, 0x58, 0x0D, 0x46, 0x9F, 0x03, 0xE4, 0x5C,
+ 0x85, 0x25, 0x44, 0x5D, 0x2D, 0x38, 0x97, 0xAC, 0xCC, 0xA4, 0x2A, 0xEA, 0x32, 0x50, 0xDE, 0x33,
+ 0xC8, 0xB3, 0xCC, 0xDE, 0x33, 0x14, 0xF6, 0x0C, 0x6D, 0xD8, 0x33, 0xE2, 0x74, 0xF4, 0xE3, 0xB7,
+ 0x6F, 0xC2, 0xAC, 0x32, 0x61, 0x44, 0x8B, 0xE7, 0xD9, 0x98, 0xE1, 0x7D, 0x74, 0x47, 0xC8, 0xC5,
+ 0x8F, 0x61, 0x5F, 0x85, 0x11, 0x14, 0x14, 0x80, 0xB0, 0xA2, 0x0D, 0xA7, 0x5A, 0x55, 0x58, 0xEE,
+ 0x6C, 0x96, 0xEA, 0x4E, 0x32, 0xDF, 0x1A, 0x0A, 0x5A, 0xCB, 0xDD, 0xB8, 0x73, 0x10, 0x1A, 0x1B,
+ 0x5B, 0x69, 0xA4, 0x8F, 0xFF, 0x0C, 0xDC, 0xCA, 0xB3, 0xAD, 0xB4, 0x93, 0xD9, 0x43, 0xBC, 0x5E,
+ 0xF7, 0x3C, 0x9B, 0x73, 0xAA, 0x63, 0xAB, 0xFA, 0x20, 0x06, 0x9D, 0xFE, 0x23, 0x4E, 0x78, 0x77,
+ 0x0D, 0x91, 0xD0, 0xE4, 0x62, 0xC2, 0xF3, 0xF6, 0xF0, 0x66, 0x34, 0x6D, 0xDA, 0xC3, 0x8D, 0xCB,
+ 0x13, 0x04, 0x02, 0x3A, 0xC9, 0x7F, 0x63, 0xCB, 0xF2, 0xA6, 0x3D, 0xC6, 0x17, 0xDD, 0xC9, 0xA5,
+ 0x0D, 0x28, 0x98, 0xED, 0xE6, 0x1D, 0x98, 0x88, 0xEB, 0x60, 0xBD, 0x47, 0xBB, 0xC1, 0x1A, 0x99,
+ 0x5A, 0xFE, 0x78, 0x9B, 0x6B, 0x38, 0xB5, 0xBC, 0x06, 0x0C, 0xF6, 0xB4, 0x6E, 0x34, 0x69, 0xA8,
+ 0x63, 0x96, 0x49, 0xAF, 0x8F, 0x6D, 0x31, 0x93, 0x84, 0x50, 0x99, 0x24, 0xF4, 0x39, 0x4A, 0x3B,
+ 0x14, 0xF7, 0x63, 0x25, 0xB2, 0x2A, 0xC1, 0x67, 0xAD, 0xD0, 0x83, 0x1A, 0x70, 0x53, 0x92, 0xF6,
+ 0x0A, 0xEE, 0x0E, 0xF7, 0xDC, 0xE1, 0x9A, 0x49, 0xCF, 0x1E, 0x30, 0x51, 0x7B, 0xBC, 0xBD, 0x23,
+ 0x9A, 0xB1, 0x3C, 0x62, 0x8D, 0xBA, 0x69, 0xB6, 0xA9, 0xEC, 0xD9, 0x5A, 0x67, 0x20, 0xA1, 0xB1,
+ 0xD0, 0xCB, 0x83, 0x12, 0x5F, 0x1C, 0x0C, 0x4F, 0xB9, 0x96, 0x8C, 0x54, 0xB1, 0xB2, 0xE2, 0x6F,
+ 0x06, 0xFD, 0xD0, 0x80, 0xD1, 0x6B, 0xF2, 0x61, 0xF3, 0xD4, 0xD0, 0x06, 0xE4, 0x4F, 0x67, 0x5F,
+ 0x33, 0xD4, 0xD2, 0x83, 0xD8, 0xC7, 0xD3, 0x86, 0xB8, 0x74, 0x90, 0xB4, 0xCA, 0xE1, 0x79, 0xF4,
+ 0x5A, 0x51, 0x49, 0x09, 0x92, 0x46, 0x56, 0xD6, 0x66, 0x05, 0xFF, 0x6C, 0x06, 0x73, 0x9D, 0x88,
+ 0x05, 0x90, 0x82, 0x68, 0xD7, 0x53, 0x8D, 0xA0, 0x24, 0x60, 0x2A, 0x17, 0xBB, 0xB9, 0x01, 0xAF,
+ 0x76, 0x68, 0x10, 0x56, 0x4E, 0x9A, 0x0E, 0x35, 0x6C, 0x48, 0xCD, 0x6C, 0xFD, 0x9E, 0x93, 0x65,
+ 0xD6, 0x40, 0xB4, 0x18, 0x70, 0xD5, 0xD5, 0x11, 0x7E, 0x95, 0x4F, 0xA2, 0x71, 0x47, 0xEC, 0x36,
+ 0x2C, 0xA3, 0xF2, 0xCF, 0x85, 0xEE, 0xBE, 0x91, 0x58, 0xD3, 0xFA, 0xB6, 0xE1, 0xF6, 0x67, 0x76,
+ 0xCF, 0x69, 0x01, 0x99, 0x4F, 0xAA, 0x89, 0x02, 0xCB, 0x87, 0xA9, 0x0A, 0x40, 0xB0, 0xB2, 0xC2,
+ 0xCC, 0x65, 0x66, 0x1D, 0x6F, 0x16, 0x13, 0xE4, 0x01, 0x94, 0x59, 0x7F, 0x8A, 0x77, 0x02, 0x9D,
+ 0x2F, 0x04, 0x65, 0xD6, 0x7F, 0x4E, 0xD2, 0x1F, 0x8C, 0x1E, 0x62, 0x46, 0x20, 0x5C, 0xE8, 0xEA,
+ 0xF2, 0xD1, 0x46, 0xA9, 0x25, 0xB8, 0x95, 0x2F, 0x2E, 0xB5, 0x4C, 0xA5, 0xD4, 0x12, 0xEB, 0xC0,
+ 0x35, 0x48, 0x3A, 0x70, 0x24, 0xBF, 0x9D, 0xA0, 0xAD, 0xCF, 0x85, 0xA4, 0x2D, 0xF1, 0x48, 0xDA,
+ 0x2E, 0x79, 0x55, 0xD4, 0x76, 0xC2, 0x1D, 0x5D, 0x79, 0xB0, 0x2D, 0x4C, 0x8B, 0x5A, 0xBD, 0x71,
+ 0x8E, 0xBB, 0xEC, 0xD4, 0xD1, 0x7A, 0x7B, 0xDC, 0x02, 0x95, 0x68, 0xD9, 0x2C, 0xEA, 0x3C, 0xC9,
+ 0x29, 0x49, 0x17, 0xBE, 0xAC, 0xE3, 0xC6, 0xC5, 0x1D, 0xFB, 0x08, 0x34, 0xEA, 0x6A, 0x37, 0x92,
+ 0x36, 0x13, 0x69, 0xA8, 0x70, 0x6F, 0x92, 0xD4, 0xE1, 0x92, 0x4F, 0x71, 0xFF, 0x6A, 0xE1, 0x57,
+ 0xEC, 0x29, 0xAA, 0x33, 0x1D, 0x29, 0x1D, 0x14, 0xD4, 0xE9, 0x8B, 0x88, 0xE9, 0xB0, 0xE9, 0x6B,
+ 0x4E, 0xAE, 0x9A, 0x04, 0x75, 0xFB, 0x40, 0xE2, 0xFA, 0x10, 0xC6, 0x40, 0x78, 0x05, 0x90, 0x5D,
+ 0x29, 0xA8, 0xB3, 0x09, 0x77, 0x52, 0x50, 0xB7, 0xCB, 0xC9, 0x36, 0x77, 0x5C, 0x40, 0x0E, 0x94,
+ 0xDC, 0x6E, 0x1F, 0xE4, 0x76, 0x87, 0x3C, 0x2E, 0x45, 0x20, 0x08, 0x9E, 0xFD, 0x6D, 0xF2, 0xDD,
+ 0xC6, 0xE4, 0x4F, 0x7F, 0x85, 0x9B, 0xD6, 0x07, 0x10, 0xC4, 0x95, 0x05, 0x6D, 0x77, 0x55, 0x41,
+ 0x9B, 0x48, 0x2A, 0x8B, 0xEA, 0xEE, 0xAA, 0xA2, 0xBA, 0x3B, 0x25, 0x88, 0x33, 0xF2, 0xB7, 0xBB,
+ 0x9A, 0x64, 0xCE, 0xE6, 0xAC, 0x6E, 0x73, 0xED, 0x83, 0x61, 0xB2, 0xAD, 0x33, 0x86, 0x2D, 0x70,
+ 0x76, 0xE4, 0x66, 0x8F, 0x47, 0x2A, 0x13, 0x5C, 0x08, 0x79, 0x6C, 0xBD, 0xD2, 0xC8, 0x2B, 0x23,
+ 0xDC, 0x7B, 0xAF, 0x00, 0x05, 0x45, 0xC9, 0x4B, 0xF5, 0x5B, 0x79, 0x2A, 0x1C, 0xAB, 0x4F, 0xE9,
+ 0xA0, 0xF0, 0xB9, 0x04, 0xD1, 0x35, 0x40, 0xAE, 0xB1, 0x76, 0x68, 0x78, 0x01, 0x19, 0xA3, 0x4C,
+ 0x85, 0xF0, 0x28, 0x38, 0xB0, 0x68, 0xBD, 0x80, 0x9C, 0x55, 0xE4, 0x7A, 0xB6, 0x2E, 0x9E, 0x30,
+ 0x6F, 0x11, 0xE8, 0x3A, 0x61, 0x09, 0x78, 0xDC, 0x5B, 0x70, 0x50, 0x51, 0x69, 0xB6, 0x03, 0xF0,
+ 0x55, 0xC1, 0x34, 0x30, 0x3D, 0xBF, 0x4A, 0xF2, 0x35, 0xF8, 0x07, 0x7C, 0x56, 0x6A, 0x85, 0x96,
+ 0xCC, 0xA7, 0x70, 0x46, 0x10, 0x0E, 0x9A, 0x2D, 0x3D, 0xD1, 0x48, 0x7C, 0xB6, 0xCD, 0x27, 0xB8,
+ 0xBA, 0x40, 0x03, 0x3C, 0x1B, 0xE1, 0x38, 0xD1, 0x43, 0xFF, 0xB9, 0xFA, 0x44, 0xBF, 0x12, 0xF2,
+ 0x06, 0xB1, 0x24, 0x03, 0x9F, 0xCF, 0x5B, 0x9D, 0xFD, 0xCE, 0xA4, 0xC8, 0x12, 0x5F, 0xE0, 0x5B,
+ 0xCC, 0x5A, 0x17, 0xF8, 0x85, 0x0B, 0x6F, 0x1A, 0xF3, 0xFD, 0x04, 0x10, 0x43, 0x86, 0x0A, 0x69,
+ 0x6F, 0x10, 0x40, 0x60, 0xE1, 0x11, 0x74, 0x85, 0xB7, 0x8B, 0x24, 0xBF, 0x6F, 0xAB, 0xDB, 0xAA,
+ 0x64, 0x3C, 0x02, 0x8C, 0x37, 0x42, 0xE1, 0x57, 0x8F, 0xB6, 0xF5, 0x0E, 0x23, 0xC8, 0xE2, 0x0D,
+ 0x13, 0x28, 0xD9, 0xBF, 0x07, 0x42, 0x07, 0x80, 0x9C, 0x92, 0x1F, 0x4C, 0x93, 0xD6, 0x89, 0x48,
+ 0x89, 0x86, 0xF5, 0xDE, 0x88, 0x02, 0xD1, 0x97, 0x15, 0x22, 0xDB, 0x2E, 0xEB, 0x62, 0x64, 0xDB,
+ 0x6D, 0x3D, 0xB3, 0x34, 0x00, 0x99, 0x03, 0xFD, 0xFF, 0xE8, 0x97, 0xC1, 0xF6, 0xE4, 0xD1, 0xEB,
+ 0xF7, 0x50, 0x90, 0xC1, 0xB7, 0x04, 0x67, 0xB8, 0xA0, 0x8F, 0x03, 0x2E, 0xA5, 0xCD, 0x7D, 0x0D,
+ 0x5F, 0x9A, 0xC2, 0x59, 0x33, 0xEF, 0x03, 0x81, 0x08, 0xAA, 0x14, 0xB8, 0x8C, 0x03, 0x51, 0x93,
+ 0x49, 0xE1, 0x7C, 0xFF, 0x81, 0xDE, 0x62, 0x32, 0x38, 0x3C, 0x02, 0x9D, 0x29, 0x19, 0x54, 0x9A,
+ 0x29, 0x2C, 0x3B, 0xC2, 0x6B, 0xAD, 0xEE, 0xC8, 0xF2, 0x96, 0xCD, 0x17, 0x15, 0x82, 0x32, 0x27,
+ 0x3B, 0xF3, 0x52, 0x53, 0xAF, 0xA4, 0x89, 0x36, 0x34, 0x23, 0x2C, 0x00, 0x01, 0x12, 0xC2, 0xDD,
+ 0x73, 0xD9, 0xB0, 0x39, 0x15, 0x35, 0xBE, 0xD3, 0xEE, 0x94, 0x58, 0x59, 0xD4, 0x07, 0x29, 0x10,
+ 0x20, 0x4C, 0xDD, 0xAC, 0x17, 0x94, 0x40, 0x69, 0x08, 0x58, 0x80, 0x37, 0x1E, 0xD7, 0x63, 0xEF,
+ 0x59, 0x1D, 0x98, 0x78, 0x76, 0x4F, 0x8B, 0x25, 0xBD, 0x37, 0x8B, 0x25, 0x15, 0xE7, 0x6D, 0xE9,
+ 0x48, 0xC7, 0x65, 0xB3, 0x61, 0xE9, 0x46, 0x3A, 0x09, 0x0C, 0xD6, 0x06, 0xEB, 0x51, 0xA0, 0x44,
+ 0x56, 0x95, 0x70, 0x16, 0x81, 0x64, 0x51, 0x6B, 0x46, 0xBA, 0x06, 0x9D, 0xBB, 0x50, 0xA3, 0x5D,
+ 0xE5, 0xDA, 0xEB, 0xC5, 0x90, 0x85, 0xC9, 0x1A, 0x9A, 0x98, 0xCF, 0xBC, 0x9B, 0xA2, 0x8A, 0x7F,
+ 0x9F, 0xD1, 0x98, 0x60, 0xE1, 0x89, 0xBB, 0xE7, 0x97, 0x2B, 0x83, 0xD7, 0xFB, 0x14, 0x39, 0xD4,
+ 0xF2, 0xC5, 0xF2, 0x05, 0x8F, 0xDC, 0xAD, 0x84, 0x30, 0x1E, 0x87, 0x59, 0x69, 0xB7, 0x10, 0xB3,
+ 0x5B, 0x24, 0x48, 0xE5, 0x8E, 0x42, 0x15, 0xD9, 0xC2, 0x8E, 0x3D, 0x42, 0xA2, 0xEB, 0x46, 0xC1,
+ 0x78, 0x0D, 0xBF, 0x2D, 0xD3, 0xAC, 0xCB, 0xC3, 0x38, 0xFB, 0xF7, 0x91, 0x31, 0x0C, 0x0A, 0x05,
+ 0xDB, 0x23, 0x7D, 0xFB, 0xA4, 0xF3, 0xA3, 0xD8, 0xE9, 0x03, 0x38, 0xB7, 0xB3, 0x51, 0x45, 0xCC,
+ 0x64, 0x1C, 0x96, 0xE7, 0xFA, 0x29, 0xBB, 0x92, 0x3F, 0x67, 0x7B, 0xA8, 0x9D, 0xAA, 0x77, 0x88,
+ 0xC8, 0xAD, 0x26, 0x5A, 0xF4, 0xAE, 0xEC, 0x80, 0x03, 0xD8, 0x02, 0x78, 0xF3, 0xF6, 0x89, 0xCA,
+ 0x85, 0x15, 0xBB, 0x7F, 0xC3, 0x4F, 0x26, 0x33, 0xF7, 0xBB, 0xCD, 0xAE, 0x6F, 0x60, 0x4D, 0x0C,
+ 0xE6, 0xEC, 0x55, 0x5A, 0xDB, 0xAB, 0xC8, 0xB3, 0x2F, 0x48, 0x41, 0x80, 0x8B, 0x63, 0x6E, 0x88,
+ 0x91, 0x86, 0xB0, 0x2D, 0xE1, 0x3C, 0x48, 0xB2, 0x47, 0x40, 0x12, 0xE7, 0xA6, 0x05, 0x83, 0xDF,
+ 0x88, 0x70, 0xAE, 0xAC, 0x34, 0x02, 0xF2, 0x6B, 0xE0, 0x88, 0x6C, 0xD9, 0xFF, 0x32, 0x1C, 0x05,
+ 0x9F, 0xA8, 0x00, 0xF9, 0x78, 0xA1, 0xCE, 0x2E, 0xF4, 0xE4, 0x75, 0x6E, 0xB9, 0x83, 0x0C, 0x8F,
+ 0x24, 0xA7, 0x34, 0x64, 0xA9, 0xF2, 0x10, 0x04, 0xC8, 0xD2, 0x15, 0xA3, 0xE2, 0x51, 0x34, 0x58,
+ 0x05, 0x6A, 0x09, 0xA3, 0xFA, 0x41, 0x2E, 0x54, 0x4D, 0xE3, 0x11, 0xF0, 0x74, 0xCE, 0x60, 0x17,
+ 0x39, 0x9C, 0xB1, 0x7A, 0xAD, 0x63, 0xA7, 0xDF, 0x02, 0x2C, 0x93, 0x31, 0x54, 0xE8, 0x99, 0x68,
+ 0x3F, 0x8C, 0x1E, 0xE5, 0x15, 0xE2, 0xB3, 0x5A, 0xFE, 0xA0, 0xEE, 0x64, 0x73, 0x9C, 0xEE, 0x91,
+ 0x24, 0x00, 0xFF, 0x87, 0x01, 0xCF, 0x92, 0x41, 0x22, 0x9B, 0x26, 0xD4, 0x2A, 0xF0, 0xA2, 0xB9,
+ 0x82, 0x2D, 0xE5, 0x6B, 0xA3, 0x1C, 0xE5, 0xEC, 0x7D, 0xA6, 0xA5, 0xFA, 0xA8, 0x2D, 0x97, 0xBA,
+ 0x43, 0xF9, 0x5D, 0x73, 0xF6, 0x81, 0x83, 0x6A, 0x6C, 0x0A, 0xCF, 0x58, 0x29, 0x19, 0xA9, 0x32,
+ 0x49, 0xD5, 0x9D, 0x68, 0x57, 0xFD, 0x71, 0x6C, 0x09, 0xC6, 0x8C, 0x2A, 0xA3, 0x4C, 0x55, 0x5E,
+ 0xF1, 0x91, 0x47, 0x26, 0xEE, 0xA4, 0x2D, 0x66, 0x04, 0x6A, 0xC3, 0xA4, 0x57, 0x19, 0x8E, 0xEE,
+ 0xDE, 0x05, 0x82, 0x13, 0xA1, 0x0C, 0xB6, 0xEF, 0x1B, 0x6E, 0x78, 0x32, 0xBB, 0xFD, 0xDF, 0xA3,
+ 0x88, 0x8D, 0xC6, 0x3F, 0x73, 0xD2, 0x40, 0x1F, 0x3E, 0x23, 0xED, 0x80, 0x12, 0xCF, 0xD1, 0xC6,
+ 0xBA, 0x54, 0xF1, 0x25, 0x54, 0xA4, 0xF1, 0x17, 0x5E, 0x4E, 0x3B, 0x16, 0x69, 0xBF, 0xF0, 0xF6,
+ 0x9C, 0xB6, 0xDE, 0x71, 0x19, 0xAB, 0xC9, 0x45, 0x61, 0xCF, 0x25, 0x74, 0x63, 0xAB, 0x31, 0x56,
+ 0x8A, 0xBE, 0xB1, 0x04, 0xFA, 0x24, 0xD5, 0xF0, 0x39, 0x14, 0x5C, 0xBC, 0x1F, 0x89, 0x09, 0x10,
+ 0xB9, 0xA9, 0x82, 0x82, 0x1B, 0x0F, 0xB5, 0xCE, 0x83, 0xC8, 0xDB, 0x40, 0x17, 0x8E, 0x56, 0x16,
+ 0xCF, 0x08, 0x56, 0x58, 0x48, 0xC4, 0x34, 0x9F, 0x84, 0x56, 0x5A, 0x08, 0x4B, 0x59, 0x76, 0x1E,
+ 0x93, 0xC9, 0x83, 0x90, 0xBF, 0xB5, 0x75, 0x6E, 0xEC, 0xE9, 0x5C, 0x60, 0x61, 0x8D, 0xF9, 0xA8,
+ 0xAF, 0xFB, 0xC3, 0x48, 0xE1, 0x84, 0x24, 0xDA, 0x0E, 0x9A, 0xE0, 0xF6, 0x83, 0x00, 0xC1, 0xE9,
+ 0x75, 0x0A, 0x5B, 0xBB, 0x2D, 0xEF, 0x38, 0xB2, 0x98, 0x85, 0x16, 0x94, 0x91, 0x57, 0x9B, 0x07,
+ 0xCA, 0xC4, 0x69, 0xE2, 0x9A, 0x66, 0x62, 0xD6, 0x94, 0xA8, 0x4D, 0x7D, 0x08, 0x9C, 0x88, 0x08,
+ 0xE0, 0xF8, 0xA0, 0xB8, 0xAA, 0x94, 0x9A, 0x00, 0x8A, 0x5E, 0xA6, 0x6B, 0x3A, 0x35, 0x10, 0x97,
+ 0xD7, 0xD6, 0x1A, 0x6F, 0xA9, 0x6E, 0x43, 0xC2, 0xF6, 0xCD, 0x0C, 0x55, 0x06, 0x9C, 0x3B, 0x41,
+ 0x6D, 0x45, 0x6C, 0x86, 0xB5, 0xBC, 0x29, 0x85, 0x96, 0xBD, 0x6F, 0x1A, 0xD2, 0x04, 0xC8, 0xBD,
+ 0x6B, 0x42, 0xD8, 0x13, 0xB9, 0x75, 0x29, 0x1E, 0x55, 0xD1, 0xFA, 0x22, 0x52, 0xA9, 0x4F, 0xB8,
+ 0x4F, 0x04, 0x47, 0x67, 0x18, 0x0E, 0xD2, 0xC5, 0x45, 0x70, 0x54, 0x1B, 0x0E, 0x52, 0x2D, 0x82,
+ 0x73, 0xAE, 0x2D, 0x35, 0x3F, 0x10, 0x98, 0xC7, 0x33, 0x38, 0x07, 0x94, 0x7B, 0x07, 0x40, 0x55,
+ 0xC9, 0x28, 0x44, 0x1A, 0x0E, 0x60, 0x32, 0xF9, 0x7B, 0x6C, 0xF2, 0x23, 0xA3, 0xFC, 0xA2, 0x0B,
+ 0x8B, 0xB7, 0xCE, 0x70, 0xBA, 0x10, 0x82, 0x19, 0xEE, 0x41, 0x36, 0x04, 0xF3, 0x56, 0x6A, 0xC3,
+ 0x30, 0x67, 0x8D, 0x94, 0x10, 0x55, 0x4F, 0xE8, 0x40, 0x31, 0x10, 0xBF, 0xEC, 0x02, 0x90, 0xA2,
+ 0xCA, 0x7E, 0x60, 0xFE, 0x54, 0xAC, 0xCB, 0xCA, 0x8A, 0x92, 0xD1, 0xC4, 0x7A, 0x06, 0x30, 0xA9,
+ 0xA2, 0x25, 0xD2, 0xA4, 0x1B, 0x03, 0xD4, 0xCF, 0x65, 0xCF, 0x10, 0x0F, 0x53, 0x92, 0x1D, 0x79,
+ 0x1D, 0xCB, 0x5C, 0x4E, 0x67, 0x65, 0x45, 0xDA, 0x22, 0xD4, 0xDB, 0x5B, 0xD4, 0x79, 0x66, 0x32,
+ 0x91, 0xD0, 0x2C, 0xD7, 0x2D, 0x41, 0x13, 0xF9, 0x2C, 0x6B, 0x10, 0x91, 0x11, 0x55, 0xDF, 0x30,
+ 0xAB, 0xC3, 0x1A, 0x13, 0x25, 0xEC, 0x94, 0x33, 0xCB, 0xC4, 0x00, 0x72, 0x0D, 0x9B, 0x78, 0xA3,
+ 0x47, 0x5C, 0x39, 0x66, 0xDE, 0x2A, 0xAE, 0xAF, 0x9B, 0xAF, 0x15, 0x08, 0xF6, 0xD9, 0xF7, 0x06,
+ 0x88, 0xB6, 0x38, 0x04, 0x4D, 0x07, 0x6E, 0x82, 0xC4, 0x6A, 0xED, 0x2C, 0x87, 0x96, 0xA9, 0xC4,
+ 0x85, 0x38, 0xB4, 0x22, 0x52, 0xB7, 0x86, 0x06, 0x84, 0x89, 0x3D, 0x11, 0xA0, 0x17, 0x51, 0x06,
+ 0x30, 0x3C, 0x68, 0x9A, 0xF4, 0x3A, 0x2C, 0x12, 0x22, 0xF5, 0xF2, 0x01, 0x09, 0x03, 0x30, 0x46,
+ 0x9B, 0x3F, 0x49, 0x89, 0x9F, 0xCF, 0xC2, 0x16, 0x5D, 0x7C, 0x16, 0x45, 0x95, 0xEE, 0x72, 0xF8,
+ 0xEC, 0x4F, 0x16, 0x35, 0x26, 0x20, 0xD4, 0x74, 0xB6, 0xE5, 0x77, 0x1B, 0xE5, 0xCF, 0x67, 0x7F,
+ 0x5B, 0x2E, 0x15, 0xF5, 0x85, 0x06, 0xFA, 0x68, 0x2E, 0x8D, 0x70, 0x12, 0x1F, 0x72, 0xD9, 0xAB,
+ 0xD9, 0xC5, 0xD0, 0xE0, 0x82, 0x8C, 0x97, 0xE0, 0x5B, 0xEA, 0x17, 0x87, 0x8A, 0xA0, 0x45, 0x41,
+ 0x7B, 0x26, 0x4B, 0x23, 0x43, 0x0C, 0xD3, 0xBA, 0x5C, 0x05, 0xA1, 0xBF, 0xCC, 0xAA, 0x33, 0x7E,
+ 0xF6, 0x27, 0xFB, 0xA5, 0xDA, 0xAF, 0x8A, 0xD4, 0x3D, 0xCE, 0x5D, 0x3F, 0x71, 0x19, 0xA7, 0x16,
+ 0xA1, 0x95, 0x2A, 0x00, 0x68, 0x2A, 0x93, 0x6A, 0x37, 0x2F, 0x0B, 0xAE, 0xCE, 0xB3, 0xBF, 0x59,
+ 0x98, 0xA3, 0x19, 0x5C, 0x06, 0x7E, 0x58, 0x6B, 0x6B, 0xE4, 0xBB, 0x8D, 0x72, 0x32, 0x28, 0xF9,
+ 0x8D, 0xAB, 0x14, 0x7C, 0xAD, 0xAE, 0x92, 0x6C, 0x6B, 0x43, 0x48, 0x19, 0xE0, 0x52, 0x9C, 0x82,
+ 0x86, 0x5F, 0x5F, 0xA8, 0xA7, 0x53, 0xED, 0x5C, 0xFF, 0xA7, 0xBF, 0xDA, 0x06, 0xB4, 0x71, 0x40,
+ 0x65, 0xB8, 0xE1, 0x99, 0xB8, 0xE9, 0xE4, 0x16, 0xE1, 0x55, 0xAC, 0xB9, 0x68, 0x2A, 0x61, 0x6C,
+ 0x32, 0x34, 0x05, 0x52, 0xA6, 0xCB, 0xBD, 0xDF, 0x4E, 0xA3, 0xF2, 0x84, 0xDB, 0x92, 0x96, 0xC1,
+ 0x6D, 0x2A, 0xFD, 0x83, 0xD4, 0xBF, 0xF4, 0xEA, 0xFA, 0x17, 0xDC, 0xB3, 0x55, 0xE1, 0x38, 0xC8,
+ 0x9F, 0x41, 0x70, 0xD9, 0xB0, 0x6F, 0x1A, 0x2B, 0x7D, 0x5C, 0xA0, 0x92, 0x2B, 0xA3, 0x17, 0x5E,
+ 0x16, 0x90, 0xEA, 0xCA, 0xDE, 0x45, 0x2B, 0xF6, 0x1C, 0xAC, 0x54, 0xB5, 0x43, 0xFC, 0x9A, 0xCB,
+ 0xC9, 0x8F, 0x4D, 0xAA, 0x17, 0xB9, 0x8E, 0xB5, 0xA0, 0x5F, 0xF5, 0x7E, 0xDF, 0x56, 0xFA, 0xAD,
+ 0x77, 0x02, 0xAC, 0x8C, 0x56, 0x82, 0xC4, 0x9E, 0xFE, 0x54, 0xA6, 0xAB, 0xBF, 0x81, 0x82, 0x2A,
+ 0x7D, 0x21, 0xFD, 0x0D, 0x36, 0x7D, 0x00, 0xE0, 0x6E, 0xD2, 0xDF, 0xFC, 0x0A, 0x82, 0x75, 0xE0,
+ 0xB8, 0xC0, 0xFD, 0x87, 0x70, 0x2A, 0x94, 0x37, 0xEA, 0x8B, 0x52, 0xA1, 0xB9, 0xE1, 0x40, 0x6D,
+ 0xA9, 0x54, 0xD5, 0xFC, 0x0A, 0xAA, 0x9A, 0x14, 0x58, 0xB1, 0xAB, 0xA4, 0xF7, 0x01, 0xE4, 0x8D,
+ 0x28, 0x72, 0x77, 0xE2, 0x75, 0x09, 0x44, 0xC7, 0x1F, 0x28, 0xFC, 0x11, 0x81, 0xB9, 0xB2, 0x52,
+ 0xE9, 0x5A, 0xB4, 0x2D, 0x6F, 0x64, 0x2E, 0xD9, 0x88, 0xFD, 0x16, 0x6D, 0xD9, 0x4F, 0x8C, 0x1D,
+ 0xEE, 0x6A, 0x84, 0x28, 0xAD, 0x68, 0x84, 0x44, 0x92, 0xD5, 0x08, 0x99, 0xCF, 0x03, 0xF9, 0x2D,
+ 0xF2, 0x65, 0x50, 0xC8, 0x7D, 0x48, 0x12, 0xDD, 0xA5, 0x32, 0xF1, 0x78, 0xEF, 0x64, 0xFF, 0x57,
+ 0x68, 0x18, 0xC2, 0xF8, 0xA5, 0x5F, 0x64, 0xD3, 0x6A, 0x45, 0xF7, 0x4F, 0x5E, 0xEF, 0x9F, 0x9C,
+ 0x28, 0x3D, 0x51, 0x4B, 0x05, 0xF5, 0x14, 0x05, 0x14, 0x6A, 0xEB, 0x22, 0x46, 0x57, 0x54, 0x2E,
+ 0x24, 0xE3, 0x74, 0x9D, 0x1D, 0x99, 0x42, 0xF2, 0xE4, 0x1D, 0x8F, 0xEA, 0x85, 0x4C, 0x5B, 0xB6,
+ 0x58, 0xB5, 0x35, 0xBF, 0xF2, 0x0A, 0x73, 0x5C, 0xB3, 0xF2, 0x82, 0xAA, 0x85, 0x5D, 0xEB, 0x43,
+ 0xBC, 0xE8, 0x6C, 0xB0, 0x26, 0xE5, 0xA3, 0x48, 0xF1, 0x21, 0x2E, 0xB3, 0xCE, 0x54, 0xA9, 0xA8,
+ 0xF7, 0xB9, 0xA5, 0x49, 0x31, 0xCE, 0x21, 0x73, 0x68, 0x32, 0xF5, 0xDA, 0x05, 0xA4, 0x67, 0xD2,
+ 0x32, 0x8C, 0x07, 0x45, 0x6E, 0xA8, 0x0E, 0xB9, 0x3C, 0xA6, 0x5A, 0xC3, 0x75, 0x8D, 0x18, 0x53,
+ 0xEE, 0x12, 0xB4, 0x1C, 0x36, 0x09, 0x79, 0x10, 0xA4, 0x39, 0x55, 0x75, 0x95, 0xC8, 0x45, 0x5D,
+ 0xD5, 0xA5, 0xC9, 0x52, 0xB3, 0x2D, 0xE5, 0xDD, 0x62, 0x3B, 0xD7, 0xE9, 0x67, 0xDA, 0x87, 0x7A,
+ 0x23, 0xB2, 0xA4, 0x7E, 0x4B, 0x95, 0x11, 0x7C, 0xA6, 0xC5, 0x9A, 0x48, 0x81, 0x9F, 0x05, 0x07,
+ 0x70, 0xDC, 0x63, 0x31, 0x30, 0xA8, 0x87, 0x6A, 0x4E, 0xC2, 0x27, 0xEA, 0xD5, 0xB1, 0x64, 0x52,
+ 0xC7, 0xB2, 0x98, 0x06, 0xA5, 0xAA, 0x83, 0x91, 0x73, 0xAD, 0x5D, 0xE1, 0xAF, 0x4D, 0x05, 0x0D,
+ 0xC5, 0x4A, 0x78, 0x12, 0x44, 0xC6, 0x72, 0xAC, 0xAB, 0xE7, 0x00, 0x20, 0x7D, 0xB5, 0x72, 0xEC,
+ 0xEB, 0xD3, 0x01, 0xCB, 0x72, 0xFA, 0x5C, 0xB5, 0x22, 0xFC, 0x49, 0x30, 0x73, 0x8E, 0x55, 0xBE,
+ 0x59, 0xD8, 0xB7, 0xE8, 0x24, 0x10, 0x6F, 0x28, 0xFD, 0x4C, 0xB6, 0x88, 0x7E, 0xC6, 0xDE, 0x71,
+ 0x9D, 0xD1, 0x36, 0xD8, 0x46, 0x73, 0x63, 0x1C, 0x5D, 0x67, 0xD5, 0xAC, 0x09, 0xA3, 0xD3, 0x12,
+ 0xB0, 0x1C, 0xDE, 0xE1, 0xCF, 0x32, 0xCE, 0xBE, 0x11, 0x1A, 0x85, 0x86, 0x59, 0x6F, 0x28, 0x55,
+ 0xBE, 0x61, 0xAE, 0x73, 0x2A, 0x25, 0xBD, 0x55, 0x89, 0x26, 0x98, 0x5F, 0x34, 0x1A, 0x3F, 0xB2,
+ 0xC8, 0x5D, 0x1D, 0xF0, 0xE0, 0x6E, 0xE2, 0xC3, 0x2B, 0xAB, 0x68, 0x22, 0x8C, 0xA1, 0x88, 0xE9,
+ 0x44, 0x43, 0xBE, 0xEC, 0x43, 0x5A, 0x4F, 0x4C, 0xFA, 0xEF, 0xC7, 0x05, 0xDF, 0x15, 0xD8, 0x53,
+ 0x2E, 0xB9, 0x57, 0xF4, 0x92, 0x91, 0x74, 0x63, 0x2D, 0x27, 0x1F, 0x23, 0x55, 0x32, 0xC9, 0x1E,
+ 0x51, 0x1C, 0x4E, 0xD9, 0xA1, 0x57, 0xB0, 0xA3, 0xBC, 0x31, 0x14, 0xC4, 0x0A, 0xDA, 0x40, 0x0A,
+ 0x16, 0x87, 0x75, 0x2B, 0x35, 0x52, 0xE4, 0xDA, 0xDE, 0x52, 0xD7, 0xEC, 0xCF, 0xD4, 0xB0, 0x14,
+ 0xCE, 0xE9, 0xD6, 0x0D, 0x33, 0x6F, 0x6B, 0x4B, 0x49, 0x50, 0x03, 0x7A, 0x6F, 0xA0, 0x48, 0xD0,
+ 0xCC, 0x1E, 0x80, 0xAC, 0xF3, 0x3D, 0xCE, 0x94, 0x54, 0x96, 0xB0, 0x37, 0x80, 0x69, 0x49, 0x76,
+ 0x3E, 0x03, 0x25, 0xFD, 0x81, 0xA2, 0x17, 0xC4, 0xD3, 0x2A, 0xB6, 0x2D, 0x60, 0x5B, 0xCB, 0xEA,
+ 0x8D, 0xB3, 0x06, 0xE5, 0x60, 0x95, 0x48, 0x28, 0x2B, 0xD6, 0x59, 0x9B, 0x43, 0x86, 0x02, 0x9E,
+ 0xB1, 0x43, 0x1E, 0x8F, 0x62, 0x25, 0xE6, 0xEF, 0xFA, 0x1A, 0xE7, 0xA8, 0x8E, 0x4D, 0xC8, 0x9B,
+ 0x65, 0xCF, 0x3E, 0xA6, 0x7E, 0xD9, 0x79, 0x0D, 0x41, 0xAB, 0xF8, 0xA7, 0x4B, 0xD5, 0x11, 0x10,
+ 0x38, 0x72, 0xC5, 0xF0, 0xFF, 0xF6, 0xB6, 0xAA, 0x4E, 0xF4, 0x22, 0x44, 0xA1, 0xD7, 0x78, 0x0A,
+ 0x85, 0xCD, 0x81, 0xD9, 0x33, 0x27, 0x30, 0xBB, 0xAD, 0x11, 0x4D, 0x9B, 0x14, 0x0F, 0xE7, 0xD2,
+ 0x09, 0xD0, 0x39, 0x50, 0xDC, 0xE3, 0xE4, 0xC2, 0xF7, 0xFE, 0x91, 0xB5, 0xBD, 0x54, 0x00, 0xA5,
+ 0x60, 0x04, 0x34, 0x7B, 0xA1, 0x66, 0xA8, 0x38, 0x38, 0x8D, 0x1E, 0x13, 0x78, 0x4B, 0x8E, 0x4B,
+ 0xA4, 0x2C, 0x70, 0xAE, 0xD9, 0xA4, 0xFA, 0xC9, 0x66, 0x33, 0xBD, 0x9B, 0x7F, 0x9E, 0x09, 0xAC,
+ 0xF7, 0x7C, 0x53, 0xC6, 0xB1, 0xFA, 0xD9, 0x8A, 0xB3, 0xBA, 0xFA, 0xA4, 0x4E, 0x73, 0x17, 0xD7,
+ 0x39, 0xA4, 0xD4, 0xAF, 0x45, 0xF8, 0x15, 0xB5, 0x08, 0x99, 0xD2, 0x15, 0x38, 0x54, 0xA8, 0x81,
+ 0xEC, 0x10, 0x36, 0x67, 0x0F, 0x37, 0x49, 0xEF, 0x57, 0x56, 0x9A, 0x72, 0xCC, 0xBB, 0x34, 0x70,
+ 0x37, 0x15, 0xDF, 0x10, 0xFB, 0xEA, 0x35, 0x84, 0xCB, 0xBA, 0x3C, 0x38, 0xDC, 0xDD, 0x9B, 0x4C,
+ 0x1C, 0x19, 0x10, 0x4A, 0x54, 0x29, 0xD8, 0x78, 0xA2, 0x20, 0xC8, 0x19, 0x86, 0xDF, 0xE9, 0x44,
+ 0x8B, 0x65, 0xE1, 0x5A, 0x30, 0xCC, 0x80, 0xC7, 0x74, 0xCB, 0x28, 0xCC, 0xAA, 0x4A, 0x89, 0x14,
+ 0x17, 0xBE, 0x80, 0x1E, 0x5A, 0x5D, 0x34, 0x96, 0x37, 0x6A, 0x74, 0xC0, 0xC2, 0xA4, 0x00, 0x21,
+ 0x4A, 0x39, 0xAB, 0xC2, 0x64, 0xF5, 0x41, 0xF2, 0x20, 0x11, 0xE1, 0x14, 0xF0, 0x70, 0xC3, 0x55,
+ 0x58, 0x96, 0x73, 0xF0, 0xCD, 0xB5, 0xC7, 0xFB, 0x17, 0x8C, 0x2B, 0x5B, 0x44, 0x8A, 0x04, 0xCC,
+ 0x5A, 0x30, 0xD8, 0xA3, 0x3D, 0xFA, 0x02, 0xD3, 0x74, 0x38, 0xBD, 0x2A, 0xFF, 0x71, 0xE0, 0x61,
+ 0x40, 0x48, 0x56, 0x46, 0x4C, 0x5A, 0x6D, 0x9C, 0xD6, 0x55, 0xFA, 0xD4, 0xCB, 0xE7, 0xC9, 0x93,
+ 0xB8, 0xEE, 0xF5, 0x34, 0x2D, 0xC5, 0x8F, 0xCF, 0x17, 0xA7, 0x97, 0x0A, 0xF6, 0x1D, 0x77, 0x2D,
+ 0x1A, 0xA8, 0x65, 0xD6, 0x40, 0x0B, 0x73, 0x45, 0x0B, 0x33, 0xB1, 0xBB, 0x1C, 0x18, 0x35, 0x69,
+ 0x15, 0x9B, 0xC9, 0x17, 0xF6, 0x5D, 0xFA, 0x74, 0x48, 0xB4, 0xAE, 0x23, 0x45, 0xDA, 0x02, 0xB6,
+ 0x1A, 0x74, 0x6E, 0xB2, 0x31, 0xBE, 0xA0, 0xEE, 0x9E, 0xD3, 0x5C, 0x89, 0x11, 0xA9, 0xD5, 0x21,
+ 0xAF, 0xF4, 0x92, 0xD5, 0x23, 0xB3, 0x61, 0x1E, 0x95, 0xB3, 0xDD, 0x1B, 0x42, 0xAF, 0xDB, 0xC0,
+ 0xB7, 0x55, 0xAE, 0x91, 0xCE, 0x4C, 0xCB, 0x52, 0x27, 0xBF, 0x88, 0x4F, 0x70, 0x74, 0x32, 0x48,
+ 0x40, 0xBB, 0xC2, 0xC0, 0x7A, 0xCF, 0xE1, 0x7A, 0x87, 0x38, 0x4D, 0xCB, 0x02, 0x2C, 0x3E, 0xCD,
+ 0xAE, 0xF6, 0x67, 0xD7, 0x13, 0x91, 0x77, 0x68, 0xEF, 0x61, 0xEA, 0x90, 0x66, 0x71, 0x9C, 0xB6,
+ 0x75, 0x6D, 0x31, 0x2C, 0x5B, 0x5D, 0x8C, 0xC6, 0x1E, 0xA4, 0x0D, 0x7E, 0x51, 0x33, 0x09, 0x16,
+ 0x03, 0xAC, 0x0D, 0xE6, 0x93, 0x20, 0x49, 0xAC, 0x04, 0x0D, 0x5A, 0x88, 0xB8, 0x49, 0x92, 0x15,
+ 0xCD, 0xBD, 0x7B, 0x35, 0x30, 0xAC, 0x15, 0x91, 0xBC, 0xCB, 0x36, 0x81, 0xC5, 0x94, 0x7C, 0xBB,
+ 0x06, 0xC7, 0x29, 0xCE, 0x29, 0x2C, 0x58, 0x84, 0x2A, 0xB5, 0x72, 0x87, 0x80, 0x85, 0xAD, 0x6F,
+ 0x7E, 0x00, 0xA1, 0xF5, 0x8D, 0x29, 0xBD, 0x84, 0xA3, 0xC7, 0x52, 0x86, 0xA4, 0x7B, 0x86, 0x03,
+ 0x62, 0x41, 0x8B, 0x4E, 0x92, 0x7D, 0x12, 0xC6, 0x77, 0xDE, 0x63, 0xF3, 0xEE, 0x44, 0xBF, 0xF2,
+ 0xC8, 0xEB, 0xDE, 0x50, 0xF0, 0xD1, 0x47, 0x92, 0x35, 0x6B, 0x03, 0xE9, 0xBE, 0x85, 0xFC, 0xBD,
+ 0x32, 0x1C, 0x03, 0xD3, 0x1E, 0xC8, 0xE2, 0xB9, 0x79, 0x32, 0x09, 0xAC, 0x70, 0x3F, 0x05, 0xD5,
+ 0xB1, 0x4B, 0xA7, 0x55, 0x14, 0xCE, 0xC7, 0x43, 0x8A, 0x87, 0x42, 0x3D, 0xDB, 0xC6, 0xA9, 0x43,
+ 0x8E, 0xBD, 0x06, 0x15, 0x75, 0x86, 0x9E, 0x66, 0xEE, 0x5A, 0x2E, 0x78, 0x25, 0x2B, 0xC3, 0xC8,
+ 0x7F, 0xF7, 0xE9, 0xD2, 0xA6, 0x1B, 0x7F, 0x5B, 0x53, 0xAC, 0xBA, 0x8D, 0x3B, 0x2C, 0x5F, 0x49,
+ 0xB5, 0x4A, 0x6B, 0x60, 0xEB, 0xD6, 0x49, 0x5B, 0xBB, 0x44, 0x35, 0x3C, 0xA7, 0xA3, 0xAE, 0x59,
+ 0x87, 0x40, 0xE5, 0x7C, 0x34, 0x8F, 0x12, 0xCA, 0x97, 0xE4, 0xD4, 0xC7, 0x92, 0x0C, 0x37, 0xAB,
+ 0x8F, 0xC5, 0xC5, 0x4F, 0x21, 0xD3, 0x4B, 0xA7, 0xDA, 0x6F, 0xC3, 0x49, 0xC4, 0x1B, 0x4E, 0xA2,
+ 0x54, 0x9D, 0x44, 0x1E, 0xD9, 0x43, 0xA5, 0xDD, 0x87, 0x26, 0xCC, 0xD7, 0x93, 0x94, 0x93, 0x70,
+ 0x4F, 0xED, 0xEA, 0xD6, 0xC2, 0x5D, 0x02, 0x2D, 0x87, 0xF2, 0xF4, 0x9D, 0x89, 0xF1, 0x8F, 0x3E,
+ 0x99, 0x1F, 0x0D, 0x93, 0x45, 0x4E, 0x67, 0xF1, 0x43, 0x0D, 0x99, 0x70, 0x97, 0x35, 0xAE, 0x53,
+ 0x5C, 0x1F, 0x63, 0x25, 0x1F, 0x69, 0xFC, 0xDE, 0xAB, 0x81, 0xB4, 0x71, 0x3B, 0x65, 0xA9, 0xCE,
+ 0x72, 0xC3, 0x65, 0x60, 0x65, 0x05, 0x91, 0xD7, 0x47, 0xCB, 0x47, 0x80, 0x66, 0x50, 0xE0, 0x15,
+ 0x58, 0x7D, 0xC7, 0x3E, 0x96, 0x5F, 0x38, 0x8D, 0x46, 0xA4, 0xB1, 0xDD, 0xE5, 0x39, 0x0D, 0x1F,
+ 0xC3, 0xB8, 0x66, 0xB5, 0x3C, 0xF5, 0x51, 0xE3, 0x32, 0x64, 0xE6, 0x0C, 0x1A, 0x3D, 0x8B, 0xE7,
+ 0x76, 0x1F, 0x04, 0xD0, 0x4D, 0x55, 0x6E, 0x54, 0x07, 0xBF, 0x43, 0x6A, 0x9A, 0x1C, 0x69, 0x3B,
+ 0x0D, 0xA0, 0xC0, 0x78, 0x30, 0x30, 0x9E, 0x55, 0x38, 0xD4, 0xB0, 0xCF, 0x2D, 0x15, 0x19, 0x3E,
+ 0x65, 0x8C, 0xCA, 0xF9, 0x7A, 0x6F, 0x54, 0x56, 0x00, 0x7A, 0xE2, 0xA4, 0x87, 0x86, 0x57, 0x68,
+ 0x1C, 0x6F, 0x66, 0x44, 0x2C, 0x0D, 0x23, 0x8A, 0x1E, 0x1E, 0x17, 0xDB, 0xE6, 0x16, 0xFD, 0x9B,
+ 0x16, 0x37, 0x4E, 0x18, 0x49, 0xE7, 0x1B, 0x6E, 0x9C, 0x6F, 0xEA, 0x3E, 0x6C, 0xD4, 0xB3, 0x18,
+ 0x24, 0x45, 0x2E, 0x51, 0x05, 0x94, 0x57, 0x19, 0xCA, 0xF7, 0x42, 0x46, 0xF7, 0x13, 0xE2, 0xF5,
+ 0x6A, 0x16, 0x61, 0xDE, 0x1A, 0xA4, 0xF4, 0xEE, 0x69, 0x0A, 0x2E, 0xF2, 0x0D, 0x38, 0x26, 0xDE,
+ 0x9B, 0x5B, 0x60, 0x9C, 0xB7, 0x40, 0xE6, 0x53, 0x56, 0x1F, 0xA6, 0x4C, 0xAF, 0x8F, 0x52, 0xE7,
+ 0x54, 0x06, 0x69, 0x53, 0xEB, 0x63, 0x5C, 0x6B, 0x1A, 0xA3, 0x12, 0xAD, 0xBA, 0x48, 0xD8, 0x04,
+ 0x3F, 0xA7, 0x50, 0x03, 0x2C, 0x2B, 0xA5, 0xFE, 0x13, 0xB8, 0xCE, 0xD8, 0xBE, 0xB5, 0x5E, 0xA0,
+ 0x1D, 0xCD, 0x69, 0xE9, 0xCD, 0xED, 0xC7, 0xE2, 0xAF, 0x41, 0xCF, 0x39, 0xF8, 0xD6, 0x88, 0x3E,
+ 0x4E, 0xDC, 0xC8, 0x5D, 0x5F, 0x01, 0xE2, 0xC3, 0xD5, 0x18, 0x78, 0xFB, 0x20, 0x80, 0xA5, 0x79,
+ 0x9C, 0xD9, 0x4E, 0x10, 0xAC, 0xAA, 0xCD, 0x41, 0x17, 0x1B, 0x6C, 0x05, 0xBF, 0x7C, 0x66, 0xFB,
+ 0x70, 0xA0, 0xD6, 0xF0, 0xD1, 0x2E, 0xA1, 0x9D, 0x58, 0x25, 0xD3, 0xE2, 0xD5, 0x42, 0xE8, 0x50,
+ 0xC3, 0x9A, 0xD9, 0x30, 0x73, 0x8A, 0x36, 0xC3, 0x8F, 0x70, 0x0F, 0x85, 0xAB, 0x21, 0xC2, 0x6C,
+ 0x0E, 0xB3, 0xC3, 0x4B, 0x4C, 0x65, 0x42, 0xC9, 0x0C, 0x89, 0x8B, 0x5E, 0xAE, 0xC6, 0x58, 0x08,
+ 0x2D, 0x11, 0xD1, 0x69, 0x4D, 0x9F, 0x9D, 0x62, 0x38, 0x5A, 0x4D, 0xE3, 0xB4, 0xAB, 0x26, 0x2F,
+ 0xDB, 0x95, 0xB6, 0x94, 0xD9, 0xAC, 0x67, 0x1B, 0xC9, 0x63, 0x9C, 0x5B, 0xE5, 0x02, 0xA0, 0x7C,
+ 0x90, 0x1A, 0xF9, 0x97, 0xCA, 0xAA, 0xBB, 0xB7, 0x5A, 0x2C, 0x80, 0xF7, 0xB1, 0xB5, 0x7B, 0xAB,
+ 0x35, 0xA4, 0xCB, 0x4C, 0x2C, 0xBE, 0x4A, 0xE3, 0xC2, 0x60, 0xEE, 0xBF, 0x11, 0x69, 0x4F, 0x33,
+ 0x82, 0x54, 0xA8, 0xA8, 0x80, 0xDF, 0x10, 0xFF, 0x42, 0xB2, 0x74, 0x65, 0xCD, 0x7E, 0x3B, 0xC3,
+ 0x8B, 0x94, 0x0A, 0x53, 0x8A, 0xCC, 0x63, 0x4A, 0x91, 0xD3, 0x8A, 0xB5, 0x00, 0x34, 0x41, 0x7C,
+ 0x86, 0x4E, 0x02, 0x7A, 0xE9, 0xDC, 0x88, 0x7E, 0xB3, 0x6C, 0x9D, 0x52, 0x13, 0x9B, 0xC6, 0x6E,
+ 0x6C, 0x4E, 0x4B, 0xEE, 0xA6, 0x62, 0x8D, 0x6D, 0x5E, 0x54, 0x0F, 0xD1, 0xB8, 0xAD, 0x1D, 0x6A,
+ 0xAC, 0x33, 0x6D, 0xB0, 0x7D, 0xBC, 0x57, 0x4B, 0x9E, 0x4C, 0x1A, 0x7C, 0x6C, 0xF3, 0x58, 0xBF,
+ 0x8C, 0xAA, 0x25, 0x63, 0xAE, 0x6D, 0x44, 0x83, 0x9E, 0x2D, 0xC7, 0x1A, 0xF6, 0x56, 0x5B, 0x89,
+ 0x01, 0x53, 0x7F, 0x27, 0x86, 0x95, 0x04, 0x37, 0xD0, 0x31, 0x9D, 0xF5, 0x68, 0x9B, 0xF4, 0xF1,
+ 0xC1, 0x23, 0x58, 0x5B, 0x37, 0xC4, 0x9E, 0x0A, 0x2A, 0xB3, 0x62, 0x83, 0xF1, 0x49, 0xA7, 0x2F,
+ 0x64, 0x83, 0x81, 0x4D, 0x33, 0x0A, 0xCA, 0xE7, 0x06, 0x1B, 0x8C, 0x7B, 0x20, 0xE0, 0x01, 0xB2,
+ 0xF4, 0x83, 0x1C, 0x68, 0x02, 0xDA, 0xD7, 0xF5, 0xC0, 0xC5, 0x12, 0xFE, 0xC1, 0xD0, 0x66, 0xF0,
+ 0x0F, 0x7C, 0xDC, 0xE2, 0x2E, 0x80, 0x9F, 0xC3, 0x8C, 0x0D, 0xC0, 0x77, 0xA0, 0x07, 0x3F, 0x41,
+ 0xA9, 0x00, 0xAD, 0xC0, 0x8F, 0x22, 0xC7, 0xCF, 0xCF, 0xC3, 0x94, 0x7D, 0x68, 0x8B, 0x1A, 0x17,
+ 0x64, 0x0F, 0xAC, 0x24, 0x82, 0x3F, 0x06, 0xED, 0xF3, 0x40, 0x90, 0x26, 0xC8, 0x86, 0x18, 0xB4,
+ 0xD8, 0x52, 0x1F, 0xDB, 0x80, 0x37, 0x0E, 0xE1, 0x1F, 0x20, 0x79, 0xD0, 0xE2, 0xFA, 0x3F, 0x85,
+ 0x60, 0xE3, 0xFC, 0x1F, 0x77, 0x6B, 0x17, 0x7F, 0x7C, 0xB2, 0x0E, 0xE1, 0xCA, 0x12, 0xA8, 0x25,
+ 0xEF, 0x99, 0x76, 0x04, 0x3C, 0xE5, 0x58, 0x38, 0x00, 0x59, 0x26, 0xB4, 0x8E, 0x36, 0x6F, 0x18,
+ 0x6C, 0xFB, 0x4A, 0xFC, 0xC9, 0xF1, 0x2F, 0x44, 0x3F, 0x90, 0xFF, 0xF4, 0x29, 0xFE, 0x0B, 0x84,
+ 0x11, 0xFF, 0xA1, 0xB7, 0xF8, 0xF7, 0x26, 0x17, 0x7F, 0x37, 0xC5, 0xDF, 0x3F, 0x89, 0xBF, 0xCF,
+ 0xC4, 0xDF, 0xEF, 0xC4, 0xDF, 0x3F, 0x8B, 0xBF, 0x7F, 0xC1, 0xBF, 0xA9, 0xF8, 0x73, 0x3B, 0x68,
+ 0x9F, 0xAB, 0x49, 0x25, 0x43, 0x6E, 0x7B, 0xD7, 0xE1, 0xEE, 0x54, 0xAC, 0x3B, 0x18, 0xC8, 0x50,
+ 0x54, 0x90, 0x5D, 0x8F, 0xC4, 0x9F, 0x5C, 0x74, 0x5F, 0x88, 0x3F, 0xB7, 0xB0, 0xD4, 0xE2, 0x07,
+ 0x98, 0x2D, 0x8A, 0x7F, 0xC7, 0x57, 0xF2, 0x1F, 0x51, 0x14, 0xD6, 0x12, 0x60, 0x89, 0xBF, 0xC6,
+ 0xE2, 0x0F, 0x16, 0x9D, 0x92, 0x1D, 0x1A, 0xAF, 0xFF, 0x33, 0xEC, 0xB6, 0xE1, 0xFF, 0xC2, 0x6B,
+ 0xAE, 0x3B, 0xB9, 0x4D, 0x20, 0xA8, 0x56, 0x36, 0xB9, 0xE6, 0x23, 0xB0, 0xF7, 0x1B, 0x4E, 0x20,
+ 0xC4, 0x16, 0x04, 0x13, 0x98, 0x9C, 0xFF, 0x73, 0xA5, 0xBD, 0xDE, 0xFD, 0xFD, 0xC5, 0x1F, 0xA1,
+ 0xE0, 0x39, 0xFE, 0x98, 0x3C, 0x89, 0xA2, 0xF5, 0x41, 0x4A, 0x4E, 0xB1, 0x01, 0xDC, 0x37, 0xD8,
+ 0x44, 0x7A, 0x9B, 0x0C, 0xE8, 0x3F, 0xD6, 0xE1, 0xD7, 0xD5, 0xED, 0x68, 0x32, 0x48, 0xAF, 0x27,
+ 0xEF, 0x47, 0x74, 0x00, 0x7F, 0x06, 0x93, 0x11, 0x78, 0x83, 0xF2, 0xF4, 0xFA, 0x7A, 0x72, 0x47,
+ 0xAF, 0x46, 0xD1, 0x04, 0x9F, 0x4D, 0xCF, 0x44, 0xC9, 0x5B, 0x2C, 0x71, 0x3B, 0xFA, 0x6E, 0x02,
+ 0x9B, 0x13, 0x33, 0x6F, 0xA3, 0x49, 0x32, 0xEE, 0xA7, 0x3A, 0xF3, 0x19, 0xA4, 0x27, 0x22, 0x0F,
+ 0x5C, 0x89, 0x0B, 0xD8, 0x9B, 0x57, 0x10, 0x83, 0xE9, 0x2F, 0xDF, 0x11, 0x7C, 0xE3, 0x7D, 0x63,
+ 0xED, 0xEF, 0xAB, 0xEB, 0x17, 0xAB, 0x31, 0xAE, 0x63, 0xC7, 0x18, 0x37, 0x9C, 0x50, 0x8C, 0xD7,
+ 0x4C, 0xA4, 0x29, 0xB2, 0x30, 0x81, 0xD3, 0x26, 0x74, 0xFA, 0x40, 0x40, 0x8A, 0x8D, 0x3B, 0x5A,
+ 0x57, 0x29, 0xDB, 0x2F, 0xDB, 0xC0, 0xFB, 0xE5, 0x10, 0x79, 0xE2, 0xD5, 0x5A, 0x75, 0x8F, 0xD8,
+ 0x3D, 0x7C, 0x7D, 0x04, 0x3C, 0x19, 0x5A, 0x6E, 0x8D, 0xF0, 0x5F, 0xA4, 0x4E, 0xF2, 0x29, 0x7A,
+ 0xE8, 0x37, 0x40, 0x1B, 0xC9, 0xF5, 0x1B, 0x7E, 0x8B, 0x52, 0xB3, 0x24, 0x56, 0xEF, 0xE0, 0x03,
+ 0xF1, 0x2C, 0x44, 0xA0, 0xE9, 0x0A, 0xE3, 0xC2, 0xE4, 0xD9, 0x56, 0x67, 0x5B, 0xFE, 0x18, 0x88,
+ 0x50, 0x7B, 0x3E, 0x66, 0x65, 0x08, 0xEF, 0xD3, 0x40, 0x16, 0x13, 0x7A, 0x10, 0xA4, 0x41, 0xEE,
+ 0x03, 0xF7, 0x38, 0x37, 0x11, 0x30, 0x3D, 0x31, 0x86, 0x82, 0x4D, 0x45, 0x23, 0xF3, 0x80, 0x7C,
+ 0x49, 0xBD, 0xE2, 0x1C, 0xB4, 0x0C, 0x4E, 0x2C, 0x34, 0xED, 0x4D, 0xD9, 0x98, 0x06, 0x92, 0xA6,
+ 0xD6, 0x66, 0x90, 0x68, 0x91, 0x21, 0xCC, 0x2F, 0xC7, 0x5C, 0xA8, 0xD0, 0x03, 0x66, 0xB5, 0x38,
+ 0x87, 0x59, 0x5C, 0x4C, 0x26, 0x88, 0x7A, 0xE7, 0xA9, 0xF8, 0x01, 0x11, 0x51, 0xBD, 0x6A, 0x3E,
+ 0x2F, 0x53, 0xC6, 0x67, 0x4E, 0x10, 0xE8, 0x30, 0xB5, 0x13, 0xD4, 0x33, 0x11, 0x13, 0xBF, 0x2F,
+ 0x67, 0x4C, 0x26, 0x3A, 0x14, 0x8C, 0x6C, 0xEF, 0x4D, 0x32, 0x1C, 0xE3, 0xED, 0x02, 0x5F, 0x03,
+ 0xD8, 0xA1, 0x90, 0x5D, 0x4F, 0x3E, 0xA5, 0xA5, 0xE8, 0x88, 0x60, 0xEE, 0xED, 0x8D, 0x32, 0x57,
+ 0x7F, 0x49, 0x5F, 0xB9, 0xF3, 0x0B, 0x75, 0xC7, 0x06, 0xC9, 0x8D, 0xE1, 0x26, 0x44, 0x57, 0xCA,
+ 0x45, 0x74, 0x25, 0x54, 0x52, 0xA8, 0x2E, 0xD0, 0xDC, 0x52, 0x8F, 0x79, 0x79, 0x43, 0x31, 0x3C,
+ 0x68, 0xE9, 0x0F, 0x00, 0xC8, 0xF1, 0xED, 0xD7, 0x9A, 0x38, 0xD6, 0x42, 0x03, 0x49, 0xBD, 0xEA,
+ 0x62, 0x58, 0xEB, 0x22, 0xCC, 0x8C, 0x39, 0xB0, 0xC4, 0x2D, 0x79, 0xEB, 0x7D, 0x79, 0xFA, 0xFA,
+ 0x95, 0xB0, 0x87, 0xB9, 0xA2, 0xE8, 0x96, 0x9E, 0x0D, 0x79, 0x0A, 0xC6, 0x41, 0xFB, 0xD2, 0xFE,
+ 0xCD, 0x7C, 0xEF, 0x4A, 0x0B, 0xB8, 0x7D, 0x4A, 0xEE, 0x94, 0x05, 0xDC, 0x15, 0xBC, 0xA9, 0xB7,
+ 0x2D, 0x4A, 0xAD, 0x99, 0x52, 0x07, 0xD4, 0x09, 0x5E, 0x10, 0xFE, 0x73, 0xF2, 0x8F, 0x7F, 0x14,
+ 0x51, 0xB0, 0xBA, 0x0D, 0xF6, 0x58, 0xFF, 0xF8, 0xC7, 0xC9, 0x2A, 0xD0, 0xAB, 0x01, 0xE0, 0xFE,
+ 0x21, 0xD2, 0xF8, 0x22, 0x61, 0x20, 0x8B, 0xF9, 0x22, 0x68, 0xD9, 0x0D, 0x50, 0x75, 0x14, 0x34,
+ 0xC2, 0x6F, 0x9D, 0xFC, 0x82, 0x01, 0x4D, 0xFB, 0x00, 0xA4, 0x1B, 0xBE, 0x6F, 0x85, 0x2C, 0xA8,
+ 0xFC, 0xE2, 0x24, 0xBD, 0x1D, 0xA1, 0x44, 0xC7, 0x78, 0x8C, 0x13, 0x24, 0x8B, 0x75, 0xDF, 0xF2,
+ 0xB2, 0xB3, 0xBA, 0x12, 0x0B, 0xDB, 0x2A, 0x7D, 0x0A, 0xD2, 0x71, 0xEB, 0xDE, 0x2E, 0x99, 0x38,
+ 0x28, 0x88, 0x1B, 0xB4, 0xD4, 0x97, 0x0E, 0x79, 0xE7, 0x7B, 0x08, 0xD3, 0x38, 0x9A, 0x98, 0xCC,
+ 0x72, 0x8F, 0xF3, 0x1D, 0xE8, 0x95, 0x69, 0x3C, 0xCD, 0xEB, 0x23, 0xB7, 0x9D, 0x5C, 0xC3, 0x5E,
+ 0xC2, 0x03, 0xF1, 0xA8, 0xDE, 0x59, 0x22, 0x22, 0xDB, 0xCD, 0xF6, 0xBC, 0xD7, 0x00, 0x2D, 0x4D,
+ 0xC9, 0xC2, 0x58, 0x0C, 0x6E, 0x38, 0x2C, 0x8F, 0xC9, 0x2C, 0x46, 0x5B, 0xF3, 0xB5, 0x10, 0xC7,
+ 0x01, 0xD6, 0x61, 0xFB, 0xEC, 0xF4, 0xB0, 0x1D, 0x24, 0x63, 0x88, 0x5E, 0x0C, 0x0F, 0x64, 0xC2,
+ 0x43, 0x97, 0xC2, 0x88, 0xEC, 0x78, 0xFF, 0x87, 0x97, 0xA7, 0x6D, 0xFD, 0xA6, 0xE7, 0xF3, 0xC3,
+ 0xD3, 0xD3, 0xC3, 0xD7, 0x6D, 0x15, 0xBE, 0x00, 0x22, 0x3E, 0xEC, 0xBD, 0x80, 0x4C, 0x10, 0xB8,
+ 0x60, 0x23, 0xAF, 0x9C, 0xC5, 0x5C, 0xDE, 0xB0, 0xAB, 0xF8, 0x74, 0x0B, 0xCE, 0xC8, 0x25, 0x71,
+ 0x3A, 0x5B, 0x14, 0x5C, 0xC2, 0x33, 0xD9, 0x7E, 0x7E, 0x5F, 0x2E, 0x03, 0xF3, 0xCE, 0xEE, 0x20,
+ 0x69, 0x1D, 0xD2, 0xBE, 0xF7, 0x54, 0x5E, 0x13, 0x98, 0xAD, 0x0A, 0xA8, 0xBF, 0x4F, 0x35, 0x06,
+ 0xA8, 0xC7, 0x53, 0x97, 0x04, 0x77, 0x64, 0x30, 0x47, 0xE3, 0xC3, 0x86, 0x5C, 0xFF, 0xE5, 0x4D,
+ 0xB3, 0xF0, 0xF0, 0xDB, 0x2E, 0xB4, 0x9A, 0xB6, 0x0E, 0xB7, 0x60, 0x56, 0x10, 0x4B, 0xF9, 0x56,
+ 0x0A, 0x9D, 0x85, 0x82, 0xA6, 0x60, 0x0C, 0x76, 0x6D, 0x96, 0x37, 0xCA, 0x8B, 0x82, 0x4B, 0x52,
+ 0x5A, 0x87, 0x3D, 0xB8, 0xD0, 0xBC, 0x37, 0x96, 0x85, 0x2F, 0xE1, 0x57, 0x36, 0x86, 0xCA, 0xC7,
+ 0x15, 0xEB, 0xCF, 0xDD, 0xBA, 0xF5, 0xE7, 0xAE, 0x6B, 0xFD, 0xB9, 0xEB, 0x58, 0x7F, 0xCA, 0xFC,
+ 0xFD, 0x83, 0x93, 0xBD, 0xE3, 0xD3, 0x3D, 0x78, 0xEB, 0x14, 0xC8, 0x15, 0xC5, 0xD7, 0x98, 0x30,
+ 0xB9, 0x12, 0x26, 0x64, 0xB7, 0x6E, 0x25, 0x6A, 0x13, 0x0F, 0xCF, 0x4E, 0x55, 0x2A, 0x0C, 0x0B,
+ 0x93, 0x1B, 0x5E, 0x69, 0xC5, 0xAC, 0xC6, 0x67, 0x5A, 0x77, 0x61, 0x8E, 0x9F, 0x8D, 0x1D, 0xE5,
+ 0x0B, 0x33, 0xDB, 0x33, 0xA4, 0x3B, 0xEE, 0xAA, 0x92, 0xB7, 0x98, 0x26, 0x51, 0x80, 0x7C, 0x84,
+ 0xDF, 0xEA, 0x2D, 0xDC, 0xD7, 0xF0, 0x53, 0xAD, 0xE9, 0x4F, 0xF0, 0x53, 0x0E, 0x9D, 0xFC, 0x0C,
+ 0x3F, 0x6F, 0x13, 0x36, 0x46, 0xED, 0xFA, 0x73, 0xBF, 0xC5, 0x63, 0x8A, 0x47, 0xCB, 0x7F, 0x12,
+ 0x70, 0x40, 0x8D, 0xEF, 0x91, 0xF1, 0x06, 0xB4, 0x3C, 0x70, 0x4F, 0x7A, 0x73, 0x59, 0xC3, 0x39,
+ 0xAE, 0x5E, 0x58, 0xD2, 0xDF, 0x62, 0x7E, 0xE2, 0x25, 0x40, 0x29, 0x46, 0xB5, 0xAF, 0xB7, 0x9D,
+ 0x4A, 0x8C, 0x06, 0x2F, 0xBE, 0xA6, 0x18, 0x18, 0x15, 0x03, 0xCD, 0x39, 0xF6, 0x9C, 0x30, 0x09,
+ 0xC7, 0xAC, 0x84, 0x7A, 0x82, 0x5A, 0xA4, 0x3E, 0xA3, 0x49, 0xE1, 0x91, 0xE6, 0x40, 0xB7, 0x3E,
+ 0x3B, 0x65, 0xFF, 0x33, 0xB7, 0xE0, 0x26, 0x16, 0x94, 0xD7, 0x31, 0x95, 0x36, 0xB3, 0x78, 0x25,
+ 0xC1, 0x56, 0xAE, 0x7A, 0xAA, 0x54, 0x0A, 0xC2, 0x99, 0x6B, 0x95, 0x73, 0x35, 0xEF, 0x57, 0x71,
+ 0x15, 0x05, 0xC5, 0xB0, 0xD0, 0xD8, 0x55, 0x14, 0x4C, 0xEA, 0x2E, 0x08, 0xAA, 0x10, 0x86, 0x2F,
+ 0x86, 0x89, 0x23, 0xAF, 0xDA, 0x40, 0xB5, 0x0E, 0xB1, 0xA1, 0x22, 0x61, 0x49, 0x06, 0xB0, 0x96,
+ 0x26, 0x74, 0x02, 0x69, 0xEC, 0x80, 0x30, 0xC8, 0x65, 0x95, 0xC5, 0x6E, 0xF5, 0xA4, 0x0A, 0xCC,
+ 0x9F, 0x0E, 0xC5, 0x61, 0x8A, 0x6F, 0x53, 0x7E, 0xB3, 0x5D, 0xCE, 0x0C, 0xA3, 0x2E, 0x64, 0x88,
+ 0x6D, 0x28, 0x08, 0x3E, 0xB4, 0xDC, 0x86, 0x04, 0xB1, 0xF9, 0x74, 0x82, 0x71, 0xED, 0x90, 0x37,
+ 0x5E, 0xA1, 0x90, 0x49, 0x47, 0xFA, 0x6E, 0x5D, 0x0E, 0xC6, 0xF8, 0x82, 0x1A, 0xF6, 0xD0, 0xBC,
+ 0xA4, 0xE8, 0x34, 0x86, 0x09, 0x12, 0xC1, 0x55, 0xA7, 0x36, 0xB9, 0xD1, 0x01, 0xDD, 0x79, 0x7B,
+ 0xCC, 0xD9, 0x08, 0x3E, 0xCB, 0x2D, 0x6D, 0xB8, 0xD5, 0xB8, 0x70, 0x46, 0x57, 0xE7, 0x68, 0xFB,
+ 0x6B, 0xC5, 0xF7, 0xDE, 0x00, 0x9D, 0x72, 0xCB, 0xD7, 0x5D, 0x6B, 0xA5, 0xA6, 0x5B, 0xB6, 0x20,
+ 0x88, 0x6D, 0xCB, 0xB8, 0x06, 0xD8, 0x8D, 0x83, 0x92, 0x3F, 0xF5, 0xBB, 0xAE, 0x2E, 0xB6, 0xF8,
+ 0xAA, 0x01, 0x61, 0xA7, 0x27, 0x92, 0xEA, 0x5B, 0xDD, 0xBA, 0xD2, 0x57, 0x16, 0xBA, 0xC1, 0xA7,
+ 0x7E, 0x51, 0x97, 0x7A, 0x97, 0x2E, 0xD8, 0x94, 0x8A, 0xE1, 0x9B, 0x25, 0x06, 0x75, 0x83, 0xE6,
+ 0xAA, 0xF1, 0xA8, 0xB4, 0x30, 0x88, 0xCD, 0x7B, 0xB4, 0xEE, 0x73, 0xB4, 0xCA, 0x0E, 0x21, 0x88,
+ 0x4A, 0x84, 0x54, 0x11, 0xD1, 0x23, 0x58, 0x73, 0x90, 0x46, 0x8D, 0xE1, 0x3F, 0xEC, 0x61, 0x09,
+ 0x88, 0xB1, 0x12, 0x60, 0x2C, 0xA9, 0x36, 0x0A, 0x15, 0x98, 0x85, 0x6B, 0x63, 0xA2, 0xDA, 0x12,
+ 0x8A, 0x64, 0x1B, 0x02, 0x59, 0xE6, 0xCB, 0x2D, 0x00, 0x5B, 0x8C, 0x0B, 0xDC, 0x5D, 0x59, 0xA9,
+ 0x2C, 0x83, 0xD1, 0x73, 0xD6, 0xAC, 0x06, 0xCC, 0xD3, 0xA6, 0x97, 0x2D, 0x38, 0x08, 0xFA, 0x27,
+ 0x37, 0x09, 0xDC, 0xEB, 0x8E, 0x81, 0xD8, 0x3B, 0xE5, 0xF1, 0x02, 0x66, 0x3D, 0x1B, 0x75, 0x78,
+ 0x53, 0x14, 0xFA, 0x95, 0x4B, 0xB5, 0xB2, 0x3B, 0x20, 0x9D, 0xBB, 0x0D, 0x3A, 0x41, 0x67, 0x21,
+ 0xC4, 0xD0, 0x9B, 0x24, 0x3F, 0xCB, 0xA9, 0xDA, 0x6E, 0xEA, 0x9A, 0xE5, 0xDB, 0x9E, 0x44, 0xE9,
+ 0x50, 0xCF, 0xF6, 0x77, 0xEB, 0x20, 0x3A, 0xD8, 0x7E, 0xBD, 0x07, 0x17, 0x2C, 0xD7, 0xEA, 0x43,
+ 0x08, 0x40, 0x72, 0x85, 0x21, 0x33, 0xEC, 0x42, 0x50, 0xC4, 0x02, 0x09, 0xB4, 0x7F, 0x75, 0x6F,
+ 0xCA, 0x63, 0x39, 0x03, 0xDD, 0x32, 0xFE, 0xB4, 0x0C, 0x87, 0x87, 0xFB, 0x21, 0x2B, 0x29, 0x1A,
+ 0x3E, 0x2B, 0xD5, 0x71, 0x11, 0x7B, 0xEE, 0xD6, 0xE5, 0x16, 0x0C, 0xAF, 0xD5, 0xF5, 0xA6, 0xDA,
+ 0x00, 0xE0, 0x24, 0x73, 0x41, 0xD8, 0xF6, 0x96, 0xB7, 0x2F, 0x3B, 0x89, 0xA9, 0xC1, 0xFD, 0x52,
+ 0x00, 0x4C, 0xD1, 0x27, 0x1C, 0x9F, 0x4D, 0x55, 0x81, 0x22, 0xE5, 0x40, 0x87, 0xA5, 0x63, 0x52,
+ 0x73, 0x79, 0x42, 0x78, 0x99, 0x29, 0xFA, 0xDC, 0x7C, 0x68, 0x48, 0xA7, 0xA8, 0x12, 0x82, 0x3C,
+ 0x16, 0x2B, 0x60, 0xEF, 0x89, 0x60, 0x2F, 0x99, 0x35, 0xB1, 0x40, 0x57, 0xD8, 0x06, 0xA4, 0x6D,
+ 0xDA, 0x13, 0x9A, 0xB7, 0xF3, 0x85, 0xF5, 0x71, 0x5A, 0x02, 0x48, 0x96, 0x9C, 0xE6, 0x93, 0x47,
+ 0x78, 0xC8, 0x5F, 0xD1, 0x1B, 0x78, 0xA4, 0x3B, 0xCB, 0x1D, 0xD8, 0xD7, 0xF8, 0xE0, 0x29, 0x11,
+ 0xEC, 0x5A, 0xFB, 0x41, 0xF5, 0xD7, 0x7E, 0x4B, 0x17, 0xF7, 0xA0, 0xAF, 0x39, 0xD0, 0x93, 0x8C,
+ 0xED, 0x08, 0xDD, 0x43, 0xBB, 0x7C, 0xC2, 0xDB, 0x97, 0x32, 0x4D, 0xB7, 0x48, 0x23, 0x0D, 0x22,
+ 0x48, 0x93, 0x38, 0x69, 0x77, 0x25, 0xF9, 0x33, 0x53, 0x6E, 0x07, 0x52, 0x07, 0x28, 0xD5, 0xC1,
+ 0xA6, 0xA5, 0x4D, 0x56, 0xDB, 0x73, 0xA3, 0x9F, 0x5F, 0x1D, 0x2E, 0xF8, 0x1A, 0xE9, 0xCD, 0x49,
+ 0x49, 0xBE, 0x95, 0x9B, 0x72, 0x73, 0xF0, 0x23, 0xF9, 0x4A, 0x88, 0x63, 0x92, 0x51, 0xDB, 0x8E,
+ 0x62, 0xF6, 0xE0, 0x17, 0x64, 0x8D, 0x20, 0x42, 0x4D, 0x59, 0x69, 0xF9, 0xDC, 0xE9, 0xD0, 0xDA,
+ 0x29, 0x24, 0x8C, 0x2B, 0xEB, 0xD1, 0xB1, 0x1B, 0x88, 0x30, 0x18, 0x5A, 0xA3, 0x51, 0xCE, 0x4B,
+ 0x0A, 0x3D, 0x3A, 0x4C, 0x01, 0xD5, 0xE6, 0x20, 0x1A, 0xC3, 0xCB, 0xA1, 0x30, 0xB4, 0x25, 0xC8,
+ 0xCD, 0xC2, 0x96, 0x20, 0xD8, 0x40, 0xC7, 0x34, 0xD6, 0x64, 0xFD, 0xD1, 0x6B, 0xB0, 0xFE, 0xB8,
+ 0x51, 0x8A, 0x90, 0x5E, 0x38, 0xD7, 0x3D, 0x82, 0x30, 0x3F, 0xC1, 0x4D, 0xE7, 0x1D, 0x49, 0x78,
+ 0x39, 0xAB, 0x1B, 0xB8, 0x5A, 0xE0, 0x02, 0x7A, 0xBE, 0xC7, 0xD0, 0xA7, 0x8E, 0x69, 0xF3, 0x2C,
+ 0x79, 0x1B, 0x95, 0x0E, 0x9E, 0x0C, 0x86, 0x21, 0xC1, 0x86, 0x49, 0xAE, 0x91, 0xDE, 0x2C, 0xE2,
+ 0x1D, 0x2D, 0xBE, 0x90, 0xF2, 0x22, 0x69, 0x82, 0xE8, 0x50, 0xCB, 0x66, 0xD0, 0x2A, 0x8F, 0xE1,
+ 0x35, 0xF6, 0x79, 0xD4, 0xFB, 0x03, 0xA2, 0x9A, 0x51, 0x8F, 0x7E, 0xFB, 0x8D, 0x33, 0xC3, 0xBF,
+ 0xDF, 0x77, 0x9B, 0x3A, 0xFF, 0x89, 0x5E, 0xC0, 0x2D, 0xC4, 0x9B, 0xF5, 0xBA, 0x39, 0xEB, 0xA3,
+ 0xCC, 0xF2, 0xA3, 0xF7, 0xA3, 0x0C, 0x9D, 0x98, 0x88, 0x0F, 0xF5, 0xF5, 0xE6, 0x4C, 0x1D, 0xCF,
+ 0xB5, 0x71, 0xEA, 0x0F, 0xA4, 0xF4, 0x75, 0x41, 0x92, 0xCA, 0xDC, 0x95, 0x4F, 0xD1, 0xA3, 0xC5,
+ 0xAA, 0x76, 0xDB, 0xF0, 0x21, 0x54, 0x15, 0x75, 0xEB, 0xE7, 0xAD, 0xB3, 0xED, 0x1A, 0xEE, 0x1E,
+ 0x96, 0xA2, 0xA2, 0x28, 0x71, 0x0D, 0x5C, 0x71, 0x45, 0x63, 0xB6, 0x98, 0x7F, 0x20, 0x86, 0x8F,
+ 0xD5, 0x3F, 0xF0, 0x4C, 0x2D, 0x1F, 0x2A, 0x4A, 0x04, 0x25, 0x9E, 0xFD, 0x32, 0x27, 0x2F, 0xB6,
+ 0x6C, 0xF9, 0x9B, 0xBA, 0x06, 0xDC, 0x37, 0xC2, 0x8E, 0xE6, 0x8A, 0x64, 0x8A, 0xAE, 0x0C, 0x88,
+ 0xED, 0x0B, 0x18, 0x82, 0x26, 0x3C, 0x55, 0xE0, 0x20, 0xE6, 0xB8, 0x7B, 0xE0, 0x33, 0x15, 0x9E,
+ 0xDE, 0x2F, 0x84, 0x85, 0x46, 0xBD, 0xED, 0x8A, 0x06, 0x40, 0x4B, 0xEB, 0x96, 0x8D, 0xBE, 0x02,
+ 0x18, 0x47, 0xEB, 0x11, 0x01, 0x36, 0x60, 0x3A, 0x7C, 0xA0, 0xC3, 0x57, 0xA1, 0xB4, 0xAB, 0xEB,
+ 0xC0, 0x44, 0x0B, 0xA2, 0x84, 0x1A, 0x15, 0x14, 0x26, 0xD4, 0xE1, 0xEF, 0x8C, 0x58, 0xCA, 0x49,
+ 0xB5, 0xC2, 0x2B, 0x9C, 0x99, 0x68, 0x34, 0xA4, 0x22, 0x42, 0x12, 0xEA, 0x3C, 0xE0, 0x67, 0xBB,
+ 0xD6, 0x27, 0x4A, 0x1D, 0x14, 0xAD, 0x0B, 0x85, 0xF2, 0x92, 0x0B, 0x89, 0x3B, 0x2C, 0x08, 0xBF,
+ 0x0F, 0x35, 0xDB, 0x03, 0x65, 0x74, 0x1B, 0x58, 0x5E, 0xFC, 0x92, 0x88, 0x64, 0x40, 0x57, 0x5F,
+ 0x9F, 0x12, 0x05, 0xF2, 0xBE, 0xE4, 0xA2, 0xF9, 0x84, 0x35, 0xA9, 0x4B, 0x33, 0x52, 0x71, 0xA1,
+ 0xA8, 0x9F, 0xC7, 0x9D, 0x8A, 0x3A, 0xDD, 0x5A, 0x8A, 0xE5, 0x4A, 0x2D, 0x3B, 0x5A, 0x2B, 0x04,
+ 0xA0, 0xF9, 0x56, 0x61, 0x99, 0xFE, 0xE3, 0xA8, 0x4C, 0xCD, 0x41, 0x99, 0xE6, 0xC4, 0x64, 0xAA,
+ 0x87, 0x64, 0x72, 0x59, 0xE4, 0xFA, 0x36, 0x5C, 0x06, 0xCD, 0x8B, 0x53, 0xD7, 0x08, 0x4D, 0xBB,
+ 0x0E, 0xD5, 0xAE, 0xC6, 0xD1, 0xAC, 0x97, 0x8F, 0xBA, 0x83, 0x86, 0x8C, 0x76, 0xC9, 0xA6, 0x40,
+ 0x5C, 0xDC, 0x1A, 0xCA, 0xE9, 0xE1, 0x5A, 0x1A, 0xE4, 0x83, 0xDB, 0x11, 0x3D, 0xE7, 0x95, 0x97,
+ 0xD9, 0x95, 0xA1, 0x5F, 0x73, 0x5C, 0xB8, 0x54, 0x5E, 0x87, 0x9D, 0x45, 0x57, 0x92, 0x19, 0xB8,
+ 0xFF, 0xA6, 0x3C, 0x84, 0xBD, 0x1C, 0x79, 0x55, 0x5C, 0xE9, 0x75, 0x39, 0x02, 0x4F, 0x34, 0x80,
+ 0x77, 0x01, 0xCB, 0x76, 0xD3, 0x69, 0xED, 0x7C, 0xD6, 0x61, 0xE6, 0x52, 0xDD, 0x91, 0x16, 0x55,
+ 0x7B, 0x1F, 0xCB, 0x96, 0xE2, 0x30, 0xC1, 0x9F, 0xDA, 0x67, 0x1D, 0x91, 0x37, 0xFE, 0x99, 0x2A,
+ 0xAC, 0x93, 0xEF, 0x3B, 0xD3, 0xAE, 0xAF, 0xA7, 0x92, 0x2C, 0xD7, 0x93, 0x6D, 0xDC, 0x51, 0xD8,
+ 0xDC, 0x36, 0xA4, 0xD0, 0xD7, 0x97, 0xAD, 0xA5, 0xC8, 0x9D, 0xEA, 0xCC, 0xE9, 0xA2, 0x33, 0x94,
+ 0x42, 0x26, 0x9C, 0xA1, 0xA8, 0xC7, 0x16, 0xAE, 0x27, 0xD8, 0x53, 0x51, 0x6F, 0x3A, 0x5D, 0x48,
+ 0x02, 0xC4, 0xAA, 0x02, 0xA0, 0xB2, 0x19, 0x86, 0x19, 0x3D, 0x84, 0xB7, 0x17, 0x1C, 0xA5, 0x79,
+ 0x86, 0xBE, 0x3A, 0x9C, 0x32, 0x01, 0x51, 0x7B, 0xD3, 0x26, 0x90, 0x07, 0xAD, 0xA9, 0x30, 0x92,
+ 0x6B, 0xAB, 0x84, 0x0A, 0x94, 0x5F, 0x8B, 0x60, 0xE6, 0xD5, 0xD9, 0x01, 0xB5, 0xCD, 0x67, 0x9D,
+ 0x1E, 0x6A, 0xBA, 0xF1, 0x68, 0xB2, 0x18, 0xCE, 0xA8, 0xA1, 0xCA, 0x4C, 0x26, 0x5A, 0x9D, 0x06,
+ 0xF8, 0x24, 0x9C, 0x98, 0x5D, 0x69, 0xC2, 0xDC, 0x5E, 0xC8, 0x42, 0x7D, 0x04, 0x33, 0xA5, 0x14,
+ 0xAA, 0x25, 0x28, 0x85, 0x90, 0x50, 0xC8, 0xE0, 0xD5, 0x8F, 0x37, 0xDE, 0xD8, 0xF1, 0x4D, 0x7C,
+ 0x38, 0x2F, 0x9B, 0x24, 0xB1, 0x70, 0xAA, 0xE1, 0x93, 0x03, 0xBF, 0x99, 0xA4, 0x97, 0x89, 0x47,
+ 0x0B, 0xC4, 0xB3, 0x60, 0x55, 0xB6, 0x52, 0xEB, 0x5B, 0x6C, 0x84, 0xAD, 0xEE, 0x6B, 0xDA, 0x16,
+ 0xCC, 0xE6, 0x86, 0x60, 0xE8, 0x67, 0xCA, 0x69, 0x61, 0xD8, 0xEE, 0x7D, 0x0E, 0xAF, 0x1C, 0x5D,
+ 0x27, 0x09, 0x12, 0xDA, 0xA1, 0x23, 0x7C, 0xA5, 0x25, 0xC9, 0x6B, 0xB5, 0xA8, 0x3D, 0x39, 0x84,
+ 0xBA, 0x6C, 0x65, 0xC5, 0xFD, 0x16, 0x82, 0xC2, 0xAE, 0x6D, 0x20, 0xF6, 0xBF, 0xFC, 0x5B, 0x1F,
+ 0x13, 0x34, 0x64, 0x1E, 0x97, 0xF3, 0x34, 0x19, 0xB5, 0x55, 0xB6, 0x58, 0x62, 0xB5, 0x6F, 0xFF,
+ 0xBF, 0x96, 0x18, 0x40, 0x54, 0x5B, 0x63, 0xF9, 0xDA, 0x85, 0x5F, 0xA2, 0x0F, 0xC3, 0x5D, 0x74,
+ 0xD9, 0x5E, 0xCE, 0x5D, 0x36, 0x24, 0x56, 0x8F, 0x5F, 0x36, 0x71, 0xC7, 0xD7, 0x84, 0x8E, 0x78,
+ 0x9A, 0xD4, 0x8E, 0x35, 0x72, 0xD9, 0xBC, 0xF3, 0x88, 0x7D, 0x0F, 0xBD, 0x2E, 0xC1, 0x65, 0xCF,
+ 0x73, 0xC7, 0x12, 0x2A, 0x9B, 0x7A, 0x32, 0x18, 0xBD, 0xD4, 0xED, 0x25, 0x1A, 0xFD, 0x99, 0xF5,
+ 0x99, 0xE7, 0x10, 0x7B, 0xBD, 0x4A, 0x86, 0x17, 0x2C, 0x1B, 0xE7, 0x50, 0xFF, 0xB1, 0x2D, 0x0C,
+ 0x4D, 0x0E, 0xAD, 0x41, 0x09, 0x92, 0xBF, 0x3E, 0x34, 0xC7, 0x41, 0x78, 0x0D, 0x43, 0x9A, 0xD6,
+ 0x9F, 0x57, 0x0E, 0xE7, 0x85, 0x6D, 0xA5, 0x75, 0x5B, 0x52, 0xBC, 0x5B, 0x77, 0x39, 0xDA, 0x92,
+ 0x46, 0x7A, 0xED, 0x42, 0x2E, 0x7F, 0xC5, 0x0F, 0x22, 0x20, 0x90, 0xFA, 0x22, 0x08, 0x68, 0xFD,
+ 0x61, 0x3B, 0x2F, 0xB5, 0x24, 0x99, 0x50, 0xA8, 0xAF, 0x7E, 0xC5, 0xEA, 0x5F, 0x60, 0x10, 0x94,
+ 0x8D, 0x52, 0xE4, 0xAD, 0xD6, 0x93, 0xF7, 0x0D, 0xA8, 0x68, 0x7E, 0xC7, 0xE6, 0x97, 0x53, 0xB9,
+ 0xEE, 0x15, 0x7E, 0x45, 0xE7, 0xC7, 0x96, 0x2D, 0xDF, 0x38, 0xB8, 0xBE, 0x92, 0x89, 0xAB, 0x87,
+ 0xFD, 0x82, 0x52, 0xA5, 0x9B, 0x47, 0xE5, 0xBE, 0x21, 0xD7, 0xDA, 0xEE, 0xC6, 0xC6, 0xC0, 0xA0,
+ 0x42, 0x44, 0x51, 0x3A, 0x69, 0x6D, 0xB0, 0x47, 0x89, 0x71, 0x36, 0xA3, 0x61, 0xC8, 0x18, 0x75,
+ 0x72, 0xD9, 0x61, 0x66, 0x21, 0x01, 0x87, 0x0D, 0xFF, 0x54, 0x52, 0x0D, 0x2A, 0xE1, 0xF0, 0x2A,
+ 0x82, 0x9B, 0xFA, 0xD0, 0xFC, 0xF7, 0x5E, 0xF1, 0x26, 0x8A, 0x7C, 0x2F, 0x48, 0x19, 0x2F, 0x46,
+ 0xCA, 0x4A, 0xE8, 0x80, 0x9A, 0xD7, 0x4D, 0x01, 0x6C, 0xF6, 0xCD, 0x21, 0xEE, 0xDC, 0x20, 0xE1,
+ 0xC6, 0x97, 0xA5, 0x2C, 0x54, 0x27, 0xE3, 0x2C, 0xF1, 0xA5, 0x57, 0xF8, 0xD5, 0xD2, 0x16, 0x4C,
+ 0x1D, 0x73, 0x91, 0x06, 0xCB, 0x71, 0x51, 0x5B, 0xAC, 0x69, 0x5D, 0x1C, 0xD5, 0x24, 0x52, 0xF7,
+ 0x49, 0xE0, 0x4B, 0xA2, 0x59, 0x31, 0xBC, 0x8A, 0x78, 0x72, 0xB1, 0x7B, 0x37, 0x00, 0xC8, 0xAB,
+ 0x7D, 0x10, 0xC0, 0x01, 0x12, 0x55, 0xE5, 0x29, 0x3E, 0xAF, 0x99, 0x4E, 0x03, 0x64, 0x57, 0x3C,
+ 0x97, 0xEE, 0x06, 0x7D, 0x86, 0x96, 0xFB, 0x28, 0x82, 0x46, 0x6C, 0xCC, 0x9F, 0xA6, 0x0A, 0x78,
+ 0x6F, 0x4F, 0x67, 0x19, 0xA2, 0x3F, 0xDA, 0x0C, 0x7D, 0x5F, 0x98, 0xA1, 0x7B, 0xED, 0xCD, 0x11,
+ 0xB3, 0xD1, 0xD4, 0x7C, 0x79, 0x5D, 0xE9, 0x56, 0xCB, 0x4F, 0x28, 0x31, 0x11, 0x10, 0xD1, 0x1A,
+ 0xA2, 0xA7, 0x7E, 0x43, 0xF4, 0xFD, 0x46, 0x43, 0xF4, 0x6F, 0x6F, 0x89, 0xAE, 0x5F, 0x4F, 0x4A,
+ 0x7F, 0x3B, 0x93, 0xF3, 0x57, 0xD4, 0x94, 0x44, 0x05, 0x58, 0x53, 0xB1, 0x2B, 0x5B, 0x4C, 0xF3,
+ 0x0A, 0x4D, 0x45, 0xF7, 0x6D, 0x51, 0x71, 0x75, 0x69, 0x2A, 0x77, 0x6C, 0xCB, 0x19, 0x05, 0x73,
+ 0x53, 0xD9, 0x5D, 0xFA, 0x98, 0x88, 0x84, 0x1F, 0xA4, 0x19, 0x7D, 0x8A, 0x66, 0xF4, 0xDA, 0x0C,
+ 0x2F, 0x7E, 0xEE, 0x31, 0xD5, 0x56, 0x99, 0xAE, 0xA9, 0xF6, 0x73, 0x93, 0xBE, 0x88, 0xA9, 0x36,
+ 0x36, 0x7D, 0x47, 0xC9, 0xF3, 0x26, 0x53, 0xED, 0x37, 0x80, 0x8A, 0x40, 0x0D, 0xA4, 0x54, 0xF5,
+ 0x9D, 0x34, 0x18, 0x34, 0xDF, 0x5F, 0xA4, 0xC1, 0xE0, 0x3B, 0x4A, 0x7E, 0x51, 0x06, 0x83, 0x6F,
+ 0xC0, 0x60, 0xF0, 0x89, 0x34, 0x18, 0x34, 0xA5, 0x7E, 0x68, 0x30, 0x18, 0x7C, 0xE2, 0x1A, 0x0C,
+ 0xFE, 0x28, 0x7D, 0x2F, 0x70, 0x28, 0x0A, 0x46, 0x25, 0x7D, 0x95, 0xB1, 0x3E, 0xD3, 0xB7, 0x26,
+ 0x6D, 0xFA, 0xA3, 0x4E, 0x28, 0xB8, 0x33, 0x79, 0x4D, 0xCD, 0xCC, 0xE0, 0x1F, 0x61, 0x6A, 0x76,
+ 0xF3, 0xAC, 0x52, 0x77, 0xED, 0x86, 0x26, 0x7D, 0x69, 0x6A, 0x76, 0xF3, 0xEC, 0x7B, 0x4F, 0xE3,
+ 0x22, 0xD6, 0x5B, 0xC5, 0x12, 0x0D, 0x4E, 0xE7, 0x5F, 0xED, 0x8C, 0x4A, 0xAB, 0x4E, 0x1E, 0xD4,
+ 0x98, 0x67, 0x59, 0x32, 0xE2, 0x95, 0x92, 0x69, 0x4B, 0x29, 0xCA, 0xB4, 0xA5, 0x14, 0x83, 0x5F,
+ 0xAD, 0xCA, 0xA8, 0x48, 0x56, 0x4A, 0x54, 0x51, 0xE7, 0x72, 0xE6, 0x9A, 0x8D, 0x7D, 0xA9, 0x9B,
+ 0x8D, 0x7D, 0x71, 0xCD, 0xC6, 0xBE, 0x54, 0xCD, 0xC6, 0xBE, 0xF8, 0xCD, 0xC6, 0xBE, 0x54, 0xCD,
+ 0xC6, 0xBE, 0xF8, 0xCC, 0xC6, 0xBE, 0xF8, 0xCD, 0xC6, 0xBE, 0x34, 0x9B, 0x8D, 0x7D, 0x69, 0x32,
+ 0x1B, 0xC3, 0x2C, 0xA0, 0x21, 0xCC, 0x17, 0x15, 0xB7, 0x53, 0x32, 0xEF, 0xB2, 0x74, 0x56, 0x86,
+ 0xD1, 0x96, 0xD4, 0x0F, 0xB8, 0x73, 0x21, 0x2F, 0x2A, 0x22, 0xF9, 0x2E, 0xF7, 0x14, 0x2E, 0x0F,
+ 0x04, 0x6E, 0x00, 0x69, 0x64, 0x2D, 0x9C, 0xA4, 0x91, 0xB6, 0xF6, 0x75, 0x0A, 0x99, 0xCD, 0x89,
+ 0xA0, 0xA8, 0xFD, 0x2A, 0xB3, 0x0A, 0x40, 0xF7, 0x5B, 0x97, 0x97, 0x22, 0xEB, 0xF2, 0x32, 0x56,
+ 0x3A, 0x7B, 0x9F, 0xD9, 0x54, 0x36, 0x57, 0x90, 0x5E, 0x91, 0x11, 0xEB, 0xF7, 0xC3, 0x07, 0x56,
+ 0xF9, 0x8E, 0x2A, 0xCB, 0xFF, 0x58, 0xA6, 0xFE, 0xC4, 0xCA, 0xD4, 0xB3, 0xDF, 0x4C, 0xA6, 0x9E,
+ 0x95, 0x64, 0xEA, 0x0B, 0xF3, 0x3F, 0x4D, 0x62, 0x75, 0x2E, 0x65, 0x7C, 0x8C, 0xD5, 0xE4, 0xE8,
+ 0xDA, 0xAD, 0xAC, 0x0E, 0xA9, 0x8E, 0x47, 0x96, 0x2A, 0x85, 0xDC, 0x3E, 0xE9, 0x2D, 0x34, 0x3C,
+ 0xB3, 0x6B, 0xD8, 0x89, 0x78, 0x7E, 0xBA, 0xFC, 0x16, 0x67, 0x42, 0x62, 0x4F, 0x19, 0x42, 0x52,
+ 0xF4, 0xDE, 0xB8, 0xB6, 0x73, 0xC5, 0x31, 0x8A, 0x12, 0xA0, 0xFC, 0xA3, 0x0C, 0x5D, 0x95, 0x0C,
+ 0xED, 0x7F, 0x73, 0x9E, 0xF2, 0x07, 0x87, 0xA7, 0xDC, 0xD8, 0x5A, 0x88, 0xAB, 0xFC, 0xB6, 0x0C,
+ 0xCE, 0xBB, 0x06, 0x06, 0xA7, 0x2B, 0x8D, 0x62, 0xBF, 0x05, 0x93, 0xF3, 0xEE, 0xFF, 0x8B, 0xC9,
+ 0xF9, 0x71, 0x31, 0x26, 0xE7, 0xCD, 0xE2, 0x4C, 0xCE, 0xBB, 0x05, 0x99, 0x9C, 0x9C, 0x2D, 0xCE,
+ 0xE4, 0x7C, 0x79, 0x14, 0x93, 0xF3, 0xAB, 0x61, 0x72, 0x9E, 0x03, 0x4A, 0x6A, 0xE6, 0x21, 0x2E,
+ 0x58, 0x03, 0x9B, 0xF3, 0xC6, 0x65, 0x73, 0xA0, 0xA0, 0x4A, 0x5F, 0x88, 0xCD, 0xC1, 0xA6, 0x7F,
+ 0xA1, 0x70, 0x8C, 0x34, 0xB0, 0x39, 0x09, 0xD3, 0x2F, 0xBE, 0x17, 0x23, 0x38, 0x3F, 0x87, 0x4C,
+ 0x30, 0x3A, 0xA5, 0x94, 0x1E, 0x13, 0xAC, 0xCE, 0x90, 0x91, 0x1B, 0x26, 0x59, 0x9D, 0x84, 0x81,
+ 0x13, 0x15, 0x33, 0x4F, 0xE8, 0x6D, 0x6E, 0x10, 0x89, 0x25, 0xDA, 0x7E, 0x5E, 0xBA, 0x94, 0xA1,
+ 0x30, 0x97, 0x5C, 0xDB, 0x62, 0xFA, 0xEA, 0xAD, 0x0B, 0x2B, 0x14, 0xD5, 0xC5, 0xEB, 0xC6, 0xFD,
+ 0x50, 0xBD, 0x0F, 0xD5, 0xB7, 0x77, 0x4E, 0xF7, 0xDF, 0x6C, 0x9F, 0xC2, 0xD1, 0x28, 0xC4, 0x22,
+ 0x09, 0x87, 0x83, 0xB1, 0xC7, 0xC8, 0xC9, 0xCE, 0xF1, 0xE1, 0xAB, 0x57, 0xDA, 0xE4, 0x5C, 0x24,
+ 0xBD, 0x3A, 0xDC, 0xDE, 0x2D, 0x45, 0xCB, 0x05, 0xC7, 0xF1, 0x3E, 0xA6, 0xBB, 0xC1, 0x72, 0x07,
+ 0x30, 0x1D, 0xE7, 0x79, 0x43, 0x78, 0x8D, 0x1C, 0x92, 0xA4, 0xC8, 0x05, 0x06, 0xC7, 0x74, 0x68,
+ 0x5A, 0x98, 0xBC, 0x82, 0x8C, 0x88, 0x4B, 0x3B, 0x62, 0xF2, 0xDD, 0x44, 0x88, 0x3B, 0x3B, 0x84,
+ 0x5B, 0xFA, 0x1A, 0x3A, 0xF0, 0x61, 0x98, 0x59, 0x95, 0xBC, 0x86, 0x3E, 0x79, 0x01, 0xB9, 0x57,
+ 0x9F, 0xAA, 0xE5, 0x3D, 0xFC, 0xB4, 0xC5, 0x55, 0xEA, 0x0E, 0xA6, 0xDA, 0x87, 0x33, 0x4F, 0xCB,
+ 0x9F, 0xAA, 0xC8, 0x89, 0x93, 0xA6, 0x9F, 0x32, 0xBC, 0x82, 0x54, 0x09, 0x4D, 0x70, 0x61, 0x61,
+ 0xF6, 0x89, 0x48, 0x70, 0x60, 0x61, 0x5E, 0xDB, 0xEF, 0x9A, 0xEC, 0xB0, 0xE3, 0x0F, 0x6C, 0x2B,
+ 0xA7, 0xA9, 0x4F, 0xC5, 0xE0, 0xF9, 0xE1, 0xEE, 0x3B, 0x21, 0xAD, 0xD3, 0xCE, 0x9E, 0x5D, 0xE9,
+ 0x00, 0xD6, 0xE6, 0x0B, 0x3E, 0x3D, 0xA8, 0x05, 0xFA, 0xEE, 0xD3, 0x1F, 0x72, 0xA1, 0xC5, 0x19,
+ 0xF2, 0x89, 0x21, 0xF3, 0xBB, 0xDA, 0x94, 0xBD, 0x37, 0x3B, 0xFB, 0x94, 0xA9, 0x7E, 0xB4, 0xFE,
+ 0xEF, 0xFC, 0x42, 0x25, 0x60, 0x21, 0x9B, 0x60, 0x45, 0x69, 0x98, 0xEC, 0x98, 0xA2, 0x96, 0xE3,
+ 0x9A, 0xC4, 0x1B, 0x8A, 0x86, 0x5A, 0x40, 0x58, 0x2D, 0x4D, 0x9F, 0xB5, 0x24, 0xAA, 0xF9, 0x74,
+ 0x2C, 0xAC, 0x85, 0x9C, 0x50, 0x8F, 0xE2, 0xE1, 0x68, 0x94, 0x21, 0xE0, 0x47, 0x09, 0xE1, 0x06,
+ 0x6E, 0xD4, 0xC5, 0x59, 0x97, 0xB0, 0x26, 0xE6, 0xCC, 0x63, 0x62, 0xAE, 0x2B, 0x35, 0xAA, 0x4C,
+ 0x7D, 0xEB, 0x14, 0xFB, 0x52, 0x5B, 0x72, 0xA9, 0xBA, 0x57, 0xAC, 0xBD, 0xCF, 0xC0, 0x3C, 0x47,
+ 0xEC, 0xC9, 0xDA, 0x2B, 0x2C, 0x72, 0x0F, 0x82, 0xE4, 0xCD, 0x93, 0x0A, 0xB6, 0x96, 0x19, 0x94,
+ 0xDF, 0x67, 0xDD, 0x4A, 0x5C, 0x90, 0x53, 0x8C, 0x7D, 0xD3, 0xDE, 0xE8, 0x2C, 0x0A, 0x7B, 0x07,
+ 0xC6, 0xB6, 0xAD, 0x52, 0x2A, 0xC0, 0x68, 0xD1, 0x78, 0x06, 0x2E, 0x62, 0x45, 0x78, 0xAA, 0x8F,
+ 0xC2, 0xC7, 0xBE, 0x17, 0x01, 0x27, 0xE0, 0xFC, 0xB7, 0x22, 0xB4, 0xC2, 0x93, 0x36, 0xB8, 0xEE,
+ 0xCB, 0xF0, 0xAB, 0xD2, 0x7F, 0x7F, 0x32, 0x01, 0x55, 0x98, 0x98, 0x8A, 0x92, 0xD1, 0x9E, 0xE3,
+ 0x2E, 0x00, 0x1F, 0xB7, 0x10, 0x5F, 0x2E, 0x1B, 0xAD, 0xE6, 0x84, 0x5D, 0xE8, 0xD7, 0x9D, 0x84,
+ 0xB5, 0x71, 0x34, 0xCB, 0xA7, 0x0E, 0x72, 0x8B, 0x2C, 0xE7, 0x8E, 0x53, 0xA0, 0xCD, 0x05, 0x86,
+ 0x73, 0x8D, 0xC2, 0x9F, 0xA9, 0x5F, 0x60, 0x4B, 0xCD, 0x92, 0xB4, 0x46, 0x63, 0x40, 0x3F, 0x2C,
+ 0x0F, 0xB3, 0x51, 0xEB, 0x62, 0x53, 0x37, 0xF1, 0x31, 0xF0, 0xC7, 0x3F, 0x93, 0x36, 0x64, 0x51,
+ 0x7D, 0x97, 0x58, 0xD3, 0xF3, 0x1E, 0xF3, 0x3E, 0x96, 0xE6, 0x94, 0xAF, 0x67, 0x54, 0xAD, 0xB2,
+ 0xED, 0x02, 0xDB, 0x34, 0x33, 0x2D, 0x9B, 0x24, 0xE7, 0xA4, 0x93, 0x1E, 0xB3, 0xD3, 0xE7, 0x3C,
+ 0xAC, 0x0F, 0x0B, 0x6B, 0x34, 0x7E, 0x15, 0xA9, 0xF6, 0x98, 0xCD, 0x96, 0x5E, 0x8B, 0x11, 0x95,
+ 0xC5, 0xEF, 0x3A, 0x45, 0x31, 0xA3, 0x69, 0x1F, 0x74, 0x8E, 0xE2, 0x69, 0x6F, 0x6B, 0xC9, 0x9C,
+ 0x48, 0x98, 0xD6, 0x4B, 0x0A, 0x6D, 0x8A, 0x4E, 0x8F, 0x83, 0xDF, 0x03, 0xD3, 0xAD, 0xB1, 0xA8,
+ 0x2E, 0x84, 0x4E, 0x80, 0x1A, 0xC2, 0x41, 0x6B, 0x25, 0xC5, 0x76, 0xBB, 0x36, 0x30, 0xE6, 0x75,
+ 0x42, 0xA2, 0x88, 0x86, 0x8F, 0x9E, 0x8C, 0xC0, 0xB1, 0xF9, 0xDD, 0xA1, 0x3C, 0xC3, 0x9D, 0xFC,
+ 0x6A, 0xE8, 0x4D, 0xDB, 0xBB, 0x05, 0xF8, 0x62, 0x03, 0x70, 0xA2, 0x4C, 0x4D, 0x26, 0xAF, 0x13,
+ 0x7E, 0x03, 0xDB, 0xFB, 0xB3, 0x6B, 0xF5, 0xE6, 0x14, 0x22, 0x4D, 0xC6, 0x72, 0x4E, 0xA9, 0xC8,
+ 0xB5, 0x47, 0x79, 0xCC, 0x90, 0x2C, 0x4C, 0xCA, 0x31, 0x94, 0x44, 0x0B, 0x5E, 0x30, 0x34, 0xD1,
+ 0x0A, 0x45, 0x1D, 0x60, 0x20, 0xE6, 0x20, 0xA8, 0x5F, 0x65, 0x7C, 0x74, 0x76, 0xD5, 0xF7, 0x44,
+ 0x18, 0x6D, 0x24, 0xA3, 0x2C, 0xF6, 0x94, 0x5F, 0xA5, 0x6B, 0x98, 0x5A, 0x05, 0x81, 0x22, 0x61,
+ 0xF5, 0xED, 0x21, 0xAF, 0x46, 0x95, 0x43, 0x8C, 0x6D, 0xC5, 0x5C, 0x93, 0x44, 0x67, 0xF3, 0x9D,
+ 0x3B, 0x5F, 0xE6, 0x85, 0x95, 0x8B, 0x4E, 0x7D, 0x3F, 0xE2, 0x6B, 0x2E, 0xD0, 0xB0, 0x4D, 0x47,
+ 0x71, 0x43, 0x6A, 0x7D, 0x72, 0xEA, 0x35, 0xA0, 0xF4, 0x96, 0xB3, 0xFB, 0x81, 0x9A, 0xC1, 0xAD,
+ 0xAD, 0x96, 0xA6, 0x3D, 0x75, 0x9A, 0x68, 0x40, 0xC9, 0x83, 0x47, 0xA8, 0x13, 0x43, 0xEB, 0xE0,
+ 0x9C, 0xC5, 0x4E, 0x63, 0xC6, 0xA3, 0x78, 0x6D, 0xAD, 0x13, 0x3D, 0xF8, 0xE6, 0x50, 0x99, 0x7E,
+ 0x76, 0x01, 0x83, 0xFC, 0xBE, 0xD4, 0x88, 0x4E, 0xF4, 0xDE, 0xB2, 0x2A, 0xC5, 0x56, 0x37, 0x2F,
+ 0xE0, 0xE6, 0xBB, 0x55, 0x4F, 0x8D, 0x6A, 0x70, 0xAA, 0xF6, 0x2A, 0x83, 0x4D, 0x9B, 0x02, 0xEE,
+ 0x6B, 0x68, 0x1E, 0x38, 0x50, 0xE2, 0xCE, 0xDF, 0xE0, 0x9C, 0x25, 0xB7, 0xC6, 0x88, 0x87, 0x04,
+ 0xF5, 0x83, 0x55, 0x43, 0x78, 0x55, 0x3F, 0xE7, 0x20, 0x9B, 0x55, 0x8F, 0x95, 0x5D, 0x90, 0xA7,
+ 0xAB, 0x98, 0x85, 0xE1, 0x23, 0x4C, 0xDA, 0xD3, 0xA9, 0x7C, 0x2D, 0x68, 0xE1, 0xC3, 0x5D, 0xDC,
+ 0xB9, 0x45, 0xF7, 0xF0, 0xBF, 0x0E, 0xB3, 0x4A, 0xFE, 0x01, 0x83, 0x27, 0x0B, 0x99, 0xB1, 0x55,
+ 0xD9, 0x61, 0xCA, 0xFE, 0xE9, 0x84, 0x95, 0x24, 0x3C, 0x97, 0x0C, 0xBA, 0x73, 0x3E, 0xA3, 0x36,
+ 0x54, 0x72, 0xF2, 0xB5, 0xE5, 0x71, 0x11, 0x8E, 0xA0, 0x2A, 0x5A, 0xE7, 0x87, 0x82, 0x09, 0x15,
+ 0xDC, 0x66, 0x34, 0xB7, 0xEC, 0x3D, 0x2B, 0x59, 0xDD, 0x7E, 0xAA, 0x54, 0x68, 0x3C, 0x19, 0x95,
+ 0xCC, 0x16, 0x99, 0x48, 0x7D, 0x8D, 0x21, 0xD5, 0x97, 0xCF, 0xC4, 0x61, 0xAC, 0x56, 0xA7, 0x4C,
+ 0x1A, 0x16, 0x86, 0x9D, 0xCB, 0x18, 0xCD, 0x64, 0x30, 0x6C, 0xD0, 0x4B, 0x6D, 0x34, 0x26, 0xC6,
+ 0x6F, 0x39, 0x8A, 0x39, 0xB5, 0x14, 0x7B, 0x80, 0x75, 0x70, 0xD8, 0xEC, 0x9B, 0xBE, 0xCC, 0x37,
+ 0x64, 0x95, 0x80, 0x42, 0xAC, 0xE9, 0x65, 0xBE, 0x95, 0x15, 0xB8, 0x69, 0x54, 0xEA, 0xFE, 0x97,
+ 0x5F, 0xE5, 0x03, 0xAF, 0xA8, 0xDF, 0x4E, 0xEA, 0x31, 0x66, 0x42, 0x42, 0xC0, 0x64, 0x34, 0x21,
+ 0x79, 0x00, 0xE9, 0xCB, 0x88, 0x73, 0xC9, 0x25, 0x15, 0xF3, 0x82, 0x47, 0x06, 0x33, 0x63, 0x52,
+ 0x5C, 0xA6, 0x29, 0x1F, 0x05, 0xCA, 0x67, 0x03, 0x83, 0x09, 0x00, 0x75, 0x76, 0x99, 0x3F, 0xB0,
+ 0x0E, 0x4E, 0x5F, 0x19, 0x15, 0x08, 0xE3, 0x32, 0x2D, 0x18, 0x88, 0x3D, 0x15, 0x74, 0xA6, 0x23,
+ 0xC6, 0x80, 0x82, 0x3A, 0x7D, 0x11, 0x31, 0x06, 0x36, 0x7D, 0xC3, 0xC8, 0x6E, 0x93, 0x18, 0xE3,
+ 0x4E, 0xCA, 0x2D, 0x78, 0x72, 0x15, 0x90, 0x6D, 0x29, 0xB1, 0xB8, 0x63, 0xE4, 0x40, 0x4A, 0x2C,
+ 0x30, 0x99, 0x1C, 0x56, 0x34, 0x03, 0xDB, 0xAC, 0xA2, 0x19, 0x10, 0x49, 0x56, 0x33, 0x60, 0x3E,
+ 0x0F, 0xE4, 0xB7, 0xC8, 0x6F, 0x7A, 0x92, 0x67, 0xBB, 0x22, 0x65, 0xF8, 0x50, 0x96, 0x32, 0xE0,
+ 0x0B, 0x85, 0x01, 0x39, 0x2A, 0x49, 0x19, 0x5E, 0xC1, 0x6F, 0xFD, 0x26, 0x5B, 0x40, 0xDE, 0x1B,
+ 0x7D, 0xC7, 0x4B, 0xA3, 0xEF, 0x38, 0x76, 0x65, 0x04, 0x9F, 0xFD, 0xD2, 0x87, 0x17, 0x98, 0xAC,
+ 0x1B, 0x3D, 0x83, 0x8F, 0xEF, 0x97, 0x86, 0xE9, 0xD2, 0xF7, 0x4B, 0x26, 0xED, 0x2D, 0xAB, 0x3E,
+ 0xC2, 0x83, 0x10, 0xBA, 0x20, 0x4B, 0x6E, 0xE2, 0x28, 0x1D, 0x0E, 0xEB, 0xA9, 0xD8, 0x13, 0x8A,
+ 0x3E, 0x3E, 0x7A, 0x85, 0x11, 0xAF, 0x45, 0x77, 0x2D, 0x67, 0x96, 0xB6, 0xE3, 0x9F, 0x58, 0x93,
+ 0x33, 0x7A, 0xF4, 0x50, 0x91, 0x41, 0x34, 0x7B, 0x5E, 0xFB, 0x3D, 0x33, 0x99, 0x7D, 0x69, 0x26,
+ 0xFC, 0xC6, 0x41, 0xF4, 0x57, 0x56, 0x66, 0xBC, 0xAB, 0x8D, 0x21, 0x55, 0x9A, 0xB3, 0x5F, 0x31,
+ 0xF3, 0x46, 0x39, 0x49, 0x61, 0x57, 0x35, 0xBE, 0x65, 0xFB, 0x99, 0x09, 0xD5, 0x41, 0xD6, 0x78,
+ 0x51, 0xAD, 0x54, 0x14, 0x21, 0x7B, 0xB4, 0x7F, 0x45, 0x70, 0xF6, 0x0A, 0xEF, 0xF2, 0x72, 0x16,
+ 0x32, 0x0C, 0x57, 0x70, 0x58, 0x49, 0xEA, 0x9E, 0xB1, 0xF6, 0x0B, 0xD0, 0x98, 0xC7, 0x21, 0xFA,
+ 0xF0, 0xDC, 0x26, 0x1F, 0xE8, 0x36, 0x06, 0xD2, 0x40, 0x13, 0x73, 0x75, 0x78, 0xE6, 0x30, 0xD4,
+ 0xF3, 0xD4, 0x72, 0x6C, 0x62, 0x05, 0x0A, 0xE3, 0xF0, 0x73, 0xC8, 0xE4, 0x6B, 0x95, 0x33, 0x9F,
+ 0x08, 0xC7, 0x18, 0x3F, 0xB6, 0x82, 0xF7, 0x05, 0x9E, 0x74, 0xAA, 0x5F, 0x2A, 0x01, 0xD3, 0x57,
+ 0x7B, 0x18, 0x16, 0xB5, 0x00, 0xCA, 0x36, 0x2F, 0x01, 0x47, 0x9A, 0xC4, 0xEB, 0x48, 0x03, 0x16,
+ 0xFE, 0x45, 0x83, 0x87, 0x4D, 0x86, 0x82, 0xF1, 0xA6, 0x2B, 0x7D, 0x06, 0x34, 0xCE, 0x65, 0xA8,
+ 0x2A, 0x17, 0x5B, 0xE5, 0xB8, 0x59, 0x8F, 0x3D, 0x5E, 0x9E, 0x9E, 0xA4, 0x14, 0xD5, 0x09, 0xB2,
+ 0x32, 0x38, 0x68, 0x15, 0x1C, 0x07, 0x3E, 0x78, 0x38, 0x90, 0x90, 0x6F, 0x9C, 0x7B, 0xC0, 0x80,
+ 0xCE, 0x64, 0xBC, 0x5B, 0x1D, 0x36, 0x29, 0xE3, 0x31, 0x19, 0x46, 0x6D, 0x7C, 0x78, 0xE4, 0x2B,
+ 0x5E, 0x3C, 0xBF, 0xF3, 0xDE, 0xD5, 0x1B, 0x78, 0x4A, 0x34, 0xDB, 0x62, 0x65, 0xFE, 0x1F, 0x30,
+ 0x37, 0x5C, 0x06, 0xBC, 0x43, 0x54, 0x5C, 0xD6, 0x78, 0xA7, 0x02, 0xC5, 0x1D, 0x56, 0x92, 0x84,
+ 0x1B, 0x84, 0xE5, 0x9B, 0x5E, 0x80, 0x5F, 0xBE, 0xC5, 0xC2, 0x33, 0x16, 0x89, 0x9D, 0x00, 0x77,
+ 0xFD, 0x95, 0x15, 0x58, 0x43, 0xF7, 0x01, 0xD7, 0xF7, 0x30, 0x48, 0xE7, 0xAA, 0x64, 0xCD, 0x9A,
+ 0xB9, 0xB1, 0xA2, 0xD9, 0xC9, 0x40, 0x19, 0x47, 0x05, 0x6C, 0x20, 0xB8, 0x9F, 0xF4, 0xD1, 0x82,
+ 0x96, 0xF4, 0xF3, 0xB0, 0x8B, 0xB8, 0x23, 0x41, 0xD8, 0xA0, 0xDA, 0x2B, 0xA6, 0x2F, 0x9B, 0xDC,
+ 0x93, 0x8A, 0x06, 0xF7, 0xA4, 0x44, 0xB9, 0x27, 0x15, 0xD2, 0x5A, 0xB9, 0x3E, 0xC2, 0x3A, 0x40,
+ 0xE5, 0xAE, 0x2E, 0x45, 0x04, 0x35, 0x54, 0xC6, 0xBC, 0x00, 0x44, 0xED, 0x72, 0x6B, 0x98, 0xBD,
+ 0x16, 0xC4, 0xA3, 0x23, 0xB7, 0x54, 0xAD, 0x26, 0x41, 0xEA, 0x2E, 0x88, 0x41, 0x45, 0xE5, 0x96,
+ 0x67, 0x32, 0x68, 0x1E, 0xF5, 0x79, 0x40, 0x4B, 0xD6, 0x11, 0x0F, 0x22, 0xB0, 0xC7, 0x9C, 0x4A,
+ 0x2F, 0x37, 0xC1, 0xDF, 0xD6, 0xDB, 0xE5, 0x0D, 0xED, 0xF2, 0xD9, 0xED, 0xA2, 0x2D, 0xAF, 0x0D,
+ 0x3A, 0x1E, 0x11, 0x2F, 0x1F, 0xFA, 0x9E, 0x61, 0x43, 0x6E, 0x58, 0x76, 0x5C, 0x09, 0x07, 0xEB,
+ 0x71, 0xDE, 0xE5, 0xEF, 0x12, 0xC2, 0x7C, 0x40, 0x15, 0x97, 0x0E, 0x7E, 0x58, 0xA2, 0xB7, 0xC7,
+ 0x02, 0x64, 0x02, 0x33, 0x14, 0x15, 0xAD, 0x30, 0x48, 0x59, 0x9D, 0x33, 0xFA, 0x08, 0x6D, 0xE1,
+ 0xDB, 0x9E, 0x2E, 0x18, 0xA6, 0x7C, 0xF6, 0x5B, 0xB9, 0xF8, 0x94, 0x15, 0x1A, 0x2B, 0x85, 0xDF,
+ 0x50, 0x71, 0x88, 0xFC, 0x99, 0xE4, 0xB3, 0xEE, 0x98, 0x3C, 0x0A, 0x8C, 0x1D, 0x6F, 0xAA, 0x4A,
+ 0x98, 0x7C, 0x42, 0x1F, 0x17, 0x67, 0x93, 0x7E, 0x85, 0xE2, 0x8F, 0xFE, 0xC7, 0x8A, 0x3F, 0xA1,
+ 0xFA, 0xAA, 0x87, 0xC9, 0x3C, 0x64, 0xB5, 0x47, 0x35, 0xD9, 0xBC, 0xC7, 0x3C, 0x7F, 0x62, 0x73,
+ 0x1E, 0xEE, 0x94, 0xCC, 0x14, 0x4A, 0xE6, 0x35, 0x1B, 0x18, 0x7B, 0xEA, 0x98, 0xCC, 0x32, 0x7F,
+ 0x0A, 0x05, 0x4D, 0xFA, 0x7C, 0xFE, 0x54, 0x34, 0x7D, 0xC0, 0xF4, 0x88, 0xEA, 0xFC, 0xE9, 0xCF,
+ 0x0C, 0x2D, 0x70, 0x92, 0x02, 0xC3, 0x48, 0x49, 0x56, 0x55, 0x7D, 0xBD, 0x91, 0xCC, 0xEA, 0x73,
+ 0x46, 0xDE, 0x29, 0xF5, 0xDA, 0xCF, 0xA0, 0x5E, 0xFB, 0x02, 0xCC, 0xEA, 0xEC, 0x17, 0x29, 0xDF,
+ 0x30, 0x52, 0xE6, 0x66, 0xDF, 0xD4, 0xB9, 0xD9, 0x37, 0x2E, 0x37, 0xFB, 0xC6, 0xE1, 0x66, 0x45,
+ 0xFE, 0x94, 0xFC, 0x62, 0x58, 0xCF, 0x27, 0xF0, 0x4B, 0x34, 0x45, 0x7E, 0x30, 0x4C, 0xE8, 0x8F,
+ 0xEA, 0x97, 0x50, 0xD3, 0xFD, 0xCA, 0xFC, 0xD1, 0xCA, 0x50, 0xBF, 0x80, 0x15, 0x4D, 0x92, 0x8A,
+ 0x35, 0x65, 0x14, 0x7E, 0x40, 0x11, 0x53, 0x37, 0x36, 0x96, 0xA9, 0x03, 0xBF, 0x55, 0xE1, 0x3F,
+ 0x6F, 0x6C, 0x80, 0x8D, 0x75, 0x5A, 0x79, 0x17, 0x52, 0xC0, 0x4C, 0x32, 0xA1, 0x2C, 0x9D, 0x15,
+ 0xDD, 0xC8, 0x3D, 0xCE, 0x16, 0x55, 0x53, 0x29, 0x7B, 0xF0, 0xAF, 0x0B, 0x0A, 0x84, 0x80, 0x69,
+ 0x78, 0xAF, 0xA4, 0x91, 0xBD, 0xF9, 0x22, 0x39, 0x03, 0xDD, 0x7F, 0xDD, 0x8B, 0xBD, 0x5C, 0xAF,
+ 0x4A, 0x0B, 0x7F, 0x51, 0x47, 0x83, 0x1B, 0x5F, 0xC8, 0x94, 0xAE, 0xDF, 0xD2, 0x7F, 0x44, 0xE2,
+ 0xD9, 0xD4, 0xDA, 0x0F, 0x52, 0xF6, 0xEC, 0x1D, 0xA4, 0xF1, 0x74, 0x37, 0x43, 0xD4, 0x0B, 0x06,
+ 0x23, 0xD4, 0x16, 0xF1, 0x5A, 0x84, 0x38, 0x63, 0x00, 0x4F, 0xA0, 0x8F, 0x19, 0x33, 0x82, 0x01,
+ 0x36, 0x41, 0x42, 0x71, 0xFA, 0x8B, 0x1C, 0xE0, 0x5F, 0xF5, 0x04, 0x1C, 0x6D, 0x38, 0xC8, 0x99,
+ 0x3A, 0xC8, 0x61, 0x82, 0xF3, 0x9E, 0x15, 0x2C, 0x77, 0xE3, 0x3D, 0xD0, 0x7E, 0xC0, 0x03, 0x2D,
+ 0x9C, 0x81, 0x0B, 0xCA, 0x85, 0x5E, 0x33, 0x7A, 0x78, 0x60, 0xE9, 0xE7, 0x36, 0x17, 0xF0, 0x55,
+ 0x50, 0xC5, 0xA7, 0x2E, 0x04, 0xC5, 0x6E, 0x8A, 0xA2, 0xAF, 0x88, 0x42, 0xE4, 0xD9, 0x12, 0x8B,
+ 0x4D, 0x71, 0x36, 0x12, 0x40, 0x19, 0xFF, 0x1B, 0x81, 0x08, 0x00, 0x87, 0xC4, 0xCD, 0x7C, 0xC2,
+ 0xEE, 0xB9, 0x45, 0xA5, 0x99, 0x6A, 0xA4, 0x85, 0x1F, 0x7A, 0xE3, 0x69, 0x6D, 0x54, 0x4A, 0x9E,
+ 0x31, 0x43, 0xCF, 0xE3, 0xF3, 0x01, 0xF8, 0x99, 0xCD, 0xF7, 0x01, 0x98, 0xEB, 0x5E, 0xD9, 0x44,
+ 0x38, 0xA0, 0x40, 0x15, 0x4C, 0x40, 0x25, 0x49, 0xFD, 0x10, 0x52, 0xDB, 0x72, 0x79, 0x43, 0x2A,
+ 0xF7, 0x14, 0x7A, 0xF8, 0xBB, 0x21, 0xF3, 0x29, 0x08, 0xEE, 0xCE, 0x27, 0xB3, 0x28, 0x84, 0x3C,
+ 0x6C, 0x16, 0xA2, 0x02, 0x3F, 0xFC, 0x6F, 0xEF, 0xF2, 0x6F, 0xCF, 0xA9, 0x3D, 0xF7, 0x73, 0x6A,
+ 0xFE, 0x48, 0xEA, 0x86, 0x7F, 0x7B, 0xFE, 0xDF, 0xE3, 0xDF, 0x70, 0x34, 0xFF, 0x11, 0x0F, 0xF7,
+ 0x08, 0x63, 0x27, 0xB6, 0xA8, 0xD0, 0x93, 0xA7, 0x96, 0x37, 0xD4, 0x7C, 0x50, 0xCC, 0xEA, 0xAB,
+ 0x43, 0x74, 0x66, 0x99, 0x5B, 0x83, 0x82, 0x2A, 0x7D, 0x21, 0x69, 0x22, 0x36, 0xFD, 0x8E, 0x11,
+ 0x4F, 0xEB, 0x53, 0xBB, 0xB7, 0x1A, 0xE1, 0x3E, 0x98, 0x1F, 0x33, 0xF1, 0xC7, 0xE4, 0x53, 0x02,
+ 0x1A, 0xBA, 0x74, 0xC4, 0x75, 0xD4, 0xC4, 0x62, 0x49, 0xF6, 0xD4, 0x52, 0xFF, 0x2E, 0xDD, 0xC2,
+ 0x43, 0x33, 0x4B, 0x57, 0xE8, 0x9A, 0xD3, 0x1B, 0x8E, 0xE1, 0xFE, 0x00, 0xBF, 0x41, 0x84, 0x4B,
+ 0x97, 0xFC, 0xCD, 0xB4, 0x4C, 0xA4, 0x30, 0x9C, 0x80, 0x0A, 0x75, 0x60, 0x5D, 0xAF, 0xF1, 0x8A,
+ 0xA3, 0xBF, 0xA0, 0xA8, 0xD8, 0x95, 0x90, 0xB4, 0xF5, 0xA7, 0x95, 0x15, 0xD4, 0xF8, 0x6F, 0xFD,
+ 0x7D, 0x32, 0x11, 0x6E, 0xEA, 0x42, 0x8B, 0xF6, 0x77, 0xF8, 0x85, 0xA9, 0x98, 0xF7, 0xA7, 0x8B,
+ 0xAD, 0xCD, 0xC9, 0xE4, 0xBB, 0x2D, 0x91, 0x55, 0x9A, 0xD7, 0x82, 0x73, 0x4A, 0xF8, 0x12, 0x06,
+ 0x39, 0xE3, 0x7A, 0x52, 0x9F, 0x36, 0x5B, 0x7F, 0x6F, 0x6D, 0x2E, 0x5D, 0x8D, 0x31, 0xBD, 0x28,
+ 0x60, 0xDB, 0x24, 0x10, 0xF4, 0xEC, 0xBB, 0xD6, 0x46, 0x6B, 0x03, 0xD8, 0x70, 0xF1, 0xC0, 0xD9,
+ 0x19, 0x4F, 0x87, 0xF1, 0x25, 0xFC, 0xD8, 0x1E, 0x82, 0x65, 0x75, 0x3C, 0x82, 0x5F, 0xCF, 0xC7,
+ 0x9C, 0x83, 0x2D, 0xF7, 0x11, 0xFC, 0xDC, 0x49, 0x72, 0x61, 0x00, 0x1D, 0x0F, 0x39, 0x7E, 0xE1,
+ 0x33, 0xE6, 0x23, 0xA0, 0x63, 0x57, 0xF8, 0xB5, 0xAB, 0x44, 0x8D, 0xF1, 0x8F, 0xF8, 0x25, 0x1F,
+ 0x8C, 0xFB, 0x44, 0xE1, 0xE7, 0x91, 0x30, 0x01, 0x47, 0xCB, 0x38, 0xF8, 0x38, 0xD1, 0xA6, 0x6B,
+ 0xF1, 0x2E, 0x7E, 0x9E, 0x22, 0xCF, 0x2F, 0x7E, 0x20, 0x27, 0x09, 0x78, 0x82, 0x3F, 0xA5, 0x29,
+ 0x3C, 0x3A, 0x0C, 0x28, 0x0B, 0x68, 0xB9, 0xC6, 0xF8, 0x62, 0x3F, 0x8C, 0xE9, 0x1E, 0xA3, 0x59,
+ 0x5F, 0x5E, 0xD2, 0x02, 0xBA, 0x18, 0xA3, 0xA8, 0x13, 0x36, 0xFA, 0x70, 0x8C, 0xCC, 0xAA, 0x88,
+ 0x35, 0xFF, 0xBB, 0xF5, 0xF5, 0xDF, 0x2F, 0x81, 0x59, 0x37, 0x3C, 0x76, 0xF8, 0x1A, 0x0C, 0xAE,
+ 0x61, 0xB3, 0x9E, 0x1D, 0xBF, 0x8A, 0xAF, 0x34, 0xA4, 0x5A, 0xB7, 0x29, 0xAC, 0x4F, 0x81, 0x6A,
+ 0xB4, 0x7F, 0x03, 0x27, 0xCC, 0x5E, 0xE8, 0xD8, 0xE2, 0x00, 0x00
+}; //bootstrap.min.js
+
+//Content of favicon.ico with gzip compression
+static const uint8_t favicon_ico[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xE0, 0x2D, 0x0C, 0x61, 0x04, 0x00, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6F,
+ 0x6E, 0x2E, 0x69, 0x63, 0x6F, 0x00, 0xED, 0x9B, 0x07, 0x68, 0xD5, 0x40, 0x18, 0xC7, 0xAF, 0x56,
+ 0xEB, 0x40, 0x70, 0xE0, 0x16, 0x37, 0x0E, 0xDC, 0x7B, 0x8B, 0x7B, 0xE3, 0x42, 0x05, 0x15, 0xB7,
+ 0x82, 0x5B, 0x04, 0x37, 0x28, 0x2E, 0xDC, 0x82, 0x22, 0x2A, 0xEE, 0x89, 0x7B, 0xE1, 0xC0, 0x05,
+ 0xA2, 0x20, 0x0E, 0xDC, 0x8A, 0x8A, 0x5B, 0xEB, 0xC0, 0x6D, 0xD5, 0x2A, 0x56, 0xED, 0xB3, 0xFE,
+ 0xEF, 0xFC, 0x3F, 0x1B, 0x62, 0x5E, 0x33, 0xFA, 0xF2, 0x92, 0x8A, 0x7F, 0xF8, 0xD1, 0xCB, 0xDD,
+ 0x77, 0xF7, 0x7D, 0xC9, 0xBB, 0xBB, 0x5C, 0x2E, 0xA9, 0x10, 0x51, 0x22, 0x5A, 0x64, 0xCF, 0x2E,
+ 0xFF, 0x16, 0x15, 0x83, 0xD3, 0x0B, 0x51, 0x53, 0x08, 0x51, 0xB4, 0xE8, 0xEF, 0xE3, 0x32, 0x39,
+ 0x84, 0x58, 0x8B, 0xBC, 0xCA, 0x95, 0x59, 0x5E, 0x4A, 0x88, 0x33, 0xB9, 0x85, 0x28, 0x03, 0x1B,
+ 0x54, 0x41, 0xCE, 0xEF, 0x7C, 0x87, 0xAA, 0x01, 0xEA, 0x09, 0x6B, 0xCA, 0x0D, 0x4A, 0xEB, 0xF2,
+ 0x9A, 0x82, 0xB6, 0xBA, 0xBC, 0xD2, 0xCA, 0xF6, 0x6F, 0x45, 0x81, 0xA5, 0x20, 0x97, 0x26, 0xAF,
+ 0x3D, 0xE8, 0x1E, 0x3C, 0x60, 0xD9, 0x12, 0x65, 0x6B, 0xAC, 0x82, 0x60, 0x2F, 0x88, 0xE1, 0x71,
+ 0x0F, 0x30, 0x88, 0xE9, 0x18, 0x96, 0x15, 0x0C, 0x79, 0x06, 0xC9, 0x75, 0xD6, 0x30, 0x3D, 0x10,
+ 0x8C, 0x65, 0x7A, 0x8D, 0x2A, 0xB3, 0xA6, 0x9D, 0x8C, 0x7D, 0x34, 0x98, 0x29, 0xD3, 0x2A, 0xCF,
+ 0xBA, 0xE4, 0x79, 0x6E, 0x05, 0x07, 0xC9, 0x56, 0x95, 0x67, 0x4F, 0xCB, 0xC1, 0x0E, 0xB0, 0x49,
+ 0xA5, 0xED, 0xAB, 0x01, 0x7F, 0x8F, 0xA5, 0x2A, 0x6D, 0x5F, 0x4D, 0xC0, 0x42, 0x89, 0x4A, 0xDB,
+ 0x53, 0x33, 0x90, 0x00, 0xE6, 0x82, 0x39, 0x32, 0xAD, 0xF2, 0xAC, 0xAB, 0xB3, 0xAA, 0x9B, 0xDC,
+ 0x4F, 0x66, 0xAB, 0x3C, 0xEB, 0x0A, 0xD6, 0x9F, 0x2F, 0xDB, 0x70, 0x50, 0xBF, 0x85, 0x3E, 0x7E,
+ 0x95, 0x67, 0x5D, 0x45, 0xC0, 0x62, 0xB0, 0x5E, 0xC2, 0x74, 0x31, 0x61, 0x5D, 0x43, 0x41, 0x92,
+ 0x8E, 0x51, 0xC2, 0x05, 0x95, 0xE1, 0x1C, 0xD3, 0x28, 0x75, 0xF3, 0x8C, 0x99, 0xFA, 0x83, 0x61,
+ 0xC2, 0x3B, 0x8D, 0x01, 0xD3, 0x84, 0x77, 0x9A, 0x0A, 0x16, 0x89, 0xF0, 0x4B, 0xF6, 0xE9, 0xB5,
+ 0xC2, 0x5C, 0x0B, 0x2C, 0xDA, 0x49, 0x9B, 0x2E, 0xC2, 0xBA, 0xA2, 0xC1, 0x69, 0x30, 0xDE, 0xC4,
+ 0x6E, 0x19, 0xD8, 0x65, 0x62, 0x33, 0x4E, 0xB6, 0xA5, 0xDA, 0xB4, 0xA7, 0x92, 0xE0, 0x13, 0xE8,
+ 0x90, 0x82, 0xCD, 0x06, 0x70, 0x2C, 0x54, 0x21, 0xEB, 0x7E, 0x52, 0x6D, 0x39, 0x93, 0x1C, 0xAF,
+ 0xF1, 0xA0, 0x52, 0x88, 0xF2, 0x3D, 0xE0, 0x6C, 0x88, 0xB2, 0x4A, 0xB2, 0xAE, 0x6A, 0xC3, 0xB9,
+ 0xA2, 0x78, 0x7E, 0xB1, 0x20, 0x9F, 0x41, 0xF9, 0x51, 0x70, 0xD3, 0x20, 0x3F, 0x1F, 0xEB, 0x1C,
+ 0x53, 0x6D, 0xA4, 0x4E, 0x85, 0x40, 0x1C, 0xCF, 0x33, 0x93, 0xAE, 0xEC, 0x14, 0x78, 0xAA, 0xCB,
+ 0xCB, 0x44, 0xDB, 0x38, 0x55, 0x37, 0x3C, 0xEA, 0xC5, 0xB9, 0x72, 0xB3, 0xEE, 0x7C, 0x2E, 0x83,
+ 0x8F, 0xBA, 0xEB, 0xB5, 0x99, 0xB6, 0xBD, 0x45, 0x78, 0xB5, 0x87, 0xED, 0x4E, 0xD2, 0xE4, 0xDD,
+ 0x05, 0x81, 0x60, 0x4C, 0x2C, 0x4B, 0x52, 0xB6, 0xE1, 0x57, 0x6E, 0xF0, 0x0A, 0xAC, 0x02, 0xC5,
+ 0x41, 0x36, 0xF0, 0x9C, 0xFE, 0xF2, 0x31, 0x6F, 0xA5, 0xB4, 0x51, 0xB6, 0xEE, 0xA8, 0x23, 0x18,
+ 0x44, 0x9F, 0x13, 0xE4, 0x6F, 0xCC, 0xF4, 0x10, 0xFE, 0x1D, 0xA4, 0x6C, 0xDC, 0x55, 0x2D, 0xFA,
+ 0x1A, 0x01, 0x7E, 0x30, 0x3D, 0x58, 0xFE, 0x55, 0x65, 0xEE, 0xAB, 0x0A, 0x7D, 0x8D, 0x03, 0x2F,
+ 0xC0, 0x77, 0x30, 0x40, 0xE6, 0xA9, 0x32, 0xF7, 0x15, 0xF4, 0xFF, 0x82, 0x7E, 0xFB, 0xCA, 0x71,
+ 0x10, 0x41, 0xFF, 0x25, 0xC1, 0x15, 0x23, 0x52, 0x31, 0xCF, 0x3A, 0x51, 0x79, 0x30, 0x4F, 0xA2,
+ 0xD2, 0x91, 0x57, 0x03, 0x70, 0x52, 0xE2, 0x70, 0x1D, 0xEE, 0x44, 0xE9, 0x38, 0xD6, 0xD6, 0x81,
+ 0xF5, 0x3A, 0xD6, 0xC9, 0x32, 0x65, 0xE3, 0x9E, 0x86, 0xEB, 0xD6, 0xAE, 0xCF, 0xC1, 0x33, 0x6D,
+ 0x9E, 0xB2, 0x71, 0x4F, 0x9B, 0x35, 0x7E, 0x1B, 0x6A, 0xF2, 0xEB, 0x83, 0xA7, 0x7F, 0xEE, 0x11,
+ 0xEE, 0x69, 0x0B, 0x7D, 0x34, 0x32, 0x28, 0xAB, 0x27, 0xCB, 0x94, 0x8D, 0x7B, 0x92, 0x6D, 0xBF,
+ 0xD0, 0x1C, 0xA7, 0xD7, 0x95, 0x3F, 0x89, 0xA0, 0xFF, 0x29, 0xBC, 0xD7, 0xC4, 0x44, 0xD8, 0xBF,
+ 0xBA, 0xFE, 0x64, 0x56, 0x84, 0xAF, 0xBF, 0xD7, 0xFD, 0x6F, 0xB4, 0x85, 0xF1, 0x37, 0x46, 0xB8,
+ 0xA7, 0x68, 0x3E, 0x13, 0xEC, 0x06, 0x7B, 0x74, 0xEC, 0x96, 0x65, 0x0E, 0xD6, 0xF9, 0x76, 0x34,
+ 0x11, 0x2C, 0x36, 0x61, 0xAA, 0x70, 0x4F, 0x37, 0xE4, 0x35, 0x36, 0xE1, 0x91, 0x70, 0x4F, 0x5E,
+ 0xFB, 0xFF, 0xE7, 0x54, 0x06, 0x54, 0x06, 0x7D, 0xDD, 0xDD, 0x27, 0x31, 0xD3, 0x52, 0x97, 0xE7,
+ 0x0D, 0xB7, 0x75, 0x0C, 0x5C, 0x12, 0x69, 0x57, 0x67, 0xC0, 0x4D, 0x97, 0xE7, 0x2E, 0x37, 0x75,
+ 0x03, 0xDC, 0x51, 0xCF, 0x84, 0xDE, 0xA9, 0x1D, 0xE8, 0xEC, 0x60, 0xFD, 0x19, 0x03, 0x6E, 0x81,
+ 0xFB, 0x20, 0xAF, 0xB0, 0x27, 0xE9, 0xAB, 0x8B, 0xF2, 0x9D, 0x7A, 0x4D, 0xE3, 0xBD, 0xB4, 0x95,
+ 0xB0, 0xA7, 0x6C, 0xBC, 0xF6, 0x8F, 0xD4, 0xFE, 0xB0, 0x3D, 0xB5, 0xA2, 0xCF, 0xE9, 0x22, 0xF5,
+ 0xCA, 0x04, 0x2E, 0x82, 0x0B, 0xA0, 0x94, 0xB0, 0xAE, 0x5C, 0xE0, 0x1E, 0xD7, 0x2F, 0xE5, 0x84,
+ 0x75, 0x49, 0x1F, 0xE7, 0xA5, 0x4F, 0xE5, 0x3B, 0x3C, 0xAA, 0xCB, 0x7D, 0xAB, 0xC3, 0x36, 0xFA,
+ 0x42, 0x61, 0xF0, 0x90, 0x6B, 0xA1, 0x6A, 0xC2, 0x9A, 0xF2, 0xD2, 0x47, 0xAC, 0xF2, 0x19, 0x3E,
+ 0x65, 0x00, 0x93, 0x41, 0x02, 0xF7, 0x45, 0xB3, 0x0A, 0x73, 0x95, 0x00, 0x8F, 0xC1, 0x2B, 0x8B,
+ 0xCF, 0x91, 0x59, 0xD9, 0x76, 0x82, 0xF4, 0xA5, 0x7C, 0x86, 0x57, 0xB9, 0x39, 0x1F, 0x26, 0x70,
+ 0x8D, 0x9F, 0xD1, 0xC4, 0xBE, 0x02, 0xFB, 0xCE, 0x1B, 0xD0, 0xDC, 0xC4, 0x36, 0x23, 0xDB, 0x4C,
+ 0x90, 0x3E, 0x5C, 0xDC, 0xA7, 0xAA, 0xC9, 0xEB, 0xF9, 0xC6, 0xC2, 0x3E, 0x60, 0x55, 0xF6, 0x9D,
+ 0x38, 0xB5, 0x5F, 0x9C, 0xB2, 0x7A, 0xB1, 0xCD, 0x57, 0xCA, 0x87, 0x7B, 0x8A, 0xE2, 0xBB, 0x95,
+ 0x44, 0xF0, 0xDE, 0xE4, 0x3D, 0x5D, 0x7D, 0xF0, 0x92, 0xFB, 0xDC, 0xDD, 0x53, 0xB0, 0x6B, 0xC2,
+ 0xB6, 0x12, 0x65, 0xDB, 0x61, 0xD8, 0x17, 0x36, 0x53, 0x76, 0xB0, 0x1D, 0x04, 0x38, 0x3E, 0x2B,
+ 0x85, 0xF0, 0xD9, 0x18, 0xBC, 0x06, 0x5F, 0x40, 0xBF, 0x10, 0xD7, 0xA2, 0x32, 0xDB, 0x08, 0xC8,
+ 0x36, 0x55, 0xDB, 0x91, 0x51, 0x6D, 0x3E, 0xD3, 0x06, 0x38, 0x5F, 0x14, 0x30, 0xB0, 0x69, 0x03,
+ 0xDE, 0xB1, 0x4F, 0x0F, 0x33, 0x7A, 0xF7, 0xCD, 0xBA, 0x01, 0xCE, 0x37, 0x75, 0x44, 0xE4, 0x14,
+ 0xAD, 0xD9, 0x1B, 0xFD, 0x01, 0xB6, 0x19, 0xDC, 0x9F, 0x3B, 0xCA, 0xBE, 0xCF, 0xF2, 0x31, 0x06,
+ 0xF7, 0xD7, 0x6D, 0xDA, 0x7D, 0x56, 0x8F, 0xD6, 0x48, 0x3B, 0xE4, 0xF5, 0x63, 0x0C, 0x73, 0x74,
+ 0xF7, 0x9B, 0xEE, 0xEC, 0xFB, 0x3F, 0xD5, 0x7C, 0x98, 0xAC, 0xCC, 0xB4, 0x4D, 0x92, 0x75, 0x55,
+ 0x1B, 0xDE, 0xA9, 0x24, 0xB8, 0xCA, 0x58, 0xDE, 0x82, 0xB6, 0x9A, 0xB1, 0xD0, 0x17, 0x7C, 0x66,
+ 0xD9, 0x2C, 0x5E, 0xF3, 0x28, 0xF6, 0xAB, 0xB7, 0xCC, 0xBF, 0x6A, 0xF3, 0x9E, 0x1E, 0x6E, 0xA5,
+ 0xE3, 0x9C, 0x11, 0xCF, 0x7D, 0xC6, 0x4A, 0xBC, 0xD7, 0x96, 0x65, 0x9F, 0xF8, 0xCA, 0x38, 0x17,
+ 0x82, 0x62, 0x2C, 0xAB, 0x48, 0xDB, 0x78, 0x59, 0xD7, 0xC1, 0xBA, 0x30, 0xDC, 0xCA, 0x08, 0x76,
+ 0x72, 0x8C, 0xEE, 0x63, 0xBC, 0x17, 0xC1, 0x48, 0xF0, 0x8D, 0xC7, 0x2B, 0xC0, 0x02, 0xA6, 0xF7,
+ 0xD1, 0x76, 0xA7, 0x85, 0x7B, 0x60, 0xA4, 0x54, 0x18, 0xB4, 0xD4, 0xC4, 0x7F, 0x9C, 0xEF, 0x21,
+ 0x12, 0x79, 0xBC, 0x11, 0x4C, 0xD5, 0xC4, 0xDF, 0xD2, 0xC1, 0x9A, 0xD4, 0x6D, 0xE5, 0xD2, 0xC4,
+ 0x7F, 0x90, 0x63, 0xF6, 0x27, 0x8F, 0x77, 0xCA, 0xF3, 0x09, 0xC6, 0xEF, 0xE0, 0xDB, 0x9A, 0x48,
+ 0x28, 0x87, 0x2E, 0xFE, 0x09, 0xBC, 0xFE, 0x3F, 0xB9, 0xF7, 0x38, 0x26, 0x18, 0xBF, 0xB2, 0xF5,
+ 0x9F, 0xB4, 0xF1, 0x3F, 0x02, 0x5D, 0xF9, 0x1E, 0xFD, 0x00, 0xE7, 0xD2, 0x43, 0x69, 0x28, 0x7E,
+ 0x79, 0xDD, 0xCF, 0xF1, 0x9D, 0x43, 0x1F, 0xEE, 0x41, 0x7E, 0xF3, 0x79, 0xFC, 0xD9, 0xB8, 0xD7,
+ 0x13, 0x67, 0xC2, 0x66, 0x8F, 0x9F, 0xE7, 0x43, 0x29, 0x3D, 0xA8, 0xCE, 0x75, 0x43, 0x27, 0xD0,
+ 0x0D, 0xF4, 0x90, 0x30, 0xDD, 0x89, 0x65, 0xD5, 0x95, 0xAD, 0xBF, 0x95, 0x97, 0xFD, 0xE4, 0x36,
+ 0xD9, 0x67, 0xE3, 0xB9, 0xD3, 0x0F, 0xCA, 0x0F, 0x4E, 0x80, 0xD7, 0xE4, 0xA4, 0xCA, 0xF3, 0xB7,
+ 0xD2, 0x71, 0x5E, 0x2F, 0x0A, 0x4A, 0x73, 0xFE, 0xE9, 0x47, 0xBA, 0x6A, 0x3E, 0x5D, 0xCB, 0xE5,
+ 0x83, 0x35, 0x83, 0xD1, 0xF3, 0xFD, 0x82, 0xE0, 0x7E, 0x8F, 0x09, 0x77, 0xA4, 0xAD, 0x0B, 0xCF,
+ 0xE7, 0x4E, 0x95, 0x8E, 0xEB, 0xB3, 0xA4, 0x3F, 0x24, 0xCF, 0xA1, 0x9F, 0x49, 0xA2, 0x41, 0xF9,
+ 0x42, 0x9F, 0xFC, 0x0E, 0xB2, 0x5F, 0xDF, 0xD7, 0xC5, 0x76, 0x83, 0xDF, 0xF1, 0xF6, 0xE1, 0x73,
+ 0xFE, 0x0C, 0x70, 0x5D, 0x67, 0x73, 0xDF, 0x27, 0x63, 0xA2, 0x14, 0x88, 0xD5, 0xC4, 0x75, 0x84,
+ 0x6B, 0xB3, 0x0C, 0xBA, 0xB9, 0xB5, 0x10, 0xD8, 0xAF, 0xB1, 0x8B, 0xF5, 0x78, 0xED, 0x1F, 0x54,
+ 0x69, 0x4D, 0xFC, 0x37, 0x4D, 0xD6, 0x95, 0x85, 0x34, 0xBF, 0x43, 0xAC, 0xAA, 0xEB, 0xBD, 0x82,
+ 0xF1, 0x27, 0xF2, 0xBB, 0xE1, 0x0C, 0x26, 0xF7, 0xB8, 0xE9, 0xD2, 0xD6, 0x87, 0xF1, 0xEB, 0xF7,
+ 0x49, 0x32, 0x86, 0x18, 0x9F, 0x3D, 0x41, 0xBC, 0xCF, 0xE3, 0xAF, 0xC8, 0xBD, 0xB7, 0x53, 0xF2,
+ 0x9A, 0xA7, 0x91, 0xF8, 0x03, 0x7F, 0xFA, 0x4F, 0xF2, 0xF7, 0xE4, 0x6F, 0x40, 0xD6, 0x34, 0xD2,
+ 0x7F, 0xB4, 0xE3, 0x37, 0x0B, 0xF7, 0x19, 0xAA, 0x19, 0x8C, 0xDF, 0x6B, 0x3E, 0x1D, 0xBF, 0xFA,
+ 0xF9, 0x33, 0xBD, 0xC1, 0xFC, 0x79, 0x40, 0xDA, 0xF8, 0x2C, 0xFE, 0x82, 0xE0, 0x81, 0xEE, 0xDE,
+ 0x74, 0xD3, 0xC2, 0xFD, 0xEB, 0x81, 0x85, 0xFF, 0x8D, 0x89, 0x84, 0xA2, 0xC1, 0x6A, 0x07, 0xEB,
+ 0x87, 0x35, 0x1E, 0xED, 0x19, 0xFE, 0x25, 0xF6, 0xF7, 0xF5, 0x5C, 0x9F, 0x3D, 0x4B, 0x09, 0xDA,
+ 0x6C, 0x50, 0x75, 0xFC, 0xA1, 0x18, 0x3E, 0xCF, 0xE6, 0xE1, 0x78, 0x6D, 0x6C, 0x42, 0x35, 0xDA,
+ 0xE6, 0xF4, 0xC9, 0xFE, 0x55, 0x6B, 0x7E, 0xA7, 0x7A, 0xD7, 0x26, 0xD7, 0xD4, 0x33, 0xA5, 0xF7,
+ 0xEA, 0x02, 0x3E, 0x58, 0xF8, 0xFE, 0x46, 0xCF, 0x67, 0xB5, 0xC7, 0xEB, 0xBD, 0xFE, 0xC7, 0xFF,
+ 0x5F, 0xFF, 0xE5, 0x91, 0x7E, 0x01, 0xE5, 0xF0, 0x8C, 0xE8, 0x2E, 0x3C, 0x00, 0x00
+}; //favicon.ico
+
+//Content of rtk-setup-wifi.png with gzip compression
+static const uint8_t rtkSetupWiFi_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xA8, 0xB4, 0x35, 0x64, 0x02, 0xFF, 0x72, 0x74, 0x6B, 0x2D, 0x73, 0x65,
+ 0x74, 0x75, 0x70, 0x2D, 0x77, 0x69, 0x66, 0x69, 0x2E, 0x70, 0x6E, 0x67, 0x2E, 0x67, 0x7A, 0x69,
+ 0x70, 0x00, 0x01, 0xB0, 0x16, 0x4F, 0xE9, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00,
+ 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x22, 0x00, 0x00, 0x00, 0x59, 0x08,
+ 0x02, 0x00, 0x00, 0x00, 0x15, 0x4E, 0xBE, 0x57, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74,
+ 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x49,
+ 0x6D, 0x61, 0x67, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xC9, 0x65, 0x3C, 0x00, 0x00, 0x16,
+ 0x52, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0xEC, 0x5D, 0x07, 0x58, 0x14, 0xD7, 0xDA, 0x66, 0x29,
+ 0x0B, 0x2E, 0x2C, 0x55, 0xA4, 0x28, 0xA0, 0x28, 0x2A, 0xD2, 0x04, 0x51, 0x73, 0x45, 0x4D, 0x8C,
+ 0x1A, 0x5B, 0x62, 0x54, 0x12, 0xC9, 0x0D, 0x76, 0x63, 0xC4, 0x5E, 0x40, 0xA3, 0x90, 0x6B, 0x8B,
+ 0xC4, 0x16, 0x44, 0x44, 0x10, 0x44, 0x14, 0xAC, 0x57, 0x12, 0x31, 0x12, 0x35, 0x6A, 0xEC, 0x28,
+ 0x4D, 0x10, 0x54, 0x94, 0xAA, 0x58, 0x50, 0x44, 0x8A, 0x94, 0x5D, 0x7A, 0xB9, 0x1F, 0x9C, 0x9F,
+ 0x71, 0x98, 0x99, 0x9D, 0x5D, 0x90, 0xE5, 0x67, 0xE5, 0xBC, 0xCF, 0x3E, 0x3C, 0xC3, 0xD9, 0xB3,
+ 0x67, 0x66, 0xBE, 0xF9, 0xDE, 0xF3, 0x95, 0x53, 0x86, 0x53, 0x5F, 0x5F, 0x2F, 0x87, 0x81, 0x81,
+ 0x21, 0x4D, 0xC8, 0x63, 0x11, 0x60, 0x60, 0x48, 0x1B, 0x8A, 0x58, 0x04, 0x9D, 0x13, 0x55, 0xB1,
+ 0x71, 0xC2, 0xB0, 0xB0, 0xAA, 0xB8, 0xBB, 0x75, 0x45, 0x45, 0x1C, 0x2E, 0x57, 0xC9, 0xDC, 0x5C,
+ 0xF9, 0xB3, 0x4F, 0x79, 0x8E, 0xD3, 0xE4, 0xB5, 0xB4, 0xB0, 0x70, 0xDA, 0x1C, 0x1C, 0xEC, 0x34,
+ 0x76, 0x36, 0xD4, 0x0B, 0x85, 0xC5, 0x9B, 0x36, 0x97, 0x5F, 0xBC, 0xC4, 0xA0, 0x0D, 0x3C, 0x1E,
+ 0x7F, 0xC9, 0x62, 0xD5, 0x99, 0x33, 0xE4, 0x38, 0x1C, 0x2C, 0x28, 0x4C, 0x33, 0x8C, 0xD6, 0x72,
+ 0xAC, 0xA2, 0xA2, 0x60, 0xDE, 0xFC, 0xEA, 0x87, 0xC9, 0x94, 0x72, 0xAE, 0xBD, 0xBD, 0xFA, 0xEA,
+ 0x55, 0x4A, 0x16, 0x03, 0x9A, 0x82, 0x09, 0x1C, 0x4D, 0x60, 0xA7, 0x11, 0xA3, 0xB5, 0x28, 0xF5,
+ 0xD9, 0x4B, 0xE7, 0x98, 0x92, 0x95, 0xA5, 0xF6, 0x81, 0x00, 0x8E, 0x92, 0x12, 0x96, 0x8F, 0x94,
+ 0x80, 0x3B, 0xAD, 0xCE, 0x64, 0xCA, 0xAA, 0xAB, 0xCB, 0x4E, 0x87, 0xD3, 0xCB, 0x79, 0xD3, 0xBF,
+ 0xC5, 0x1C, 0xC3, 0x34, 0xC3, 0x68, 0x1B, 0xD4, 0xE5, 0xE6, 0xD6, 0x97, 0x97, 0x33, 0x94, 0x17,
+ 0x16, 0x62, 0xE1, 0x60, 0x9A, 0x61, 0xB4, 0x51, 0x20, 0xCE, 0xE7, 0x33, 0x96, 0x0B, 0x8F, 0x1D,
+ 0xA7, 0x30, 0xAD, 0x36, 0x27, 0xA7, 0xF2, 0x4E, 0x14, 0x96, 0x18, 0xA6, 0x19, 0x46, 0xCB, 0x1F,
+ 0xB6, 0x86, 0x86, 0x92, 0x79, 0x7F, 0x06, 0x6B, 0x96, 0x97, 0x5F, 0x30, 0x7F, 0x41, 0x5D, 0x41,
+ 0xC1, 0xFB, 0x92, 0xA2, 0xA2, 0x42, 0x97, 0x45, 0xA5, 0x7B, 0x7C, 0xB0, 0xD0, 0x30, 0xCD, 0x30,
+ 0x5A, 0x0C, 0xD5, 0x39, 0x73, 0x18, 0xCB, 0x6B, 0x32, 0x33, 0xF3, 0xA6, 0x4E, 0x2B, 0x0B, 0x3F,
+ 0x53, 0x5F, 0x59, 0x09, 0xFF, 0x56, 0x34, 0xA6, 0xFB, 0x05, 0xC1, 0x87, 0x84, 0x27, 0x4F, 0x62,
+ 0xA1, 0xB5, 0x81, 0x1F, 0x81, 0x13, 0xFA, 0x9D, 0x2C, 0x3E, 0xAB, 0x2B, 0x74, 0x59, 0x5C, 0x19,
+ 0x1D, 0x2D, 0x52, 0x21, 0x78, 0x3C, 0x8E, 0x8A, 0x0A, 0xE1, 0x43, 0x72, 0x94, 0x95, 0x75, 0xFF,
+ 0x3A, 0xAB, 0x60, 0x60, 0x80, 0x25, 0x87, 0xAD, 0x19, 0x86, 0xE4, 0x0F, 0x5C, 0x5E, 0x73, 0xC7,
+ 0x36, 0x05, 0x23, 0x23, 0x51, 0xDF, 0xD7, 0x97, 0x95, 0x91, 0xE3, 0x34, 0x30, 0x6E, 0x82, 0x03,
+ 0x41, 0x58, 0x6C, 0x98, 0x66, 0x18, 0x2D, 0x7C, 0xE4, 0x5A, 0x5A, 0x3A, 0x87, 0x83, 0x59, 0x98,
+ 0x46, 0x41, 0xF9, 0xB9, 0xF3, 0x8C, 0xF9, 0x49, 0x0C, 0x4C, 0x33, 0x8C, 0x26, 0xD4, 0xD6, 0x56,
+ 0x25, 0x25, 0x95, 0xFF, 0x75, 0x0E, 0xD8, 0x52, 0x9D, 0x9A, 0x86, 0xCA, 0x14, 0xF4, 0xF4, 0x74,
+ 0x0E, 0x06, 0x49, 0x38, 0x7D, 0xB1, 0xBE, 0xA2, 0xA2, 0x32, 0x26, 0x06, 0x0B, 0x12, 0xD3, 0x0C,
+ 0x83, 0x91, 0x1F, 0xF5, 0xC2, 0x13, 0x27, 0x73, 0x47, 0x8F, 0x2D, 0x98, 0x39, 0xBB, 0xC8, 0xDD,
+ 0x43, 0x10, 0x1C, 0x5C, 0x5F, 0x5C, 0x4C, 0x7C, 0xA9, 0x60, 0x68, 0xA0, 0xEE, 0xBA, 0x4A, 0xC2,
+ 0x96, 0xAA, 0x93, 0x1E, 0x60, 0x71, 0x62, 0x9A, 0x61, 0x30, 0xA0, 0xF8, 0x97, 0xAD, 0x25, 0xDB,
+ 0xB6, 0xA3, 0x34, 0xBD, 0x42, 0x8F, 0x1E, 0x5D, 0x4F, 0x9E, 0xE0, 0x0E, 0x1D, 0x42, 0xAE, 0xA0,
+ 0x32, 0x66, 0x8C, 0x84, 0x4D, 0xD5, 0x64, 0x67, 0x63, 0x79, 0x62, 0x9A, 0x61, 0x50, 0x51, 0x19,
+ 0x19, 0x59, 0xF6, 0xFB, 0x1F, 0xC4, 0xBF, 0x5C, 0x2B, 0x4B, 0x8E, 0x8A, 0x0A, 0xA5, 0x4E, 0x9D,
+ 0xB0, 0x4C, 0xC2, 0xD6, 0xF0, 0x34, 0x11, 0x4C, 0x33, 0x0C, 0x06, 0x08, 0x4F, 0xFC, 0xB7, 0x19,
+ 0xEB, 0xA2, 0x63, 0xE8, 0x54, 0x11, 0x86, 0x84, 0x48, 0xDA, 0x9C, 0x02, 0xD6, 0x13, 0x4C, 0x33,
+ 0x0C, 0x1A, 0xAA, 0x12, 0x12, 0x9A, 0x99, 0xA3, 0xA2, 0xA2, 0x82, 0x39, 0xF3, 0x2A, 0xEF, 0x44,
+ 0xD5, 0x57, 0x57, 0x37, 0x38, 0x81, 0x4F, 0x9F, 0x16, 0x6F, 0xDE, 0x22, 0x3C, 0x7A, 0x4C, 0x52,
+ 0x96, 0xE9, 0xE8, 0x60, 0x91, 0x7E, 0x08, 0xF0, 0x42, 0x98, 0x8F, 0x10, 0x75, 0xF9, 0xF9, 0xF4,
+ 0x14, 0x7C, 0x4D, 0x56, 0x56, 0xA1, 0xCB, 0x22, 0x39, 0x45, 0x45, 0xB9, 0xDA, 0x5A, 0xB9, 0x16,
+ 0xCE, 0x49, 0x50, 0x34, 0x35, 0xC5, 0x52, 0xC5, 0x34, 0xC3, 0x68, 0x86, 0xFA, 0xAA, 0x2A, 0x91,
+ 0xDF, 0xD5, 0xD4, 0xB4, 0xA2, 0x41, 0xAE, 0xAD, 0x2D, 0x96, 0x2A, 0x76, 0x1A, 0x31, 0xA4, 0xA9,
+ 0x22, 0x5A, 0x5A, 0x5C, 0xFB, 0x41, 0x58, 0x0E, 0x98, 0x66, 0x18, 0x52, 0x04, 0x6F, 0xFA, 0xB7,
+ 0x78, 0xCF, 0x02, 0x4C, 0x33, 0x0C, 0x69, 0xEA, 0x07, 0x9F, 0xAF, 0xEA, 0xEC, 0x8C, 0xE5, 0x80,
+ 0x69, 0x86, 0x21, 0x45, 0xF0, 0x57, 0xAD, 0x94, 0xD7, 0xD2, 0xC4, 0x72, 0xC0, 0x34, 0xC3, 0x90,
+ 0x16, 0x54, 0x46, 0x7F, 0xCE, 0xFB, 0xC6, 0x11, 0xCB, 0x01, 0xD3, 0x0C, 0x43, 0x5A, 0x50, 0xB2,
+ 0xB2, 0xD4, 0xDC, 0xBA, 0x15, 0x6F, 0xD8, 0x88, 0x69, 0x86, 0x21, 0x35, 0x8E, 0x59, 0x5B, 0x6B,
+ 0xEF, 0xF7, 0xE7, 0xA8, 0xA9, 0x62, 0x51, 0x60, 0x9A, 0x61, 0x48, 0x01, 0x1C, 0x0E, 0xEF, 0x3B,
+ 0x27, 0x9D, 0x90, 0x43, 0xF2, 0x1A, 0x1A, 0x58, 0x18, 0x6D, 0x05, 0x3C, 0x3C, 0x8D, 0xF1, 0x1E,
+ 0xCA, 0xC3, 0x87, 0xF3, 0x17, 0xB9, 0x28, 0x59, 0x5B, 0x61, 0x51, 0x60, 0x9A, 0x61, 0x7C, 0xB0,
+ 0xC5, 0xEA, 0xD2, 0x85, 0xC8, 0x1F, 0xCA, 0x6B, 0x6A, 0x29, 0x1A, 0x1B, 0x29, 0xD9, 0xDA, 0xAA,
+ 0x7C, 0x3A, 0x52, 0xA1, 0x7B, 0x77, 0x2C, 0x1C, 0x4C, 0x33, 0x8C, 0x0F, 0x85, 0x82, 0x91, 0x91,
+ 0xC6, 0x4F, 0x6B, 0x95, 0x87, 0x3B, 0xC8, 0x29, 0x28, 0x60, 0x69, 0x60, 0x9A, 0x61, 0xB4, 0x3D,
+ 0xB8, 0x83, 0xED, 0xB5, 0xF7, 0xFA, 0x70, 0xD4, 0xD4, 0xB0, 0x28, 0x30, 0xCD, 0x30, 0xA4, 0xC3,
+ 0x31, 0x3B, 0x3B, 0x6D, 0x7F, 0x3F, 0xFA, 0xE2, 0x4E, 0x8C, 0x76, 0x00, 0xCE, 0x34, 0x76, 0x8E,
+ 0xDE, 0xD4, 0xCC, 0x4C, 0xDB, 0xCF, 0x17, 0x73, 0x0C, 0xD3, 0x0C, 0x43, 0x6A, 0xF1, 0x98, 0xA1,
+ 0xA1, 0x76, 0xC0, 0x7E, 0xEC, 0x2B, 0x62, 0x9A, 0x61, 0x48, 0xED, 0x01, 0x6B, 0x69, 0x6A, 0x1F,
+ 0x08, 0x50, 0xE8, 0xA6, 0x8B, 0x45, 0x81, 0x63, 0x33, 0x8C, 0xD6, 0xA0, 0xAE, 0xBE, 0xFE, 0xCA,
+ 0xB5, 0x9B, 0x9A, 0x9A, 0x1A, 0x43, 0x06, 0x31, 0x2F, 0xBB, 0xE4, 0x28, 0x2B, 0x6B, 0xEF, 0xDB,
+ 0xA7, 0x68, 0x62, 0x22, 0xB6, 0xA9, 0xCA, 0xCA, 0xAA, 0xC8, 0xA8, 0x98, 0xA4, 0x87, 0xC9, 0x39,
+ 0xB9, 0x6F, 0x05, 0x02, 0x21, 0x97, 0xCB, 0x55, 0x53, 0x55, 0x35, 0xD4, 0xD7, 0x5B, 0xE6, 0x32,
+ 0x1F, 0xCB, 0x19, 0xD3, 0xAC, 0xF3, 0x22, 0x21, 0xF1, 0xBE, 0xFF, 0xC1, 0xD0, 0xF4, 0xCC, 0x27,
+ 0xEE, 0xAE, 0xCB, 0x45, 0x18, 0x32, 0x79, 0x2D, 0x6F, 0x2F, 0x49, 0xC6, 0x9A, 0x2F, 0x5E, 0xB9,
+ 0xEE, 0x1F, 0x74, 0xF8, 0x5D, 0x51, 0x31, 0xA5, 0x5C, 0xBF, 0x5B, 0x37, 0x4C, 0x33, 0x4C, 0xB3,
+ 0x4E, 0x8A, 0x27, 0x59, 0xCF, 0xF6, 0x1F, 0x0C, 0x8D, 0x8D, 0xBF, 0xC7, 0x5E, 0x4D, 0x63, 0xE3,
+ 0x06, 0xE5, 0x11, 0x23, 0xC4, 0xB6, 0x16, 0x76, 0x26, 0xC2, 0x37, 0x20, 0x18, 0x4B, 0x15, 0xD3,
+ 0x0C, 0xE3, 0x3D, 0xBC, 0xFD, 0x02, 0xCF, 0xFC, 0xF5, 0xB7, 0xD8, 0xF7, 0xF8, 0xA8, 0xCE, 0x9D,
+ 0xC3, 0x9B, 0x36, 0x55, 0x6C, 0x6B, 0xD9, 0xAF, 0x73, 0xFC, 0x83, 0x42, 0xB0, 0x54, 0x31, 0xCD,
+ 0x30, 0x9A, 0x21, 0x2A, 0x26, 0x5E, 0x2C, 0xC7, 0xE4, 0x75, 0x75, 0xD5, 0x57, 0xAE, 0x90, 0xA4,
+ 0xB5, 0x33, 0x7F, 0x5D, 0xA8, 0xAD, 0xAD, 0xA5, 0x97, 0x6B, 0x69, 0x6A, 0x54, 0x55, 0x55, 0x63,
+ 0x69, 0x63, 0x9A, 0x61, 0x88, 0x84, 0xE4, 0xEF, 0x6B, 0x4F, 0xBC, 0xFF, 0x90, 0x52, 0x32, 0xA0,
+ 0x7F, 0xDF, 0x9D, 0x5B, 0xFE, 0xA3, 0xA1, 0xA1, 0x8E, 0xC5, 0xD8, 0x86, 0xC0, 0x09, 0xFD, 0x4E,
+ 0x8D, 0xBC, 0x7C, 0xEA, 0x56, 0xC4, 0x53, 0x26, 0x8D, 0xC7, 0x1C, 0xC3, 0x34, 0xC3, 0x68, 0x4B,
+ 0x54, 0xD7, 0x50, 0x3D, 0xC3, 0xAE, 0x5D, 0xF1, 0x06, 0xC3, 0xD8, 0x69, 0x6C, 0x78, 0xAB, 0x6B,
+ 0x5D, 0xD2, 0xC3, 0x47, 0x09, 0x89, 0xF7, 0xB3, 0x9E, 0xBF, 0xCC, 0x7D, 0x9B, 0x57, 0x2A, 0x10,
+ 0xD4, 0xD7, 0xD5, 0xAB, 0xA8, 0x28, 0x6B, 0x6A, 0x6A, 0x18, 0xF7, 0xE8, 0x6E, 0x63, 0x39, 0xE0,
+ 0x93, 0x21, 0xF6, 0xEA, 0x7C, 0x31, 0x33, 0x1E, 0xAA, 0xAB, 0xAB, 0x9F, 0xBD, 0xC8, 0x36, 0xEA,
+ 0x61, 0xA8, 0xA2, 0xAC, 0x0C, 0xA1, 0xCE, 0xDD, 0x7B, 0x49, 0x37, 0x22, 0xA3, 0xD2, 0x32, 0x9E,
+ 0x94, 0x94, 0x94, 0xCA, 0x2B, 0xC8, 0xEB, 0x68, 0x6B, 0xF5, 0x31, 0xED, 0x35, 0x6C, 0xA8, 0xFD,
+ 0x10, 0x7B, 0x3B, 0x79, 0x09, 0x56, 0xE9, 0x57, 0x54, 0x54, 0xC4, 0x27, 0x3E, 0x48, 0x7C, 0xF0,
+ 0xF0, 0x65, 0xF6, 0x2B, 0xB0, 0x0F, 0x02, 0x81, 0x10, 0x0A, 0x95, 0xB8, 0x8A, 0x5A, 0x9A, 0x9A,
+ 0xFA, 0x7A, 0xDD, 0xFA, 0x9B, 0xF5, 0x19, 0x3C, 0x68, 0x60, 0x4F, 0x63, 0x31, 0xAF, 0xED, 0x83,
+ 0x18, 0xE9, 0x45, 0xF6, 0x2B, 0x1D, 0x6D, 0x6D, 0x74, 0xF1, 0xE8, 0xAA, 0xAE, 0xDC, 0x88, 0xCC,
+ 0xC8, 0x7C, 0x0A, 0x0D, 0xDA, 0xDA, 0x58, 0xBA, 0xBB, 0xAD, 0x68, 0x09, 0x7F, 0x6A, 0x62, 0xEF,
+ 0x26, 0xD0, 0xA3, 0xB8, 0x01, 0xFD, 0xFB, 0xC1, 0xDD, 0x81, 0x0C, 0x63, 0xE3, 0x13, 0x6B, 0x6A,
+ 0xAA, 0x6B, 0x6B, 0xA8, 0x81, 0xD9, 0xE3, 0xD4, 0x74, 0xB8, 0x1D, 0x74, 0x6C, 0x6C, 0xD4, 0xC3,
+ 0xC4, 0xA8, 0xC7, 0xBD, 0xFB, 0x0F, 0x85, 0x42, 0x21, 0xB9, 0x4E, 0xDF, 0x3E, 0xBD, 0xF5, 0x44,
+ 0x8C, 0x77, 0x47, 0xC7, 0xC5, 0xD7, 0x34, 0xDF, 0x71, 0xD5, 0xDA, 0x62, 0x00, 0x32, 0x8F, 0xF0,
+ 0xB0, 0xD2, 0x33, 0x9F, 0x90, 0xBF, 0x42, 0xED, 0xA3, 0xAB, 0xBD, 0x7E, 0xEB, 0x4E, 0x54, 0x4C,
+ 0xDC, 0xD3, 0x67, 0x2F, 0xCA, 0xCB, 0x2B, 0x54, 0x55, 0x79, 0xF0, 0x34, 0xFB, 0x98, 0xF6, 0xFC,
+ 0x6C, 0xC4, 0x30, 0x4B, 0xA6, 0x77, 0xD2, 0xCB, 0x9E, 0x1B, 0x2F, 0x43, 0xEF, 0x9E, 0x86, 0x4B,
+ 0xFD, 0xFB, 0x9F, 0x6B, 0x21, 0xC7, 0x4F, 0xE5, 0xBC, 0xC9, 0x65, 0xA9, 0xC6, 0xE5, 0x72, 0x27,
+ 0x4F, 0xFC, 0x62, 0x8E, 0xF3, 0x77, 0x1A, 0xEA, 0x7C, 0x72, 0xF9, 0xBD, 0xA4, 0x07, 0xC9, 0x29,
+ 0x69, 0x4F, 0xB3, 0x9E, 0x65, 0x66, 0x3D, 0x7B, 0x99, 0xFD, 0x1A, 0x54, 0xED, 0xF7, 0x23, 0x0D,
+ 0xAF, 0x7B, 0xDD, 0xBC, 0xDD, 0x2B, 0xF9, 0x71, 0x2A, 0x63, 0x53, 0xC0, 0xC3, 0x55, 0x4B, 0x16,
+ 0x0E, 0xB6, 0x1B, 0x28, 0xEA, 0x5C, 0xC5, 0x25, 0xA5, 0xC7, 0xC3, 0x4E, 0x9F, 0x3D, 0x77, 0xB1,
+ 0x4C, 0xDC, 0xFB, 0x2C, 0x2D, 0xCC, 0xFB, 0x2D, 0x9A, 0x3F, 0xDB, 0xC6, 0xCA, 0x82, 0x5C, 0x98,
+ 0x92, 0x96, 0x91, 0xF4, 0x30, 0xF9, 0xC9, 0xD3, 0xE7, 0x4F, 0xB2, 0x9E, 0x3D, 0x7F, 0xF1, 0x12,
+ 0xB4, 0x6D, 0xEF, 0x2E, 0x4F, 0x5B, 0x6B, 0xCB, 0xEC, 0xD7, 0x39, 0x9E, 0x3B, 0xF7, 0x24, 0xA7,
+ 0xBC, 0xBF, 0x2A, 0xA0, 0xFD, 0xE1, 0xFD, 0x7B, 0xE0, 0xE0, 0xDB, 0x99, 0x0B, 0xDE, 0xBC, 0x7D,
+ 0x4B, 0x6E, 0xC4, 0xDD, 0x75, 0xF9, 0x84, 0x2F, 0x46, 0x53, 0x7A, 0x22, 0x8F, 0x2D, 0xDB, 0x6E,
+ 0x47, 0xC7, 0x51, 0xAE, 0x61, 0xEC, 0xA8, 0x91, 0x3F, 0xAF, 0x6D, 0x78, 0xA7, 0xD9, 0xD6, 0x9D,
+ 0xDE, 0xFF, 0x5C, 0xBF, 0x25, 0x56, 0xE0, 0x73, 0x67, 0x7C, 0x37, 0x6F, 0xE6, 0xBF, 0xE7, 0x2E,
+ 0x5A, 0x99, 0xF9, 0x34, 0x8B, 0xFD, 0x8C, 0x04, 0xC6, 0x4F, 0xFB, 0xB7, 0xB0, 0xF9, 0x8B, 0x66,
+ 0xD0, 0x1D, 0xC1, 0xC1, 0xDF, 0x97, 0xAF, 0xFE, 0xEA, 0xB5, 0x97, 0xDE, 0x3E, 0xF4, 0x26, 0x3B,
+ 0xBC, 0xF7, 0x01, 0x09, 0x19, 0x1B, 0xB4, 0xB3, 0xB1, 0xDA, 0xB0, 0xCE, 0x15, 0xBA, 0x06, 0x6C,
+ 0xCD, 0xDA, 0xC9, 0x88, 0x79, 0xEE, 0xDA, 0x73, 0xF9, 0xDA, 0x4D, 0xB1, 0x35, 0xAB, 0xAA, 0xAA,
+ 0xFE, 0xF8, 0xF3, 0x5C, 0x64, 0x54, 0xEC, 0x9E, 0x1D, 0xBF, 0xF4, 0x30, 0x7C, 0xFF, 0x6E, 0x72,
+ 0xDF, 0xC0, 0x43, 0x14, 0x75, 0x79, 0x9D, 0xF3, 0x66, 0xCB, 0x8E, 0xDD, 0x05, 0x85, 0xEF, 0x44,
+ 0x35, 0x05, 0x6C, 0x5C, 0xBD, 0x7E, 0x23, 0xD0, 0xE3, 0xFB, 0xE9, 0xD3, 0xE8, 0xDF, 0xA6, 0xA4,
+ 0x67, 0xAC, 0xDB, 0xE0, 0x59, 0xF8, 0xEE, 0x9D, 0x24, 0xD7, 0xFF, 0x28, 0x25, 0x6D, 0xD9, 0x1A,
+ 0x0F, 0xB7, 0xE5, 0x8B, 0x26, 0x4F, 0x1C, 0x47, 0x14, 0x1E, 0x39, 0xF9, 0xFB, 0xED, 0xE8, 0x58,
+ 0x4A, 0x4D, 0xE8, 0xF2, 0x97, 0xAF, 0xFD, 0x59, 0x28, 0xF1, 0x5B, 0x91, 0xE8, 0x82, 0x02, 0x16,
+ 0xD1, 0x39, 0x36, 0x6E, 0xF4, 0x28, 0x77, 0xB7, 0xE5, 0x92, 0x73, 0xAC, 0x7D, 0x70, 0xE9, 0xEA,
+ 0xF5, 0x5F, 0x77, 0xF9, 0xD4, 0x89, 0xEE, 0xEB, 0xC1, 0x96, 0x2E, 0x75, 0x5D, 0x1F, 0xE4, 0xEB,
+ 0xA5, 0x26, 0xCB, 0x1B, 0x93, 0xC8, 0x4C, 0x6C, 0x06, 0x1A, 0x29, 0x09, 0xC7, 0x08, 0x40, 0xEF,
+ 0xE8, 0xEA, 0xBE, 0x89, 0xF0, 0x7F, 0x18, 0x01, 0x9D, 0x2B, 0x0B, 0xC7, 0x08, 0xEC, 0x0F, 0x0E,
+ 0x3D, 0x7B, 0xFE, 0x12, 0xA5, 0xF0, 0x55, 0xCE, 0x9B, 0x55, 0xEB, 0x36, 0x48, 0xC8, 0x31, 0xC2,
+ 0x1A, 0x7B, 0xF9, 0x06, 0x80, 0xE1, 0x62, 0xA9, 0x53, 0x5A, 0x2A, 0x58, 0xB7, 0xD1, 0xB3, 0xD5,
+ 0x1C, 0x83, 0x53, 0x6C, 0xDF, 0xED, 0x4B, 0x67, 0xD1, 0x94, 0x49, 0xE3, 0x3D, 0xD6, 0xAC, 0xA8,
+ 0xAD, 0xAB, 0x83, 0x6E, 0xA5, 0xE3, 0x70, 0x2C, 0x35, 0x3D, 0x63, 0x9B, 0x97, 0x6F, 0x9D, 0x38,
+ 0x7F, 0x0A, 0x6C, 0x7B, 0xE0, 0xE1, 0xA3, 0xD8, 0x9A, 0x49, 0x1D, 0xC2, 0xB2, 0xB2, 0xE3, 0x61,
+ 0xE1, 0x94, 0x42, 0xD3, 0x9E, 0x26, 0xE3, 0xC7, 0x8E, 0x32, 0xEA, 0x6E, 0x08, 0xAE, 0x6F, 0x7E,
+ 0x61, 0x61, 0x74, 0x6C, 0x7C, 0x54, 0xEC, 0x5D, 0x8A, 0xB1, 0x3A, 0x15, 0x1E, 0x01, 0x96, 0x88,
+ 0x85, 0x8A, 0xC4, 0x31, 0xC4, 0x0F, 0xE0, 0x99, 0x00, 0xEB, 0x8A, 0x8B, 0x4B, 0xE8, 0x35, 0x7D,
+ 0xF6, 0x07, 0x81, 0xF7, 0x02, 0x3E, 0x24, 0x51, 0xE2, 0xBD, 0x2F, 0x90, 0x4E, 0x86, 0x21, 0x83,
+ 0x6C, 0x21, 0x9C, 0xE8, 0xAA, 0xA3, 0x5D, 0x59, 0x59, 0x05, 0x8D, 0x5F, 0xB8, 0x7C, 0xF5, 0xE9,
+ 0xB3, 0xE7, 0x14, 0x53, 0xF3, 0xC7, 0xD9, 0x73, 0x3F, 0xAD, 0x5C, 0x2A, 0xBA, 0x37, 0x09, 0xCB,
+ 0xCB, 0x2F, 0x68, 0x35, 0xC7, 0x76, 0xFB, 0x06, 0x80, 0x5F, 0x4D, 0x29, 0x9F, 0xFA, 0xD5, 0xC4,
+ 0x55, 0x4B, 0x7E, 0xE4, 0x70, 0x38, 0x7E, 0x07, 0x0E, 0x3F, 0x7A, 0x9C, 0xA6, 0xDF, 0xAD, 0xDB,
+ 0x7B, 0x09, 0xE4, 0xE5, 0x51, 0xA2, 0x06, 0x6D, 0x2D, 0x4D, 0xAE, 0x12, 0x17, 0x1D, 0x4B, 0xDB,
+ 0x80, 0x44, 0xC7, 0x25, 0x48, 0x58, 0x13, 0x24, 0xB9, 0x64, 0xC1, 0x1C, 0x15, 0x99, 0x5D, 0xC8,
+ 0x23, 0x1B, 0x34, 0x4B, 0xBC, 0xFF, 0x90, 0x62, 0x97, 0x40, 0x95, 0x03, 0xF7, 0xEE, 0x52, 0x51,
+ 0x56, 0x26, 0x77, 0xD8, 0xE1, 0x11, 0x17, 0xBC, 0xFD, 0x02, 0xC9, 0xD5, 0x2E, 0x5E, 0xB9, 0xCE,
+ 0x42, 0x33, 0xC2, 0xFB, 0x77, 0x99, 0x3F, 0xDB, 0xBC, 0x9F, 0x19, 0xFA, 0x17, 0xE2, 0x34, 0x50,
+ 0x47, 0x72, 0x5C, 0x84, 0x52, 0x26, 0x07, 0x42, 0x8E, 0xFE, 0xF2, 0xF3, 0x4F, 0x04, 0x81, 0xE9,
+ 0x73, 0x9D, 0xE6, 0x38, 0x3B, 0xCD, 0x9F, 0xF5, 0x3D, 0xB9, 0x64, 0xDA, 0xD7, 0x93, 0x16, 0xAF,
+ 0xFA, 0x29, 0x35, 0x3D, 0x93, 0x5C, 0x98, 0xFC, 0x28, 0x95, 0xE5, 0x62, 0xD2, 0x32, 0x9A, 0x25,
+ 0x09, 0x94, 0x14, 0x15, 0x35, 0x1B, 0x46, 0x8A, 0xAB, 0x24, 0x91, 0xD2, 0xBE, 0xC0, 0x43, 0x7F,
+ 0x9E, 0xBF, 0x48, 0x29, 0x74, 0x9E, 0xEE, 0xE8, 0x32, 0x7F, 0x16, 0x3A, 0x5E, 0xB9, 0x78, 0x01,
+ 0x7C, 0xD8, 0x43, 0xA9, 0x4D, 0xEE, 0x6B, 0x50, 0x28, 0xD5, 0x6E, 0xE8, 0xA6, 0xDB, 0x15, 0xE4,
+ 0x06, 0x3D, 0x14, 0xC4, 0xD2, 0x45, 0xC5, 0x25, 0xD7, 0x6E, 0xDE, 0x0E, 0x0A, 0x3D, 0x06, 0xFD,
+ 0x14, 0x25, 0x10, 0x88, 0x4F, 0x7C, 0x30, 0xFC, 0x5F, 0x43, 0x30, 0xCD, 0xA4, 0x88, 0x67, 0x2F,
+ 0xA8, 0xEF, 0x3E, 0xD6, 0xD7, 0xEB, 0x46, 0xE6, 0x58, 0x53, 0xB7, 0x3D, 0xE1, 0xD8, 0xA9, 0x3F,
+ 0xC8, 0xD6, 0x20, 0xFB, 0xD5, 0xEB, 0x77, 0x45, 0xC5, 0x5A, 0x9A, 0x22, 0xF7, 0x42, 0xB3, 0xB1,
+ 0xB2, 0xF8, 0xED, 0xD7, 0x4D, 0xA0, 0xCD, 0x44, 0x89, 0xE5, 0x80, 0xFE, 0x7B, 0x7F, 0xF3, 0x5C,
+ 0xE6, 0xE6, 0x0E, 0xD1, 0x14, 0xB9, 0xE6, 0xAD, 0xDB, 0xD1, 0x6F, 0xF3, 0xF2, 0x41, 0x27, 0x1A,
+ 0xB3, 0x29, 0xD4, 0x51, 0x5D, 0x30, 0x86, 0xB3, 0xBF, 0x77, 0xA2, 0x14, 0x42, 0xB3, 0x93, 0xC6,
+ 0x8D, 0xA1, 0xD0, 0xAC, 0xB8, 0xA4, 0x44, 0xBC, 0x2B, 0xCF, 0xE1, 0x4C, 0xF9, 0x6A, 0x02, 0xFC,
+ 0xB6, 0x6F, 0x9F, 0xDE, 0x44, 0xE6, 0x90, 0xFD, 0x27, 0x01, 0xC1, 0x47, 0xC2, 0xCE, 0x44, 0x50,
+ 0x0A, 0x67, 0x38, 0x39, 0x2E, 0x9C, 0x37, 0xAB, 0x23, 0x3F, 0x59, 0x78, 0x34, 0x01, 0x7B, 0x76,
+ 0xEA, 0x36, 0x8D, 0x22, 0xC0, 0x81, 0x93, 0xE3, 0xD7, 0xEA, 0xEA, 0xFC, 0x5F, 0x7F, 0xF3, 0xA1,
+ 0xD4, 0x84, 0xB8, 0x1A, 0xD3, 0x4C, 0xEA, 0xF9, 0x0F, 0x6A, 0xFA, 0x21, 0x2D, 0x03, 0xEC, 0xC9,
+ 0x50, 0x7B, 0x3B, 0x72, 0x21, 0xF8, 0x45, 0x9B, 0xD6, 0xBB, 0x51, 0xF4, 0x98, 0x2B, 0x7A, 0x4A,
+ 0x84, 0xBC, 0xBC, 0xFC, 0xBA, 0xD5, 0xCB, 0xC8, 0x1C, 0x23, 0xE8, 0xE1, 0xE1, 0xB6, 0x72, 0xC6,
+ 0x82, 0x25, 0xE4, 0xF3, 0x42, 0x08, 0x71, 0xF3, 0x76, 0xF4, 0xB7, 0x53, 0xBF, 0x82, 0x63, 0xD0,
+ 0x83, 0x09, 0x63, 0x3F, 0x27, 0xFF, 0xA4, 0xA7, 0xB1, 0x91, 0xA2, 0x22, 0xC3, 0x26, 0x36, 0xEA,
+ 0xCD, 0xB3, 0x9D, 0x0D, 0x1D, 0x73, 0x75, 0xB5, 0x58, 0x8E, 0x79, 0x6E, 0x74, 0xA7, 0xA8, 0x14,
+ 0xFD, 0x22, 0xC9, 0x08, 0x3D, 0x11, 0x76, 0x3C, 0xEC, 0x34, 0xA5, 0x10, 0x08, 0x06, 0x34, 0xEB,
+ 0xE0, 0x4F, 0x76, 0x86, 0xD3, 0x37, 0xBA, 0xB4, 0x91, 0xBA, 0xF1, 0x63, 0x46, 0x05, 0x1E, 0x3A,
+ 0x42, 0x09, 0x9B, 0xDF, 0xE4, 0xBE, 0xC5, 0xB1, 0x99, 0xD4, 0xFD, 0x0A, 0x4A, 0x49, 0x6D, 0x6D,
+ 0xAD, 0x9B, 0xC7, 0xE6, 0x7F, 0x0D, 0xB1, 0x87, 0x58, 0x68, 0xD0, 0x40, 0x6B, 0x62, 0x18, 0xC7,
+ 0xDA, 0x72, 0x80, 0xE4, 0xCD, 0x82, 0xA3, 0x42, 0x4E, 0x45, 0x92, 0x01, 0x61, 0xD8, 0x50, 0x7B,
+ 0x5B, 0x4A, 0xF0, 0xF0, 0x28, 0x25, 0x15, 0xD1, 0x6C, 0xA4, 0xC3, 0x27, 0xF0, 0x11, 0xDB, 0x7E,
+ 0x59, 0x79, 0x79, 0xC4, 0x85, 0xCB, 0x2D, 0xBD, 0xD9, 0xA9, 0x93, 0x27, 0xB5, 0xA8, 0xDB, 0x3E,
+ 0x73, 0xEE, 0x6F, 0xE8, 0x74, 0x64, 0x91, 0x63, 0x48, 0x92, 0xF4, 0x42, 0xE8, 0x2E, 0x2D, 0xCD,
+ 0xFB, 0xDF, 0xBC, 0x13, 0x4D, 0x89, 0xCF, 0x31, 0xCD, 0xA4, 0x8B, 0x61, 0x43, 0x07, 0x73, 0xB9,
+ 0x5C, 0x7A, 0x88, 0x12, 0x1D, 0x17, 0x0F, 0x1F, 0xE4, 0x43, 0x5A, 0x98, 0xF7, 0xB3, 0xE8, 0xDF,
+ 0x6F, 0xA0, 0xB5, 0x65, 0x6F, 0xD3, 0x9E, 0xF2, 0x92, 0xED, 0xFC, 0x6E, 0x6F, 0x6B, 0xC3, 0xF2,
+ 0x2D, 0x98, 0x4A, 0x0A, 0xCD, 0x28, 0x81, 0x13, 0x23, 0x0A, 0xDF, 0xBD, 0xCB, 0x7E, 0x95, 0xF3,
+ 0xFC, 0x65, 0x76, 0x7A, 0xC6, 0x13, 0x50, 0x14, 0xFA, 0x22, 0x2E, 0xB1, 0xF8, 0xF7, 0x37, 0x53,
+ 0x5A, 0x54, 0x9F, 0xCE, 0x31, 0x40, 0x77, 0x03, 0xFD, 0x8E, 0xFF, 0x58, 0x79, 0x5D, 0xBA, 0xC0,
+ 0x83, 0x63, 0xFC, 0x4A, 0x57, 0x97, 0x6A, 0xE2, 0x6A, 0x6A, 0x6A, 0x31, 0xCD, 0xA4, 0x0B, 0x75,
+ 0xBE, 0xDA, 0xB2, 0x85, 0xF3, 0xBC, 0x7C, 0x03, 0x44, 0x55, 0x00, 0x8F, 0x02, 0x3E, 0x57, 0x6F,
+ 0x44, 0xC2, 0xB1, 0x8E, 0xB6, 0xD6, 0x67, 0x23, 0x1C, 0x1C, 0x27, 0x4F, 0x22, 0x27, 0x06, 0x19,
+ 0x61, 0xDC, 0x38, 0x05, 0x41, 0x14, 0x7A, 0x99, 0x18, 0x53, 0x4A, 0xF2, 0x0A, 0xA8, 0x39, 0xC0,
+ 0xFA, 0xFA, 0xFA, 0xC7, 0xA9, 0xE9, 0xF1, 0x89, 0xF7, 0x53, 0xD3, 0x33, 0x20, 0x80, 0xCC, 0xCD,
+ 0x7D, 0x5B, 0xDD, 0xAA, 0xB7, 0xCE, 0x12, 0x30, 0xED, 0x65, 0xA2, 0xD7, 0x16, 0x1B, 0x0A, 0x78,
+ 0xED, 0x0B, 0xB0, 0xB5, 0xB1, 0xD4, 0xEC, 0xD8, 0x1B, 0x74, 0xAB, 0xF3, 0xF9, 0x2C, 0x0C, 0x94,
+ 0xFB, 0x88, 0x20, 0x33, 0xC3, 0xD3, 0x53, 0xBE, 0x9C, 0xA0, 0xA1, 0xAE, 0xBE, 0xC7, 0x3F, 0x48,
+ 0xEC, 0x50, 0x15, 0xF8, 0xF4, 0xA7, 0xCF, 0x9E, 0x3B, 0xF3, 0xD7, 0x85, 0xEF, 0xBE, 0x99, 0xB2,
+ 0x70, 0xEE, 0x4C, 0x79, 0xD1, 0x6F, 0x9A, 0xE4, 0xF3, 0xD9, 0x12, 0xD6, 0x7C, 0x9A, 0x12, 0x54,
+ 0x56, 0x42, 0x60, 0x55, 0xAD, 0xD4, 0x14, 0xEC, 0xDD, 0x88, 0x8C, 0x3A, 0x10, 0x72, 0xF4, 0x65,
+ 0xF6, 0xEB, 0x36, 0xBC, 0x4D, 0x3A, 0xB7, 0x5B, 0x87, 0xE2, 0xE2, 0x12, 0xE8, 0x95, 0x88, 0xD4,
+ 0x68, 0x07, 0xB5, 0x66, 0xAA, 0x22, 0xB9, 0xA4, 0xF0, 0x71, 0xED, 0xD6, 0x2A, 0x4B, 0x53, 0x87,
+ 0x47, 0x8D, 0x74, 0x38, 0x15, 0x12, 0xF0, 0xD3, 0xAA, 0xA5, 0x83, 0xED, 0x06, 0x82, 0x0F, 0x29,
+ 0x36, 0x6B, 0x72, 0x22, 0x2C, 0x7C, 0xCB, 0x76, 0x2F, 0xB6, 0x4A, 0x2D, 0x9F, 0x67, 0x56, 0xDB,
+ 0x94, 0x14, 0x01, 0xC2, 0xFF, 0x67, 0xEB, 0x0E, 0x16, 0x8E, 0x01, 0xBD, 0x21, 0xC0, 0x98, 0x34,
+ 0x6E, 0x4C, 0x8B, 0xDA, 0xD7, 0x54, 0x6F, 0xB3, 0xD9, 0xF1, 0xD0, 0x0B, 0x5C, 0xBF, 0x75, 0xA7,
+ 0x9D, 0x9F, 0x11, 0x3D, 0x59, 0xC5, 0x9A, 0xEC, 0xE9, 0x2C, 0x33, 0xD7, 0x65, 0x6C, 0xEA, 0xB0,
+ 0x8A, 0x8A, 0xCA, 0x97, 0xE3, 0xC7, 0xC2, 0x07, 0xE2, 0xB4, 0x07, 0x8F, 0x52, 0x1E, 0x24, 0x3F,
+ 0x4E, 0x7E, 0x9C, 0xFA, 0x28, 0x25, 0x4D, 0xD4, 0x94, 0xC2, 0xAB, 0x37, 0x6F, 0x4F, 0xFC, 0x62,
+ 0xCC, 0x10, 0x7B, 0xE6, 0x1D, 0x69, 0x8A, 0x8A, 0xD9, 0x72, 0xEB, 0xA5, 0x02, 0x01, 0xA5, 0x44,
+ 0x49, 0x51, 0x11, 0x8D, 0x22, 0x5C, 0xBE, 0x76, 0x13, 0x0C, 0x26, 0x3D, 0x70, 0xB7, 0xB6, 0x30,
+ 0xB7, 0xB5, 0xB1, 0xEA, 0x69, 0x62, 0x6C, 0xDC, 0xA3, 0x3B, 0x9A, 0x97, 0x1C, 0x19, 0x15, 0x73,
+ 0xFE, 0xD2, 0x95, 0x16, 0x68, 0x9E, 0x42, 0x6B, 0x34, 0x0F, 0x62, 0xD1, 0x1F, 0xE7, 0xCD, 0x0C,
+ 0x39, 0x76, 0xAA, 0xA2, 0xB2, 0xB2, 0x1D, 0x5C, 0xC7, 0x4A, 0xD1, 0xE3, 0x78, 0xE5, 0xE5, 0x15,
+ 0x72, 0x18, 0xB2, 0x4E, 0x33, 0x02, 0x60, 0xCD, 0xEC, 0x6D, 0x6D, 0x50, 0x0E, 0x03, 0x7A, 0x50,
+ 0x20, 0xDB, 0xE5, 0x6B, 0x37, 0xFE, 0xFE, 0xE7, 0x3A, 0x3D, 0x4D, 0x72, 0xE5, 0xC6, 0x2D, 0x51,
+ 0x34, 0xCB, 0x7A, 0xF6, 0x9C, 0x25, 0xA7, 0x97, 0xF5, 0xFC, 0x05, 0xA5, 0x44, 0xBB, 0x69, 0x02,
+ 0xEB, 0xF1, 0x53, 0xD4, 0xEC, 0xB9, 0xA1, 0x81, 0xFE, 0xB6, 0x8D, 0xEE, 0x10, 0x59, 0x7D, 0x48,
+ 0xEF, 0xDE, 0xCA, 0x47, 0xA8, 0xA8, 0xE0, 0xE1, 0xB6, 0x72, 0xCC, 0xA8, 0x91, 0x1C, 0x39, 0xCE,
+ 0xFE, 0xE0, 0xD0, 0x76, 0x70, 0x1D, 0xCB, 0x45, 0x4C, 0x61, 0x93, 0x64, 0xE6, 0x5A, 0xE7, 0x84,
+ 0x6C, 0x58, 0xED, 0xC4, 0x07, 0xC9, 0xE4, 0x0F, 0x65, 0xE0, 0x18, 0xDC, 0x33, 0x6B, 0xCB, 0x01,
+ 0x6E, 0xCB, 0x17, 0x1F, 0x3D, 0xE0, 0x4B, 0x1F, 0x84, 0x79, 0x95, 0xF3, 0x46, 0x54, 0xB3, 0xD1,
+ 0x77, 0xD9, 0x26, 0xFB, 0xC4, 0xD2, 0xBE, 0x1D, 0xD0, 0xBF, 0x2F, 0xFC, 0x2D, 0x29, 0x15, 0x50,
+ 0xA6, 0x50, 0x01, 0xC0, 0x95, 0xA5, 0x73, 0x4C, 0xAE, 0x71, 0x0A, 0xBF, 0xB4, 0x85, 0xF3, 0xE3,
+ 0x9C, 0x99, 0xC0, 0x31, 0x38, 0x70, 0x72, 0xFC, 0xDA, 0xB4, 0xA7, 0x49, 0x9B, 0xBB, 0x8E, 0x5C,
+ 0x2E, 0x75, 0xE0, 0x31, 0xE7, 0x0D, 0xF3, 0x10, 0x16, 0x65, 0x9D, 0x0B, 0x86, 0x8C, 0x59, 0x33,
+ 0x57, 0xF7, 0x4D, 0xD5, 0xA4, 0x51, 0x5D, 0x30, 0x65, 0xFF, 0x9C, 0x3D, 0x45, 0xCF, 0x6D, 0x80,
+ 0x49, 0x99, 0xF2, 0xE5, 0x84, 0xA0, 0x90, 0x63, 0xCD, 0xE2, 0x2F, 0xD1, 0x33, 0x53, 0x1F, 0x3E,
+ 0x4A, 0x49, 0x4E, 0x49, 0x65, 0x5C, 0xD1, 0x04, 0xA6, 0x2C, 0xE6, 0x2E, 0x75, 0x3A, 0x95, 0xE5,
+ 0x80, 0x86, 0x9A, 0xF9, 0x4C, 0x73, 0x0E, 0xAD, 0x2D, 0x98, 0xC7, 0xEB, 0x52, 0xD2, 0x33, 0xA4,
+ 0x2D, 0x1C, 0xCD, 0xA6, 0xC5, 0xCE, 0x0A, 0x0A, 0x0A, 0x6E, 0xCB, 0x17, 0x2D, 0x5E, 0xBD, 0x8E,
+ 0x52, 0xE1, 0x03, 0x5D, 0x47, 0x55, 0x1E, 0x8F, 0x52, 0x92, 0x90, 0x78, 0x1F, 0xA4, 0xCA, 0xA1,
+ 0x8D, 0x9A, 0xB4, 0x68, 0x6E, 0x37, 0xB6, 0x66, 0x1D, 0x0E, 0x3D, 0x8D, 0x9B, 0x65, 0xDE, 0xC1,
+ 0x33, 0xBC, 0x71, 0x3B, 0x8A, 0xB1, 0x66, 0x7E, 0x01, 0x75, 0xD5, 0x3D, 0x7B, 0x7E, 0x7C, 0xEB,
+ 0x0E, 0xEF, 0xA2, 0x62, 0xEA, 0xD0, 0x16, 0x44, 0x7A, 0x5B, 0x77, 0xEE, 0xA1, 0xF0, 0x13, 0x58,
+ 0x3D, 0x6A, 0x84, 0x83, 0xA8, 0x76, 0x18, 0xF3, 0x9F, 0x70, 0x31, 0x68, 0x8C, 0xA1, 0xDD, 0x60,
+ 0x65, 0x61, 0x0E, 0x81, 0x2B, 0xD5, 0xA2, 0x36, 0xBA, 0x8E, 0xAD, 0x6E, 0x93, 0x3E, 0x2E, 0xF2,
+ 0x22, 0xFB, 0xD5, 0x45, 0xDA, 0x04, 0xE5, 0xDB, 0xD1, 0xB1, 0xED, 0x76, 0xB3, 0x99, 0x4F, 0xB3,
+ 0xFC, 0x83, 0x42, 0xF6, 0xF8, 0x1D, 0x88, 0x4F, 0xBC, 0x8F, 0x69, 0xD6, 0x66, 0x70, 0xF8, 0x84,
+ 0x1A, 0x41, 0xED, 0xF4, 0xF6, 0x8B, 0xA1, 0x39, 0x75, 0xD1, 0x71, 0xF1, 0xE7, 0x2E, 0xFE, 0x43,
+ 0x29, 0x1C, 0x64, 0x63, 0xCD, 0xD2, 0x32, 0xB8, 0x94, 0xF3, 0x16, 0xAF, 0xFA, 0xF3, 0xFC, 0xC5,
+ 0xBC, 0xFC, 0x02, 0x88, 0xA3, 0x20, 0xBA, 0xB8, 0x78, 0xE5, 0xFA, 0x82, 0xA5, 0xAE, 0x74, 0xFF,
+ 0x67, 0xF4, 0xA7, 0xC3, 0x91, 0x47, 0xCA, 0xB8, 0x8C, 0x3F, 0xF0, 0xF0, 0x51, 0x4A, 0x18, 0x96,
+ 0xFB, 0x36, 0x6F, 0xFD, 0x26, 0xCF, 0xF6, 0x4F, 0x09, 0xB8, 0xCC, 0x9F, 0x4D, 0xDF, 0xCC, 0xE3,
+ 0x43, 0x5C, 0x47, 0x62, 0x5E, 0x25, 0x19, 0xDB, 0xBD, 0xF7, 0x79, 0xFB, 0x05, 0xC6, 0xC6, 0xDF,
+ 0x03, 0x07, 0xFE, 0xE6, 0x9D, 0xE8, 0x5F, 0x7F, 0xF3, 0xF1, 0xD8, 0xB2, 0xBD, 0x7D, 0x96, 0x08,
+ 0xC7, 0x25, 0x24, 0xFA, 0xEC, 0x3F, 0xC8, 0xE3, 0x75, 0xE9, 0x6E, 0x68, 0x10, 0x71, 0xE1, 0xD2,
+ 0xE9, 0x88, 0xF3, 0xD8, 0x69, 0x6C, 0x1B, 0x80, 0x2B, 0x18, 0x16, 0x1E, 0x41, 0x4E, 0x27, 0x0A,
+ 0xCB, 0xCA, 0xD6, 0xFC, 0xBC, 0xC5, 0xAC, 0x77, 0x2F, 0x5B, 0x6B, 0x2B, 0x3E, 0x5F, 0xAD, 0xB4,
+ 0x54, 0x00, 0xEE, 0xDF, 0xE3, 0xD4, 0x74, 0x9A, 0x43, 0xA5, 0x31, 0xFA, 0x33, 0x31, 0x5B, 0x82,
+ 0x02, 0xC1, 0xBC, 0xF6, 0xEE, 0xF7, 0x92, 0xDB, 0xCF, 0x52, 0xA7, 0x4B, 0x17, 0x95, 0x1F, 0xE7,
+ 0xCE, 0x44, 0xC7, 0xEA, 0x7C, 0x35, 0xF3, 0xBE, 0x66, 0x14, 0x6F, 0xF0, 0xF2, 0xD5, 0x1B, 0xE0,
+ 0x82, 0x0E, 0x1D, 0x64, 0xAB, 0xAD, 0xAD, 0x55, 0x5E, 0x51, 0xF1, 0xFC, 0x45, 0xF6, 0xDD, 0x84,
+ 0x44, 0xC6, 0xA1, 0xEA, 0xDA, 0x5A, 0xE9, 0x26, 0x45, 0x34, 0xD4, 0xF9, 0x4B, 0x7E, 0x98, 0x43,
+ 0x59, 0xA7, 0xFC, 0x21, 0xAE, 0xA3, 0xC3, 0x27, 0x83, 0xC1, 0x92, 0x53, 0x3A, 0x11, 0xF8, 0x37,
+ 0x3C, 0xE2, 0x02, 0x7C, 0xDA, 0x5F, 0x19, 0x8E, 0xFE, 0xF7, 0x8F, 0x25, 0x0B, 0xE6, 0x06, 0x85,
+ 0x1E, 0x33, 0x35, 0x31, 0xB1, 0xB7, 0xB5, 0x39, 0x72, 0xF2, 0x77, 0xC7, 0xC9, 0x93, 0xB0, 0x35,
+ 0x6B, 0x03, 0xE8, 0x68, 0x6B, 0xAD, 0x59, 0xB1, 0x98, 0x5E, 0x9E, 0xF1, 0x24, 0x2B, 0xEC, 0x4C,
+ 0x44, 0xF0, 0x91, 0x13, 0xF0, 0x97, 0xCE, 0x31, 0x79, 0x0E, 0x67, 0xDD, 0xEA, 0x65, 0xC0, 0x10,
+ 0x51, 0xCD, 0xD2, 0x13, 0x06, 0xA2, 0x00, 0xED, 0x90, 0xA7, 0x05, 0x7D, 0xD3, 0x38, 0xB3, 0x91,
+ 0x96, 0x18, 0xC8, 0x05, 0xAB, 0x78, 0xE8, 0xE8, 0xC9, 0x93, 0xBF, 0x9F, 0x89, 0x8A, 0xBD, 0x2B,
+ 0x6A, 0x3A, 0x48, 0x45, 0x45, 0x05, 0x65, 0x95, 0x47, 0x9B, 0x63, 0xFC, 0xD8, 0xCF, 0x6D, 0x68,
+ 0x73, 0x3B, 0x5B, 0xED, 0x3A, 0x02, 0x33, 0x27, 0x8E, 0x1B, 0x2D, 0x49, 0x4D, 0x03, 0x7D, 0x3D,
+ 0x49, 0xA6, 0x7A, 0x7E, 0x20, 0x8A, 0x8A, 0x8A, 0xBB, 0xEA, 0x68, 0x17, 0x16, 0x16, 0x81, 0x59,
+ 0xFB, 0x74, 0xF8, 0x30, 0x99, 0x18, 0xC8, 0x96, 0x99, 0xF1, 0xC1, 0x31, 0xA3, 0x46, 0x6E, 0x5C,
+ 0xE7, 0xCA, 0xC2, 0x19, 0x0A, 0x78, 0x5D, 0xBA, 0x6C, 0xF6, 0x58, 0x0B, 0x3D, 0x31, 0x4B, 0x9D,
+ 0x49, 0xE3, 0xC7, 0x88, 0x9D, 0x5F, 0xAB, 0xA4, 0xA8, 0xE8, 0xEE, 0xB6, 0xE2, 0xF3, 0x91, 0xC3,
+ 0xC9, 0x85, 0x5F, 0x7C, 0xFE, 0xE9, 0x97, 0x13, 0xC6, 0x8A, 0xBD, 0x06, 0xF8, 0xAD, 0xEB, 0xF2,
+ 0x45, 0xF4, 0xC9, 0xF5, 0xCF, 0x5F, 0xBE, 0x94, 0xAA, 0xAC, 0x38, 0x1C, 0x8E, 0xDB, 0x8A, 0xC5,
+ 0x74, 0xFD, 0x6B, 0xB5, 0xEB, 0xB8, 0x74, 0xC1, 0x3C, 0xB1, 0x5D, 0x12, 0x78, 0xD4, 0xBB, 0xB7,
+ 0x6D, 0xA6, 0xE7, 0x4B, 0xDA, 0x1C, 0x43, 0x06, 0xD9, 0xC2, 0x8D, 0x80, 0x1D, 0x9B, 0xF8, 0xC5,
+ 0xE8, 0xC8, 0x3B, 0x31, 0xA2, 0x26, 0x7F, 0x63, 0x9A, 0xB5, 0x9E, 0x69, 0xC7, 0x0F, 0xFA, 0x4F,
+ 0xFD, 0x6A, 0x22, 0x9F, 0xF5, 0x55, 0x5D, 0x6A, 0x6A, 0xAA, 0x50, 0xE7, 0xD8, 0x41, 0xBF, 0xCF,
+ 0x46, 0x0C, 0x13, 0xDB, 0xE6, 0xC2, 0x79, 0xB3, 0x36, 0xB9, 0xAF, 0xD1, 0x15, 0xB1, 0x6B, 0x9A,
+ 0xDD, 0x40, 0xEB, 0x03, 0xBE, 0xBF, 0x51, 0xD6, 0xBC, 0x20, 0xAC, 0x5D, 0xB1, 0x04, 0x28, 0xA4,
+ 0xA1, 0xCE, 0x3C, 0x2B, 0x0F, 0x0C, 0x29, 0xF4, 0xEB, 0x07, 0xFD, 0x76, 0x4F, 0x99, 0x34, 0xBE,
+ 0x9F, 0x59, 0x1F, 0xCA, 0xB7, 0x77, 0xEF, 0x25, 0x49, 0x3F, 0x69, 0x64, 0xF4, 0xFD, 0xB7, 0x0C,
+ 0x9B, 0x7B, 0x83, 0xEB, 0x48, 0x4F, 0xF9, 0x88, 0x85, 0xAA, 0x2A, 0xCF, 0xCF, 0x6B, 0x1B, 0xC8,
+ 0x81, 0x71, 0xE6, 0x1A, 0xB0, 0x1A, 0x1E, 0x4D, 0xB0, 0x9F, 0x77, 0xFB, 0x68, 0xFC, 0x82, 0xB9,
+ 0x33, 0x52, 0xD2, 0xD2, 0x5F, 0xBE, 0x7A, 0x05, 0x7F, 0x6F, 0x45, 0xC5, 0x80, 0xA3, 0xD1, 0xF1,
+ 0x55, 0x57, 0x96, 0x76, 0xB6, 0x22, 0x07, 0x06, 0x0F, 0x1F, 0xA7, 0xA6, 0x65, 0x64, 0xBE, 0xCC,
+ 0x7E, 0x2D, 0x10, 0x0A, 0xC1, 0x0B, 0x53, 0x56, 0xE6, 0xAA, 0xA9, 0xF2, 0x8C, 0x8D, 0x7A, 0xF4,
+ 0xED, 0x6D, 0x6A, 0x65, 0x61, 0xCE, 0xE8, 0x48, 0xD0, 0x77, 0x68, 0x5A, 0xE6, 0x32, 0x7F, 0xFA,
+ 0xD4, 0xC9, 0x8D, 0xF1, 0x52, 0x2D, 0x78, 0x20, 0x89, 0x0F, 0x92, 0xC1, 0xF1, 0xAB, 0xA9, 0xA9,
+ 0xD5, 0xD4, 0x50, 0xEF, 0x65, 0x62, 0x3C, 0xD4, 0xDE, 0xCE, 0xC4, 0xB8, 0x07, 0xFB, 0x95, 0x54,
+ 0x57, 0x57, 0xC7, 0x25, 0x24, 0x41, 0x54, 0xF6, 0x36, 0x2F, 0xAF, 0xBC, 0xA2, 0x12, 0xFA, 0x72,
+ 0x6D, 0x2D, 0xCD, 0x3E, 0xA6, 0x3D, 0x07, 0xD9, 0xDA, 0x10, 0x9B, 0x31, 0x81, 0xB7, 0x46, 0x19,
+ 0xCF, 0x55, 0x56, 0x56, 0x46, 0x2B, 0x4D, 0xDF, 0x15, 0x15, 0x57, 0x36, 0x9F, 0xB7, 0xC1, 0xE3,
+ 0xF1, 0xD8, 0x77, 0xBF, 0x83, 0x48, 0x92, 0xB2, 0x1D, 0xB7, 0x86, 0xBA, 0x3A, 0xA3, 0x91, 0x07,
+ 0xAF, 0xB5, 0x80, 0x96, 0x77, 0xA5, 0xD4, 0xCF, 0x7D, 0xCB, 0xB4, 0x49, 0x81, 0xE8, 0x89, 0x6C,
+ 0xF9, 0x05, 0x85, 0x31, 0x71, 0x09, 0x59, 0xCF, 0x5F, 0x14, 0x95, 0x94, 0x94, 0x95, 0x95, 0x43,
+ 0x54, 0x6C, 0x66, 0xDA, 0x6B, 0xC4, 0xB0, 0xA1, 0x84, 0x47, 0x4D, 0xBF, 0x59, 0xA2, 0xC1, 0xF2,
+ 0xF2, 0x0A, 0xCA, 0x52, 0x40, 0x45, 0x45, 0x45, 0x70, 0xFF, 0x18, 0x4F, 0x24, 0x10, 0x08, 0x05,
+ 0xCD, 0x77, 0xAD, 0x23, 0x84, 0x86, 0x00, 0x8E, 0x77, 0x4D, 0x4D, 0x0D, 0xF0, 0x5F, 0x26, 0x34,
+ 0x56, 0x26, 0x69, 0xD6, 0x3A, 0xB0, 0xD0, 0x0C, 0x03, 0x03, 0x3B, 0x8D, 0x18, 0x18, 0x98, 0x66,
+ 0x18, 0x18, 0x18, 0x98, 0x66, 0x1F, 0x2B, 0x22, 0x2E, 0x5C, 0x5A, 0xB5, 0x6E, 0x83, 0x24, 0x6B,
+ 0xBA, 0xE5, 0x1A, 0x97, 0x7E, 0x43, 0x65, 0xF8, 0x09, 0x96, 0x5B, 0xFB, 0x03, 0xBF, 0x78, 0xA9,
+ 0x19, 0xE2, 0x13, 0xEF, 0x83, 0x2E, 0x52, 0x0A, 0x0D, 0x0D, 0xF4, 0xED, 0x6D, 0x6D, 0x9C, 0xA7,
+ 0x4F, 0x83, 0x83, 0xE3, 0x61, 0xA7, 0x03, 0x82, 0x8F, 0x88, 0xFA, 0xB9, 0xF7, 0xF6, 0x2D, 0xF0,
+ 0x97, 0x68, 0x21, 0xF2, 0xD2, 0x59, 0x74, 0xB0, 0xCB, 0xC7, 0x9F, 0xD0, 0xEF, 0x35, 0x2B, 0x16,
+ 0x93, 0x37, 0x1E, 0x2E, 0x15, 0x08, 0x26, 0x3A, 0x3A, 0xA3, 0x63, 0x97, 0xF9, 0xB3, 0x9C, 0xA7,
+ 0xFF, 0xDF, 0x00, 0xC3, 0x0F, 0x4B, 0x57, 0x23, 0xFE, 0xC0, 0xA9, 0x51, 0xB3, 0xE4, 0x76, 0xFA,
+ 0x99, 0xF5, 0x3E, 0xB8, 0x6F, 0x37, 0xFC, 0x2B, 0xD7, 0xB0, 0xA7, 0x55, 0x28, 0xAA, 0x30, 0x62,
+ 0xDC, 0xD7, 0x8C, 0x57, 0x85, 0x5A, 0x80, 0x6A, 0x70, 0x77, 0xF0, 0x21, 0x9F, 0x9D, 0xC2, 0x43,
+ 0x68, 0x1C, 0x2A, 0xBC, 0x6E, 0x9C, 0x6C, 0x0D, 0xA7, 0xE8, 0x67, 0xD6, 0x07, 0x2A, 0xC3, 0x01,
+ 0x4B, 0xE3, 0xE8, 0xAE, 0xD3, 0x32, 0x32, 0xD9, 0xC5, 0x42, 0xC8, 0x04, 0x8E, 0x89, 0xBD, 0x21,
+ 0xC8, 0xD2, 0x06, 0x59, 0x31, 0x0A, 0x7F, 0xD4, 0x48, 0x87, 0x51, 0x23, 0x1C, 0xE0, 0x2F, 0xB6,
+ 0x66, 0x1F, 0x39, 0x40, 0xED, 0x40, 0xFF, 0x7E, 0x58, 0xEA, 0xFA, 0x5A, 0xF4, 0x64, 0x7F, 0x76,
+ 0x9B, 0x43, 0x70, 0x0C, 0x88, 0x44, 0xD1, 0x72, 0xBE, 0x9A, 0xDA, 0x7B, 0xB5, 0xBB, 0x77, 0x9F,
+ 0xE0, 0x1E, 0x61, 0xA3, 0xC8, 0xC6, 0x8A, 0x98, 0xC2, 0x87, 0xD4, 0x0E, 0x35, 0x25, 0xA1, 0x0A,
+ 0x92, 0x7F, 0xC2, 0x62, 0xEB, 0x88, 0x7B, 0x44, 0xAC, 0x03, 0xB6, 0xB7, 0xFF, 0xDA, 0x50, 0x32,
+ 0xE0, 0xEC, 0x1B, 0x3C, 0x77, 0xCA, 0xBA, 0x11, 0xC6, 0xD6, 0x4C, 0x24, 0xD0, 0x2E, 0xA2, 0xA5,
+ 0xA5, 0x42, 0x78, 0xC6, 0xA0, 0xF7, 0xF0, 0x09, 0x38, 0x74, 0x84, 0x58, 0xE4, 0x46, 0x66, 0x02,
+ 0xC1, 0x13, 0xE0, 0x0C, 0x65, 0x31, 0x28, 0x10, 0x03, 0xD9, 0x1C, 0xA4, 0xE2, 0x84, 0xB1, 0x6A,
+ 0x66, 0x6D, 0xEC, 0x6C, 0x10, 0x7F, 0x08, 0x16, 0x91, 0xA9, 0x85, 0x4E, 0x04, 0x26, 0x05, 0x0E,
+ 0x08, 0x0E, 0xD8, 0xDB, 0x0E, 0x44, 0x86, 0x91, 0x71, 0x72, 0x0C, 0x9C, 0xC8, 0xD0, 0x40, 0x8F,
+ 0xCC, 0x64, 0x54, 0x28, 0x8A, 0x63, 0x8D, 0x76, 0xD2, 0x0F, 0x5D, 0x39, 0x54, 0x46, 0xE7, 0x42,
+ 0xD7, 0xD0, 0xD0, 0x0B, 0xD8, 0xD9, 0xB0, 0x34, 0xDE, 0x60, 0xED, 0xF5, 0xF5, 0xE0, 0xC2, 0xD8,
+ 0xC5, 0xD2, 0x3A, 0xE1, 0x03, 0xC7, 0x50, 0x6B, 0x60, 0x2A, 0x59, 0x2E, 0x1E, 0xD3, 0xAC, 0x03,
+ 0x61, 0xF9, 0xA2, 0x1F, 0xD6, 0x6F, 0xF4, 0x94, 0x7C, 0x1F, 0x32, 0x82, 0x12, 0xA0, 0x55, 0x88,
+ 0x2A, 0x60, 0x6D, 0xB6, 0x78, 0xAC, 0x45, 0xCF, 0x9B, 0xEC, 0xE1, 0x10, 0x4E, 0x1D, 0x99, 0x2A,
+ 0x88, 0x2D, 0x1B, 0xB6, 0xEE, 0x24, 0x74, 0x8E, 0x91, 0x12, 0x0D, 0x76, 0x66, 0x84, 0x03, 0xE1,
+ 0x71, 0xC1, 0xCF, 0xA1, 0x26, 0xF8, 0x60, 0xCD, 0xB9, 0x9A, 0x04, 0xAA, 0x4F, 0xD8, 0x3A, 0xF0,
+ 0x5D, 0x1B, 0xFE, 0x6D, 0xEE, 0x71, 0x51, 0x0C, 0x17, 0x7D, 0xD3, 0x2E, 0xC2, 0xEB, 0x23, 0xBB,
+ 0x6D, 0xE4, 0x4B, 0x25, 0x58, 0x84, 0x54, 0x1C, 0x98, 0x73, 0x3C, 0x2C, 0x1C, 0xEE, 0x9D, 0x42,
+ 0x12, 0xC6, 0xC6, 0x27, 0x1B, 0xE8, 0xB3, 0x8B, 0xA5, 0xA5, 0x40, 0xC2, 0x07, 0xC9, 0x38, 0xCD,
+ 0x59, 0x48, 0xEE, 0x6B, 0xB0, 0xD3, 0xD8, 0xD1, 0x61, 0x6B, 0x6D, 0xE9, 0xEF, 0xBD, 0x5D, 0xB7,
+ 0xE5, 0xAF, 0xC9, 0x33, 0x6C, 0xDA, 0x8C, 0x8D, 0xBE, 0x6D, 0x01, 0x3B, 0x08, 0x13, 0x01, 0xFA,
+ 0xB1, 0xE5, 0xE7, 0xB5, 0x2C, 0xED, 0x13, 0x0A, 0x84, 0x08, 0x86, 0x18, 0x45, 0x2A, 0x7C, 0x42,
+ 0x66, 0x02, 0xCB, 0x7A, 0x9C, 0x56, 0x83, 0xB8, 0x47, 0x08, 0x3E, 0x81, 0x27, 0x68, 0x73, 0x55,
+ 0xE8, 0x17, 0x18, 0xCD, 0x6F, 0xBB, 0xC1, 0x90, 0xB4, 0x0D, 0x5E, 0x4B, 0x85, 0x8F, 0xAD, 0xD9,
+ 0xFF, 0x1B, 0x4C, 0x7B, 0x9A, 0x04, 0xFA, 0xEC, 0x72, 0xF3, 0xD8, 0x4C, 0x5F, 0xFE, 0xCC, 0x1E,
+ 0x9B, 0xB5, 0xCE, 0xF9, 0x21, 0x88, 0xF1, 0x3A, 0x27, 0x57, 0x6C, 0xE0, 0x84, 0x2A, 0x03, 0xC1,
+ 0x40, 0xB3, 0x91, 0x49, 0xEC, 0x67, 0xD6, 0xA7, 0x54, 0x20, 0x84, 0xB3, 0x23, 0xD6, 0x11, 0x26,
+ 0x4E, 0x6C, 0x30, 0xD6, 0xE8, 0x6B, 0x65, 0x92, 0xAD, 0xA5, 0xA1, 0xB8, 0x6D, 0x1B, 0x9D, 0xA7,
+ 0x4F, 0x23, 0x9C, 0x5B, 0x94, 0x29, 0x01, 0x03, 0x0B, 0x27, 0x72, 0x99, 0x37, 0x8B, 0xF2, 0x5B,
+ 0x4A, 0x96, 0x82, 0x9C, 0xA1, 0x91, 0x46, 0x54, 0x4C, 0x76, 0x4D, 0x31, 0xCD, 0x64, 0x06, 0x60,
+ 0xCD, 0xFC, 0x77, 0x6F, 0x5F, 0xBF, 0xC9, 0x33, 0xF1, 0x41, 0x32, 0x7B, 0x4D, 0xD4, 0xA3, 0xA3,
+ 0xD8, 0x4C, 0x42, 0xFD, 0x16, 0x05, 0xE8, 0x89, 0x8F, 0x9F, 0x0A, 0x27, 0x5E, 0x19, 0xC1, 0x10,
+ 0x9E, 0x35, 0xC4, 0x5A, 0x47, 0x10, 0x33, 0x09, 0x72, 0x22, 0x6B, 0x16, 0x91, 0xF3, 0x06, 0x45,
+ 0x65, 0x88, 0x7B, 0x28, 0x76, 0x12, 0x9B, 0x74, 0x21, 0xFF, 0x0B, 0x74, 0x15, 0x4B, 0x33, 0x70,
+ 0xF9, 0xA0, 0x65, 0xB8, 0x65, 0x72, 0x58, 0x08, 0x74, 0x05, 0x86, 0x9F, 0x0A, 0x0D, 0x6C, 0x45,
+ 0x70, 0xF5, 0x81, 0x40, 0xC2, 0x27, 0xB2, 0x2F, 0x70, 0xFD, 0x86, 0xB2, 0xB0, 0xC1, 0x2B, 0xA6,
+ 0xD9, 0x7B, 0xA8, 0xAA, 0xF2, 0xBC, 0xB6, 0x6D, 0xF6, 0xDC, 0xE9, 0xCD, 0x5E, 0x8D, 0x92, 0xA1,
+ 0x06, 0x55, 0x73, 0x66, 0x7A, 0x99, 0xA0, 0x58, 0xB7, 0x07, 0x75, 0xC9, 0xA0, 0x37, 0xF6, 0x76,
+ 0x36, 0xA2, 0xF6, 0x39, 0x06, 0xE6, 0xA0, 0x9A, 0xC0, 0x28, 0x82, 0x24, 0xE4, 0xCA, 0xC4, 0x7B,
+ 0xA7, 0xA4, 0x94, 0xDA, 0x06, 0x76, 0x35, 0xA4, 0xCE, 0x47, 0x3A, 0xC0, 0x35, 0x5C, 0x8F, 0xBC,
+ 0x13, 0x71, 0xE1, 0x32, 0xBA, 0x6C, 0xB8, 0x1E, 0xD0, 0x75, 0x72, 0xFA, 0x81, 0x31, 0xBF, 0xD2,
+ 0x82, 0x1E, 0xA7, 0x54, 0xC0, 0x78, 0xCC, 0x2E, 0x7C, 0x97, 0x8E, 0xFD, 0xC2, 0x0D, 0x4C, 0x33,
+ 0x66, 0x28, 0x29, 0x2A, 0x6E, 0x5C, 0xEF, 0xD6, 0xA2, 0xB7, 0x1F, 0x40, 0xA0, 0xD2, 0xD2, 0x0E,
+ 0x15, 0xCC, 0x17, 0x98, 0xA9, 0x1F, 0x96, 0xAE, 0x6E, 0x52, 0x9D, 0xD0, 0x83, 0xFB, 0x76, 0x8B,
+ 0xF4, 0x1B, 0x47, 0x38, 0x90, 0xBB, 0x70, 0xD4, 0x7F, 0xDB, 0xD3, 0x0C, 0x14, 0xFB, 0x86, 0xE4,
+ 0x2C, 0x49, 0x0E, 0x76, 0x6C, 0xF0, 0xDC, 0x09, 0x5E, 0x99, 0xB3, 0x93, 0x63, 0xE3, 0x08, 0xA1,
+ 0x23, 0x70, 0x89, 0x18, 0xCD, 0xA3, 0x04, 0x45, 0x8C, 0x29, 0x10, 0xB1, 0x80, 0x7E, 0x04, 0xD9,
+ 0x49, 0xD4, 0xDD, 0xA0, 0x94, 0x2C, 0x71, 0x53, 0xA2, 0xEC, 0x33, 0xCA, 0x73, 0xC2, 0xC5, 0xB4,
+ 0xE2, 0x8C, 0x98, 0x66, 0x1D, 0x02, 0x1C, 0x0E, 0xC7, 0x80, 0xD5, 0xDD, 0x47, 0xE9, 0x3B, 0x62,
+ 0x98, 0x18, 0x74, 0xA2, 0xA5, 0x96, 0x04, 0xE5, 0x0F, 0xE0, 0x2F, 0xE2, 0x0F, 0xB4, 0x03, 0x07,
+ 0xA2, 0x92, 0x0A, 0x84, 0xAA, 0x21, 0xB5, 0x46, 0x8A, 0x85, 0xC8, 0x46, 0x0E, 0x0E, 0xA5, 0x61,
+ 0xCD, 0xD0, 0x70, 0x19, 0xF2, 0x4B, 0x1B, 0xCE, 0xA8, 0xAF, 0x47, 0xA6, 0x96, 0xA1, 0xBE, 0x3E,
+ 0x4B, 0xE0, 0x27, 0x61, 0xEC, 0x07, 0x54, 0x41, 0xB1, 0x1F, 0x08, 0xC1, 0x69, 0xF6, 0x42, 0xC4,
+ 0x3A, 0xE2, 0x2C, 0xF4, 0x64, 0x3D, 0x25, 0x77, 0x2A, 0xEB, 0xC0, 0xE3, 0x66, 0x62, 0x2D, 0xD2,
+ 0x6C, 0x14, 0xF4, 0x83, 0x0A, 0xB2, 0x90, 0x84, 0x8D, 0x6C, 0x4E, 0xD3, 0xD0, 0xC8, 0x5B, 0x43,
+ 0x5F, 0x7E, 0x2A, 0x5C, 0x94, 0x52, 0x02, 0x7F, 0xF8, 0x3E, 0xEF, 0x87, 0xDD, 0x08, 0xD6, 0x01,
+ 0xDF, 0x22, 0x88, 0xE1, 0x32, 0x3B, 0xA9, 0x74, 0xEA, 0xC0, 0x5E, 0x62, 0xC4, 0x0F, 0xF1, 0x8D,
+ 0x9C, 0xE1, 0xA0, 0x10, 0x9B, 0x3E, 0x52, 0x2C, 0x61, 0xEC, 0x87, 0xC6, 0xBB, 0x51, 0x3F, 0x42,
+ 0x1E, 0xF6, 0x60, 0x1F, 0xD0, 0xFB, 0x38, 0x80, 0x67, 0x81, 0x88, 0x01, 0x31, 0x1E, 0x8D, 0x48,
+ 0x22, 0xE1, 0x04, 0x42, 0x8A, 0x12, 0x13, 0xC9, 0x0F, 0x34, 0xC6, 0x2D, 0xD2, 0x6F, 0x24, 0x29,
+ 0x34, 0x71, 0x52, 0x72, 0x20, 0x24, 0x25, 0xDF, 0xA9, 0x61, 0xD7, 0xF4, 0xD0, 0x40, 0xF0, 0x8A,
+ 0x1B, 0xA8, 0xDE, 0x14, 0x6B, 0x01, 0xCF, 0xE1, 0xB2, 0xDB, 0x30, 0x8B, 0x08, 0xED, 0x43, 0x6B,
+ 0x70, 0x0A, 0xC4, 0x49, 0xF8, 0x0B, 0xC7, 0xA2, 0x46, 0xD8, 0x3F, 0x36, 0xD7, 0xA9, 0xF3, 0xAC,
+ 0x37, 0xC3, 0xC0, 0xC0, 0xD6, 0x0C, 0x03, 0x03, 0xD3, 0x0C, 0x03, 0x03, 0xA3, 0xB5, 0xF8, 0x9F,
+ 0x00, 0x03, 0x00, 0xF2, 0x82, 0x33, 0x9A, 0xC5, 0x9A, 0xEA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x49,
+ 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, 0xB0, 0x61, 0x73, 0x28, 0xB0, 0x16, 0x00
+}; //rtk-setup-wifi.png
+
+//Content of rtk-setup.png with gzip compression
+static const uint8_t rtkSetup_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x72, 0xB4, 0x35, 0x64, 0x02, 0xFF, 0x72, 0x74, 0x6B, 0x2D, 0x73, 0x65,
+ 0x74, 0x75, 0x70, 0x2E, 0x70, 0x6E, 0x67, 0x2E, 0x67, 0x7A, 0x69, 0x70, 0x00, 0x01, 0x67, 0x17,
+ 0x98, 0xE8, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48,
+ 0x44, 0x52, 0x00, 0x00, 0x01, 0x22, 0x00, 0x00, 0x00, 0x59, 0x08, 0x06, 0x00, 0x00, 0x00, 0x9A,
+ 0x2C, 0x29, 0x00, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9,
+ 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05,
+ 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E, 0xC4,
+ 0x01, 0x95, 0x2B, 0x0E, 0x1B, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66,
+ 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x49, 0x6D, 0x61, 0x67,
+ 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xC9, 0x65, 0x3C, 0x00, 0x00, 0x16, 0xD7, 0x49, 0x44,
+ 0x41, 0x54, 0x78, 0x5E, 0xED, 0x9D, 0x09, 0x78, 0x4C, 0x57, 0x1B, 0xC7, 0xDF, 0x24, 0xB2, 0x08,
+ 0x91, 0x08, 0x6A, 0x29, 0xDA, 0x6A, 0xB5, 0xA8, 0xA5, 0x54, 0x69, 0xAD, 0xB5, 0xB4, 0xB6, 0x6E,
+ 0x96, 0xD6, 0x5E, 0xB1, 0xD5, 0xAE, 0x08, 0x8A, 0xA8, 0xAD, 0xD4, 0xBE, 0x0B, 0xA1, 0x62, 0x5F,
+ 0x8A, 0xA2, 0x54, 0x55, 0x77, 0xAD, 0xD6, 0xAE, 0x7C, 0x76, 0xA5, 0x54, 0xA9, 0x9D, 0xDA, 0x92,
+ 0x48, 0x44, 0xF2, 0xDD, 0xFF, 0xC9, 0x19, 0xB9, 0x33, 0x73, 0xEF, 0xCC, 0x4D, 0x32, 0x32, 0x4B,
+ 0xDE, 0xDF, 0xF3, 0xDC, 0x67, 0xCE, 0xB9, 0x73, 0x33, 0x73, 0x67, 0x32, 0xF7, 0x7F, 0xDF, 0xF3,
+ 0x2E, 0xE7, 0x78, 0x25, 0x2B, 0x10, 0xC3, 0x30, 0x8C, 0x13, 0xF1, 0x96, 0x8F, 0x0C, 0xC3, 0x30,
+ 0x4E, 0x83, 0x2D, 0x22, 0xC6, 0xA5, 0x49, 0xD8, 0xB5, 0x9B, 0x62, 0x56, 0xAF, 0xA6, 0x84, 0xDD,
+ 0x7B, 0x28, 0xE9, 0xE6, 0x4D, 0xF2, 0xF2, 0xF3, 0x23, 0xDF, 0x92, 0x25, 0xC9, 0xFF, 0xD5, 0x9A,
+ 0x14, 0xD8, 0xB4, 0x09, 0x79, 0xE7, 0xCE, 0x2D, 0x8F, 0x64, 0xDC, 0x19, 0x16, 0x22, 0xC6, 0x25,
+ 0x49, 0x8E, 0x89, 0xA1, 0x5B, 0x23, 0x46, 0x52, 0xDC, 0x96, 0x6F, 0xE5, 0x1E, 0x6B, 0xBC, 0x02,
+ 0x03, 0x29, 0xA8, 0x47, 0x77, 0xCA, 0xD1, 0xB6, 0x8D, 0xD2, 0xF1, 0x92, 0x7B, 0x19, 0x77, 0x84,
+ 0x85, 0x88, 0x71, 0x39, 0x92, 0xEF, 0xDD, 0xA3, 0xEB, 0x1D, 0x3A, 0xD2, 0xFD, 0x43, 0x87, 0xE5,
+ 0x1E, 0x73, 0xFC, 0x2A, 0x56, 0xA4, 0x5C, 0xFD, 0xFA, 0x92, 0xEF, 0xF3, 0xA5, 0xE4, 0x1E, 0x05,
+ 0x6F, 0xF6, 0x32, 0xB8, 0x33, 0xFC, 0xDF, 0x63, 0x5C, 0x8E, 0x3B, 0xD3, 0x67, 0xE8, 0x8A, 0x90,
+ 0x6F, 0x99, 0xD2, 0x14, 0x3A, 0x2F, 0x4A, 0x3C, 0x0A, 0xF1, 0x31, 0x6D, 0x8C, 0x5B, 0xC3, 0xFF,
+ 0x41, 0xC6, 0xA5, 0x48, 0xBE, 0x7F, 0x9F, 0x62, 0xD7, 0xAE, 0x93, 0x3D, 0x6B, 0x02, 0xDF, 0x7B,
+ 0x97, 0xBC, 0x7C, 0x7D, 0x65, 0x8F, 0xF1, 0x14, 0x58, 0x88, 0x18, 0x97, 0x22, 0xE9, 0xF2, 0x65,
+ 0x4A, 0x8E, 0x8B, 0x93, 0x3D, 0x6B, 0x92, 0x6E, 0xDC, 0x90, 0x2D, 0xC6, 0x93, 0x60, 0x21, 0x62,
+ 0x5C, 0x0A, 0xAF, 0xA0, 0x20, 0xD9, 0xD2, 0x26, 0x66, 0xD9, 0x72, 0x4D, 0x31, 0x7A, 0x70, 0xF1,
+ 0x22, 0xC5, 0xFF, 0xBE, 0x5D, 0xF6, 0x18, 0x77, 0x83, 0x85, 0x88, 0x71, 0x29, 0xBC, 0x83, 0x83,
+ 0xC9, 0xB7, 0x64, 0x09, 0xD9, 0xB3, 0x26, 0xE9, 0xEA, 0x35, 0xBA, 0xDE, 0xB1, 0x33, 0x25, 0x5D,
+ 0xBF, 0x2E, 0xF7, 0xA4, 0x80, 0xD0, 0xFE, 0x8D, 0xAE, 0xDD, 0xE8, 0xCE, 0xB4, 0xE9, 0x72, 0x0F,
+ 0xE3, 0x4E, 0xB0, 0x10, 0x31, 0x2E, 0x47, 0x8E, 0xB0, 0x30, 0xD9, 0xD2, 0x26, 0xF1, 0xD4, 0x29,
+ 0xBA, 0xDA, 0xB8, 0x09, 0xC5, 0xAE, 0x5B, 0x4F, 0xC9, 0xF1, 0xF1, 0x62, 0xDF, 0x3D, 0x19, 0xE6,
+ 0xBF, 0x1B, 0xBD, 0x80, 0x62, 0x56, 0xAE, 0x14, 0x6D, 0xC6, 0x7D, 0xE0, 0xF0, 0x3D, 0xE3, 0x7A,
+ 0x24, 0x25, 0x29, 0xD6, 0x4D, 0x77, 0x8A, 0xDF, 0xB1, 0x43, 0xEE, 0xD0, 0x07, 0xB9, 0x44, 0x5E,
+ 0x01, 0x01, 0x66, 0xC3, 0x35, 0x2F, 0x7F, 0x7F, 0xCA, 0xF7, 0xD5, 0x06, 0xF2, 0x29, 0x58, 0x50,
+ 0xEE, 0x61, 0x5C, 0x1D, 0xB6, 0x88, 0x18, 0xD7, 0xC3, 0xDB, 0x9B, 0x42, 0xC6, 0x8F, 0x25, 0x9F,
+ 0x22, 0x45, 0xE4, 0x0E, 0x7D, 0x92, 0x63, 0x63, 0xAD, 0x7C, 0x46, 0xB0, 0x92, 0xEE, 0xCE, 0xFB,
+ 0x4C, 0xF6, 0x18, 0x77, 0x80, 0x85, 0x88, 0x71, 0x49, 0x50, 0xBA, 0x91, 0x67, 0x61, 0xB4, 0x21,
+ 0x31, 0xD2, 0x22, 0x6E, 0xD3, 0xD7, 0x36, 0xA3, 0x6F, 0x8C, 0x6B, 0xC1, 0x42, 0xC4, 0x38, 0x97,
+ 0x07, 0x0F, 0x28, 0xE1, 0xC0, 0x01, 0x8A, 0xFB, 0x6A, 0x93, 0x10, 0x8F, 0xFB, 0xC7, 0x4F, 0xC8,
+ 0x27, 0x88, 0x7C, 0xF2, 0xE7, 0xA7, 0x3C, 0xF3, 0x3F, 0x4B, 0x57, 0x3D, 0x19, 0xB2, 0xB3, 0xE3,
+ 0x77, 0xEE, 0x94, 0x3D, 0xC6, 0xD5, 0x61, 0x21, 0x62, 0x9C, 0x43, 0x72, 0x32, 0xC5, 0xAC, 0x58,
+ 0x49, 0x97, 0xEB, 0xBC, 0x46, 0xD7, 0xDB, 0xB6, 0xA3, 0x9B, 0x43, 0x22, 0xE8, 0x6E, 0x74, 0x34,
+ 0x25, 0xDF, 0xBA, 0x25, 0x0F, 0x48, 0xC1, 0xA7, 0x50, 0x41, 0xCA, 0x15, 0xDE, 0x57, 0xF6, 0xD2,
+ 0xC6, 0xFD, 0x03, 0x07, 0x65, 0x8B, 0x71, 0x75, 0x58, 0x88, 0x18, 0xA7, 0x70, 0xEB, 0x93, 0xD1,
+ 0x74, 0x7B, 0xEC, 0xB8, 0x87, 0x61, 0x78, 0x9F, 0xC2, 0x85, 0x29, 0xEF, 0xCA, 0x15, 0xE4, 0x57,
+ 0xB9, 0x92, 0xE8, 0xAB, 0x09, 0xA8, 0x5B, 0x57, 0xB6, 0xD2, 0x46, 0xE2, 0xF9, 0xF3, 0xB2, 0xC5,
+ 0xB8, 0x3A, 0x2C, 0x44, 0x4C, 0xA6, 0x13, 0xBF, 0x6D, 0x1B, 0xC5, 0xAE, 0xF9, 0x42, 0xF6, 0x52,
+ 0xF0, 0x2B, 0x53, 0x5A, 0x44, 0xBF, 0xB4, 0x48, 0x8A, 0x89, 0x95, 0xAD, 0xB4, 0xC1, 0x59, 0xD8,
+ 0xEE, 0x03, 0x0B, 0x11, 0x93, 0xE9, 0xC4, 0xAC, 0xF8, 0x5C, 0xB6, 0x52, 0x89, 0xDF, 0xB1, 0x53,
+ 0x57, 0x38, 0x62, 0x16, 0x2D, 0x92, 0xAD, 0x34, 0xE2, 0xC3, 0x3F, 0x6F, 0x77, 0x81, 0xFF, 0x53,
+ 0x4C, 0xA6, 0x93, 0xB0, 0x6F, 0x9F, 0x6C, 0xA5, 0x82, 0xCC, 0xE8, 0xEB, 0x61, 0x1D, 0x44, 0x99,
+ 0x06, 0x0A, 0x5F, 0x41, 0xE2, 0xE9, 0xD3, 0x74, 0x6B, 0xE4, 0x28, 0x8A, 0x59, 0xBA, 0x4C, 0xF4,
+ 0xD3, 0x8A, 0x4F, 0x9E, 0x3C, 0xB2, 0xC5, 0xB8, 0x3A, 0x9C, 0xD0, 0xC8, 0x64, 0x2A, 0x49, 0xD7,
+ 0xAE, 0xD1, 0xE5, 0x5A, 0x75, 0x64, 0x4F, 0x87, 0x6C, 0xD9, 0x44, 0x34, 0x0D, 0x0E, 0xED, 0x8C,
+ 0x10, 0xD4, 0xB3, 0x07, 0xE5, 0xEC, 0xF2, 0x81, 0xEC, 0x31, 0xAE, 0x0C, 0x5B, 0x44, 0x4C, 0xA6,
+ 0x92, 0x9C, 0x90, 0x20, 0x5B, 0x36, 0x48, 0x4C, 0xCC, 0xB0, 0x08, 0x01, 0xBF, 0xF2, 0xE5, 0x65,
+ 0x8B, 0x71, 0x75, 0x58, 0x88, 0x18, 0x8F, 0x04, 0xB9, 0x47, 0x7E, 0x15, 0x5F, 0x94, 0x3D, 0xC6,
+ 0xD5, 0x61, 0x21, 0x62, 0x3C, 0x12, 0x4C, 0xA0, 0xC6, 0x33, 0x37, 0xBA, 0x0F, 0xFC, 0x9F, 0x62,
+ 0x3C, 0x0E, 0xEF, 0xA0, 0x20, 0xCA, 0xD1, 0xBA, 0xB5, 0xEC, 0x31, 0xEE, 0x00, 0x0B, 0x11, 0xE3,
+ 0x71, 0x04, 0xF5, 0xED, 0xA3, 0x0C, 0xCD, 0x42, 0x64, 0x8F, 0x71, 0x07, 0x58, 0x88, 0x18, 0x8F,
+ 0x22, 0xA0, 0x4E, 0x6D, 0x0A, 0x6C, 0xD6, 0x54, 0xF6, 0x18, 0x77, 0x81, 0x85, 0x88, 0xF1, 0x18,
+ 0xB0, 0xB2, 0x47, 0xC8, 0xE8, 0xD1, 0xC4, 0x6B, 0x9C, 0xB9, 0x1F, 0x2C, 0x44, 0x8C, 0x47, 0xE0,
+ 0x5B, 0xB6, 0x2C, 0x85, 0xCE, 0x99, 0x4D, 0x5E, 0x39, 0x73, 0xC8, 0x3D, 0x8C, 0x3B, 0xC1, 0x42,
+ 0xC4, 0xB8, 0x37, 0x8A, 0xF5, 0x13, 0xD8, 0xA2, 0x39, 0xE5, 0x59, 0xB4, 0x40, 0xCC, 0x77, 0xCD,
+ 0xB8, 0x27, 0x2C, 0x44, 0x8C, 0xDB, 0xE2, 0x5F, 0xAD, 0x1A, 0xE5, 0x5D, 0xB6, 0x94, 0x82, 0x23,
+ 0x86, 0xF0, 0x5A, 0x67, 0x6E, 0x0E, 0x97, 0x78, 0x30, 0x99, 0xCA, 0x83, 0x0B, 0x17, 0xE8, 0x4A,
+ 0xBD, 0x06, 0xB2, 0x67, 0x1B, 0xAF, 0xEC, 0xD9, 0xCD, 0xA2, 0x5F, 0xDE, 0x21, 0xB9, 0x29, 0x5B,
+ 0xD1, 0x22, 0xE4, 0x5B, 0xBE, 0x3C, 0x05, 0xD4, 0xAC, 0x41, 0x3E, 0x8F, 0x3F, 0x2E, 0x9F, 0x61,
+ 0xDC, 0x1D, 0x16, 0x22, 0x26, 0x53, 0x31, 0x22, 0x44, 0x98, 0x1E, 0x36, 0xF8, 0xA3, 0x81, 0x8A,
+ 0xC5, 0x53, 0x55, 0xE9, 0xF8, 0xC8, 0xBD, 0x8C, 0x27, 0xC3, 0x43, 0x33, 0xC6, 0xA5, 0xF0, 0x7B,
+ 0xA9, 0x22, 0xE5, 0x5B, 0xFD, 0x39, 0xF9, 0x2B, 0x16, 0x0F, 0x8B, 0x50, 0xD6, 0x81, 0x85, 0x88,
+ 0x71, 0x19, 0xFC, 0x2A, 0x54, 0xA0, 0xD0, 0xD9, 0x91, 0xE4, 0x95, 0x33, 0xA7, 0xDC, 0xC3, 0x64,
+ 0x15, 0x58, 0x88, 0x18, 0x97, 0x20, 0x5B, 0xF1, 0xE2, 0x14, 0x1A, 0x39, 0x53, 0x77, 0x96, 0x46,
+ 0xC6, 0xB3, 0x61, 0x21, 0x62, 0x9C, 0x8E, 0x4F, 0xA1, 0x42, 0x14, 0x1A, 0x35, 0x87, 0x2D, 0xA1,
+ 0x2C, 0x0C, 0x0B, 0x11, 0xE3, 0x54, 0x10, 0x15, 0x0B, 0x9D, 0x17, 0x45, 0x3E, 0x8F, 0xE5, 0x93,
+ 0x7B, 0x98, 0xAC, 0x08, 0x47, 0xCD, 0x18, 0x87, 0x92, 0xA4, 0xFC, 0x9C, 0x7E, 0xF8, 0xE9, 0x17,
+ 0x0A, 0x09, 0x09, 0xA6, 0x4A, 0x2F, 0x5A, 0x4F, 0x4C, 0xA6, 0x8E, 0x9A, 0x61, 0x69, 0xE8, 0x3C,
+ 0x0B, 0xA2, 0xC9, 0xB7, 0x6C, 0x19, 0xD1, 0x37, 0x4A, 0x7C, 0x7C, 0x02, 0x6D, 0xDB, 0xBE, 0x93,
+ 0x0E, 0x1C, 0x3A, 0x4C, 0x17, 0x2F, 0x5F, 0xA1, 0xBB, 0x77, 0x63, 0xC8, 0xCF, 0xCF, 0x8F, 0x72,
+ 0xE6, 0xC8, 0x41, 0x85, 0x0A, 0xE4, 0xA7, 0x5E, 0x5D, 0x3B, 0xCA, 0x23, 0x19, 0x77, 0x81, 0x85,
+ 0x88, 0x71, 0x18, 0xFB, 0xF6, 0xFF, 0x8F, 0x66, 0xCF, 0x5F, 0x4C, 0x7F, 0x9E, 0xFA, 0x8B, 0x86,
+ 0x84, 0xF7, 0xA6, 0x06, 0xAF, 0x5B, 0x4F, 0x09, 0xFB, 0x50, 0x88, 0xBC, 0xBD, 0x29, 0x74, 0xD6,
+ 0x0C, 0xF2, 0xAF, 0x5E, 0x5D, 0x3E, 0x63, 0x8C, 0x2D, 0x3F, 0xFC, 0x4C, 0xB3, 0x3F, 0x5B, 0x48,
+ 0xFF, 0xDD, 0x34, 0x5F, 0xFF, 0xCC, 0x44, 0x81, 0xC7, 0x1E, 0xA3, 0x35, 0x4B, 0x79, 0xB9, 0x69,
+ 0x77, 0x83, 0x87, 0x66, 0x4C, 0x86, 0xF9, 0xEB, 0xCC, 0xDF, 0xD4, 0x3F, 0x62, 0x24, 0xF5, 0x19,
+ 0x34, 0x4C, 0x88, 0x90, 0x11, 0x82, 0x87, 0x0F, 0x4B, 0xB3, 0x08, 0xAD, 0x5E, 0xBF, 0x91, 0xC6,
+ 0x4C, 0x9C, 0xA6, 0x2B, 0x42, 0x8C, 0xFB, 0xC2, 0x42, 0xC4, 0x64, 0x88, 0xA9, 0x91, 0x73, 0xA9,
+ 0x7D, 0xB7, 0x3E, 0xB4, 0x6B, 0xEF, 0x1F, 0x72, 0x8F, 0x7D, 0x72, 0xB4, 0x0F, 0xA3, 0xC0, 0x26,
+ 0x8D, 0x65, 0xCF, 0x18, 0xE7, 0x2F, 0x5C, 0x54, 0x2C, 0xA1, 0x74, 0x2E, 0x2B, 0xC4, 0xB8, 0x3C,
+ 0x2C, 0x44, 0x4C, 0x86, 0xD8, 0xBE, 0x73, 0x2F, 0xA5, 0x65, 0x74, 0xEF, 0x9D, 0x2F, 0x1F, 0xE5,
+ 0xEA, 0xF3, 0xA1, 0xEC, 0x19, 0x67, 0xFD, 0x57, 0x9B, 0xE9, 0x01, 0x56, 0xF6, 0xD0, 0x21, 0x77,
+ 0x48, 0x30, 0xE5, 0x08, 0x0C, 0x94, 0x3D, 0xC6, 0xDD, 0x60, 0x21, 0x62, 0x32, 0x15, 0x51, 0x9C,
+ 0x9A, 0x8E, 0xB9, 0xA4, 0xF7, 0xFF, 0xEF, 0x90, 0x6C, 0x99, 0x53, 0xAA, 0xC4, 0xB3, 0xB4, 0x69,
+ 0xF5, 0x52, 0xDA, 0xB8, 0x6A, 0x09, 0x6D, 0x59, 0xBF, 0x92, 0xFD, 0x43, 0x6E, 0x0A, 0x0B, 0x11,
+ 0xE3, 0x16, 0x5C, 0xBD, 0xA6, 0xBD, 0x0A, 0xEC, 0x3B, 0x8D, 0xEA, 0x53, 0x70, 0x70, 0x2E, 0xD9,
+ 0x63, 0xDC, 0x15, 0x16, 0x22, 0xC6, 0x2D, 0xB8, 0x9F, 0x98, 0xB2, 0xFA, 0xAB, 0x25, 0x79, 0xF3,
+ 0xF2, 0x6A, 0xAE, 0x9E, 0x40, 0x96, 0x0A, 0xDF, 0x27, 0x25, 0x25, 0xD1, 0x81, 0x43, 0x47, 0x44,
+ 0x98, 0xF9, 0xCC, 0xD9, 0x73, 0x74, 0xF9, 0xCA, 0x55, 0xBA, 0x73, 0xF7, 0x2E, 0x25, 0x27, 0x25,
+ 0x53, 0x40, 0x80, 0xBF, 0xC8, 0x7D, 0x29, 0x5A, 0xF8, 0x71, 0x2A, 0x57, 0xBA, 0x14, 0xBD, 0x5C,
+ 0xA9, 0x22, 0xE5, 0x0A, 0x32, 0x9E, 0xE9, 0x7B, 0xFF, 0xFE, 0x7D, 0xFA, 0xFB, 0x9F, 0xF3, 0x54,
+ 0xA4, 0x70, 0x21, 0x0A, 0xF0, 0xF7, 0x17, 0x7E, 0x93, 0x3D, 0x7F, 0x1C, 0xA0, 0xAD, 0xDB, 0xB6,
+ 0xD3, 0x89, 0x93, 0x7F, 0xD1, 0xED, 0xDB, 0x77, 0xC8, 0xDB, 0xC7, 0x9B, 0xF2, 0x84, 0xE6, 0xA6,
+ 0x67, 0x8A, 0x3D, 0x45, 0x55, 0x2A, 0x57, 0xA4, 0x4A, 0x15, 0x2B, 0x90, 0x77, 0x1A, 0xA7, 0x35,
+ 0xBD, 0x77, 0xEF, 0x1E, 0xED, 0xDD, 0x7F, 0x90, 0xF6, 0x1F, 0x3C, 0x44, 0xE7, 0xCE, 0xFF, 0x2B,
+ 0x2C, 0x05, 0xE4, 0xD1, 0x00, 0x5F, 0xBF, 0x6C, 0x94, 0x3B, 0x24, 0x84, 0x0A, 0xE4, 0x7F, 0x8C,
+ 0x4A, 0x14, 0x7F, 0x86, 0x5E, 0x7A, 0xF1, 0x05, 0x7A, 0xB2, 0x68, 0x11, 0xF1, 0x9C, 0x11, 0xE0,
+ 0x83, 0xF9, 0x47, 0x79, 0xCD, 0x3C, 0xA1, 0xA1, 0x0F, 0x3F, 0xBB, 0xE9, 0x73, 0xFC, 0xB0, 0x75,
+ 0x1B, 0x9D, 0x3C, 0x75, 0x5A, 0xBC, 0x57, 0xF9, 0x72, 0xA5, 0x69, 0x48, 0xFF, 0x14, 0x3F, 0xCF,
+ 0xBB, 0x6D, 0x3B, 0xD3, 0xA5, 0x2B, 0x57, 0x44, 0x5B, 0x8D, 0x5E, 0xF8, 0x1E, 0xDC, 0x4F, 0x4C,
+ 0xA4, 0x5D, 0x7B, 0xF6, 0xE9, 0xFA, 0x96, 0x4A, 0x95, 0x78, 0x4E, 0x7C, 0x4F, 0xF8, 0x7F, 0xED,
+ 0xDA, 0xBB, 0x9F, 0x12, 0x15, 0x11, 0x1A, 0x35, 0x6E, 0x0A, 0xDD, 0x8B, 0x8F, 0x97, 0x47, 0xA4,
+ 0xD2, 0xA9, 0x5D, 0x6B, 0x2A, 0xF6, 0x64, 0x51, 0xD9, 0x23, 0x2A, 0x5A, 0xA4, 0x30, 0x3D, 0xA1,
+ 0x6C, 0x7F, 0x28, 0x43, 0xB9, 0x98, 0x98, 0x94, 0xEF, 0x45, 0xCD, 0xB3, 0xCF, 0x3C, 0x4D, 0xF9,
+ 0xED, 0x24, 0x4F, 0xEE, 0xD8, 0xBD, 0x57, 0x79, 0xCF, 0x44, 0xD9, 0x4B, 0xA5, 0xEC, 0xF3, 0xA5,
+ 0x1E, 0x5A, 0x5F, 0xF8, 0xED, 0x68, 0x45, 0x08, 0x4D, 0xEF, 0x0F, 0xF0, 0x39, 0x7F, 0xFE, 0xF5,
+ 0x77, 0xDA, 0xBE, 0x73, 0x37, 0x9D, 0xFE, 0xFB, 0x1F, 0x8A, 0x8B, 0xBB, 0x47, 0x39, 0x72, 0x04,
+ 0x8A, 0xDF, 0xD9, 0x33, 0xC5, 0x9E, 0xA4, 0x57, 0xAB, 0x57, 0xA1, 0xD2, 0x25, 0x4B, 0x88, 0x63,
+ 0xB3, 0x3A, 0x59, 0x42, 0x88, 0xF0, 0x11, 0xBF, 0xF9, 0xFE, 0x27, 0x5A, 0xB4, 0x7C, 0x15, 0x5D,
+ 0xBC, 0x74, 0x59, 0xEE, 0xB5, 0x0D, 0x12, 0xE4, 0xDE, 0x6A, 0xF8, 0x3A, 0x85, 0xB5, 0x6E, 0x41,
+ 0xC1, 0xB9, 0x82, 0xE4, 0xDE, 0x54, 0xFE, 0x38, 0x70, 0x90, 0x0E, 0x1F, 0x3B, 0x41, 0xA7, 0xCF,
+ 0xFC, 0x4D, 0xA7, 0x94, 0xED, 0xDC, 0xF9, 0x0B, 0xE2, 0xC2, 0x59, 0xB3, 0x24, 0xC5, 0x47, 0x31,
+ 0x72, 0xDC, 0x64, 0x3A, 0x7C, 0xF4, 0xB8, 0x68, 0xEB, 0x01, 0xD1, 0xEA, 0xDB, 0xA3, 0x0B, 0xBD,
+ 0x54, 0xE1, 0x05, 0xB9, 0x47, 0x9F, 0x5B, 0x8A, 0x90, 0x2D, 0x5F, 0xBD, 0x96, 0x36, 0x6C, 0xDA,
+ 0x42, 0xB1, 0x71, 0x71, 0x72, 0xAF, 0x7D, 0x9E, 0x2F, 0xF9, 0x1C, 0x75, 0xEB, 0xD8, 0x8E, 0xCA,
+ 0x95, 0x79, 0x5E, 0xEE, 0x49, 0xE5, 0xD8, 0x89, 0x93, 0x22, 0x29, 0xF0, 0xAF, 0xD3, 0x67, 0x45,
+ 0x08, 0xFE, 0xEC, 0x3F, 0xE7, 0xC4, 0xC5, 0x33, 0x63, 0xE2, 0x18, 0x2A, 0x5F, 0xB6, 0xB4, 0x88,
+ 0x54, 0x8D, 0x99, 0x30, 0x4D, 0xF9, 0x9C, 0xE6, 0x9F, 0x03, 0x42, 0xBA, 0x70, 0xCE, 0x34, 0xD1,
+ 0x4E, 0xAB, 0x10, 0xE1, 0x3B, 0x8A, 0x18, 0x35, 0x96, 0x7E, 0xDB, 0xB1, 0x5B, 0xEE, 0x31, 0xE7,
+ 0xB5, 0x5A, 0x35, 0x68, 0xE8, 0xC0, 0xBE, 0xA2, 0x3D, 0x7A, 0xC2, 0x54, 0xFA, 0xFE, 0xE7, 0x5F,
+ 0x45, 0xDB, 0x28, 0xED, 0xDB, 0xB4, 0xA0, 0x0E, 0x6D, 0x5B, 0x8A, 0x48, 0xDE, 0xA9, 0xD3, 0x67,
+ 0xE4, 0xDE, 0x54, 0x6C, 0x09, 0xA4, 0x89, 0xFA, 0x4D, 0x5A, 0x2A, 0x22, 0x16, 0x2B, 0x7B, 0xA9,
+ 0x98, 0xBE, 0x17, 0xF0, 0xCD, 0x77, 0x3F, 0xD2, 0xA7, 0x93, 0x67, 0x88, 0xB6, 0x1A, 0xD3, 0xFB,
+ 0x43, 0xBC, 0xC7, 0x4F, 0x9D, 0x25, 0x04, 0xCB, 0x16, 0x15, 0xCA, 0x95, 0xA1, 0x61, 0x83, 0xC2,
+ 0x85, 0xF0, 0x66, 0x65, 0x3C, 0x7E, 0x68, 0x86, 0x1F, 0x3E, 0x7E, 0xD0, 0x63, 0x95, 0x1F, 0x8D,
+ 0x51, 0x11, 0x02, 0x09, 0x09, 0x09, 0xF4, 0xC5, 0x97, 0x9B, 0xA8, 0x63, 0x8F, 0xBE, 0xE2, 0x82,
+ 0xB4, 0x64, 0xE6, 0xDC, 0x05, 0xF4, 0xD9, 0xA2, 0x65, 0xF4, 0xE3, 0x2F, 0xBF, 0x29, 0x17, 0xF0,
+ 0x79, 0xF1, 0x3E, 0xE0, 0xC2, 0xC5, 0x4B, 0xD4, 0xB5, 0xCF, 0x40, 0xBB, 0x22, 0x04, 0x20, 0x5E,
+ 0xFD, 0x06, 0x0F, 0xA7, 0x15, 0xAB, 0xD7, 0xC9, 0x3D, 0xDA, 0x1C, 0xFB, 0xF3, 0x24, 0xBD, 0xFF,
+ 0x41, 0x2F, 0x5A, 0xB9, 0x66, 0x7D, 0x9A, 0x44, 0x08, 0x1C, 0x51, 0xC4, 0xB2, 0xD7, 0x80, 0x08,
+ 0xDA, 0xB8, 0xF9, 0x5B, 0xB9, 0x27, 0x95, 0x25, 0x2B, 0xD7, 0x88, 0x90, 0xF8, 0xB7, 0x3F, 0xFE,
+ 0x2C, 0x2E, 0x5A, 0x88, 0x90, 0x09, 0xDC, 0xED, 0x3B, 0xF5, 0xEC, 0x67, 0x25, 0x42, 0x19, 0xC1,
+ 0xF4, 0xBF, 0xD0, 0x13, 0xA1, 0x7A, 0x75, 0x6A, 0x65, 0x48, 0x84, 0x5C, 0x05, 0x7C, 0x9F, 0xFD,
+ 0x87, 0x8C, 0xB0, 0x2B, 0x42, 0x00, 0x96, 0x5B, 0xCF, 0xF0, 0xC1, 0x0F, 0xAD, 0xDA, 0xAC, 0x8A,
+ 0xC7, 0x0B, 0x11, 0x2E, 0xB6, 0xEF, 0x7E, 0xFA, 0x45, 0xF6, 0xD2, 0x0E, 0x7E, 0x4C, 0xE1, 0xCA,
+ 0x8F, 0x0A, 0x43, 0x22, 0x23, 0xE0, 0x2E, 0x79, 0xFD, 0xC6, 0x7F, 0xB2, 0x67, 0x8C, 0x39, 0xD1,
+ 0x8B, 0x69, 0xC3, 0xD7, 0xD6, 0x42, 0x01, 0xFE, 0x55, 0x84, 0xAD, 0xEF, 0xA0, 0x61, 0x74, 0xE3,
+ 0xBF, 0xB4, 0xBD, 0xA6, 0x1A, 0x58, 0x84, 0x93, 0x67, 0x46, 0x09, 0xAB, 0xC7, 0x08, 0x77, 0xEE,
+ 0xDC, 0xA5, 0x41, 0xC3, 0xC7, 0x68, 0x5A, 0x05, 0xE9, 0x05, 0xE7, 0x30, 0x6E, 0xCA, 0x4C, 0x5D,
+ 0x71, 0x81, 0xD3, 0x39, 0x62, 0xC0, 0x87, 0xF4, 0x40, 0x11, 0xAB, 0x51, 0xE3, 0xA7, 0xB8, 0xAD,
+ 0x08, 0x1D, 0x57, 0x6E, 0x1A, 0x63, 0x27, 0xCF, 0x14, 0xA5, 0x2E, 0x46, 0xC1, 0x8D, 0x6E, 0xEE,
+ 0xC2, 0xA5, 0xB2, 0x97, 0x35, 0xF1, 0x68, 0x21, 0x8A, 0x89, 0x8D, 0x55, 0x86, 0x33, 0xDA, 0xD6,
+ 0x46, 0xB1, 0x27, 0x9F, 0xA0, 0xEE, 0x9D, 0xC3, 0x68, 0xEC, 0x88, 0x21, 0xCA, 0x16, 0x41, 0xE1,
+ 0xBD, 0xBB, 0x51, 0x95, 0xCA, 0x2F, 0xA5, 0x3C, 0x69, 0x01, 0xAC, 0x9C, 0x55, 0xEB, 0x36, 0xCA,
+ 0x9E, 0x6D, 0x2C, 0xEF, 0x82, 0xF0, 0x29, 0x14, 0x7B, 0xEA, 0x09, 0xBB, 0x91, 0x9D, 0xE9, 0x73,
+ 0x3E, 0x13, 0x16, 0x92, 0x25, 0x53, 0x67, 0xCD, 0xD5, 0x15, 0x04, 0xD4, 0x72, 0x0D, 0xEC, 0xD3,
+ 0x83, 0x26, 0x7C, 0xF2, 0x31, 0x7D, 0x32, 0xF4, 0x23, 0xEA, 0xF9, 0x41, 0x07, 0xF1, 0xB9, 0xB4,
+ 0x80, 0x35, 0xF2, 0xC5, 0x86, 0x4D, 0xB2, 0x67, 0x9B, 0x25, 0x2B, 0x57, 0xD3, 0xD5, 0x6B, 0xD7,
+ 0x65, 0x2F, 0xE3, 0x40, 0x84, 0xA6, 0x28, 0x42, 0x88, 0xE1, 0xB1, 0x16, 0x8D, 0xDF, 0x6C, 0x48,
+ 0xFD, 0x7A, 0x75, 0x25, 0x2F, 0x2F, 0x2F, 0x8A, 0x9C, 0xB7, 0x90, 0x8E, 0x1C, 0x3D, 0x21, 0x4A,
+ 0x35, 0xD4, 0x1B, 0x9E, 0xD3, 0x22, 0x34, 0x77, 0x88, 0xD9, 0x71, 0x39, 0x9D, 0xBC, 0x8A, 0xC7,
+ 0x8E, 0xDD, 0xFB, 0x6C, 0xE6, 0x3B, 0xE9, 0xB1, 0x59, 0x19, 0xEA, 0x19, 0xBD, 0xD9, 0x79, 0x22,
+ 0x1E, 0x2D, 0x44, 0xC8, 0x3D, 0xD1, 0xFA, 0xE7, 0xE6, 0xCD, 0x13, 0x4A, 0x73, 0x67, 0x4C, 0xA4,
+ 0x96, 0xCD, 0x1A, 0x53, 0xB5, 0x57, 0x2A, 0x2B, 0x5B, 0x25, 0x71, 0x47, 0x1E, 0x3F, 0x6A, 0xA8,
+ 0xF0, 0xD9, 0x68, 0x81, 0x1A, 0xA7, 0xB4, 0x80, 0xB1, 0xFF, 0xBC, 0x19, 0x93, 0x44, 0x8E, 0xCB,
+ 0xE2, 0xA8, 0x19, 0xE2, 0x71, 0xCE, 0xD4, 0xF1, 0xBA, 0xCE, 0x49, 0x38, 0xBB, 0xE7, 0x2D, 0x32,
+ 0xBF, 0x2B, 0x42, 0x00, 0xF5, 0x32, 0x96, 0xC3, 0x5A, 0x37, 0xA7, 0xC9, 0x9F, 0x8E, 0xA0, 0x37,
+ 0x1B, 0xBC, 0x4E, 0xAF, 0x54, 0xAA, 0x28, 0x1C, 0x9F, 0xCD, 0x9B, 0xBE, 0x4D, 0xF3, 0x23, 0xA7,
+ 0x50, 0x89, 0x67, 0x9F, 0x91, 0x47, 0x99, 0x73, 0xF8, 0x88, 0xB1, 0x61, 0x16, 0x9C, 0xEB, 0x96,
+ 0xF8, 0x66, 0xCB, 0x46, 0xF9, 0xF2, 0xE6, 0xD1, 0xF4, 0x97, 0xD9, 0x63, 0x96, 0x32, 0x8C, 0xFD,
+ 0xF2, 0xEB, 0x2D, 0xB2, 0x67, 0x4E, 0xEB, 0xF7, 0x9A, 0x52, 0xBF, 0x9E, 0x5D, 0x1E, 0x0A, 0x4D,
+ 0x9F, 0xEE, 0x9D, 0x45, 0x2E, 0x90, 0xE5, 0x16, 0x18, 0x98, 0x5D, 0x3C, 0x6F, 0xC9, 0x88, 0x21,
+ 0x03, 0xCC, 0x8E, 0x7B, 0xAF, 0xF1, 0x5B, 0xF2, 0x19, 0xE7, 0xF2, 0x58, 0xBE, 0xBC, 0x34, 0x38,
+ 0xBC, 0x37, 0xAD, 0x5F, 0xB1, 0x90, 0x7E, 0xDA, 0xF4, 0x05, 0xAD, 0x5B, 0xBE, 0x40, 0xDC, 0x28,
+ 0xFC, 0xFD, 0xFD, 0xE4, 0x11, 0xE6, 0xC0, 0x15, 0x80, 0x20, 0x44, 0x56, 0xC5, 0xA3, 0x85, 0x08,
+ 0x51, 0x2C, 0x2D, 0x10, 0x55, 0x42, 0x64, 0x4B, 0x8B, 0xC6, 0x6F, 0x36, 0x10, 0x17, 0x9C, 0x25,
+ 0xE7, 0xFF, 0xBD, 0x60, 0xB8, 0xC6, 0x09, 0x8E, 0xE1, 0x49, 0x8A, 0x48, 0x94, 0x7C, 0xAE, 0xB8,
+ 0xDC, 0x93, 0x42, 0xE9, 0x52, 0x25, 0x68, 0xC6, 0xA4, 0x31, 0xC2, 0x81, 0xAC, 0xC5, 0xAF, 0xBF,
+ 0xED, 0xA0, 0x2B, 0x57, 0xAF, 0xC9, 0x1E, 0x1C, 0xE2, 0xDA, 0x49, 0x7C, 0xB0, 0xAE, 0xDA, 0xB5,
+ 0x6A, 0x2E, 0x7B, 0xE6, 0x40, 0x30, 0x1A, 0xD5, 0xAB, 0x2B, 0x7B, 0xE6, 0xDC, 0xBA, 0x7D, 0x5B,
+ 0xB6, 0x8C, 0x81, 0x88, 0x5E, 0x93, 0xB7, 0x1A, 0x52, 0xB4, 0x22, 0x6E, 0x3F, 0x7D, 0xBD, 0x56,
+ 0x5C, 0x4C, 0x9B, 0xD6, 0x2C, 0xA3, 0x79, 0x33, 0x27, 0xC9, 0x23, 0xEC, 0x13, 0x15, 0xBD, 0x44,
+ 0xD4, 0x88, 0x69, 0xD1, 0xA6, 0x79, 0x53, 0xEA, 0xDA, 0xF1, 0x7D, 0xD9, 0xF3, 0x1C, 0x90, 0xE5,
+ 0x1D, 0x35, 0x6D, 0x02, 0x35, 0x7C, 0xBD, 0x8E, 0xB8, 0xE9, 0xF9, 0xFA, 0xFA, 0x8A, 0xDF, 0x14,
+ 0x6E, 0x14, 0xE1, 0xBD, 0xBA, 0xC9, 0xA3, 0xAC, 0xD1, 0x72, 0xAE, 0x67, 0x15, 0x3C, 0x5A, 0x88,
+ 0x4C, 0x0E, 0x64, 0x4B, 0x10, 0x2D, 0xD2, 0xB3, 0x34, 0x70, 0x67, 0x1E, 0x31, 0xB8, 0x3F, 0x7D,
+ 0x3A, 0x7C, 0xB0, 0xD5, 0xE6, 0x67, 0x60, 0xC9, 0x1A, 0x6F, 0x6F, 0x6F, 0x1A, 0xD4, 0xAF, 0x97,
+ 0x10, 0x04, 0x2D, 0xB0, 0x3F, 0xA2, 0x7F, 0x1F, 0x71, 0x9C, 0x25, 0xF0, 0x2B, 0xFC, 0xA2, 0x88,
+ 0x91, 0x89, 0x5C, 0x8A, 0xF5, 0xD1, 0xE0, 0xB5, 0xDA, 0x56, 0x5B, 0x2B, 0xC5, 0x92, 0xCB, 0x96,
+ 0x4D, 0x7F, 0x3E, 0x67, 0xFC, 0x9D, 0x16, 0x09, 0x8A, 0xD5, 0x65, 0x14, 0x88, 0xD0, 0x98, 0xE1,
+ 0x43, 0x84, 0x85, 0x88, 0x90, 0xB7, 0x1A, 0xBD, 0xCF, 0x66, 0xC9, 0xE2, 0x15, 0xAB, 0x45, 0xA4,
+ 0x4F, 0x8B, 0x2E, 0x1D, 0xDE, 0x17, 0x9B, 0x27, 0xD2, 0xA6, 0x79, 0x33, 0xCD, 0x9B, 0x19, 0xA8,
+ 0x5F, 0xB7, 0x96, 0x6E, 0x84, 0xEC, 0xD2, 0x65, 0xEB, 0xE8, 0x63, 0x56, 0xC1, 0xA3, 0x85, 0x08,
+ 0xE6, 0xB1, 0x16, 0x18, 0xC3, 0xA3, 0x5A, 0x7C, 0xE0, 0xC7, 0x9F, 0x88, 0xB1, 0xB9, 0xA5, 0x5F,
+ 0xA7, 0x6C, 0xE9, 0x52, 0x54, 0xBD, 0xCA, 0xCB, 0x56, 0x1B, 0x72, 0x40, 0xEC, 0x01, 0xBF, 0x4D,
+ 0xE1, 0x42, 0x05, 0x65, 0x4F, 0x1B, 0x84, 0xED, 0x2B, 0x57, 0xB4, 0x9E, 0xAB, 0x07, 0x1C, 0x51,
+ 0x45, 0xA9, 0x6A, 0x54, 0x7D, 0x59, 0xE4, 0xEB, 0x58, 0x6E, 0xAD, 0xDE, 0x6B, 0x22, 0x8F, 0xB0,
+ 0x06, 0x51, 0xB5, 0x8D, 0x9B, 0xBF, 0x93, 0xBD, 0xF4, 0xD3, 0xF8, 0xAD, 0x46, 0x62, 0xC8, 0x9A,
+ 0x5E, 0xD6, 0x6F, 0xFA, 0x86, 0xE6, 0x2F, 0x5E, 0x2E, 0x7B, 0xE6, 0x40, 0x80, 0x60, 0x0D, 0x79,
+ 0x2A, 0xF8, 0xBF, 0xE9, 0x81, 0x1B, 0x9D, 0xDE, 0xF0, 0x1C, 0x3E, 0xCD, 0xAC, 0x8A, 0x47, 0x0B,
+ 0x11, 0x9C, 0xCF, 0xC8, 0x07, 0xD2, 0x03, 0x89, 0x6B, 0x08, 0xEB, 0x37, 0x6B, 0xDB, 0x89, 0xDE,
+ 0x7D, 0xBF, 0x33, 0x8D, 0x18, 0x3B, 0x89, 0xD6, 0xAC, 0xFF, 0x8A, 0x4E, 0xFE, 0x75, 0x26, 0x4D,
+ 0x51, 0x0F, 0x35, 0x15, 0xCB, 0x97, 0x93, 0x2D, 0xDB, 0x54, 0xAE, 0x58, 0x41, 0xB6, 0xCC, 0xD1,
+ 0xF2, 0xCF, 0xD8, 0x02, 0xD1, 0xB4, 0x83, 0x87, 0x8F, 0xD2, 0x57, 0xDF, 0x7C, 0x47, 0x93, 0x67,
+ 0xCC, 0xA1, 0x16, 0x61, 0x5D, 0x44, 0xC2, 0x66, 0x46, 0x69, 0xD9, 0xEC, 0x1D, 0xD9, 0x4A, 0x1F,
+ 0xB0, 0x3A, 0xF5, 0x78, 0xBC, 0x60, 0x01, 0xD9, 0xF2, 0x3C, 0x02, 0xB3, 0x67, 0x17, 0x43, 0x7F,
+ 0x5B, 0xE4, 0xCB, 0xA7, 0x6D, 0x2D, 0x25, 0x26, 0xA6, 0xDD, 0xC9, 0xED, 0x29, 0x78, 0xB4, 0x10,
+ 0x21, 0x3B, 0xB8, 0x57, 0x97, 0x0E, 0xB2, 0x67, 0x1B, 0x98, 0xC5, 0x3F, 0x6E, 0xDD, 0x46, 0x33,
+ 0xA2, 0xE6, 0x53, 0x87, 0xEE, 0x7D, 0xA8, 0x49, 0xAB, 0xF6, 0x34, 0x6D, 0xB6, 0x76, 0x24, 0xCB,
+ 0x16, 0xC8, 0xAC, 0x35, 0xC2, 0x53, 0x4F, 0xA4, 0x66, 0x03, 0xAB, 0xB9, 0x7A, 0x5D, 0x3B, 0x5A,
+ 0x85, 0xC8, 0x13, 0x72, 0x82, 0x30, 0xDC, 0x19, 0x3C, 0x62, 0x0C, 0xB5, 0xEC, 0xD0, 0x8D, 0x6A,
+ 0x37, 0x6A, 0x4A, 0x6F, 0xB7, 0x08, 0xA3, 0x1E, 0xE1, 0x83, 0x69, 0xC2, 0xB4, 0x48, 0xE1, 0x10,
+ 0x76, 0xC4, 0x5C, 0x3D, 0x88, 0xF2, 0xD9, 0xCB, 0x3E, 0xCE, 0x08, 0x93, 0x67, 0x45, 0xD1, 0xCD,
+ 0x5B, 0x9E, 0x39, 0xA7, 0x50, 0xAE, 0x20, 0xFB, 0xCE, 0x7C, 0x88, 0x15, 0x63, 0x8E, 0x47, 0x0B,
+ 0x11, 0x78, 0xE7, 0x8D, 0x06, 0x34, 0x2A, 0x62, 0x20, 0x85, 0xE6, 0x4E, 0x5B, 0xE6, 0x2A, 0x72,
+ 0x81, 0xD6, 0x6E, 0xD8, 0x44, 0x6D, 0x3A, 0xF7, 0x10, 0x79, 0x3E, 0x7A, 0xFE, 0x26, 0x4B, 0x82,
+ 0x82, 0x8C, 0x85, 0x8F, 0x83, 0x74, 0x7E, 0xB0, 0x98, 0x06, 0x15, 0x11, 0x34, 0x35, 0x28, 0x13,
+ 0x69, 0xDD, 0xA9, 0xBB, 0x48, 0x94, 0xC4, 0x70, 0x07, 0x09, 0x81, 0x70, 0x9E, 0xAB, 0x13, 0x10,
+ 0x1D, 0x89, 0x9E, 0x48, 0x3A, 0x8A, 0x5B, 0xB7, 0x6E, 0x8B, 0xBC, 0x26, 0x4F, 0x24, 0x30, 0x87,
+ 0x7D, 0x91, 0xF1, 0xE1, 0xF5, 0xDA, 0xAC, 0xF0, 0x78, 0x21, 0x02, 0xB5, 0x6A, 0x54, 0xA5, 0x55,
+ 0x8B, 0xA2, 0xE8, 0xA3, 0xBE, 0x3D, 0x45, 0x39, 0x85, 0xAD, 0xE1, 0x9A, 0x25, 0x10, 0x20, 0x64,
+ 0x3E, 0x8F, 0x1A, 0x37, 0x59, 0xEE, 0xB1, 0x83, 0x03, 0x0A, 0x66, 0x90, 0xD4, 0x67, 0x02, 0x56,
+ 0xD9, 0xC7, 0xA3, 0xC7, 0x1B, 0xB2, 0xCC, 0xE0, 0x00, 0x87, 0xFF, 0x41, 0x2F, 0x6A, 0x66, 0x94,
+ 0x90, 0x5C, 0x8F, 0xBE, 0x9A, 0x1D, 0xE2, 0x8A, 0x3A, 0x2C, 0x57, 0xC4, 0xE8, 0x4D, 0x47, 0x0B,
+ 0x6F, 0xAF, 0x2C, 0x71, 0x49, 0x39, 0x9C, 0x2C, 0xF3, 0xAD, 0x05, 0x04, 0x04, 0xD0, 0x1B, 0xF5,
+ 0x5F, 0xA3, 0x29, 0x63, 0x47, 0xD2, 0x37, 0x6B, 0x97, 0xD3, 0xD4, 0x71, 0xA3, 0x44, 0x5D, 0x10,
+ 0x84, 0xC9, 0x88, 0xA9, 0x8C, 0x52, 0x8E, 0xDD, 0x7B, 0xF7, 0xCB, 0x9E, 0x3E, 0x37, 0x95, 0xBB,
+ 0xBD, 0x11, 0x50, 0x6C, 0xAB, 0x05, 0x22, 0x52, 0xA6, 0xD4, 0x02, 0x64, 0x84, 0xC3, 0x2A, 0xD3,
+ 0x02, 0x4E, 0x4F, 0x14, 0xE7, 0x22, 0x9F, 0x08, 0xB9, 0x34, 0x0B, 0x66, 0x4F, 0xA3, 0x6F, 0xBF,
+ 0xFC, 0x9C, 0xE6, 0x4C, 0x1B, 0x4F, 0x55, 0x5F, 0xD6, 0x4E, 0xCC, 0x34, 0x0A, 0x8A, 0x73, 0x1D,
+ 0x01, 0x22, 0x6F, 0x08, 0xCF, 0xEB, 0xA5, 0x4A, 0x38, 0x63, 0x88, 0x16, 0x9F, 0x90, 0x20, 0x5B,
+ 0xFA, 0xA0, 0x38, 0x95, 0xC9, 0x5C, 0xB2, 0xA4, 0x7C, 0xC3, 0x22, 0x82, 0x53, 0x19, 0xC5, 0x89,
+ 0x42, 0x98, 0xD6, 0xAD, 0xA0, 0xC8, 0xC9, 0x63, 0xE9, 0xED, 0x46, 0xF5, 0x6C, 0x5A, 0x4B, 0x3F,
+ 0x6C, 0xB5, 0x5F, 0x76, 0x70, 0xE6, 0xEF, 0xB3, 0xB2, 0x65, 0x9B, 0x33, 0x67, 0xFF, 0x91, 0x2D,
+ 0x73, 0x42, 0x55, 0xA1, 0xDD, 0xE5, 0xAB, 0xB4, 0x43, 0xDF, 0x85, 0x0A, 0x16, 0xA0, 0x45, 0x73,
+ 0xA6, 0xD3, 0x2C, 0xE5, 0x9C, 0x3B, 0xBE, 0xDF, 0x8A, 0xEA, 0xD4, 0xAC, 0x46, 0xC5, 0x9F, 0x7E,
+ 0xEA, 0xE1, 0x05, 0x9F, 0x91, 0x3B, 0xBA, 0xA3, 0x40, 0x7A, 0xC1, 0xC7, 0x1F, 0xF5, 0x13, 0x09,
+ 0x8B, 0x10, 0x7C, 0x2D, 0x9C, 0x31, 0x44, 0x8B, 0xB3, 0x93, 0xBD, 0x9C, 0xD6, 0xF2, 0x1C, 0xC6,
+ 0x31, 0x78, 0xB4, 0x10, 0xED, 0x3F, 0x78, 0xD8, 0x6A, 0x83, 0xC3, 0xD7, 0x12, 0x0C, 0x69, 0x10,
+ 0xB2, 0xEF, 0xDF, 0xBB, 0x3B, 0x2D, 0x9D, 0x37, 0x53, 0x37, 0x07, 0x04, 0x75, 0x5F, 0xF6, 0xD8,
+ 0xB1, 0x67, 0x9F, 0x6C, 0xD9, 0x06, 0xD3, 0x60, 0x68, 0x81, 0x19, 0x07, 0xC1, 0xED, 0x3B, 0x77,
+ 0xE9, 0xB4, 0x8E, 0xA8, 0x61, 0x88, 0x09, 0x87, 0xB2, 0x1E, 0xA8, 0xD4, 0x77, 0x36, 0x1F, 0x84,
+ 0xB5, 0xA5, 0xBA, 0xB5, 0x6A, 0x88, 0x36, 0x12, 0xF9, 0xF4, 0x4A, 0x4F, 0x1E, 0xD5, 0x10, 0xCD,
+ 0xCF, 0x4F, 0x3B, 0xE7, 0xEB, 0xE2, 0x25, 0xDB, 0xB9, 0x3A, 0x5A, 0x53, 0x7B, 0x30, 0x8F, 0x1E,
+ 0x8F, 0x16, 0x22, 0x14, 0xAB, 0xF6, 0x1E, 0x10, 0x61, 0xBE, 0x0D, 0x1C, 0x6A, 0xD3, 0x62, 0x80,
+ 0xB5, 0x01, 0x07, 0xB7, 0x16, 0x88, 0x5C, 0xD9, 0xE3, 0xD0, 0x91, 0x63, 0x76, 0x2B, 0xD6, 0x61,
+ 0x0D, 0xED, 0xDC, 0xA3, 0x9D, 0x50, 0x89, 0xEC, 0x6B, 0x70, 0xCD, 0x46, 0xAD, 0x17, 0xE6, 0xC5,
+ 0xB1, 0x05, 0xAA, 0xF5, 0x9D, 0x4D, 0x88, 0xAA, 0xB6, 0x0E, 0xCE, 0xD9, 0xFE, 0xBD, 0xF5, 0x33,
+ 0x8A, 0x1F, 0xC5, 0x10, 0x4D, 0x6F, 0xFE, 0x6A, 0xA4, 0x36, 0xD8, 0xFA, 0x3F, 0x66, 0xA4, 0x40,
+ 0x9A, 0x49, 0x3F, 0x1E, 0x2D, 0x44, 0x4F, 0x16, 0xB5, 0x0E, 0xA5, 0xA3, 0xA6, 0x67, 0xEB, 0x6F,
+ 0xDB, 0x65, 0x4F, 0x9B, 0x6B, 0xD7, 0xB5, 0xA7, 0x25, 0x35, 0x1A, 0xD2, 0x1E, 0x3D, 0x7E, 0xAA,
+ 0xEE, 0x85, 0x85, 0x84, 0xC3, 0xD1, 0x13, 0xA6, 0x69, 0x5E, 0x0C, 0xB0, 0xCC, 0x6A, 0x55, 0xAF,
+ 0x2A, 0x7B, 0xFA, 0xD8, 0xAA, 0xC4, 0xC7, 0xB9, 0x23, 0x0D, 0xC1, 0xD5, 0x28, 0xF3, 0x7C, 0x49,
+ 0xE1, 0xA3, 0xD3, 0xE2, 0x51, 0x0C, 0xD1, 0x90, 0x34, 0xAA, 0x05, 0x26, 0x7E, 0xDB, 0xA2, 0x53,
+ 0x7C, 0xFB, 0xDB, 0x8E, 0x5D, 0x2E, 0xF5, 0xDD, 0xA1, 0xE4, 0x03, 0xD3, 0xB4, 0x4C, 0x8B, 0x9C,
+ 0x47, 0x7B, 0x1D, 0x90, 0x1B, 0xE6, 0xCA, 0x78, 0xB4, 0x10, 0x55, 0x7D, 0x59, 0x3B, 0x33, 0x78,
+ 0xC2, 0xD4, 0x48, 0xC5, 0x22, 0xD1, 0x1E, 0x1A, 0x21, 0xC9, 0x71, 0xD3, 0x96, 0xEF, 0x65, 0xCF,
+ 0x9C, 0x17, 0xCB, 0x95, 0x95, 0x2D, 0xDB, 0x60, 0x08, 0xD7, 0xA1, 0x7B, 0x5F, 0x91, 0xD7, 0x83,
+ 0x2A, 0x76, 0x58, 0x60, 0xF0, 0x3D, 0xA0, 0x70, 0xB6, 0x73, 0xCF, 0x70, 0x5D, 0xF3, 0x1F, 0xBE,
+ 0x1E, 0xD3, 0xB0, 0xD0, 0xD6, 0x14, 0xA8, 0x98, 0x32, 0x42, 0xCB, 0xAA, 0x43, 0x86, 0x38, 0x72,
+ 0x8C, 0x5C, 0xD5, 0xD9, 0xDA, 0xB5, 0x63, 0x3B, 0xDD, 0x59, 0x08, 0x1C, 0x3D, 0x44, 0xB3, 0x2C,
+ 0x4B, 0x51, 0x33, 0x6E, 0xEA, 0x2C, 0xB1, 0x0C, 0x12, 0xCA, 0x7C, 0x30, 0x54, 0xFF, 0xE5, 0xF7,
+ 0x1D, 0xF4, 0xE9, 0xA4, 0xE9, 0x14, 0x31, 0x6A, 0x9C, 0x21, 0xAB, 0x37, 0x33, 0xD8, 0xBD, 0x6F,
+ 0x3F, 0x4D, 0x9F, 0x33, 0x5F, 0x14, 0xFB, 0x3E, 0x5E, 0xA8, 0xA0, 0x98, 0x4F, 0x6A, 0xED, 0xC6,
+ 0xAF, 0xE5, 0xB3, 0x9E, 0x87, 0x47, 0x0B, 0x11, 0x86, 0x58, 0x5A, 0x11, 0x31, 0xA4, 0xD2, 0x0F,
+ 0x18, 0x3A, 0x4A, 0x24, 0x2E, 0xCE, 0x8C, 0x8A, 0x16, 0x33, 0x37, 0xE2, 0xB1, 0xCB, 0x87, 0x03,
+ 0x44, 0xD9, 0x87, 0x65, 0x1E, 0x0F, 0x08, 0x09, 0x0E, 0xA6, 0x3A, 0xAF, 0x1A, 0x5F, 0x10, 0x10,
+ 0x02, 0x84, 0x4C, 0xE7, 0x26, 0xAD, 0x3B, 0x50, 0xCD, 0x06, 0x8D, 0xE9, 0x9D, 0x96, 0x61, 0x62,
+ 0x71, 0x40, 0xDC, 0x91, 0xB5, 0xC8, 0x9E, 0x3D, 0x80, 0x3E, 0x68, 0xDF, 0x56, 0xF6, 0x52, 0x92,
+ 0x31, 0x4B, 0x3E, 0x6B, 0x5E, 0x34, 0x6B, 0xE2, 0xBB, 0x1F, 0xB7, 0x52, 0x8B, 0xF6, 0x5D, 0xC5,
+ 0xEB, 0x2F, 0x5C, 0xF6, 0x39, 0xCD, 0x9E, 0xBF, 0x88, 0x3E, 0x1A, 0x36, 0x9A, 0x5A, 0x2A, 0xFB,
+ 0x8E, 0xFF, 0x79, 0x4A, 0x1E, 0x65, 0xCD, 0x83, 0x07, 0xCE, 0x75, 0x62, 0xA3, 0x72, 0xBF, 0x47,
+ 0xA7, 0x30, 0xD9, 0xB3, 0xC6, 0x91, 0x43, 0x34, 0x44, 0x0E, 0xB5, 0xEA, 0xF9, 0x00, 0x44, 0x7C,
+ 0xDD, 0xC6, 0xCD, 0xA2, 0xCC, 0x07, 0xB9, 0x59, 0x43, 0x15, 0x01, 0xC2, 0x14, 0x25, 0xAE, 0xE0,
+ 0xE4, 0x37, 0xB1, 0xF4, 0xF3, 0x2F, 0xA8, 0x47, 0xE7, 0xF6, 0x74, 0xE8, 0xE8, 0x31, 0x51, 0x08,
+ 0x8D, 0xE0, 0x0A, 0x26, 0xC6, 0xF3, 0x54, 0x3C, 0x5A, 0x88, 0x50, 0x5C, 0x38, 0xE0, 0xC3, 0xEE,
+ 0xB2, 0x67, 0x0D, 0x4A, 0x39, 0x50, 0x19, 0x1E, 0xBD, 0x64, 0x85, 0x78, 0x3C, 0x7A, 0xFC, 0x4F,
+ 0xF9, 0x8C, 0x39, 0x08, 0x43, 0xA3, 0x90, 0x15, 0x62, 0x61, 0x0F, 0x3D, 0xA7, 0xAC, 0x3D, 0xF0,
+ 0xFA, 0x96, 0xA5, 0x01, 0xCD, 0x1A, 0xBF, 0x29, 0x5B, 0xD6, 0x60, 0xB6, 0x49, 0x58, 0x5C, 0x0B,
+ 0x96, 0xAE, 0x14, 0x3F, 0xD0, 0xED, 0xBB, 0xF6, 0xD8, 0x4D, 0x70, 0xC4, 0x94, 0x28, 0x48, 0x98,
+ 0x74, 0x26, 0xF5, 0x5F, 0xAB, 0x2D, 0xD2, 0x0E, 0xB4, 0x70, 0xE4, 0x10, 0x0D, 0x37, 0x8E, 0x86,
+ 0xF5, 0x6C, 0x4F, 0x09, 0xAB, 0x45, 0xC1, 0x02, 0xF9, 0x6D, 0xD6, 0x8A, 0x65, 0x16, 0x37, 0x6F,
+ 0xDE, 0x12, 0x95, 0xFB, 0x37, 0x6E, 0xDC, 0x14, 0xD6, 0x51, 0xCD, 0x6A, 0x55, 0x3C, 0x3A, 0x11,
+ 0xD2, 0xA3, 0x85, 0x08, 0x20, 0x72, 0x33, 0x7C, 0x50, 0xB8, 0x21, 0x11, 0xD1, 0x02, 0x16, 0xD5,
+ 0xC8, 0x88, 0x81, 0x86, 0x73, 0x73, 0x1A, 0xD5, 0xAF, 0x9B, 0xA6, 0x82, 0x4E, 0xE4, 0x0D, 0xA1,
+ 0x90, 0xB5, 0x76, 0x8D, 0x6A, 0x72, 0x4F, 0x2A, 0xAF, 0xD7, 0xAE, 0x49, 0x6F, 0x34, 0xD0, 0xF6,
+ 0xAB, 0xE8, 0x81, 0xD7, 0xC3, 0x24, 0x6F, 0x7A, 0x15, 0xF2, 0x67, 0xCF, 0x9D, 0x93, 0x2D, 0xE7,
+ 0x80, 0xFC, 0xA7, 0xFE, 0xCA, 0xCD, 0x41, 0xEF, 0xA2, 0x72, 0xE4, 0x10, 0xAD, 0x67, 0x67, 0xFD,
+ 0x89, 0xE2, 0xB4, 0xC0, 0xB0, 0x18, 0xE9, 0x1C, 0xAE, 0xB0, 0x50, 0x23, 0x8A, 0xA7, 0xF1, 0x5D,
+ 0xC0, 0x12, 0xC2, 0x74, 0x22, 0xDB, 0x7E, 0xDF, 0x69, 0xB7, 0x98, 0xDA, 0x9D, 0xF1, 0x78, 0x21,
+ 0x02, 0x10, 0xA3, 0xE5, 0xF3, 0x67, 0x8B, 0x99, 0x00, 0x83, 0x72, 0x1A, 0x5B, 0x99, 0x03, 0x33,
+ 0xFD, 0xE1, 0xF8, 0x65, 0xF3, 0x23, 0xC5, 0xA4, 0x63, 0x69, 0x01, 0xD5, 0xE5, 0x48, 0x32, 0xD4,
+ 0x4B, 0x03, 0x30, 0x51, 0xE1, 0x85, 0xB2, 0x62, 0x6E, 0x1F, 0x4C, 0xED, 0xA1, 0xC7, 0xC0, 0x0F,
+ 0x7B, 0x08, 0x61, 0xB1, 0x37, 0x21, 0x19, 0xAC, 0x36, 0xDC, 0xC9, 0x31, 0x31, 0x1A, 0x26, 0x79,
+ 0x7B, 0xAE, 0xB8, 0xF6, 0xE4, 0x68, 0x98, 0xD4, 0xDD, 0xD9, 0x60, 0x65, 0x91, 0x56, 0xEF, 0xEA,
+ 0x2F, 0x39, 0xED, 0xA8, 0x21, 0x1A, 0x66, 0x4B, 0x40, 0x7E, 0x18, 0xBE, 0x5F, 0xBD, 0x61, 0x1A,
+ 0x80, 0x38, 0xE2, 0x37, 0x12, 0x1D, 0x39, 0xD5, 0x65, 0x2E, 0xF6, 0xCE, 0xED, 0xDB, 0xD0, 0xB1,
+ 0x13, 0x7F, 0xD2, 0xB9, 0x7F, 0xFF, 0x15, 0x8F, 0xBF, 0x6E, 0xDF, 0x29, 0xAC, 0x66, 0x4F, 0x25,
+ 0x4B, 0x2D, 0x27, 0x04, 0xE0, 0x07, 0x38, 0x74, 0xF4, 0x38, 0x9D, 0x38, 0x79, 0x4A, 0x94, 0x4D,
+ 0xDC, 0x8D, 0x89, 0x51, 0x86, 0x2C, 0xF1, 0x62, 0xE6, 0xBC, 0x9C, 0xCA, 0x0F, 0x17, 0x45, 0xAB,
+ 0xCF, 0x3E, 0x5D, 0x4C, 0x44, 0x79, 0x6C, 0x99, 0xC2, 0x7A, 0xAB, 0x44, 0xF4, 0xEA, 0xDA, 0xF1,
+ 0xE1, 0x2C, 0x81, 0x98, 0x6E, 0x04, 0x66, 0x35, 0xF2, 0x97, 0x30, 0x94, 0x42, 0x75, 0x35, 0xC2,
+ 0xDA, 0xA8, 0xE5, 0x42, 0xF5, 0xFD, 0x13, 0x1A, 0x51, 0x3D, 0x3D, 0xE0, 0xB7, 0xDA, 0xBD, 0xEF,
+ 0x80, 0x48, 0x0F, 0xB8, 0x72, 0xF5, 0x2A, 0xC5, 0x29, 0xE7, 0x8C, 0x3B, 0x37, 0xA6, 0x4A, 0xC5,
+ 0xD2, 0x34, 0x2F, 0x2A, 0x77, 0x4E, 0xF5, 0x3C, 0x37, 0x18, 0xE6, 0x68, 0x25, 0xEF, 0xF9, 0xFB,
+ 0xFB, 0x8B, 0x89, 0xBB, 0x00, 0x0A, 0x64, 0xE3, 0x35, 0x96, 0xE8, 0x09, 0x54, 0x5E, 0xD7, 0xE8,
+ 0x52, 0x4A, 0xF0, 0x85, 0x69, 0x4D, 0x8D, 0x1A, 0x9C, 0x2B, 0x97, 0x4D, 0x2B, 0x14, 0xC3, 0xC8,
+ 0xEB, 0x3A, 0xD1, 0x49, 0x60, 0xF9, 0xF7, 0x70, 0xC4, 0x6B, 0xFD, 0x54, 0xF1, 0xF9, 0x8D, 0x94,
+ 0xEC, 0x20, 0x9A, 0xB8, 0x73, 0xF7, 0x3E, 0x91, 0x3A, 0x71, 0xF3, 0xF6, 0x6D, 0x8A, 0x8D, 0x8D,
+ 0xA3, 0x20, 0xE5, 0x33, 0x16, 0x2F, 0xF6, 0x14, 0x55, 0xAF, 0x52, 0xD9, 0x6C, 0x58, 0xAC, 0xF7,
+ 0xDD, 0xA9, 0xDF, 0x0B, 0x01, 0x01, 0xAD, 0x89, 0xE6, 0xB2, 0x29, 0x96, 0x28, 0x86, 0x54, 0xB6,
+ 0xC0, 0x44, 0xF9, 0xF8, 0xDD, 0x59, 0xA2, 0xFE, 0xDF, 0x98, 0xC0, 0x50, 0x1A, 0x4B, 0x1B, 0x19,
+ 0x99, 0x82, 0xC6, 0x9D, 0xC9, 0x72, 0x42, 0xE4, 0x28, 0x8C, 0x08, 0x11, 0xC3, 0x30, 0xC6, 0xC8,
+ 0x12, 0x43, 0x33, 0x86, 0x61, 0x5C, 0x1B, 0x16, 0x22, 0x86, 0x61, 0x9C, 0x0E, 0x0B, 0x11, 0xC3,
+ 0x30, 0x4E, 0x87, 0x7D, 0x44, 0xE9, 0xC4, 0x55, 0x7C, 0x44, 0x48, 0xFD, 0xC7, 0x02, 0x8C, 0x5A,
+ 0xA0, 0x6E, 0x0E, 0xE1, 0xDF, 0xD6, 0xEF, 0x35, 0x11, 0x6D, 0x4C, 0x64, 0x8F, 0x55, 0x35, 0xEC,
+ 0x81, 0x29, 0x52, 0x80, 0xFA, 0x75, 0xB7, 0x7D, 0xBB, 0x41, 0xB6, 0x88, 0x26, 0x4E, 0x9F, 0x6D,
+ 0xB6, 0x72, 0x2C, 0x72, 0xB5, 0xDE, 0x6A, 0x58, 0x4F, 0xF6, 0xB4, 0xC1, 0x14, 0xB8, 0xF8, 0x1B,
+ 0x9C, 0x2F, 0x96, 0x49, 0x02, 0xCF, 0x15, 0x7F, 0x5A, 0x44, 0xF7, 0xF0, 0xB7, 0x68, 0x83, 0xEA,
+ 0xF5, 0xDE, 0x16, 0x8F, 0xB6, 0xC0, 0xF9, 0x21, 0xD8, 0x60, 0xF4, 0xB3, 0xA8, 0x3F, 0x07, 0xFA,
+ 0xEA, 0xE9, 0x7C, 0x2D, 0xBF, 0x3F, 0x7C, 0x4E, 0x5B, 0xDF, 0x29, 0xE6, 0xB6, 0x42, 0x19, 0x0E,
+ 0x1E, 0x19, 0xC7, 0xC1, 0x16, 0x91, 0x07, 0x83, 0x0B, 0x1E, 0x17, 0x7F, 0xA7, 0x9E, 0xE1, 0x0F,
+ 0x2F, 0xFE, 0x8C, 0x82, 0xD7, 0x53, 0x8B, 0x10, 0xE6, 0x1B, 0x32, 0x22, 0x42, 0xB8, 0xB0, 0xF1,
+ 0x77, 0xEA, 0xF3, 0x30, 0x89, 0x13, 0x96, 0xB6, 0x76, 0xD5, 0x49, 0xD2, 0x2C, 0xC1, 0x79, 0x0E,
+ 0x1B, 0x33, 0xC1, 0xEC, 0x3B, 0x60, 0x32, 0x0E, 0x0B, 0x91, 0x87, 0x01, 0x61, 0xC0, 0x86, 0x79,
+ 0x80, 0x4C, 0x39, 0x53, 0x98, 0x84, 0x2D, 0x6A, 0xC1, 0x12, 0xD1, 0x87, 0x35, 0x80, 0xCD, 0x64,
+ 0x81, 0x98, 0x30, 0xED, 0xC7, 0xA6, 0x97, 0x6B, 0x05, 0x4B, 0x01, 0xD6, 0x90, 0x09, 0x08, 0x10,
+ 0xDE, 0xC7, 0x1E, 0x13, 0xA7, 0x47, 0x3E, 0x9C, 0x08, 0xCE, 0x74, 0x0E, 0xEA, 0xF7, 0x17, 0xFB,
+ 0x2A, 0x58, 0x2F, 0x3A, 0x80, 0xD7, 0x37, 0x7D, 0x1E, 0xF5, 0x56, 0xA8, 0x40, 0xFE, 0x0C, 0x7F,
+ 0x96, 0xB4, 0x60, 0x7A, 0x5F, 0xF5, 0xFB, 0x18, 0xB1, 0xC6, 0x18, 0xE3, 0xF0, 0xD0, 0x2C, 0x9D,
+ 0x20, 0x37, 0x68, 0x30, 0xD6, 0x87, 0xB7, 0x58, 0x02, 0xC6, 0xD9, 0x43, 0x33, 0xF5, 0x10, 0x0A,
+ 0x77, 0x6D, 0x93, 0x70, 0xE0, 0x82, 0xDC, 0xBC, 0x36, 0x75, 0x79, 0x1F, 0x5B, 0x7F, 0x07, 0x2C,
+ 0x9F, 0x9F, 0x3F, 0x6B, 0x8A, 0xE8, 0x9B, 0x04, 0x05, 0x17, 0xB9, 0x69, 0x08, 0x67, 0x0F, 0xF5,
+ 0x70, 0x0B, 0xC2, 0x85, 0x8B, 0x1A, 0xC0, 0x3A, 0xC2, 0x92, 0xE0, 0x85, 0x0A, 0xE6, 0x7F, 0x28,
+ 0x68, 0xEA, 0x63, 0x2D, 0x87, 0x51, 0x7A, 0xD8, 0xFB, 0x2C, 0xB6, 0x5E, 0x53, 0xEB, 0x6F, 0xF5,
+ 0x5E, 0x0F, 0xE7, 0xDB, 0x3C, 0x2C, 0x75, 0x25, 0x60, 0x7C, 0x27, 0x96, 0x22, 0xC8, 0xA4, 0x0F,
+ 0xB6, 0x88, 0xD2, 0x49, 0xF9, 0xB2, 0xA5, 0x69, 0xF6, 0xD4, 0x71, 0x76, 0xB3, 0xA7, 0x9D, 0x09,
+ 0xFC, 0x42, 0x26, 0xF4, 0xA6, 0xA6, 0x35, 0x8A, 0xDA, 0xAA, 0xC1, 0xC5, 0x37, 0x6A, 0xE8, 0x40,
+ 0xD1, 0x36, 0x82, 0xFA, 0x3C, 0xE0, 0xA7, 0xC2, 0x45, 0x6E, 0x5A, 0x78, 0x11, 0xFE, 0x25, 0x23,
+ 0x56, 0x95, 0x2B, 0xA0, 0xFE, 0x1C, 0x20, 0xA3, 0xDF, 0x29, 0x93, 0x0A, 0x0B, 0x51, 0x06, 0x40,
+ 0x1D, 0xD3, 0xDC, 0xE9, 0x13, 0xD3, 0x5D, 0xE8, 0xFA, 0xA8, 0x51, 0xFB, 0x63, 0x32, 0x3A, 0x44,
+ 0x51, 0xAF, 0xB7, 0x76, 0xE1, 0xE2, 0x65, 0xD9, 0x32, 0x06, 0x9C, 0xE5, 0x6A, 0x60, 0x71, 0x60,
+ 0x68, 0x03, 0xEB, 0x02, 0xFE, 0x16, 0x3D, 0xFF, 0x15, 0x04, 0x0B, 0xD6, 0x8C, 0x7A, 0x53, 0x5B,
+ 0x2A, 0x99, 0x8D, 0xE5, 0x79, 0x62, 0x88, 0xC8, 0x38, 0x06, 0x16, 0xA2, 0x0C, 0x02, 0x8B, 0x68,
+ 0xF6, 0x94, 0x71, 0xC2, 0x42, 0x72, 0x05, 0x60, 0x69, 0x98, 0xA2, 0x63, 0x6A, 0x3F, 0x86, 0x23,
+ 0xA3, 0x3C, 0xB0, 0x04, 0x96, 0xAF, 0x5A, 0x27, 0x7B, 0xF6, 0x81, 0xAF, 0x07, 0x4B, 0x3A, 0x69,
+ 0x0D, 0x63, 0xE0, 0xFC, 0x85, 0x33, 0xDD, 0x95, 0xAD, 0x0B, 0xD3, 0x77, 0x0A, 0xD1, 0x34, 0x01,
+ 0xEB, 0xC8, 0xD2, 0x42, 0x62, 0xD2, 0x0F, 0x0B, 0x91, 0x03, 0x40, 0x1D, 0xD0, 0xE4, 0xB1, 0x23,
+ 0xC5, 0xC4, 0x66, 0xCE, 0xC6, 0x24, 0x40, 0xB8, 0x70, 0x4C, 0x17, 0x37, 0xAC, 0x21, 0x4B, 0xAB,
+ 0x24, 0x3D, 0x58, 0x0E, 0xB1, 0x60, 0xD9, 0x18, 0x01, 0xD6, 0x14, 0x84, 0x10, 0x3E, 0x95, 0x55,
+ 0x8B, 0xE6, 0xA6, 0x38, 0x9C, 0x55, 0xAF, 0x85, 0xF3, 0xD4, 0x8A, 0x9A, 0x69, 0x39, 0xAB, 0x33,
+ 0x2A, 0xA8, 0x77, 0xEE, 0x98, 0x0B, 0x9E, 0x65, 0x5F, 0x0B, 0xD3, 0x77, 0xAA, 0xB6, 0x0A, 0xBB,
+ 0x7A, 0xE8, 0xBA, 0xFD, 0xCE, 0x82, 0x85, 0xC8, 0x41, 0x60, 0xDA, 0x8D, 0xE1, 0x83, 0xFB, 0x53,
+ 0xF5, 0x57, 0x2A, 0xCB, 0x3D, 0xAE, 0x03, 0xFC, 0x30, 0x19, 0xBD, 0x7B, 0x43, 0x04, 0x60, 0xD5,
+ 0xA8, 0x89, 0x8A, 0x5E, 0x2C, 0x5B, 0xB6, 0x81, 0x25, 0x81, 0x21, 0x15, 0x84, 0x0B, 0xE7, 0x01,
+ 0x9F, 0xD0, 0xFC, 0x59, 0xE6, 0xEB, 0xC4, 0x69, 0x59, 0x44, 0x10, 0x1D, 0x1C, 0xAB, 0xDE, 0xEC,
+ 0xA5, 0x0A, 0x68, 0xA1, 0xB6, 0xC4, 0xD4, 0x02, 0x8D, 0x47, 0x75, 0x18, 0xDE, 0x9E, 0xE3, 0x19,
+ 0x82, 0x8E, 0x73, 0x82, 0xC3, 0xDB, 0x91, 0x16, 0x26, 0xC3, 0x42, 0xE4, 0x50, 0x30, 0x9D, 0x04,
+ 0x26, 0xD6, 0x72, 0x26, 0x88, 0xF0, 0x60, 0x53, 0x5F, 0x54, 0x8E, 0xC8, 0x79, 0x81, 0x08, 0xE0,
+ 0x35, 0xF1, 0x68, 0x02, 0x16, 0x02, 0x2E, 0x6C, 0x5B, 0xE0, 0xBD, 0xE1, 0x5B, 0x31, 0x45, 0xA2,
+ 0xE0, 0x17, 0xC2, 0xA3, 0xA5, 0xAF, 0xA7, 0x50, 0x01, 0x6B, 0xA1, 0x84, 0x95, 0x84, 0xD7, 0xB7,
+ 0xDC, 0xF4, 0x7C, 0x4A, 0x7A, 0xA8, 0xC5, 0x0B, 0xE7, 0xDC, 0xBC, 0x5D, 0xCA, 0x39, 0xE0, 0x51,
+ 0x6D, 0xD5, 0xE9, 0x89, 0x9C, 0xE9, 0x3B, 0x45, 0xD4, 0x11, 0x62, 0x6C, 0x24, 0x92, 0xC7, 0xA4,
+ 0x0D, 0x16, 0x22, 0x0F, 0x05, 0xF3, 0x43, 0x9B, 0xC0, 0xC5, 0x86, 0x0B, 0xD8, 0x11, 0xB4, 0x6E,
+ 0xDE, 0xC4, 0xCC, 0xF1, 0x0D, 0x5F, 0x91, 0x2D, 0x61, 0xC0, 0xB1, 0xEA, 0xE3, 0x4D, 0xA2, 0xA4,
+ 0x1E, 0xE6, 0xE0, 0xC2, 0xD6, 0xB2, 0x30, 0x20, 0x62, 0xA6, 0x61, 0x91, 0x7A, 0xBB, 0x70, 0x29,
+ 0x6D, 0xCE, 0x72, 0x08, 0x8C, 0x5A, 0x64, 0x60, 0x09, 0xE1, 0x1C, 0xD4, 0x56, 0x98, 0xE5, 0x31,
+ 0x4C, 0xE6, 0xC2, 0x42, 0xE4, 0xA1, 0xE0, 0xE2, 0x56, 0xDF, 0xB9, 0x21, 0x18, 0xEA, 0x8B, 0x3F,
+ 0xBD, 0x40, 0x54, 0x30, 0x4C, 0x33, 0x81, 0x8B, 0x19, 0xC9, 0x92, 0x7A, 0x40, 0x60, 0x56, 0x2D,
+ 0x9E, 0x2B, 0x86, 0x87, 0x68, 0xAB, 0x45, 0x09, 0x16, 0x16, 0x5E, 0xCB, 0x68, 0x3E, 0x52, 0x46,
+ 0xC0, 0xFB, 0x9B, 0x86, 0x54, 0xA6, 0x61, 0x2A, 0x1E, 0xD1, 0xC7, 0x73, 0xD8, 0x18, 0xE7, 0xC1,
+ 0x09, 0x8D, 0x0C, 0xC3, 0x38, 0x1D, 0xB6, 0x88, 0x18, 0x86, 0x71, 0x3A, 0x2C, 0x44, 0x0C, 0xC3,
+ 0x38, 0x19, 0xA2, 0xFF, 0x03, 0x3E, 0x9F, 0xCD, 0xAF, 0x59, 0xF8, 0xCE, 0xCA, 0x00, 0x00, 0x00,
+ 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, 0x2C, 0x30, 0x0E, 0x20, 0x67, 0x17, 0x00
+}; //rtk-setup.png
+
+//Content of Battery0.png with gzip compression
+static const uint8_t battery0_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x33, 0x16, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x2D, 0x30, 0x2E, 0x70, 0x6E, 0x67, 0x00, 0x01, 0xB8, 0x01, 0x47, 0xFE, 0x89, 0x50, 0x4E,
+ 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x1B, 0x08, 0x03, 0x00, 0x00, 0x00, 0x39, 0xCE, 0x3D, 0x5C, 0x00, 0x00,
+ 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67,
+ 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x8A, 0x50,
+ 0x4C, 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xB5,
+ 0xB5, 0xB5, 0x6A, 0x6A, 0x6A, 0x6D, 0x6D, 0x6D, 0x6C, 0x6C, 0x6C, 0xBA, 0xBA, 0xBA, 0xD3, 0xD3,
+ 0xD3, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0xDB, 0xDB, 0xDB, 0xB6, 0xB6, 0xB6, 0x9C, 0x9C, 0x9C,
+ 0xF1, 0xF1, 0xF1, 0xE1, 0xE1, 0xE1, 0xE4, 0xE4, 0xE4, 0x92, 0x92, 0x92, 0xBE, 0xBE, 0xBE, 0xFA,
+ 0xFA, 0xFA, 0xB1, 0xB1, 0xB1, 0xA5, 0xA5, 0xA5, 0xC9, 0xC9, 0xC9, 0xB9, 0xB9, 0xB9, 0xAC, 0xAC,
+ 0xAC, 0xA0, 0xA0, 0xA0, 0xA6, 0xA6, 0xA6, 0xE3, 0xE3, 0xE3, 0xD6, 0xD6, 0xD6, 0xD9, 0xD9, 0xD9,
+ 0xAD, 0xAD, 0xAD, 0x94, 0x94, 0x94, 0xA1, 0xA1, 0xA1, 0x60, 0x60, 0x60, 0x87, 0x87, 0x87, 0x72,
+ 0x72, 0x72, 0xFB, 0xFB, 0xFB, 0xCD, 0xCD, 0xCD, 0xF3, 0xF3, 0xF3, 0x71, 0x71, 0x71, 0xE2, 0xE2,
+ 0xE2, 0x70, 0x70, 0x70, 0xC1, 0xC1, 0xC1, 0x73, 0x73, 0x73, 0xF2, 0xF2, 0xF2, 0xCA, 0xE4, 0xF2,
+ 0x31, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC1, 0x00, 0x00, 0x0E,
+ 0xC1, 0x01, 0xB8, 0x91, 0x6B, 0xED, 0x00, 0x00, 0x00, 0xB7, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F,
+ 0x95, 0x93, 0x69, 0x0F, 0x82, 0x30, 0x0C, 0x86, 0x5F, 0x86, 0x1C, 0xD6, 0x03, 0x10, 0xBC, 0x0F,
+ 0x14, 0xBC, 0x8F, 0xFF, 0xFF, 0xF7, 0xEC, 0x08, 0x6A, 0x4C, 0x06, 0x2B, 0xCF, 0x87, 0xAD, 0xC9,
+ 0x9E, 0x2D, 0xDD, 0xD6, 0xA2, 0x0B, 0x8E, 0x6A, 0xC1, 0xA9, 0x25, 0xC6, 0xAD, 0xE7, 0x06, 0xBE,
+ 0xCB, 0x2E, 0x7A, 0x9E, 0x1F, 0x34, 0xE0, 0x7B, 0xE1, 0xC7, 0x74, 0xD0, 0xA7, 0x36, 0x06, 0x43,
+ 0x56, 0x34, 0x0A, 0x23, 0x1A, 0x47, 0x71, 0x62, 0x24, 0x8E, 0x26, 0x94, 0x22, 0xAB, 0x4C, 0x85,
+ 0x90, 0xA6, 0xD5, 0x16, 0x23, 0x33, 0x9A, 0xD7, 0x91, 0xC2, 0x82, 0x96, 0xBC, 0xC9, 0x48, 0x86,
+ 0x15, 0xAD, 0x37, 0xDB, 0x9D, 0x3E, 0x52, 0x8B, 0x39, 0x8F, 0x46, 0x14, 0x8B, 0x9A, 0x3D, 0x47,
+ 0x16, 0xF1, 0x40, 0x45, 0x79, 0xA4, 0x00, 0x27, 0xBB, 0x78, 0xC6, 0x85, 0xAE, 0x1C, 0x59, 0xC5,
+ 0x14, 0x37, 0xBA, 0x4B, 0xC4, 0x07, 0x92, 0x2E, 0xA2, 0x20, 0x47, 0xB1, 0x28, 0xCE, 0x51, 0x7C,
+ 0xEB, 0xA2, 0x7C, 0xF2, 0x3B, 0x5A, 0xC5, 0xFF, 0x9F, 0x11, 0xFE, 0xB5, 0xB8, 0x7A, 0xDA, 0xEA,
+ 0xF1, 0xF5, 0xAB, 0x47, 0x71, 0x85, 0x8B, 0x7B, 0x46, 0xDE, 0x85, 0xF2, 0xBE, 0x96, 0x00, 0xBC,
+ 0x01, 0x39, 0x68, 0x15, 0xFA, 0x56, 0xD4, 0xD8, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E,
+ 0x44, 0xAE, 0x42, 0x60, 0x82, 0x77, 0x0A, 0x6F, 0x96, 0xB8, 0x01, 0x00, 0x00
+}; //Battery0.png
+
+//Content of Battery1.png with gzip compression
+static const uint8_t battery1_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xCA, 0x15, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x31, 0x2E, 0x70, 0x6E, 0x67, 0x00, 0x01, 0x28, 0x02, 0xD7, 0xFD, 0x89, 0x50, 0x4E, 0x47,
+ 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28,
+ 0x00, 0x00, 0x00, 0x1B, 0x08, 0x03, 0x00, 0x00, 0x00, 0x39, 0xCE, 0x3D, 0x5C, 0x00, 0x00, 0x00,
+ 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41,
+ 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0xBA, 0x50, 0x4C,
+ 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xB5, 0xB5,
+ 0xB5, 0x6A, 0x6A, 0x6A, 0x6D, 0x6D, 0x6D, 0x6C, 0x6C, 0x6C, 0xBA, 0xBA, 0xBA, 0xD3, 0xD3, 0xD3,
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0xDB, 0xDB, 0xDB, 0xB6, 0xB6, 0xB6, 0x9D, 0x9D, 0x9D, 0xF0,
+ 0xF0, 0xF0, 0xE9, 0xE9, 0xE9, 0xF2, 0xF2, 0xF2, 0xF1, 0xF1, 0xF1, 0xE3, 0xE3, 0xE3, 0xE4, 0xE4,
+ 0xE4, 0xE1, 0xE1, 0xE1, 0x92, 0x92, 0x92, 0xBE, 0xBE, 0xBE, 0xFA, 0xFA, 0xFA, 0xB0, 0xB0, 0xB0,
+ 0xDC, 0xDC, 0xDC, 0xE8, 0xE8, 0xE8, 0xA5, 0xA5, 0xA5, 0xC9, 0xC9, 0xC9, 0xB9, 0xB9, 0xB9, 0xAC,
+ 0xAC, 0xAC, 0x2C, 0x2C, 0x2C, 0x97, 0x97, 0x97, 0xA0, 0xA0, 0xA0, 0xA6, 0xA6, 0xA6, 0xD6, 0xD6,
+ 0xD6, 0xD9, 0xD9, 0xD9, 0xAF, 0xAF, 0xAF, 0x24, 0x24, 0x24, 0x04, 0x04, 0x04, 0x94, 0x94, 0x94,
+ 0x26, 0x26, 0x26, 0x03, 0x03, 0x03, 0xA1, 0xA1, 0xA1, 0x60, 0x60, 0x60, 0x87, 0x87, 0x87, 0x72,
+ 0x72, 0x72, 0xFB, 0xFB, 0xFB, 0xCD, 0xCD, 0xCD, 0xF3, 0xF3, 0xF3, 0x71, 0x71, 0x71, 0xE2, 0xE2,
+ 0xE2, 0x70, 0x70, 0x70, 0xC1, 0xC1, 0xC1, 0x25, 0x25, 0x25, 0x73, 0x73, 0x73, 0x2D, 0x2D, 0x2D,
+ 0xDD, 0xDD, 0xDD, 0xB8, 0xB8, 0xB8, 0xBC, 0xBC, 0xBC, 0xBB, 0xBB, 0xBB, 0xBF, 0x7C, 0x50, 0xBC,
+ 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC1, 0x00, 0x00, 0x0E, 0xC1,
+ 0x01, 0xB8, 0x91, 0x6B, 0xED, 0x00, 0x00, 0x00, 0xF7, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xAD,
+ 0x93, 0xE9, 0x72, 0x82, 0x30, 0x14, 0x46, 0x3F, 0x02, 0x6A, 0xBC, 0xA8, 0x05, 0xC4, 0x4A, 0x5D,
+ 0x6A, 0xED, 0xA2, 0xB8, 0xB6, 0xD5, 0xEE, 0xEA, 0xFB, 0xBF, 0x56, 0x93, 0x08, 0x16, 0xA6, 0x2C,
+ 0x71, 0xC6, 0xF3, 0x87, 0x9B, 0xE1, 0x24, 0x84, 0xE4, 0xBB, 0x38, 0x07, 0x83, 0x15, 0x60, 0x44,
+ 0x92, 0xC0, 0x8C, 0x9E, 0x39, 0x9C, 0x5E, 0x9B, 0xB0, 0x2A, 0xD5, 0x5A, 0x0E, 0xD5, 0x0A, 0x8F,
+ 0x4D, 0x03, 0x75, 0x2A, 0xC2, 0x6E, 0x08, 0x45, 0xC2, 0xD0, 0xA4, 0xD6, 0x95, 0xE3, 0x7A, 0x9E,
+ 0xE7, 0x3A, 0x6D, 0x3F, 0x4D, 0xC7, 0xBB, 0xA6, 0x2E, 0x02, 0x65, 0x32, 0x70, 0xBA, 0x41, 0xCF,
+ 0xE2, 0x9C, 0x5B, 0x7D, 0x35, 0x35, 0xC5, 0x80, 0x86, 0x51, 0xC5, 0x70, 0x4B, 0x23, 0xDC, 0xA9,
+ 0xCF, 0x8C, 0xC5, 0xE4, 0x14, 0x01, 0xEE, 0xE9, 0xA1, 0xFD, 0xF8, 0x24, 0x97, 0x94, 0xE2, 0x04,
+ 0x53, 0x0A, 0xED, 0x90, 0x5A, 0x62, 0x94, 0x82, 0x09, 0x51, 0x32, 0x13, 0xD5, 0x51, 0x9C, 0x93,
+ 0x4D, 0x8B, 0x2C, 0x71, 0x49, 0xAB, 0xF5, 0x33, 0xD5, 0xF0, 0x92, 0x10, 0xED, 0x6C, 0xF1, 0x15,
+ 0x1B, 0xDA, 0x8A, 0xAA, 0x54, 0xEC, 0xE2, 0x8D, 0xDE, 0x75, 0xC4, 0x0F, 0xF8, 0x09, 0xF1, 0xB3,
+ 0x4C, 0x8C, 0xF7, 0xA8, 0x2D, 0x5E, 0x62, 0x8F, 0xFF, 0xFF, 0x3A, 0xF7, 0x1C, 0xBF, 0xC4, 0x39,
+ 0xC6, 0xE2, 0x94, 0x16, 0x3A, 0x37, 0x33, 0xC2, 0xB7, 0x1A, 0x97, 0xDC, 0xB5, 0x4C, 0xCF, 0xCF,
+ 0x6E, 0x7F, 0xD8, 0xEF, 0x9C, 0xE3, 0x3A, 0x49, 0x92, 0xE9, 0x91, 0x79, 0xEC, 0xE7, 0xE4, 0xD1,
+ 0xFD, 0xCB, 0xA3, 0x76, 0xC2, 0xB5, 0x7B, 0x46, 0xBF, 0x0B, 0xF5, 0xFB, 0x5A, 0x07, 0xE0, 0x17,
+ 0xAB, 0x60, 0x21, 0x45, 0x7A, 0x29, 0x95, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44,
+ 0xAE, 0x42, 0x60, 0x82, 0x51, 0xB3, 0x1E, 0xF4, 0x28, 0x02, 0x00, 0x00
+}; //Battery1.png
+
+//Content of Battery2.png with gzip compression
+static const uint8_t battery2_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x5D, 0x10, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x32, 0x2E, 0x70, 0x6E, 0x67, 0x00, 0x01, 0xB9, 0x02, 0x46, 0xFD, 0x89, 0x50, 0x4E, 0x47,
+ 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28,
+ 0x00, 0x00, 0x00, 0x1A, 0x08, 0x03, 0x00, 0x00, 0x00, 0xF2, 0x92, 0xEE, 0xF9, 0x00, 0x00, 0x00,
+ 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41,
+ 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x01, 0x17, 0x50, 0x4C,
+ 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0x96, 0x96,
+ 0x96, 0x45, 0x45, 0x45, 0x47, 0x47, 0x47, 0x9C, 0x9C, 0x9C, 0xBC, 0xBC, 0xBC, 0x0A, 0x0A, 0x0A,
+ 0x16, 0x16, 0x16, 0x2E, 0x2E, 0x2E, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x28, 0x28, 0x28, 0x29,
+ 0x29, 0x29, 0x13, 0x13, 0x13, 0x0E, 0x0E, 0x0E, 0xC5, 0xC5, 0xC5, 0xFB, 0xFB, 0xFB, 0xA7, 0xA7,
+ 0xA7, 0x08, 0x08, 0x08, 0xA6, 0xA6, 0xA6, 0xFA, 0xFA, 0xFA, 0xF3, 0xF3, 0xF3, 0xF7, 0xF7, 0xF7,
+ 0xF4, 0xF4, 0xF4, 0xF5, 0xF5, 0xF5, 0xF2, 0xF2, 0xF2, 0x9B, 0x9B, 0x9B, 0xAF, 0xAF, 0xAF, 0xF8,
+ 0xF8, 0xF8, 0xAB, 0xAB, 0xAB, 0x6E, 0x6E, 0x6E, 0x77, 0x77, 0x77, 0x75, 0x75, 0x75, 0xC6, 0xC6,
+ 0xC6, 0xCB, 0xCB, 0xCB, 0x71, 0x71, 0x71, 0x76, 0x76, 0x76, 0x73, 0x73, 0x73, 0x7C, 0x7C, 0x7C,
+ 0xE6, 0xE6, 0xE6, 0x0D, 0x0D, 0x0D, 0xBD, 0xBD, 0xBD, 0xAA, 0xAA, 0xAA, 0x06, 0x06, 0x06, 0xF0,
+ 0xF0, 0xF0, 0x31, 0x31, 0x31, 0x00, 0x00, 0x00, 0x8E, 0x8E, 0x8E, 0x95, 0x95, 0x95, 0xC9, 0xC9,
+ 0xC9, 0x9A, 0x9A, 0x9A, 0x05, 0x05, 0x05, 0x6D, 0x6D, 0x6D, 0x9D, 0x9D, 0x9D, 0x90, 0x90, 0x90,
+ 0xE3, 0xE3, 0xE3, 0xA9, 0xA9, 0xA9, 0xEF, 0xEF, 0xEF, 0x34, 0x34, 0x34, 0x03, 0x03, 0x03, 0x01,
+ 0x01, 0x01, 0x9E, 0x9E, 0x9E, 0x04, 0x04, 0x04, 0x18, 0x18, 0x18, 0xCE, 0xCE, 0xCE, 0x0F, 0x0F,
+ 0x0F, 0x0B, 0x0B, 0x0B, 0x78, 0x78, 0x78, 0x35, 0x35, 0x35, 0x02, 0x02, 0x02, 0x09, 0x09, 0x09,
+ 0xD0, 0xD0, 0xD0, 0xB4, 0xB4, 0xB4, 0x6F, 0x6F, 0x6F, 0x0C, 0x0C, 0x0C, 0xBB, 0xBB, 0xBB, 0xED,
+ 0xED, 0xED, 0x70, 0x70, 0x70, 0xB1, 0xB1, 0xB1, 0xDE, 0xDE, 0xDE, 0xB3, 0xB3, 0xB3, 0xE2, 0xE2,
+ 0xE2, 0x32, 0x32, 0x32, 0xA8, 0xA8, 0xA8, 0x79, 0x79, 0x79, 0x72, 0x72, 0x72, 0x74, 0x74, 0x74,
+ 0x7E, 0x7E, 0x7E, 0x97, 0x97, 0x97, 0x46, 0x46, 0x46, 0xC4, 0xCE, 0xCA, 0x12, 0x00, 0x00, 0x00,
+ 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC1, 0x00, 0x00, 0x0E, 0xC1, 0x01, 0xB8, 0x91,
+ 0x6B, 0xED, 0x00, 0x00, 0x01, 0x2B, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xAD, 0x93, 0xE9, 0x52,
+ 0xC2, 0x30, 0x14, 0x46, 0xBF, 0x14, 0xB0, 0x16, 0xB4, 0x2E, 0x2D, 0xE0, 0x86, 0x4A, 0xEB, 0x8E,
+ 0x82, 0x01, 0xA9, 0x2B, 0x2D, 0xA8, 0x54, 0xDC, 0xEB, 0x8A, 0xDB, 0xFB, 0x3F, 0x87, 0x49, 0x68,
+ 0x1D, 0x45, 0xED, 0xB4, 0x33, 0x9C, 0x3F, 0xF7, 0x76, 0xE6, 0x4C, 0x92, 0xE6, 0x7E, 0x41, 0x1C,
+ 0x88, 0x44, 0x42, 0xF0, 0xA5, 0x18, 0x24, 0x90, 0x4C, 0x0D, 0xFD, 0x4B, 0x4A, 0x66, 0x82, 0x80,
+ 0x60, 0x58, 0x49, 0x67, 0x46, 0x46, 0x39, 0xEA, 0x98, 0x2A, 0x2A, 0x6B, 0x7A, 0xA8, 0x99, 0xF1,
+ 0x89, 0x49, 0x48, 0x42, 0xD4, 0xA0, 0x67, 0x73, 0xC8, 0x8B, 0x3E, 0x3F, 0xD5, 0xAB, 0x98, 0x9E,
+ 0x11, 0xCC, 0xCE, 0xA1, 0xA0, 0xCC, 0x63, 0x81, 0xF0, 0x45, 0x09, 0x16, 0xB3, 0x3A, 0x72, 0x45,
+ 0xC3, 0x34, 0x8A, 0x4B, 0x58, 0x5E, 0x31, 0xCD, 0xD5, 0xB5, 0xF5, 0x0D, 0x7F, 0x3F, 0x09, 0x72,
+ 0x69, 0x93, 0x37, 0xEC, 0x93, 0x60, 0xAB, 0xAC, 0x57, 0xB6, 0x29, 0xA7, 0x8A, 0x9A, 0xA8, 0xA5,
+ 0x1D, 0x68, 0x09, 0x8E, 0x86, 0xBA, 0xB5, 0xBB, 0xB7, 0x5F, 0x3F, 0x60, 0x26, 0x17, 0x0F, 0x8F,
+ 0x1A, 0xB4, 0x6C, 0x5B, 0x4E, 0x12, 0x4D, 0xAB, 0x65, 0x5B, 0xF4, 0xF8, 0xC4, 0x3F, 0x17, 0x41,
+ 0x81, 0x2A, 0xA7, 0x6D, 0xEA, 0xB2, 0x13, 0x0A, 0xB1, 0x72, 0x46, 0x6D, 0xDA, 0xA1, 0x35, 0xC8,
+ 0x2D, 0xC7, 0xE9, 0xD0, 0xF4, 0x37, 0xF1, 0xBC, 0x7A, 0x71, 0x49, 0xAF, 0xBE, 0xC4, 0x46, 0x20,
+ 0xDA, 0xFD, 0xE2, 0xF5, 0x0D, 0x6E, 0xA9, 0x17, 0x41, 0x6C, 0xDF, 0xE1, 0x3E, 0x9A, 0xF8, 0x80,
+ 0xC7, 0x01, 0x8B, 0x51, 0xB7, 0xEE, 0xFB, 0x99, 0xA8, 0xD7, 0x13, 0xF5, 0xC2, 0xF5, 0xCA, 0x93,
+ 0x18, 0xDD, 0xAF, 0x11, 0xE6, 0x7F, 0x8C, 0x90, 0x87, 0xE2, 0xD9, 0xEB, 0x1A, 0x5D, 0x8F, 0x85,
+ 0xE2, 0xC5, 0x70, 0xDD, 0xD7, 0xB7, 0xBF, 0x42, 0x11, 0x23, 0x66, 0x3C, 0xB8, 0x41, 0x5E, 0x43,
+ 0x82, 0xCB, 0x56, 0x7D, 0x0F, 0x79, 0x0A, 0x1F, 0x72, 0xE0, 0xC5, 0x61, 0x90, 0xCF, 0x15, 0xF8,
+ 0x04, 0xA3, 0x28, 0x52, 0xB8, 0x37, 0x7F, 0x84, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E,
+ 0x44, 0xAE, 0x42, 0x60, 0x82, 0x0E, 0x75, 0xED, 0xEE, 0xB9, 0x02, 0x00, 0x00
+}; //Battery2.png
+
+//Content of Battery3.png with gzip compression
+static const uint8_t battery3_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xC2, 0x15, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x33, 0x2E, 0x70, 0x6E, 0x67, 0x00, 0x01, 0xCC, 0x02, 0x33, 0xFD, 0x89, 0x50, 0x4E, 0x47,
+ 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28,
+ 0x00, 0x00, 0x00, 0x1B, 0x08, 0x03, 0x00, 0x00, 0x00, 0x39, 0xCE, 0x3D, 0x5C, 0x00, 0x00, 0x00,
+ 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41,
+ 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x01, 0x0B, 0x50, 0x4C,
+ 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xB5, 0xB5,
+ 0xB5, 0x6A, 0x6A, 0x6A, 0x6D, 0x6D, 0x6D, 0x6C, 0x6C, 0x6C, 0xBA, 0xBA, 0xBA, 0xD3, 0xD3, 0xD3,
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0xDB, 0xDB, 0xDB, 0xB6, 0xB6, 0xB6, 0x9D, 0x9D, 0x9D, 0xF0,
+ 0xF0, 0xF0, 0xE9, 0xE9, 0xE9, 0xF2, 0xF2, 0xF2, 0xF1, 0xF1, 0xF1, 0xE2, 0xE2, 0xE2, 0xE8, 0xE8,
+ 0xE8, 0xE6, 0xE6, 0xE6, 0xE3, 0xE3, 0xE3, 0xEC, 0xEC, 0xEC, 0x92, 0x92, 0x92, 0xBE, 0xBE, 0xBE,
+ 0xFA, 0xFA, 0xFA, 0xB0, 0xB0, 0xB0, 0xDC, 0xDC, 0xDC, 0xE7, 0xE7, 0xE7, 0xEB, 0xEB, 0xEB, 0xB9,
+ 0xB9, 0xB9, 0xBC, 0xBC, 0xBC, 0xF9, 0xF9, 0xF9, 0xD6, 0xD6, 0xD6, 0xE1, 0xE1, 0xE1, 0xA4, 0xA4,
+ 0xA4, 0xC9, 0xC9, 0xC9, 0xAC, 0xAC, 0xAC, 0x2C, 0x2C, 0x2C, 0x94, 0x94, 0x94, 0xA3, 0xA3, 0xA3,
+ 0xD4, 0xD4, 0xD4, 0x59, 0x59, 0x59, 0x3B, 0x3B, 0x3B, 0x9F, 0x9F, 0x9F, 0xA6, 0xA6, 0xA6, 0xD9,
+ 0xD9, 0xD9, 0xAF, 0xAF, 0xAF, 0x24, 0x24, 0x24, 0x04, 0x04, 0x04, 0x9A, 0x9A, 0x9A, 0xA8, 0xA8,
+ 0xA8, 0x03, 0x03, 0x03, 0x09, 0x09, 0x09, 0xD7, 0xD7, 0xD7, 0x62, 0x62, 0x62, 0x05, 0x05, 0x05,
+ 0x32, 0x32, 0x32, 0xA0, 0xA0, 0xA0, 0x26, 0x26, 0x26, 0xA7, 0xA7, 0xA7, 0x01, 0x01, 0x01, 0x08,
+ 0x08, 0x08, 0x61, 0x61, 0x61, 0x34, 0x34, 0x34, 0xA1, 0xA1, 0xA1, 0x60, 0x60, 0x60, 0x87, 0x87,
+ 0x87, 0x72, 0x72, 0x72, 0xFB, 0xFB, 0xFB, 0x33, 0x33, 0x33, 0xCD, 0xCD, 0xCD, 0xF3, 0xF3, 0xF3,
+ 0x71, 0x71, 0x71, 0x70, 0x70, 0x70, 0xC1, 0xC1, 0xC1, 0xE4, 0xE4, 0xE4, 0x25, 0x25, 0x25, 0x73,
+ 0x73, 0x73, 0x31, 0x31, 0x31, 0x2D, 0x2D, 0x2D, 0x5A, 0x5A, 0x5A, 0x3C, 0x3C, 0x3C, 0xDD, 0xDD,
+ 0xDD, 0xB8, 0xB8, 0xB8, 0xBB, 0xBB, 0xBB, 0xD8, 0xD8, 0xD8, 0xB7, 0xB7, 0xB7, 0x73, 0x2D, 0x27,
+ 0xCD, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC2, 0x00, 0x00, 0x0E,
+ 0xC2, 0x01, 0x15, 0x28, 0x4A, 0x80, 0x00, 0x00, 0x01, 0x4A, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F,
+ 0xAD, 0x93, 0xE9, 0x56, 0xC2, 0x30, 0x14, 0x84, 0x07, 0x28, 0x10, 0x6F, 0x45, 0x20, 0x54, 0x10,
+ 0x14, 0xA9, 0xA8, 0x88, 0xBB, 0x20, 0x15, 0x77, 0xDC, 0xF7, 0x5D, 0x8B, 0xBE, 0xFF, 0x93, 0x98,
+ 0x84, 0x52, 0x28, 0x1E, 0x30, 0x9E, 0xE3, 0xFC, 0xE8, 0xA4, 0xE9, 0xD7, 0x34, 0xCD, 0x9D, 0x8B,
+ 0xBF, 0x28, 0x14, 0x1E, 0xA1, 0x90, 0x07, 0x09, 0x45, 0x3C, 0x1F, 0x22, 0xFF, 0x71, 0x04, 0x46,
+ 0x34, 0x16, 0x1F, 0xA2, 0x58, 0x94, 0x75, 0xC9, 0x10, 0xC6, 0x68, 0x94, 0xCC, 0x71, 0x81, 0x48,
+ 0x85, 0x91, 0xA0, 0x89, 0x64, 0x2A, 0xCD, 0x39, 0x4F, 0xA7, 0x32, 0x96, 0x74, 0xCE, 0x27, 0xB3,
+ 0x39, 0x35, 0x61, 0xF1, 0x29, 0xCA, 0xA3, 0xA0, 0xC8, 0x30, 0x18, 0x4D, 0x63, 0xC6, 0x60, 0x8C,
+ 0x19, 0x45, 0xCC, 0x1A, 0xAC, 0xC4, 0x4A, 0xF6, 0x1C, 0xCA, 0x72, 0x22, 0x31, 0x8F, 0x05, 0x5A,
+ 0x54, 0xEB, 0x49, 0xB0, 0x44, 0x15, 0x2C, 0xA9, 0xCF, 0x54, 0xB1, 0xAC, 0x9C, 0x56, 0xB0, 0xAA,
+ 0x7C, 0x0D, 0xEB, 0xB4, 0x91, 0x2D, 0x6F, 0xCA, 0x25, 0x25, 0xB8, 0x85, 0x1A, 0xD5, 0xCD, 0x3A,
+ 0x6D, 0xA3, 0x21, 0xDC, 0xA1, 0x9D, 0x26, 0x76, 0x69, 0x4F, 0x4C, 0xEC, 0xE3, 0x40, 0xBD, 0x50,
+ 0x15, 0x58, 0x07, 0x3C, 0x24, 0x93, 0x1C, 0x01, 0x1E, 0x89, 0xEB, 0x31, 0xB5, 0x9A, 0x38, 0x11,
+ 0x03, 0x87, 0x4E, 0x71, 0x46, 0xE7, 0x17, 0x97, 0x14, 0xC7, 0x55, 0x1F, 0x68, 0x0E, 0x82, 0x26,
+ 0x5D, 0x0B, 0xF0, 0x06, 0xB7, 0x74, 0x17, 0x58, 0x71, 0x08, 0x98, 0x47, 0x86, 0xEE, 0x75, 0xC0,
+ 0x07, 0x3C, 0xF6, 0x81, 0x4F, 0xBF, 0x81, 0xDD, 0x3D, 0x6A, 0x83, 0xFF, 0xB1, 0xC7, 0x9F, 0x7F,
+ 0x2D, 0xCF, 0xB1, 0xD1, 0x03, 0xEB, 0x62, 0xD8, 0x39, 0xC7, 0x67, 0x71, 0x8E, 0x5D, 0xB0, 0x46,
+ 0x8E, 0x5F, 0x19, 0xB3, 0x57, 0x99, 0x97, 0x81, 0xCA, 0x54, 0xF0, 0xEA, 0xDD, 0xFB, 0xB5, 0x7E,
+ 0x53, 0xFE, 0x1E, 0xA8, 0xB5, 0x4C, 0xCF, 0x87, 0x6B, 0xB7, 0x6D, 0xD7, 0x42, 0x4E, 0x7A, 0x3B,
+ 0x5F, 0xC0, 0xE7, 0x97, 0x6D, 0xDB, 0x6E, 0x36, 0x90, 0x1E, 0x99, 0x47, 0x95, 0x43, 0x3F, 0x8F,
+ 0x49, 0x2F, 0x8F, 0xBC, 0xD8, 0x97, 0x47, 0xED, 0x84, 0x6B, 0xF7, 0x8C, 0x7E, 0x17, 0xEA, 0xF7,
+ 0xB5, 0x8E, 0x80, 0x6F, 0xE5, 0x3C, 0x46, 0x4D, 0x8A, 0xE9, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00,
+ 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, 0x09, 0xE7, 0x9D, 0xBA, 0xCC, 0x02, 0x00, 0x00
+}; //Battery3.png
+
+//Content of BatteryBlank.png with gzip compression
+static const uint8_t batteryBlank_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xA9, 0x26, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x42, 0x6C, 0x61, 0x6E, 0x6B, 0x2E, 0x70, 0x6E, 0x67, 0x00, 0x01, 0xAF, 0x00, 0x50, 0xFF,
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1B, 0x04, 0x03, 0x00, 0x00, 0x00, 0xFC, 0x3E, 0xD0,
+ 0x5D, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
+ 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00,
+ 0x00, 0x0C, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFC, 0xFB,
+ 0xFB, 0xFB, 0xB9, 0x60, 0x3E, 0xF0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
+ 0x0E, 0xC0, 0x00, 0x00, 0x0E, 0xC0, 0x01, 0x6A, 0xD6, 0x89, 0x09, 0x00, 0x00, 0x00, 0x2C, 0x49,
+ 0x44, 0x41, 0x54, 0x28, 0xCF, 0x63, 0xA0, 0x05, 0x10, 0xC4, 0x04, 0x02, 0x0C, 0x4A, 0x98, 0x40,
+ 0x81, 0x32, 0x41, 0x25, 0x6C, 0x82, 0xCA, 0x44, 0xAB, 0x54, 0xA2, 0xCC, 0x4C, 0x5A, 0x58, 0xA4,
+ 0x44, 0x69, 0x28, 0x61, 0x0D, 0x79, 0xAA, 0x03, 0x06, 0x06, 0x00, 0xEC, 0x94, 0x2F, 0xC6, 0xFE,
+ 0xC5, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, 0x2E,
+ 0xF7, 0x8A, 0xC2, 0xAF, 0x00, 0x00, 0x00
+}; //BatteryBlank.png
+
+//Content of Battery0_Charging.png with gzip compression
+static const uint8_t battery0_Charging_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x4F, 0x16, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x30, 0x5F, 0x43, 0x68, 0x61, 0x72, 0x67, 0x69, 0x6E, 0x67, 0x2E, 0x70, 0x6E, 0x67, 0x00,
+ 0x01, 0xC4, 0x01, 0x3B, 0xFE, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00,
+ 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1A, 0x08, 0x03, 0x00,
+ 0x00, 0x00, 0xF2, 0x92, 0xEE, 0xF9, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE,
+ 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B,
+ 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x87, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFC, 0xFC,
+ 0xFC, 0xFD, 0xFD, 0xFD, 0xEB, 0xF4, 0xEC, 0xBC, 0xDD, 0xBF, 0xFE, 0xFE, 0xFE, 0x21, 0x84, 0x2C,
+ 0x00, 0x7F, 0x0E, 0x27, 0x86, 0x31, 0x5F, 0x9E, 0x66, 0x6A, 0xA4, 0x71, 0xFB, 0xFB, 0xFB, 0x49,
+ 0x90, 0x51, 0x30, 0x8A, 0x3A, 0xB4, 0xD7, 0xB8, 0xA9, 0xCF, 0xAD, 0xB0, 0xD4, 0xB4, 0xAA, 0xD0,
+ 0xAE, 0xAB, 0xD1, 0xB0, 0xA7, 0xCE, 0xAC, 0x25, 0x85, 0x30, 0x51, 0x95, 0x59, 0xF8, 0xF8, 0xF8,
+ 0x4D, 0x92, 0x54, 0x3E, 0x8A, 0x46, 0x49, 0x97, 0x51, 0x4B, 0x92, 0x53, 0x3C, 0x89, 0x44, 0x28,
+ 0x86, 0x32, 0x33, 0x85, 0x3C, 0xCA, 0xD8, 0xCB, 0x4A, 0x91, 0x52, 0x3D, 0x8A, 0x45, 0x0B, 0x7E,
+ 0x17, 0x1B, 0x81, 0x26, 0x77, 0xAD, 0x7D, 0x3F, 0x91, 0x48, 0x5E, 0x9D, 0x65, 0xA0, 0xC9, 0xA5,
+ 0x53, 0x96, 0x5A, 0x8A, 0xBA, 0x8F, 0x55, 0x97, 0x5C, 0x91, 0xBE, 0x96, 0xFA, 0xFA, 0xFA, 0x22,
+ 0x84, 0x2D, 0x45, 0x7D, 0x7F, 0x57, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
+ 0x0E, 0xC0, 0x00, 0x00, 0x0E, 0xC0, 0x01, 0x6A, 0xD6, 0x89, 0x09, 0x00, 0x00, 0x00, 0xC6, 0x49,
+ 0x44, 0x41, 0x54, 0x38, 0x4F, 0xAD, 0x93, 0xD9, 0x12, 0x82, 0x30, 0x0C, 0x45, 0x6F, 0x37, 0x83,
+ 0x16, 0xF7, 0x5D, 0xDC, 0x77, 0xC5, 0xFF, 0xFF, 0x3E, 0x5B, 0xE8, 0x03, 0x8C, 0x88, 0x61, 0xC6,
+ 0xF3, 0xD2, 0x99, 0x70, 0x9A, 0x96, 0x34, 0x41, 0x13, 0x84, 0x14, 0x35, 0x04, 0xC9, 0xA3, 0x74,
+ 0x0D, 0x2A, 0x48, 0x80, 0x51, 0x2D, 0xAA, 0x21, 0x52, 0x26, 0xF7, 0x04, 0xDA, 0x21, 0xF4, 0x85,
+ 0x0E, 0x64, 0x26, 0x5A, 0xC4, 0xD4, 0xD5, 0xBD, 0xEC, 0x98, 0x5E, 0x3F, 0x5F, 0xF5, 0x60, 0x98,
+ 0x31, 0x1A, 0xEB, 0x09, 0x4D, 0x31, 0x13, 0x3E, 0xA9, 0xC0, 0x9C, 0x62, 0x63, 0x42, 0xFE, 0x22,
+ 0x46, 0x4A, 0x89, 0x05, 0x2D, 0xFD, 0x2D, 0xDD, 0x67, 0x81, 0x15, 0xC5, 0xD6, 0x5A, 0xE9, 0xE4,
+ 0x32, 0xD2, 0x45, 0x91, 0x10, 0xAD, 0x37, 0xC9, 0xD6, 0x99, 0x5E, 0xDC, 0xB9, 0xBD, 0x9F, 0x29,
+ 0x8D, 0xAF, 0xCD, 0x3E, 0xBB, 0xE7, 0x41, 0xD9, 0xDF, 0xE2, 0xF1, 0x74, 0x26, 0xD2, 0x0C, 0xF1,
+ 0x82, 0x2B, 0x4F, 0xBC, 0xE1, 0xCE, 0x13, 0x1F, 0x78, 0xFE, 0x59, 0x64, 0x1F, 0xCD, 0xFE, 0x19,
+ 0x76, 0x79, 0x8A, 0x05, 0xAF, 0x7C, 0x42, 0x29, 0xD2, 0xB4, 0xFC, 0x84, 0xCC, 0xA6, 0x68, 0xD0,
+ 0x66, 0xCC, 0xC6, 0x75, 0xA3, 0xF0, 0x0A, 0xA1, 0x4A, 0x22, 0x15, 0x3C, 0x07, 0x73, 0xB8, 0x1A,
+ 0x8C, 0x2B, 0x03, 0xE0, 0x0D, 0x08, 0xEF, 0x14, 0xF0, 0xC6, 0x56, 0x07, 0x1F, 0x00, 0x00, 0x00,
+ 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, 0x66, 0x5C, 0xEC, 0xE1, 0xC4, 0x01, 0x00,
+ 0x00
+}; //Battery0_Charging.png
+
+//Content of Battery1_Charging.png with gzip compression
+static const uint8_t battery1_Charging_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x47, 0x16, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x31, 0x5F, 0x43, 0x68, 0x61, 0x72, 0x67, 0x69, 0x6E, 0x67, 0x2E, 0x70, 0x6E, 0x67, 0x00,
+ 0x01, 0x0E, 0x02, 0xF1, 0xFD, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00,
+ 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1A, 0x08, 0x03, 0x00,
+ 0x00, 0x00, 0xF2, 0x92, 0xEE, 0xF9, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE,
+ 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B,
+ 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0xB1, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFC, 0xFC,
+ 0xFC, 0xFD, 0xFD, 0xFD, 0xEB, 0xF4, 0xEC, 0xBC, 0xDD, 0xBF, 0xFE, 0xFE, 0xFE, 0x21, 0x84, 0x2C,
+ 0x00, 0x7F, 0x0E, 0x27, 0x86, 0x31, 0x5F, 0x9E, 0x66, 0x6A, 0xA4, 0x71, 0xFB, 0xFB, 0xFB, 0x49,
+ 0x90, 0x51, 0x30, 0x8A, 0x3A, 0xB4, 0xD7, 0xB8, 0xA9, 0xCF, 0xAD, 0xB0, 0xD4, 0xB4, 0xAA, 0xD0,
+ 0xAE, 0xAB, 0xD1, 0xB0, 0xA7, 0xCE, 0xAC, 0x25, 0x85, 0x30, 0x51, 0x95, 0x59, 0xF8, 0xF8, 0xF8,
+ 0x4D, 0x92, 0x54, 0xF5, 0xF9, 0xF5, 0x5D, 0x95, 0x63, 0x28, 0x78, 0x31, 0x30, 0x7B, 0x38, 0x2E,
+ 0x7B, 0x36, 0xB2, 0xBF, 0xB3, 0x3E, 0x8A, 0x46, 0x49, 0x97, 0x51, 0x4B, 0x92, 0x53, 0xC3, 0xDB,
+ 0xC6, 0x44, 0x86, 0x4B, 0x3C, 0x89, 0x44, 0x28, 0x86, 0x32, 0x33, 0x85, 0x3C, 0xCA, 0xD8, 0xCB,
+ 0x4A, 0x91, 0x52, 0xC2, 0xDA, 0xC5, 0x4C, 0x8A, 0x53, 0x3D, 0x8A, 0x45, 0x0B, 0x7E, 0x17, 0x4B,
+ 0x8A, 0x52, 0x1B, 0x81, 0x26, 0x77, 0xAD, 0x7D, 0x3F, 0x91, 0x48, 0x5E, 0x9D, 0x65, 0xA0, 0xC9,
+ 0xA5, 0x53, 0x96, 0x5A, 0x8A, 0xBA, 0x8F, 0x55, 0x97, 0x5C, 0x91, 0xBE, 0x96, 0xFA, 0xFA, 0xFA,
+ 0x5F, 0x96, 0x65, 0x2A, 0x79, 0x33, 0x31, 0x7C, 0x3A, 0x22, 0x84, 0x2D, 0x95, 0x79, 0x7B, 0x97,
+ 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC0, 0x00, 0x00, 0x0E, 0xC0,
+ 0x01, 0x6A, 0xD6, 0x89, 0x09, 0x00, 0x00, 0x00, 0xE6, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xAD,
+ 0x93, 0x69, 0x4F, 0xC2, 0x40, 0x10, 0x40, 0x67, 0x2F, 0x07, 0xD9, 0x52, 0x45, 0x45, 0xA4, 0xC5,
+ 0xB3, 0x78, 0x80, 0x22, 0xA0, 0x58, 0x5B, 0xFE, 0xFF, 0x0F, 0x63, 0xAF, 0x04, 0x49, 0xDA, 0xCD,
+ 0x34, 0xF1, 0x7D, 0xD9, 0x64, 0xF6, 0x65, 0xA6, 0x9D, 0x9D, 0x81, 0x2E, 0x30, 0xCE, 0x22, 0x04,
+ 0xC9, 0x22, 0x64, 0x04, 0x11, 0x24, 0x00, 0x25, 0x4E, 0x30, 0x42, 0x4F, 0x28, 0xEF, 0x31, 0x38,
+ 0x0D, 0xA1, 0x16, 0xFA, 0xC0, 0x9D, 0xA8, 0x21, 0xC1, 0x81, 0x4C, 0x5D, 0x99, 0xF4, 0xCC, 0x9F,
+ 0xF2, 0x7C, 0xE8, 0xB8, 0xB8, 0x94, 0x57, 0x38, 0x82, 0x6B, 0x66, 0x93, 0x32, 0x18, 0x63, 0x72,
+ 0x33, 0xC9, 0xF2, 0x69, 0x9E, 0xDD, 0xAA, 0x50, 0xC6, 0xA3, 0x38, 0x87, 0x3B, 0xBC, 0xB7, 0x5F,
+ 0x69, 0xE2, 0x0C, 0x1E, 0x30, 0x79, 0xF4, 0x55, 0x9E, 0xB4, 0xE6, 0xEA, 0x00, 0xD7, 0x1A, 0x0A,
+ 0xC4, 0xD9, 0x73, 0xF1, 0x62, 0x4C, 0x2B, 0xBE, 0xBE, 0x79, 0x71, 0xCE, 0xF9, 0xDF, 0x94, 0xCA,
+ 0xF4, 0x66, 0xE1, 0x2E, 0xDE, 0x85, 0xF6, 0x62, 0xC8, 0xF8, 0xD1, 0x24, 0x2E, 0x3F, 0x57, 0x88,
+ 0x92, 0x20, 0xAE, 0x61, 0x43, 0x13, 0xBF, 0xE0, 0x9B, 0x26, 0x6E, 0xE1, 0xE7, 0x9F, 0x45, 0x72,
+ 0x69, 0xF2, 0xCF, 0x1C, 0xB5, 0x87, 0xDA, 0xF0, 0x96, 0x27, 0x64, 0x65, 0x79, 0xF4, 0x84, 0x76,
+ 0x28, 0x7E, 0xAB, 0x3A, 0xAF, 0xAB, 0xE8, 0x50, 0x74, 0x18, 0x33, 0xE2, 0xE0, 0x9A, 0x55, 0xD8,
+ 0x85, 0x50, 0x23, 0x3D, 0x11, 0x3C, 0x03, 0x71, 0xB9, 0x3A, 0xAC, 0x2B, 0x01, 0x80, 0x3D, 0x76,
+ 0xDE, 0x1E, 0x86, 0xD1, 0x89, 0x12, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
+ 0x42, 0x60, 0x82, 0xE1, 0xFD, 0x90, 0x99, 0x0E, 0x02, 0x00, 0x00
+}; //Battery1_Charging.png
+
+//Content of Battery2_Charging.png with gzip compression
+static const uint8_t battery2_Charging_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x3B, 0x13, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x32, 0x5F, 0x43, 0x68, 0x61, 0x72, 0x67, 0x69, 0x6E, 0x67, 0x2E, 0x70, 0x6E, 0x67, 0x00,
+ 0x01, 0x4E, 0x02, 0xB1, 0xFD, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00,
+ 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1A, 0x08, 0x03, 0x00,
+ 0x00, 0x00, 0xF2, 0x92, 0xEE, 0xF9, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE,
+ 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B,
+ 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0xDB, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFC, 0xFC,
+ 0xFC, 0xFD, 0xFD, 0xFD, 0xEB, 0xF4, 0xEC, 0xBC, 0xDD, 0xBF, 0xFE, 0xFE, 0xFE, 0x21, 0x84, 0x2C,
+ 0x00, 0x7F, 0x0E, 0x27, 0x86, 0x31, 0x5F, 0x9E, 0x66, 0x6A, 0xA4, 0x71, 0xFB, 0xFB, 0xFB, 0x49,
+ 0x90, 0x51, 0x30, 0x8A, 0x3A, 0xB4, 0xD7, 0xB8, 0xA9, 0xCF, 0xAD, 0xB0, 0xD4, 0xB4, 0xAA, 0xD0,
+ 0xAE, 0xAB, 0xD1, 0xB0, 0xA7, 0xCE, 0xAC, 0x25, 0x85, 0x30, 0x51, 0x95, 0x59, 0xF8, 0xF8, 0xF8,
+ 0x4D, 0x92, 0x54, 0xF5, 0xF9, 0xF5, 0x5D, 0x95, 0x63, 0x28, 0x78, 0x31, 0x30, 0x7B, 0x38, 0x2E,
+ 0x7B, 0x36, 0xB2, 0xBF, 0xB3, 0xB7, 0xC3, 0xB9, 0x2B, 0x79, 0x34, 0x2F, 0x7B, 0x37, 0x2D, 0x7A,
+ 0x35, 0x34, 0x7D, 0x3C, 0xD6, 0xDF, 0xD7, 0x3E, 0x8A, 0x46, 0x49, 0x97, 0x51, 0x4B, 0x92, 0x53,
+ 0xC3, 0xDB, 0xC6, 0x44, 0x86, 0x4B, 0x4B, 0x8A, 0x52, 0x89, 0xB1, 0x8E, 0x3C, 0x89, 0x44, 0x28,
+ 0x86, 0x32, 0x33, 0x85, 0x3C, 0xCA, 0xD8, 0xCB, 0x4A, 0x91, 0x52, 0xC2, 0xDA, 0xC5, 0x4C, 0x8A,
+ 0x53, 0x54, 0x8F, 0x5B, 0x8F, 0xB6, 0x94, 0x3D, 0x8A, 0x45, 0x0B, 0x7E, 0x17, 0x51, 0x8E, 0x58,
+ 0x1B, 0x81, 0x26, 0x77, 0xAD, 0x7D, 0x3F, 0x91, 0x48, 0x5E, 0x9D, 0x65, 0xA0, 0xC9, 0xA5, 0x53,
+ 0x96, 0x5A, 0x8A, 0xBA, 0x8F, 0x55, 0x97, 0x5C, 0x91, 0xBE, 0x96, 0xFA, 0xFA, 0xFA, 0x5F, 0x96,
+ 0x65, 0x2A, 0x79, 0x33, 0x31, 0x7C, 0x3A, 0x2C, 0x79, 0x34, 0x30, 0x7C, 0x39, 0x2D, 0x7A, 0x36,
+ 0x35, 0x7E, 0x3D, 0x22, 0x84, 0x2D, 0xC5, 0x39, 0x3A, 0xC0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48,
+ 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC1, 0x00, 0x00, 0x0E, 0xC1, 0x01, 0xB8, 0x91, 0x6B, 0xED, 0x00,
+ 0x00, 0x00, 0xFC, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xAD, 0x93, 0x69, 0x53, 0xC2, 0x30, 0x10,
+ 0x86, 0xDF, 0x34, 0x29, 0x0B, 0xB6, 0x22, 0x20, 0x5E, 0xB4, 0x8A, 0xA2, 0x80, 0x88, 0xF7, 0x7D,
+ 0x73, 0x89, 0xFA, 0xFF, 0x7F, 0x91, 0x49, 0x37, 0x1F, 0x64, 0x06, 0x3A, 0xCB, 0x0C, 0xCF, 0x97,
+ 0x6E, 0xD2, 0x67, 0xB2, 0x39, 0x76, 0xB1, 0x0C, 0x2A, 0x50, 0x39, 0x78, 0xC9, 0xA1, 0x4D, 0x0E,
+ 0xDA, 0x4B, 0x40, 0xA8, 0x0B, 0x94, 0x43, 0x51, 0x87, 0xEC, 0x29, 0x94, 0xFC, 0xD4, 0x02, 0xD6,
+ 0x10, 0x64, 0x62, 0x84, 0x98, 0xD6, 0x4D, 0x39, 0x4B, 0x53, 0xDE, 0xE0, 0xAF, 0xA9, 0x54, 0x33,
+ 0x6A, 0x9B, 0xA6, 0x4E, 0x5B, 0xD8, 0x56, 0x6E, 0x51, 0x85, 0x1D, 0x8A, 0x77, 0xF7, 0x1A, 0x49,
+ 0x9A, 0x34, 0xF6, 0x71, 0xD0, 0x4C, 0xD3, 0xC3, 0xA3, 0xD6, 0x31, 0x38, 0x5F, 0x80, 0x13, 0x6A,
+ 0xBB, 0x5D, 0xDA, 0xA1, 0x42, 0x87, 0xE2, 0x2E, 0x67, 0x39, 0x45, 0x8F, 0x83, 0x33, 0x44, 0xA1,
+ 0x23, 0x42, 0x9F, 0xE8, 0xFC, 0xA2, 0x7F, 0x69, 0x4D, 0x27, 0x5E, 0x5D, 0xF3, 0xFF, 0x1B, 0xDC,
+ 0x72, 0x70, 0xE7, 0xF7, 0xA5, 0x70, 0x9F, 0x8D, 0x1F, 0x74, 0xC4, 0xA2, 0x5F, 0xB1, 0x87, 0x47,
+ 0x0E, 0xFE, 0x8B, 0x4F, 0xCF, 0x2F, 0x44, 0x46, 0x20, 0xBE, 0xE2, 0x4D, 0x26, 0xBE, 0xE3, 0x43,
+ 0x26, 0x7E, 0x62, 0xB0, 0x62, 0x51, 0x9C, 0x5A, 0x7C, 0x98, 0x99, 0xEB, 0x91, 0x5E, 0xF8, 0xC2,
+ 0x27, 0x1C, 0xCE, 0x3C, 0xA1, 0x2B, 0x8A, 0xD1, 0x78, 0x92, 0x4C, 0xC6, 0xB6, 0x28, 0xBE, 0x92,
+ 0xE9, 0xF4, 0xFB, 0x67, 0x5E, 0x51, 0x2C, 0x51, 0x66, 0xC2, 0xC2, 0xB5, 0xAD, 0xF0, 0xEB, 0xA7,
+ 0xE6, 0x52, 0xD4, 0xDE, 0xB3, 0x08, 0x9B, 0xCB, 0x66, 0x97, 0xB6, 0xAB, 0x00, 0xE0, 0x0F, 0x62,
+ 0x8A, 0x29, 0x7A, 0x97, 0xAB, 0x67, 0x54, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
+ 0x42, 0x60, 0x82, 0xAB, 0xD7, 0x1E, 0x8B, 0x4E, 0x02, 0x00, 0x00
+}; //Battery2_Charging.png
+
+//Content of Battery3_Charging.png with gzip compression
+static const uint8_t battery3_Charging_png[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xBA, 0x15, 0x02, 0x64, 0x04, 0x00, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
+ 0x79, 0x33, 0x5F, 0x43, 0x68, 0x61, 0x72, 0x67, 0x69, 0x6E, 0x67, 0x2E, 0x70, 0x6E, 0x67, 0x00,
+ 0x01, 0x7C, 0x02, 0x83, 0xFD, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00,
+ 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1B, 0x08, 0x03, 0x00,
+ 0x00, 0x00, 0x39, 0xCE, 0x3D, 0x5C, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE,
+ 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B,
+ 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0xED, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0xFF, 0xFF, 0xFD, 0xFD,
+ 0xFD, 0xFC, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xF5, 0xF9, 0xF5, 0x6F, 0xA0, 0x74, 0x26, 0x77, 0x2F,
+ 0x28, 0x78, 0x31, 0x27, 0x77, 0x30, 0x75, 0xA4, 0x7A, 0x96, 0xBA, 0x9A, 0x00, 0x7F, 0x0E, 0xA3,
+ 0xC3, 0xA6, 0x70, 0xA1, 0x76, 0x41, 0x8B, 0x4A, 0xC3, 0xDB, 0xC6, 0xB9, 0xD3, 0xBC, 0xC7, 0xDD,
+ 0xC9, 0xC5, 0xDC, 0xC8, 0xAD, 0xCB, 0xB0, 0xB6, 0xD1, 0xB9, 0xB3, 0xCF, 0xB6, 0xAF, 0xCC, 0xB2,
+ 0xBB, 0xD5, 0xBE, 0x35, 0x85, 0x3E, 0x7A, 0xA7, 0x7F, 0xFA, 0xFA, 0xFA, 0x68, 0x9C, 0x6E, 0xA4,
+ 0xC4, 0xA8, 0xD7, 0xE0, 0xD8, 0xDC, 0xE4, 0xDD, 0x74, 0xA3, 0x79, 0x78, 0xA6, 0x7D, 0xED, 0xF3,
+ 0xED, 0xC4, 0xCE, 0xC5, 0xAC, 0xC9, 0xAF, 0x5B, 0x93, 0x61, 0x89, 0xB1, 0x8E, 0x64, 0x99, 0x6A,
+ 0xDD, 0xED, 0xDE, 0x4A, 0x89, 0x51, 0x5A, 0x92, 0x60, 0x98, 0xBC, 0x9C, 0x1A, 0x73, 0x24, 0x56,
+ 0x90, 0x5C, 0x4B, 0x90, 0x53, 0x9B, 0xBE, 0x9F, 0x9F, 0xC1, 0xA3, 0xF2, 0xF7, 0xF2, 0x68, 0x9B,
+ 0x6D, 0x50, 0x8D, 0x57, 0x5F, 0x96, 0x65, 0x9C, 0xBF, 0xA0, 0x20, 0x75, 0x2A, 0x57, 0x90, 0x5D,
+ 0x5E, 0x95, 0x65, 0x1F, 0x75, 0x29, 0x57, 0x91, 0x5E, 0x0E, 0x7A, 0x1A, 0x3D, 0x82, 0x45, 0x1A,
+ 0x7B, 0x25, 0xFB, 0xFB, 0xFB, 0x8F, 0xB5, 0x93, 0xC8, 0xDE, 0xCA, 0x2B, 0x79, 0x34, 0x2A, 0x79,
+ 0x33, 0x7E, 0xAA, 0x83, 0xB0, 0xCD, 0xB3, 0x1B, 0x7C, 0x26, 0x1B, 0x73, 0x25, 0xA5, 0xC5, 0xA9,
+ 0x72, 0xA2, 0x78, 0x76, 0xA5, 0x7B, 0xD9, 0xE1, 0xDA, 0xDD, 0xE5, 0xDE, 0xEE, 0xF4, 0xEE, 0xC7,
+ 0xD1, 0xC8, 0x71, 0xA1, 0x76, 0xB5, 0xD0, 0xB8, 0xC5, 0x47, 0x46, 0xC2, 0x00, 0x00, 0x00, 0x09,
+ 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC2, 0x00, 0x00, 0x0E, 0xC2, 0x01, 0x15, 0x28, 0x4A,
+ 0x80, 0x00, 0x00, 0x01, 0x18, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x95, 0x93, 0xE7, 0x72, 0xC2,
+ 0x30, 0x10, 0x84, 0xD7, 0xC6, 0x06, 0x94, 0x0B, 0x01, 0x04, 0x71, 0xE2, 0x34, 0x2B, 0xD5, 0xA4,
+ 0x37, 0xD2, 0x20, 0xBD, 0x99, 0xD4, 0xF7, 0x7F, 0x9C, 0x58, 0x65, 0x9C, 0x78, 0xC6, 0x26, 0x62,
+ 0xFF, 0xDC, 0x8E, 0xFC, 0x8D, 0x24, 0xEB, 0xF6, 0x30, 0x89, 0x1C, 0x77, 0x8C, 0x1C, 0x03, 0xA5,
+ 0xAA, 0x98, 0x5A, 0xA2, 0xEC, 0x73, 0xC5, 0xF3, 0xAB, 0xB5, 0x7A, 0x89, 0x6A, 0x55, 0xE6, 0x19,
+ 0xD2, 0xC1, 0x14, 0x8D, 0xD5, 0x74, 0x8A, 0x48, 0xB9, 0x68, 0xD0, 0x4C, 0xB3, 0xD5, 0xE6, 0x9C,
+ 0xB7, 0x5B, 0x9D, 0xAE, 0xAC, 0x9C, 0xCF, 0x06, 0x73, 0x6A, 0xA1, 0xCB, 0xE7, 0x29, 0xC4, 0x82,
+ 0x22, 0x5D, 0x30, 0x5A, 0xF4, 0x96, 0x7C, 0xC6, 0x98, 0xBF, 0x8C, 0x15, 0x9F, 0x45, 0x2C, 0x12,
+ 0xAB, 0x58, 0x93, 0x0B, 0x8D, 0x75, 0x6F, 0x83, 0x36, 0xD5, 0x7E, 0x12, 0x8C, 0x68, 0x2B, 0xD6,
+ 0xA7, 0xF4, 0xB0, 0xAD, 0xCD, 0x0E, 0x76, 0xB5, 0x89, 0xF7, 0x68, 0x3F, 0x38, 0x38, 0x3C, 0x32,
+ 0xE0, 0xB1, 0x01, 0x4F, 0x70, 0xAA, 0x4D, 0x1F, 0x67, 0xDA, 0xC4, 0xE7, 0xAA, 0xF4, 0x52, 0x2C,
+ 0x0F, 0x5E, 0x68, 0xD3, 0xC7, 0xA5, 0x36, 0xF1, 0x15, 0x0D, 0x86, 0xD7, 0x54, 0xC7, 0xCD, 0xFF,
+ 0xE0, 0x2D, 0xEE, 0xE8, 0xDE, 0x66, 0xC7, 0x10, 0x1D, 0x7A, 0xB0, 0x01, 0x1F, 0xF1, 0x34, 0x09,
+ 0x68, 0x71, 0x47, 0x6B, 0xD0, 0xFA, 0x8E, 0xC5, 0x7F, 0x9D, 0x3D, 0xF8, 0x2F, 0x38, 0x18, 0x3E,
+ 0xA7, 0xEF, 0x58, 0x0A, 0x16, 0x77, 0xA6, 0xA0, 0xD7, 0x2F, 0xDA, 0xE4, 0x7A, 0x2D, 0xD3, 0xF3,
+ 0x9A, 0x88, 0x91, 0x48, 0xDE, 0xF0, 0x2E, 0xEB, 0x28, 0xFC, 0xC0, 0xE7, 0x97, 0x10, 0x22, 0x09,
+ 0x72, 0xE9, 0x91, 0x79, 0x54, 0x39, 0xCC, 0xF2, 0xD8, 0x34, 0x79, 0xE4, 0xDF, 0x7F, 0xF2, 0x68,
+ 0x9D, 0x70, 0xEB, 0x99, 0xB1, 0x9F, 0x42, 0xFB, 0xB9, 0xB6, 0x11, 0xF0, 0x03, 0x9C, 0x73, 0x3B,
+ 0x83, 0xC2, 0x60, 0x2B, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60,
+ 0x82, 0x44, 0xD9, 0x6C, 0x7F, 0x7C, 0x02, 0x00, 0x00
+}; //Battery3_Charging.png
+
+//Content of style.css with gzip compression
+static const uint8_t style_css[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x73, 0x2D, 0x02, 0x64, 0x04, 0x00, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x2E,
+ 0x63, 0x73, 0x73, 0x00, 0x8D, 0x53, 0xCD, 0x6E, 0xDA, 0x40, 0x10, 0xBE, 0x47, 0xE2, 0x1D, 0xA6,
+ 0xAE, 0x2A, 0x07, 0x14, 0x87, 0x44, 0x55, 0x14, 0xE4, 0x08, 0xB5, 0xEA, 0xA1, 0x0F, 0xD0, 0x6B,
+ 0xDA, 0x4A, 0xCB, 0x7A, 0x8C, 0x47, 0xAC, 0x77, 0xDD, 0xDD, 0x31, 0x86, 0x56, 0x7D, 0xF7, 0x8C,
+ 0x8D, 0x81, 0xA0, 0x00, 0xC1, 0x27, 0xEF, 0xF7, 0x33, 0xBF, 0xBB, 0x5F, 0x73, 0x67, 0x39, 0xC9,
+ 0x95, 0x46, 0xF8, 0x37, 0xB8, 0x02, 0xE8, 0x8F, 0x25, 0x99, 0x75, 0x0A, 0x31, 0x69, 0x57, 0x3A,
+ 0x67, 0xE3, 0xA7, 0x96, 0x0A, 0x5E, 0xA7, 0x00, 0xB5, 0x37, 0xD7, 0x71, 0xAB, 0x0A, 0xE3, 0x9E,
+ 0xBD, 0x45, 0xC7, 0x5F, 0x26, 0xF7, 0xCD, 0xEA, 0xCF, 0xE7, 0x78, 0x78, 0xA9, 0xF2, 0x23, 0x61,
+ 0x4E, 0xAB, 0x78, 0x28, 0x09, 0x7D, 0xA9, 0xF8, 0x3A, 0xC6, 0x72, 0x86, 0x59, 0x86, 0x59, 0xE2,
+ 0x2A, 0xB4, 0xBC, 0xAE, 0x30, 0x1E, 0xDE, 0xB4, 0xB1, 0x8E, 0xC6, 0x61, 0xCE, 0xB7, 0x19, 0xF7,
+ 0x11, 0xD8, 0xD7, 0xF8, 0x9E, 0xB1, 0x71, 0xF9, 0x11, 0x67, 0x8B, 0x9E, 0x73, 0x85, 0xE5, 0x7C,
+ 0x5B, 0x76, 0x0F, 0xBD, 0x32, 0x0B, 0x29, 0x6D, 0xEF, 0x66, 0xD7, 0x20, 0xCD, 0x0B, 0x4E, 0xC1,
+ 0xB6, 0xB4, 0xD9, 0xE3, 0x81, 0xD7, 0x06, 0xDF, 0xC2, 0x19, 0x85, 0xCA, 0x28, 0x99, 0xF5, 0xCC,
+ 0x38, 0xBD, 0x10, 0xFC, 0xFF, 0xE0, 0x6A, 0x70, 0xF5, 0xAC, 0x8D, 0x0A, 0xE1, 0xF7, 0x34, 0x92,
+ 0x74, 0x36, 0x89, 0x7E, 0xDD, 0xC0, 0x06, 0x19, 0x4D, 0x23, 0xE8, 0xA1, 0xCD, 0xBA, 0xC6, 0x23,
+ 0xA8, 0x03, 0xC2, 0x07, 0x2A, 0x2B, 0xE7, 0x59, 0x59, 0x06, 0x76, 0x50, 0x79, 0x5C, 0xA2, 0xFC,
+ 0x52, 0x08, 0x35, 0x06, 0x68, 0x88, 0x0B, 0x98, 0x79, 0xD7, 0x04, 0xF4, 0x80, 0x2B, 0x46, 0x1B,
+ 0xC8, 0xD9, 0x00, 0x5C, 0x28, 0x06, 0x5D, 0x28, 0x3B, 0x47, 0xE8, 0xFA, 0x85, 0xD1, 0xF8, 0xF4,
+ 0x15, 0x78, 0x95, 0x63, 0xB3, 0xE3, 0x0A, 0xD5, 0x42, 0xFA, 0x91, 0x54, 0xFE, 0xDD, 0x2E, 0x4F,
+ 0x0D, 0x65, 0xA9, 0x3C, 0x49, 0xC0, 0x03, 0x82, 0xA5, 0xC4, 0x84, 0xBD, 0xB2, 0xA1, 0x9D, 0x70,
+ 0x4B, 0x59, 0xEC, 0x08, 0x43, 0x16, 0x93, 0xA2, 0x8F, 0x74, 0x2F, 0x50, 0x3F, 0x80, 0x6F, 0xC8,
+ 0x2C, 0x9D, 0x7D, 0x97, 0x80, 0xF0, 0x03, 0x6D, 0x86, 0x9E, 0xEC, 0x1C, 0xA6, 0xFB, 0xAF, 0xEF,
+ 0x4B, 0xAA, 0x98, 0x2D, 0x88, 0x93, 0x4D, 0xA1, 0xD2, 0x14, 0x17, 0x22, 0x4C, 0x41, 0x0A, 0x20,
+ 0x65, 0x48, 0x05, 0xCC, 0x9E, 0x3A, 0x5D, 0xE9, 0xFE, 0x26, 0x2E, 0xAC, 0xDE, 0x08, 0xE7, 0x5E,
+ 0xAD, 0x83, 0x56, 0x06, 0xB7, 0x6B, 0x22, 0x5B, 0xD5, 0xFC, 0xDC, 0xDE, 0xB9, 0x69, 0x94, 0x93,
+ 0xC1, 0xED, 0x56, 0xFA, 0xA5, 0x76, 0xB5, 0xEF, 0xC4, 0xB7, 0xDD, 0xE2, 0xB4, 0x71, 0x01, 0xD3,
+ 0x19, 0x4A, 0x6F, 0xFD, 0x8B, 0x13, 0x94, 0xB1, 0x9D, 0x41, 0xF4, 0x33, 0xBF, 0xBB, 0xCB, 0xA2,
+ 0x56, 0xBF, 0x55, 0x7B, 0x2C, 0xDD, 0xF2, 0x72, 0x39, 0x53, 0x89, 0xE1, 0x62, 0x35, 0xD9, 0xDC,
+ 0x25, 0x9A, 0xBC, 0x36, 0x67, 0x32, 0x3C, 0xA8, 0x03, 0x4F, 0x6E, 0x5C, 0x55, 0xAD, 0x13, 0x77,
+ 0xDA, 0xA0, 0x1F, 0x0F, 0x0C, 0x41, 0x2D, 0xF1, 0x62, 0xB1, 0x56, 0x1E, 0xE5, 0x49, 0xB8, 0xC6,
+ 0x9E, 0xB6, 0x64, 0xC7, 0x2C, 0x75, 0x75, 0xC6, 0x30, 0x89, 0x76, 0x2B, 0x30, 0x98, 0xF3, 0x46,
+ 0xD2, 0x50, 0xC6, 0x45, 0x0A, 0x93, 0x87, 0x4F, 0x3B, 0xD2, 0xCB, 0xCD, 0x3A, 0x64, 0x1F, 0x3B,
+ 0xF2, 0x05, 0x66, 0x1A, 0xFF, 0xC3, 0x23, 0x05, 0x00, 0x00
+}; //style.css
+
+//Content of icomoon.eot with gzip compression
+static const uint8_t icomoon_eot[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x21, 0x7F, 0x4E, 0x61, 0x04, 0x00, 0x69, 0x63, 0x6F, 0x6D, 0x6F, 0x6F,
+ 0x6E, 0x2E, 0x65, 0x6F, 0x74, 0x00, 0x95, 0x55, 0x4D, 0x88, 0xD3, 0x40, 0x14, 0x7E, 0x33, 0x69,
+ 0x93, 0xB4, 0xD1, 0x8D, 0x2B, 0x4D, 0x03, 0xBA, 0x6A, 0xB6, 0x71, 0x15, 0x11, 0xD7, 0xBA, 0xDD,
+ 0xB6, 0x88, 0x3F, 0xD5, 0x8B, 0x18, 0x7F, 0x8B, 0x8A, 0x07, 0x15, 0xB4, 0xAE, 0xB5, 0x0A, 0x6D,
+ 0x5A, 0x6A, 0xC5, 0x1F, 0x44, 0x3C, 0x89, 0x78, 0x50, 0xC1, 0x8B, 0x9E, 0x14, 0x51, 0x28, 0x08,
+ 0x5E, 0x8B, 0xE0, 0x61, 0x41, 0x44, 0x10, 0xEF, 0x0A, 0xEA, 0x41, 0x94, 0xBD, 0x08, 0xD2, 0x8B,
+ 0x28, 0xE8, 0x5A, 0xBF, 0x49, 0x52, 0xAB, 0x76, 0x11, 0x9D, 0x76, 0xE6, 0x7D, 0xDF, 0x9B, 0xF7,
+ 0xDE, 0xCC, 0x9B, 0x99, 0xCC, 0x4C, 0x2A, 0x44, 0x43, 0xA8, 0x8C, 0x38, 0xFD, 0x5A, 0x84, 0xE6,
+ 0x1A, 0x13, 0x72, 0x5B, 0x9E, 0xFA, 0x7A, 0xFC, 0x72, 0x8B, 0x5F, 0xD8, 0x48, 0x7D, 0x65, 0x80,
+ 0x8E, 0xD3, 0x04, 0x55, 0xA9, 0x42, 0x55, 0xFC, 0x5C, 0x4F, 0xB3, 0x8B, 0x8A, 0x54, 0xA2, 0x93,
+ 0x54, 0xA6, 0x02, 0xD5, 0xA1, 0x89, 0xD3, 0x5E, 0x2A, 0x02, 0x9D, 0x80, 0xAD, 0xB0, 0xB1, 0x68,
+ 0x15, 0x8D, 0x52, 0xB2, 0xDF, 0xBB, 0x37, 0xA0, 0x46, 0x17, 0x48, 0xA2, 0xE4, 0xCE, 0xDD, 0x2B,
+ 0xC7, 0xF4, 0xB9, 0xB3, 0x9E, 0x40, 0xF3, 0x08, 0xF5, 0xD0, 0x44, 0xA5, 0x50, 0x7B, 0x7B, 0xF6,
+ 0xED, 0x1D, 0x18, 0x0D, 0x81, 0x97, 0x4B, 0x85, 0x13, 0x35, 0xC8, 0x39, 0xE0, 0x97, 0x20, 0xD5,
+ 0x52, 0xF9, 0xCC, 0xD1, 0x87, 0x6A, 0xF3, 0x0A, 0xF8, 0x35, 0x22, 0x29, 0x79, 0xAC, 0x58, 0x38,
+ 0xB2, 0x60, 0xD9, 0xBE, 0x15, 0x44, 0xA1, 0x49, 0xF4, 0x67, 0x8E, 0x41, 0xA1, 0x24, 0xA5, 0x67,
+ 0xE0, 0x5F, 0xC0, 0x17, 0x1F, 0xAB, 0x34, 0x4E, 0xC7, 0xAE, 0x53, 0x8E, 0x28, 0x3C, 0x24, 0x78,
+ 0xB9, 0x3A, 0x51, 0xE0, 0xE7, 0xA5, 0x1A, 0xF8, 0x06, 0xF0, 0x58, 0xA5, 0x70, 0xBA, 0x86, 0x49,
+ 0xEE, 0x00, 0xDF, 0x03, 0x6E, 0xB9, 0x85, 0x4A, 0xF1, 0xE6, 0x96, 0xC8, 0x57, 0xF0, 0x06, 0xC6,
+ 0xB8, 0x58, 0xAB, 0x9E, 0x68, 0x60, 0x9E, 0x28, 0xF2, 0x37, 0xD1, 0x4F, 0x12, 0x7F, 0x8C, 0x91,
+ 0xC3, 0x44, 0xFC, 0x26, 0x7F, 0x0E, 0xCD, 0x55, 0x5F, 0xB2, 0x0F, 0x94, 0x62, 0x91, 0x3F, 0x17,
+ 0x76, 0xCE, 0x9F, 0x8B, 0x89, 0x31, 0xDB, 0x2F, 0xA5, 0xC9, 0xCE, 0x24, 0x6D, 0x90, 0x50, 0xBD,
+ 0xB5, 0xE8, 0x15, 0xCB, 0x6B, 0xA5, 0xA0, 0x0E, 0xA1, 0xCF, 0x93, 0x68, 0x99, 0x27, 0x43, 0x94,
+ 0x17, 0x6B, 0x81, 0x9F, 0xE4, 0x79, 0x5A, 0xED, 0xD9, 0xED, 0x7D, 0xED, 0xA7, 0xED, 0x97, 0x9D,
+ 0xE9, 0x4E, 0x47, 0xB8, 0xFE, 0xD4, 0xBC, 0xF0, 0x34, 0xAC, 0xF3, 0x4E, 0xFF, 0xAC, 0xDF, 0xD7,
+ 0x73, 0x7A, 0xD2, 0x8B, 0xD1, 0xBF, 0xF1, 0x0C, 0x56, 0x7A, 0xAF, 0x47, 0x1C, 0x9A, 0xEC, 0x6A,
+ 0x41, 0xFF, 0x43, 0x97, 0xA3, 0x1C, 0x9F, 0xE2, 0x53, 0xB4, 0x96, 0x68, 0x24, 0x26, 0xEB, 0x6C,
+ 0x80, 0xD9, 0x89, 0x25, 0x2B, 0x99, 0x12, 0xC8, 0x51, 0x96, 0x1E, 0xCF, 0xE4, 0xD8, 0xD2, 0x40,
+ 0xAE, 0x67, 0xA9, 0xB1, 0xF8, 0x22, 0x96, 0x0D, 0xE4, 0x42, 0x66, 0x08, 0x1F, 0x13, 0x92, 0x4F,
+ 0x45, 0x94, 0x1D, 0x6A, 0x4C, 0xD3, 0x62, 0x6A, 0xB3, 0xA9, 0x18, 0x5A, 0xD4, 0x00, 0x45, 0x69,
+ 0x36, 0x45, 0xBB, 0x43, 0x31, 0xA2, 0x9A, 0xA1, 0x80, 0x08, 0x0B, 0xD0, 0x48, 0x04, 0x44, 0x89,
+ 0x3C, 0xFE, 0x37, 0xB3, 0xDF, 0x43, 0x7B, 0x2B, 0x2C, 0x1A, 0x57, 0x72, 0x69, 0x11, 0xAD, 0xA4,
+ 0xAD, 0x98, 0x7B, 0x7A, 0x7C, 0xC9, 0x72, 0x36, 0x28, 0x9A, 0x84, 0xBC, 0x80, 0xC5, 0xE2, 0x6B,
+ 0x98, 0x61, 0xFB, 0x28, 0x35, 0x3C, 0x96, 0x49, 0x4B, 0xE9, 0x5F, 0xBB, 0xC6, 0x32, 0xE1, 0x18,
+ 0x32, 0x54, 0x64, 0x3B, 0x81, 0xCC, 0x96, 0x2E, 0x49, 0x8F, 0x23, 0xA3, 0x6C, 0x26, 0x35, 0x86,
+ 0x4C, 0xCC, 0x38, 0x77, 0xA2, 0x6A, 0x56, 0x53, 0x5A, 0x6A, 0x14, 0x32, 0x2B, 0x5A, 0x46, 0x6A,
+ 0xD4, 0xD1, 0x14, 0x57, 0x60, 0x57, 0xD1, 0x98, 0x6B, 0x27, 0x4E, 0xE5, 0xF3, 0xFB, 0xF7, 0xE7,
+ 0xF3, 0xA7, 0xEC, 0x44, 0xC2, 0xEE, 0xE2, 0x84, 0x7D, 0x6F, 0xBF, 0x1A, 0x65, 0x23, 0xC2, 0xEC,
+ 0x80, 0xA2, 0xB5, 0xA2, 0xEA, 0x7E, 0x55, 0xD3, 0x54, 0x4E, 0xD0, 0xFA, 0x78, 0xCA, 0x37, 0x0B,
+ 0xDC, 0x7B, 0x58, 0xA4, 0x13, 0xEA, 0xE6, 0x04, 0x64, 0x53, 0x8A, 0x36, 0x63, 0x97, 0x86, 0xD3,
+ 0xC3, 0xC6, 0x70, 0x6A, 0x70, 0xDC, 0xDB, 0x09, 0x1B, 0x8B, 0x6D, 0x0F, 0x27, 0x96, 0xAC, 0x63,
+ 0xF6, 0x60, 0x0A, 0xDB, 0x81, 0xC4, 0xE2, 0x0B, 0x58, 0x5F, 0x66, 0x83, 0xBE, 0x55, 0x7A, 0x50,
+ 0x58, 0x04, 0xBB, 0xF4, 0x8A, 0xB5, 0xBE, 0x3B, 0x9C, 0xB6, 0xCC, 0x92, 0x6F, 0xCB, 0xF3, 0x23,
+ 0x96, 0xF9, 0xBD, 0x65, 0x5A, 0x8E, 0x63, 0x99, 0xEC, 0x75, 0xDC, 0x7A, 0xA5, 0x29, 0xC8, 0x09,
+ 0x8D, 0x48, 0xCC, 0x32, 0xA7, 0xC9, 0xB4, 0x2C, 0x93, 0xCF, 0x35, 0xB3, 0x73, 0x6E, 0xCF, 0x31,
+ 0x9D, 0xD7, 0xAF, 0x39, 0xA9, 0x43, 0x70, 0x9B, 0xF5, 0x51, 0xE8, 0x3F, 0x4E, 0x2F, 0xF6, 0x25,
+ 0x9F, 0xDB, 0x12, 0xB9, 0xB5, 0xE0, 0xA9, 0x4D, 0x7B, 0x2A, 0x09, 0x8E, 0x26, 0x7C, 0xB2, 0x26,
+ 0xCE, 0x95, 0xF8, 0x73, 0x87, 0x3B, 0x64, 0x00, 0xC5, 0x64, 0xC5, 0x3F, 0x57, 0x4B, 0x91, 0x45,
+ 0x30, 0x71, 0x83, 0x3B, 0x72, 0xB8, 0x43, 0xE1, 0xD9, 0x8A, 0x3A, 0x5B, 0x48, 0x39, 0xAE, 0x73,
+ 0xD2, 0x0D, 0x3E, 0x12, 0x50, 0x39, 0xCC, 0xD0, 0xA9, 0xEA, 0x86, 0xA1, 0x07, 0x67, 0xF7, 0x15,
+ 0xE2, 0x8D, 0x50, 0x0C, 0xC4, 0x4F, 0x10, 0x81, 0xB2, 0xFE, 0x81, 0x34, 0x91, 0x22, 0x77, 0x0C,
+ 0xFD, 0x3B, 0xE9, 0xF1, 0xC0, 0x4F, 0x99, 0x2D, 0xA4, 0xCC, 0xA0, 0x89, 0xEB, 0xCA, 0x80, 0x47,
+ 0xBC, 0xF1, 0x06, 0xBA, 0xDF, 0xC1, 0xC6, 0x0B, 0xFC, 0xD6, 0x41, 0x7D, 0xED, 0x27, 0xD2, 0x42,
+ 0x1E, 0x7F, 0xB3, 0x33, 0xFA, 0xB6, 0x2B, 0xBB, 0x3B, 0x21, 0xEE, 0xAC, 0xEE, 0x55, 0x2C, 0xFC,
+ 0xC4, 0xB7, 0x2F, 0x36, 0xCA, 0xEF, 0xEF, 0xFB, 0x26, 0x23, 0xA1, 0x1E, 0xF6, 0xDC, 0xA4, 0x11,
+ 0xCA, 0x89, 0x28, 0xA2, 0x72, 0xC7, 0xAB, 0x7E, 0x89, 0x22, 0x8F, 0x85, 0x74, 0x96, 0xDE, 0xB3,
+ 0x2D, 0xAC, 0xCE, 0x6E, 0x78, 0x91, 0x22, 0xB4, 0x0D, 0xA1, 0xBB, 0xAE, 0xFD, 0x17, 0xF9, 0x03,
+ 0x7F, 0x3C, 0xB4, 0x8A, 0x68, 0x7D, 0x4B, 0xE0, 0x43, 0x01, 0x96, 0x80, 0x33, 0x01, 0x0E, 0x01,
+ 0x9F, 0x0C, 0x70, 0x18, 0xB7, 0xB5, 0x11, 0x60, 0x19, 0xFA, 0xAD, 0xC0, 0xFE, 0x1C, 0xE6, 0xD1,
+ 0x65, 0x71, 0xB3, 0x84, 0x22, 0xD0, 0x0C, 0xA0, 0xC7, 0xC7, 0x1C, 0xB8, 0x14, 0x60, 0x09, 0x78,
+ 0x5D, 0x80, 0x43, 0xC0, 0xE7, 0x02, 0x1C, 0xC6, 0xA3, 0x61, 0x05, 0x58, 0x16, 0x4F, 0x8A, 0x8F,
+ 0x11, 0x73, 0x9C, 0xEE, 0x1E, 0x9F, 0xA8, 0x56, 0xAA, 0x55, 0xF7, 0xF7, 0xB7, 0x63, 0x6F, 0xB1,
+ 0x7E, 0xE2, 0x78, 0xD5, 0xB5, 0x56, 0x8D, 0x26, 0x67, 0x7E, 0x6E, 0x66, 0xF4, 0x9A, 0x51, 0xB9,
+ 0xAB, 0x58, 0x3A, 0x59, 0x2E, 0xD4, 0x7F, 0x7F, 0xC7, 0x66, 0xB4, 0xDC, 0x54, 0x75, 0x1B, 0x56,
+ 0xA9, 0xE8, 0x16, 0xEB, 0x85, 0x46, 0xF1, 0x88, 0x75, 0xF8, 0x8C, 0xE5, 0x4C, 0x54, 0xB7, 0xC3,
+ 0x6C, 0x94, 0x36, 0x79, 0x63, 0x37, 0x30, 0x7A, 0x09, 0x61, 0x5C, 0x31, 0x23, 0x84, 0x69, 0x40,
+ 0x1E, 0x81, 0xEE, 0x30, 0x9D, 0x41, 0xEB, 0x78, 0xB1, 0xB6, 0x07, 0x8F, 0xDF, 0xA8, 0xB7, 0xC6,
+ 0x7F, 0x2D, 0x3F, 0x00, 0x43, 0x96, 0xB1, 0x3B, 0xC0, 0x07, 0x00, 0x00
+}; //icomoon.eot
+
+//Content of icomoon.svg with gzip compression
+static const uint8_t icomoon_svg[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x21, 0x7F, 0x4E, 0x61, 0x04, 0x00, 0x69, 0x63, 0x6F, 0x6D, 0x6F, 0x6F,
+ 0x6E, 0x2E, 0x73, 0x76, 0x67, 0x00, 0xAD, 0x57, 0x59, 0x8F, 0xD3, 0x30, 0x10, 0x7E, 0xE7, 0x57,
+ 0x98, 0x20, 0xF1, 0xC4, 0x24, 0xB6, 0x73, 0x43, 0xBB, 0x48, 0x1C, 0x42, 0x48, 0x2C, 0x20, 0x71,
+ 0x89, 0xC7, 0x90, 0xA4, 0x9B, 0x48, 0x69, 0x52, 0x35, 0x21, 0x85, 0xFD, 0xF5, 0x8C, 0xED, 0x71,
+ 0xB6, 0x09, 0x94, 0x2E, 0xC7, 0x43, 0x34, 0xB1, 0xFD, 0xCD, 0xE7, 0xB9, 0xD3, 0xAE, 0x1E, 0x7F,
+ 0xDB, 0x36, 0x6C, 0x2C, 0xF7, 0x7D, 0xDD, 0xB5, 0x6B, 0x47, 0xB8, 0xDC, 0x61, 0xFD, 0x90, 0xB5,
+ 0x45, 0xD6, 0x74, 0x6D, 0xB9, 0x76, 0xDA, 0xCE, 0x79, 0x7C, 0x71, 0x67, 0x75, 0xF7, 0xD9, 0x9B,
+ 0xA7, 0xEF, 0x3F, 0xBF, 0x7D, 0xCE, 0xFA, 0xF1, 0x8A, 0xBD, 0xFD, 0xF0, 0xE4, 0xD5, 0xCB, 0xA7,
+ 0xCC, 0x01, 0xCF, 0xFB, 0xE4, 0x3F, 0xF5, 0xBC, 0x67, 0xEF, 0x9F, 0xB1, 0x77, 0x1F, 0x5F, 0x30,
+ 0xE1, 0x0A, 0xCF, 0x7B, 0xFE, 0xDA, 0x61, 0x4E, 0x35, 0x0C, 0xBB, 0x87, 0x9E, 0x77, 0x38, 0x1C,
+ 0xDC, 0x83, 0xEF, 0x76, 0xFB, 0x2B, 0xEF, 0xC5, 0x3E, 0xDB, 0x55, 0x75, 0xDE, 0x7B, 0x08, 0xF4,
+ 0x10, 0xA8, 0x94, 0x3C, 0x24, 0x13, 0xC2, 0x2D, 0x86, 0xC2, 0x61, 0x78, 0x87, 0xA2, 0x46, 0x63,
+ 0xDA, 0x7E, 0xFD, 0x0B, 0x7D, 0xC9, 0x39, 0x57, 0x78, 0x07, 0x81, 0xDB, 0x72, 0xC8, 0x8A, 0x6C,
+ 0xC8, 0x2E, 0x5E, 0x94, 0x6D, 0xB9, 0xCF, 0x86, 0xB2, 0x60, 0x5F, 0xBE, 0xB3, 0x97, 0x79, 0x77,
+ 0xD9, 0x75, 0xED, 0xCA, 0xB3, 0xC7, 0x88, 0x2C, 0xCA, 0x4D, 0x8F, 0x62, 0xD3, 0xB5, 0x03, 0xAB,
+ 0x8B, 0xB5, 0x53, 0xE7, 0xDD, 0x16, 0x31, 0x0E, 0xAB, 0xBA, 0x7D, 0x7D, 0x0D, 0x59, 0x31, 0xC2,
+ 0x37, 0x74, 0x9A, 0xCB, 0xC0, 0x21, 0x18, 0x6C, 0xB2, 0xBC, 0x64, 0x5F, 0xDB, 0x7A, 0xE8, 0x61,
+ 0x57, 0xEE, 0xA1, 0xDC, 0xD2, 0x39, 0xCB, 0xFA, 0xBC, 0x6C, 0x87, 0xB5, 0x93, 0x46, 0x18, 0xA2,
+ 0xA2, 0xA4, 0x15, 0x44, 0x78, 0xE4, 0x29, 0xA3, 0xEA, 0xBE, 0xAF, 0xDB, 0x2B, 0xB8, 0x6A, 0xBE,
+ 0xEF, 0xAA, 0x9F, 0xF8, 0x09, 0xA4, 0x0F, 0x91, 0x1D, 0xED, 0x28, 0x30, 0xB8, 0xF7, 0xEF, 0x7D,
+ 0x93, 0xFC, 0xD1, 0xC2, 0x9A, 0x50, 0x48, 0xE4, 0x5F, 0x3B, 0xA7, 0x34, 0x36, 0x9C, 0x17, 0xA8,
+ 0xA3, 0x0F, 0xA0, 0xCD, 0xB6, 0xB8, 0x9B, 0x37, 0x5D, 0x5F, 0x3E, 0x60, 0xFB, 0x72, 0xDB, 0x8D,
+ 0x28, 0x87, 0x7A, 0x5B, 0xF6, 0x0B, 0xD6, 0x84, 0x87, 0x9A, 0xF5, 0x32, 0x0E, 0x84, 0x1B, 0x8B,
+ 0x80, 0x89, 0x34, 0x74, 0x03, 0x99, 0xE4, 0x1C, 0x44, 0xE0, 0xCA, 0x24, 0x82, 0x50, 0x6D, 0x83,
+ 0x4C, 0xDC, 0x30, 0x16, 0x20, 0x22, 0xF0, 0x13, 0x37, 0x09, 0xE3, 0x06, 0xE2, 0x58, 0x1D, 0x90,
+ 0xC8, 0x41, 0x70, 0x85, 0xB6, 0x42, 0x06, 0x73, 0x38, 0xBE, 0xF5, 0xC4, 0xC1, 0x34, 0x21, 0xED,
+ 0x33, 0x11, 0x35, 0x78, 0x96, 0x30, 0x7C, 0x80, 0x9E, 0x7F, 0xE2, 0x32, 0xE6, 0xB0, 0xB9, 0x55,
+ 0xCC, 0x12, 0x46, 0x6C, 0x22, 0x63, 0x46, 0xA9, 0x0F, 0x35, 0x9E, 0xE8, 0xA6, 0xED, 0xC6, 0xDA,
+ 0x44, 0xF2, 0x1F, 0x98, 0xE6, 0x16, 0x1D, 0xD3, 0x58, 0x8E, 0x1B, 0x30, 0xBE, 0xF5, 0x14, 0x69,
+ 0xC3, 0x36, 0x79, 0xDC, 0x58, 0x63, 0xE8, 0xF9, 0x7B, 0xA2, 0x79, 0xDE, 0x8C, 0xFE, 0xE4, 0x9D,
+ 0x8D, 0xB6, 0x7A, 0x23, 0xB7, 0x4E, 0x16, 0x80, 0xB5, 0x88, 0xE4, 0x79, 0xAA, 0xEB, 0xD3, 0xA5,
+ 0x1B, 0x66, 0x8B, 0xD2, 0xAD, 0xDB, 0x4D, 0x07, 0x79, 0xBD, 0xCF, 0x9B, 0x72, 0x59, 0xB1, 0x71,
+ 0x62, 0x2A, 0x36, 0x4C, 0x42, 0x57, 0x04, 0x3E, 0xF2, 0xEB, 0x7B, 0xC6, 0x54, 0x60, 0xE1, 0xA6,
+ 0x39, 0xB7, 0x39, 0x42, 0xCB, 0x12, 0xFD, 0x62, 0x04, 0xAD, 0x2A, 0x08, 0x03, 0x65, 0xCC, 0x28,
+ 0x53, 0xA9, 0xD4, 0xCE, 0xE3, 0x45, 0x22, 0x95, 0xC2, 0x54, 0x02, 0x1C, 0x77, 0x8C, 0x02, 0xCC,
+ 0x14, 0x46, 0xB0, 0x16, 0x58, 0xA4, 0x05, 0xCC, 0x89, 0x2B, 0x32, 0xC0, 0x12, 0x2B, 0x8B, 0xFE,
+ 0xEF, 0x05, 0x32, 0x8C, 0x72, 0xCB, 0x45, 0x47, 0xCC, 0x7A, 0x47, 0xE2, 0xFA, 0x12, 0xE7, 0x09,
+ 0x8B, 0xE2, 0xE8, 0x8F, 0x42, 0x27, 0x78, 0xAA, 0x8A, 0xE1, 0xFF, 0x59, 0x4A, 0x84, 0xE7, 0xAD,
+ 0x4D, 0xA8, 0x8D, 0x02, 0x53, 0x4A, 0x48, 0x2C, 0x03, 0x69, 0x98, 0x52, 0xED, 0x03, 0xD0, 0xC9,
+ 0x52, 0xF6, 0xF6, 0x85, 0x2D, 0x80, 0x6C, 0xB1, 0x6F, 0xD7, 0xA7, 0xA4, 0xBD, 0x68, 0x5A, 0x07,
+ 0x67, 0xAB, 0x3A, 0x8F, 0x17, 0x55, 0xBD, 0x69, 0xBA, 0xDD, 0xEE, 0x3B, 0x74, 0x0F, 0x58, 0x9F,
+ 0x8D, 0x27, 0x0B, 0x5B, 0x8A, 0x54, 0x05, 0x8F, 0xC5, 0xBE, 0xAA, 0xEF, 0x8A, 0xEE, 0x19, 0x69,
+ 0xBB, 0x02, 0xBB, 0x01, 0xB4, 0x73, 0x7D, 0x19, 0xFB, 0xE2, 0x58, 0xC3, 0x88, 0x11, 0x33, 0xAC,
+ 0x53, 0xAA, 0xAD, 0x4D, 0xCD, 0x18, 0x08, 0x4C, 0xCC, 0x62, 0xDD, 0x39, 0x66, 0xE4, 0xAB, 0x4E,
+ 0xE6, 0xDA, 0x33, 0x92, 0x39, 0x24, 0x98, 0x2F, 0xDF, 0xA4, 0xC4, 0x40, 0xC1, 0x40, 0x69, 0x85,
+ 0x37, 0xFB, 0x66, 0x7C, 0x70, 0x03, 0xB3, 0xBD, 0x4E, 0x95, 0x3C, 0x17, 0x15, 0xF8, 0x32, 0x55,
+ 0x6A, 0xB9, 0xE5, 0xE4, 0x74, 0x42, 0x63, 0xCB, 0xAE, 0xA8, 0x2F, 0x88, 0xBB, 0x02, 0x72, 0x03,
+ 0xC8, 0x3B, 0xEB, 0xD6, 0x74, 0x37, 0xF3, 0x8F, 0x87, 0x1F, 0xA9, 0xCF, 0x45, 0x15, 0xC4, 0xA1,
+ 0x2E, 0x44, 0x7B, 0x33, 0xED, 0xDB, 0xD9, 0x34, 0x33, 0x74, 0x72, 0x8B, 0x9A, 0x83, 0xFB, 0xEA,
+ 0x4B, 0x38, 0x52, 0x9B, 0xE2, 0x7D, 0x3A, 0x86, 0xA0, 0x07, 0xE1, 0xB9, 0x06, 0xD1, 0x62, 0xEA,
+ 0x0F, 0xA3, 0xB3, 0xEC, 0x91, 0x89, 0x98, 0xD0, 0x1A, 0xF4, 0xFB, 0x3E, 0x21, 0x5A, 0x3A, 0x35,
+ 0x1A, 0xA7, 0x5A, 0x85, 0xE6, 0xE3, 0x08, 0xA1, 0x71, 0xFE, 0x96, 0xC9, 0x8A, 0xA3, 0xE4, 0x96,
+ 0x89, 0x42, 0xE4, 0xED, 0x92, 0x40, 0x06, 0xDC, 0x24, 0x21, 0x96, 0xB6, 0x04, 0x53, 0x5F, 0xC7,
+ 0x74, 0xFA, 0xEA, 0x72, 0x55, 0x8C, 0xB9, 0x14, 0x7A, 0x57, 0x89, 0x9B, 0x6F, 0x57, 0xE4, 0xAB,
+ 0x4C, 0xDA, 0x95, 0x51, 0xFC, 0x4D, 0xF3, 0x15, 0xCB, 0xE6, 0xCB, 0xB3, 0x7D, 0x39, 0x40, 0xD1,
+ 0x1D, 0x96, 0xBF, 0xF3, 0x30, 0x50, 0xB3, 0x2F, 0x0A, 0x9A, 0xAD, 0x03, 0x6B, 0x13, 0x03, 0x01,
+ 0x08, 0x73, 0x29, 0xF5, 0x92, 0x0C, 0x4D, 0xDF, 0xE0, 0x9C, 0x55, 0x4F, 0x0E, 0x91, 0x31, 0x90,
+ 0x3E, 0xB1, 0x73, 0x18, 0xAD, 0x7A, 0xE2, 0x60, 0x01, 0xED, 0x53, 0x63, 0x6A, 0x16, 0x76, 0xC3,
+ 0xC2, 0xA2, 0xA3, 0x9B, 0xD8, 0x44, 0x86, 0x08, 0x4A, 0xBD, 0xC4, 0xEC, 0x9B, 0x41, 0xE4, 0xFF,
+ 0x42, 0x54, 0xAA, 0xE9, 0x11, 0xC2, 0x69, 0x0D, 0x33, 0x2C, 0x18, 0xF1, 0xBB, 0xA8, 0x25, 0xBF,
+ 0x8C, 0xDA, 0xD7, 0xDD, 0x99, 0x98, 0x29, 0x0F, 0xD4, 0x50, 0xC6, 0xFC, 0xD9, 0x9B, 0x7E, 0x16,
+ 0x15, 0x28, 0xEB, 0x40, 0x99, 0x47, 0x3B, 0x8C, 0xD0, 0x73, 0x57, 0xA8, 0xD3, 0x58, 0xC0, 0x28,
+ 0x66, 0xB3, 0x18, 0x34, 0x36, 0x60, 0x47, 0xF1, 0x42, 0x9E, 0x39, 0x88, 0x56, 0x3D, 0x25, 0x2E,
+ 0x60, 0xB3, 0x64, 0x34, 0x36, 0x73, 0x47, 0x89, 0xB3, 0x83, 0x72, 0x62, 0x42, 0xC0, 0x51, 0x89,
+ 0x79, 0xEA, 0x5F, 0xC0, 0xC5, 0xCA, 0xD3, 0x7F, 0x1D, 0x56, 0xEA, 0x2F, 0xC7, 0xC5, 0x0F, 0x30,
+ 0x32, 0xAD, 0x05, 0x24, 0x0D, 0x00, 0x00
+}; //icomoon.svg
+
+//Content of icomoon.ttf with gzip compression
+static const uint8_t icomoon_ttf[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x21, 0x7F, 0x4E, 0x61, 0x04, 0x00, 0x69, 0x63, 0x6F, 0x6D, 0x6F, 0x6F,
+ 0x6E, 0x2E, 0x74, 0x74, 0x66, 0x00, 0x95, 0x54, 0x4B, 0xA8, 0xD3, 0x40, 0x14, 0xBD, 0x33, 0x69,
+ 0x93, 0xB4, 0xD1, 0xC6, 0x27, 0x4D, 0x03, 0x7E, 0xF3, 0x1A, 0x9F, 0x22, 0xE2, 0xB3, 0xF6, 0x8B,
+ 0xF8, 0xA9, 0x6E, 0xC4, 0xF8, 0x2D, 0x2A, 0x2E, 0x54, 0xD0, 0x5A, 0x6B, 0x15, 0xDA, 0xB4, 0xF4,
+ 0x55, 0xFC, 0x20, 0xE2, 0x4A, 0xC4, 0x85, 0x0A, 0x6E, 0x74, 0xA5, 0x88, 0x42, 0x41, 0x70, 0x5B,
+ 0x04, 0x17, 0x82, 0x88, 0x20, 0xEE, 0x15, 0xD4, 0x85, 0x28, 0x6F, 0x23, 0x48, 0x37, 0xA2, 0xA0,
+ 0xCF, 0x7A, 0x26, 0x49, 0xFD, 0x23, 0x3A, 0xED, 0xDC, 0x7B, 0xCE, 0xFD, 0xCD, 0xDC, 0x4C, 0x26,
+ 0xC4, 0x88, 0x48, 0xA3, 0x33, 0x24, 0x51, 0x6A, 0xDB, 0x8E, 0x65, 0x69, 0x7D, 0xE6, 0xB4, 0x87,
+ 0xB0, 0xDC, 0xC3, 0xDC, 0x5F, 0x69, 0x94, 0x5B, 0xAF, 0x4E, 0xBE, 0xBA, 0x41, 0xC4, 0xE6, 0x80,
+ 0xD7, 0x6B, 0xE5, 0x89, 0x16, 0xF4, 0x0C, 0xF0, 0x73, 0xD0, 0x6A, 0xAD, 0x7E, 0xE2, 0xD0, 0x5D,
+ 0xB5, 0x7B, 0x01, 0xFC, 0x12, 0x91, 0x94, 0x3A, 0x5C, 0x2D, 0x1F, 0x9C, 0xBB, 0x78, 0xF7, 0x52,
+ 0xA2, 0xD0, 0x7D, 0xF8, 0xF3, 0x87, 0x61, 0x50, 0x52, 0xD2, 0x63, 0xF0, 0x8F, 0xE0, 0x0B, 0x0E,
+ 0x37, 0x3A, 0xC7, 0xE3, 0x97, 0xA9, 0x48, 0x14, 0x9E, 0x23, 0x78, 0xBD, 0x59, 0x29, 0xF3, 0xD3,
+ 0x52, 0x0B, 0x7C, 0x2D, 0x78, 0xBC, 0x51, 0x3E, 0xDE, 0xA2, 0x18, 0x6D, 0x05, 0xDF, 0x09, 0x6E,
+ 0xB9, 0xE5, 0x46, 0xF5, 0xEA, 0xC6, 0xC8, 0x27, 0xF0, 0x0E, 0xD6, 0x38, 0xDB, 0x6A, 0x4E, 0x74,
+ 0xB0, 0x4F, 0x0C, 0xF9, 0xB3, 0xF0, 0x93, 0xC4, 0x1F, 0x60, 0xE5, 0x30, 0x11, 0xBF, 0xCA, 0x9F,
+ 0xC0, 0x72, 0xD1, 0xD7, 0xEC, 0x2D, 0x65, 0x58, 0x84, 0x7E, 0x1E, 0x6C, 0x06, 0xFD, 0x32, 0xB0,
+ 0x66, 0xFF, 0x99, 0x74, 0x7F, 0x70, 0x9F, 0xD6, 0x4A, 0x98, 0x22, 0xE6, 0x07, 0xAF, 0xE5, 0x49,
+ 0x29, 0x98, 0x73, 0xE0, 0xF3, 0x34, 0x24, 0xF3, 0x74, 0x88, 0x4A, 0xE2, 0x59, 0xE0, 0x27, 0x79,
+ 0x99, 0x56, 0x7F, 0x7A, 0x7F, 0x77, 0xFF, 0x51, 0xFF, 0xD9, 0x60, 0x6A, 0x30, 0x10, 0xA9, 0xDF,
+ 0x2C, 0x4F, 0x3D, 0x0B, 0x1B, 0xBC, 0xD6, 0x3F, 0xE8, 0xB7, 0xF5, 0xA2, 0x9E, 0xF2, 0x6A, 0xFC,
+ 0x32, 0x60, 0x61, 0x88, 0xD2, 0x7F, 0xF0, 0x70, 0xA2, 0xC2, 0x0A, 0x41, 0xFF, 0xC3, 0x56, 0xA4,
+ 0x22, 0x9F, 0xE4, 0x93, 0xB4, 0x8A, 0x68, 0x2C, 0x2E, 0xEB, 0x2C, 0xC6, 0xEC, 0xE4, 0xC2, 0x65,
+ 0x4C, 0x09, 0xF4, 0x38, 0xCB, 0x65, 0xF3, 0x45, 0xB6, 0x28, 0xD0, 0x6B, 0x58, 0x26, 0x9D, 0x98,
+ 0xCF, 0x0A, 0x81, 0x9E, 0xC7, 0x0C, 0x91, 0x63, 0x42, 0xF3, 0xC9, 0x88, 0xB2, 0x55, 0x8D, 0x6B,
+ 0x5A, 0x5C, 0xED, 0x76, 0x15, 0x43, 0x8B, 0x1A, 0xA0, 0x18, 0xDD, 0xAE, 0x90, 0x5B, 0x15, 0x23,
+ 0xAA, 0x19, 0x0A, 0x88, 0x88, 0x00, 0x8D, 0x44, 0x40, 0x94, 0xC8, 0x83, 0x7F, 0x0B, 0xFB, 0xB9,
+ 0xB4, 0xF7, 0x84, 0x85, 0x70, 0x25, 0x97, 0xE6, 0xD3, 0x32, 0xDA, 0x84, 0xBD, 0xE7, 0xB2, 0x0B,
+ 0x97, 0xB0, 0x11, 0x21, 0x92, 0xF2, 0x5C, 0x16, 0x4F, 0xAC, 0x64, 0x86, 0xED, 0xA3, 0xCC, 0x68,
+ 0x3A, 0x9F, 0x93, 0x72, 0x3F, 0xBA, 0xD2, 0xF9, 0x70, 0x1C, 0x1D, 0x2A, 0xB2, 0x9D, 0x44, 0x67,
+ 0x8B, 0x16, 0xE6, 0xB2, 0xE8, 0xA8, 0x90, 0xCF, 0xA4, 0xD1, 0x89, 0x99, 0xE0, 0x4E, 0x54, 0x2D,
+ 0x68, 0x4A, 0x4F, 0x8D, 0x42, 0x17, 0x84, 0x64, 0xA4, 0x46, 0x1D, 0x4D, 0x71, 0x05, 0x76, 0x15,
+ 0x8D, 0xB9, 0x76, 0xF2, 0x58, 0xA9, 0xB4, 0x67, 0x4F, 0xA9, 0x74, 0xCC, 0x4E, 0x26, 0xED, 0x21,
+ 0x4E, 0xDA, 0xB7, 0xF6, 0xA8, 0x51, 0x36, 0x26, 0xC2, 0xF6, 0x2A, 0x5A, 0x2F, 0xAA, 0xEE, 0x51,
+ 0x35, 0x4D, 0xE5, 0x04, 0xAB, 0x8F, 0x27, 0xFD, 0xB0, 0x20, 0xFD, 0x3B, 0x16, 0xED, 0x84, 0x86,
+ 0x3D, 0x01, 0xD9, 0x94, 0xA1, 0x0D, 0x38, 0xA5, 0xD1, 0xDC, 0xA8, 0x31, 0x9A, 0x19, 0xC9, 0x7A,
+ 0x27, 0x61, 0xE3, 0x61, 0xDB, 0xA3, 0xC9, 0x85, 0xAB, 0x99, 0x3D, 0x92, 0xC1, 0x71, 0xA0, 0xB1,
+ 0xC4, 0x5C, 0xF6, 0x5B, 0x67, 0x23, 0x7E, 0x54, 0x6E, 0x44, 0x44, 0x04, 0xA7, 0xF4, 0x9C, 0xF5,
+ 0xBE, 0x38, 0x9C, 0x36, 0x4E, 0x93, 0xAF, 0xCB, 0xB3, 0x23, 0x96, 0xF9, 0xA5, 0x67, 0x5A, 0x8E,
+ 0x63, 0x99, 0xEC, 0x45, 0xC2, 0x7A, 0xAE, 0x29, 0xE8, 0x09, 0x42, 0x34, 0x66, 0x99, 0x53, 0x64,
+ 0x5A, 0x96, 0xC9, 0x67, 0x9A, 0x85, 0x19, 0xD7, 0x67, 0x98, 0xCE, 0x8B, 0x17, 0x9C, 0xD4, 0x39,
+ 0x48, 0x9B, 0xF6, 0x4E, 0xD8, 0xDF, 0x4D, 0x2D, 0xF0, 0x35, 0x9F, 0xD9, 0x13, 0xBD, 0xF5, 0x90,
+ 0xA9, 0x4D, 0x79, 0x26, 0x09, 0x89, 0x26, 0x72, 0x0A, 0x26, 0xDE, 0x2B, 0xF1, 0xE7, 0x0E, 0x77,
+ 0xC8, 0x00, 0x8A, 0xCB, 0x8A, 0xFF, 0x5E, 0x2D, 0x42, 0x17, 0xC1, 0xC6, 0x0D, 0xEE, 0xC8, 0xE1,
+ 0x01, 0x85, 0xA7, 0x2B, 0xEA, 0x74, 0xA1, 0xE5, 0x84, 0xCE, 0x49, 0x37, 0xF8, 0x58, 0x40, 0xE5,
+ 0x30, 0x83, 0x53, 0xD5, 0x0D, 0x43, 0x0F, 0xDE, 0xDD, 0xE7, 0xA8, 0x37, 0x46, 0x71, 0x10, 0xBF,
+ 0x41, 0x14, 0x2A, 0xF8, 0x2F, 0xA4, 0x89, 0x16, 0xB9, 0x63, 0xE8, 0x5F, 0x48, 0x4F, 0x04, 0x79,
+ 0xCA, 0x74, 0xA1, 0x65, 0x06, 0x4B, 0x42, 0x57, 0x62, 0x1E, 0xF1, 0xD6, 0x8B, 0x0D, 0xEF, 0xC1,
+ 0xBA, 0x33, 0xFC, 0xDA, 0x3E, 0x7D, 0xD5, 0x7B, 0xD2, 0x42, 0x1E, 0x7F, 0xB9, 0x2D, 0xFA, 0x6A,
+ 0xA8, 0x87, 0x27, 0x21, 0xBE, 0x59, 0xB8, 0x2D, 0xDE, 0x10, 0x79, 0xE2, 0xEE, 0x8B, 0x83, 0xF2,
+ 0xFD, 0xBF, 0xDD, 0xC9, 0x48, 0xE8, 0x3B, 0xF6, 0xD2, 0xA4, 0x31, 0x2A, 0x8A, 0x2A, 0x62, 0x72,
+ 0xC7, 0x9B, 0xFE, 0x88, 0xA2, 0x8F, 0x79, 0x74, 0x92, 0xDE, 0xB0, 0x8D, 0xAC, 0xCD, 0xAE, 0x78,
+ 0x95, 0x22, 0xB4, 0x19, 0xA5, 0xFD, 0xD4, 0xDF, 0x47, 0x8C, 0xEE, 0xF8, 0xEB, 0x41, 0x2A, 0x42,
+ 0xFA, 0x91, 0xC0, 0xFB, 0x03, 0x2C, 0x01, 0xE7, 0x03, 0x1C, 0x02, 0x3E, 0x1A, 0xE0, 0x30, 0xBE,
+ 0xD6, 0x46, 0x80, 0x65, 0xD8, 0x37, 0x01, 0xFB, 0x7B, 0x98, 0x45, 0xE7, 0xC5, 0x97, 0x25, 0x14,
+ 0x81, 0x25, 0x06, 0x8F, 0x8F, 0x39, 0x70, 0x2D, 0xC0, 0x12, 0xF0, 0xEA, 0x00, 0x87, 0x80, 0x4F,
+ 0xF9, 0x18, 0x35, 0x13, 0x64, 0x05, 0x58, 0x86, 0x7D, 0xBB, 0x8F, 0x51, 0x33, 0x4B, 0x37, 0x8F,
+ 0x54, 0x9A, 0x8D, 0x66, 0xD3, 0xA5, 0x23, 0x54, 0xA1, 0x26, 0x35, 0xA8, 0x89, 0x9F, 0xBB, 0xAB,
+ 0xDA, 0x9E, 0x38, 0xD2, 0x74, 0xAD, 0xE5, 0xE3, 0x29, 0xDA, 0x45, 0x55, 0x6A, 0xD3, 0x04, 0x02,
+ 0xE0, 0x40, 0x9D, 0xE5, 0x34, 0x4E, 0xA9, 0x3F, 0x66, 0xFD, 0xD1, 0xB8, 0xBD, 0x5A, 0x3B, 0x5A,
+ 0x2F, 0xB7, 0x69, 0x3B, 0xCA, 0xD4, 0xD0, 0x66, 0x9D, 0xCA, 0xD4, 0xFE, 0x63, 0xE4, 0xFA, 0xA6,
+ 0xDB, 0xB1, 0x6A, 0x55, 0xB7, 0xDA, 0x2E, 0x77, 0xAA, 0x07, 0xAD, 0x03, 0x27, 0x2C, 0xA7, 0xD2,
+ 0xDC, 0x82, 0xB0, 0x71, 0x5A, 0xEF, 0xAD, 0xDD, 0xC1, 0xEA, 0x35, 0x94, 0x71, 0xC5, 0x8E, 0x50,
+ 0xA6, 0x03, 0x7D, 0x10, 0xB6, 0x03, 0x74, 0x02, 0xD2, 0xF1, 0x6A, 0x6D, 0xF1, 0x6B, 0xD1, 0xB8,
+ 0xF7, 0x8C, 0xFF, 0x3A, 0xBE, 0x02, 0x44, 0xC0, 0xB3, 0xAF, 0x1C, 0x07, 0x00, 0x00
+}; //icomoon.ttf
+
+//Content of icomoon.woof with gzip compression
+static const uint8_t icomoon_woof[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0x21, 0x7F, 0x4E, 0x61, 0x04, 0x00, 0x69, 0x63, 0x6F, 0x6D, 0x6F, 0x6F,
+ 0x6E, 0x2E, 0x77, 0x6F, 0x66, 0x66, 0x00, 0x95, 0x55, 0x4D, 0x68, 0x13, 0x41, 0x14, 0x7E, 0x33,
+ 0x9B, 0xEC, 0x6E, 0xB2, 0x9A, 0xB5, 0x92, 0xCD, 0x82, 0xBF, 0xDB, 0xAC, 0xAD, 0x88, 0x58, 0x6B,
+ 0xD3, 0x24, 0x88, 0x3F, 0x51, 0x90, 0xE2, 0xFA, 0x5B, 0x14, 0x3C, 0x58, 0x41, 0x63, 0x8D, 0x89,
+ 0xD0, 0x6C, 0x4A, 0x1A, 0xF1, 0x07, 0x11, 0x4F, 0x1E, 0x44, 0x54, 0xF0, 0xA2, 0x27, 0x45, 0x14,
+ 0x02, 0x82, 0xD7, 0xE0, 0x4D, 0x10, 0x11, 0xC4, 0xA3, 0xA0, 0x60, 0x3D, 0x88, 0xD2, 0x8B, 0x20,
+ 0xB9, 0x88, 0x07, 0xA9, 0xF1, 0x9B, 0xD9, 0x8D, 0xFF, 0x8A, 0x4E, 0xFA, 0xDE, 0xFB, 0xDE, 0x37,
+ 0xEF, 0xBD, 0x99, 0x37, 0xB3, 0xBB, 0x3D, 0xB1, 0x7B, 0x64, 0x84, 0x18, 0x61, 0x68, 0x15, 0x32,
+ 0xA4, 0x5D, 0x44, 0x7F, 0x18, 0xBB, 0xF7, 0xAE, 0x1E, 0x22, 0x62, 0x3A, 0xE0, 0x21, 0x21, 0xE6,
+ 0xFC, 0x39, 0x8F, 0xC6, 0xAB, 0xC5, 0x49, 0x70, 0x15, 0xF8, 0x13, 0x42, 0x5E, 0x9F, 0x7E, 0x7D,
+ 0xAB, 0x5C, 0x9C, 0x12, 0xDC, 0x33, 0xF8, 0x7A, 0x28, 0xF3, 0xCA, 0x13, 0xA7, 0x8E, 0x82, 0x9B,
+ 0x26, 0x52, 0x06, 0x85, 0xDC, 0xD7, 0x9B, 0x97, 0x2A, 0xA5, 0xE2, 0x11, 0xA2, 0xE8, 0x1C, 0xCC,
+ 0xE7, 0x84, 0x2C, 0x5E, 0xB1, 0x7F, 0x55, 0x05, 0x24, 0xB8, 0x2D, 0xF0, 0x97, 0x09, 0xD1, 0x06,
+ 0x95, 0x27, 0x95, 0x6A, 0xE3, 0x24, 0xB8, 0x4A, 0x97, 0x4B, 0x5E, 0xA5, 0xC2, 0x44, 0x6D, 0x5C,
+ 0xC4, 0x5D, 0x84, 0x9F, 0x14, 0xC2, 0xCF, 0x2A, 0x93, 0xD5, 0xE2, 0xC9, 0x49, 0x70, 0x37, 0xE1,
+ 0x3B, 0x52, 0x12, 0xB4, 0xCB, 0x2F, 0x56, 0x4B, 0xE0, 0x1E, 0x60, 0xED, 0xF3, 0x42, 0xAE, 0x6F,
+ 0x8B, 0x7D, 0x9A, 0xAC, 0x4D, 0x35, 0xD0, 0xE7, 0xD6, 0xAF, 0x71, 0x0A, 0xB4, 0xC2, 0x1F, 0xB2,
+ 0x2B, 0x14, 0x25, 0xE2, 0xD7, 0xF9, 0x53, 0xF8, 0x97, 0x03, 0xCB, 0xDE, 0x51, 0x86, 0xC5, 0x7E,
+ 0x3A, 0x08, 0x36, 0xEF, 0xE7, 0xA3, 0xD9, 0x44, 0xD4, 0x7E, 0xA1, 0x3C, 0xE8, 0x3C, 0xA0, 0x4D,
+ 0x0A, 0x44, 0xC4, 0x7C, 0x37, 0xEB, 0x48, 0xAD, 0x84, 0xB2, 0x08, 0x73, 0xD2, 0x42, 0x33, 0x69,
+ 0x23, 0x34, 0x2A, 0xCE, 0x08, 0x3F, 0x45, 0x66, 0x3A, 0xED, 0xB9, 0xED, 0xFD, 0xED, 0xC7, 0xED,
+ 0x17, 0x9D, 0xD9, 0x4E, 0x47, 0xA4, 0x7E, 0x65, 0x9E, 0x4B, 0x86, 0x75, 0xDE, 0x98, 0x1F, 0xCD,
+ 0xBB, 0x66, 0xC1, 0x1C, 0x94, 0x35, 0x7E, 0x1A, 0x60, 0x18, 0xA2, 0xCC, 0xEF, 0x66, 0x38, 0x51,
+ 0x7E, 0xAD, 0x70, 0xFF, 0x83, 0x2B, 0x50, 0x81, 0xCF, 0xF0, 0x19, 0x5A, 0x4F, 0xD4, 0x97, 0x54,
+ 0x4D, 0x96, 0x60, 0x6E, 0xBA, 0x7F, 0x35, 0xD3, 0x42, 0x3B, 0xC0, 0xB2, 0xC3, 0xB9, 0x02, 0x5B,
+ 0x1E, 0xDA, 0x8D, 0x2C, 0x33, 0x94, 0x5A, 0xCA, 0xF2, 0xA1, 0x5D, 0xC2, 0x2C, 0x91, 0x63, 0xC3,
+ 0xF2, 0x99, 0x98, 0xB6, 0x4B, 0x4F, 0x1A, 0x46, 0x52, 0x6F, 0x36, 0x35, 0xCB, 0x88, 0x5B, 0x70,
+ 0x31, 0x9A, 0x4D, 0xA1, 0x77, 0x69, 0x56, 0xDC, 0xB0, 0x34, 0x38, 0x22, 0x02, 0x6E, 0x2C, 0x06,
+ 0x47, 0x8B, 0x3D, 0xFC, 0xB7, 0xB0, 0x1F, 0x4B, 0xCB, 0x13, 0x16, 0xCA, 0x57, 0x7C, 0x5A, 0x4A,
+ 0xAB, 0x69, 0x3B, 0xF6, 0x9E, 0x1D, 0xEE, 0x5F, 0xC9, 0x7A, 0x84, 0x4A, 0xAB, 0x8B, 0x59, 0x32,
+ 0xB5, 0x8E, 0x59, 0x6E, 0x80, 0x32, 0xBD, 0x43, 0xB9, 0xAC, 0x92, 0xFD, 0x7E, 0x6A, 0x28, 0x17,
+ 0x4D, 0xA2, 0x43, 0x4D, 0x75, 0xD3, 0xE8, 0x6C, 0x79, 0x7F, 0x76, 0x18, 0x1D, 0xE5, 0x73, 0x99,
+ 0x21, 0x74, 0x62, 0xA7, 0xB8, 0x17, 0xD7, 0xF3, 0x86, 0xD6, 0xD2, 0xE3, 0xB0, 0x79, 0xA1, 0x19,
+ 0xE9, 0x71, 0xCF, 0xD0, 0x7C, 0x81, 0x7D, 0xCD, 0x60, 0xBE, 0x9B, 0x3E, 0x31, 0x3A, 0x3A, 0x36,
+ 0x36, 0x3A, 0x7A, 0xC2, 0x4D, 0xA7, 0xDD, 0x2E, 0x4E, 0xBB, 0x77, 0xC6, 0xF4, 0x38, 0xEB, 0x13,
+ 0x61, 0x07, 0x34, 0xA3, 0x15, 0xD7, 0xC7, 0x74, 0xC3, 0xD0, 0x39, 0x81, 0x0D, 0xF0, 0x4C, 0x10,
+ 0x16, 0xA6, 0x7F, 0xC3, 0xA2, 0x9D, 0x48, 0xB7, 0x27, 0x20, 0x97, 0x32, 0xB4, 0x15, 0xB7, 0xD4,
+ 0x9B, 0xED, 0xB5, 0x7A, 0x33, 0x3D, 0xC3, 0xF2, 0x26, 0x5C, 0x1C, 0xB6, 0xDB, 0x9B, 0xEE, 0xDF,
+ 0xC0, 0xDC, 0x9E, 0x0C, 0xAE, 0x03, 0x8D, 0xA5, 0x16, 0xB3, 0x5F, 0x3A, 0xEB, 0x09, 0xA2, 0xB2,
+ 0x3D, 0x22, 0x22, 0xBC, 0xA5, 0x97, 0xAC, 0xF5, 0xD9, 0xE3, 0xB4, 0x6D, 0x8E, 0x7A, 0x53, 0x5D,
+ 0x18, 0x73, 0xEC, 0xCF, 0x2D, 0xDB, 0xF1, 0x3C, 0xC7, 0x66, 0xD3, 0x29, 0xE7, 0xA5, 0xA1, 0xA1,
+ 0x27, 0x28, 0xD1, 0x98, 0x63, 0xCF, 0x92, 0xED, 0x38, 0x36, 0x9F, 0x6F, 0xE7, 0xE7, 0xDD, 0x9C,
+ 0x67, 0x7B, 0xD3, 0xD3, 0x9C, 0xF4, 0x45, 0x48, 0x9B, 0xF3, 0x5E, 0xF0, 0xEF, 0x67, 0x97, 0x05,
+ 0x96, 0xCF, 0x6F, 0x89, 0xDE, 0x5A, 0xC8, 0x34, 0x66, 0x25, 0xA5, 0x20, 0xD1, 0x46, 0x4E, 0xDE,
+ 0xC6, 0x73, 0x25, 0xFE, 0xB8, 0xC7, 0x3D, 0xB2, 0x80, 0x92, 0xAA, 0x16, 0x3C, 0x57, 0xCB, 0xD1,
+ 0x45, 0xB8, 0x71, 0x8B, 0x7B, 0x6A, 0xB4, 0x43, 0xD1, 0xB9, 0x9A, 0x3E, 0x57, 0x58, 0x35, 0x65,
+ 0x72, 0x32, 0x2D, 0xDE, 0x17, 0xBA, 0x6A, 0x94, 0x61, 0x52, 0x37, 0x2D, 0xCB, 0x0C, 0x9F, 0xDD,
+ 0x97, 0xA8, 0xD7, 0x27, 0xBE, 0x03, 0x2C, 0x68, 0x10, 0x85, 0xF2, 0xC1, 0x03, 0x69, 0xA3, 0x45,
+ 0xEE, 0x59, 0xE6, 0x67, 0x32, 0x53, 0x61, 0x9E, 0x36, 0x57, 0x58, 0x95, 0x81, 0x49, 0x99, 0x5A,
+ 0x42, 0x3A, 0x72, 0xBD, 0x44, 0xF7, 0x3D, 0xD8, 0x7C, 0x8E, 0xDF, 0x38, 0x68, 0xAE, 0xFF, 0x40,
+ 0x46, 0x44, 0xFA, 0xAF, 0x76, 0xC7, 0x5F, 0x77, 0x6D, 0xF7, 0x26, 0xE4, 0x77, 0x8D, 0x7F, 0x7D,
+ 0xDF, 0x48, 0xBC, 0xFB, 0xE2, 0xA2, 0x82, 0xF9, 0x5F, 0xDE, 0xC9, 0x58, 0xE4, 0x1B, 0x96, 0x69,
+ 0x4A, 0x1F, 0x15, 0x44, 0x15, 0x21, 0xDC, 0x93, 0x12, 0x8C, 0x38, 0xFA, 0x58, 0x42, 0xA7, 0xE9,
+ 0x2D, 0xDB, 0xC6, 0xEA, 0xEC, 0x9A, 0xAC, 0x14, 0xA3, 0x1D, 0x28, 0x1D, 0xA4, 0xFE, 0x3A, 0x12,
+ 0x74, 0x2F, 0x58, 0x0F, 0x5A, 0x13, 0x3A, 0x88, 0x04, 0x3E, 0x14, 0x62, 0x05, 0x38, 0x17, 0xE2,
+ 0x08, 0xF0, 0xF1, 0x10, 0x47, 0xF1, 0xC5, 0xB7, 0x42, 0xAC, 0x82, 0xDF, 0x0E, 0x1C, 0xEC, 0x61,
+ 0x01, 0x5D, 0x10, 0x5F, 0x96, 0x48, 0x0C, 0x4C, 0x02, 0x33, 0x01, 0xE6, 0xC0, 0xE5, 0x10, 0x2B,
+ 0xC0, 0x1B, 0x42, 0x1C, 0x01, 0x3E, 0x13, 0x60, 0xD4, 0x4C, 0x91, 0x13, 0x62, 0x15, 0xFC, 0x9E,
+ 0x00, 0xA3, 0xE6, 0x30, 0xDD, 0x3E, 0x36, 0x5E, 0xAB, 0xD6, 0x6A, 0x3E, 0x1D, 0xA3, 0x71, 0xAA,
+ 0x51, 0x95, 0x6A, 0xF8, 0xF9, 0xFB, 0x4A, 0xF5, 0xA9, 0x63, 0x35, 0xDF, 0x59, 0x33, 0x30, 0x48,
+ 0xFB, 0xA8, 0x44, 0x75, 0x9A, 0x42, 0x00, 0x26, 0x50, 0x67, 0x0D, 0x0D, 0xD0, 0xE0, 0x6F, 0xB3,
+ 0x7E, 0x4B, 0xEE, 0x29, 0x95, 0x8F, 0x4F, 0x14, 0xEB, 0x58, 0xB4, 0x84, 0x8D, 0x1E, 0xC7, 0xBF,
+ 0xA4, 0x22, 0xD5, 0x7F, 0x1B, 0x39, 0x52, 0xF3, 0x1B, 0x4E, 0xB9, 0xE4, 0x97, 0xEA, 0xC5, 0x46,
+ 0xE9, 0x88, 0x73, 0xF8, 0x94, 0xE3, 0x8D, 0xD7, 0x76, 0x22, 0x6C, 0x80, 0x46, 0xE4, 0xDA, 0x0D,
+ 0xAC, 0x5E, 0x46, 0x19, 0x5F, 0xEC, 0x08, 0x65, 0x1A, 0xB0, 0x47, 0xC0, 0x1D, 0xA6, 0x53, 0xD0,
+ 0x9E, 0xAC, 0xB5, 0x33, 0xA8, 0x45, 0x03, 0xF2, 0x8C, 0xFF, 0x3A, 0xBE, 0x00, 0x66, 0xCF, 0x16,
+ 0x00, 0x68, 0x07, 0x00, 0x00
+}; //icomoon.woof
+
+//Content of xxx with gzip compression
+//static const uint8_t rtkSetup_png[] PROGMEM = {
+//}; //
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/icons.h b/Firmware/RTK_Surveyor/icons.h
index 398efbaa0..807f83705 100644
--- a/Firmware/RTK_Surveyor/icons.h
+++ b/Firmware/RTK_Surveyor/icons.h
@@ -1,123 +1,1498 @@
-//Create a bitmap then use http://en.radzio.dxp.pl/bitmap_converter/ to generate output
-//Make sure the bitmap is n*8 pixels tall (pad white pixels to lower area as needed)
-//Otherwise the bitmap bitmap_converter will compress some of the bytes together
+// Create a bitmap then use http://en.radzio.dxp.pl/bitmap_converter/ to generate output
+// Make sure the bitmap is n*8 pixels tall (pad white pixels to lower area as needed)
+// Otherwise the bitmap bitmap_converter will compress some of the bytes together
-uint8_t BT_Symbol [] = {
-0x18, 0x30, 0xE0, 0xFF, 0xE6, 0x3C, 0x18, 0x06, 0x03, 0x01, 0x3F, 0x19, 0x0F, 0x06,
-};
-int BT_Symbol_Height = 14;
-int BT_Symbol_Width = 7;
+/*
+ BT_Symbol [7, 14]
-uint8_t WiFi_Symbol [] = {
-0x08, 0x04, 0x12, 0x09, 0x25, 0x95, 0xD5, 0x95, 0x25, 0x09, 0x12, 0x04, 0x08, 0x00, 0x00, 0x00,
-0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
-int WiFi_Symbol_Height = 9;
-int WiFi_Symbol_Width = 13;
-
-//uint8_t WiFi_Symbol [] = {
-//0x78, 0x04, 0x72, 0x09, 0x65, 0x15, 0xD5, 0xD5, 0x15, 0x65, 0x09, 0x72, 0x04, 0x78,
-//};
-//int WiFi_Symbol_Height = 8;
-//int WiFi_Symbol_Width = 13;
-
-uint8_t CrossHair [] = {
-0x80, 0x80, 0xF0, 0x88, 0x84, 0x84, 0x84, 0x7F, 0x84, 0x84, 0x84, 0x88, 0xF0, 0x80, 0x80, 0x00,
-0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x7F, 0x10, 0x10, 0x10, 0x08, 0x07, 0x00, 0x00,
-};
-int CrossHair_Height = 15;
-int CrossHair_Width = 15;
+ 1234567
+ .-------.
+ 0x01| * |
+ 0x02| ** |
+ 0x04| *** |
+ 0x08|* * **|
+ 0x10|** * **|
+ 0x20| ***** |
+ 0x40| *** |
+ 0x80| *** |
+ 0x01| ***** |
+ 0x02|** * **|
+ 0x04|* * **|
+ 0x08| *** |
+ 0x10| ** |
+ 0x20| * |
+ '-------'
+*/
-uint8_t CrossHairDual [] = {
-0x80, 0x80, 0xF0, 0x88, 0xE4, 0x94, 0x94, 0x7F, 0x94, 0x94, 0xE4, 0x88, 0xF0, 0x80, 0x80, 0x00,
-0x00, 0x07, 0x08, 0x13, 0x14, 0x14, 0x7F, 0x14, 0x14, 0x13, 0x08, 0x07, 0x00, 0x00,
-};
-int CrossHairDual_Height = 15;
-int CrossHairDual_Width = 15;
+const int BT_Symbol_Height = 14;
+const int BT_Symbol_Width = 7;
+const uint8_t BT_Symbol[] = {0x18, 0x30, 0xE0, 0xFF, 0xE6, 0x3C, 0x18, 0x06, 0x03, 0x01, 0x3F, 0x19, 0x0F, 0x06};
-uint8_t Antenna [] = {
-0x00, 0x1E, 0x62, 0x84, 0x08, 0x10, 0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1F,
-0x1F, 0x12, 0x12, 0x04, 0x04, 0x05, 0x06, 0x00,
-};
-int Antenna_Height = 13;
-int Antenna_Width = 12;
+/*
+ WiFi_Symbol_3 [13, 9]
-uint8_t Rover [] = {
-0x1E, 0x61, 0x91, 0x91, 0x61, 0x21, 0x21, 0x21, 0x21, 0x21, 0x62, 0x94, 0x94, 0x64, 0x1C,
-};
-int Rover_Height = 8;
-int Rover_Width = 15;
+ 1
+ 1234567890123
+ .-------------.
+ 0x01| ******* |
+ 0x02| * * |
+ 0x04| * ***** * |
+ 0x08|* * * *|
+ 0x10| * *** * |
+ 0x20| * * |
+ 0x40| * |
+ 0x80| *** |
+ 0x01| * |
+ '-------------'
+*/
-uint8_t BaseTemporary [] = {
-0x00, 0xFF, 0x99, 0x99, 0xE7, 0xCE, 0x32, 0x32, 0xE7, 0xE7, 0x99, 0x32, 0xFE, 0x00, 0x00, 0x1F,
-0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
-};
-int BaseTemporary_Height = 12;
-int BaseTemporary_Width = 14;
+const int WiFi_Symbol_Height = 9;
+const int WiFi_Symbol_Width = 13;
+const uint8_t WiFi_Symbol_3[] = {0x08, 0x04, 0x12, 0x09, 0x25, 0x95, 0xD5, 0x95, 0x25, 0x09, 0x12, 0x04, 0x08,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-uint8_t BaseFixed [] = {
-0x00, 0xFF, 0x01, 0x0F, 0x01, 0x8F, 0x88, 0x88, 0x8F, 0x01, 0x0F, 0x01, 0xFF, 0x00, 0x0E, 0x09,
-0x08, 0x08, 0x08, 0x0F, 0x00, 0x00, 0x0F, 0x08, 0x08, 0x08, 0x09, 0x0E,
-};
-int BaseFixed_Height = 12;
-int BaseFixed_Width = 14;
+/*
+ WiFi_Symbol_2 [13, 9]
-uint8_t Battery_3 [] = {
-0xFF, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0xFD, 0xFD, 0xFD, 0x01,
-0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x0B,
-0x0B, 0x0B, 0x08, 0x0F, 0x01, 0x01,
-};
-int Battery_3_Height = 12;
-int Battery_3_Width = 19;
+ 1
+ 1234567890123
+ .-------------.
+ 0x01| |
+ 0x02| |
+ 0x04| ***** |
+ 0x08| * * |
+ 0x10| * *** * |
+ 0x20| * * |
+ 0x40| * |
+ 0x80| *** |
+ 0x01| * |
+ '-------------'
+*/
-uint8_t Battery_2 [] = {
-0xFF, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
-0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x08,
-0x08, 0x08, 0x08, 0x0F, 0x01, 0x01,
-};
-int Battery_2_Height = 12;
-int Battery_2_Width = 19;
+const uint8_t WiFi_Symbol_2[] = {0x00, 0x00, 0x10, 0x08, 0x24, 0x94, 0xD4, 0x94, 0x24, 0x08, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-uint8_t Battery_1 [] = {
-0xFF, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
-0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
-0x08, 0x08, 0x08, 0x0F, 0x01, 0x01,
-};
-int Battery_1_Height = 12;
-int Battery_1_Width = 19;
+/*
+ WiFi_Symbol_1 [13, 9]
-uint8_t Battery_0 [] = {
-0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
-0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
-0x08, 0x08, 0x08, 0x0F, 0x01, 0x01,
-};
-int Battery_0_Height = 12;
-int Battery_0_Width = 19;
+ 1
+ 1234567890123
+ .-------------.
+ 0x01| |
+ 0x02| |
+ 0x04| |
+ 0x08| |
+ 0x10| *** |
+ 0x20| * * |
+ 0x40| * |
+ 0x80| *** |
+ 0x01| * |
+ '-------------'
+*/
-uint8_t Logging_3 [] = {
-0xFF, 0x01, 0x51, 0x51, 0x51, 0x51, 0x53, 0x06, 0xFC, 0x0F, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09,
-0x08, 0x0F,
-};
-int Logging_3_Height = 12;
-int Logging_3_Width = 9;
+const uint8_t WiFi_Symbol_1[] = {0x00, 0x00, 0x00, 0x00, 0x20, 0x90, 0xD0, 0x90, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-uint8_t Logging_2 [] = {
-0xFF, 0x01, 0x41, 0x41, 0x41, 0x41, 0x43, 0x06, 0xFC, 0x0F, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09,
-0x08, 0x0F,
-};
-int Logging_2_Height = 12;
-int Logging_2_Width = 9;
+/*
+ WiFi_Symbol_0 [13, 9]
+
+ 1
+ 1234567890123
+ .-------------.
+ 0x01| |
+ 0x02| |
+ 0x04| |
+ 0x08| |
+ 0x10| |
+ 0x20| |
+ 0x40| * |
+ 0x80| *** |
+ 0x01| * |
+ '-------------'
+*/
+
+const uint8_t WiFi_Symbol_0[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ Clock [15, 15]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ***** |
+ 0x02| ** * ** |
+ 0x04| * * * * |
+ 0x08| * * * |
+ 0x10| ** * ** |
+ 0x20|* * *|
+ 0x40|* * *|
+ 0x80|** * **|
+ 0x01|* ** *|
+ 0x02|* * *|
+ 0x04| ** ** |
+ 0x08| * * |
+ 0x10| * * * * |
+ 0x20| ** * ** |
+ 0x40| ***** |
+ '---------------'
+*/
+
+const int Clock_Icon_Height = 15;
+const int Clock_Icon_Width = 15;
+const uint8_t Clock_Icon_1[] = {0xE0, 0x98, 0x14, 0x02, 0x06, 0x01, 0x01, 0xFB, 0x01, 0x01,
+ 0x06, 0x02, 0x14, 0x98, 0xE0, 0x03, 0x0C, 0x14, 0x20, 0x30,
+ 0x40, 0x40, 0x60, 0x41, 0x41, 0x32, 0x20, 0x14, 0x0C, 0x03};
+
+/*
+ Clock [15, 15]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ***** |
+ 0x02| ** * ** |
+ 0x04| * * * * |
+ 0x08| * * |
+ 0x10| ** ** |
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|** ***** **|
+ 0x01|* ** *|
+ 0x02|* * *|
+ 0x04| ** ** |
+ 0x08| * * |
+ 0x10| * * * * |
+ 0x20| ** * ** |
+ 0x40| ***** |
+ '---------------'
+*/
+
+const uint8_t Clock_Icon_2[] = {0xE0, 0x98, 0x14, 0x02, 0x06, 0x01, 0x01, 0x83, 0x81, 0x81,
+ 0x86, 0x82, 0x14, 0x98, 0xE0, 0x03, 0x0C, 0x14, 0x20, 0x30,
+ 0x40, 0x40, 0x60, 0x41, 0x41, 0x32, 0x20, 0x14, 0x0C, 0x03};
+
+/*
+ Clock [15, 15]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ***** |
+ 0x02| ** * ** |
+ 0x04| * * * * |
+ 0x08| * * |
+ 0x10| ** ** |
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|** * **|
+ 0x01|* ** *|
+ 0x02|* ** *|
+ 0x04| ** * * ** |
+ 0x08| * * * |
+ 0x10| * * * * |
+ 0x20| ** * ** |
+ 0x40| ***** |
+ '---------------'
+*/
+
+const uint8_t Clock_Icon_3[] = {0xE0, 0x98, 0x14, 0x02, 0x06, 0x01, 0x01, 0x83, 0x01, 0x01,
+ 0x06, 0x02, 0x14, 0x98, 0xE0, 0x03, 0x0C, 0x14, 0x20, 0x30,
+ 0x40, 0x40, 0x6F, 0x43, 0x44, 0x30, 0x20, 0x14, 0x0C, 0x03};
+
+/*
+ Clock [15, 15]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ***** |
+ 0x02| ** * ** |
+ 0x04| * * * * |
+ 0x08| * * |
+ 0x10| ** ** |
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|** ***** **|
+ 0x01|* * *|
+ 0x02|* * *|
+ 0x04| ** * ** |
+ 0x08| * * |
+ 0x10| * * * * |
+ 0x20| ** * ** |
+ 0x40| ***** |
+ '---------------'
+*/
+
+const uint8_t Clock_Icon_4[] = {0xE0, 0x98, 0x14, 0x82, 0x86, 0x81, 0x81, 0x83, 0x01, 0x01,
+ 0x06, 0x02, 0x14, 0x98, 0xE0, 0x03, 0x0C, 0x14, 0x20, 0x30,
+ 0x40, 0x40, 0x60, 0x43, 0x44, 0x30, 0x20, 0x14, 0x0C, 0x03};
+
+/*
+ CrossHair [15, 15]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| * |
+ 0x02| * |
+ 0x04| ******* |
+ 0x08| * * * |
+ 0x10| * * * |
+ 0x20| * * * |
+ 0x40| * * * |
+ 0x80|******* *******|
+ 0x01| * * * |
+ 0x02| * * * |
+ 0x04| * * * |
+ 0x08| * * * |
+ 0x10| ******* |
+ 0x20| * |
+ 0x40| * |
+ '---------------'
+*/
+
+const int CrossHair_Height = 15;
+const int CrossHair_Width = 15;
+const uint8_t CrossHair[] = {0x80, 0x80, 0xF0, 0x88, 0x84, 0x84, 0x84, 0x7F, 0x84, 0x84, 0x84, 0x88, 0xF0, 0x80, 0x80,
+ 0x00, 0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x7F, 0x10, 0x10, 0x10, 0x08, 0x07, 0x00, 0x00};
+
+/*
+ CrossHairDual [15, 15]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| * |
+ 0x02| * |
+ 0x04| ******* |
+ 0x08| * * * |
+ 0x10| * ***** * |
+ 0x20| * * * * * |
+ 0x40| * * * * * |
+ 0x80|******* *******|
+ 0x01| * * * * * |
+ 0x02| * * * * * |
+ 0x04| * ***** * |
+ 0x08| * * * |
+ 0x10| ******* |
+ 0x20| * |
+ 0x40| * |
+ '---------------'
+*/
+
+const int CrossHairDual_Height = 15;
+const int CrossHairDual_Width = 15;
+const uint8_t CrossHairDual[] = {0x80, 0x80, 0xF0, 0x88, 0xE4, 0x94, 0x94, 0x7F, 0x94, 0x94,
+ 0xE4, 0x88, 0xF0, 0x80, 0x80, 0x00, 0x00, 0x07, 0x08, 0x13,
+ 0x14, 0x14, 0x7F, 0x14, 0x14, 0x13, 0x08, 0x07, 0x00, 0x00};
+
+/*
+ SIV_Antenna [12, 13]
+
+ 1
+ 123456789012
+ .------------.
+ 0x01| |
+ 0x02| ** |
+ 0x04| * * |
+ 0x08| * * * |
+ 0x10| * * * |
+ 0x20| * * |
+ 0x40| * * |
+ 0x80| * * |
+ 0x01| ** * |
+ 0x02| **** * |
+ 0x04| ** **** |
+ 0x08| ** |
+ 0x10| ****** |
+ '------------'
+*/
+
+const int SIV_Antenna_Height = 13;
+const int SIV_Antenna_Width = 12;
+const uint8_t SIV_Antenna[] = {0x00, 0x1E, 0x62, 0x84, 0x08, 0x10, 0x20, 0x50, 0x88, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x10, 0x1F, 0x1F, 0x12, 0x12, 0x04, 0x04, 0x05, 0x06, 0x00};
+
+/*
+ SIV_Antenna_LBand [12, 13]
+
+ 1
+ 123456789012
+ .------------.
+ 0x01| |
+ 0x02| ** * |
+ 0x04| * * * |
+ 0x08| * * * |
+ 0x10| * * * |
+ 0x20| * * * |
+ 0x40| * * * |
+ 0x80| * * |
+ 0x01| ** * |
+ 0x02| **** * |
+ 0x04| ** **** |
+ 0x08| ** |
+ 0x10| ****** |
+ '------------'
+*/
+
+const int SIV_Antenna_LBand_Height = 13;
+const int SIV_Antenna_LBand_Width = 12;
+const uint8_t SIV_Antenna_LBand[] = {0x00, 0x1E, 0x62, 0x84, 0x08, 0x14, 0x22, 0x50, 0x88, 0x40, 0x20, 0x00,
+ 0x00, 0x10, 0x10, 0x1F, 0x1F, 0x12, 0x12, 0x04, 0x04, 0x05, 0x06, 0x00};
+
+/*
+ Antenna_Short [12, 13]
+
+ 1
+ 123456789012
+ .------------.
+ 0x01| * |
+ 0x02| * |
+ 0x04| * |
+ 0x08| ** |
+ 0x10| * * |
+ 0x20| * ***** |
+ 0x40| * * |
+ 0x80| ***** * |
+ 0x01| * * |
+ 0x02| ** |
+ 0x04| * |
+ 0x08| * |
+ 0x10| * |
+ '------------'
+*/
+
+const int Antenna_Short_Height = 13;
+const int Antenna_Short_Width = 12;
+const uint8_t Antenna_Short[] = {0x00, 0x80, 0xC0, 0xA0, 0x90, 0x88, 0x3F, 0x20, 0xA0, 0x60, 0x20, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ Antenna_Open [12, 13]
+
+ 1
+ 123456789012
+ .------------.
+ 0x01| ** |
+ 0x02| ** |
+ 0x04| ** |
+ 0x08| ** |
+ 0x10| ** |
+ 0x20| ** |
+ 0x40| ** ** |
+ 0x80| ** |
+ 0x01| ** |
+ 0x02| ** |
+ 0x04| ** |
+ 0x08| ** |
+ 0x10| ** |
+ '------------'
+*/
+
+const int Antenna_Open_Height = 13;
+const int Antenna_Open_Width = 12;
+const uint8_t Antenna_Open[] = {0x00, 0x00, 0x00, 0x60, 0x70, 0x1F, 0x0F, 0xC0, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ Rover_Fusion [15, 9]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ********* |
+ 0x02|* * |
+ 0x04|* **** ****|
+ 0x08|* * *|
+ 0x10|* *** *|
+ 0x20|* ** * ** *|
+ 0x40| * ******* * |
+ 0x80| * * * * |
+ 0x01| ** ** |
+ '---------------'
+*/
+
+const int Rover_Fusion_Height = 9;
+const int Rover_Fusion_Width = 15;
+const uint8_t Rover_Fusion[] = {0x3E, 0xC1, 0x21, 0x21, 0xC1, 0x7D, 0x55, 0x55, 0x45, 0x41,
+ 0xC2, 0x24, 0x24, 0xC4, 0x3C, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00};
+
+/*
+ Rover_Fusion_Empty [15, 9]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ********* |
+ 0x02|* * |
+ 0x04|* ****|
+ 0x08|* *|
+ 0x10|* *|
+ 0x20|* ** ** *|
+ 0x40| * ******* * |
+ 0x80| * * * * |
+ 0x01| ** ** |
+ '---------------'
+*/
+
+const int Rover_Fusion_Empty_Height = 9;
+const int Rover_Fusion_Empty_Width = 15;
+const uint8_t Rover_Fusion_Empty[] = {0x3E, 0xC1, 0x21, 0x21, 0xC1, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0xC2, 0x24, 0x24, 0xC4, 0x3C, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00};
+
+/*
+ BaseTemporary [14, 12]
+
+ 1
+ 12345678901234
+ .--------------.
+ 0x01| **** *** |
+ 0x02| * ****** ** |
+ 0x04| * ** ** * |
+ 0x08| *** * * * |
+ 0x10| *** ** *** |
+ 0x20| * * **** ** |
+ 0x40| * ** ** * |
+ 0x80| ***** *** * |
+ 0x01| * *** ** |
+ 0x02| * |
+ 0x04| * |
+ 0x08| * |
+ '--------------'
+*/
+
+const int BaseTemporary_Height = 12;
+const int BaseTemporary_Width = 14;
+const uint8_t BaseTemporary[] = {0x00, 0xFF, 0x99, 0x99, 0xE7, 0xCE, 0x32, 0x32, 0xE7, 0xE7, 0x99, 0x32, 0xFE, 0x00,
+ 0x00, 0x1F, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00};
+
+/*
+ BaseFixed [14, 12]
+
+ 1
+ 12345678901234
+ .--------------.
+ 0x01| ***** ***** |
+ 0x02| * * * * * * |
+ 0x04| * * * * * * |
+ 0x08| * * **** * * |
+ 0x10| * * |
+ 0x20| * * |
+ 0x40| * * |
+ 0x80| * **** * |
+ 0x01| * * * * |
+ 0x02|* * * *|
+ 0x04|* * * *|
+ 0x08|****** ******|
+ '--------------'
+*/
+
+const int BaseFixed_Height = 12;
+const int BaseFixed_Width = 14;
+const uint8_t BaseFixed[] = {0x00, 0xFF, 0x01, 0x0F, 0x01, 0x8F, 0x88, 0x88, 0x8F, 0x01, 0x0F, 0x01, 0xFF, 0x00,
+ 0x0E, 0x09, 0x08, 0x08, 0x08, 0x0F, 0x00, 0x00, 0x0F, 0x08, 0x08, 0x08, 0x09, 0x0E};
+
+/*
+ Battery_3 [19, 12]
+
+ 1
+ 1234567890123456789
+ .-------------------.
+ 0x01|***************** |
+ 0x02|* * |
+ 0x04|* *** *** *** * |
+ 0x08|* *** *** *** ***|
+ 0x10|* *** *** *** *|
+ 0x20|* *** *** *** *|
+ 0x40|* *** *** *** *|
+ 0x80|* *** *** *** *|
+ 0x01|* *** *** *** ***|
+ 0x02|* *** *** *** * |
+ 0x04|* * |
+ 0x08|***************** |
+ '-------------------'
+*/
+
+const int Battery_3_Height = 12;
+const int Battery_3_Width = 19;
+const uint8_t Battery_3[] = {0xFF, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0xFD,
+ 0xFD, 0xFD, 0x01, 0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08,
+ 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x0F, 0x01, 0x01};
+
+/*
+ Battery_2 [19, 12]
+
+ 1
+ 1234567890123456789
+ .-------------------.
+ 0x01|***************** |
+ 0x02|* * |
+ 0x04|* *** *** * |
+ 0x08|* *** *** ***|
+ 0x10|* *** *** *|
+ 0x20|* *** *** *|
+ 0x40|* *** *** *|
+ 0x80|* *** *** *|
+ 0x01|* *** *** ***|
+ 0x02|* *** *** * |
+ 0x04|* * |
+ 0x08|***************** |
+ '-------------------'
+*/
+
+const int Battery_2_Height = 12;
+const int Battery_2_Width = 19;
+const uint8_t Battery_2[] = {0xFF, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08,
+ 0x0B, 0x0B, 0x0B, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F, 0x01, 0x01};
+
+/*
+ Battery_1 [19, 12]
+
+ 1
+ 1234567890123456789
+ .-------------------.
+ 0x01|***************** |
+ 0x02|* * |
+ 0x04|* *** * |
+ 0x08|* *** ***|
+ 0x10|* *** *|
+ 0x20|* *** *|
+ 0x40|* *** *|
+ 0x80|* *** *|
+ 0x01|* *** ***|
+ 0x02|* *** * |
+ 0x04|* * |
+ 0x08|***************** |
+ '-------------------'
+*/
+
+const int Battery_1_Height = 12;
+const int Battery_1_Width = 19;
+const uint8_t Battery_1[] = {0xFF, 0x01, 0xFD, 0xFD, 0xFD, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x0B, 0x0B, 0x0B, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F, 0x01, 0x01};
+
+/*
+ Battery_0 [19, 12]
+
+ 1
+ 1234567890123456789
+ .-------------------.
+ 0x01|***************** |
+ 0x02|* * |
+ 0x04|* * |
+ 0x08|* ***|
+ 0x10|* *|
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|* *|
+ 0x01|* ***|
+ 0x02|* * |
+ 0x04|* * |
+ 0x08|***************** |
+ '-------------------'
+*/
+
+const int Battery_0_Height = 12;
+const int Battery_0_Width = 19;
+const uint8_t Battery_0[] = {0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x0F, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F, 0x01, 0x01};
+
+/*
+ Ethernet [19, 12]
+
+ 1
+ 1234567890123456789
+ .-------------------.
+ 0x01| |
+ 0x02| ***** |
+ 0x04| * * |
+ 0x08| ***** |
+ 0x10| * |
+ 0x20| ***************** |
+ 0x40| * * |
+ 0x80| ***** ***** |
+ 0x01| * * * * |
+ 0x02| ***** ***** |
+ 0x04| |
+ 0x08| |
+ '-------------------'
+*/
+
+const int Ethernet_Icon_Height = 12;
+const int Ethernet_Icon_Width = 19;
+const uint8_t Ethernet_Icon[] = {0x00, 0x20, 0xA0, 0xA0, 0xE0, 0xA0, 0xA0, 0x2E, 0x2A, 0x3A, 0x2A, 0x2E, 0xA0,
+ 0xA0, 0xE0, 0xA0, 0xA0, 0x20, 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x03, 0x00, 0x00};
+
+/*
+ Logging_3 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* ***** *|
+ 0x20|* *|
+ 0x40|* ***** *|
+ 0x80|* *|
+ 0x01|* ***** *|
+ 0x02|* *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const int Logging_3_Height = 12;
+const int Logging_3_Width = 9;
+const uint8_t Logging_3[] = {0xFF, 0x01, 0x51, 0x51, 0x51, 0x51, 0x53, 0x06, 0xFC,
+ 0x0F, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0F};
+
+/*
+ Logging_2 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* *|
+ 0x20|* *|
+ 0x40|* ***** *|
+ 0x80|* *|
+ 0x01|* ***** *|
+ 0x02|* *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const int Logging_2_Height = 12;
+const int Logging_2_Width = 9;
+const uint8_t Logging_2[] = {0xFF, 0x01, 0x41, 0x41, 0x41, 0x41, 0x43, 0x06, 0xFC,
+ 0x0F, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0F};
+
+/*
+ Logging_1 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* *|
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|* *|
+ 0x01|* ***** *|
+ 0x02|* *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const int Logging_1_Height = 12;
+const int Logging_1_Width = 9;
+const uint8_t Logging_1[] = {0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0F};
+
+/*
+ Logging_0 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* *|
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|* *|
+ 0x01|* *|
+ 0x02|* *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const int Logging_0_Height = 12;
+const int Logging_0_Width = 9;
+const uint8_t Logging_0[] = {0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F};
+
+/*
+ Logging_PPP_3 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* **** *|
+ 0x10|* * * *|
+ 0x20|* * * *|
+ 0x40|* **** *|
+ 0x80|* * *|
+ 0x01|* * *|
+ 0x02|* * *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_PPP_3[] = {0xFF, 0x01, 0xF9, 0x49, 0x49, 0x49, 0x33, 0x06, 0xFC,
+ 0x0F, 0x08, 0x0B, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F};
-uint8_t Logging_1 [] = {
-0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC, 0x0F, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09,
-0x08, 0x0F,
+/*
+ Logging_PPP_2 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* * *|
+ 0x10|* * *|
+ 0x20|* * *|
+ 0x40|* **** *|
+ 0x80|* * *|
+ 0x01|* * *|
+ 0x02|* * *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_PPP_2[] = {0xFF, 0x01, 0xF9, 0x41, 0x41, 0x41, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x0B, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F};
+
+/*
+ Logging_PPP_1 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* *|
+ 0x20|* *|
+ 0x40|* * *|
+ 0x80|* * *|
+ 0x01|* * *|
+ 0x02|* * *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_PPP_1[] = {0xFF, 0x01, 0xC1, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x0B, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F};
+
+/*
+ Logging_Custom_3 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *** *|
+ 0x10|* * * *|
+ 0x20|* * *|
+ 0x40|* * *|
+ 0x80|* * *|
+ 0x01|* * * *|
+ 0x02|* *** *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_Custom_3[] = {0xFF, 0x01, 0xF1, 0x09, 0x09, 0x09, 0x13, 0x06, 0xFC,
+ 0x0F, 0x08, 0x09, 0x0A, 0x0A, 0x0A, 0x09, 0x08, 0x0F};
+
+/*
+ Logging_Custom_2 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* * *|
+ 0x20|* * *|
+ 0x40|* * *|
+ 0x80|* * *|
+ 0x01|* * * *|
+ 0x02|* *** *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_Custom_2[] = {0xFF, 0x01, 0xF1, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x09, 0x0A, 0x0A, 0x0A, 0x09, 0x08, 0x0F};
+
+/*
+ Logging_Custom_1 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* *|
+ 0x20|* *|
+ 0x40|* *|
+ 0x80|* *|
+ 0x01|* * * *|
+ 0x02|* *** *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_Custom_1[] = {0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x09, 0x0A, 0x0A, 0x0A, 0x09, 0x08, 0x0F};
+
+/*
+ Logging_NTP_3 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* * * *|
+ 0x20|* ** * *|
+ 0x40|* * * * *|
+ 0x80|* * * * *|
+ 0x01|* * ** *|
+ 0x02|* * * *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_NTP_3[] = {0xFF, 0x01, 0xF1, 0x21, 0xC1, 0x01, 0xF3, 0x06, 0xFC,
+ 0x0F, 0x08, 0x0B, 0x08, 0x08, 0x09, 0x0B, 0x08, 0x0F};
+
+/*
+ Logging_NTP_2 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* * *|
+ 0x20|* ** *|
+ 0x40|* * * *|
+ 0x80|* * * *|
+ 0x01|* * * *|
+ 0x02|* * *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_NTP_2[] = {0xFF, 0x01, 0xF1, 0x21, 0xC1, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x0B, 0x08, 0x08, 0x09, 0x08, 0x08, 0x0F};
+
+/*
+ Logging_NTP_1 [9, 12]
+
+ 123456789
+ .---------.
+ 0x01|******* |
+ 0x02|* ** |
+ 0x04|* **|
+ 0x08|* *|
+ 0x10|* * *|
+ 0x20|* * *|
+ 0x40|* * *|
+ 0x80|* * *|
+ 0x01|* * *|
+ 0x02|* * *|
+ 0x04|* *|
+ 0x08|*********|
+ '---------'
+*/
+
+const uint8_t Logging_NTP_1[] = {0xFF, 0x01, 0xF1, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC,
+ 0x0F, 0x08, 0x0B, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0F};
+
+/*
+ DynamicModel_1_Portable [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| ** |
+ 0x02| ** |
+ 0x04| ****** |
+ 0x08| * * |
+ 0x10| * * **** * * |
+ 0x20| * * **** * * |
+ 0x40| * * * * |
+ 0x80| * * * * |
+ 0x01| * * * * |
+ 0x02| * * * * |
+ 0x04| * * |
+ 0x08| ****** |
+ '---------------'
+*/
+
+const int DynamicModel_Height = 12;
+const int DynamicModel_Width = 15;
+const uint8_t DynamicModel_1_Portable[] = {0x00, 0xF0, 0x00, 0xF8, 0x04, 0x34, 0x34, 0x37, 0x37, 0x04,
+ 0xF8, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x00, 0x03, 0x00, 0x00};
+
+/*
+ DynamicModel_2_Stationary [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| ******* |
+ 0x04| ***** |
+ 0x08| *** |
+ 0x10| * |
+ 0x20| *** |
+ 0x40| ***** |
+ 0x80| ** * ** |
+ 0x01| ** * ** |
+ 0x02| ** * ** |
+ 0x04| ** * ** |
+ 0x08| |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_2_Stationary[] = {0x00, 0x00, 0x00, 0x00, 0x82, 0xC6, 0x6E, 0xFE, 0x6E, 0xC6,
+ 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x06, 0x03, 0x01,
+ 0x00, 0x00, 0x07, 0x00, 0x00, 0x01, 0x03, 0x06, 0x04, 0x00};
+
+/*
+ DynamicModel_3_Pedestrian [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| *** |
+ 0x02| * * |
+ 0x04| * * |
+ 0x08| * |
+ 0x10| ***** |
+ 0x20| ** * ** |
+ 0x40| * * |
+ 0x80| *** |
+ 0x01| ** ** |
+ 0x02| ** * |
+ 0x04| ** ** |
+ 0x08| |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_3_Pedestrian[] = {0x00, 0x00, 0x00, 0x00, 0x20, 0x32, 0x95, 0xF9, 0x95, 0x32,
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x06,
+ 0x03, 0x01, 0x00, 0x01, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ DynamicModel_4_Automotive [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| |
+ 0x04| ********* |
+ 0x08|* * |
+ 0x10|* ****|
+ 0x20|* *|
+ 0x40|* ** ** *|
+ 0x80| * ******* * |
+ 0x01| * * * * |
+ 0x02| ** ** |
+ 0x04| |
+ 0x08| |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_4_Automotive[] = {0x78, 0x84, 0x44, 0x44, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x88, 0x50, 0x50, 0x90, 0x70, 0x00, 0x01, 0x02, 0x02, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00};
+
+/*
+ DynamicModel_5_Sea [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| * |
+ 0x04| *** |
+ 0x08| * * |
+ 0x10| * * |
+ 0x20| ************* |
+ 0x40| ** ** |
+ 0x80| * ** * |
+ 0x01| * * |
+ 0x02| ** ** |
+ 0x04| ********* |
+ 0x08| |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_5_Sea[] = {0x00, 0x60, 0xE0, 0x3C, 0x26, 0x3C, 0x20, 0x20, 0x20, 0xA0,
+ 0xA0, 0x20, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x03, 0x06, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x06, 0x03, 0x00, 0x00};
+
+/*
+ DynamicModel_6_Airborne1g [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| * |
+ 0x04| ** |
+ 0x08| *********** |
+ 0x10| * * ** |
+ 0x20| * * * ** |
+ 0x40| * * * |
+ 0x80| * * *** ** |
+ 0x01| ****** ***** |
+ 0x02| * * |
+ 0x04| * * |
+ 0x08| * |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_6_Airborne1g[] = {0x00, 0xFE, 0x0C, 0xF8, 0x08, 0x08, 0x88, 0x88, 0x88, 0x28,
+ 0x08, 0x18, 0xB0, 0xE0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x07, 0x08, 0x07, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00};
+
+/*
+ DynamicModel_7_Airborne2g [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| * |
+ 0x04| ** |
+ 0x08| *********** |
+ 0x10| * * ** |
+ 0x20| * * * * ** |
+ 0x40| * * * |
+ 0x80| * * *** ** |
+ 0x01| ****** ***** |
+ 0x02| * * |
+ 0x04| * * |
+ 0x08| * |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_7_Airborne2g[] = {0x00, 0xFE, 0x0C, 0xF8, 0x08, 0x08, 0x88, 0xA8, 0x88, 0x28,
+ 0x08, 0x18, 0xB0, 0xE0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x07, 0x08, 0x07, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00};
+
+/*
+ DynamicModel_8_Airborne4g [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| * |
+ 0x04| ** |
+ 0x08| *********** |
+ 0x10| * * ** |
+ 0x20| * * * * * ** |
+ 0x40| * * * |
+ 0x80| * * *** ** |
+ 0x01| ****** ***** |
+ 0x02| * * |
+ 0x04| * * |
+ 0x08| * |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_8_Airborne4g[] = {0x00, 0xFE, 0x0C, 0xF8, 0x08, 0x28, 0x88, 0xA8, 0x88, 0x28,
+ 0x08, 0x18, 0xB0, 0xE0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x07, 0x08, 0x07, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00};
+
+/*
+ DynamicModel_9_Wrist [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| *** |
+ 0x02| *** |
+ 0x04| *** |
+ 0x08| ***** |
+ 0x10| * * |
+ 0x20| * * |
+ 0x40| * *** * |
+ 0x80| * * |
+ 0x01| * * |
+ 0x02| ***** |
+ 0x04| *** |
+ 0x08| *** |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_9_Wrist[] = {0x00, 0x00, 0x00, 0xE0, 0x10, 0x08, 0x4F, 0x4F, 0x4F, 0x08,
+ 0x10, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x02, 0x1E, 0x1E, 0x1E, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ DynamicModel_10_Bike [15, 12]
+
+ 1
+ 123456789012345
+ .---------------.
+ 0x01| |
+ 0x02| |
+ 0x04| ** |
+ 0x08| *** |
+ 0x10| *** * |
+ 0x20| * * |
+ 0x40| ** *** ** |
+ 0x80| * ******* * |
+ 0x01| * * * * |
+ 0x02| ** ** |
+ 0x04| |
+ 0x08| |
+ '---------------'
+*/
+
+const uint8_t DynamicModel_10_Bike[] = {0x00, 0x80, 0x40, 0x50, 0x90, 0xB0, 0xC0, 0xC0, 0xC0, 0xA0,
+ 0x98, 0x4C, 0x4C, 0x80, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00};
+
+const uint8_t DynamicModel_11_Mower[] = {
+ 0x01, 0x03, 0x86, 0x8C, 0x98, 0xB0, 0xE0, 0xC0, 0xF4, 0xDC, 0xDC, 0xF4, 0xC0, 0x80, 0x00,
+ 0x00, 0x00, 0x03, 0x0E, 0x0A, 0x0E, 0x02, 0x02, 0x02, 0x02, 0x0E, 0x0A, 0x0E, 0x03, 0x00,
};
-int Logging_1_Height = 12;
-int Logging_1_Width = 9;
-uint8_t Logging_0 [] = {
-0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x06, 0xFC, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
-0x08, 0x0F,
+const uint8_t DynamicModel_12_EScooter[] = {
+ 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0xFF, 0xFF, 0x01, 0x02, 0x00, 0x00,
+ 0x00, 0x03, 0x0F, 0x0B, 0x0F, 0x03, 0x03, 0x03, 0x03, 0x0F, 0x0B, 0x0F, 0x03, 0x00, 0x00,
};
-int Logging_0_Height = 12;
-int Logging_0_Width = 9;
+
+/*
+ DownloadArrow [8, 9]
+
+ 12345678
+ .--------.
+ 0x01| ** |
+ 0x02| ** |
+ 0x04| ** |
+ 0x08| ** |
+ 0x10| ** |
+ 0x20|** ** **|
+ 0x40| ****** |
+ 0x80| **** |
+ 0x01| ** |
+ '--------'
+*/
+
+const int DownloadArrow_Height = 9;
+const int DownloadArrow_Width = 8;
+const uint8_t DownloadArrow[] = {0x20, 0x60, 0xC0, 0xFF, 0xFF, 0xC0, 0x60, 0x20,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00};
+
+/*
+ UploadArrow [8, 9]
+
+ 12345678
+ .--------.
+ 0x01| ** |
+ 0x02| **** |
+ 0x04| ****** |
+ 0x08|** ** **|
+ 0x10| ** |
+ 0x20| ** |
+ 0x40| ** |
+ 0x80| ** |
+ 0x01| ** |
+ '--------'
+*/
+
+const int UploadArrow_Height = 9;
+const int UploadArrow_Width = 8;
+const uint8_t UploadArrow[] = {0x08, 0x0C, 0x06, 0xFF, 0xFF, 0x06, 0x0C, 0x08,
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00};
+
+/*
+ logoSparkFun [64, 48]
+
+ 1 2 3 4 5 6
+ 1234567890123456789012345678901234567890123456789012345678901234
+ .----------------------------------------------------------------.
+ 0x01| ********** |
+ 0x02| ************* |
+ 0x04| ************** |
+ 0x08| *********** |
+ 0x10| ********** |
+ 0x20| *********** |
+ 0x40| *********** |
+ 0x80| *********** ** |
+ 0x01| ************ *** |
+ 0x02| ************* **** |
+ 0x04| ********************* |
+ 0x08| ********************* |
+ 0x10| ******************** |
+ 0x20| ********************* |
+ 0x40| ******************* |
+ 0x80| ******* ******************* |
+ 0x01| ******* ****************** |
+ 0x02| ******* ***************** |
+ 0x04| ******** ****************** |
+ 0x08| ******** ****************** |
+ 0x10| ********* ******************* |
+ 0x20| ********************************* |
+ 0x40| ********************************* |
+ 0x80| ********************************* |
+ 0x01| ********************************* |
+ 0x02| ******************************** |
+ 0x04| ******************************** |
+ 0x08| ******************************* |
+ 0x10| ******************************* |
+ 0x20| ****************************** |
+ 0x40| ***************************** |
+ 0x80| **************************** |
+ 0x01| *************************** |
+ 0x02| ************************** |
+ 0x04| ************************ |
+ 0x08| ********************* |
+ 0x10| ************* |
+ 0x20| *********** |
+ 0x40| ********** |
+ 0x80| ********* |
+ 0x01| ******** |
+ 0x02| ******* |
+ 0x04| ****** |
+ 0x08| ***** |
+ 0x10| **** |
+ 0x20| *** |
+ 0x40| ** |
+ 0x80| * |
+ '----------------------------------------------------------------'
+*/
+
+const int logoSparkFun_Height = 48;
+const int logoSparkFun_Width = 64;
+const uint8_t logoSparkFun[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x07, 0x07,
+ 0x06, 0x06, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x07, 0x0F, 0x3F, 0x3F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFC, 0xFC, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0xFC, 0xF8, 0xE0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x1F, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x1F, 0x1F, 0x0F,
+ 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x07, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+/*
+ ESPNOW_Symbol_3 [8, 13]
+
+ 12345678
+ .--------.
+ 0x01| * |
+ 0x02| * |
+ 0x04| * * |
+ 0x08| * *|
+ 0x10| * * *|
+ 0x20|* * * *|
+ 0x40|** * * *|
+ 0x80|* * * *|
+ 0x01| * * *|
+ 0x02| * *|
+ 0x04| * * |
+ 0x08| * |
+ 0x10| * |
+ '--------'
+*/
+
+const int ESPNOW_Symbol_Height = 13;
+const int ESPNOW_Symbol_Width = 8;
+const uint8_t ESPNOW_Symbol_3[] = {0xE0, 0x40, 0x10, 0xE4, 0x09, 0xF2, 0x04, 0xF8,
+ 0x00, 0x00, 0x01, 0x04, 0x12, 0x09, 0x04, 0x03};
+
+/*
+ ESPNOW_Symbol_2 [8, 13]
+
+ 12345678
+ .--------.
+ 0x01| |
+ 0x02| |
+ 0x04| * |
+ 0x08| * |
+ 0x10| * * |
+ 0x20|* * * |
+ 0x40|** * * |
+ 0x80|* * * |
+ 0x01| * * |
+ 0x02| * |
+ 0x04| * |
+ 0x08| |
+ 0x10| |
+ '--------'
+*/
+
+const uint8_t ESPNOW_Symbol_2[] = {0xE0, 0x40, 0x10, 0xE4, 0x08, 0xF0, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x02, 0x01, 0x00, 0x00};
+
+/*
+ ESPNOW_Symbol_1 [8, 13]
+
+ 12345678
+ .--------.
+ 0x01| |
+ 0x02| |
+ 0x04| |
+ 0x08| |
+ 0x10| * |
+ 0x20|* * |
+ 0x40|** * |
+ 0x80|* * |
+ 0x01| * |
+ 0x02| |
+ 0x04| |
+ 0x08| |
+ 0x10| |
+ '--------'
+*/
+
+const uint8_t ESPNOW_Symbol_1[] = {0xE0, 0x40, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ ESPNOW_Symbol_0 [8, 13]
+
+ 12345678
+ .--------.
+ 0x01| |
+ 0x02| |
+ 0x04| |
+ 0x08| |
+ 0x10| |
+ 0x20|* |
+ 0x40|** |
+ 0x80|* |
+ 0x01| |
+ 0x02| |
+ 0x04| |
+ 0x08| |
+ 0x10| |
+ '--------'
+*/
+
+const uint8_t ESPNOW_Symbol_0[] = {0xE0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/*
+ milliseconds [8, 12]
+
+
+ 12345678
+ .--------.
+ 0x01|** * |
+ 0x02|* * * |
+ 0x04|* * * |
+ 0x08|* * * |
+ 0x10|* * * |
+ 0x20| |
+ 0x40| |
+ 0x80| **** |
+ 0x01| * |
+ 0x02| *** |
+ 0x04| * |
+ 0x08| **** |
+ '--------'
+*/
+
+const int Millis_Icon_Height = 12;
+const int Millis_Icon_Width = 8;
+const uint8_t Millis_Icon[] = {0x1F, 0x01, 0x1E, 0x81, 0x9E, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x09, 0x0A, 0x0A, 0x0A, 0x04, 0x00};
+
+/*
+ microseconds [8, 12]
+
+
+ 12345678
+ .--------.
+ 0x01|* * |
+ 0x02|* * |
+ 0x04|** ** |
+ 0x08|* ** * |
+ 0x10|* |
+ 0x20|* |
+ 0x40| |
+ 0x80| **** |
+ 0x01| * |
+ 0x02| *** |
+ 0x04| * |
+ 0x08| **** |
+ '--------'
+*/
+
+const uint8_t Micros_Icon[] = {0x3F, 0x04, 0x08, 0x88, 0x84, 0x8F, 0x80, 0x00,
+ 0x00, 0x00, 0x09, 0x0A, 0x0A, 0x0A, 0x04, 0x00};
+
+/*
+ nanoseconds [8, 12]
+
+
+ 12345678
+ .--------.
+ 0x01|**** |
+ 0x02|* * |
+ 0x04|* * |
+ 0x08|* * |
+ 0x10|* * |
+ 0x20| |
+ 0x40| |
+ 0x80| **** |
+ 0x01| * |
+ 0x02| *** |
+ 0x04| * |
+ 0x08| **** |
+ '--------'
+*/
+
+const uint8_t Nanos_Icon[] = {0x1F, 0x01, 0x01, 0x81, 0x9E, 0x80, 0x80, 0x00,
+ 0x00, 0x00, 0x09, 0x0A, 0x0A, 0x0A, 0x04, 0x00};
diff --git a/Firmware/RTK_Surveyor/menuBase.ino b/Firmware/RTK_Surveyor/menuBase.ino
index be8b739b4..288a6d6af 100644
--- a/Firmware/RTK_Surveyor/menuBase.ino
+++ b/Firmware/RTK_Surveyor/menuBase.ino
@@ -1,218 +1,993 @@
-//Configure the survey in settings (time and 3D dev max)
-//Set the ECEF coordinates for a known location
+/*------------------------------------------------------------------------------
+menuBase.ino
+------------------------------------------------------------------------------*/
+
+//----------------------------------------
+// Constants
+//----------------------------------------
+
+static const float maxObservationPositionAccuracy = 10.0;
+static const float maxSurveyInStartingAccuracy = 10.0;
+
+//----------------------------------------
+// Menus
+//----------------------------------------
+
+// Configure the survey in settings (time and 3D dev max)
+// Set the ECEF coordinates for a known location
void menuBase()
{
- int menuTimeoutExtended = 30; //Increase time needed for complex data entry (mount point ID, ECEF coords, etc).
-
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Base Menu"));
-
- Serial.print(F("1) Toggle Base Mode: "));
- if (settings.fixedBase == true) Serial.println(F("Fixed/Static Position"));
- else Serial.println(F("Use Survey-In"));
-
- if (settings.fixedBase == true)
- {
- Serial.print(F("2) Toggle Coordinate System: "));
- if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF) Serial.println(F("ECEF"));
- else Serial.println(F("Geographic"));
-
- if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
- {
- Serial.print(F("3) Set ECEF X/Y/Z coordinates: "));
- Serial.print(settings.fixedEcefX, 4);
- Serial.print(F("m, "));
- Serial.print(settings.fixedEcefY, 4);
- Serial.print(F("m, "));
- Serial.print(settings.fixedEcefZ, 4);
- Serial.println(F("m"));
- }
- else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEOGRAPHIC)
- {
- Serial.print(F("3) Set Lat/Long/Altitude coordinates: "));
- Serial.print(settings.fixedLat, 9);
- Serial.print(F("°, "));
- Serial.print(settings.fixedLong, 9);
- Serial.print(F("°, "));
- Serial.print(settings.fixedAltitude, 4);
- Serial.println(F("m"));
- }
- }
- else
+ int serverIndex = 0;
+ int value;
+
+ while (1)
{
- Serial.print(F("2) Set minimum observation time: "));
- Serial.print(settings.observationSeconds);
- Serial.println(F(" seconds"));
+ systemPrintln();
+ systemPrintln("Menu: Base");
- Serial.print(F("3) Set required Mean 3D Standard Deviation: "));
- Serial.print(settings.observationPositionAccuracy, 3);
- Serial.println(F(" meters"));
- }
+ // Print the combined HAE APC if we are in the given mode
+ if (settings.fixedBase == true && settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC)
+ {
+ systemPrintf(
+ "Total Height Above Ellipsoid - Antenna Phase Center (HAE APC): %0.3fmm\r\n",
+ (((settings.fixedAltitude * 1000) + settings.antennaHeight + settings.antennaReferencePoint) / 1000));
+ }
- Serial.print(F("4) Toggle NTRIP Server: "));
- if (settings.enableNtripServer == true) Serial.println(F("Enabled"));
- else Serial.println(F("Disabled"));
+ systemPrint("1) Toggle Base Mode: ");
+ if (settings.fixedBase == true)
+ systemPrintln("Fixed/Static Position");
+ else
+ systemPrintln("Use Survey-In");
- if (settings.enableNtripServer == true)
- {
- Serial.print(F("5) Set WiFi SSID: "));
- Serial.println(settings.wifiSSID);
+ if (settings.fixedBase == true)
+ {
+ systemPrint("2) Toggle Coordinate System: ");
+ if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
+ systemPrintln("ECEF");
+ else
+ systemPrintln("Geodetic");
- Serial.print(F("6) Set WiFi PW: "));
- Serial.println(settings.wifiPW);
+ if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
+ {
+ systemPrint("3) Set ECEF X/Y/Z coordinates: ");
+ systemPrint(settings.fixedEcefX, 4);
+ systemPrint("m, ");
+ systemPrint(settings.fixedEcefY, 4);
+ systemPrint("m, ");
+ systemPrint(settings.fixedEcefZ, 4);
+ systemPrintln("m");
+ }
+ else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC)
+ {
+ systemPrint("3) Set Lat/Long/Altitude coordinates: ");
- Serial.print(F("7) Set Caster Address: "));
- Serial.println(settings.casterHost);
+ char coordinatePrintable[50];
+ coordinateConvertInput(settings.fixedLat, settings.coordinateInputType, coordinatePrintable,
+ sizeof(coordinatePrintable));
+ systemPrint(coordinatePrintable);
- Serial.print(F("8) Set Caster Port: "));
- Serial.println(settings.casterPort);
+ systemPrint(", ");
- Serial.print(F("9) Set Mountpoint: "));
- Serial.println(settings.mountPoint);
+ coordinateConvertInput(settings.fixedLong, settings.coordinateInputType, coordinatePrintable,
+ sizeof(coordinatePrintable));
+ systemPrint(coordinatePrintable);
- Serial.print(F("10) Set Mountpoint PW: "));
- Serial.println(settings.mountPointPW);
- }
+ systemPrint(", ");
+ systemPrint(settings.fixedAltitude, 4);
+ systemPrint("m");
+ systemPrintln();
- Serial.println(F("x) Exit"));
+ systemPrint("4) Set coordinate display format: ");
+ systemPrintln(coordinatePrintableInputType(settings.coordinateInputType));
- int incoming = getNumber(menuTimeoutExtended); //Timeout after x seconds
+ systemPrintf("5) Set Antenna Height: %dmm\r\n", settings.antennaHeight);
- if (incoming == 1)
- {
- settings.fixedBase ^= 1;
+ systemPrintf("6) Set Antenna Reference Point: %0.1fmm\r\n", settings.antennaReferencePoint);
+ }
+ }
+ else
+ {
+ systemPrint("2) Set minimum observation time: ");
+ systemPrint(settings.observationSeconds);
+ systemPrintln(" seconds");
+
+ systemPrint("3) Set required Mean 3D Standard Deviation: ");
+ systemPrint(settings.observationPositionAccuracy, 3);
+ systemPrintln(" meters");
+
+ systemPrint("4) Set required initial positional accuracy before Survey-In: ");
+ systemPrint(settings.surveyInStartingAccuracy, 3);
+ systemPrintln(" meters");
+ }
+
+ systemPrint("7) Toggle NTRIP Server: ");
+ if (settings.enableNtripServer == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ if (settings.enableNtripServer == true)
+ {
+ systemPrintf("8) Select NTRIP server index: %d\r\n", serverIndex + 1);
+
+ systemPrintf("9) Set Caster Host / Address %d: ", serverIndex + 1);
+ systemPrintln(&settings.ntripServer_CasterHost[serverIndex][0]);
+
+ systemPrintf("10) Set Caster Port %d: ", serverIndex + 1);
+ systemPrintln(settings.ntripServer_CasterPort[serverIndex]);
+
+ systemPrintf("11) Set Caster User %d: ", serverIndex + 1);
+ systemPrintln(&settings.ntripServer_CasterUser[serverIndex][0]);
+
+ systemPrintf("12) Set Caster User PW %d: ", serverIndex + 1);
+ systemPrintln(settings.ntripServer_CasterUserPW[serverIndex]);
+
+ systemPrintf("13) Set Mountpoint %d: ", serverIndex + 1);
+ systemPrintln(&settings.ntripServer_MountPoint[serverIndex][0]);
+
+ systemPrintf("14) Set Mountpoint PW %d: ", serverIndex + 1);
+ systemPrintln(&settings.ntripServer_MountPointPW[serverIndex][0]);
+
+ systemPrint("15) Set RTCM Message Rates\r\n");
+
+ if (settings.fixedBase == false) // Survey-in
+ {
+ systemPrint("16) Select survey-in radio: ");
+ systemPrintf("%s\r\n", settings.ntripServer_StartAtSurveyIn ? "WiFi" : "Bluetooth");
+ }
+ }
+ else
+ {
+ systemPrintln("8) Set RTCM Message Rates");
+
+ if (settings.fixedBase == false) // Survey-in
+ {
+ systemPrint("9) Select survey-in radio: ");
+ systemPrintf("%s\r\n", settings.ntripServer_StartAtSurveyIn ? "WiFi" : "Bluetooth");
+ }
+ }
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ settings.fixedBase ^= 1;
+ restartBase = true;
+ }
+ else if (settings.fixedBase == true && incoming == 2)
+ {
+ settings.fixedBaseCoordinateType ^= 1;
+ restartBase = true;
+ }
+
+ else if (settings.fixedBase == true && incoming == 3)
+ {
+ if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
+ {
+ systemPrintln("Enter the fixed ECEF coordinates that will be used in Base mode:");
+
+ systemPrint("ECEF X in meters (ex: -1280182.9200): ");
+ double fixedEcefX = getDouble();
+
+ // Progress with additional prompts only if the user enters valid data
+ if (fixedEcefX != INPUT_RESPONSE_GETNUMBER_TIMEOUT && fixedEcefX != INPUT_RESPONSE_GETNUMBER_EXIT)
+ {
+ settings.fixedEcefX = fixedEcefX;
+
+ systemPrint("\nECEF Y in meters (ex: -4716808.5807): ");
+ double fixedEcefY = getDouble();
+ if (fixedEcefY != INPUT_RESPONSE_GETNUMBER_TIMEOUT && fixedEcefY != INPUT_RESPONSE_GETNUMBER_EXIT)
+ {
+ settings.fixedEcefY = fixedEcefY;
+
+ systemPrint("\nECEF Z in meters (ex: 4086669.6393): ");
+ double fixedEcefZ = getDouble();
+ if (fixedEcefZ != INPUT_RESPONSE_GETNUMBER_TIMEOUT &&
+ fixedEcefZ != INPUT_RESPONSE_GETNUMBER_EXIT)
+ settings.fixedEcefZ = fixedEcefZ;
+ }
+ }
+ }
+ else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC)
+ {
+ // Progress with additional prompts only if the user enters valid data
+ char userEntry[50];
+
+ systemPrintln("Enter the fixed Lat/Long/Altitude coordinates that will be used in Base mode:");
+
+ systemPrint("Latitude in degrees (ex: 40.090335429, 40 05.4201257, 40-05.4201257, 4005.4201257, 40 05 "
+ "25.207544, etc): ");
+ if (getString(userEntry, sizeof(userEntry)) == INPUT_RESPONSE_VALID)
+ {
+ double fixedLat = 0.0;
+
+ // Identify which type of method they used
+ CoordinateInputType latCoordinateInputType = coordinateIdentifyInputType(userEntry, &fixedLat);
+ if (latCoordinateInputType != COORDINATE_INPUT_TYPE_INVALID_UNKNOWN)
+ {
+ // Progress with additional prompts only if the user enters valid data
+ systemPrint("\r\nLongitude in degrees (ex: -105.184774720, -105 11.0864832, -105-11.0864832, "
+ "-105 11 05.188992, etc): ");
+ if (getString(userEntry, sizeof(userEntry)) == INPUT_RESPONSE_VALID)
+ {
+ double fixedLong = 0.0;
+
+ // Identify which type of method they used
+ CoordinateInputType longCoordinateInputType = coordinateIdentifyInputType(userEntry, &fixedLong);
+ if (longCoordinateInputType != COORDINATE_INPUT_TYPE_INVALID_UNKNOWN)
+ {
+ if (latCoordinateInputType == longCoordinateInputType)
+ {
+ settings.fixedLat = fixedLat;
+ settings.fixedLong = fixedLong;
+ settings.coordinateInputType = latCoordinateInputType;
+
+ systemPrint("\r\nAltitude in meters (ex: 1560.2284): ");
+ double fixedAltitude = getDouble();
+ if (fixedAltitude != INPUT_RESPONSE_GETNUMBER_TIMEOUT &&
+ fixedAltitude != INPUT_RESPONSE_GETNUMBER_EXIT)
+ settings.fixedAltitude = fixedAltitude;
+ }
+ else
+ {
+ systemPrintln("\r\nCoordinate types must match!");
+ }
+ } // idInput on fixedLong
+ } // getString for fixedLong
+ } // idInput on fixedLat
+ } // getString for fixedLat
+ } // COORD_TYPE_GEODETIC
+ } // Fixed base and '3'
+
+ else if (settings.fixedBase == true && settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC && incoming == 4)
+ {
+ menuBaseCoordinateType(); // Set coordinate display format
+ }
+ else if (settings.fixedBase == true && settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC && incoming == 5)
+ {
+ systemPrint("Enter the antenna height (a.k.a. pole length) in millimeters (-15000 to 15000mm): ");
+ int antennaHeight = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((antennaHeight != INPUT_RESPONSE_GETNUMBER_EXIT) && (antennaHeight != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (antennaHeight < -15000 || antennaHeight > 15000) // Arbitrary 15m max
+ systemPrintln("Error: Antenna Height out of range");
+ else
+ settings.antennaHeight = antennaHeight; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (settings.fixedBase == true && settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC && incoming == 6)
+ {
+ systemPrint("Enter the antenna reference point (a.k.a. ARP) in millimeters (-200.0 to 200.0mm). Common "
+ "antennas Facet=71.8mm Facet L-Band=69.0mm TOP106=52.9: ");
+ float antennaReferencePoint = getDouble();
+ if (antennaReferencePoint < -200.0 || antennaReferencePoint > 200.0) // Arbitrary 200mm max
+ systemPrintln("Error: Antenna Reference Point out of range");
+ else
+ settings.antennaReferencePoint = antennaReferencePoint; // Recorded to NVM and file at main menu exit
+ }
+
+ else if (settings.fixedBase == false && incoming == 2)
+ {
+ systemPrint("Enter the number of seconds for survey-in obseration time (60 to 600s): ");
+ int observationSeconds = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((observationSeconds != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (observationSeconds != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (observationSeconds < 60 || observationSeconds > 60 * 10) // Arbitrary 10 minute limit
+ systemPrintln("Error: Observation seconds out of range");
+ else
+ settings.observationSeconds = observationSeconds; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (settings.fixedBase == false && incoming == 3)
+ {
+ systemPrintf("Enter the number of meters for survey-in required position accuracy (1.0 to %.1fm): ", maxObservationPositionAccuracy);
+ float observationPositionAccuracy = getDouble();
+
+ if (observationPositionAccuracy < 1.0 ||
+ observationPositionAccuracy > maxObservationPositionAccuracy) // Arbitrary 1m minimum
+ systemPrintln("Error: Observation positional accuracy requirement out of range");
+ else
+ settings.observationPositionAccuracy =
+ observationPositionAccuracy; // Recorded to NVM and file at main menu exit
+ }
+ else if (settings.fixedBase == false && incoming == 4)
+ {
+ systemPrintf("Enter the positional accuracy required before Survey-In begins (0.1 to %.1fm): ", maxSurveyInStartingAccuracy);
+ float surveyInStartingAccuracy = getDouble();
+ if (surveyInStartingAccuracy < 0.1 ||
+ surveyInStartingAccuracy > maxSurveyInStartingAccuracy) // Arbitrary 0.1m minimum
+ systemPrintln("Error: Starting accuracy out of range");
+ else
+ settings.surveyInStartingAccuracy =
+ surveyInStartingAccuracy; // Recorded to NVM and file at main menu exit
+ }
+
+ else if (incoming == 7)
+ {
+ settings.enableNtripServer ^= 1;
+ restartBase = true;
+ }
+
+ else if ((incoming == 8) && settings.enableNtripServer == true)
+ {
+ serverIndex++;
+ if (serverIndex >= NTRIP_SERVER_MAX)
+ serverIndex = 0;
+ }
+ else if ((incoming == 9) && settings.enableNtripServer == true)
+ {
+ systemPrint("Enter new Caster Host / Address: ");
+ if (getString(&settings.ntripServer_CasterHost[serverIndex][0],
+ sizeof(settings.ntripServer_CasterHost[serverIndex])
+ == INPUT_RESPONSE_VALID))
+ restartBase = true;
+ }
+ else if ((incoming == 10) && settings.enableNtripServer == true)
+ {
+ systemPrint("Enter new Caster Port: ");
+
+ int ntripServer_CasterPort = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((ntripServer_CasterPort != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (ntripServer_CasterPort != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (ntripServer_CasterPort < 1 || ntripServer_CasterPort > 65535)
+ systemPrintln("Error: Caster port out of range");
+ else
+ settings.ntripServer_CasterPort[serverIndex] =
+ ntripServer_CasterPort; // Recorded to NVM and file at main menu exit
+ restartBase = true;
+ }
+ }
+ else if ((incoming == 11) && settings.enableNtripServer == true)
+ {
+ systemPrint("Enter new Caster Username: ");
+ if (getString(&settings.ntripServer_CasterUser[serverIndex][0],
+ sizeof(settings.ntripServer_CasterUser[serverIndex]))
+ == INPUT_RESPONSE_VALID)
+ restartBase = true;
+ }
+ else if ((incoming == 12) && settings.enableNtripServer == true)
+ {
+ systemPrintf("Enter password for Caster User %s: ", settings.ntripServer_CasterUser[serverIndex]);
+ if (getString(&settings.ntripServer_CasterUserPW[serverIndex][0],
+ sizeof(settings.ntripServer_CasterUserPW[serverIndex]))
+ == INPUT_RESPONSE_VALID)
+ restartBase = true;
+ }
+ else if ((incoming == 13) && settings.enableNtripServer == true)
+ {
+ systemPrint("Enter new Mount Point: ");
+ if (getString(&settings.ntripServer_MountPoint[serverIndex][0],
+ sizeof(settings.ntripServer_MountPoint[serverIndex]))
+ == INPUT_RESPONSE_VALID)
+ restartBase = true;
+ }
+ else if ((incoming == 14) && settings.enableNtripServer == true)
+ {
+ systemPrintf("Enter password for Mount Point %s: ", settings.ntripServer_MountPoint[serverIndex]);
+ if (getString(&settings.ntripServer_MountPointPW[serverIndex][0],
+ sizeof(settings.ntripServer_MountPointPW[serverIndex]))
+ == INPUT_RESPONSE_VALID)
+ restartBase = true;
+ }
+ else if (((settings.enableNtripServer == true) && ((incoming == 15))) ||
+ ((settings.enableNtripServer == false) && (incoming == 8)))
+ {
+ menuMessagesBaseRTCM(); // Set rates for RTCM during Base mode
+ }
+ else if (((settings.enableNtripServer == true) && (settings.fixedBase == false) && ((incoming == 16))) ||
+ ((settings.enableNtripServer == false) && (settings.fixedBase == false) && (incoming == 9)))
+ {
+ settings.ntripServer_StartAtSurveyIn ^= 1;
+ restartBase = true;
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (settings.fixedBase == true && incoming == 2)
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Set coordinate display format
+void menuBaseCoordinateType()
+{
+ while (1)
{
- settings.fixedBaseCoordinateType ^= 1;
+ systemPrintln();
+ systemPrintln("Menu: Coordinate Display Type");
+
+ systemPrintln("The coordinate type is autodetected during entry but can be changed here.");
+
+ systemPrint("Current display format: ");
+ systemPrintln(coordinatePrintableInputType(settings.coordinateInputType));
+
+ for (int x = 0; x < COORDINATE_INPUT_TYPE_INVALID_UNKNOWN; x++)
+ systemPrintf("%d) %s\r\n", x + 1, coordinatePrintableInputType((CoordinateInputType)x));
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming >= 1 && incoming < (COORDINATE_INPUT_TYPE_INVALID_UNKNOWN + 1))
+ {
+ settings.coordinateInputType = (CoordinateInputType)(incoming - 1); // Align from 1-9 to 0-8
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (settings.fixedBase == true && incoming == 3)
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Configure ESF settings
+void menuSensorFusion()
+{
+ while (1)
{
- if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF)
- {
- Serial.println(F("Enter the fixed ECEF coordinates that will be used in Base mode:"));
+ systemPrintln();
+ systemPrintln("Menu: Sensor Fusion");
+
+ if (settings.enableSensorFusion == true)
+ {
+ // packetUBXESFSTATUS is sent automatically by the module
+ systemPrint("Fusion Mode: ");
+ systemPrint(theGNSS.packetUBXESFSTATUS->data.fusionMode);
+ systemPrint(" - ");
+ if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 0)
+ systemPrint("Initializing");
+ else if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 1)
+ systemPrint("Calibrated");
+ else if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 2)
+ systemPrint("Suspended");
+ else if (theGNSS.packetUBXESFSTATUS->data.fusionMode == 3)
+ systemPrint("Disabled");
+ systemPrintln();
+
+ if (theGNSS.getEsfAlignment()) // Poll new ESF ALG data
+ {
+ systemPrint("Alignment Mode: ");
+ systemPrint(theGNSS.packetUBXESFALG->data.flags.bits.status);
+ systemPrint(" - ");
+ if (theGNSS.packetUBXESFALG->data.flags.bits.status == 0)
+ systemPrint("User Defined");
+ else if (theGNSS.packetUBXESFALG->data.flags.bits.status == 1)
+ systemPrint("Alignment Roll/Pitch Ongoing");
+ else if (theGNSS.packetUBXESFALG->data.flags.bits.status == 2)
+ systemPrint("Alignment Roll/Pitch/Yaw Ongoing");
+ else if (theGNSS.packetUBXESFALG->data.flags.bits.status == 3)
+ systemPrint("Coarse Alignment Used");
+ else if (theGNSS.packetUBXESFALG->data.flags.bits.status == 3)
+ systemPrint("Fine Alignment Used");
+ systemPrintln();
+ }
+
+ if (settings.dynamicModel != DYN_MODEL_AUTOMOTIVE)
+ systemPrintln("Warning: Dynamic Model not set to Automotive. Sensor Fusion is best used with the "
+ "Automotive Dynamic Model.");
+ }
- Serial.print(F("ECEF X in meters (ex: -1280182.920): "));
- double fixedEcefX = getDouble(menuTimeoutExtended); //Timeout after x seconds
+ systemPrintf("1) Toggle Sensor Fusion: %s\r\n", settings.enableSensorFusion ? "Enabled" : "Disabled");
- //Progress with additional prompts only if the user enters valid data
- if (fixedEcefX != STATUS_GETNUMBER_TIMEOUT && fixedEcefX != STATUS_PRESSED_X)
+ if (settings.enableSensorFusion == true)
{
- settings.fixedEcefX = fixedEcefX;
+ if (settings.autoIMUmountAlignment == true)
+ {
+ systemPrintf("2) Toggle Automatic IMU-mount Alignment: True - Yaw: %0.2f Pitch: %0.2f Roll: %0.2f\r\n",
+ theGNSS.getESFyaw(), theGNSS.getESFpitch(), theGNSS.getESFroll());
+
+ systemPrintf("3) Disable automatic wheel tick direction pin polarity detection: %s\r\n",
+ settings.sfDisableWheelDirection ? "True" : "False");
+
+ systemPrintf("4) Use combined rear wheel ticks instead of the single tick: %s\r\n",
+ settings.sfCombineWheelTicks ? "True" : "False");
+
+ systemPrintf("5) Output rate of priority nav mode message: %d\r\n", settings.rateNavPrio);
+
+ systemPrintf("6) Use speed measurements instead of single ticks: %s\r\n",
+ settings.sfUseSpeed ? "True" : "False");
+ }
+ else
+ {
+ systemPrintf("2) Toggle Automatic IMU-mount Alignment: False\r\n");
+
+ systemPrintf("3) Manually set yaw: %0.2f\r\n", settings.imuYaw / 100.0);
+
+ systemPrintf("4) Manually set pitch: %0.2f\r\n", settings.imuPitch / 100.0);
- Serial.print(F("\nECEF Y in meters (ex: -4716808.5807): "));
- double fixedEcefY = getDouble(menuTimeoutExtended);
- if (fixedEcefY != STATUS_GETNUMBER_TIMEOUT && fixedEcefY != STATUS_PRESSED_X)
- {
- settings.fixedEcefY = fixedEcefY;
+ systemPrintf("5) Manually set roll: %0.2f\r\n", settings.imuRoll / 100.0);
- Serial.print(F("\nECEF Z in meters (ex: 4086669.6393): "));
- double fixedEcefZ = getDouble(menuTimeoutExtended);
- if (fixedEcefZ != STATUS_GETNUMBER_TIMEOUT && fixedEcefZ != STATUS_PRESSED_X)
- settings.fixedEcefZ = fixedEcefZ;
- }
+ systemPrintf("6) Disable automatic wheel tick direction pin polarity detection: %s\r\n",
+ settings.sfDisableWheelDirection ? "True" : "False");
+
+ systemPrintf("7) Use combined rear wheel ticks instead of the single tick: %s\r\n",
+ settings.sfCombineWheelTicks ? "True" : "False");
+
+ systemPrintf("8) Output rate of priority nav mode message: %d\r\n", settings.rateNavPrio);
+
+ systemPrintf("9) Use speed measurements instead of single ticks: %s\r\n",
+ settings.sfUseSpeed ? "True" : "False");
+
+ // CFG-SFIMU-IMU_MNTALG_YAW
+ // CFG-SFIMU-IMU_MNTALG_PITCH
+ // CFG-SFIMU-IMU_MNTALG_ROLL
+ // CFG-SFODO-DIS_AUTODIRPINPOL
+ // CFG-SFODO-COMBINE_TICKS
+
+ // CFG-RATE-NAV_PRIO
+ // CFG-NAV2-OUT_ENABLED
+ // CFG-SFODO-USE_SPEED
+ }
}
- }
- else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEOGRAPHIC)
- {
- Serial.println(F("Enter the fixed Lat/Long/Altitude coordinates that will be used in Base mode:"));
- Serial.print(F("Lat in degrees (ex: 40.090335429): "));
- double fixedLat = getDouble(menuTimeoutExtended); //Timeout after x seconds
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ settings.enableSensorFusion ^= 1;
+ setSensorFusion(settings.enableSensorFusion); // Enable/disable sensor fusion
+ }
+ else if (settings.enableSensorFusion == true && incoming == 2)
+ {
+ settings.autoIMUmountAlignment ^= 1;
+ }
+ else if (settings.enableSensorFusion == true && ((settings.autoIMUmountAlignment == true && incoming == 3) ||
+ (settings.autoIMUmountAlignment == false && incoming == 6)))
+ {
+ settings.sfDisableWheelDirection ^= 1;
+ }
+ else if (settings.enableSensorFusion == true && ((settings.autoIMUmountAlignment == true && incoming == 4) ||
+ (settings.autoIMUmountAlignment == false && incoming == 7)))
+ {
+ settings.sfCombineWheelTicks ^= 1;
+ }
- //Progress with additional prompts only if the user enters valid data
- if (fixedLat != STATUS_GETNUMBER_TIMEOUT && fixedLat != STATUS_PRESSED_X)
+ else if (settings.enableSensorFusion == true && settings.autoIMUmountAlignment == false && incoming == 3)
{
- settings.fixedLat = fixedLat;
+ systemPrint("Enter yaw alignment in degrees (0.00 to 360.00): ");
+ double yaw = getDouble();
+ if (yaw < 0.00 || yaw > 360.00) // 0 to 36,000
+ {
+ systemPrintln("Error: Yaw out of range");
+ }
+ else
+ {
+ settings.imuYaw = yaw * 100; // 56.44 to 5644
+ }
+ }
+ else if (settings.enableSensorFusion == true && settings.autoIMUmountAlignment == false && incoming == 4)
+ {
+ systemPrint("Enter pitch alignment in degrees (-90.00 to 90.00): ");
+ double pitch = getDouble();
+ if (pitch < -90.00 || pitch > 90.00) //-9000 to 9000
+ {
+ systemPrintln("Error: Pitch out of range");
+ }
+ else
+ {
+ settings.imuPitch = pitch * 100; // 56.44 to 5644
+ }
+ }
+ else if (settings.enableSensorFusion == true && settings.autoIMUmountAlignment == false && incoming == 5)
+ {
+ systemPrint("Enter roll alignment in degrees (-180.00 to 180.00): ");
+ double roll = getDouble();
+ if (roll < -180.00 || roll > 180.0) //-18000 to 18000
+ {
+ systemPrintln("Error: Roll out of range");
+ }
+ else
+ {
+ settings.imuRoll = roll * 100; // 56.44 to 5644
+ }
+ }
- Serial.print(F("\nLong in degrees (ex: -105.184774720): "));
- double fixedLong = getDouble(menuTimeoutExtended);
- if (fixedLong != STATUS_GETNUMBER_TIMEOUT && fixedLong != STATUS_PRESSED_X)
- {
- settings.fixedLong = fixedLong;
+ else if (settings.enableSensorFusion == true && ((settings.autoIMUmountAlignment == true && incoming == 5) ||
+ (settings.autoIMUmountAlignment == false && incoming == 8)))
+ {
+ systemPrint("Enter the output rate of priority nav mode message (0 to 30Hz): "); // TODO check maximum
+ int rate = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((rate != INPUT_RESPONSE_GETNUMBER_EXIT) && (rate != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (rate < 0 || rate > 30)
+ systemPrintln("Error: Output rate out of range");
+ else
+ settings.rateNavPrio = rate;
+ }
+ }
- Serial.print(F("\nAltitude in meters (ex: 1560.2284): "));
- double fixedAltitude = getDouble(menuTimeoutExtended);
- if (fixedAltitude != STATUS_GETNUMBER_TIMEOUT && fixedAltitude != STATUS_PRESSED_X)
- settings.fixedAltitude = fixedAltitude;
- }
+ else if (settings.enableSensorFusion == true && ((settings.autoIMUmountAlignment == true && incoming == 6) ||
+ (settings.autoIMUmountAlignment == false && incoming == 9)))
+ {
+ settings.sfUseSpeed ^= 1;
}
- }
+
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (settings.fixedBase == false && incoming == 2)
+
+ theGNSS.setVal8(UBLOX_CFG_SFCORE_USE_SF, settings.enableSensorFusion); // Enable/disable sensor fusion
+ theGNSS.setVal8(UBLOX_CFG_SFIMU_AUTO_MNTALG_ENA,
+ settings.autoIMUmountAlignment); // Enable/disable Automatic IMU-mount Alignment
+ theGNSS.setVal8(UBLOX_CFG_SFIMU_IMU_MNTALG_YAW, settings.imuYaw);
+ theGNSS.setVal8(UBLOX_CFG_SFIMU_IMU_MNTALG_PITCH, settings.imuPitch);
+ theGNSS.setVal8(UBLOX_CFG_SFIMU_IMU_MNTALG_ROLL, settings.imuRoll);
+ theGNSS.setVal8(UBLOX_CFG_SFODO_DIS_AUTODIRPINPOL, settings.sfDisableWheelDirection);
+ theGNSS.setVal8(UBLOX_CFG_SFODO_COMBINE_TICKS, settings.sfCombineWheelTicks);
+ theGNSS.setVal8(UBLOX_CFG_RATE_NAV_PRIO, settings.rateNavPrio);
+ theGNSS.setVal8(UBLOX_CFG_SFODO_USE_SPEED, settings.sfUseSpeed);
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+//----------------------------------------
+// Support functions
+//----------------------------------------
+
+// Enable or disable sensor fusion using keys
+void setSensorFusion(bool enable)
+{
+ if (getSensorFusion() != enable)
+ theGNSS.setVal8(UBLOX_CFG_SFCORE_USE_SF, enable, VAL_LAYER_ALL);
+}
+
+bool getSensorFusion()
+{
+ return (theGNSS.getVal8(UBLOX_CFG_SFCORE_USE_SF, VAL_LAYER_RAM, 1200));
+}
+
+// Open the given file and load a given line to the given pointer
+bool getFileLineLFS(const char *fileName, int lineToFind, char *lineData, int lineDataLength)
+{
+ File file = LittleFS.open(fileName, FILE_READ);
+ if (!file)
{
- Serial.print(F("Enter the number of seconds for survey-in obseration time (60 to 600s): "));
- int observationSeconds = getNumber(menuTimeout); //Timeout after x seconds
- if (observationSeconds < 60 || observationSeconds > 60 * 10) //Arbitrary 10 minute limit
- {
- Serial.println(F("Error: observation seconds out of range"));
- }
- else
- {
- settings.observationSeconds = observationSeconds; //Recorded to NVM and file at main menu exit
- }
+ log_d("File %s not found", fileName);
+ return (false);
}
- else if (settings.fixedBase == false && incoming == 3)
+
+ // We cannot be sure how the user will terminate their files so we avoid the use of readStringUntil
+ int lineNumber = 0;
+ int x = 0;
+ bool lineFound = false;
+
+ while (file.available())
{
- Serial.print(F("Enter the number of meters for survey-in required position accuracy (1.0 to 5.0m): "));
- float observationPositionAccuracy = getDouble(menuTimeout); //Timeout after x seconds
- if (observationPositionAccuracy < 1.0 || observationPositionAccuracy > 5.0) //Arbitrary 1m minimum
- {
- Serial.println(F("Error: observation positional accuracy requirement out of range"));
- }
- else
- {
- settings.observationPositionAccuracy = observationPositionAccuracy; //Recorded to NVM and file at main menu exit
- }
+ byte incoming = file.read();
+ if (incoming == '\r' || incoming == '\n')
+ {
+ lineData[x] = '\0'; // Terminate
+
+ if (lineNumber == lineToFind)
+ {
+ lineFound = true; // We found the line. We're done!
+ break;
+ }
+
+ // Sometimes a line has multiple terminators
+ while (file.peek() == '\r' || file.peek() == '\n')
+ file.read(); // Dump it to prevent next line read corruption
+
+ lineNumber++; // Advance
+ x = 0; // Reset
+ }
+ else
+ {
+ if (x == (lineDataLength - 1))
+ {
+ lineData[x] = '\0'; // Terminate
+ break; // Max size hit
+ }
+
+ // Record this character to the lineData array
+ lineData[x++] = incoming;
+ }
}
- else if (incoming == 4)
+ file.close();
+ return (lineFound);
+}
+
+// Given a fileName, return the given line number
+// Returns true if line was loaded
+// Returns false if a file was not opened/loaded
+bool getFileLineSD(const char *fileName, int lineToFind, char *lineData, int lineDataLength)
+{
+ bool gotSemaphore = false;
+ bool lineFound = false;
+ bool wasSdCardOnline;
+
+ // Try to gain access the SD card
+ wasSdCardOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ while (online.microSD == true)
{
- settings.enableNtripServer ^= 1;
- }
- else if (incoming == 5 && settings.enableNtripServer == true)
+ // Attempt to access file system. This avoids collisions with file writing from other functions like
+ // recordSystemSettingsToFile() and F9PSerialReadTask()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_GETLINE);
+
+ gotSemaphore = true;
+
+ if (USE_SPI_MICROSD)
+ {
+ SdFile file; // FAT32
+ if (file.open(fileName, O_READ) == false)
+ {
+ log_d("File %s not found", fileName);
+ break;
+ }
+
+ int lineNumber = 0;
+
+ while (file.available())
+ {
+ // Get the next line from the file
+ int n = file.fgets(lineData, lineDataLength);
+ if (n <= 0)
+ {
+ systemPrintf("Failed to read line %d from settings file\r\n", lineNumber);
+ break;
+ }
+ else
+ {
+ if (lineNumber == lineToFind)
+ {
+ lineFound = true;
+ break;
+ }
+ }
+
+ if (strlen(lineData) > 0) // Ignore single \n or \r
+ lineNumber++;
+ }
+
+ file.close();
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ File file = SD_MMC.open(fileName, FILE_READ);
+
+ if (!file)
+ {
+ log_d("File %s not found", fileName);
+ break;
+ }
+
+ int lineNumber = 0;
+
+ while (file.available())
+ {
+ // Get the next line from the file
+ int n = getLine(&file, lineData, lineDataLength);
+ if (n <= 0)
+ {
+ systemPrintf("Failed to read line %d from settings file\r\n", lineNumber);
+ break;
+ }
+ else
+ {
+ if (lineNumber == lineToFind)
+ {
+ lineFound = true;
+ break;
+ }
+ }
+
+ if (strlen(lineData) > 0) // Ignore single \n or \r
+ lineNumber++;
+ }
+
+ file.close();
+ }
+#endif // COMPILE_SD_MMC
+ break;
+ } // End Semaphore check
+ else
+ {
+ systemPrintf("sdCardSemaphore failed to yield, menuBase.ino line %d\r\n", __LINE__);
+ }
+ break;
+ } // End SD online
+
+ // Release access the SD card
+ if (online.microSD && (!wasSdCardOnline))
+ endSD(gotSemaphore, true);
+ else if (gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
+
+ return (lineFound);
+}
+
+// Given a string, replace a single char with another char
+void replaceCharacter(char *myString, char toReplace, char replaceWith)
+{
+ for (int i = 0; i < strlen(myString); i++)
{
- Serial.print(F("Enter local WiFi SSID: "));
- readLine(settings.wifiSSID, sizeof(settings.wifiSSID), menuTimeoutExtended);
+ if (myString[i] == toReplace)
+ myString[i] = replaceWith;
}
- else if (incoming == 6 && settings.enableNtripServer == true)
+}
+
+// Remove a given filename from SD
+bool removeFileSD(const char *fileName)
+{
+ bool removed = false;
+
+ bool gotSemaphore = false;
+ bool wasSdCardOnline;
+
+ // Try to gain access the SD card
+ wasSdCardOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ while (online.microSD == true)
{
- Serial.printf("Enter password for WiFi network %s: ", settings.wifiSSID);
- readLine(settings.wifiPW, sizeof(settings.wifiPW), menuTimeoutExtended);
- }
- else if (incoming == 7 && settings.enableNtripServer == true)
+ // Attempt to access file system. This avoids collisions with file writing from other functions like
+ // recordSystemSettingsToFile() and F9PSerialReadTask()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_REMOVEFILE);
+
+ gotSemaphore = true;
+
+ if (USE_SPI_MICROSD)
+ {
+ if (sd->exists(fileName))
+ {
+ log_d("Removing from SD: %s", fileName);
+ sd->remove(fileName);
+ removed = true;
+ }
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (SD_MMC.exists(fileName))
+ {
+ log_d("Removing from SD: %s", fileName);
+ SD_MMC.remove(fileName);
+ removed = true;
+ }
+ }
+#endif // COMPILE_SD_MMC
+
+ break;
+ } // End Semaphore check
+ else
+ {
+ systemPrintf("sdCardSemaphore failed to yield, menuBase.ino line %d\r\n", __LINE__);
+ }
+ break;
+ } // End SD online
+
+ // Release access the SD card
+ if (online.microSD && (!wasSdCardOnline))
+ endSD(gotSemaphore, true);
+ else if (gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
+
+ return (removed);
+}
+
+// Remove a given filename from LFS
+bool removeFileLFS(const char *fileName)
+{
+ if (LittleFS.exists(fileName))
{
- Serial.print(F("Enter new Caster Address: "));
- readLine(settings.casterHost, sizeof(settings.casterHost), menuTimeoutExtended);
+ LittleFS.remove(fileName);
+ log_d("Removing LittleFS: %s", fileName);
+ return (true);
}
- else if (incoming == 8 && settings.enableNtripServer == true)
- {
- Serial.print(F("Enter new Caster Port: "));
- int casterPort = getNumber(menuTimeoutExtended); //Timeout after x seconds
- if (casterPort < 1 || casterPort > 99999) //Arbitrary 99k max port #
- Serial.println(F("Error: Caster Port out of range"));
- else
- settings.casterPort = casterPort; //Recorded to NVM and file at main menu exit
- }
- else if (incoming == 9 && settings.enableNtripServer == true)
+ return (false);
+}
+
+// Remove a given filename from SD and LFS
+bool removeFile(const char *fileName)
+{
+ bool removed = true;
+
+ removed &= removeFileSD(fileName);
+ removed &= removeFileLFS(fileName);
+
+ return (removed);
+}
+
+// Given a filename and char array, append to file
+void recordLineToSD(const char *fileName, const char *lineData)
+{
+ bool gotSemaphore = false;
+ bool wasSdCardOnline;
+
+ // Try to gain access the SD card
+ wasSdCardOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ while (online.microSD == true)
{
- Serial.print(F("Enter new Mount Point: "));
- readLine(settings.mountPoint, sizeof(settings.mountPoint), menuTimeoutExtended);
- }
- else if (incoming == 10 && settings.enableNtripServer == true)
+ // Attempt to access file system. This avoids collisions with file writing from other functions like
+ // recordSystemSettingsToFile() and F9PSerialReadTask()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_RECORDLINE);
+
+ gotSemaphore = true;
+
+ FileSdFatMMC file;
+ if (!file)
+ {
+ systemPrintln("ERROR - Failed to allocate file");
+ break;
+ }
+ if (file.open(fileName, O_CREAT | O_APPEND | O_WRITE) == false)
+ {
+ log_d("File %s not found", fileName);
+ break;
+ }
+
+ file.println(lineData);
+ file.close();
+ break;
+ } // End Semaphore check
+ else
+ {
+ systemPrintf("sdCardSemaphore failed to yield, menuBase.ino line %d\r\n", __LINE__);
+ }
+ break;
+ } // End SD online
+
+ // Release access the SD card
+ if (online.microSD && (!wasSdCardOnline))
+ endSD(gotSemaphore, true);
+ else if (gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
+}
+
+// Given a filename and char array, append to file
+void recordLineToLFS(const char *fileName, const char *lineData)
+{
+ File file = LittleFS.open(fileName, FILE_APPEND);
+ if (!file)
{
- Serial.printf("Enter password for Mount Point %s: ", settings.mountPoint);
- readLine(settings.mountPointPW, sizeof(settings.mountPointPW), menuTimeoutExtended);
+ systemPrintf("File %s failed to create\r\n", fileName);
+ return;
}
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
-
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+
+ file.println(lineData);
+ file.close();
+}
+
+// Remove ' ', \t, \v, \f, \r, \n from end of a char array
+void trim(char *str)
+{
+ int x = 0;
+ for (; str[x] != '\0'; x++)
+ ;
+ if (x > 0)
+ x--;
+
+ for (; isspace(str[x]); x--)
+ str[x] = '\0';
}
diff --git a/Firmware/RTK_Surveyor/menuDebug.ino b/Firmware/RTK_Surveyor/menuDebug.ino
deleted file mode 100644
index b1645bee4..000000000
--- a/Firmware/RTK_Surveyor/menuDebug.ino
+++ /dev/null
@@ -1,201 +0,0 @@
-//Toggle control of heap reports and I2C GNSS debug
-void menuDebug()
-{
- int maxWait = 2000;
-
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Debug Menu"));
-
- //Check the firmware version of the ZED-F9P. Based on Example21_ModuleInfo.
- if (i2cGNSS.getModuleInfo(maxWait) == true) // Try to get the module info
- {
- Serial.print(F("ZED-F9P firmware: "));
- Serial.println(i2cGNSS.minfo.extension[1]);
- }
-
- Serial.print(F("1) I2C Debugging Output: "));
- if (settings.enableI2Cdebug == true) Serial.println(F("Enabled"));
- else Serial.println(F("Disabled"));
-
- Serial.print(F("2) Heap Reporting: "));
- if (settings.enableHeapReport == true) Serial.println(F("Enabled"));
- else Serial.println(F("Disabled"));
-
- Serial.print(F("3) Task Highwater Reporting: "));
- if (settings.enableTaskReports == true) Serial.println(F("Enabled"));
- else Serial.println(F("Disabled"));
-
- Serial.print(F("4) Set SPI/SD Interface Frequency: "));
- Serial.print(settings.spiFrequency);
- Serial.println(" MHz");
-
- Serial.print(F("5) Set SPP RX Buffer Size: "));
- Serial.println(settings.sppRxQueueSize);
-
- Serial.print(F("6) Set SPP TX Buffer Size: "));
- Serial.println(settings.sppTxQueueSize);
-
- Serial.print(F("7) Throttle BT Transmissions During SPP Congestion: "));
- if (settings.throttleDuringSPPCongestion == true) Serial.println(F("Enabled"));
- else Serial.println(F("Disabled"));
-
- Serial.println(F("x) Exit"));
-
- byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds
-
- if (incoming == '1')
- {
- settings.enableI2Cdebug ^= 1;
-
- if (settings.enableI2Cdebug)
- i2cGNSS.enableDebugging(Serial, true); //Enable only the critical debug messages over Serial
- else
- i2cGNSS.disableDebugging();
- }
- else if (incoming == '2')
- {
- settings.enableHeapReport ^= 1;
- }
- else if (incoming == '3')
- {
- settings.enableTaskReports ^= 1;
- }
- else if (incoming == '4')
- {
- Serial.print(F("Enter SPI frequency in MHz (1 to 48): "));
- int freq = getNumber(menuTimeout); //Timeout after x seconds
- if (freq < 1 || freq > 48) //Arbitrary 48 hour limit
- {
- Serial.println(F("Error: SPI frequency out of range"));
- }
- else
- {
- settings.spiFrequency = freq; //Recorded to NVM and file at main menu exit
- }
- }
- else if (incoming == '5')
- {
- Serial.print(F("Enter SPP RX Queue Size in Bytes (32 to 16384): "));
- uint16_t queSize = getNumber(menuTimeout); //Timeout after x seconds
- if (queSize < 32 || queSize > 16384) //Arbitrary 16k limit
- {
- Serial.println(F("Error: Queue size out of range"));
- }
- else
- {
- settings.sppRxQueueSize = queSize; //Recorded to NVM and file at main menu exit
- }
- }
- else if (incoming == '6')
- {
- Serial.print(F("Enter SPP TX Queue Size in Bytes (32 to 16384): "));
- uint16_t queSize = getNumber(menuTimeout); //Timeout after x seconds
- if (queSize < 32 || queSize > 16384) //Arbitrary 16k limit
- {
- Serial.println(F("Error: Queue size out of range"));
- }
- else
- {
- settings.sppTxQueueSize = queSize; //Recorded to NVM and file at main menu exit
- }
- }
- else if (incoming == '7')
- {
- settings.throttleDuringSPPCongestion ^= 1;
- }
- else if (incoming == 'x')
- break;
- else if (incoming == STATUS_GETBYTE_TIMEOUT)
- {
- break;
- }
- else
- printUnknown(incoming);
- }
-
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
-}
-
-//Globals but only used for menuBubble
-double averagedRoll = 0.0;
-double averagedPitch = 0.0;
-
-//A bubble level
-void menuBubble()
-{
- Serial.println();
- Serial.println(F("Menu: Bubble Level"));
-
- Serial.print(F("Press any key to exit"));
-
- delay(10);
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
-
- while (1)
- {
- if (Serial.available()) break;
-
- getAngles();
-
- if (online.display == true)
- {
- oled.clear(PAGE); // Clear the display's internal memory
-
- //Draw dot in middle
- oled.pixel(LCDWIDTH / 2, LCDHEIGHT / 2);
- oled.pixel(LCDWIDTH / 2 + 1, LCDHEIGHT / 2);
- oled.pixel(LCDWIDTH / 2, LCDHEIGHT / 2 + 1);
- oled.pixel(LCDWIDTH / 2 + 1, LCDHEIGHT / 2 + 1);
-
- //Draw circle relative to dot
- const int radiusLarge = 10;
- const int radiusSmall = 4;
-
- oled.circle(LCDWIDTH / 2 - averagedPitch, LCDHEIGHT / 2 + averagedRoll, radiusLarge);
- oled.circle(LCDWIDTH / 2 - averagedPitch, LCDHEIGHT / 2 + averagedRoll, radiusSmall);
-
- oled.display();
- }
- }
-
- displaySerialConfig(); //Display 'Serial Config' while user is configuring
-
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
-}
-
-void getAngles()
-{
- if (online.accelerometer == true)
- {
- averagedRoll = 0.0;
- averagedPitch = 0.0;
- const int avgAmount = 16;
-
- //Take an average readings
- for (int reading = 0 ; reading < avgAmount ; reading++)
- {
- while (accel.available() == false) delay(1);
-
- float accelX = accel.getX();
- float accelZ = accel.getY();
- float accelY = accel.getZ();
- accelZ *= -1.0;
- accelX *= -1.0;
-
- double roll = atan2(accelY , accelZ) * 57.3;
- double pitch = atan2((-accelX) , sqrt(accelY * accelY + accelZ * accelZ)) * 57.3;
-
- averagedRoll += roll;
- averagedPitch += pitch;
- }
-
- averagedRoll /= (float)avgAmount;
- averagedPitch /= (float)avgAmount;
-
- //Avoid -0 since we're not printing the decimal portion
- if (averagedRoll < 0.5 && averagedRoll > -0.5) averagedRoll = 0;
- if (averagedPitch < 0.5 && averagedPitch > -0.5) averagedPitch = 0;
- }
-}
diff --git a/Firmware/RTK_Surveyor/menuFirmware.ino b/Firmware/RTK_Surveyor/menuFirmware.ino
index 7d8fefa54..3e4e7a5da 100644
--- a/Firmware/RTK_Surveyor/menuFirmware.ino
+++ b/Firmware/RTK_Surveyor/menuFirmware.ino
@@ -1,223 +1,851 @@
-//Update firmware if bin files found
+/*------------------------------------------------------------------------------
+menuFirmware.ino
+
+ This module implements the firmware menu and update code.
+------------------------------------------------------------------------------*/
+
+//----------------------------------------
+// Menu
+//----------------------------------------
+
+// Update firmware if bin files found
void menuFirmware()
{
- if (online.microSD == false)
- {
- Serial.println(F("No SD card detected"));
- }
+ bool newOTAFirmwareAvailable = false;
+ char reportedVersion[50] = {'\0'};
- if (binCount == 0)
- {
- Serial.println(F("No valid binary files found."));
- delay(2000);
- return;
- }
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Update Firmware");
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Update Firmware Menu"));
+ if (btPrintEcho == true)
+ systemPrintln("Firmware update not available while configuration over Bluetooth is active");
- for (int x = 0 ; x < binCount ; x++)
- {
- Serial.printf("%d) Load %s\n\r", x + 1, binFileNames[x]);
+ char currentVersion[21];
+ getFirmwareVersion(currentVersion, sizeof(currentVersion), enableRCFirmware);
+ systemPrintf("Current firmware: %s\r\n", currentVersion);
+
+ // Automatic firmware updates
+ systemPrintf("a) Automatic firmware updates: %s\r\n",
+ settings.enableAutoFirmwareUpdate ? "Enabled" : "Disabled");
+
+ if (strlen(reportedVersion) > 0)
+ {
+ if (newOTAFirmwareAvailable == false)
+ systemPrintf("c) Check SparkFun for device firmware: Up to date\r\n");
+ }
+ else
+ systemPrintln("c) Check SparkFun for device firmware");
+
+ systemPrintf("e) Allow Beta Firmware: %s\r\n", enableRCFirmware ? "Enabled" : "Disabled");
+
+ if (settings.enableAutoFirmwareUpdate)
+ systemPrintf("i) Automatic firmware check minutes: %d\r\n", settings.autoFirmwareCheckMinutes);
+
+ if (newOTAFirmwareAvailable)
+ systemPrintf("u) Update to new firmware: v%s\r\n", reportedVersion);
+
+ for (int x = 0; x < binCount; x++)
+ systemPrintf("%d) Load SD file: %s\r\n", x + 1, binFileNames[x]);
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming > 0 && incoming < (binCount + 1))
+ {
+ // Adjust incoming to match array
+ incoming--;
+ updateFromSD(binFileNames[incoming]);
+ }
+
+ else if (incoming == 'a')
+ settings.enableAutoFirmwareUpdate ^= 1;
+
+ else if (incoming == 'c' && btPrintEcho == false)
+ {
+ if (wifiNetworkCount() == 0)
+ {
+ systemPrintln("Error: Please enter at least one SSID before updating firmware");
+ }
+ else
+ {
+ bool previouslyConnected = wifiIsConnected();
+
+ bluetoothStop(); // Stop Bluetooth to allow for SSL on the heap
+
+ // Attempt to connect to local WiFi
+ if (wifiConnect(10000) == true)
+ {
+ // Get firmware version from server
+ 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);
+
+ // Allow update if locally compiled developer version
+ if (isReportedVersionNewer(reportedVersion, ¤tVersion[1]) == true ||
+ FIRMWARE_VERSION_MAJOR == 99)
+ {
+ systemPrintln("New version detected");
+ newOTAFirmwareAvailable = true;
+ }
+ else
+ {
+ systemPrintln("No new firmware available");
+ }
+ }
+ else
+ {
+ // Failed to get version number
+ systemPrintln("Failed to get version number from server.");
+ }
+ }
+ else if (incoming == 'c' && btPrintEcho == false)
+ {
+ bool previouslyConnected = wifiIsConnected();
+
+ bool bluetoothOriginallyConnected = false;
+ if (bluetoothState == BT_CONNECTED)
+ bluetoothOriginallyConnected = true;
+
+ bluetoothStop(); // Stop Bluetooth to allow for SSL on the heap
+
+ // Attempt to connect to local WiFi
+ if (wifiConnect(10000) == true)
+ {
+ // Get firmware version from server
+ 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, ¤tVersion[1]) == true)
+ {
+ systemPrintln("New version detected");
+ newOTAFirmwareAvailable = true;
+ }
+ else
+ {
+ systemPrintln("No new firmware available");
+ }
+ }
+ else
+ {
+ // Failed to get version number
+ systemPrintln("Failed to get version number from server.");
+ }
+ }
+ else
+ systemPrintln("Firmware update failed to connect to WiFi.");
+
+ if (previouslyConnected == false)
+ WIFI_STOP();
+
+ if (bluetoothOriginallyConnected == true)
+ bluetoothStart(); // Restart BT according to settings
+ }
+ } // End wifiNetworkCount() check
+ }
+ else if (incoming == 'c' && btPrintEcho == true)
+ {
+ systemPrintln("Firmware update not available while configuration over Bluetooth is active");
+ delay(2000);
+ }
+
+ else if (incoming == 'e')
+ {
+ enableRCFirmware ^= 1;
+ strncpy(reportedVersion, "", sizeof(reportedVersion) - 1); // Reset to force c) menu
+ newOTAFirmwareAvailable = false;
+ }
+
+ else if ((incoming == 'i') && settings.enableAutoFirmwareUpdate)
+ {
+ systemPrint("Enter minutes (1 - 999999) before next firmware check: ");
+ int minutes = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((minutes != INPUT_RESPONSE_GETNUMBER_EXIT) && (minutes != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if ((minutes < 1) || (minutes > 999999))
+ systemPrintln("Error: Out of range (1 - 999999)");
+ else
+ settings.autoFirmwareCheckMinutes = minutes;
+ }
+ }
+
+ else if ((incoming == 'u') && newOTAFirmwareAvailable)
+ {
+ bool previouslyConnected = wifiIsConnected();
+
+ otaUpdate();
+
+ // We get here if WiFi failed or the server was not available
+
+ if (previouslyConnected == false)
+ WIFI_STOP();
+ }
+
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- Serial.println(F("x) Exit"));
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+//----------------------------------------
+// Firmware update code
+//----------------------------------------
- int incoming = getNumber(menuTimeout); //Timeout after x seconds
+void mountSDThenUpdate(const char *firmwareFileName)
+{
+ bool gotSemaphore;
+ bool wasSdCardOnline;
- if (incoming > 0 && incoming < (binCount + 1))
+ // Try to gain access the SD card
+ gotSemaphore = false;
+ wasSdCardOnline = online.microSD;
+ if (online.microSD != true)
+ beginSD();
+
+ if (online.microSD != true)
+ systemPrintln("microSD card is offline!");
+ else
{
- //Adjust incoming to match array
- incoming--;
- updateFromSD(binFileNames[incoming]);
+ // Attempt to access file system. This avoids collisions with file writing from other functions like
+ // recordSystemSettingsToFile() and F9PSerialReadTask()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ gotSemaphore = true;
+ updateFromSD(firmwareFileName);
+ } // End Semaphore check
+ else
+ {
+ systemPrintf("sdCardSemaphore failed to yield, menuFirmware.ino line %d\r\n", __LINE__);
+ }
}
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
- else
- Serial.printf("Bad value: %d\n\r", incoming);
- }
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ // Release access the SD card
+ if (online.microSD && (!wasSdCardOnline))
+ endSD(gotSemaphore, true);
+ else if (gotSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
}
-//Looks for matching binary files in root
-//Loads a global called binCount
+// Looks for matching binary files in root
+// Loads a global called binCount
+// Called from beginSD with microSD card mounted and sdCardsemaphore held
void scanForFirmware()
{
- if (online.microSD == true)
- {
- //Attempt to access file system. This avoids collisions with file writing in F9PSerialReadTask()
- //Wait up to 5s, this is important
- if (xSemaphoreTake(xFATSemaphore, 5000 / portTICK_PERIOD_MS) == pdPASS)
- {
- //Count available binaries
- SdFile tempFile;
- SdFile dir;
- const char* BIN_EXT = "bin";
- const char* BIN_HEADER = "RTK_Surveyor_Firmware";
-
- char fname[50]; //Handle long file names
-
- dir.open("/"); //Open root
-
- while (tempFile.openNext(&dir, O_READ))
- {
- if (tempFile.isFile())
- {
- tempFile.getName(fname, sizeof(fname));
-
- if (strcmp(forceFirmwareFileName, fname) == 0)
- {
- Serial.println(F("Forced firmware detected. Loading..."));
- displayForcedFirmwareUpdate();
- updateFromSD((char *)forceFirmwareFileName);
- }
-
- //Check 'bin' extension
- if (strcmp(BIN_EXT, &fname[strlen(fname) - strlen(BIN_EXT)]) == 0)
- {
- //Check for 'RTK_Surveyor_Firmware' start of file name
- if (strncmp(fname, BIN_HEADER, strlen(BIN_HEADER)) == 0)
+ // Count available binaries
+ if (USE_SPI_MICROSD)
+ {
+ SdFile tempFile;
+ SdFile dir;
+ const char *BIN_EXT = "bin";
+ const char *BIN_HEADER = "RTK_Surveyor_Firmware";
+
+ char fname[50]; // Handle long file names
+
+ dir.open("/"); // Open root
+
+ binCount = 0; // Reset count in case scanForFirmware is called again
+
+ while (tempFile.openNext(&dir, O_READ) && binCount < maxBinFiles)
+ {
+ if (tempFile.isFile())
{
- strcpy(binFileNames[binCount++], fname); //Add this to the array
+ tempFile.getName(fname, sizeof(fname));
+
+ if (strcmp(forceFirmwareFileName, fname) == 0)
+ {
+ systemPrintln("Forced firmware detected. Loading...");
+ displayForcedFirmwareUpdate();
+ updateFromSD(forceFirmwareFileName);
+ }
+
+ // Check 'bin' extension
+ if (strcmp(BIN_EXT, &fname[strlen(fname) - strlen(BIN_EXT)]) == 0)
+ {
+ // Check for 'RTK_Surveyor_Firmware' start of file name
+ if (strncmp(fname, BIN_HEADER, strlen(BIN_HEADER)) == 0)
+ {
+ strncpy(binFileNames[binCount++], fname, sizeof(binFileNames[0]) - 1); // Add this to the array
+ }
+ else
+ systemPrintf("Unknown: %s\r\n", fname);
+ }
}
- else
- Serial.printf("Unknown: %s\n\r", fname);
- }
+ tempFile.close();
}
- tempFile.close();
- }
-
- xSemaphoreGive(xFATSemaphore);
}
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ const char *BIN_EXT = "bin";
+ const char *BIN_HEADER = "/RTK_Surveyor_Firmware";
- }
+ char fname[60]; // Handle long file names
+
+ File dir = SD_MMC.open("/"); // Open root
+ if (!dir || !dir.isDirectory())
+ return;
+
+ binCount = 0; // Reset count in case scanForFirmware is called again
+
+ File tempFile = dir.openNextFile();
+ while (tempFile && (binCount < maxBinFiles))
+ {
+ if (!tempFile.isDirectory())
+ {
+ snprintf(fname, sizeof(fname), "%s", tempFile.name());
+
+ if (strcmp(forceFirmwareFileName, fname) == 0)
+ {
+ systemPrintln("Forced firmware detected. Loading...");
+ displayForcedFirmwareUpdate();
+ updateFromSD(forceFirmwareFileName);
+ }
+
+ // Check 'bin' extension
+ if (strcmp(BIN_EXT, &fname[strlen(fname) - strlen(BIN_EXT)]) == 0)
+ {
+ // Check for 'RTK_Surveyor_Firmware' start of file name
+ if (strncmp(fname, BIN_HEADER, strlen(BIN_HEADER)) == 0)
+ {
+ strncpy(binFileNames[binCount++], fname, sizeof(binFileNames[0]) - 1); // Add this to the array
+ }
+ else
+ systemPrintf("Unknown: %s\r\n", fname);
+ }
+ }
+ tempFile.close();
+ tempFile = dir.openNextFile();
+ }
+ }
+#endif // COMPILE_SD_MMC
}
-//Look for firmware file on SD card and update as needed
-void updateFromSD(char *firmwareFileName)
+// Look for firmware file on SD card and update as needed
+// Called from scanForFirmware with microSD card mounted and sdCardsemaphore held
+// Called from mountSDThenUpdate with microSD card mounted and sdCardsemaphore held
+void updateFromSD(const char *firmwareFileName)
{
- //Turn off any tasks so that we are not disrupted
- stopWiFi();
- endBluetooth();
-
- Serial.printf("Loading %s\n\r", firmwareFileName);
- if (sd.exists(firmwareFileName))
- {
- SdFile firmwareFile;
+ // Count app partitions
+ int appPartitions = 0;
+ esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, nullptr);
+ while (it != nullptr)
+ {
+ appPartitions++;
+ it = esp_partition_next(it);
+ }
+
+ // We cannot do OTA if there is only one partition
+ if (appPartitions < 2)
+ {
+ systemPrintln(
+ "SD firmware updates are not available on 4MB devices. Please use the GUI or CLI update methods.");
+ return;
+ }
+
+ // Turn off any tasks so that we are not disrupted
+ espnowStop();
+ WIFI_STOP();
+ bluetoothStop();
+
+ // Delete tasks if running
+ tasksStopUART2();
+
+ systemPrintf("Loading %s\r\n", firmwareFileName);
+
+ if (USE_SPI_MICROSD)
+ {
+ if (!sd->exists(firmwareFileName))
+ {
+ systemPrintln("No firmware file found");
+ return;
+ }
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (!SD_MMC.exists(firmwareFileName))
+ {
+ systemPrintln("No firmware file found");
+ return;
+ }
+ }
+#endif // COMPILE_SD_MMC
+
+ FileSdFatMMC firmwareFile;
+ if (!firmwareFile)
+ {
+ systemPrintln("ERROR - Failed to allocate firmwareFile");
+ return;
+ }
firmwareFile.open(firmwareFileName, O_READ);
- size_t updateSize = firmwareFile.fileSize();
+ size_t updateSize = firmwareFile.size();
+
if (updateSize == 0)
{
- Serial.println(F("Error: Binary is empty"));
- firmwareFile.close();
- return;
+ systemPrintln("Error: Binary is empty");
+ firmwareFile.close();
+ return;
}
if (Update.begin(updateSize) == false)
{
- Serial.println(F("Update begin failed. Not enough partition space available."));
- firmwareFile.close();
- return;
+ systemPrintln("Update begin failed. Not enough partition space available.");
+ firmwareFile.close();
+ return;
}
- Serial.println(F("Moving file to OTA section"));
- Serial.print(F("Bytes to write: "));
- Serial.print(updateSize);
+ systemPrintln("Moving file to OTA section");
+ systemPrint("Bytes to write: ");
+ systemPrint(updateSize);
const int pageSize = 512 * 4;
byte dataArray[pageSize];
int bytesWritten = 0;
- //Indicate progress
- int barWidthInCharacters = 20; //Width of progress bar, ie [###### % complete
+ // Indicate progress
+ int barWidthInCharacters = 20; // Width of progress bar, ie [###### % complete
long portionSize = updateSize / barWidthInCharacters;
int barWidth = 0;
- //Bulk write from the SD file to the EEPROM
+ // Bulk write from the SD file to flash
while (firmwareFile.available())
{
- if (productVariant == RTK_SURVEYOR)
- digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED)); //Toggle LED to indcate activity
+ if (productVariant == RTK_SURVEYOR)
+ digitalWrite(pin_baseStatusLED, !digitalRead(pin_baseStatusLED)); // Toggle LED to indcate activity
- int bytesToWrite = pageSize; //Max number of bytes to read
- if (firmwareFile.available() < bytesToWrite) bytesToWrite = firmwareFile.available(); //Trim this read size as needed
+ int bytesToWrite = pageSize; // Max number of bytes to read
+ if (firmwareFile.available() < bytesToWrite)
+ bytesToWrite = firmwareFile.available(); // Trim this read size as needed
- firmwareFile.read(dataArray, bytesToWrite); //Read the next set of bytes from file into our temp array
- delay(10); //Give RTOS time
+ firmwareFile.read(dataArray, bytesToWrite); // Read the next set of bytes from file into our temp array
- if (Update.write(dataArray, bytesToWrite) != bytesToWrite)
- {
- Serial.println(F("\nWrite failed. Binary may be incorrectly aligned."));
- break;
- }
- else
- bytesWritten += bytesToWrite;
- delay(10); //Give RTOS time
-
- //Indicate progress
- if (bytesWritten > barWidth * portionSize)
- {
- //Advance the bar
- barWidth++;
- Serial.print(F("\n["));
- for (int x = 0 ; x < barWidth ; x++)
- Serial.print("=");
- Serial.printf("%d%%", bytesWritten * 100 / updateSize);
- if (bytesWritten == updateSize) Serial.println("]");
- }
- }
- Serial.println(F("\nFile move complete"));
+ if (Update.write(dataArray, bytesToWrite) != bytesToWrite)
+ {
+ systemPrintln("\nWrite failed. Binary may be incorrectly aligned.");
+ break;
+ }
+ else
+ bytesWritten += bytesToWrite;
+
+ // Indicate progress
+ if (bytesWritten > barWidth * portionSize)
+ {
+ // Advance the bar
+ barWidth++;
+ systemPrint("\n[");
+ for (int x = 0; x < barWidth; x++)
+ systemPrint("=");
+ systemPrintf("%d%%", bytesWritten * 100 / updateSize);
+ if (bytesWritten == updateSize)
+ systemPrintln("]");
+
+ displayFirmwareUpdateProgress(bytesWritten * 100 / updateSize);
+ }
+ }
+ systemPrintln("\nFile move complete");
if (Update.end())
{
- if (Update.isFinished())
- {
- Serial.println(F("Firmware updated successfully. Rebooting. Good bye!"));
+ if (Update.isFinished())
+ {
+ displayFirmwareUpdateProgress(100);
- //If forced firmware is detected, do a full reset of config as well
- if (strcmp(forceFirmwareFileName, firmwareFileName) == 0)
+ // Clear all settings from LittleFS
+ LittleFS.format();
+
+ systemPrintln("Firmware updated successfully. Rebooting. Goodbye!");
+
+ // If forced firmware is detected, do a full reset of config as well
+ if (strcmp(forceFirmwareFileName, firmwareFileName) == 0)
+ {
+ systemPrintln("Removing firmware file");
+
+ // Remove forced firmware file to prevent endless loading
+ firmwareFile.close();
+
+ if (USE_SPI_MICROSD)
+ sd->remove(firmwareFileName);
+#ifdef COMPILE_SD_MMC
+ else
+ SD_MMC.remove(firmwareFileName);
+#endif // COMPILE_SD_MMC
+
+ theGNSS.factoryDefault(); // Reset everything: baud rate, I2C address, update rate, everything. And save
+ // to BBR.
+ }
+
+ delay(1000);
+ ESP.restart();
+ }
+ else
+ systemPrintln("Update not finished? Something went wrong!");
+ }
+ else
+ {
+ systemPrint("Error Occurred. Error #: ");
+ systemPrintln(String(Update.getError()));
+ }
+
+ firmwareFile.close();
+
+ displayMessage("Update Failed", 0);
+
+ systemPrintln("Firmware update failed. Please try again.");
+}
+
+// Format the firmware version
+void formatFirmwareVersion(uint8_t major, uint8_t minor, char *buffer, int bufferLength, bool includeDate)
+{
+ char prefix;
+
+ // Construct the full or release candidate version number
+ prefix = ENABLE_DEVELOPER ? 'd' : 'v';
+ if (enableRCFirmware && (bufferLength >= 21))
+ // 123456789012345678901
+ // pxxx.yyy-dd-mmm-yyyy0
+ snprintf(buffer, bufferLength, "%c%d.%d-%s", prefix, major, minor, __DATE__);
+
+ // Construct a truncated version number
+ else if (bufferLength >= 9)
+ // 123456789
+ // pxxx.yyy0
+ snprintf(buffer, bufferLength, "%c%d.%d", prefix, major, minor);
+
+ // The buffer is too small for the version number
+ else
+ {
+ systemPrintf("ERROR: Buffer too small for version number!\r\n");
+ if (bufferLength > 0)
+ *buffer = 0;
+ }
+}
+
+// Get the current firmware version
+void getFirmwareVersion(char *buffer, int bufferLength, bool includeDate)
+{
+ formatFirmwareVersion(FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, buffer, bufferLength, includeDate);
+}
+
+const char *otaGetUrl()
+{
+ // Select the URL for the over-the-air (OTA) updates
+ return enableRCFirmware ? OTA_RC_FIRMWARE_JSON_URL : OTA_FIRMWARE_JSON_URL;
+}
+
+// Returns true if we successfully got the versionAvailable
+// Modifies versionAvailable with OTA getVersion response
+// Connects to WiFi as needed
+bool otaCheckVersion(char *versionAvailable, uint8_t versionAvailableLength)
+{
+ bool gotVersion = false;
+#ifdef COMPILE_WIFI
+ bool previouslyConnected = wifiIsConnected();
+
+ if (wifiConnect(10000) == true)
+ {
+ char versionString[21];
+ getFirmwareVersion(versionString, sizeof(versionString), enableRCFirmware);
+ systemPrintf("Current firmware version: %s\r\n", versionString);
+
+ const char *url = otaGetUrl();
+ systemPrintf("Checking to see if an update is available from %s\r\n", url);
+
+ ESP32OTAPull ota;
+
+ int response = ota.CheckForOTAUpdate(url, versionString, ESP32OTAPull::DONT_DO_UPDATE);
+
+ // We don't care if the library thinks the available firmware is newer, we just need a successful JSON parse
+ if (response == ESP32OTAPull::UPDATE_AVAILABLE || response == ESP32OTAPull::NO_UPDATE_AVAILABLE)
+ {
+ gotVersion = true;
+
+ // Call getVersion after original inquiry
+ String otaVersion = ota.GetVersion();
+ otaVersion.toCharArray(versionAvailable, versionAvailableLength);
+ }
+ else if (response == ESP32OTAPull::HTTP_FAILED)
+ {
+ systemPrintln("Firmware server not available");
+ }
+ else
+ {
+ systemPrintln("OTA failed");
+ }
+ }
+ else
+ {
+ systemPrintln("WiFi not available.");
+ }
+
+ 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 (previouslyConnected == false)
+ WIFI_STOP();
+ }
+
+ if (gotVersion == true)
+ log_d("Available OTA firmware version: %s\r\n", versionAvailable);
+
+#endif // COMPILE_WIFI
+ return (gotVersion);
+}
+
+// Force updates firmware using OTA pull
+// Exits by either updating firmware and resetting, or failing to connect
+void otaUpdate()
+{
+#ifdef COMPILE_WIFI
+ bool previouslyConnected = wifiIsConnected();
+
+ if (wifiConnect(10000) == true)
+ {
+ char versionString[9];
+ formatFirmwareVersion(0, 0, versionString, sizeof(versionString), false);
+
+ ESP32OTAPull ota;
+
+ int response;
+ const char *url = otaGetUrl();
+ response = ota.CheckForOTAUpdate(url, &versionString[1], ESP32OTAPull::DONT_DO_UPDATE);
+
+ if (response == ESP32OTAPull::UPDATE_AVAILABLE)
{
- Serial.println(F("Removing firmware file"));
+ systemPrintln("Installing new firmware");
+ ota.SetCallback(otaPullCallback);
+ ota.CheckForOTAUpdate(url, &versionString[1]); // Install new firmware, no reset
- //Remove forced firmware file to prevent endless loading
- firmwareFile.close();
- sd.remove(firmwareFileName);
+ if (apConfigFirmwareUpdateInProcess)
+ {
+#ifdef COMPILE_AP
+ // Tell AP page to display reset info
+ websocket->textAll("confirmReset,1,");
+#endif // COMPILE_AP
+ }
+ ESP.restart();
+ }
+ else if (response == ESP32OTAPull::NO_UPDATE_AVAILABLE)
+ {
+ systemPrintln("OTA Update: Current firmware is up to date");
+ }
+ else if (response == ESP32OTAPull::HTTP_FAILED)
+ {
+ systemPrintln("OTA Update: Firmware server not available");
+ }
+ else
+ {
+ systemPrintln("OTA Update: OTA failed");
+ }
+ }
+ else
+ {
+ systemPrintln("WiFi not available.");
+ }
- eepromErase();
+ // Update failed. If WiFi was originally off, turn it off again
+ if (previouslyConnected == false)
+ WIFI_STOP();
- //Assemble settings file name
- char settingsFileName[40]; //SFE_Surveyor_Settings.txt
- strcpy(settingsFileName, platformFilePrefix);
- strcat(settingsFileName, "_Settings.txt");
+#endif // COMPILE_WIFI
+}
- if (sd.exists(settingsFileName))
- sd.remove(settingsFileName);
+// Called while the OTA Pull update is happening
+void otaPullCallback(int bytesWritten, int totalLength)
+{
+ otaDisplayPercentage(bytesWritten, totalLength, false);
+}
- i2cGNSS.factoryReset(); //Reset everything: baud rate, I2C address, update rate, everything.
+void otaDisplayPercentage(int bytesWritten, int totalLength, bool alwaysDisplay)
+{
+ static int previousPercent = -1;
+ int percent = 100 * bytesWritten / totalLength;
+ if (alwaysDisplay || (percent != previousPercent))
+ {
+ // Indicate progress
+ int barWidthInCharacters = 20; // Width of progress bar, ie [###### % complete
+ long portionSize = totalLength / barWidthInCharacters;
+
+ // Indicate progress
+ systemPrint("\r\n[");
+ int barWidth = bytesWritten / portionSize;
+ for (int x = 0; x < barWidth; x++)
+ systemPrint("=");
+ systemPrintf(" %d%%", percent);
+ if (bytesWritten == totalLength)
+ systemPrintln("]");
+
+ displayFirmwareUpdateProgress(percent);
+
+ if (apConfigFirmwareUpdateInProcess == true)
+ {
+#ifdef COMPILE_AP
+ char myProgress[50];
+ snprintf(myProgress, sizeof(myProgress), "otaFirmwareStatus,%d,", percent);
+ websocket->textAll(myProgress);
+#endif // COMPILE_AP
}
- delay(1000);
- ESP.restart();
- }
- else
- Serial.println(F("Update not finished? Something went wrong!"));
+ previousPercent = percent;
+ }
+}
+
+const char *otaPullErrorText(int code)
+{
+#ifdef COMPILE_WIFI
+ switch (code)
+ {
+ case ESP32OTAPull::UPDATE_AVAILABLE:
+ return "An update is available but wasn't installed";
+ case ESP32OTAPull::NO_UPDATE_PROFILE_FOUND:
+ return "No profile matches";
+ case ESP32OTAPull::NO_UPDATE_AVAILABLE:
+ return "Profile matched, but update not applicable";
+ case ESP32OTAPull::UPDATE_OK:
+ return "An update was done, but no reboot";
+ case ESP32OTAPull::HTTP_FAILED:
+ return "HTTP GET failure";
+ case ESP32OTAPull::WRITE_ERROR:
+ return "Write error";
+ case ESP32OTAPull::JSON_PROBLEM:
+ return "Invalid JSON";
+ case ESP32OTAPull::OTA_UPDATE_FAIL:
+ return "Update fail (no OTA partition?)";
+ default:
+ if (code > 0)
+ return "Unexpected HTTP response code";
+ break;
+ }
+#endif // COMPILE_WIFI
+ return "Unknown error";
+}
+
+// Returns true if reportedVersion is newer than currentVersion
+// Version number comes in as v2.7-Jan 5 2023
+// 2.7-Jan 5 2023 is newer than v2.7-Jan 1 2023
+// We can't use just the float number: v3.12 is a greater version than v3.9 but it is a smaller float number
+bool isReportedVersionNewer(char *reportedVersion, char *currentVersion)
+{
+ int currentVersionNumberMajor = 0;
+ int currentVersionNumberMinor = 0;
+ int currentDay = 0;
+ int currentMonth = 0;
+ int currentYear = 0;
+
+ int reportedVersionNumberMajor = 0;
+ int reportedVersionNumberMinor = 0;
+ int reportedDay = 0;
+ int reportedMonth = 0;
+ int reportedYear = 0;
+
+ breakVersionIntoParts(currentVersion, ¤tVersionNumberMajor, ¤tVersionNumberMinor, ¤tYear,
+ ¤tMonth, ¤tDay);
+ breakVersionIntoParts(reportedVersion, &reportedVersionNumberMajor, &reportedVersionNumberMinor, &reportedYear,
+ &reportedMonth, &reportedDay);
+
+ log_d("currentVersion (%s): %d.%d %d %d %d", currentVersion, currentVersionNumberMajor, currentVersionNumberMinor,
+ currentYear, currentMonth, currentDay);
+ log_d("reportedVersion (%s): %d.%d %d %d %d", reportedVersion, reportedVersionNumberMajor,
+ reportedVersionNumberMinor, reportedYear, reportedMonth, reportedDay);
+ if (enableRCFirmware)
+ log_d("RC firmware enabled");
+
+ // Production firmware is named "2.6"
+ // Release Candidate firmware is named "2.6-Dec 5 2022"
+
+ // If the user is not using Release Candidate firmware, then check only the version number
+ if (enableRCFirmware == false)
+ {
+ if (reportedVersionNumberMajor > currentVersionNumberMajor)
+ return (true);
+ if (reportedVersionNumberMajor == currentVersionNumberMajor &&
+ reportedVersionNumberMinor > currentVersionNumberMinor)
+ return (true);
+ return (false);
+ }
+
+ // For RC firmware, compare firmware date as well
+ // Check version number
+ if (reportedVersionNumberMajor > currentVersionNumberMajor)
+ return (true);
+ if (reportedVersionNumberMajor == currentVersionNumberMajor &&
+ reportedVersionNumberMinor > currentVersionNumberMinor)
+ return (true);
+
+ // Check which date is more recent
+ // https://stackoverflow.com/questions/5283120/date-comparison-to-find-which-is-bigger-in-c
+ int reportedVersionScore = reportedDay + reportedMonth * 100 + reportedYear * 2000;
+ int currentVersionScore = currentDay + currentMonth * 100 + currentYear * 2000;
+
+ if (reportedVersionScore > currentVersionScore)
+ {
+ log_d("Reported version is greater");
+ return (true);
+ }
+
+ return (false);
+}
+
+// Version number comes in as v2.7-Jan 5 2023
+// Given a char string, break into version number major/minor, year, month, day
+// Returns false if parsing failed
+bool breakVersionIntoParts(char *version, int *versionNumberMajor, int *versionNumberMinor, int *year, int *month,
+ int *day)
+{
+ char monthStr[20];
+ int placed = 0;
+
+ if (enableRCFirmware == false)
+ {
+ placed = sscanf(version, "%d.%d", versionNumberMajor, versionNumberMinor);
+ if (placed != 2)
+ {
+ log_d("Failed to sscanf basic");
+ return (false); // Something went wrong
+ }
}
else
{
- Serial.print(F("Error Occurred. Error #: "));
- Serial.println(String(Update.getError()));
+ placed = sscanf(version, "%d.%d-%s %d %d", versionNumberMajor, versionNumberMinor, monthStr, day, year);
+
+ if (placed != 5)
+ {
+ log_d("Failed to sscanf RC");
+ return (false); // Something went wrong
+ }
+
+ (*month) = mapMonthName(monthStr);
+ if (*month == -1)
+ return (false); // Something went wrong
}
- firmwareFile.close();
- }
- else
- {
- Serial.println(F("No firmware file found"));
- }
+ return (true);
+}
+
+// https://stackoverflow.com/questions/21210319/assign-month-name-and-integer-values-from-string-using-sscanf
+int mapMonthName(char *mmm)
+{
+ static char const *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ for (size_t i = 0; i < sizeof(months) / sizeof(months[0]); i++)
+ {
+ if (strcmp(mmm, months[i]) == 0)
+ return i + 1;
+ }
+ return -1;
}
diff --git a/Firmware/RTK_Surveyor/menuGNSS.ino b/Firmware/RTK_Surveyor/menuGNSS.ino
index 9f553d3f6..b101f77b4 100644
--- a/Firmware/RTK_Surveyor/menuGNSS.ino
+++ b/Firmware/RTK_Surveyor/menuGNSS.ino
@@ -1,273 +1,476 @@
-//Configure the basic GNSS reception settings
-//Update rate, constellations, etc
+// Configure the basic GNSS reception settings
+// Update rate, constellations, etc
void menuGNSS()
{
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: GNSS Menu"));
+ restartRover = false; // If user modifies any NTRIP settings, we need to restart the rover
- //Because we may be in base mode (always 1Hz), do not get freq from module, use settings instead
- float measurementFrequency = (1000.0 / settings.measurementRate) / settings.navigationRate;
+ while (1)
+ {
+ int minCNO = settings.minCNO_F9P;
+ if (zedModuleType == PLATFORM_F9R)
+ minCNO = settings.minCNO_F9R;
+
+ systemPrintln();
+ systemPrintln("Menu: GNSS Receiver");
+
+ // Because we may be in base mode, do not get freq from module, use settings instead
+ float measurementFrequency = (1000.0 / settings.measurementRate) / settings.navigationRate;
+
+ systemPrint("1) Set measurement rate in Hz: ");
+ systemPrintln(measurementFrequency, 5);
+
+ systemPrint("2) Set measurement rate in seconds between measurements: ");
+ systemPrintln(1 / measurementFrequency, 5);
+
+ systemPrintln("\tNote: The measurement rate is overridden to 1Hz when in Base mode.");
+
+ systemPrint("3) Set dynamic model: ");
+ switch (settings.dynamicModel)
+ {
+ case DYN_MODEL_PORTABLE:
+ systemPrint("Portable");
+ break;
+ case DYN_MODEL_STATIONARY:
+ systemPrint("Stationary");
+ break;
+ case DYN_MODEL_PEDESTRIAN:
+ systemPrint("Pedestrian");
+ break;
+ case DYN_MODEL_AUTOMOTIVE:
+ systemPrint("Automotive");
+ break;
+ case DYN_MODEL_SEA:
+ systemPrint("Sea");
+ break;
+ case DYN_MODEL_AIRBORNE1g:
+ systemPrint("Airborne 1g");
+ break;
+ case DYN_MODEL_AIRBORNE2g:
+ systemPrint("Airborne 2g");
+ break;
+ case DYN_MODEL_AIRBORNE4g:
+ systemPrint("Airborne 4g");
+ break;
+ case DYN_MODEL_WRIST:
+ systemPrint("Wrist");
+ break;
+ case DYN_MODEL_BIKE:
+ systemPrint("Bike");
+ break;
+ case DYN_MODEL_MOWER:
+ systemPrint("Mower");
+ break;
+ case DYN_MODEL_ESCOOTER:
+ systemPrint("E-Scooter");
+ break;
+ default:
+ systemPrint("Unknown");
+ break;
+ }
+ systemPrintln();
+
+ systemPrintln("4) Set Constellations");
+
+ systemPrint("5) Toggle NTRIP Client: ");
+ if (settings.enableNtripClient == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
- Serial.print(F("1) Set measurement rate in Hz: "));
- Serial.println(measurementFrequency, 4);
+ if (settings.enableNtripClient == true)
+ {
+ systemPrint("6) Set Caster Address: ");
+ systemPrintln(settings.ntripClient_CasterHost);
- Serial.print(F("2) Set measurement rate in seconds between measurements: "));
- Serial.println(1 / measurementFrequency, 2);
+ systemPrint("7) Set Caster Port: ");
+ systemPrintln(settings.ntripClient_CasterPort);
- Serial.print(F("3) Set dynamic model: "));
- switch (settings.dynamicModel)
- {
- case DYN_MODEL_PORTABLE:
- Serial.print(F("Portable"));
- break;
- case DYN_MODEL_STATIONARY:
- Serial.print(F("Stationary"));
- break;
- case DYN_MODEL_PEDESTRIAN:
- Serial.print(F("Pedestrian"));
- break;
- case DYN_MODEL_AUTOMOTIVE:
- Serial.print(F("Automotive"));
- break;
- case DYN_MODEL_SEA:
- Serial.print(F("Sea"));
- break;
- case DYN_MODEL_AIRBORNE1g:
- Serial.print(F("Airborne 1g"));
- break;
- case DYN_MODEL_AIRBORNE2g:
- Serial.print(F("Airborne 2g"));
- break;
- case DYN_MODEL_AIRBORNE4g:
- Serial.print(F("Airborne 4g"));
- break;
- case DYN_MODEL_WRIST:
- Serial.print(F("Wrist"));
- break;
- case DYN_MODEL_BIKE:
- Serial.print(F("Bike"));
- break;
- default:
- Serial.print(F("Unknown"));
- break;
- }
- Serial.println();
+ systemPrint("8) Set Caster User Name: ");
+ systemPrintln(settings.ntripClient_CasterUser);
- Serial.println(F("4) Set Constellations "));
+ systemPrint("9) Set Caster User Password: ");
+ systemPrintln(settings.ntripClient_CasterUserPW);
- Serial.println(F("x) Exit"));
+ systemPrint("10) Set Mountpoint: ");
+ systemPrintln(settings.ntripClient_MountPoint);
- int incoming = getNumber(menuTimeout); //Timeout after x seconds
+ systemPrint("11) Set Mountpoint PW: ");
+ systemPrintln(settings.ntripClient_MountPointPW);
- if (incoming == 1)
- {
- Serial.print(F("Enter GNSS measurement rate in Hz: "));
- double rate = getDouble(menuTimeout); //Timeout after x seconds
- if (rate < 0.0 || rate > 20.0) //20Hz limit with all constellations enabled.
- {
- Serial.println(F("Error: measurement rate out of range"));
- }
- else
- {
- setMeasurementRates(1.0 / rate); //Convert Hz to seconds. This will set settings.measurementRate and settings.navigationRate
- //Settings recorded to NVM and file at main menu exit
- }
+ systemPrint("12) Toggle sending GGA Location to Caster: ");
+ if (settings.ntripClient_TransmitGGA == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ systemPrintf("13) Minimum elevation for a GNSS satellite to be used in fix (degrees): %d\r\n",
+ settings.minElev);
+
+ systemPrintf("14) Minimum satellite signal level for navigation (dBHz): %d\r\n", minCNO);
+ }
+ else
+ {
+ systemPrintf("6) Minimum elevation for a GNSS satellite to be used in fix (degrees): %d\r\n",
+ settings.minElev);
+
+ systemPrintf("7) Minimum satellite signal level for navigation (dBHz): %d\r\n", minCNO);
+ }
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ systemPrint("Enter GNSS measurement rate in Hz: ");
+ double rate = getDouble();
+ if (rate < 0.00012 || rate > 20.0) // 20Hz limit with all constellations enabled
+ {
+ systemPrintln("Error: Measurement rate out of range");
+ }
+ else
+ {
+ setRate(1.0 / rate); // Convert Hz to seconds. This will set settings.measurementRate,
+ // settings.navigationRate, and GSV message
+ // Settings recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 2)
+ {
+ systemPrint("Enter GNSS measurement rate in seconds between measurements: ");
+ float rate = getDouble();
+ if (rate < 0.0 || rate > 8255.0) // Limit of 127 (navRate) * 65000ms (measRate) = 137 minute limit.
+ {
+ systemPrintln("Error: Measurement rate out of range");
+ }
+ else
+ {
+ setRate(rate); // This will set settings.measurementRate, settings.navigationRate, and GSV message
+ // Settings recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 3)
+ {
+ systemPrintln("Enter the dynamic model to use: ");
+ systemPrintln("1) Portable");
+ systemPrintln("2) Stationary");
+ systemPrintln("3) Pedestrian");
+ systemPrintln("4) Automotive");
+ systemPrintln("5) Sea");
+ systemPrintln("6) Airborne 1g");
+ systemPrintln("7) Airborne 2g");
+ systemPrintln("8) Airborne 4g");
+ systemPrintln("9) Wrist");
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ systemPrintln("10) Bike");
+ // F9R versions starting at 1.21 have Mower and E-Scooter dynamic models
+ if (zedFirmwareVersionInt >= 121)
+ {
+ systemPrintln("11) Mower");
+ systemPrintln("12) E-Scooter");
+ }
+ }
+
+ int dynamicModel = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((dynamicModel != INPUT_RESPONSE_GETNUMBER_EXIT) && (dynamicModel != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ uint8_t maxModel = DYN_MODEL_WRIST;
+
+ if (zedModuleType == PLATFORM_F9R)
+ {
+ maxModel = DYN_MODEL_BIKE;
+ // F9R versions starting at 1.21 have Mower and E-Scooter dynamic models
+ if (zedFirmwareVersionInt >= 121)
+ maxModel = DYN_MODEL_ESCOOTER;
+ }
+
+ if (dynamicModel < 1 || dynamicModel > maxModel)
+ systemPrintln("Error: Dynamic model out of range");
+ else
+ {
+ if (dynamicModel == 1)
+ settings.dynamicModel = DYN_MODEL_PORTABLE; // The enum starts at 0 and skips 1.
+ else
+ settings.dynamicModel = dynamicModel; // Recorded to NVM and file at main menu exit
+
+ theGNSS.setVal8(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); // Set dynamic model
+ }
+ }
+ }
+ else if (incoming == 4)
+ {
+ menuConstellations();
+ }
+ else if (incoming == 5)
+ {
+ settings.enableNtripClient ^= 1;
+ restartRover = true;
+ }
+ else if ((incoming == 6) && settings.enableNtripClient == true)
+ {
+ systemPrint("Enter new Caster Address: ");
+ getString(settings.ntripClient_CasterHost, sizeof(settings.ntripClient_CasterHost));
+ restartRover = true;
+ }
+ else if ((incoming == 7) && settings.enableNtripClient == true)
+ {
+ systemPrint("Enter new Caster Port: ");
+
+ int ntripClient_CasterPort = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((ntripClient_CasterPort != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (ntripClient_CasterPort != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (ntripClient_CasterPort < 1 || ntripClient_CasterPort > 99999) // Arbitrary 99k max port #
+ systemPrintln("Error: Caster port out of range");
+ else
+ settings.ntripClient_CasterPort =
+ ntripClient_CasterPort; // Recorded to NVM and file at main menu exit
+ restartRover = true;
+ }
+ }
+ else if ((incoming == 8) && settings.enableNtripClient == true)
+ {
+ systemPrintf("Enter user name for %s: ", settings.ntripClient_CasterHost);
+ getString(settings.ntripClient_CasterUser, sizeof(settings.ntripClient_CasterUser));
+ restartRover = true;
+ }
+ else if ((incoming == 9) && settings.enableNtripClient == true)
+ {
+ systemPrintf("Enter user password for %s: ", settings.ntripClient_CasterHost);
+ getString(settings.ntripClient_CasterUserPW, sizeof(settings.ntripClient_CasterUserPW));
+ restartRover = true;
+ }
+ else if ((incoming == 10) && settings.enableNtripClient == true)
+ {
+ systemPrint("Enter new Mount Point: ");
+ getString(settings.ntripClient_MountPoint, sizeof(settings.ntripClient_MountPoint));
+ restartRover = true;
+ }
+ else if ((incoming == 11) && settings.enableNtripClient == true)
+ {
+ systemPrintf("Enter password for Mount Point %s: ", settings.ntripClient_MountPoint);
+ getString(settings.ntripClient_MountPointPW, sizeof(settings.ntripClient_MountPointPW));
+ restartRover = true;
+ }
+ else if ((incoming == 12) && settings.enableNtripClient == true)
+ {
+ settings.ntripClient_TransmitGGA ^= 1;
+ restartRover = true;
+ }
+ else if (((incoming == 13) && settings.enableNtripClient == true) ||
+ incoming == 6 && settings.enableNtripClient == false)
+ {
+ systemPrint("Enter minimum elevation in degrees: ");
+
+ int minElev = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((minElev != INPUT_RESPONSE_GETNUMBER_EXIT) && (minElev != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (minElev <= 0 || minElev > 90) // Arbitrary 90 degree max
+ systemPrintln("Error: Minimum elevation out of range");
+ else
+ {
+ settings.minElev = minElev; // Recorded to NVM and file at main menu exit
+
+ theGNSS.setVal8(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation
+ }
+ restartRover = true;
+ }
+ }
+ else if (((incoming == 14) && settings.enableNtripClient == true) ||
+ incoming == 7 && settings.enableNtripClient == false)
+ {
+ systemPrint("Enter minimum satellite signal level for navigation in dBHz: ");
+
+ int newMinCNO = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((newMinCNO != INPUT_RESPONSE_GETNUMBER_EXIT) && (newMinCNO != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (newMinCNO <= 0 || newMinCNO > 90) // Arbitrary 90 dBHz max
+ systemPrintln("Error: Minimum dBHz out of range");
+ else
+ {
+ if (zedModuleType == PLATFORM_F9R)
+ settings.minCNO_F9R = newMinCNO; // Recorded to NVM and file at main menu exit
+ else
+ settings.minCNO_F9P = newMinCNO;
+
+ theGNSS.setVal8(UBLOX_CFG_NAVSPG_INFIL_MINCNO, newMinCNO); // Update minCNO
+ }
+ restartRover = true;
+ }
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (incoming == 2)
+
+ // Error check for RTK2Go without email in user name
+ // First force tolower the host name
+ char lowerHost[51];
+ strncpy(lowerHost, settings.ntripClient_CasterHost, sizeof(lowerHost) - 1);
+ for (int x = 0; x < 50; x++)
{
- Serial.print(F("Enter GNSS measurement rate in seconds between measurements: "));
- double rate = getDouble(menuTimeout); //Timeout after x seconds
- if (rate < 0.0 || rate > 8255.0) //Limit of 127 (navRate) * 65000ms (measRate) = 137 minute limit.
- {
- Serial.println(F("Error: measurement rate out of range"));
- }
- else
- {
- setMeasurementRates(rate); //This will set settings.measurementRate and settings.navigationRate
- //Settings recorded to NVM and file at main menu exit
- }
+ if (lowerHost[x] == '\0')
+ break;
+ if (lowerHost[x] >= 'A' && lowerHost[x] <= 'Z')
+ lowerHost[x] = lowerHost[x] - 'A' + 'a';
}
- else if (incoming == 3)
+
+ if (strncmp(lowerHost, "rtk2go.com", strlen("rtk2go.com")) == 0 ||
+ strncmp(lowerHost, "www.rtk2go.com", strlen("www.rtk2go.com")) == 0)
{
- Serial.println(F("Enter the dynamic model to use: "));
- Serial.println(F("1) Portable"));
- Serial.println(F("2) Stationary"));
- Serial.println(F("3) Pedestrian"));
- Serial.println(F("4) Automotive"));
- Serial.println(F("5) Sea"));
- Serial.println(F("6) Airborne 1g"));
- Serial.println(F("7) Airborne 2g"));
- Serial.println(F("8) Airborne 4g"));
- Serial.println(F("9) Wrist"));
- Serial.println(F("10) Bike"));
-
- int dynamicModel = getNumber(menuTimeout); //Timeout after x seconds
- if (dynamicModel < 1 || dynamicModel > DYN_MODEL_BIKE)
- Serial.println(F("Error: Dynamic model out of range"));
- else
- {
- if (dynamicModel == 1)
- settings.dynamicModel = DYN_MODEL_PORTABLE; //The enum starts at 0 and skips 1.
- else
- settings.dynamicModel = dynamicModel; //Recorded to NVM and file at main menu exit
- }
+ // Rudamentary user name length check
+ if (strlen(settings.ntripClient_CasterUser) == 0)
+ {
+ systemPrintln("WARNING: RTK2Go requires that you use your email address as the mountpoint user name");
+ delay(2000);
+ }
}
- else if (incoming == 4)
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Controls the constellations that are used to generate a fix and logged
+void menuConstellations()
+{
+ while (1)
{
- menuConstellations();
+ systemPrintln();
+ systemPrintln("Menu: Constellations");
+
+ for (int x = 0; x < MAX_CONSTELLATIONS; x++)
+ {
+ systemPrintf("%d) Constellation %s: ", x + 1, settings.ubxConstellations[x].textName);
+ if (settings.ubxConstellations[x].enabled == true)
+ systemPrint("Enabled");
+ else
+ systemPrint("Disabled");
+ systemPrintln();
+ }
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming >= 1 && incoming <= MAX_CONSTELLATIONS)
+ {
+ incoming--; // Align choice to constallation array of 0 to 5
+
+ settings.ubxConstellations[incoming].enabled ^= 1;
+
+ // 3.10.6: To avoid cross-correlation issues, it is recommended that GPS and QZSS are always both enabled or
+ // both disabled.
+ if (incoming == SFE_UBLOX_GNSS_ID_GPS || incoming == 4) // QZSS ID is 5 but array location is 4
+ {
+ settings.ubxConstellations[SFE_UBLOX_GNSS_ID_GPS].enabled =
+ settings.ubxConstellations[incoming].enabled; // GPS ID is 0 and array location is 0
+ settings.ubxConstellations[4].enabled =
+ settings.ubxConstellations[incoming].enabled; // QZSS ID is 5 but array location is 4
+ }
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
-
- int maxWait = 2000;
- // Set dynamic model
- if (i2cGNSS.getDynamicModel(maxWait) != settings.dynamicModel)
- {
- if (i2cGNSS.setDynamicModel((dynModel)settings.dynamicModel, maxWait) == false)
- Serial.println(F("menuGNSS: setDynamicModel failed"));
- }
+ // Apply current settings to module
+ setConstellations(true); // Apply newCfg and sendCfg values to batch
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ clearBuffer(); // Empty buffer of any newline chars
}
-//Controls the constellations that are used to generate a fix and logged
-void menuConstellations()
+// Given the number of seconds between desired solution reports, determine measurementRate and navigationRate
+// measurementRate > 25 & <= 65535
+// navigationRate >= 1 && <= 127
+// We give preference to limiting a measurementRate to 30s or below due to reported problems with measRates above 30.
+bool setRate(double secondsBetweenSolutions)
{
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Constellations Menu"));
+ uint16_t measRate = 0; // Calculate these locally and then attempt to apply them to ZED at completion
+ uint16_t navRate = 0;
- for (int x = 0 ; x < MAX_CONSTELLATIONS ; x++)
+ // If we have more than an hour between readings, increase mesaurementRate to near max of 65,535
+ if (secondsBetweenSolutions > 3600.0)
{
- Serial.printf("%d) Constellation %s: ", x + 1, ubxConstellations[x].textName);
- if (ubxConstellations[x].enabled == true)
- Serial.print("Enabled");
- else
- Serial.print("Disabled");
- Serial.println();
+ measRate = 65000;
}
- Serial.println(F("x) Exit"));
-
- int incoming = getNumber(menuTimeout); //Timeout after x seconds
-
- if (incoming >= 1 && incoming <= MAX_CONSTELLATIONS)
+ // If we have more than 30s, but less than 3600s between readings, use 30s measurement rate
+ else if (secondsBetweenSolutions > 30.0)
{
- ubxConstellations[incoming - 1].enabled ^= 1;
-
- //3.10.6: To avoid cross-correlation issues, it is recommended that GPS and QZSS are always both enabled or both disabled.
- if((incoming - 1) == SFE_UBLOX_GNSS_ID_GPS || (incoming - 1) == SFE_UBLOX_GNSS_ID_QZSS)
- {
- ubxConstellations[SFE_UBLOX_GNSS_ID_GPS].enabled = ubxConstellations[incoming - 1].enabled;
- ubxConstellations[SFE_UBLOX_GNSS_ID_QZSS].enabled = ubxConstellations[incoming - 1].enabled;
- }
+ measRate = 30000;
}
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
+
+ // User wants measurements less than 30s (most common), set measRate to match user request
+ // This will make navRate = 1.
else
- printUnknown(incoming);
- }
+ {
+ measRate = secondsBetweenSolutions * 1000.0;
+ }
- //Apply current settings to module
- configureConstellations();
+ navRate = secondsBetweenSolutions * 1000.0 / measRate; // Set navRate to nearest int value
+ measRate = secondsBetweenSolutions * 1000.0 / navRate; // Adjust measurement rate to match actual navRate
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
-}
+ // systemPrintf("measurementRate / navRate: %d / %d\r\n", measRate, navRate);
-//Given the number of seconds between desired solution reports, determine measurementRate and navigationRate
-//measurementRate > 25 & <= 65535
-//navigationRate >= 1 && <= 127
-//We give preference to limiting a measurementRate to 30s or below due to reported problems with measRates above 30.
-void setMeasurementRates(float secondsBetweenSolutions)
-{
- uint16_t measRate = 0; //Calculate these locally and then attempt to apply them to ZED at completion
- uint16_t navRate = 0;
-
- //If we have more than an hour between readings, increase mesaurementRate to near max of 65,535
- if (secondsBetweenSolutions > 3600.0)
- {
- measRate = 65000;
- }
-
- //If we have more than 30s, but less than 3600s between readings, use 30s measurement rate
- else if (secondsBetweenSolutions > 30.0)
- {
- measRate = 30000;
- }
-
- //User wants measurements less than 30s (most common), set measRate to match user request
- //This will make navRate = 1.
- else
- {
- measRate = secondsBetweenSolutions * 1000.0;
- }
-
- navRate = secondsBetweenSolutions * 1000.0 / measRate; //Set navRate to nearest int value
- measRate = secondsBetweenSolutions * 1000.0 / navRate; //Adjust measurement rate to match actual navRate
-
- //Serial.printf("measurementRate / navRate: %d / %d\n\r", measRate, navRate);
-
- //If we successfully set rates, only then record to settings
- if (i2cGNSS.setMeasurementRate(measRate) == true && i2cGNSS.setNavigationRate(navRate) == true)
- {
- settings.measurementRate = measRate;
- settings.navigationRate = navRate;
- }
- else
- {
- Serial.println(F("menuGNSS: Failed to set measurement and navigation rates"));
- }
-}
+ bool response = true;
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_MEAS, measRate);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_RATE_NAV, navRate);
-//We need to know our overall measurement frequency for things like setting the GSV NMEA sentence rate.
-//This returns a float of the rate based on settings that is the readings per second (Hz).
-float getMeasurementFrequency()
-{
- uint16_t currentMeasurementRate = i2cGNSS.getMeasurementRate();
- uint16_t currentNavigationRate = i2cGNSS.getNavigationRate();
+ int gsvRecordNumber = getMessageNumberByName("UBX_NMEA_GSV");
+
+ // If enabled, adjust GSV NMEA to be reported at 1Hz to avoid swamping SPP connection
+ if (settings.ubxMessageRates[gsvRecordNumber] > 0)
+ {
+ float measurementFrequency = (1000.0 / measRate) / navRate;
+ if (measurementFrequency < 1.0)
+ measurementFrequency = 1.0;
+
+ log_d("Adjusting GSV setting to %f", measurementFrequency);
- currentNavigationRate = i2cGNSS.getNavigationRate();
- //The ZED-F9P will report an incorrect nav rate if we have rececently changed it.
- //Reading a second time insures a correct read.
+ setMessageRateByName("UBX_NMEA_GSV", measurementFrequency); // Update GSV setting in file
+ response &= theGNSS.addCfgValset(ubxMessages[gsvRecordNumber].msgConfigKey,
+ settings.ubxMessageRates[gsvRecordNumber]); // Update rate on module
+ }
- //Serial.printf("currentMeasurementRate / currentNavigationRate: %d / %d\n\r", currentMeasurementRate, currentNavigationRate);
+ response &= theGNSS.sendCfgValset(); // Closing value - max 4 pairs
- float measurementFrequency = (1000.0 / currentMeasurementRate) / currentNavigationRate;
- return (measurementFrequency);
+ // If we successfully set rates, only then record to settings
+ if (response == true)
+ {
+ settings.measurementRate = measRate;
+ settings.navigationRate = navRate;
+ }
+ else
+ {
+ systemPrintln("Failed to set measurement and navigation rates");
+ return (false);
+ }
+
+ return (true);
+}
+
+// Print the module type and firmware version
+void printZEDInfo()
+{
+ if (zedModuleType == PLATFORM_F9P)
+ systemPrintf("ZED-F9P firmware: %s\r\n", zedFirmwareVersion);
+ else if (zedModuleType == PLATFORM_F9R)
+ systemPrintf("ZED-F9R firmware: %s\r\n", zedFirmwareVersion);
+ else
+ // This will never be printed as beginGNSS defaults zedModuleType to PLATFORM_F9P
+ systemPrintf("Unknown module with firmware: %s\r\n", zedFirmwareVersion);
}
-//Updates the enabled constellations
-bool configureConstellations()
+// Print the NEO firmware version
+void printNEOInfo()
{
- bool response = true;
-
- //long startTime = millis();
- for (int x = 0 ; x < MAX_CONSTELLATIONS ; x++)
- {
- //Standard UBX protocol method takes ~533-783ms
- uint8_t currentlyEnabled = getConstellation(ubxConstellations[x].gnssID); //Qeury the module for the current setting
- if (currentlyEnabled != ubxConstellations[x].enabled)
- response &= setConstellation(ubxConstellations[x].gnssID, ubxConstellations[x].enabled);
-
- //Get/set val method takes ~642ms but does not work because we don't send additional sigCfg keys at same time
- // uint8_t currentlyEnabled = i2cGNSS.getVal8(ubxConstellations[x].configKey, VAL_LAYER_RAM, 1200);
- // if (currentlyEnabled != ubxConstellations[x].enabled)
- // response &= i2cGNSS.setVal(ubxConstellations[x].configKey, ubxConstellations[x].enabled);
- }
- //long stopTime = millis();
-
- //Serial.printf("setConstellation time delta: %ld ms\n\r", stopTime - startTime);
-
- return (response);
+ if (productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ systemPrintf("NEO-D9S firmware: %s\r\n", neoFirmwareVersion);
}
diff --git a/Firmware/RTK_Surveyor/menuMain.ino b/Firmware/RTK_Surveyor/menuMain.ino
index 31125739b..aa498b6b1 100644
--- a/Firmware/RTK_Surveyor/menuMain.ino
+++ b/Firmware/RTK_Surveyor/menuMain.ino
@@ -1,123 +1,533 @@
-//Display the options
-//If user doesn't respond within a few seconds, return to main loop
+// Check to see if we've received serial over USB
+// Report status if ~ received, otherwise present config menu
+void updateSerial()
+{
+ if (systemAvailable())
+ {
+ byte incoming = systemRead();
+
+ if (incoming == '~')
+ {
+ // Output custom GNTXT message with all current system data
+ printCurrentConditionsNMEA();
+ }
+ else
+ menuMain(); // Present user menu
+ }
+}
+
+// Display the options
+// If user doesn't respond within a few seconds, return to main loop
void menuMain()
{
- displaySerialConfig(); //Display 'Serial Config' while user is configuring
+ inMainMenu = true;
+ displaySerialConfig(); // Display 'Serial Config' while user is configuring
+
+ while (1)
+ {
+ systemPrintln();
+ char versionString[21];
+ getFirmwareVersion(versionString, sizeof(versionString), true);
+ systemPrintf("SparkFun RTK %s %s\r\n", platformPrefix, versionString);
+
+#ifdef COMPILE_BT
+
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP)
+ systemPrint("** Bluetooth SPP broadcasting as: ");
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ systemPrint("** Bluetooth Low-Energy broadcasting as: ");
+ systemPrint(deviceName);
+ systemPrintln(" **");
+#else // COMPILE_BT
+ systemPrintln("** Bluetooth Not Compiled **");
+#endif // COMPILE_BT
+
+ systemPrintln("Menu: Main");
+
+ systemPrintln("1) Configure GNSS Receiver");
- while (1)
- {
- Serial.println();
- Serial.printf("SparkFun RTK %s v%d.%d-%s\r\n", platformPrefix, FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, __DATE__);
+ systemPrintln("2) Configure GNSS Messages");
- Serial.print(F("** Bluetooth broadcasting as: "));
- Serial.print(deviceName);
- Serial.println(F(" **"));
+ if (zedModuleType == PLATFORM_F9P)
+ systemPrintln("3) Configure Base");
+ else if (zedModuleType == PLATFORM_F9R)
+ systemPrintln("3) Configure Sensor Fusion");
- Serial.println(F("Menu: Main Menu"));
+ systemPrintln("4) Configure Ports");
- Serial.println(F("1) Configure GNSS Receiver"));
+ systemPrintln("5) Configure Logging");
- Serial.println(F("2) Configure GNSS Messages"));
+#ifdef COMPILE_WIFI
+ systemPrintln("6) Configure WiFi");
+#else // COMPILE_WIFI
+ systemPrintln("6) **WiFi Not Compiled**");
+#endif // COMPILE_WIFI
- Serial.println(F("3) Configure Base"));
+#if COMPILE_NETWORK
+ systemPrintln("7) Configure Network");
+#else // COMPILE_NETWORK
+ systemPrintln("7) **Network Not Compiled**");
+#endif // COMPILE_NETWORK
+#ifdef COMPILE_ETHERNET
+ if (HAS_ETHERNET)
+ {
+ systemPrintln("e) Configure Ethernet");
+ systemPrintln("n) Configure NTP");
+ }
+#endif // COMPILE_ETHERNET
+
+ systemPrintln("p) Configure User Profiles");
+
+#ifdef COMPILE_ESPNOW
+ systemPrintln("r) Configure Radios");
+#else // COMPILE_ESPNOW
+ systemPrintln("r) **ESP-Now Not Compiled**");
+#endif // COMPILE_ESPNOW
+
+ if (online.lband == true)
+ systemPrintln("P) Configure PointPerfect");
+
+ systemPrintln("s) Configure System");
+
+ systemPrintln("f) Firmware upgrade");
+
+ if (btPrintEcho)
+ systemPrintln("b) Exit Bluetooth Echo mode");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ menuGNSS();
+ else if (incoming == 2)
+ menuMessages();
+ else if (incoming == 3 && zedModuleType == PLATFORM_F9P)
+ menuBase();
+ else if (incoming == 3 && zedModuleType == PLATFORM_F9R)
+ menuSensorFusion();
+ else if (incoming == 4)
+ menuPorts();
+ else if (incoming == 5)
+ menuLog();
+ else if (incoming == 6)
+ menuWiFi();
+ else if (incoming == 7)
+ menuNetwork();
+ else if (incoming == 'e' && (HAS_ETHERNET))
+ menuEthernet();
+ else if (incoming == 'n' && (HAS_ETHERNET))
+ menuNTP();
+ else if (incoming == 's')
+ menuSystem();
+ else if (incoming == 'p')
+ menuUserProfiles();
+ else if (incoming == 'P' && online.lband == true)
+ menuPointPerfect();
+#ifdef COMPILE_ESPNOW
+ else if (incoming == 'r')
+ menuRadio();
+#endif // COMPILE_ESPNOW
+ else if (incoming == 'f')
+ menuFirmware();
+ else if (incoming == 'b')
+ {
+ printEndpoint = PRINT_ENDPOINT_SERIAL;
+ systemPrintln("BT device has exited echo mode");
+ btPrintEcho = false;
+ break; // Exit config menu
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
- Serial.println(F("4) Configure Ports"));
+ recordSystemSettings(); // Once all menus have exited, record the new settings to LittleFS and config file
- Serial.println(F("5) Configure Logging"));
+ if (online.gnss == true)
+ theGNSS.saveConfiguration(); // Save the current settings to flash and BBR on the ZED-F9P
- if (settings.enableSD == true && online.microSD == true)
+ // Reboot as base only if currently operating as a base station
+ if (restartBase && (systemState >= STATE_BASE_NOT_STARTED) && (systemState < STATE_BUBBLE_LEVEL))
{
- Serial.println(F("6) Display microSD contents"));
+ restartBase = false;
+ requestChangeState(STATE_BASE_NOT_STARTED); // Restart base upon exit for latest changes to take effect
}
- if (online.accelerometer == true)
- Serial.println(F("b) Bubble Level"));
+ if (restartRover == true)
+ {
+ restartRover = false;
+ requestChangeState(STATE_ROVER_NOT_STARTED); // Restart rover upon exit for latest changes to take effect
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+ btPrintEchoExit = false; // We are out of the menu system
+ inMainMenu = false;
+}
+
+// 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 updateZEDSettings = true
+// Profile - Before profile is changed, set updateZEDSettings = true
+// AP - once new settings are parsed, set updateZEDSettings = true
+// Setup button -
+// Factory reset - updatesZEDSettings = true by default
+void menuUserProfiles()
+{
+ uint8_t originalProfileNumber = profileNumber;
+
+ bool forceReset =
+ false; // If we reset a profile to default, the profile number has not changed, but we still need to reset
+
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: User Profiles");
+
+ // List available profiles
+ for (int x = 0; x < MAX_PROFILE_COUNT; x++)
+ {
+ if (activeProfiles & (1 << x))
+ systemPrintf("%d) Select %s", x + 1, profileNames[x]);
+ else
+ systemPrintf("%d) Select (Empty)", x + 1);
+
+ if (x == profileNumber)
+ systemPrint(" <- Current");
+
+ systemPrintln();
+ }
+
+ systemPrintf("%d) Edit profile name: %s\r\n", MAX_PROFILE_COUNT + 1, profileNames[profileNumber]);
+
+ systemPrintf("%d) Set profile '%s' to factory defaults\r\n", MAX_PROFILE_COUNT + 2,
+ profileNames[profileNumber]);
+
+ systemPrintf("%d) Delete profile '%s'\r\n", MAX_PROFILE_COUNT + 3, profileNames[profileNumber]);
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming >= 1 && incoming <= MAX_PROFILE_COUNT)
+ {
+ changeProfileNumber(incoming - 1); // Align inputs to array
+ }
+ else if (incoming == MAX_PROFILE_COUNT + 1)
+ {
+ systemPrint("Enter new profile name: ");
+ getString(settings.profileName, sizeof(settings.profileName));
+ recordSystemSettings(); // We need to update this immediately in case user lists the available profiles
+ // again
+ setProfileName(profileNumber);
+ }
+ else if (incoming == MAX_PROFILE_COUNT + 2)
+ {
+ systemPrintf("\r\nReset profile '%s' to factory defaults. Press 'y' to confirm:",
+ profileNames[profileNumber]);
+ byte bContinue = getCharacterNumber();
+ if (bContinue == 'y')
+ {
+ settingsToDefaults(); // Overwrite our current settings with defaults
+
+ recordSystemSettings(); // Overwrite profile file and NVM with these settings
+
+ // Get bitmask of active profiles
+ activeProfiles = loadProfileNames();
- Serial.println(F("d) Configure Debug"));
+ forceReset = true; // Upon exit of menu, reset the device
+ }
+ else
+ systemPrintln("Reset aborted");
+ }
+ else if (incoming == MAX_PROFILE_COUNT + 3)
+ {
+ systemPrintf("\r\nDelete profile '%s'. Press 'y' to confirm:", profileNames[profileNumber]);
+ byte bContinue = getCharacterNumber();
+ if (bContinue == 'y')
+ {
+ // Remove profile from LittleFS
+ if (LittleFS.exists(settingsFileName))
+ LittleFS.remove(settingsFileName);
+
+ // Remove profile from SD if available
+ if (online.microSD == true)
+ {
+ if (USE_SPI_MICROSD)
+ {
+ if (sd->exists(settingsFileName))
+ sd->remove(settingsFileName);
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ if (SD_MMC.exists(settingsFileName))
+ SD_MMC.remove(settingsFileName);
+ }
+#endif // COMPILE_SD_MMC
+ }
- Serial.println(F("r) Reset all settings to default"));
+ recordProfileNumber(0); // Move to Profile1
+ profileNumber = 0;
- if (binCount > 0)
- Serial.println(F("f) Firmware upgrade"));
+ snprintf(settingsFileName, sizeof(settingsFileName), "/%s_Settings_%d.txt", platformFilePrefix,
+ profileNumber); // Update file name with new profileNumber
- //Serial.println(F("t) Test menu"));
+ // We need to load these settings from file so that we can record a profile name change correctly
+ bool responseLFS = loadSystemSettingsFromFileLFS(settingsFileName, &settings);
+ bool responseSD = loadSystemSettingsFromFileSD(settingsFileName, &settings);
- Serial.println(F("x) Exit"));
+ // If this is an empty/new profile slot, overwrite our current settings with defaults
+ if (responseLFS == false && responseSD == false)
+ {
+ settingsToDefaults();
+ }
- byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds
+ // Get bitmask of active profiles
+ activeProfiles = loadProfileNames();
+ }
+ else
+ systemPrintln("Delete aborted");
+ }
+
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
- if (incoming == '1')
- menuGNSS();
- else if (incoming == '2')
- menuMessages();
- else if (incoming == '3')
- menuBase();
- else if (incoming == '4')
- menuPorts();
- else if (incoming == '5')
- menuLog();
- else if (incoming == '6' && settings.enableSD == true && online.microSD == true)
+ if (originalProfileNumber != profileNumber || forceReset == true)
{
- //Attempt to write to file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile() and F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
- {
- Serial.println(F("Files found (date time size name):\n\r"));
- sd.ls(LS_R | LS_DATE | LS_SIZE);
-
- xSemaphoreGive(xFATSemaphore);
- }
+ systemPrintln("Rebooting to apply new profile settings. Goodbye!");
+ delay(2000);
+ ESP.restart();
}
- else if (incoming == 'd')
- menuDebug();
- else if (incoming == 'r')
+
+ // A user may edit the name of a profile, but then switch back to original profile.
+ // Thus, no reset, and activeProfiles is not updated. Do it here.
+ // Get bitmask of active profiles
+ activeProfiles = loadProfileNames();
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Change the active profile number, without unit reset
+void changeProfileNumber(byte newProfileNumber)
+{
+ settings.updateZEDSettings = true; // When this profile is loaded next, force system to update ZED settings.
+ recordSystemSettings(); // Before switching, we need to record the current settings to LittleFS and SD
+
+ recordProfileNumber(newProfileNumber);
+ profileNumber = newProfileNumber;
+ setSettingsFileName(); // Load the settings file name into memory (enabled profile name delete)
+
+ // We need to load these settings from file so that we can record a profile name change correctly
+ bool responseLFS = loadSystemSettingsFromFileLFS(settingsFileName, &settings);
+ bool responseSD = loadSystemSettingsFromFileSD(settingsFileName, &settings);
+
+ // If this is an empty/new profile slot, overwrite our current settings with defaults
+ if (responseLFS == false && responseSD == false)
{
- Serial.println(F("\r\nResetting to factory defaults. Press 'y' to confirm:"));
- byte bContinue = getByteChoice(menuTimeout);
- if (bContinue == 'y')
- {
- eepromErase();
-
- //Assemble settings file name
- char settingsFileName[40]; //SFE_Surveyor_Settings.txt
- strcpy(settingsFileName, platformFilePrefix);
- strcat(settingsFileName, "_Settings.txt");
-
- //Attempt to write to file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile() and F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
- {
- if (sd.exists(settingsFileName))
- sd.remove(settingsFileName);
- xSemaphoreGive(xFATSemaphore);
- } //End xFATSemaphore
-
- i2cGNSS.factoryReset(); //Reset everything: baud rate, I2C address, update rate, everything.
-
- Serial.println(F("Settings erased. Please reset RTK Surveyor. Freezing."));
- while (1)
- delay(1); //Prevent CPU freakout
- }
- else
- Serial.println(F("Reset aborted"));
+ systemPrintln("No profile found: Applying default settings");
+ settingsToDefaults();
}
- else if (incoming == 'f' && binCount > 0)
- menuFirmware();
- else if (incoming == 't')
- menuTest();
- else if (incoming == 'b')
+}
+
+// Erase all settings. Upon restart, unit will use defaults
+void factoryReset(bool alreadyHasSemaphore)
+{
+ displaySytemReset(); // Display friendly message on OLED
+
+ tasksStopUART2();
+
+ // Attempt to write to file system. This avoids collisions with file writing from other functions like
+ // recordSystemSettingsToFile() and F9PSerialReadTask() if (settings.enableSD && online.microSD)
+ //Don't check settings.enableSD - it could be corrupt
+ if (online.microSD)
{
- if (online.accelerometer == true) menuBubble();
+ if (alreadyHasSemaphore == true || xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ if (USE_SPI_MICROSD)
+ {
+ // Remove this specific settings file. Don't remove the other profiles.
+ sd->remove(settingsFileName);
+
+ sd->remove(stationCoordinateECEFFileName); // Remove station files
+ sd->remove(stationCoordinateGeodeticFileName);
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ SD_MMC.remove(settingsFileName);
+
+ SD_MMC.remove(stationCoordinateECEFFileName); // Remove station files
+ SD_MMC.remove(stationCoordinateGeodeticFileName);
+ }
+#endif // COMPILE_SD_MMC
+
+ xSemaphoreGive(sdCardSemaphore);
+ } // End sdCardSemaphore
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+
+ // 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__);
+ }
}
- else if (incoming == 'x')
- break;
- else if (incoming == STATUS_GETBYTE_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
- recordSystemSettings(); //Once all menus have exited, record the new settings to EEPROM and config file
+ systemPrintln("Formatting internal file system...");
+ LittleFS.format();
+
+ if (online.gnss == true)
+ {
+ systemPrintln("Factory resetting the GNSS receiver...");
+ theGNSS.factoryDefault(); // Reset everything: baud rate, I2C address, update rate, everything. And save to BBR.
+ theGNSS.saveConfiguration();
+ theGNSS.hardReset(); // Perform a reset leading to a cold start (zero info start-up)
+ }
+
+ systemPrintln("Settings erased successfully. Rebooting. Goodbye!");
+ delay(2000);
+ ESP.restart();
+}
+
+// Configure the internal radio, if available
+void menuRadio()
+{
+#ifdef COMPILE_ESPNOW
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Radios");
+
+ systemPrint("1) Select Radio Type: ");
+ if (settings.radioType == RADIO_EXTERNAL)
+ systemPrintln("External only");
+ else if (settings.radioType == RADIO_ESPNOW)
+ systemPrintln("Internal ESP-Now");
+
+ if (settings.radioType == RADIO_ESPNOW)
+ {
+ // Pretty print the MAC of all radios
+ systemPrint(" Radio MAC: ");
+ for (int x = 0; x < 5; x++)
+ systemPrintf("%02X:", wifiMACAddress[x]);
+ systemPrintf("%02X\r\n", wifiMACAddress[5]);
+
+ if (settings.espnowPeerCount > 0)
+ {
+ systemPrintln(" Paired Radios: ");
+ for (int x = 0; x < settings.espnowPeerCount; x++)
+ {
+ systemPrint(" ");
+ for (int y = 0; y < 5; y++)
+ systemPrintf("%02X:", settings.espnowPeers[x][y]);
+ systemPrintf("%02X\r\n", settings.espnowPeers[x][5]);
+ }
+ }
+ else
+ systemPrintln(" No Paired Radios");
+
+ systemPrintln("2) Pair radios");
+ systemPrintln("3) Forget all radios");
+ if (ENABLE_DEVELOPER)
+ {
+ systemPrintln("4) Add dummy radio");
+ systemPrintln("5) Send dummy data");
+ systemPrintln("6) Broadcast dummy data");
+ }
+ }
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ if (settings.radioType == RADIO_EXTERNAL)
+ settings.radioType = RADIO_ESPNOW;
+ else if (settings.radioType == RADIO_ESPNOW)
+ settings.radioType = RADIO_EXTERNAL;
+ }
+ else if (settings.radioType == RADIO_ESPNOW && incoming == 2)
+ {
+ espnowStaticPairing();
+ }
+ else if (settings.radioType == RADIO_ESPNOW && incoming == 3)
+ {
+ systemPrintln("\r\nForgetting all paired radios. Press 'y' to confirm:");
+ byte bContinue = getCharacterNumber();
+ if (bContinue == 'y')
+ {
+ if (espnowState > ESPNOW_OFF)
+ {
+ for (int x = 0; x < settings.espnowPeerCount; x++)
+ espnowRemovePeer(settings.espnowPeers[x]);
+ }
+ settings.espnowPeerCount = 0;
+ systemPrintln("Radios forgotten");
+ }
+ }
+ else if (ENABLE_DEVELOPER && settings.radioType == RADIO_ESPNOW && incoming == 4)
+ {
+ uint8_t peer1[] = {0xB8, 0xD6, 0x1A, 0x0D, 0x8F, 0x9C}; // Random MAC
+ if (esp_now_is_peer_exist(peer1) == true)
+ log_d("Peer already exists");
+ else
+ {
+ // Add new peer to system
+ espnowAddPeer(peer1);
+
+ // Record this MAC to peer list
+ memcpy(settings.espnowPeers[settings.espnowPeerCount], peer1, 6);
+ settings.espnowPeerCount++;
+ settings.espnowPeerCount %= ESPNOW_MAX_PEERS;
+ recordSystemSettings();
+ }
+
+ espnowSetState(ESPNOW_PAIRED);
+ }
+ else if (ENABLE_DEVELOPER && settings.radioType == RADIO_ESPNOW && incoming == 5)
+ {
+ uint8_t espnowData[] =
+ "This is the long string to test how quickly we can send one string to the other unit. I am going to "
+ "need a much longer sentence if I want to get a long amount of data into one transmission. This is "
+ "nearing 200 characters but needs to be near 250.";
+ esp_now_send(0, (uint8_t *)&espnowData, sizeof(espnowData)); // Send packet to all peers
+ }
+ else if (ENABLE_DEVELOPER && settings.radioType == RADIO_ESPNOW && incoming == 6)
+ {
+ uint8_t espnowData[] =
+ "This is the long string to test how quickly we can send one string to the other unit. I am going to "
+ "need a much longer sentence if I want to get a long amount of data into one transmission. This is "
+ "nearing 200 characters but needs to be near 250.";
+ uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ esp_now_send(broadcastMac, (uint8_t *)&espnowData, sizeof(espnowData)); // Send packet to all peers
+ }
+
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
- i2cGNSS.saveConfiguration(); //Save the current settings to flash and BBR on the ZED-F9P
+ radioStart();
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ clearBuffer(); // Empty buffer of any newline chars
+#endif // COMPILE_ESPNOW
}
diff --git a/Firmware/RTK_Surveyor/menuMessages.ino b/Firmware/RTK_Surveyor/menuMessages.ino
index cfe84c065..75448f724 100644
--- a/Firmware/RTK_Surveyor/menuMessages.ino
+++ b/Firmware/RTK_Surveyor/menuMessages.ino
@@ -1,521 +1,1144 @@
-//Control the messages that get logged to SD
-//Control max logging time (limit to a certain number of minutes)
-//The main use case is the setup for a base station to log RAW sentences that then get post processed
+// Control the messages that get logged to SD
+// Control max logging time (limit to a certain number of minutes)
+// The main use case is the setup for a base station to log RAW sentences that then get post processed
void menuLog()
{
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Logging Menu"));
-
- if (settings.enableSD && online.microSD)
- Serial.println(F("microSD card is online"));
- else
+ while (1)
{
- beginSD(); //Test if SD is present
- if (online.microSD == true)
- Serial.println(F("microSD card online"));
- else
- Serial.println(F("No microSD card is detected"));
- }
+ systemPrintln();
+ systemPrintln("Menu: Logging");
- Serial.print(F("1) Log to microSD: "));
- if (settings.enableLogging == true) Serial.println(F("Enabled"));
- else Serial.println(F("Disabled"));
+ if (settings.enableSD && online.microSD)
+ {
+ char sdCardSizeChar[20];
+ String cardSize;
+ stringHumanReadableSize(cardSize, sdCardSize);
+ cardSize.toCharArray(sdCardSizeChar, sizeof(sdCardSizeChar));
+ char sdFreeSpaceChar[20];
+ String freeSpace;
+ stringHumanReadableSize(freeSpace, sdFreeSpace);
+ freeSpace.toCharArray(sdFreeSpaceChar, sizeof(sdFreeSpaceChar));
+
+ char myString[60];
+ snprintf(myString, sizeof(myString), "SD card size: %s / Free space: %s", sdCardSizeChar, sdFreeSpaceChar);
+ systemPrintln(myString);
+
+ if (online.logging)
+ {
+ systemPrintf("Current log file name: %s\r\n", logFileName);
+ }
+ }
+ else
+ systemPrintln("No microSD card is detected");
- if (settings.enableLogging == true)
- {
- Serial.print(F("2) Set max logging time: "));
- Serial.print(settings.maxLogTime_minutes);
- Serial.println(F(" minutes"));
- }
+ if (bufferOverruns)
+ systemPrintf("Buffer overruns: %d\r\n", bufferOverruns);
- Serial.println(F("x) Exit"));
+ systemPrint("1) Log to microSD: ");
+ if (settings.enableLogging == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
- byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds
+ if (settings.enableLogging == true)
+ {
+ systemPrint("2) Set max logging time: ");
+ systemPrint(settings.maxLogTime_minutes);
+ systemPrintln(" minutes");
- if (incoming == '1')
- {
- settings.enableLogging ^= 1;
- }
- else if (incoming == '2' && settings.enableLogging == true)
- {
- Serial.print(F("Enter max minutes to log data: "));
- int maxMinutes = getNumber(menuTimeout); //Timeout after x seconds
- if (maxMinutes < 0 || maxMinutes > 60 * 48) //Arbitrary 48 hour limit
- {
- Serial.println(F("Error: max minutes out of range"));
- }
- else
- {
- settings.maxLogTime_minutes = maxMinutes; //Recorded to NVM and file at main menu exit
- }
- }
- else if (incoming == 'x')
- break;
- else if (incoming == STATUS_GETBYTE_TIMEOUT)
- {
- break;
+ systemPrint("3) Set max log length: ");
+ systemPrint(settings.maxLogLength_minutes);
+ systemPrintln(" minutes");
+
+ if (online.logging == true)
+ systemPrintln("4) Start new log");
+
+ systemPrint("5) Log Antenna Reference Position from RTCM 1005/1006: ");
+ if (settings.enableARPLogging == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ if (settings.enableARPLogging == true)
+ {
+ systemPrint("6) Set ARP logging interval: ");
+ systemPrint(settings.ARPLoggingInterval_s);
+ systemPrintln(" seconds");
+ }
+ }
+
+ systemPrint("7) Write Marks_date.csv file to microSD: ");
+ if (settings.enableMarksFile == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ systemPrint("8) Reset system if the SD card is detected but fails to initialize: ");
+ if (settings.forceResetOnSDFail == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ if (HAS_ETHERNET)
+ {
+ systemPrint("9) Write NTP requests to microSD: ");
+ if (settings.enableNTPFile == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+ }
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ settings.enableLogging ^= 1;
+
+ // Reset the maximum logging time when logging is disabled to ensure that
+ // the next time logging is enabled that the maximum amount of data can be
+ // captured.
+ if (settings.enableLogging == false)
+ startLogTime_minutes = 0;
+ }
+ else if (incoming == 2 && settings.enableLogging == true)
+ {
+ systemPrint("Enter max minutes before logging stops: ");
+ int maxMinutes = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((maxMinutes != INPUT_RESPONSE_GETNUMBER_EXIT) && (maxMinutes != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (maxMinutes < 0 ||
+ maxMinutes >
+ (60 * 24 * 365 *
+ 2)) // Arbitrary 2 year limit. See https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/86
+ systemPrintln("Error: Max minutes out of range");
+ else
+ settings.maxLogTime_minutes = maxMinutes; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 3 && settings.enableLogging == true)
+ {
+ systemPrint("Enter max minutes of logging before new log is created: ");
+ int maxLogMinutes = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((maxLogMinutes != INPUT_RESPONSE_GETNUMBER_EXIT) && (maxLogMinutes != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (maxLogMinutes < 0 || maxLogMinutes > 60 * 48) // Arbitrary 48 hour limit
+ systemPrintln("Error: Max minutes out of range");
+ else
+ settings.maxLogLength_minutes = maxLogMinutes; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 4 && settings.enableLogging == true && online.logging == true)
+ {
+ endLogging(false, true); //(gotSemaphore, releaseSemaphore) Close file. Reset parser stats.
+ beginLogging(); // Create new file based on current RTC.
+ setLoggingType(); // Determine if we are standard, PPP, or custom. Changes logging icon accordingly.
+ }
+ else if (incoming == 5 && settings.enableLogging == true && online.logging == true)
+ {
+ settings.enableARPLogging ^= 1;
+ }
+ else if (incoming == 6 && settings.enableLogging == true && settings.enableARPLogging == true)
+ {
+ systemPrint("Enter the ARP logging interval in seconds: ");
+ int logSecs = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((logSecs != INPUT_RESPONSE_GETNUMBER_EXIT) && (logSecs != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (logSecs < 1 || logSecs > 600) // Arbitrary 10 minute limit
+ systemPrintln("Error: Logging interval out of range");
+ else
+ settings.ARPLoggingInterval_s = logSecs; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 7)
+ {
+ settings.enableMarksFile ^= 1;
+ }
+ else if (incoming == 8)
+ {
+ settings.forceResetOnSDFail ^= 1;
+ }
+ else if ((HAS_ETHERNET) && (incoming == 9))
+ {
+ settings.enableNTPFile ^= 1;
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else
- printUnknown(incoming);
- }
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ clearBuffer(); // Empty buffer of any newline chars
}
-//Control the messages that get broadcast over Bluetooth and logged (if enabled)
+// Control the messages that get broadcast over Bluetooth and logged (if enabled)
void menuMessages()
{
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Messages Menu"));
-
- Serial.printf("Active messages: %d\n\r", getActiveMessageCount());
-
- Serial.println(F("1) Set NMEA Messages"));
- Serial.println(F("2) Set RTCM Messages"));
- Serial.println(F("3) Set RXM Messages"));
- Serial.println(F("4) Set NAV Messages"));
- Serial.println(F("5) Set MON Messages"));
- Serial.println(F("6) Set TIM Messages"));
- Serial.println(F("7) Reset to Surveying Defaults (NMEAx5)"));
- Serial.println(F("8) Reset to PPP Logging Defaults (NMEAx5 + RXMx2)"));
- Serial.println(F("9) Turn off all messages"));
- Serial.println(F("10) Turn on all messages"));
-
- Serial.println(F("x) Exit"));
-
- int incoming = getNumber(menuTimeout); //Timeout after x seconds
-
- if (incoming == 1)
- menuMessagesSubtype((char*)"NMEA");
- else if (incoming == 2)
- menuMessagesSubtype((char*)"RTCM");
- else if (incoming == 3)
- menuMessagesSubtype((char*)"RXM");
- else if (incoming == 4)
- menuMessagesSubtype((char*)"NAV");
- else if (incoming == 5)
- menuMessagesSubtype((char*)"MON");
- else if (incoming == 6)
- menuMessagesSubtype((char*)"TIM");
- else if (incoming == 7)
- {
- setGNSSMessageRates(ubxMessages, 0); //Turn off all messages
- setMessageRateByName((char*)"UBX_NMEA_GGA", 1);
- setMessageRateByName((char*)"UBX_NMEA_GSA", 1);
- setMessageRateByName((char*)"UBX_NMEA_GST", 1);
- setMessageRateByName((char*)"UBX_NMEA_GSV", 4); //One update per 4 fixes to avoid swamping SPP connection
- setMessageRateByName((char*)"UBX_NMEA_RMC", 1);
- Serial.println(F("Reset to Surveying Defaults (NMEAx5)"));
- }
- else if (incoming == 8)
- {
- setGNSSMessageRates(ubxMessages, 0); //Turn off all messages
- setMessageRateByName((char*)"UBX_NMEA_GGA", 1);
- setMessageRateByName((char*)"UBX_NMEA_GSA", 1);
- setMessageRateByName((char*)"UBX_NMEA_GST", 1);
- setMessageRateByName((char*)"UBX_NMEA_GSV", 4); //One update per 4 fixes to avoid swamping SPP connection
- setMessageRateByName((char*)"UBX_NMEA_RMC", 1);
-
- setMessageRateByName((char*)"UBX_RXM_RAWX", 1);
- setMessageRateByName((char*)"UBX_RXM_SFRBX", 1);
- Serial.println(F("Reset to PPP Logging Defaults (NMEAx5 + RXMx2)"));
- }
- else if (incoming == 9)
- {
- setGNSSMessageRates(ubxMessages, 0); //Turn off all messages
- Serial.println(F("All messages disabled"));
- }
- else if (incoming == 10)
+ while (1)
{
- setGNSSMessageRates(ubxMessages, 1); //Turn on all messages to report once per fix
- Serial.println(F("All messages enabled"));
+ systemPrintln();
+ systemPrintln("Menu: GNSS Messages");
+
+ systemPrintf("Active messages: %d\r\n", getActiveMessageCount());
+
+ systemPrintln("1) Set NMEA Messages");
+ if (zedModuleType == PLATFORM_F9P)
+ systemPrintln("2) Set RTCM Messages");
+ else if (zedModuleType == PLATFORM_F9R)
+ systemPrintln("2) Set ESF Messages");
+ systemPrintln("3) Set RXM Messages");
+ systemPrintln("4) Set NAV Messages");
+ systemPrintln("5) Set NAV2 Messages");
+ systemPrintln("6) Set NMEA NAV2 Messages");
+ systemPrintln("7) Set MON Messages");
+ systemPrintln("8) Set TIM Messages");
+ systemPrintln("9) Set PUBX Messages");
+
+ systemPrintln("10) Reset to Surveying Defaults (NMEAx5)");
+ systemPrintln("11) Reset to PPP Logging Defaults (NMEAx5 + RXMx2)");
+ systemPrintln("12) Turn off all messages");
+ systemPrintln("13) Turn on all messages");
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ menuMessagesSubtype(settings.ubxMessageRates, "NMEA_"); // The following _ avoids listing NMEANAV2 messages
+ else if (incoming == 2 && zedModuleType == PLATFORM_F9P)
+ menuMessagesSubtype(settings.ubxMessageRates, "RTCM");
+ else if (incoming == 2 && zedModuleType == PLATFORM_F9R)
+ menuMessagesSubtype(settings.ubxMessageRates, "ESF");
+ else if (incoming == 3)
+ menuMessagesSubtype(settings.ubxMessageRates, "RXM");
+ else if (incoming == 4)
+ menuMessagesSubtype(settings.ubxMessageRates, "NAV_"); // The following _ avoids listing NAV2 messages
+ else if (incoming == 5)
+ menuMessagesSubtype(settings.ubxMessageRates, "NAV2");
+ else if (incoming == 6)
+ menuMessagesSubtype(settings.ubxMessageRates, "NMEANAV2");
+ else if (incoming == 7)
+ menuMessagesSubtype(settings.ubxMessageRates, "MON");
+ else if (incoming == 8)
+ menuMessagesSubtype(settings.ubxMessageRates, "TIM");
+ else if (incoming == 9)
+ menuMessagesSubtype(settings.ubxMessageRates, "PUBX");
+ else if (incoming == 10)
+ {
+ setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages
+ setMessageRateByName("UBX_NMEA_GGA", 1);
+ setMessageRateByName("UBX_NMEA_GSA", 1);
+ setMessageRateByName("UBX_NMEA_GST", 1);
+
+ // We want GSV NMEA to be reported at 1Hz to avoid swamping SPP connection
+ float measurementFrequency = (1000.0 / settings.measurementRate) / settings.navigationRate;
+ if (measurementFrequency < 1.0)
+ measurementFrequency = 1.0;
+ setMessageRateByName("UBX_NMEA_GSV", measurementFrequency); // One report per second
+
+ setMessageRateByName("UBX_NMEA_RMC", 1);
+ systemPrintln("Reset to Surveying Defaults (NMEAx5)");
+ }
+ else if (incoming == 11)
+ {
+ setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages
+ setMessageRateByName("UBX_NMEA_GGA", 1);
+ setMessageRateByName("UBX_NMEA_GSA", 1);
+ setMessageRateByName("UBX_NMEA_GST", 1);
+
+ // We want GSV NMEA to be reported at 1Hz to avoid swamping SPP connection
+ float measurementFrequency = (1000.0 / settings.measurementRate) / settings.navigationRate;
+ if (measurementFrequency < 1.0)
+ measurementFrequency = 1.0;
+ setMessageRateByName("UBX_NMEA_GSV", measurementFrequency); // One report per second
+
+ setMessageRateByName("UBX_NMEA_RMC", 1);
+
+ setMessageRateByName("UBX_RXM_RAWX", 1);
+ setMessageRateByName("UBX_RXM_SFRBX", 1);
+ systemPrintln("Reset to PPP Logging Defaults (NMEAx5 + RXMx2)");
+ }
+ else if (incoming == 12)
+ {
+ setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages
+ systemPrintln("All messages disabled");
+ }
+ else if (incoming == 13)
+ {
+ setGNSSMessageRates(settings.ubxMessageRates, 1); // Turn on all messages to report once per fix
+ systemPrintln("All messages enabled");
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ clearBuffer(); // Empty buffer of any newline chars
- bool response = configureGNSSMessageRates(COM_PORT_UART1, ubxMessages); //Make sure the appropriate messages are enabled
- if (response == false)
- {
- Serial.println(F("menuMessages: Failed to enable UART1 messages - Try 1"));
- //Try again
- response = configureGNSSMessageRates(COM_PORT_UART1, ubxMessages); //Make sure the appropriate messages are enabled
+ // Make sure the appropriate messages are enabled
+ bool response = setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set
if (response == false)
- Serial.println(F("menuMessages: Failed to enable UART1 messages - Try 2"));
+ systemPrintf("menuMessages: Failed to enable messages - after %d tries", MAX_SET_MESSAGES_RETRIES);
else
- Serial.println(F("menuMessages: UART1 messages successfully enabled"));
- }
- else
- {
- Serial.println(F("menuMessages: UART1 messages successfully enabled"));
- }
+ systemPrintln("menuMessages: Messages successfully enabled");
+ setLoggingType(); // Update Standard, PPP, or custom for icon selection
}
-//Given a sub type (ie "RTCM", "NMEA") present menu showing messages with this subtype
-//Controls the messages that get broadcast over Bluetooth and logged (if enabled)
-void menuMessagesSubtype(char* messageType)
+// Control the RTCM message rates when in Base mode
+void menuMessagesBaseRTCM()
{
- while (1)
- {
- Serial.println();
- Serial.printf("Menu: Message %s Menu\n\r", messageType);
-
- int startOfBlock = 0;
- int endOfBlock = 0;
- setMessageOffsets(messageType, startOfBlock, endOfBlock); //Find start and stop of RTCM records in message array
- for (int x = 0 ; x < (endOfBlock - startOfBlock) ; x++)
+ while (1)
{
- Serial.printf("%d) Message %s: ", x + 1, ubxMessages[x + startOfBlock].msgTextName);
- Serial.println(ubxMessages[x + startOfBlock].msgRate);
- }
+ systemPrintln();
+ systemPrintln("Menu: GNSS Messages - Base RTCM");
- Serial.println(F("x) Exit"));
+ systemPrintln("1) Set RXM Messages for Base Mode");
+ systemPrintln("2) Reset to Defaults (1005/74/84/94/124 1Hz & 1230 0.1Hz)");
+ systemPrintln("3) Reset to Low Bandwidth Link (1074/84/94/124 0.5Hz & 1005/230 0.1Hz)");
- int incoming = getNumber(menuTimeout); //Timeout after x seconds
+ systemPrintln("x) Exit");
- if (incoming >= 1 && incoming <= (endOfBlock - startOfBlock))
- {
- inputMessageRate(ubxMessages[ (incoming - 1) + startOfBlock]);
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ menuMessagesSubtype(settings.ubxMessageRatesBase, "RTCM-Base");
+ restartBase = true;
+ }
+ else if (incoming == 2)
+ {
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1005") - firstRTCMRecord] = 1; // 1105
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1074") - firstRTCMRecord] = 1; // 1074
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1077") - firstRTCMRecord] = 0; // 1077
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1084") - firstRTCMRecord] = 1; // 1084
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1087") - firstRTCMRecord] = 0; // 1087
+
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1094") - firstRTCMRecord] = 1; // 1094
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1097") - firstRTCMRecord] = 0; // 1097
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1124") - firstRTCMRecord] = 1; // 1124
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1127") - firstRTCMRecord] = 0; // 1127
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1230") - firstRTCMRecord] = 10; // 1230
+
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_4072_0") - firstRTCMRecord] = 0; // 4072_0
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_4072_1") - firstRTCMRecord] = 0; // 4072_1
+
+ systemPrintln("Reset to Defaults (1005/74/84/94/124 1Hz & 1230 0.1Hz)");
+ restartBase = true;
+ }
+ else if (incoming == 3)
+ {
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1005") - firstRTCMRecord] = 10; // 1105 0.1Hz
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1074") - firstRTCMRecord] = 2; // 1074 0.5Hz
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1077") - firstRTCMRecord] = 0; // 1077
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1084") - firstRTCMRecord] = 2; // 1084 0.5Hz
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1087") - firstRTCMRecord] = 0; // 1087
+
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1094") - firstRTCMRecord] = 2; // 1094 0.5Hz
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1097") - firstRTCMRecord] = 0; // 1097
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1124") - firstRTCMRecord] = 2; // 1124 0.5Hz
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1127") - firstRTCMRecord] = 0; // 1127
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_1230") - firstRTCMRecord] = 10; // 1230 0.1Hz
+
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_4072_0") - firstRTCMRecord] = 0; // 4072_0
+ settings.ubxMessageRatesBase[getMessageNumberByName("UBX_RTCM_4072_1") - firstRTCMRecord] = 0; // 4072_1
+
+ systemPrintln("Reset to Low Bandwidth Link (1074/84/94/124 0.5Hz & 1005/230 0.1Hz)");
+ restartBase = true;
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ clearBuffer(); // Empty buffer of any newline chars
}
-//Prompt the user to enter the message rate for a given ID
-//Assign the given value to the message
-void inputMessageRate(ubxMsg &localMessage)
+// Given a sub type (ie "RTCM", "NMEA") present menu showing messages with this subtype
+// Controls the messages that get broadcast over Bluetooth and logged (if enabled)
+void menuMessagesSubtype(uint8_t *localMessageRate, const char *messageType)
{
- Serial.printf("Enter %s message rate (0 to disable): ", localMessage.msgTextName);
- int64_t rate = getNumber(menuTimeout); //Timeout after x seconds
+ while (1)
+ {
+ systemPrintln();
+ systemPrintf("Menu: Message %s\r\n", messageType);
+
+ int startOfBlock = 0;
+ int endOfBlock = 0;
+ int rtcmOffset = 0; // Used to offset messageSupported lookup
+
+ if (strcmp(messageType, "RTCM-Base") == 0) // The ubxMessageRatesBase array is 0 to MAX_UBX_MSG_RTCM - 1
+ {
+ startOfBlock = 0;
+ endOfBlock = MAX_UBX_MSG_RTCM;
+ rtcmOffset = getMessageNumberByName("UBX_RTCM_1005");
+ }
+ else
+ setMessageOffsets(&ubxMessages[0], messageType, startOfBlock,
+ endOfBlock); // Find start and stop of given messageType in message array
+
+ for (int x = 0; x < (endOfBlock - startOfBlock); x++)
+ {
+ // Check to see if this ZED platform supports this message
+ if (messageSupported(x + startOfBlock + rtcmOffset) == true)
+ {
+ systemPrintf("%d) Message %s: ", x + 1, ubxMessages[x + startOfBlock + rtcmOffset].msgTextName);
+ systemPrintln(localMessageRate[x + startOfBlock]);
+ }
+ }
+
+ systemPrintln("x) Exit");
- while (rate < 0 || rate > 60) //Arbitrary 60 fixes per report limit
- {
- Serial.println(F("Error: message rate out of range"));
- Serial.printf("Enter %s message rate (0 to disable): ", localMessage.msgTextName);
- rate = getNumber(menuTimeout); //Timeout after x seconds
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
- if (rate == STATUS_GETNUMBER_TIMEOUT || rate == STATUS_PRESSED_X)
- return; //Give up
- }
+ if (incoming >= 1 && incoming <= (endOfBlock - startOfBlock))
+ {
+ // Check to see if this ZED platform supports this message
+ int msgNumber = (incoming - 1) + startOfBlock;
- if (rate == STATUS_GETNUMBER_TIMEOUT || rate == STATUS_PRESSED_X)
- return;
+ if (messageSupported(msgNumber + rtcmOffset) == true)
+ inputMessageRate(localMessageRate[msgNumber], msgNumber + rtcmOffset);
+ else
+ printUnknown(incoming);
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
- localMessage.msgRate = rate;
+ clearBuffer(); // Empty buffer of any newline chars
}
-//Updates the message rates on the ZED-F9P for all known messages
-//Any port and messages by reference can be passed in. This allows us to modify the USB
-//port settings a separate (not NVM backed) message struct for testing
-bool configureGNSSMessageRates(uint8_t portType, ubxMsg *localMessage)
+// Prompt the user to enter the message rate for a given message ID
+// Assign the given value to the message
+void inputMessageRate(uint8_t &localMessageRate, uint8_t messageNumber)
{
- bool response = true;
+ systemPrintf("Enter %s message rate (0 to disable): ", ubxMessages[messageNumber].msgTextName);
+ int rate = getNumber(); // Returns EXIT, TIMEOUT, or long
- for (int x = 0 ; x < MAX_UBX_MSG ; x++)
- response &= configureMessageRate(portType, localMessage[x]);
+ if (rate == INPUT_RESPONSE_GETNUMBER_TIMEOUT || rate == INPUT_RESPONSE_GETNUMBER_EXIT)
+ return;
- return (response);
+ while (rate < 0 || rate > 255) // 8 bit limit
+ {
+ systemPrintln("Error: Message rate out of range");
+ systemPrintf("Enter %s message rate (0 to disable): ", ubxMessages[messageNumber].msgTextName);
+ rate = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (rate == INPUT_RESPONSE_GETNUMBER_TIMEOUT || rate == INPUT_RESPONSE_GETNUMBER_EXIT)
+ return; // Give up
+ }
+
+ localMessageRate = rate;
}
-//Set all GNSS message report rates to one value
-//Useful for turning on or off all messages for resetting and testing
-//We pass in the message array by reference so that we can modify a temp struct
-//like a dummy struct for USB message changes (all on/off) for testing
-void setGNSSMessageRates(ubxMsg *localMessage, uint8_t msgRate)
+// Set all GNSS message report rates to one value
+// Useful for turning on or off all messages for resetting and testing
+// We pass in the message array by reference so that we can modify a temp struct
+// like a dummy struct for USB message changes (all on/off) for testing
+void setGNSSMessageRates(uint8_t *localMessageRate, uint8_t msgRate)
{
- for (int x = 0 ; x < MAX_UBX_MSG ; x++)
- localMessage[x].msgRate = msgRate;
+ for (int x = 0; x < MAX_UBX_MSG; x++)
+ localMessageRate[x] = msgRate;
}
-//Given a message, set the message rate on the ZED-F9P
-bool configureMessageRate(uint8_t portID, ubxMsg localMessage)
+// Creates a log if logging is enabled, and SD is detected
+// Based on GPS data/time, create a log file in the format SFE_Surveyor_YYMMDD_HHMMSS.ubx
+void beginLogging()
{
- uint8_t currentSendRate = getMessageRate(localMessage.msgClass, localMessage.msgID, portID); //Qeury the module for the current setting
-
- bool response = true;
- if (currentSendRate != localMessage.msgRate)
- response &= i2cGNSS.configureMessage(localMessage.msgClass, localMessage.msgID, portID, localMessage.msgRate); //Update setting
- return response;
+ beginLogging(nullptr);
}
-//Lookup the send rate for a given message+port
-uint8_t getMessageRate(uint8_t msgClass, uint8_t msgID, uint8_t portID)
+void beginLogging(const char *customFileName)
{
- ubxPacket customCfg = {0, 0, 0, 0, 0, settingPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
+ if (online.microSD == false)
+ beginSD();
- customCfg.cls = UBX_CLASS_CFG; // This is the message Class
- customCfg.id = UBX_CFG_MSG; // This is the message ID
- customCfg.len = 2;
- customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing)
-
- uint16_t maxWait = 1250; // Wait for up to 1250ms (Serial may need a lot longer e.g. 1100)
-
- settingPayload[0] = msgClass;
- settingPayload[1] = msgID;
+ if (online.logging == false)
+ {
+ if (online.microSD == true && settings.enableLogging == true &&
+ online.rtc == true) // We can't create a file until we have date/time
+ {
+ if (customFileName == nullptr)
+ {
+ // Generate a standard log file name
+ if (reuseLastLog == true) // attempt to use previous log
+ {
+ reuseLastLog = false; // Don't reuse the file a second time
+
+ // findLastLog does not add the preceding slash. We need to do it manually
+ logFileName[0] = '/'; // Erase any existing file name
+ logFileName[1] = 0;
+
+ if (findLastLog(&logFileName[1], sizeof(logFileName) - 1) == false)
+ {
+ logFileName[0] = 0; // No file found. Erase the slash
+ log_d("Failed to find last log. Making new one.");
+ }
+ else
+ log_d("Using last log file.");
+ }
+ else
+ {
+ // We are not reusing the last log, so erase the global/original filename
+ logFileName[0] = 0;
+ }
+
+ if (strlen(logFileName) == 0)
+ {
+ snprintf(logFileName, sizeof(logFileName), "/%s_%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
+ );
+ }
+ }
+ else
+ {
+ strncpy(logFileName, customFileName,
+ sizeof(logFileName) - 1); // customFileName already has the preceding slash added
+ }
- // Read the current setting. The results will be loaded into customCfg.
- if (i2cGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK
- {
- Serial.printf("getMessageSetting failed: Class-0x%02X ID-0x%02X\n\r", msgClass, msgID);
- return (false);
- }
+ // Allocate the ubxFile
+ if (!ubxFile)
+ {
+ ubxFile = new FileSdFatMMC;
+ if (!ubxFile)
+ {
+ systemPrintln("Failed to allocate ubxFile!");
+ return;
+ }
+ }
- uint8_t sendRate = settingPayload[2 + portID];
+ // Attempt to write to file system. This avoids collisions with file writing in F9PSerialReadTask()
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_CREATEFILE);
+
+ // O_CREAT - create the file if it does not exist
+ // O_APPEND - seek to the end of the file prior to each write
+ // O_WRITE - open for write
+ if (ubxFile->open(logFileName, O_CREAT | O_APPEND | O_WRITE) == false)
+ {
+ systemPrintf("Failed to create GNSS UBX data file: %s\r\n", logFileName);
+ online.logging = false;
+ xSemaphoreGive(sdCardSemaphore);
+ return;
+ }
+
+ fileSize = 0;
+ lastLogSize = 0; // Reset counter - used for displaying active logging icon
+
+ bufferOverruns = 0; // Reset counter
+
+ ubxFile->updateFileCreateTimestamp(); // Update the file to create time & date
+
+ startCurrentLogTime_minutes = millis() / 1000L / 60; // Mark now as start of logging
+
+ // If it hasn't been done before, mark the initial start of logging for total run time
+ if (startLogTime_minutes == 0)
+ startLogTime_minutes = millis() / 1000L / 60;
+
+ // Add NMEA txt message with restart reason
+ char rstReason[30];
+ switch (esp_reset_reason())
+ {
+ case ESP_RST_UNKNOWN:
+ strcpy(rstReason, "ESP_RST_UNKNOWN");
+ break;
+ case ESP_RST_POWERON:
+ strcpy(rstReason, "ESP_RST_POWERON");
+ break;
+ case ESP_RST_SW:
+ strcpy(rstReason, "ESP_RST_SW");
+ break;
+ case ESP_RST_PANIC:
+ strcpy(rstReason, "ESP_RST_PANIC");
+ break;
+ case ESP_RST_INT_WDT:
+ strcpy(rstReason, "ESP_RST_INT_WDT");
+ break;
+ case ESP_RST_TASK_WDT:
+ strcpy(rstReason, "ESP_RST_TASK_WDT");
+ break;
+ case ESP_RST_WDT:
+ strcpy(rstReason, "ESP_RST_WDT");
+ break;
+ case ESP_RST_DEEPSLEEP:
+ strcpy(rstReason, "ESP_RST_DEEPSLEEP");
+ break;
+ case ESP_RST_BROWNOUT:
+ strcpy(rstReason, "ESP_RST_BROWNOUT");
+ break;
+ case ESP_RST_SDIO:
+ strcpy(rstReason, "ESP_RST_SDIO");
+ break;
+ default:
+ strcpy(rstReason, "Unknown");
+ }
+
+ // Mark top of log with system information
+ char nmeaMessage[82]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_RESET_REASON, nmeaMessage, sizeof(nmeaMessage),
+ rstReason); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+
+ // Record system firmware versions and info to log
+
+ // SparkFun RTK Express v1.10-Feb 11 2022
+ char firmwareVersion[30]; // v1.3 December 31 2021
+ firmwareVersion[0] = 'v';
+ getFirmwareVersion(&firmwareVersion[1], sizeof(firmwareVersion) -1, true);
+ createNMEASentence(CUSTOM_NMEA_TYPE_SYSTEM_VERSION, nmeaMessage, sizeof(nmeaMessage),
+ firmwareVersion); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+
+ // ZED-F9P firmware: HPG 1.30
+ createNMEASentence(CUSTOM_NMEA_TYPE_ZED_VERSION, nmeaMessage, sizeof(nmeaMessage),
+ zedFirmwareVersion); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+
+ // ZED-F9 unique chip ID
+ createNMEASentence(CUSTOM_NMEA_TYPE_ZED_UNIQUE_ID, nmeaMessage, sizeof(nmeaMessage),
+ zedUniqueId); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+
+ // Device BT MAC. See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/346
+ char macAddress[5];
+ snprintf(macAddress, sizeof(macAddress), "%02X%02X", btMACAddress[4], btMACAddress[5]);
+ createNMEASentence(CUSTOM_NMEA_TYPE_DEVICE_BT_ID, nmeaMessage, sizeof(nmeaMessage),
+ macAddress); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+
+ // Record today's time/date into log. This is in case a log is restarted. See issue 440:
+ // https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/440
+ char currentDate[sizeof("230101,120101")];
+ snprintf(currentDate, sizeof(currentDate), "%02d%02d%02d,%02d%02d%02d", 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
+ );
+ createNMEASentence(CUSTOM_NMEA_TYPE_CURRENT_DATE, nmeaMessage, sizeof(nmeaMessage),
+ currentDate); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+
+ if (reuseLastLog == true)
+ {
+ systemPrintln("Appending last available log");
+ }
+
+ xSemaphoreGive(sdCardSemaphore);
+ }
+ else
+ {
+ // A retry will happen during the next loop, the log will eventually be opened
+ log_d("Failed to get file system lock to create GNSS UBX data file");
+ online.logging = false;
+ return;
+ }
- return (sendRate);
+ systemPrintf("Log file name: %s\r\n", logFileName);
+ online.logging = true;
+ } // online.sd, enable.logging, online.rtc
+ } // online.logging
}
-//Creates a log if logging is enabled, and SD is detected
-//Based on GPS data/time, create a log file in the format SFE_Surveyor_YYMMDD_HHMMSS.ubx
-void beginLogging()
+// Stop writing to the log file on the microSD card
+void endLogging(bool gotSemaphore, bool releaseSemaphore)
{
- if (online.logging == false)
- {
- if (online.microSD == true && settings.enableLogging == true)
+ if (online.logging == true)
{
- char fileName[40] = "";
+ // Wait up to 1000ms to allow hanging SD writes to time out
+ if (gotSemaphore || (xSemaphoreTake(sdCardSemaphore, 1000 / portTICK_PERIOD_MS) == pdPASS))
+ {
+ markSemaphore(FUNCTION_ENDLOGGING);
- i2cGNSS.checkUblox();
+ online.logging = false;
- if (reuseLastLog == true) //attempt to use previous log
- {
- if (findLastLog(fileName) == false)
+ // Record the number of NMEA/RTCM/UBX messages that were filtered out
+ char parserStats[50];
+
+ snprintf(parserStats, sizeof(parserStats), "%d,%d,%d,", failedParserMessages_NMEA,
+ failedParserMessages_RTCM, failedParserMessages_UBX);
+
+ char nmeaMessage[82]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_PARSER_STATS, nmeaMessage, sizeof(nmeaMessage),
+ parserStats); // textID, buffer, sizeOfBuffer, text
+ ubxFile->println(nmeaMessage);
+ ubxFile->sync();
+
+ // Reset stats in case a new log is created
+ failedParserMessages_NMEA = 0;
+ failedParserMessages_RTCM = 0;
+ failedParserMessages_UBX = 0;
+
+ // Close down file system
+ ubxFile->close();
+ // Done with the log file
+ delete ubxFile;
+ ubxFile = nullptr;
+
+ systemPrintln("Log file closed");
+
+ // Release the semaphore if requested
+ if (releaseSemaphore)
+ xSemaphoreGive(sdCardSemaphore);
+ } // End sdCardSemaphore
+ else
{
- Serial.println(F("Failed to find last log. Making new one."));
- }
- }
-
- if (strlen(fileName) == 0)
- {
- //Based on GPS data/time, create a log file in the format SFE_Surveyor_YYMMDD_HHMMSS.ubx
- bool timeValid = false;
- // if (i2cGNSS.getTimeValid() == true && i2cGNSS.getDateValid() == true) //Will pass if ZED's RTC is reporting (regardless of GNSS fix)
- // timeValid = true;
- if (i2cGNSS.getConfirmedTime() == true && i2cGNSS.getConfirmedDate() == true) //Requires GNSS fix
- timeValid = true;
-
- if (timeValid == false)
- {
- Serial.println(F("beginLoggingUBX: No date/time available. No file created."));
- delay(1000); //Give the receiver time to get a lock
- online.logging = false;
- return;
- }
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
- sprintf(fileName, "%s_%02d%02d%02d_%02d%02d%02d.ubx", //SdFat library
- platformFilePrefix,
- i2cGNSS.getYear() - 2000, i2cGNSS.getMonth(), i2cGNSS.getDay(),
- i2cGNSS.getHour(), i2cGNSS.getMinute(), i2cGNSS.getSecond()
- );
- }
-
- //Attempt to write to file system. This avoids collisions with file writing in F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
- {
- // O_CREAT - create the file if it does not exist
- // O_APPEND - seek to the end of the file prior to each write
- // O_WRITE - open for write
- if (ubxFile.open(fileName, O_CREAT | O_APPEND | O_WRITE) == false)
- {
- Serial.printf("Failed to create GNSS UBX data file: %s\n\r", fileName);
- delay(1000);
- online.logging = false;
- xSemaphoreGive(xFATSemaphore);
- return;
+ // This is OK because in the interim more data will be written to the log
+ // and the log file will eventually be closed by the next call in loop
+ log_d("sdCardSemaphore failed to yield, held by %s, menuMessages.ino line %d\r\n", semaphoreHolder,
+ __LINE__);
}
+ }
+}
- updateDataFileCreate(&ubxFile); // Update the file to create time & date
-
- startLogTime_minutes = millis() / 1000L / 60; //Mark now as start of logging
-
- //Add NMEA txt message with restart reason
- char rstReason[30];
- switch (esp_reset_reason())
- {
- case ESP_RST_UNKNOWN: strcpy(rstReason, "ESP_RST_UNKNOWN"); break;
- case ESP_RST_POWERON : strcpy(rstReason, "ESP_RST_POWERON"); break;
- case ESP_RST_SW : strcpy(rstReason, "ESP_RST_SW"); break;
- case ESP_RST_PANIC : strcpy(rstReason, "ESP_RST_PANIC"); break;
- case ESP_RST_INT_WDT : strcpy(rstReason, "ESP_RST_INT_WDT"); break;
- case ESP_RST_TASK_WDT : strcpy(rstReason, "ESP_RST_TASK_WDT"); break;
- case ESP_RST_WDT : strcpy(rstReason, "ESP_RST_WDT"); break;
- case ESP_RST_DEEPSLEEP : strcpy(rstReason, "ESP_RST_DEEPSLEEP"); break;
- case ESP_RST_BROWNOUT : strcpy(rstReason, "ESP_RST_BROWNOUT"); break;
- case ESP_RST_SDIO : strcpy(rstReason, "ESP_RST_SDIO"); break;
- default : strcpy(rstReason, "Unknown");
- }
+// Finds last log
+// Returns true if successful
+// lastLogName will contain the name of the last log file on return - ** but without the preceding slash **
+bool findLastLog(char *lastLogNamePrt, size_t lastLogNameSize)
+{
+ bool foundAFile = false;
+
+ if (online.microSD == true)
+ {
+ // Attempt to access file system. This avoids collisions with file writing in F9PSerialReadTask()
+ // Wait up to 5s, this is important
+ if (xSemaphoreTake(sdCardSemaphore, 5000 / portTICK_PERIOD_MS) == pdPASS)
+ {
+ markSemaphore(FUNCTION_FINDLOG);
- char nmeaMessage[82]; //Max NMEA sentence length is 82
- createNMEASentence(1, 1, nmeaMessage, rstReason); //sentenceNumber, textID
- ubxFile.println(nmeaMessage);
+ // Count available binaries
+ if (USE_SPI_MICROSD)
+ {
+ SdFile tempFile;
+ SdFile dir;
+ const char *LOG_EXTENSION = "ubx";
+ const char *LOG_PREFIX = platformFilePrefix;
+ char fname[100]; // Handle long file names
+
+ dir.open("/"); // Open root
+
+ while (tempFile.openNext(&dir, O_READ))
+ {
+ if (tempFile.isFile())
+ {
+ tempFile.getName(fname, sizeof(fname));
+
+ // Check for matching file name prefix and extension
+ if (strcmp(LOG_EXTENSION, &fname[strlen(fname) - strlen(LOG_EXTENSION)]) == 0)
+ {
+ if (strstr(fname, LOG_PREFIX) != nullptr)
+ {
+ strncpy(lastLogNamePrt, fname,
+ lastLogNameSize - 1); // Store this file as last known log file
+ foundAFile = true;
+ }
+ }
+ }
+ tempFile.close();
+ }
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ File tempFile;
+ File dir;
+ const char *LOG_EXTENSION = "ubx";
+ const char *LOG_PREFIX = platformFilePrefix;
+ char fname[100]; // Handle long file names
+
+ dir = SD_MMC.open("/"); // Open root
+
+ if (dir && dir.isDirectory())
+ {
+ tempFile = dir.openNextFile();
+ while (tempFile)
+ {
+ if (!tempFile.isDirectory())
+ {
+ snprintf(fname, sizeof(fname), "%s", tempFile.name());
+
+ // Check for matching file name prefix and extension
+ if (strcmp(LOG_EXTENSION, &fname[strlen(fname) - strlen(LOG_EXTENSION)]) == 0)
+ {
+ if (strstr(fname, LOG_PREFIX) != nullptr)
+ {
+ strncpy(lastLogNamePrt, fname,
+ lastLogNameSize - 1); // Store this file as last known log file
+ foundAFile = true;
+ }
+ }
+ }
+ tempFile.close();
+ tempFile = dir.openNextFile();
+ }
+ }
+ }
+#endif // COMPILE_SD_MMC
- if (reuseLastLog == true)
+ xSemaphoreGive(sdCardSemaphore);
+ }
+ else
{
- Serial.println(F("Appending last available log"));
+ // Error when a log file exists on the microSD card, data should be appended
+ // to the existing log file
+ systemPrintf("sdCardSemaphore failed to yield, menuMessages.ino line %d\r\n", __LINE__);
}
+ }
+
+ return (foundAFile);
+}
- xSemaphoreGive(xFATSemaphore);
- }
- else
- {
- Serial.println(F("Failed to get file system lock to create GNSS UBX data file"));
- online.logging = false;
+// Given a unique string, find first and last records containing that string in message array
+void setMessageOffsets(const ubxMsg *localMessage, const char *messageType, int &startOfBlock, int &endOfBlock)
+{
+ char messageNamePiece[40]; // UBX_RTCM
+ snprintf(messageNamePiece, sizeof(messageNamePiece), "UBX_%s", messageType); // Put UBX_ infront of type
+
+ // Find the first occurrence
+ for (startOfBlock = 0; startOfBlock < MAX_UBX_MSG; startOfBlock++)
+ {
+ if (strstr(localMessage[startOfBlock].msgTextName, messageNamePiece) != nullptr)
+ break;
+ }
+ if (startOfBlock == MAX_UBX_MSG)
+ {
+ // Error out
+ startOfBlock = 0;
+ endOfBlock = 0;
return;
- }
+ }
- Serial.printf("Log file created: %s\n\r", fileName);
- online.logging = true;
+ // Find the last occurrence
+ for (endOfBlock = startOfBlock + 1; endOfBlock < MAX_UBX_MSG; endOfBlock++)
+ {
+ if (strstr(localMessage[endOfBlock].msgTextName, messageNamePiece) == nullptr)
+ break;
}
- else
- online.logging = false;
- }
}
-//Updates the timestemp on a given data file
-void updateDataFileAccess(SdFile *dataFile)
+// Return the number of active/enabled messages
+uint8_t getActiveMessageCount()
{
- bool timeValid = false;
- if (i2cGNSS.getTimeValid() == true && i2cGNSS.getDateValid() == true) //Will pass if ZED's RTC is reporting (regardless of GNSS fix)
- timeValid = true;
- if (i2cGNSS.getConfirmedTime() == true && i2cGNSS.getConfirmedDate() == true) //Requires GNSS fix
- timeValid = true;
-
- if (timeValid == true)
- {
- //Update the file access time
- dataFile->timestamp(T_ACCESS, i2cGNSS.getYear(), i2cGNSS.getMonth(), i2cGNSS.getDay(), i2cGNSS.getHour(), i2cGNSS.getMinute(), i2cGNSS.getSecond());
- //Update the file write time
- dataFile->timestamp(T_WRITE, i2cGNSS.getYear(), i2cGNSS.getMonth(), i2cGNSS.getDay(), i2cGNSS.getHour(), i2cGNSS.getMinute(), i2cGNSS.getSecond());
- }
+ uint8_t count = 0;
+ for (int x = 0; x < MAX_UBX_MSG; x++)
+ if (settings.ubxMessageRates[x] > 0)
+ count++;
+ return (count);
}
-void updateDataFileCreate(SdFile *dataFile)
+// Count the number of NAV2 messages with rates more than 0. Used for determining if we need the enable
+// the global NAV2 feature.
+uint8_t getNAV2MessageCount()
{
- bool timeValid = false;
- if (i2cGNSS.getTimeValid() == true && i2cGNSS.getDateValid() == true) //Will pass if ZED's RTC is reporting (regardless of GNSS fix)
- timeValid = true;
- if (i2cGNSS.getConfirmedTime() == true && i2cGNSS.getConfirmedDate() == true) //Requires GNSS fix
- timeValid = true;
-
- if (timeValid == true)
- {
- //Update the file create time
- dataFile->timestamp(T_CREATE, i2cGNSS.getYear(), i2cGNSS.getMonth(), i2cGNSS.getDay(), i2cGNSS.getHour(), i2cGNSS.getMinute(), i2cGNSS.getSecond());
- }
+ int enabledMessages = 0;
+ int startOfBlock = 0;
+ int endOfBlock = 0;
+
+ setMessageOffsets(&ubxMessages[0], "NAV2", startOfBlock,
+ endOfBlock); // Find start and stop of given messageType in message array
+
+ for (int x = 0; x < (endOfBlock - startOfBlock); x++)
+ {
+ if (settings.ubxMessageRates[x + startOfBlock] > 0)
+ enabledMessages++;
+ }
+
+ setMessageOffsets(&ubxMessages[0], "NMEANAV2", startOfBlock,
+ endOfBlock); // Find start and stop of given messageType in message array
+
+ for (int x = 0; x < (endOfBlock - startOfBlock); x++)
+ {
+ if (settings.ubxMessageRates[x + startOfBlock] > 0)
+ enabledMessages++;
+ }
+
+ return (enabledMessages);
+}
+
+// Given the name of a message, find it, and set the rate
+bool setMessageRateByName(const char *msgName, uint8_t msgRate)
+{
+ for (int x = 0; x < MAX_UBX_MSG; x++)
+ {
+ if (strcmp(ubxMessages[x].msgTextName, msgName) == 0)
+ {
+ settings.ubxMessageRates[x] = msgRate;
+ return (true);
+ }
+ }
+
+ systemPrintf("setMessageRateByName: %s not found\r\n", msgName);
+ return (false);
}
-//Finds last log
-//Returns true if succesful
-bool findLastLog(char *lastLogName)
+// Given the name of a message, find it, and return the rate
+uint8_t getMessageRateByName(const char *msgName)
{
- bool foundAFile = false;
+ return (settings.ubxMessageRates[getMessageNumberByName(msgName)]);
+}
- if (online.microSD == true)
- {
- //Attempt to access file system. This avoids collisions with file writing in F9PSerialReadTask()
- //Wait up to 5s, this is important
- if (xSemaphoreTake(xFATSemaphore, 5000 / portTICK_PERIOD_MS) == pdPASS)
+// Given the name of a message, return the array number
+uint8_t getMessageNumberByName(const char *msgName)
+{
+ for (int x = 0; x < MAX_UBX_MSG; x++)
{
- //Count available binaries
- SdFile tempFile;
- SdFile dir;
- const char* LOG_EXTENSION = "ubx";
- const char* LOG_PREFIX = platformFilePrefix;
- char fname[50]; //Handle long file names
+ if (strcmp(ubxMessages[x].msgTextName, msgName) == 0)
+ return (x);
+ }
- dir.open("/"); //Open root
+ systemPrintf("getMessageNumberByName: %s not found\r\n", msgName);
+ return (0);
+}
- while (tempFile.openNext(&dir, O_READ))
- {
- if (tempFile.isFile())
+// Check rates to see if they need to be reset to defaults
+void checkMessageRates()
+{
+ if (settings.ubxMessageRates[0] == 254)
+ {
+ // Reset rates to defaults
+ for (int x = 0; x < MAX_UBX_MSG; x++)
{
- tempFile.getName(fname, sizeof(fname));
-
- //Check for matching file name prefix and extension
- if (strcmp(LOG_EXTENSION, &fname[strlen(fname) - strlen(LOG_EXTENSION)]) == 0)
- {
- if (strstr(fname, LOG_PREFIX) != NULL)
- {
- strcpy(lastLogName, fname); //Store this file as last known log file
- foundAFile = true;
- }
- }
+ if (ubxMessages[x].msgClass == UBX_RTCM_MSB)
+ settings.ubxMessageRates[x] = 0; // For general rover messages, RTCM should be zero by default.
+ // ubxMessageRatesBase will have the proper defaults.
+ else
+ settings.ubxMessageRates[x] = ubxMessages[x].msgDefaultRate;
}
- tempFile.close();
- }
-
- xSemaphoreGive(xFATSemaphore);
}
- }
- return (foundAFile);
+ if (settings.ubxMessageRatesBase[0] == 254)
+ {
+ // Reset Base rates to defaults
+ int firstRTCMRecord = getMessageNumberByName("UBX_RTCM_1005");
+ for (int x = 0; x < MAX_UBX_MSG_RTCM; x++)
+ settings.ubxMessageRatesBase[x] = ubxMessages[firstRTCMRecord + x].msgDefaultRate;
+ }
}
-//Given a unique string, find first and last records containing that string in message array
-void setMessageOffsets(char* messageType, int& startOfBlock, int& endOfBlock)
+// Determine logging type
+// If user is logging basic 5 sentences, this is 'S'tandard logging
+// If user is logging 7 PPP sentences, this is 'P'PP logging
+// If user has other setences turned on, it's custom logging
+// This controls the type of icon displayed
+void setLoggingType()
{
- char messageNamePiece[40]; //UBX_RTCM
- sprintf(messageNamePiece, "UBX_%s", messageType); //Put UBX_ infront of type
-
- //Find the first occurrence
- for (startOfBlock = 0 ; startOfBlock < MAX_UBX_MSG ; startOfBlock++)
- {
- if (strstr(ubxMessages[startOfBlock].msgTextName, messageNamePiece) != NULL) break;
- }
- if (startOfBlock == MAX_UBX_MSG)
- {
- //Error out
- startOfBlock = 0;
- endOfBlock = 0;
- return;
- }
-
- //Find the last occurrence
- for (endOfBlock = startOfBlock + 1 ; endOfBlock < MAX_UBX_MSG ; endOfBlock++)
- {
- if (strstr(ubxMessages[endOfBlock].msgTextName, messageNamePiece) == NULL) break;
- }
+ loggingType = LOGGING_CUSTOM;
+
+ int messageCount = getActiveMessageCount();
+ if (messageCount == 5 || messageCount == 7)
+ {
+ if (getMessageRateByName("UBX_NMEA_GGA") > 0 && getMessageRateByName("UBX_NMEA_GSA") > 0 &&
+ getMessageRateByName("UBX_NMEA_GST") > 0 && getMessageRateByName("UBX_NMEA_GSV") > 0 &&
+ getMessageRateByName("UBX_NMEA_RMC") > 0)
+ {
+ loggingType = LOGGING_STANDARD;
+
+ if (getMessageRateByName("UBX_RXM_RAWX") > 0 && getMessageRateByName("UBX_RXM_SFRBX") > 0)
+ loggingType = LOGGING_PPP;
+ }
+ }
}
-//Return the number of active/enabled messages
-uint8_t getActiveMessageCount()
+// During the logging test, we have to modify the messages and rate of the device
+void setLogTestFrequencyMessages(int rate, int messages)
{
- uint8_t count = 0;
- for (int x = 0 ; x < MAX_UBX_MSG ; x++)
- if (ubxMessages[x].msgRate > 0) count++;
- return (count);
+ // Set measurement frequency
+ setRate(1.0 / rate); // Convert Hz to seconds. This will set settings.measurementRate, settings.navigationRate, and
+ // GSV message
+
+ // Set messages
+ setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages
+ if (messages == 5)
+ {
+ setMessageRateByName("UBX_NMEA_GGA", 1);
+ setMessageRateByName("UBX_NMEA_GSA", 1);
+ setMessageRateByName("UBX_NMEA_GST", 1);
+ setMessageRateByName("UBX_NMEA_GSV", rate); // One report per second
+ setMessageRateByName("UBX_NMEA_RMC", 1);
+
+ log_d("Messages: Surveying Defaults (NMEAx5)");
+ }
+ else if (messages == 7)
+ {
+ setMessageRateByName("UBX_NMEA_GGA", 1);
+ setMessageRateByName("UBX_NMEA_GSA", 1);
+ setMessageRateByName("UBX_NMEA_GST", 1);
+ setMessageRateByName("UBX_NMEA_GSV", rate); // One report per second
+ setMessageRateByName("UBX_NMEA_RMC", 1);
+ setMessageRateByName("UBX_RXM_RAWX", 1);
+ setMessageRateByName("UBX_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
+ setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set
+ setMessagesUSB(MAX_SET_MESSAGES_RETRIES);
}
-//Given the name of a message, find it, and set the rate
-bool setMessageRateByName(char *msgName, uint8_t msgRate)
+// 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()
{
- for (int x = 0 ; x < MAX_UBX_MSG ; x++)
- {
- if (strcmp(ubxMessages[x].msgTextName, msgName) == 0)
+ // 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)
{
- ubxMessages[x].msgRate = msgRate;
- return (true);
+ 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;
}
- }
- Serial.printf("setMessageRateByName: %s not found\n\r", msgName);
- return (false);
+ 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);
+
+ ubxFile->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_Surveyor/menuPP.ino b/Firmware/RTK_Surveyor/menuPP.ino
new file mode 100644
index 000000000..1054a5fae
--- /dev/null
+++ b/Firmware/RTK_Surveyor/menuPP.ino
@@ -0,0 +1,1491 @@
+#ifdef COMPILE_L_BAND
+
+#include "mbedtls/ssl.h" //Needed for certificate validation
+
+//----------------------------------------
+// Locals - compiled out
+//----------------------------------------
+
+#define MQTT_CERT_SIZE 2000
+
+// The PointPerfect token is provided at compile time via build flags
+#define DEVELOPMENT_TOKEN 0xAA, 0xBB, 0xCC, 0xDD, 0x00, 0x11, 0x22, 0x33, 0x0A, 0x0B, 0x0C, 0x0D, 0x00, 0x01, 0x02, 0x03
+#ifndef POINTPERFECT_TOKEN
+#warning Using the DEVELOPMENT_TOKEN for point perfect!
+#define POINTPERFECT_TOKEN DEVELOPMENT_TOKEN
+#endif // POINTPERFECT_TOKEN
+
+static const uint8_t developmentTokenArray[16] = {DEVELOPMENT_TOKEN}; // Token in HEX form
+static const uint8_t pointPerfectTokenArray[16] = {POINTPERFECT_TOKEN}; // Token in HEX form
+
+static const char *pointPerfectAPI = "https://api.thingstream.io/ztp/pointperfect/credentials";
+
+//----------------------------------------
+// Forward declarations - compiled out
+//----------------------------------------
+
+void checkRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct);
+
+//----------------------------------------
+// L-Band Routines - compiled out
+//----------------------------------------
+
+void menuPointPerfectKeys()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: PointPerfect Keys");
+
+ systemPrint("1) Set ThingStream Device Profile Token: ");
+ if (strlen(settings.pointPerfectDeviceProfileToken) > 0)
+ systemPrintln(settings.pointPerfectDeviceProfileToken);
+ else
+ systemPrintln("Use SparkFun Token");
+
+ systemPrint("2) Set Current Key: ");
+ if (strlen(settings.pointPerfectCurrentKey) > 0)
+ systemPrintln(settings.pointPerfectCurrentKey);
+ else
+ systemPrintln("N/A");
+
+ systemPrint("3) Set Current Key Expiration Date (DD/MM/YYYY): ");
+ if (strlen(settings.pointPerfectCurrentKey) > 0 && settings.pointPerfectCurrentKeyStart > 0 &&
+ settings.pointPerfectCurrentKeyDuration > 0)
+ {
+ long long gpsEpoch = thingstreamEpochToGPSEpoch(settings.pointPerfectCurrentKeyStart);
+
+ gpsEpoch += (settings.pointPerfectCurrentKeyDuration / 1000) -
+ 1; // Add key duration back to the key start date to get key expiration
+
+ systemPrintf("%s\r\n", printDateFromGPSEpoch(gpsEpoch));
+
+ if (settings.debugLBand == true)
+ systemPrintf("settings.pointPerfectCurrentKeyDuration: %lld (%d)\r\n",
+ settings.pointPerfectCurrentKeyDuration,
+ settings.pointPerfectCurrentKeyDuration / (1000L * 60 * 60 * 24));
+ }
+ else
+ systemPrintln("N/A");
+
+ systemPrint("4) Set Next Key: ");
+ if (strlen(settings.pointPerfectNextKey) > 0)
+ systemPrintln(settings.pointPerfectNextKey);
+ else
+ systemPrintln("N/A");
+
+ systemPrint("5) Set Next Key Expiration Date (DD/MM/YYYY): ");
+ if (strlen(settings.pointPerfectNextKey) > 0 && settings.pointPerfectNextKeyStart > 0 &&
+ settings.pointPerfectNextKeyDuration > 0)
+ {
+ long long gpsEpoch = thingstreamEpochToGPSEpoch(settings.pointPerfectNextKeyStart);
+
+ gpsEpoch += (settings.pointPerfectNextKeyDuration /
+ 1000); // Add key duration back to the key start date to get key expiration
+
+ systemPrintf("%s\r\n", printDateFromGPSEpoch(gpsEpoch));
+ }
+ else
+ systemPrintln("N/A");
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ systemPrint("Enter Device Profile Token: ");
+ getString(settings.pointPerfectDeviceProfileToken, sizeof(settings.pointPerfectDeviceProfileToken));
+ }
+ else if (incoming == 2)
+ {
+ systemPrint("Enter Current Key: ");
+ getString(settings.pointPerfectCurrentKey, sizeof(settings.pointPerfectCurrentKey));
+ }
+ else if (incoming == 3)
+ {
+ clearBuffer();
+
+ systemPrintln("Enter Current Key Expiration Date: ");
+ uint8_t expDay;
+ uint8_t expMonth;
+ uint16_t expYear;
+ while (getDate(expDay, expMonth, expYear) == false)
+ {
+ systemPrintln("Date invalid. Please try again.");
+ }
+
+ dateToKeyStart(expDay, expMonth, expYear, &settings.pointPerfectCurrentKeyStart);
+
+ // The u-blox API reports key durations of 5 weeks, but the web interface reports expiration dates
+ // that are 4 weeks.
+ // If the user has manually entered a date, force duration down to four weeks
+ settings.pointPerfectCurrentKeyDuration = (1000LL * 60 * 60 * 24 * 28);
+
+ // Calculate the next key expiration date
+ settings.pointPerfectNextKeyStart = settings.pointPerfectCurrentKeyStart +
+ settings.pointPerfectCurrentKeyDuration +
+ 1; // Next key starts after current key
+ settings.pointPerfectNextKeyDuration = settings.pointPerfectCurrentKeyDuration;
+
+ if (settings.debugLBand == true)
+ {
+ systemPrintf(" settings.pointPerfectCurrentKeyStart: %lld - %s\r\n",
+ settings.pointPerfectCurrentKeyStart,
+ printDateFromUnixEpoch(settings.pointPerfectCurrentKeyStart / 1000));
+ systemPrintf(" settings.pointPerfectCurrentKeyDuration: %lld - %s\r\n",
+ settings.pointPerfectCurrentKeyDuration,
+ printDaysFromDuration(settings.pointPerfectCurrentKeyDuration));
+ systemPrintf(" settings.pointPerfectNextKeyStart: %lld - %s\r\n", settings.pointPerfectNextKeyStart,
+ printDateFromUnixEpoch(settings.pointPerfectNextKeyStart / 1000));
+ systemPrintf(" settings.pointPerfectNextKeyDuration: %lld - %s\r\n",
+ settings.pointPerfectNextKeyDuration,
+ printDaysFromDuration(settings.pointPerfectNextKeyDuration));
+ }
+ }
+ else if (incoming == 4)
+ {
+ systemPrint("Enter Next Key: ");
+ getString(settings.pointPerfectNextKey, sizeof(settings.pointPerfectNextKey));
+ }
+ else if (incoming == 5)
+ {
+ clearBuffer();
+
+ systemPrintln("Enter Next Key Expiration Date: ");
+ uint8_t expDay;
+ uint8_t expMonth;
+ uint16_t expYear;
+ while (getDate(expDay, expMonth, expYear) == false)
+ {
+ systemPrintln("Date invalid. Please try again.");
+ }
+
+ dateToKeyStart(expDay, expMonth, expYear, &settings.pointPerfectNextKeyStart);
+ }
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Given a GPS Epoch, return a DD/MM/YYYY string
+char *printDateFromGPSEpoch(long long gpsEpoch)
+{
+ uint16_t keyGPSWeek;
+ uint32_t keyGPSToW;
+ epochToWeekToW(gpsEpoch, &keyGPSWeek, &keyGPSToW);
+
+ long expDay;
+ long expMonth;
+ long expYear;
+ gpsWeekToWToDate(keyGPSWeek, keyGPSToW, &expDay, &expMonth, &expYear);
+
+ char *response = (char *)malloc(strlen("01/01/1010"));
+
+ sprintf(response, "%02ld/%02ld/%ld", expDay, expMonth, expYear);
+ return (response);
+}
+
+// Given a Unix Epoch, return a DD/MM/YYYY string
+// https://www.epochconverter.com/programming/c
+char *printDateFromUnixEpoch(long long unixEpoch)
+{
+ char *buf = (char *)malloc(strlen("01/01/2023") + 1); // Make room for terminator
+ time_t rawtime = unixEpoch;
+
+ struct tm ts;
+ ts = *localtime(&rawtime);
+
+ // Format time, "dd/mm/yyyy"
+ strftime(buf, strlen("01/01/2023") + 1, "%d/%m/%Y", &ts);
+ return (buf);
+}
+
+// Given a duration in ms, print days
+char *printDaysFromDuration(long long duration)
+{
+ float days = duration / (1000.0 * 60 * 60 * 24); // Convert ms to days
+
+ char *response = (char *)malloc(strlen("34.9") + 1); // Make room for terminator
+ sprintf(response, "%0.2f", days);
+ return (response);
+}
+
+// Connect to 'home' WiFi and then ThingStream API. This will attach this unique device to the ThingStream network.
+bool pointperfectProvisionDevice()
+{
+#ifdef COMPILE_WIFI
+ bool bluetoothOriginallyStarted = true;
+ if (bluetoothState == BT_OFF)
+ bluetoothOriginallyStarted = false;
+
+ bluetoothStop(); // Free heap before starting secure client (requires ~70KB)
+
+ DynamicJsonDocument *jsonZtp = nullptr;
+ char *tempHolderPtr = nullptr;
+ bool retVal = false;
+
+ do
+ {
+ WiFiClientSecure client;
+ client.setCACert(AWS_PUBLIC_CERT);
+
+ char hardwareID[13];
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X%02X%02X%02X", lbandMACAddress[0], lbandMACAddress[1],
+ lbandMACAddress[2], lbandMACAddress[3], lbandMACAddress[4],
+ lbandMACAddress[5]); // Get ready for JSON
+
+#ifdef WHITELISTED_ID
+ // Override ID with testing ID
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X%02X%02X%02X", whitelistID[0], whitelistID[1],
+ whitelistID[2], whitelistID[3], whitelistID[4], whitelistID[5]);
+#endif // WHITELISTED_ID
+
+ // Given name must between 1 and 50 characters
+ char givenName[100];
+ char versionString[9];
+ getFirmwareVersion(versionString, sizeof(versionString), false);
+
+ if (productVariant == RTK_FACET_LBAND)
+ {
+ // Facet L-Band v3.12 AABBCCDD1122
+ snprintf(givenName, sizeof(givenName), "Facet LBand %s - %s", versionString,
+ hardwareID); // Get ready for JSON
+ }
+ else if (productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ // Facet L-Band Direct v3.12 AABBCCDD1122
+ snprintf(givenName, sizeof(givenName), "Facet LBand Direct %s - %s", versionString,
+ hardwareID); // Get ready for JSON
+ }
+
+ if (strlen(givenName) >= 50)
+ {
+ systemPrintf("Error: GivenName '%s' too long: %d bytes\r\n", givenName, strlen(givenName));
+ }
+
+ StaticJsonDocument<256> pointPerfectAPIPost;
+
+ // Determine if we use the SparkFun token or custom token
+ char tokenString[37] = "\0";
+ if (strlen(settings.pointPerfectDeviceProfileToken) == 0)
+ {
+ // Convert uint8_t array into string with dashes in spots
+ // We must assume u-blox will not change the position of their dashes or length of their token
+ if (!memcmp(pointPerfectTokenArray, developmentTokenArray, sizeof(developmentTokenArray)))
+ systemPrintln("Warning: Using the development token!");
+ for (int x = 0; x < sizeof(pointPerfectTokenArray); x++)
+ {
+ char temp[3];
+ snprintf(temp, sizeof(temp), "%02x", pointPerfectTokenArray[x]);
+ strcat(tokenString, temp);
+ if (x == 3 || x == 5 || x == 7 || x == 9)
+ strcat(tokenString, "-");
+ }
+ }
+ else
+ {
+ // Use the user's custom token
+ strcpy(tokenString, settings.pointPerfectDeviceProfileToken);
+ systemPrintf("Using custom token: %s\r\n", tokenString);
+ }
+
+ pointPerfectAPIPost["token"] = tokenString;
+ pointPerfectAPIPost["givenName"] = givenName;
+ pointPerfectAPIPost["hardwareId"] = hardwareID;
+ // pointPerfectAPIPost["tags"] = "mac";
+
+ String json;
+ serializeJson(pointPerfectAPIPost, json);
+
+ systemPrintf("Connecting to: %s\r\n", pointPerfectAPI);
+
+ HTTPClient http;
+ http.begin(client, pointPerfectAPI);
+ http.addHeader("Content-Type", "application/json");
+
+ int httpResponseCode = http.POST(json);
+
+ String response = http.getString();
+
+ http.end();
+
+ if (httpResponseCode != 200)
+ {
+ systemPrintf("HTTP response error %d: ", httpResponseCode);
+ systemPrintln(response);
+
+ // If a device has been deactivated, response will be: "HTTP response error 403: No plan for device
+ // device:9f49e97f-e6a7-4a08-8d58-ac7ecdc90e23"
+ if (response.indexOf("No plan for device") >= 0)
+ {
+ char hardwareID[13];
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X%02X%02X%02X", lbandMACAddress[0],
+ lbandMACAddress[1], lbandMACAddress[2], lbandMACAddress[3], lbandMACAddress[4],
+ lbandMACAddress[5]);
+
+ systemPrintf("This device has been deactivated. Please contact "
+ "support@sparkfun.com to renew the L-Band "
+ "subscription. Please reference device ID: %s\r\n",
+ hardwareID);
+
+ displayAccountExpired(5000);
+ }
+ // If a device is not whitelisted, reponse will be: "HTTP response error 403: Device hardware code not
+ // whitelisted"
+ else if (response.indexOf("not whitelisted") >= 0)
+ {
+ char hardwareID[13];
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X%02X%02X%02X", lbandMACAddress[0],
+ lbandMACAddress[1], lbandMACAddress[2], lbandMACAddress[3], lbandMACAddress[4],
+ lbandMACAddress[5]);
+
+ systemPrintf(
+ "This device is not white-listed. Please contact "
+ "support@sparkfun.com to get your subscription activated. Please reference device ID: %s\r\n",
+ hardwareID);
+
+ displayNotListed(5000);
+ }
+ break;
+ }
+ else
+ {
+ // Device is now active with ThingStream
+ // Pull pertinent values from response
+ jsonZtp = new DynamicJsonDocument(4096);
+ if (!jsonZtp)
+ {
+ systemPrintln("ERROR - Failed to allocate jsonZtp!\r\n");
+ break;
+ }
+
+ DeserializationError error = deserializeJson(*jsonZtp, response);
+ if (DeserializationError::Ok != error)
+ {
+ systemPrintln("JSON error");
+ break;
+ }
+ else
+ {
+ tempHolderPtr = (char *)malloc(MQTT_CERT_SIZE);
+ if (!tempHolderPtr)
+ {
+ systemPrintln("ERROR - Failed to allocate tempHolderPtr buffer!\r\n");
+ break;
+ }
+ strncpy(tempHolderPtr, (const char *)((*jsonZtp)["certificate"]), MQTT_CERT_SIZE - 1);
+ recordFile("certificate", tempHolderPtr, strlen(tempHolderPtr));
+
+ strncpy(tempHolderPtr, (const char *)((*jsonZtp)["privateKey"]), MQTT_CERT_SIZE - 1);
+ recordFile("privateKey", tempHolderPtr, strlen(tempHolderPtr));
+
+ // Validate the keys
+ if (!checkCertificates())
+ {
+ systemPrintln("ERROR - Failed to validate the Point Perfect certificates!");
+ }
+ else
+ {
+ if (settings.debugPpCertificate)
+ systemPrintln("Certificates written to the SD card.");
+
+ strcpy(settings.pointPerfectClientID, (const char *)((*jsonZtp)["clientId"]));
+ strcpy(settings.pointPerfectBrokerHost, (const char *)((*jsonZtp)["brokerHost"]));
+
+ // Note: from the ZTP documentation:
+ // ["subscriptions"][0] will contain the key distribution topic
+ // But, assuming the key distribution topic is always ["subscriptions"][0] is potentially brittle
+ // It is safer to check the "description" contains "key distribution topic"
+ int subscription =
+ findZtpJSONEntry("subscriptions", "description", "key distribution topic", jsonZtp);
+ if (subscription >= 0)
+ strncpy(settings.pointPerfectLBandTopic,
+ (const char *)((*jsonZtp)["subscriptions"][subscription]["path"]),
+ sizeof(settings.pointPerfectLBandTopic));
+
+ strcpy(settings.pointPerfectCurrentKey,
+ (const char *)((*jsonZtp)["dynamickeys"]["current"]["value"]));
+ settings.pointPerfectCurrentKeyDuration = (*jsonZtp)["dynamickeys"]["current"]["duration"];
+ settings.pointPerfectCurrentKeyStart = (*jsonZtp)["dynamickeys"]["current"]["start"];
+
+ strcpy(settings.pointPerfectNextKey, (const char *)((*jsonZtp)["dynamickeys"]["next"]["value"]));
+ settings.pointPerfectNextKeyDuration = (*jsonZtp)["dynamickeys"]["next"]["duration"];
+ settings.pointPerfectNextKeyStart = (*jsonZtp)["dynamickeys"]["next"]["start"];
+
+ if (settings.debugLBand == true)
+ {
+ systemPrintf(" pointPerfectCurrentKey: %s\r\n", settings.pointPerfectCurrentKey);
+ systemPrintf(" pointPerfectCurrentKeyStart: %lld - %s\r\n",
+ settings.pointPerfectCurrentKeyStart,
+ printDateFromUnixEpoch(settings.pointPerfectCurrentKeyStart /
+ 1000)); // printDateFromUnixEpoch expects seconds
+ systemPrintf(" pointPerfectCurrentKeyDuration: %lld - %s\r\n",
+ settings.pointPerfectCurrentKeyDuration,
+ printDaysFromDuration(settings.pointPerfectCurrentKeyDuration));
+ systemPrintf(" pointPerfectNextKey: %s\r\n", settings.pointPerfectNextKey);
+ systemPrintf(" pointPerfectNextKeyStart: %lld - %s\r\n", settings.pointPerfectNextKeyStart,
+ printDateFromUnixEpoch(settings.pointPerfectNextKeyStart / 1000));
+ systemPrintf(" pointPerfectNextKeyDuration: %lld - %s\r\n",
+ settings.pointPerfectNextKeyDuration,
+ printDaysFromDuration(settings.pointPerfectNextKeyDuration));
+ }
+ }
+ }
+ } // HTTP Response was 200
+
+ systemPrintln("Device successfully provisioned. Keys obtained.");
+
+ recordSystemSettings();
+ retVal = true;
+ } while (0);
+
+ // Free the allocated buffers
+ if (tempHolderPtr)
+ free(tempHolderPtr);
+ if (jsonZtp)
+ delete jsonZtp;
+
+ if (bluetoothOriginallyStarted == true)
+ bluetoothStart();
+
+ return (retVal);
+#else // COMPILE_WIFI
+ return (false);
+#endif // COMPILE_WIFI
+}
+
+// Find thing3 in (*jsonZtp)[thing1][n][thing2]. Return n on success. Return -1 on error / not found.
+int findZtpJSONEntry(const char *thing1, const char *thing2, const char *thing3, DynamicJsonDocument *jsonZtp)
+{
+ if (!jsonZtp)
+ return (-1);
+
+ int i = (*jsonZtp)[thing1].size();
+
+ if (i == 0)
+ return (-1);
+
+ for (int j = 0; j < i; j++)
+ if (strstr((const char *)(*jsonZtp)[thing1][j][thing2], thing3) != nullptr)
+ {
+ return j;
+ }
+
+ return (-1);
+}
+
+// Check certificate and privatekey for valid formatting
+// Return false if improperly formatted
+bool checkCertificates()
+{
+ bool validCertificates = true;
+ char *certificateContents = nullptr; // Holds the contents of the keys prior to MQTT connection
+ char *keyContents = nullptr;
+
+ // Allocate the buffers
+ certificateContents = (char *)malloc(MQTT_CERT_SIZE);
+ keyContents = (char *)malloc(MQTT_CERT_SIZE);
+ if ((!certificateContents) || (!keyContents))
+ {
+ if (certificateContents)
+ free(certificateContents);
+ if (keyContents)
+ free(keyContents);
+ systemPrintln("Failed to allocate content buffers!");
+ return (false);
+ }
+
+ // Load the certificate
+ memset(certificateContents, 0, MQTT_CERT_SIZE);
+ loadFile("certificate", certificateContents);
+
+ if (checkCertificateValidity(certificateContents, strlen(certificateContents)) == false)
+ {
+ if (settings.debugPpCertificate)
+ systemPrintln("Certificate is corrupt.");
+ validCertificates = false;
+ }
+
+ // Load the private key
+ memset(keyContents, 0, MQTT_CERT_SIZE);
+ loadFile("privateKey", keyContents);
+
+ if (checkPrivateKeyValidity(keyContents, strlen(keyContents)) == false)
+ {
+ if (settings.debugPpCertificate)
+ systemPrintln("PrivateKey is corrupt.");
+ validCertificates = false;
+ }
+
+ // Free the content buffers
+ if (certificateContents)
+ free(certificateContents);
+ if (keyContents)
+ free(keyContents);
+
+ if (settings.debugPpCertificate)
+ systemPrintln("Stored certificates are valid!");
+ return (validCertificates);
+}
+
+// Check if a given certificate is in a valid format
+// This was created to detect corrupt or invalid certificates caused by bugs in v3.0 to and including v3.3.
+bool checkCertificateValidity(char *certificateContent, int certificateContentSize)
+{
+ // Check for valid format of certificate
+ // From ssl_client.cpp
+ // https://stackoverflow.com/questions/70670070/mbedtls-cannot-parse-valid-x509-certificate
+ mbedtls_x509_crt certificate;
+ mbedtls_x509_crt_init(&certificate);
+
+ int result_code =
+ mbedtls_x509_crt_parse(&certificate, (unsigned char *)certificateContent, certificateContentSize + 1);
+
+ mbedtls_x509_crt_free(&certificate);
+
+ if (result_code < 0)
+ {
+ if (settings.debugPpCertificate)
+ systemPrintln("ERROR - Invalid certificate format!");
+ return (false);
+ }
+
+ return (true);
+}
+
+// Check if a given private key is in a valid format
+// This was created to detect corrupt or invalid private keys caused by bugs in v3.0 to and including v3.3.
+// See https://github.com/Mbed-TLS/mbedtls/blob/development/library/pkparse.c
+bool checkPrivateKeyValidity(char *privateKey, int privateKeySize)
+{
+ // Check for valid format of private key
+ // From ssl_client.cpp
+ // https://stackoverflow.com/questions/70670070/mbedtls-cannot-parse-valid-x509-certificate
+ mbedtls_pk_context pk;
+ mbedtls_pk_init(&pk);
+
+ int result_code = mbedtls_pk_parse_key(&pk, (unsigned char *)privateKey, privateKeySize + 1, nullptr, 0);
+ mbedtls_pk_free(&pk);
+ if (result_code < 0)
+ {
+ if (settings.debugPpCertificate)
+ systemPrintln("ERROR - Invalid private key format!");
+ return (false);
+ }
+ return (true);
+}
+
+// When called, removes the files used for SSL to PointPerfect obtained during provisioning
+// Also deletes keys so the user can immediately re-provision
+void erasePointperfectCredentials()
+{
+ char fileName[80];
+
+ snprintf(fileName, sizeof(fileName), "/%s_%s_%d.txt", platformFilePrefix, "certificate", profileNumber);
+ LittleFS.remove(fileName);
+
+ snprintf(fileName, sizeof(fileName), "/%s_%s_%d.txt", platformFilePrefix, "privateKey", profileNumber);
+ LittleFS.remove(fileName);
+ strcpy(settings.pointPerfectCurrentKey, ""); // Clear contents
+ strcpy(settings.pointPerfectNextKey, ""); // Clear contents
+}
+
+// Subscribe to MQTT channel, grab keys, then stop
+bool pointperfectUpdateKeys()
+{
+#ifdef COMPILE_WIFI
+ bool bluetoothOriginallyStarted = true;
+ if (bluetoothState == BT_OFF)
+ bluetoothOriginallyStarted = false;
+
+ bluetoothStop(); // Release available heap to allow room for TLS
+
+ char *certificateContents = nullptr; // Holds the contents of the keys prior to MQTT connection
+ char *keyContents = nullptr;
+ WiFiClientSecure secureClient;
+ bool gotKeys = false;
+
+ do
+ {
+ // Allocate the buffers
+ certificateContents = (char *)malloc(MQTT_CERT_SIZE);
+ keyContents = (char *)malloc(MQTT_CERT_SIZE);
+ if ((!certificateContents) || (!keyContents))
+ {
+ if (certificateContents)
+ free(certificateContents);
+ systemPrintln("Failed to allocate content buffers!");
+ break;
+ }
+
+ // Get the certificate
+ memset(certificateContents, 0, MQTT_CERT_SIZE);
+ loadFile("certificate", certificateContents);
+ secureClient.setCertificate(certificateContents);
+
+ // Get the private key
+ memset(keyContents, 0, MQTT_CERT_SIZE);
+ loadFile("privateKey", keyContents);
+ secureClient.setPrivateKey(keyContents);
+
+ secureClient.setCACert(AWS_PUBLIC_CERT);
+
+ PubSubClient mqttClient(secureClient);
+ mqttClient.setCallback(mqttCallback);
+ mqttClient.setServer(settings.pointPerfectBrokerHost, 8883);
+
+ systemPrintf("Attempting to connect to MQTT broker: %s\r\n", settings.pointPerfectBrokerHost);
+
+ if (mqttClient.connect(settings.pointPerfectClientID) == true)
+ {
+ // Successful connection
+ systemPrintln("MQTT connected");
+
+ mqttClient.subscribe(settings.pointPerfectLBandTopic);
+ }
+ else
+ {
+ systemPrintln("Failed to connect to MQTT Broker");
+
+ // MQTT does not provide good error reporting.
+ // Throw out everything and attempt to provision the device to get better error checking.
+ pointperfectProvisionDevice();
+ break; //Skip the remaining MQTT checking, release resources
+ }
+
+ systemPrint("Waiting for keys");
+
+ mqttMessageReceived = false;
+
+ // Wait for callback
+ startTime = millis();
+ while (1)
+ {
+ mqttClient.loop();
+ if (mqttMessageReceived == true)
+ break;
+ if (mqttClient.connected() == false)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("Client disconnected");
+ break;
+ }
+ if (millis() - startTime > 8000)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("Channel failed to respond");
+ break;
+ }
+
+ // Continue waiting for the keys
+ delay(100);
+ systemPrint(".");
+ }
+ systemPrintln();
+
+ // Determine if the keys were updated
+ if (mqttMessageReceived)
+ {
+ systemPrintln("Keys successfully updated");
+ gotKeys = true;
+ }
+
+ // Done with the MQTT client
+ mqttClient.disconnect();
+ } while (0);
+
+ // Free the content buffers
+ if (keyContents)
+ free(keyContents);
+ if (certificateContents)
+ free(certificateContents);
+
+ if (bluetoothOriginallyStarted == true)
+ bluetoothStart();
+
+ // Return the key status
+ return (gotKeys);
+#else // COMPILE_WIFI
+ return (false);
+#endif // COMPILE_WIFI
+}
+
+char *ltrim(char *s)
+{
+ while (isspace(*s))
+ s++;
+ return s;
+}
+
+// Called when a subscribed to message arrives
+void mqttCallback(char *topic, byte *message, unsigned int length)
+{
+ if (String(topic) == settings.pointPerfectLBandTopic)
+ {
+ // Separate the UBX message into its constituent Key/ToW/Week parts
+ // Obtained from SparkFun u-blox Arduino library - setDynamicSPARTNKeys()
+ byte *payLoad = &message[6];
+ uint8_t currentKeyLength = payLoad[5];
+ uint16_t currentWeek = (payLoad[7] << 8) | payLoad[6];
+ uint32_t currentToW =
+ (payLoad[11] << 8 * 3) | (payLoad[10] << 8 * 2) | (payLoad[9] << 8 * 1) | (payLoad[8] << 8 * 0);
+
+ char currentKey[currentKeyLength];
+ memcpy(¤tKey, &payLoad[20], currentKeyLength);
+
+ uint8_t nextKeyLength = payLoad[13];
+ uint16_t nextWeek = (payLoad[15] << 8) | payLoad[14];
+ uint32_t nextToW =
+ (payLoad[19] << 8 * 3) | (payLoad[18] << 8 * 2) | (payLoad[17] << 8 * 1) | (payLoad[16] << 8 * 0);
+
+ char nextKey[nextKeyLength];
+ memcpy(&nextKey, &payLoad[20 + currentKeyLength], nextKeyLength);
+
+ // Convert byte array to HEX character array
+ strcpy(settings.pointPerfectCurrentKey, ""); // Clear contents
+ strcpy(settings.pointPerfectNextKey, ""); // Clear contents
+ for (int x = 0; x < 16; x++) // Force length to max of 32 bytes
+ {
+ char temp[3];
+ snprintf(temp, sizeof(temp), "%02X", currentKey[x]);
+ strcat(settings.pointPerfectCurrentKey, temp);
+
+ snprintf(temp, sizeof(temp), "%02X", nextKey[x]);
+ strcat(settings.pointPerfectNextKey, temp);
+ }
+
+ // Convert from ToW and Week to key duration and key start
+ WeekToWToUnixEpoch(&settings.pointPerfectCurrentKeyStart, currentWeek, currentToW);
+ WeekToWToUnixEpoch(&settings.pointPerfectNextKeyStart, nextWeek, nextToW);
+
+ settings.pointPerfectCurrentKeyStart -= getLeapSeconds(); // Remove GPS leap seconds to align with u-blox
+ settings.pointPerfectNextKeyStart -= getLeapSeconds();
+
+ settings.pointPerfectCurrentKeyStart *= 1000; // Convert to ms
+ settings.pointPerfectNextKeyStart *= 1000;
+
+ settings.pointPerfectCurrentKeyDuration =
+ settings.pointPerfectNextKeyStart - settings.pointPerfectCurrentKeyStart - 1;
+ // settings.pointPerfectNextKeyDuration =
+ // settings.pointPerfectCurrentKeyDuration; // We assume next key duration is the same as current key
+ // duration because we have to
+
+ settings.pointPerfectNextKeyDuration = (1000LL * 60 * 60 * 24 * 28) - 1; // Assume next key duration is 28 days
+
+ if (settings.debugLBand == true)
+ {
+ systemPrintln();
+ systemPrintf(" pointPerfectCurrentKey: %s\r\n", settings.pointPerfectCurrentKey);
+ systemPrintf(" pointPerfectCurrentKeyStart: %lld - %s\r\n", settings.pointPerfectCurrentKeyStart,
+ printDateFromUnixEpoch(settings.pointPerfectCurrentKeyStart));
+ systemPrintf(" pointPerfectCurrentKeyDuration: %lld - %s\r\n", settings.pointPerfectCurrentKeyDuration,
+ printDaysFromDuration(settings.pointPerfectCurrentKeyDuration));
+ systemPrintf(" pointPerfectNextKey: %s\r\n", settings.pointPerfectNextKey);
+ systemPrintf(" pointPerfectNextKeyStart: %lld - %s\r\n", settings.pointPerfectNextKeyStart,
+ printDateFromUnixEpoch(settings.pointPerfectNextKeyStart));
+ systemPrintf(" pointPerfectNextKeyDuration: %lld - %s\r\n", settings.pointPerfectNextKeyDuration,
+ printDaysFromDuration(settings.pointPerfectNextKeyDuration));
+ }
+ }
+
+ mqttMessageReceived = true;
+}
+
+// Get a date from a user
+// Return true if validated
+// https://www.includehelp.com/c-programs/validate-date.aspx
+bool getDate(uint8_t &dd, uint8_t &mm, uint16_t &yy)
+{
+ systemPrint("Enter Day: ");
+ dd = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ systemPrint("Enter Month: ");
+ mm = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ systemPrint("Enter Year (YYYY): ");
+ yy = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ // check year
+ if (yy >= 2022 && yy <= 9999)
+ {
+ // check month
+ if (mm >= 1 && mm <= 12)
+ {
+ // check days
+ if ((dd >= 1 && dd <= 31) && (mm == 1 || mm == 3 || mm == 5 || mm == 7 || mm == 8 || mm == 10 || mm == 12))
+ return (true);
+ else if ((dd >= 1 && dd <= 30) && (mm == 4 || mm == 6 || mm == 9 || mm == 11))
+ return (true);
+ else if ((dd >= 1 && dd <= 28) && (mm == 2))
+ return (true);
+ else if (dd == 29 && mm == 2 && (yy % 400 == 0 || (yy % 4 == 0 && yy % 100 != 0)))
+ return (true);
+ else
+ {
+ printf("Day is invalid.\n");
+ return (false);
+ }
+ }
+ else
+ {
+ printf("Month is not valid.\n");
+ return (false);
+ }
+ }
+
+ printf("Year is not valid.\n");
+ return (false);
+}
+
+// Given an epoch in ms, return the number of days from given and Epoch now
+int daysFromEpoch(long long endEpoch)
+{
+ endEpoch /= 1000; // Convert PointPerfect ms Epoch to s
+
+ if (online.rtc == false)
+ {
+ // If we don't have RTC we can't calculate days to expire
+ if (settings.debugLBand == true)
+ systemPrintln("No RTC available");
+ return (0);
+ }
+
+ long localEpoch = rtc.getEpoch();
+
+ long delta = endEpoch - localEpoch; // number of s between dates
+ delta /= (60 * 60); // hours
+ delta /= 24; // days
+ return ((int)delta);
+}
+
+// Given the key's starting epoch time, and the key's duration
+// Convert from ms to s
+// Add leap seconds (the API reports start times with GPS leap seconds removed)
+// Convert from unix epoch (the API reports unix epoch time) to GPS epoch (the NED-D9S expects)
+// Note: I believe the Thingstream API is reporting startEpoch 18 seconds less than actual
+long long thingstreamEpochToGPSEpoch(long long startEpoch)
+{
+ long long epoch = startEpoch;
+ epoch /= 1000; // Convert PointPerfect ms Epoch to s
+
+ // Convert Unix Epoch time from PointPerfect to GPS Time Of Week needed for UBX message
+ long long gpsEpoch = epoch - 315964800 + getLeapSeconds(); // Shift to GPS Epoch.
+ return (gpsEpoch);
+}
+
+// Query GNSS for current leap seconds
+uint8_t getLeapSeconds()
+{
+ if (online.gnss == true)
+ {
+ if (leapSeconds == 0) // Check to see if we've already set it
+ {
+ sfe_ublox_ls_src_e leapSecSource;
+ leapSeconds = theGNSS.getCurrentLeapSeconds(leapSecSource);
+ return (leapSeconds);
+ }
+ }
+ return (18); // Default to 18 if GNSS is offline
+}
+
+// Covert a given key's expiration date to a GPS Epoch, so that we can calculate GPS Week and ToW
+// Add a millisecond to roll over from 11:59UTC to midnight of the following day
+// Convert from unix epoch (time lib outputs unix) to GPS epoch (the NED-D9S expects)
+long long dateToGPSEpoch(uint8_t day, uint8_t month, uint16_t year)
+{
+ long long unixEpoch = dateToUnixEpoch(day, month, year); // Returns Unix Epoch
+
+ // Convert Unix Epoch time from PP to GPS Time Of Week needed for UBX message
+ long long gpsEpoch = unixEpoch - 315964800; // Shift to GPS Epoch.
+
+ return (gpsEpoch);
+}
+
+// Given an epoch, set the GPSWeek and GPSToW
+void epochToWeekToW(long long epoch, uint16_t *GPSWeek, uint32_t *GPSToW)
+{
+ *GPSWeek = (uint16_t)(epoch / (7 * 24 * 60 * 60));
+ *GPSToW = (uint32_t)(epoch % (7 * 24 * 60 * 60));
+}
+
+// Given an epoch, set the GPSWeek and GPSToW
+void WeekToWToUnixEpoch(uint64_t *unixEpoch, uint16_t GPSWeek, uint32_t GPSToW)
+{
+ *unixEpoch = GPSWeek * (7 * 24 * 60 * 60); // 2192
+ *unixEpoch += GPSToW; // 518400
+ *unixEpoch += 315964800;
+}
+
+// Given a GPS Week and ToW, convert to an expiration date
+void gpsWeekToWToDate(uint16_t keyGPSWeek, uint32_t keyGPSToW, long *expDay, long *expMonth, long *expYear)
+{
+ long gpsDays = gpsToMjd(0, (long)keyGPSWeek, (long)keyGPSToW); // Covert ToW and Week to # of days since Jan 6, 1980
+ mjdToDate(gpsDays, expYear, expMonth, expDay);
+}
+
+// Given a date, convert into epoch
+// https://www.epochconverter.com/programming/c
+long dateToUnixEpoch(uint8_t day, uint8_t month, uint16_t year)
+{
+ struct tm t;
+ time_t t_of_day;
+
+ t.tm_year = year - 1900;
+ t.tm_mon = month - 1;
+ t.tm_mday = day;
+
+ t.tm_hour = 0;
+ t.tm_min = 0;
+ t.tm_sec = 0;
+ t.tm_isdst = -1; // Is DST on? 1 = yes, 0 = no, -1 = unknown
+
+ t_of_day = mktime(&t);
+
+ return (t_of_day);
+}
+
+// Given a date, calculate and return the key start in unixEpoch
+void dateToKeyStart(uint8_t expDay, uint8_t expMonth, uint16_t expYear, uint64_t *settingsKeyStart)
+{
+ long long expireUnixEpoch = dateToUnixEpoch(expDay, expMonth, expYear);
+
+ // Thingstream lists the date that a key expires at midnight
+ // So if a user types in March 7th, 2022 as exp date the key's Week and ToW need to be
+ // calculated from (March 7th - 27 days).
+ long long startUnixEpoch = expireUnixEpoch - (27 * 24 * 60 * 60); // Move back 27 days
+
+ // Additionally, Thingstream seems to be reporting Epochs that do not have leap seconds
+ startUnixEpoch -= getLeapSeconds(); // Modify our Epoch to match Point Perfect
+
+ // PointPerfect uses/reports unix epochs in milliseconds
+ *settingsKeyStart = startUnixEpoch * 1000L; // Convert to ms
+
+ uint16_t keyGPSWeek;
+ uint32_t keyGPSToW;
+ long long gpsEpoch = thingstreamEpochToGPSEpoch(*settingsKeyStart);
+
+ epochToWeekToW(gpsEpoch, &keyGPSWeek, &keyGPSToW);
+
+ // Print ToW and Week for debugging
+ if (settings.debugLBand == true)
+ {
+ systemPrintf(" expireUnixEpoch: %lld - %s\r\n", expireUnixEpoch, printDateFromUnixEpoch(expireUnixEpoch));
+ systemPrintf(" startUnixEpoch: %lld - %s\r\n", startUnixEpoch, printDateFromUnixEpoch(startUnixEpoch));
+ systemPrintf(" gpsEpoch: %lld - %s\r\n", gpsEpoch, printDateFromGPSEpoch(gpsEpoch));
+ systemPrintf(" KeyStart: %lld - %s\r\n", *settingsKeyStart, printDateFromUnixEpoch(*settingsKeyStart));
+ systemPrintf(" keyGPSWeek: %d\r\n", keyGPSWeek);
+ systemPrintf(" keyGPSToW: %d\r\n", keyGPSToW);
+ }
+}
+
+/*
+ http://www.leapsecond.com/tools/gpsdate.c
+ Return Modified Julian Day given calendar year,
+ month (1-12), and day (1-31).
+ - Valid for Gregorian dates from 17-Nov-1858.
+ - Adapted from sci.astro FAQ.
+*/
+
+long dateToMjd(long Year, long Month, long Day)
+{
+ return 367 * Year - 7 * (Year + (Month + 9) / 12) / 4 - 3 * ((Year + (Month - 9) / 7) / 100 + 1) / 4 +
+ 275 * Month / 9 + Day + 1721028 - 2400000;
+}
+
+/*
+ Convert Modified Julian Day to calendar date.
+ - Assumes Gregorian calendar.
+ - Adapted from Fliegel/van Flandern ACM 11/#10 p 657 Oct 1968.
+*/
+
+void mjdToDate(long Mjd, long *Year, long *Month, long *Day)
+{
+ long J, C, Y, M;
+
+ J = Mjd + 2400001 + 68569;
+ C = 4 * J / 146097;
+ J = J - (146097 * C + 3) / 4;
+ Y = 4000 * (J + 1) / 1461001;
+ J = J - 1461 * Y / 4 + 31;
+ M = 80 * J / 2447;
+ *Day = J - 2447 * M / 80;
+ J = M / 11;
+ *Month = M + 2 - (12 * J);
+ *Year = 100 * (C - 49) + Y + J;
+}
+
+/*
+ Convert GPS Week and Seconds to Modified Julian Day.
+ - Ignores UTC leap seconds.
+*/
+
+long gpsToMjd(long GpsCycle, long GpsWeek, long GpsSeconds)
+{
+ long GpsDays = ((GpsCycle * 1024) + GpsWeek) * 7 + (GpsSeconds / 86400);
+ // GpsDays -= 1; //Correction
+ return dateToMjd(1980, 1, 6) + GpsDays;
+}
+
+// When new PMP message arrives from NEO-D9S push it back to ZED-F9P
+void pushRXMPMP(UBX_RXM_PMP_message_data_t *pmpData)
+{
+ uint16_t payloadLen = ((uint16_t)pmpData->lengthMSB << 8) | (uint16_t)pmpData->lengthLSB;
+
+ if (settings.debugLBand == true && !inMainMenu)
+ systemPrintf("Pushing %d bytes of RXM-PMP data to GNSS\r\n", payloadLen);
+
+ theGNSS.pushRawData(&pmpData->sync1, (size_t)payloadLen + 6); // Push the sync chars, class, ID, length and payload
+ theGNSS.pushRawData(&pmpData->checksumA, (size_t)2); // Push the checksum bytes
+}
+
+// If we have decryption keys, and L-Band is online, configure module
+void pointperfectApplyKeys()
+{
+ if (online.lband == true)
+ {
+ if (online.gnss == false)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("ZED-F9P not available");
+ return;
+ }
+
+ // NEO-D9S encrypted PMP messages are only supported on ZED-F9P firmware v1.30 and above
+ if (zedModuleType != PLATFORM_F9P)
+ {
+ systemPrintln("Error: PointPerfect corrections currently only supported on the ZED-F9P.");
+ return;
+ }
+ if (zedFirmwareVersionInt < 130)
+ {
+ systemPrintln("Error: PointPerfect corrections currently supported by ZED-F9P firmware v1.30 and above. "
+ "Please upgrade your ZED firmware: "
+ "https://learn.sparkfun.com/tutorials/how-to-upgrade-firmware-of-a-u-blox-gnss-receiver");
+ return;
+ }
+
+ if (strlen(settings.pointPerfectNextKey) > 0)
+ {
+ const uint8_t currentKeyLengthBytes = 16;
+ const uint8_t nextKeyLengthBytes = 16;
+
+ uint16_t currentKeyGPSWeek;
+ uint32_t currentKeyGPSToW;
+ long long epoch = thingstreamEpochToGPSEpoch(settings.pointPerfectCurrentKeyStart);
+ epochToWeekToW(epoch, ¤tKeyGPSWeek, ¤tKeyGPSToW);
+
+ uint16_t nextKeyGPSWeek;
+ uint32_t nextKeyGPSToW;
+ epoch = thingstreamEpochToGPSEpoch(settings.pointPerfectNextKeyStart);
+ epochToWeekToW(epoch, &nextKeyGPSWeek, &nextKeyGPSToW);
+
+ theGNSS.setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, 1); // use LBAND PMP message
+
+ theGNSS.setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C
+
+ theGNSS.setVal8(UBLOX_CFG_NAVHPG_DGNSSMODE,
+ 3); // Set the differential mode - ambiguities are fixed whenever possible
+
+ bool response = theGNSS.setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW,
+ settings.pointPerfectCurrentKey, nextKeyLengthBytes,
+ nextKeyGPSWeek, nextKeyGPSToW, settings.pointPerfectNextKey);
+
+ if (response == false)
+ systemPrintln("setDynamicSPARTNKeys failed");
+ else
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("PointPerfect keys applied");
+ online.lbandCorrections = true;
+ }
+ }
+ else
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("No PointPerfect keys available");
+ }
+ }
+}
+
+// Check if the PMP data is being decrypted successfully
+void checkRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct)
+{
+ if (settings.debugLBand == true && !inMainMenu)
+ systemPrintf("L-Band Eb/N0[dB] (>9 is good): %0.2f\r\n", ubxDataStruct->ebno * pow(2, -3));
+
+ lBandEBNO = ubxDataStruct->ebno * pow(2, -3);
+
+ if (ubxDataStruct->statusInfo.bits.msgDecrypted == 2) // Successfully decrypted
+ {
+ lbandCorrectionsReceived = true;
+ lastLBandDecryption = millis();
+ }
+ else
+ {
+ if (settings.debugLBand == true && !inMainMenu)
+ systemPrintln("PMP decryption failed");
+ }
+}
+
+#endif // COMPILE_L_BAND
+
+//----------------------------------------
+// Global L-Band Routines
+//----------------------------------------
+
+// Check if NEO-D9S is connected. Configure if available.
+void beginLBand()
+{
+ // Skip if going into configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("configureViaEthernet: skipping beginLBand");
+ return;
+ }
+
+#ifdef COMPILE_L_BAND
+ if (i2cLBand.begin(Wire, 0x43) ==
+ false) // Connect to the u-blox NEO-D9S using Wire port. The D9S default I2C address is 0x43 (not 0x42)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("L-Band not detected");
+ 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());
+
+ printNEOInfo(); // Print module firmware version
+ }
+
+ if (online.gnss == true)
+ {
+ theGNSS.checkUblox(); // Regularly poll to get latest data and any RTCM
+ theGNSS.checkCallbacks(); // Process any callbacks: ie, eventTriggerReceived
+ }
+
+ uint32_t LBandFreq;
+ // If we have a fix, check which frequency to use
+ if (fixType == 2 || fixType == 3 || fixType == 4 || fixType == 5) // 2D, 3D, 3D+DR, or Time
+ {
+ 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.debugLBand == 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[settings.geographicRegion].frequency;
+ systemPrintf("Error: Unknown L-Band geographic region. Using %s (%dHz)\r\n", Regional_Information_Table[settings.geographicRegion].name, LBandFreq);
+ }
+ }
+ else
+ {
+ LBandFreq = Regional_Information_Table[settings.geographicRegion].frequency;
+ if (settings.debugLBand == 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();
+
+ lBandCommunicationEnabled = zedEnableLBandCommunication();
+
+ if (response == false)
+ systemPrintln("L-Band failed to configure");
+
+ i2cLBand.softwareResetGNSSOnly(); // Do a restart
+
+ if (settings.debugLBand == true)
+ systemPrintln("L-Band online");
+
+ online.lband = true;
+#endif // COMPILE_L_BAND
+}
+
+// Set 'home' WiFi credentials
+// Provision device on ThingStream
+// Download keys
+void menuPointPerfect()
+{
+#ifdef COMPILE_L_BAND
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: PointPerfect Corrections");
+
+ if (settings.debugLBand == true)
+ systemPrintf("Time to first L-Band fix: %ds Restarts: %d\r\n", lbandTimeToFix / 1000, lbandRestarts);
+
+ if (settings.debugLBand == true)
+ systemPrintf("settings.pointPerfectLBandTopic: %s\r\n", settings.pointPerfectLBandTopic);
+
+ systemPrint("Days until keys expire: ");
+ if (strlen(settings.pointPerfectCurrentKey) > 0)
+ {
+ if (online.rtc == false)
+ {
+ // If we don't have RTC we can't calculate days to expire
+ systemPrintln("No RTC");
+ }
+ else
+ {
+ int daysRemaining =
+ daysFromEpoch(settings.pointPerfectNextKeyStart + settings.pointPerfectNextKeyDuration + 1);
+
+ if (daysRemaining < 0)
+ systemPrintln("Expired");
+ else
+ systemPrintln(daysRemaining);
+ }
+ }
+ else
+ systemPrintln("No keys");
+
+ systemPrint("1) Use PointPerfect Corrections: ");
+ if (settings.enablePointPerfectCorrections == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ systemPrint("2) Toggle Auto Key Renewal: ");
+ if (settings.autoKeyRenewal == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ if (strlen(settings.pointPerfectCurrentKey) == 0 || strlen(settings.pointPerfectLBandTopic) == 0)
+ systemPrintln("3) Provision Device");
+ else
+ systemPrintln("3) Update Keys");
+
+ systemPrintln("4) Show device ID");
+
+ systemPrintln("c) Clear the Keys");
+
+ systemPrintln("k) Manual Key Entry");
+
+ systemPrint("g) Geographic Region: ");
+ systemPrintln(Regional_Information_Table[settings.geographicRegion].name);
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ {
+ settings.enablePointPerfectCorrections ^= 1;
+ }
+ else if (incoming == 2)
+ {
+ settings.autoKeyRenewal ^= 1;
+ }
+ else if (incoming == 3)
+ {
+ if (wifiNetworkCount() == 0)
+ {
+ systemPrintln("Error: Please enter at least one SSID before getting keys");
+ }
+ else
+ {
+ if (wifiConnect(10000) == true)
+ {
+ // Check if we have certificates
+ char fileName[80];
+ snprintf(fileName, sizeof(fileName), "/%s_%s_%d.txt", platformFilePrefix, "certificate",
+ profileNumber);
+ if (LittleFS.exists(fileName) == false)
+ {
+ pointperfectProvisionDevice(); // Connect to ThingStream API and get keys
+ }
+ else if (strlen(settings.pointPerfectCurrentKey) == 0 ||
+ strlen(settings.pointPerfectLBandTopic) == 0)
+ {
+ pointperfectProvisionDevice(); // Connect to ThingStream API and get keys
+ }
+ else // We have certs and keys
+ {
+ // Check that the certs are valid
+ if (checkCertificates() == true)
+ {
+ // Update the keys
+ pointperfectUpdateKeys();
+ }
+ else
+ {
+ // Erase keys
+ erasePointperfectCredentials();
+
+ // Provision device
+ pointperfectProvisionDevice(); // Connect to ThingStream API and get keys
+ }
+ }
+ }
+ else
+ {
+ systemPrintln("Error: No WiFi available to get keys");
+ break;
+ }
+ }
+
+ WIFI_STOP();
+ }
+ else if (incoming == 4)
+ {
+ char hardwareID[13];
+ snprintf(hardwareID, sizeof(hardwareID), "%02X%02X%02X%02X%02X%02X", lbandMACAddress[0], lbandMACAddress[1],
+ lbandMACAddress[2], lbandMACAddress[3], lbandMACAddress[4], lbandMACAddress[5]);
+ systemPrintf("Device ID: %s\r\n", hardwareID);
+ }
+ else if (incoming == 'c')
+ {
+ settings.pointPerfectCurrentKey[0] = 0;
+ settings.pointPerfectNextKey[0] = 0;
+ }
+ else if (incoming == 'k')
+ {
+ menuPointPerfectKeys();
+ }
+ else if (incoming == 'g')
+ {
+ settings.geographicRegion++;
+ if (settings.geographicRegion >= numRegionalAreas)
+ settings.geographicRegion = 0;
+ }
+ 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 (strlen(settings.pointPerfectClientID) > 0)
+ {
+ pointperfectApplyKeys();
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+#endif // COMPILE_L_BAND
+}
+
+// Process any new L-Band from I2C
+void updateLBand()
+{
+ // Skip if in configure-via-ethernet mode
+ if (configureViaEthernet)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("configureViaEthernet: skipping updateLBand");
+ return;
+ }
+
+#ifdef COMPILE_L_BAND
+ if (online.lbandCorrections == true)
+ {
+ i2cLBand.checkUblox(); // Check for the arrival of new PMP data and process it.
+ i2cLBand.checkCallbacks(); // Check if any L-Band callbacks are waiting to be processed.
+
+ // If a certain amount of time has elapsed between last decryption, turn off L-Band icon
+ if (lbandCorrectionsReceived == true && millis() - lastLBandDecryption > 5000)
+ lbandCorrectionsReceived = false;
+
+ // If we don't get an L-Band fix within Timeout, hot-start ZED-F9x
+ if (systemState == STATE_ROVER_RTK_FLOAT)
+ {
+ if (millis() - lbandLastReport > 1000)
+ {
+ lbandLastReport = millis();
+
+ if (settings.debugLBand == true)
+ systemPrintf("ZED restarts: %d Time remaining before L-Band forced restart: %ds\r\n", lbandRestarts,
+ settings.lbandFixTimeout_seconds - ((millis() - lbandTimeFloatStarted) / 1000));
+ }
+
+ if (settings.lbandFixTimeout_seconds > 0)
+ {
+ if ((millis() - lbandTimeFloatStarted) > (settings.lbandFixTimeout_seconds * 1000L))
+ {
+ lbandRestarts++;
+
+ lbandTimeFloatStarted =
+ millis(); // Restart timer for L-Band. Don't immediately reset ZED to achieve fix.
+
+ // Hotstart ZED to try to get RTK lock
+ theGNSS.softwareResetGNSSOnly();
+
+ if (settings.debugLBand == true)
+ systemPrintf("Restarting ZED. Number of L-Band restarts: %d\r\n", lbandRestarts);
+ }
+ }
+ }
+ else if (carrSoln == 2 && lbandTimeToFix == 0)
+ {
+ lbandTimeToFix = millis();
+ if (settings.debugLBand == true)
+ systemPrintf("Time to first L-Band fix: %ds\r\n", lbandTimeToFix / 1000);
+ }
+
+ if ((millis() - rtcmLastPacketReceived) / 1000 > settings.rtcmTimeoutBeforeUsingLBand_s)
+ {
+ // If we have not received RTCM in a certain amount of time,
+ // and if communication was disabled because RTCM was being received at some point,
+ // re-enable L-Band communcation
+ if (lBandCommunicationEnabled == false)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("Enabling L-Band communication due to RTCM timeout");
+ lBandCommunicationEnabled = zedEnableLBandCommunication();
+ }
+ }
+ else
+ {
+ // If we *have* recently received RTCM then disable corrections from then NEO-D9S L-Band receiver
+ if (lBandCommunicationEnabled == true)
+ {
+ if (settings.debugLBand == true)
+ systemPrintln("Disabling L-Band communication due to RTCM reception");
+ lBandCommunicationEnabled = !zedDisableLBandCommunication(); // zedDisableLBandCommunication() returns
+ // true if we successfully disabled
+ }
+ }
+ }
+
+#endif // COMPILE_L_BAND
+}
diff --git a/Firmware/RTK_Surveyor/menuPorts.ino b/Firmware/RTK_Surveyor/menuPorts.ino
index 6651c23c4..d4e255f2b 100644
--- a/Firmware/RTK_Surveyor/menuPorts.ino
+++ b/Firmware/RTK_Surveyor/menuPorts.ino
@@ -1,159 +1,593 @@
void menuPorts()
{
- if(productVariant == RTK_SURVEYOR)
- menuPortsSurveyor();
- else if(productVariant == RTK_EXPRESS)
- menuPortsExpress();
+ if (productVariant == RTK_SURVEYOR || productVariant == REFERENCE_STATION)
+ menuPortsSurveyor();
+ else if (productVariant == RTK_EXPRESS || productVariant == RTK_EXPRESS_PLUS || productVariant == RTK_FACET ||
+ productVariant == RTK_FACET_LBAND || productVariant == RTK_FACET_LBAND_DIRECT)
+ menuPortsMultiplexed();
}
-//Set the baud rates for the radio and data ports
+// Set the baud rates for the radio and data ports
void menuPortsSurveyor()
{
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Port Menu"));
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Ports");
- Serial.print(F("1) Set serial baud rate for Radio Port: "));
- Serial.print(getSerialRate(COM_PORT_UART2));
- Serial.println(F(" bps"));
+ systemPrint("1) Set serial baud rate for Radio Port: ");
+ if (settings.radioPortBaud == 0)
+ {
+ systemPrintln("Disabled");
+ }
+ else
+ {
+ systemPrint(theGNSS.getVal32(UBLOX_CFG_UART2_BAUDRATE));
+ systemPrintln(" bps");
+ }
- Serial.print(F("2) Set serial baud rate for Data Port: "));
- Serial.print(getSerialRate(COM_PORT_UART1));
- Serial.println(F(" bps"));
+ systemPrint("2) Set serial baud rate for Data Port: ");
+ if (settings.dataPortBaud == 0)
+ {
+ systemPrintln("Disabled");
+ }
+ else
+ {
+ systemPrint(theGNSS.getVal32(UBLOX_CFG_UART1_BAUDRATE));
+ systemPrintln(" bps");
+ }
- Serial.println(F("x) Exit"));
+ systemPrint("3) GNSS UART2 UBX Protocol In: ");
+ if (settings.enableUART2UBXIn == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
- byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds
+ systemPrintln("x) Exit");
- if (incoming == '1')
- {
- Serial.print(F("Enter baud rate (4800 to 921600) for Radio Port: "));
- int newBaud = getNumber(menuTimeout); //Timeout after x seconds
- if (newBaud < 4800 || newBaud > 921600)
- {
- Serial.println(F("Error: baud rate out of range"));
- }
- else
- {
- settings.radioPortBaud = newBaud;
- i2cGNSS.setSerialRate(newBaud, COM_PORT_UART2); //Set Radio Port
- }
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ systemPrint("Enter baud rate (4800 to 921600, 0 = disable) for Radio Port: ");
+ int newBaud = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((newBaud != INPUT_RESPONSE_GETNUMBER_EXIT) && (newBaud != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (newBaud == 0 || newBaud == 4800 || newBaud == 9600 || newBaud == 19200 || newBaud == 38400 ||
+ newBaud == 57600 || newBaud == 115200 || newBaud == 230400 || newBaud == 460800 ||
+ newBaud == 921600)
+ {
+ settings.radioPortBaud = newBaud;
+ if (online.gnss == true)
+ {
+ if (newBaud == 0)
+ {
+ // Disable all protocols in/out of UART2
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0);
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART2 settings");
+ }
+ else
+ {
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+
+ // Set the baud rate
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2_BAUDRATE, settings.radioPortBaud);
+
+ // Set the UART2 to only do RTCM (in case this device goes into base mode)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_UBX, settings.enableUART2UBXIn);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0);
+
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART2 settings");
+ }
+ }
+ }
+ else
+ {
+ systemPrintln("Error: Baud rate out of range");
+ }
+ }
+ }
+ else if (incoming == 2)
+ {
+ systemPrint("Enter baud rate (4800 to 921600, 0 = disable) for Data Port: ");
+ int newBaud = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((newBaud != INPUT_RESPONSE_GETNUMBER_EXIT) && (newBaud != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (newBaud == 0 || newBaud == 4800 || newBaud == 9600 || newBaud == 19200 || newBaud == 38400 ||
+ newBaud == 57600 || newBaud == 115200 || newBaud == 230400 || newBaud == 460800 ||
+ newBaud == 921600)
+ {
+ settings.dataPortBaud = newBaud;
+ if (online.gnss == true)
+ {
+ if (newBaud == 0)
+ {
+ // Disable all protocols in/out of UART1
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0);
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART1 settings");
+ }
+ else
+ {
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+
+ // Set the baud rate
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1_BAUDRATE, settings.dataPortBaud);
+
+ // Turn on all protocols except SPARTN
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0);
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART1 settings");
+ }
+ }
+ }
+ else
+ {
+ systemPrintln("Error: Baud rate out of range");
+ }
+ }
+ }
+ else if (incoming == 3)
+ {
+ settings.enableUART2UBXIn ^= 1;
+ systemPrintln("UART2 Protocol In updated. Changes will be applied at next restart");
+ }
+
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (incoming == '2')
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Set the baud rates for the radio and data ports
+void menuPortsMultiplexed()
+{
+ while (1)
{
- Serial.print(F("Enter baud rate (4800 to 921600) for Data Port: "));
- int newBaud = getNumber(menuTimeout); //Timeout after x seconds
- if (newBaud < 4800 || newBaud > 921600)
- {
- Serial.println(F("Error: baud rate out of range"));
- }
- else
- {
- settings.dataPortBaud = newBaud;
- i2cGNSS.setSerialRate(newBaud, COM_PORT_UART1); //Set Data Port
- }
- }
+ systemPrintln();
+ systemPrintln("Menu: Ports");
+
+ systemPrint("1) Set Radio port serial baud rate: ");
+ if (settings.radioPortBaud == 0)
+ {
+ systemPrintln("Disabled");
+ }
+ else
+ {
+ systemPrint(theGNSS.getVal32(UBLOX_CFG_UART2_BAUDRATE));
+ systemPrintln(" bps");
+ }
+
+ systemPrint("2) Set Data port connections: ");
+ if (settings.dataPortChannel == MUX_UBLOX_NMEA)
+ systemPrintln("NMEA TX Out/RX In");
+ else if (settings.dataPortChannel == MUX_PPS_EVENTTRIGGER)
+ systemPrintln("PPS OUT/Event Trigger In");
+ else if (settings.dataPortChannel == MUX_I2C_WT)
+ {
+ if (zedModuleType == PLATFORM_F9P)
+ systemPrintln("I2C SDA/SCL");
+ else if (zedModuleType == PLATFORM_F9R)
+ systemPrintln("Wheel Tick/Direction");
+ }
+ else if (settings.dataPortChannel == MUX_ADC_DAC)
+ systemPrintln("ESP32 DAC Out/ADC In");
+
+ if (settings.dataPortChannel == MUX_UBLOX_NMEA)
+ {
+ systemPrint("3) Set Data port serial baud rate: ");
+ if (settings.dataPortBaud == 0)
+ {
+ systemPrintln("Disabled");
+ }
+ else
+ {
+ systemPrint(theGNSS.getVal32(UBLOX_CFG_UART1_BAUDRATE));
+ systemPrintln(" bps");
+ }
+ }
+ else if (settings.dataPortChannel == MUX_PPS_EVENTTRIGGER)
+ {
+ systemPrintln("3) Configure External Triggers");
+ }
+
+ systemPrint("4) GNSS UART2 UBX Protocol In: ");
+ if (settings.enableUART2UBXIn == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ // The Facet L-Band Direct has ZED tied to NEO over serial. Attaching an external radio
+ // will cause the NEO to compete with the radio. If user wants to use external radio, we
+ // switch off the NEO and send PMP over I2C. By default, Facet L-Band v14 does not
+ // useI2cForLbandCorrections.
+ if (productVariant == RTK_FACET_LBAND_DIRECT)
+ {
+ systemPrint("5) Enable external radio: ");
+ if (settings.useI2cForLbandCorrections == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+ }
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ systemPrint("Enter baud rate (4800 to 921600, 0 = disable) for Radio Port: ");
+ int newBaud = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((newBaud != INPUT_RESPONSE_GETNUMBER_EXIT) && (newBaud != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (newBaud == 0 || newBaud == 4800 || newBaud == 9600 || newBaud == 19200 || newBaud == 38400 ||
+ newBaud == 57600 || newBaud == 115200 || newBaud == 230400 || newBaud == 460800 ||
+ newBaud == 921600)
+ {
+ settings.radioPortBaud = newBaud;
+ if (online.gnss == true)
+ {
+ if (newBaud == 0)
+ {
+ // Disable all protocols in/out of UART2
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 0);
+
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0);
+ response &= theGNSS.sendCfgValset();
- else if (incoming == 'x')
- break;
- else if (incoming == STATUS_GETBYTE_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
+ if (response == false)
+ systemPrintln("Failed to set UART2 settings");
+ }
+ else
+ {
+ bool response = true;
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
+ response &= theGNSS.newCfgValset();
+
+ // Set the baud rate
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2_BAUDRATE, settings.radioPortBaud);
+
+ // Set the UART2 to only do RTCM (in case this device goes into base mode)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_UBX, settings.enableUART2UBXIn);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0);
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART2 settings");
+ }
+ }
+ }
+ else
+ {
+ systemPrintln("Error: Baud rate out of range");
+ }
+ }
+ }
+ else if (incoming == 2)
+ {
+ systemPrintln("\r\nEnter the pin connection to use (1 to 4) for Data Port: ");
+ systemPrintln("1) NMEA TX Out/RX In");
+ systemPrintln("2) PPS OUT/Event Trigger In");
+ if (zedModuleType == PLATFORM_F9P)
+ systemPrintln("3) I2C SDA/SCL");
+ else if (zedModuleType == PLATFORM_F9R)
+ systemPrintln("3) Wheel Tick/Direction");
+ systemPrintln("4) ESP32 DAC Out/ADC In");
+
+ int muxPort = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if (muxPort < 1 || muxPort > 4)
+ {
+ systemPrintln("Error: Pin connection out of range");
+ }
+ else
+ {
+ settings.dataPortChannel = (muxConnectionType_e)(muxPort - 1); // Adjust user input from 1-4 to 0-3
+ setMuxport(settings.dataPortChannel);
+ }
+ }
+ else if (incoming == 3 && settings.dataPortChannel == MUX_UBLOX_NMEA)
+ {
+ systemPrint("Enter baud rate (4800 to 921600, 0 = disable) for Data Port: ");
+ int newBaud = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((newBaud != INPUT_RESPONSE_GETNUMBER_EXIT) && (newBaud != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (newBaud == 0 || newBaud == 4800 || newBaud == 9600 || newBaud == 19200 || newBaud == 38400 ||
+ newBaud == 57600 || newBaud == 115200 || newBaud == 230400 || newBaud == 460800 ||
+ newBaud == 921600)
+ {
+ settings.dataPortBaud = newBaud;
+ if (online.gnss == true)
+ {
+ if (newBaud == 0)
+ {
+ // Disable all protocols in/out of UART1
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0);
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART1 settings");
+ }
+ else
+ {
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+
+ // Set the baud rate
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1_BAUDRATE, settings.dataPortBaud);
+
+ // Turn on all protocols except SPARTN
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0);
+
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART1 settings");
+ }
+ }
+ }
+ else
+ {
+ systemPrintln("Error: Baud rate out of range");
+ }
+ }
+ }
+ else if (incoming == 3 && settings.dataPortChannel == MUX_PPS_EVENTTRIGGER)
+ {
+ menuPortHardwareTriggers();
+ }
+ else if (incoming == 4)
+ {
+ settings.enableUART2UBXIn ^= 1;
+ systemPrintln("UART2 Protocol In updated. Changes will be applied at next restart.");
+ }
+ else if (productVariant == RTK_FACET_LBAND_DIRECT && incoming == 5)
+ {
+ settings.useI2cForLbandCorrectionsConfigured =
+ true; // Record that the user has manually modified the settings.
+ settings.useI2cForLbandCorrections ^= 1;
+ systemPrintln("External radio port updated. Changes will be applied at next restart.");
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
}
-//Set the baud rates for the radio and data ports
-void menuPortsExpress()
+// 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
+void menuPortHardwareTriggers()
{
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Port Menu"));
-
- Serial.print(F("1) Set Radio port serial baud rate: "));
- Serial.print(getSerialRate(COM_PORT_UART2));
- Serial.println(F(" bps"));
-
- Serial.print(F("2) Set Data port connections: "));
- if (settings.dataPortChannel == MUX_UBLOX_NMEA)
- Serial.println(F("NMEA TX Out/RX In"));
- else if (settings.dataPortChannel == MUX_PPS_EVENTTRIGGER)
- Serial.println(F("PPS OUT/Event Trigger In"));
- else if (settings.dataPortChannel == MUX_I2C)
- Serial.println(F("I2C SDA/SCL"));
- else if (settings.dataPortChannel == MUX_ADC_DAC)
- Serial.println(F("ESP32 DAC Out/ADC In"));
-
-
- if (settings.dataPortChannel == MUX_UBLOX_NMEA)
+ bool updateSettings = false;
+ while (1)
{
- Serial.print(F("3) Set Data port serial baud rate: "));
- Serial.print(getSerialRate(COM_PORT_UART1));
- Serial.println(F(" bps"));
- }
+ systemPrintln();
+ systemPrintln("Menu: Port Hardware Trigger");
- Serial.println(F("x) Exit"));
+ systemPrint("1) Enable External Pulse: ");
+ if (settings.enableExternalPulse == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
- byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds
+ if (settings.enableExternalPulse == true)
+ {
+ systemPrint("2) Set time between pulses: ");
+ systemPrint(settings.externalPulseTimeBetweenPulse_us / 1000.0, 0);
+ systemPrintln("ms");
- if (incoming == '1')
- {
- Serial.print(F("Enter baud rate (4800 to 921600) for Radio Port: "));
- int newBaud = getNumber(menuTimeout); //Timeout after x seconds
- if (newBaud < 4800 || newBaud > 921600)
- {
- Serial.println(F("Error: baud rate out of range"));
- }
- else
- {
- settings.radioPortBaud = newBaud;
- i2cGNSS.setSerialRate(newBaud, COM_PORT_UART2); //Set Radio Port
- }
+ systemPrint("3) Set pulse length: ");
+ systemPrint(settings.externalPulseLength_us / 1000.0, 0);
+ systemPrintln("ms");
+
+ systemPrint("4) Set pulse polarity: ");
+ if (settings.externalPulsePolarity == PULSE_RISING_EDGE)
+ systemPrintln("Rising");
+ else
+ systemPrintln("Falling");
+ }
+
+ systemPrint("5) Log External Events: ");
+ if (settings.enableExternalHardwareEventLogging == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ systemPrintln("x) Exit");
+
+ int incoming = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (incoming == 1)
+ {
+ settings.enableExternalPulse ^= 1;
+ updateSettings = true;
+ }
+ else if (incoming == 2 && settings.enableExternalPulse == true)
+ {
+ systemPrint("Time between pulses in milliseconds: ");
+ long pulseTime = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (pulseTime != INPUT_RESPONSE_GETNUMBER_TIMEOUT && pulseTime != INPUT_RESPONSE_GETNUMBER_EXIT)
+ {
+ if (pulseTime < 1 || pulseTime > 60000) // 60s max
+ systemPrintln("Error: Time between pulses out of range");
+ else
+ {
+ settings.externalPulseTimeBetweenPulse_us = pulseTime * 1000;
+
+ if (pulseTime <
+ (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;
+ }
+ }
+ }
+ else if (incoming == 3 && settings.enableExternalPulse == true)
+ {
+ systemPrint("Pulse length in milliseconds: ");
+ long pulseLength = getNumber(); // Returns EXIT, TIMEOUT, or long
+
+ if (pulseLength != INPUT_RESPONSE_GETNUMBER_TIMEOUT && pulseLength != INPUT_RESPONSE_GETNUMBER_EXIT)
+ {
+ if (pulseLength >
+ (settings.externalPulseTimeBetweenPulse_us / 1000)) // pulseLength must be shorter than pulseTime
+ systemPrintln("Error: Pulse length must be shorter than time between pulses");
+ else
+ {
+ settings.externalPulseLength_us = pulseLength * 1000;
+ updateSettings = true;
+ }
+ }
+ }
+ else if (incoming == 4 && settings.enableExternalPulse == true)
+ {
+ if (settings.externalPulsePolarity == PULSE_RISING_EDGE)
+ 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 == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
}
- else if (incoming == '2')
+
+ clearBuffer(); // Empty buffer of any newline chars
+
+ if (updateSettings)
{
- Serial.println(F("\n\rEnter the pin connection to use (1 to 4) for Data Port: "));
- Serial.println(F("1) NMEA TX Out/RX In"));
- Serial.println(F("2) PPS OUT/Event Trigger In"));
- Serial.println(F("3) I2C SDA/SCL"));
- Serial.println(F("4) ESP32 DAC Out/ADC In"));
-
- int muxPort = getNumber(menuTimeout); //Timeout after x seconds
- if (muxPort < 1 || muxPort > 4)
- {
- Serial.println(F("Error: Pin connection out of range"));
- }
- else
- {
- settings.dataPortChannel = (muxConnectionType_e)(muxPort - 1); //Adjust user input from 1-4 to 0-3
- setMuxport(settings.dataPortChannel);
- }
+ settings.updateZEDSettings = true; // Force update
+ beginExternalTriggers(); // Update with new settings
}
- else if (incoming == '3' && settings.dataPortChannel == MUX_UBLOX_NMEA)
+}
+
+void eventTriggerReceived(UBX_TIM_TM2_data_t *ubxDataStruct)
+{
+ // It is the rising edge of the sound event (TRIG) which is important
+ // The falling edge is less useful, as it will be "debounced" by the loop code
+ if (ubxDataStruct->flags.bits.newRisingEdge) // 1 if a new rising edge was detected
{
- Serial.print(F("Enter baud rate (4800 to 921600) for Data Port: "));
- int newBaud = getNumber(menuTimeout); //Timeout after x seconds
- if (newBaud < 4800 || newBaud > 921600)
- {
- Serial.println(F("Error: baud rate out of range"));
- }
- else
- {
- settings.dataPortBaud = newBaud;
- i2cGNSS.setSerialRate(newBaud, COM_PORT_UART1); //Set Data Port
- }
+ systemPrintln("Rising Edge Event");
+
+ triggerCount = ubxDataStruct->count;
+ triggerTowMsR = ubxDataStruct->towMsR; // Time Of Week of rising edge (ms)
+ triggerTowSubMsR =
+ ubxDataStruct->towSubMsR; // Millisecond fraction of Time Of Week of rising edge in nanoseconds
+ triggerAccEst = ubxDataStruct->accEst; // Nanosecond accuracy estimate
+
+ newEventToRecord = true;
}
- else if (incoming == 'x')
- break;
- else if (incoming == STATUS_GETBYTE_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
-
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
}
diff --git a/Firmware/RTK_Surveyor/menuSystem.ino b/Firmware/RTK_Surveyor/menuSystem.ino
new file mode 100644
index 000000000..d914f75ac
--- /dev/null
+++ b/Firmware/RTK_Surveyor/menuSystem.ino
@@ -0,0 +1,1625 @@
+// Display current system status
+void menuSystem()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: System");
+
+ beginI2C();
+ if (online.i2c == false)
+ systemPrintln("I2C: Offline - Something is causing bus problems");
+
+ systemPrint("GNSS: ");
+ if (online.gnss == true)
+ {
+ systemPrint("Online - ");
+
+ printZEDInfo();
+
+ systemPrintf("Module unique chip ID: %s\r\n", zedUniqueId);
+
+ printCurrentConditions();
+ }
+ else
+ systemPrintln("Offline");
+
+ systemPrint("Display: ");
+ if (online.display == true)
+ systemPrintln("Online");
+ else
+ systemPrintln("Offline");
+
+ if (online.accelerometer == true)
+ systemPrintln("Accelerometer: Online");
+
+ systemPrint("Fuel Gauge: ");
+ if (online.battery == true)
+ {
+ systemPrint("Online - ");
+
+ battLevel = lipo.getSOC();
+ battVoltage = lipo.getVoltage();
+
+ systemPrintf("Batt (%d%%) / Voltage: %0.02fV", battLevel, battVoltage);
+ systemPrintln();
+ }
+ else
+ systemPrintln("Offline");
+
+ systemPrint("microSD: ");
+ if (online.microSD == true)
+ systemPrintln("Online");
+ else
+ systemPrintln("Offline");
+
+ if (online.lband == true)
+ {
+ systemPrint("L-Band: Online - ");
+
+ if (online.lbandCorrections == true)
+ systemPrint("Keys Good");
+ else
+ systemPrint("No Keys");
+
+ systemPrint(" / Corrections Received");
+ if (lbandCorrectionsReceived == false)
+ systemPrint(" Failed");
+
+ systemPrintf(" / Eb/N0[dB] (>9 is good): %0.2f", lBandEBNO);
+
+ systemPrint(" - ");
+
+ printNEOInfo();
+ }
+
+ // Display the Bluetooth status
+ bluetoothTest(false);
+
+#ifdef COMPILE_WIFI
+ systemPrint("WiFi MAC Address: ");
+ systemPrintf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", wifiMACAddress[0], wifiMACAddress[1], wifiMACAddress[2],
+ wifiMACAddress[3], wifiMACAddress[4], wifiMACAddress[5]);
+ if (wifiState == WIFI_STATE_CONNECTED)
+ wifiDisplayIpAddress();
+#endif // COMPILE_WIFI
+
+#ifdef COMPILE_ETHERNET
+ if (HAS_ETHERNET)
+ {
+ systemPrint("Ethernet cable: ");
+ if (Ethernet.linkStatus() == LinkON)
+ systemPrintln("connected");
+ else
+ systemPrintln("disconnected");
+ systemPrint("Ethernet MAC Address: ");
+ systemPrintf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", ethernetMACAddress[0], ethernetMACAddress[1],
+ ethernetMACAddress[2], ethernetMACAddress[3], ethernetMACAddress[4], ethernetMACAddress[5]);
+ systemPrint("Ethernet IP Address: ");
+ systemPrintln(Ethernet.localIP());
+ if (!settings.ethernetDHCP)
+ {
+ systemPrint("Ethernet DNS: ");
+ systemPrintf("%s\r\n", settings.ethernetDNS.toString());
+ systemPrint("Ethernet Gateway: ");
+ systemPrintf("%s\r\n", settings.ethernetGateway.toString());
+ systemPrint("Ethernet Subnet Mask: ");
+ systemPrintf("%s\r\n", settings.ethernetSubnet.toString());
+ }
+ }
+#endif // COMPILE_ETHERNET
+
+ // Display the uptime
+ uint64_t uptimeMilliseconds = millis();
+ uint32_t uptimeDays = 0;
+ byte uptimeHours = 0;
+ byte uptimeMinutes = 0;
+ byte uptimeSeconds = 0;
+
+ uptimeDays = uptimeMilliseconds / MILLISECONDS_IN_A_DAY;
+ uptimeMilliseconds %= MILLISECONDS_IN_A_DAY;
+
+ uptimeHours = uptimeMilliseconds / MILLISECONDS_IN_AN_HOUR;
+ uptimeMilliseconds %= MILLISECONDS_IN_AN_HOUR;
+
+ uptimeMinutes = uptimeMilliseconds / MILLISECONDS_IN_A_MINUTE;
+ uptimeMilliseconds %= MILLISECONDS_IN_A_MINUTE;
+
+ uptimeSeconds = uptimeMilliseconds / MILLISECONDS_IN_A_SECOND;
+ uptimeMilliseconds %= MILLISECONDS_IN_A_SECOND;
+
+ systemPrint("System Uptime: ");
+ systemPrintf("%d %02d:%02d:%02d.%03lld (Resets: %d)\r\n", uptimeDays, uptimeHours, uptimeMinutes, uptimeSeconds,
+ uptimeMilliseconds, settings.resetCount);
+
+ // Display NTRIP Client status and uptime
+ ntripClientPrintStatus();
+
+ // Display NTRIP Server status and uptime
+ for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++)
+ ntripServerPrintStatus(serverIndex);
+
+ systemPrintf("Filtered by parser: %d NMEA / %d RTCM / %d UBX\r\n", failedParserMessages_NMEA,
+ failedParserMessages_RTCM, failedParserMessages_UBX);
+
+ // Separate the menu from the status
+ systemPrintln("----- Mode Switch -----");
+
+ // Support mode switching
+ systemPrintln("B) Switch to Base mode");
+ if (HAS_ETHERNET)
+ systemPrintln("N) Switch to NTP Server mode");
+ systemPrintln("R) Switch to Rover mode");
+ systemPrintln("W) Switch to WiFi Config mode");
+
+ systemPrintln("----- Settings -----");
+
+ systemPrint("b) Set Bluetooth Mode: ");
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP)
+ systemPrintln("Classic");
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ systemPrintln("BLE");
+ else
+ systemPrintln("Off");
+
+ if (settings.shutdownNoChargeTimeout_s == 0)
+ systemPrintln("c) Shutdown if not charging: Disabled");
+ else
+ systemPrintf("c) Shutdown if not charging after: %d seconds\r\n", settings.shutdownNoChargeTimeout_s);
+
+ systemPrintln("d) Debug software");
+
+ systemPrint("e) Echo User Input: ");
+ if (settings.echoUserInput == true)
+ systemPrintln("On");
+ else
+ systemPrintln("Off");
+
+ if (settings.enableSD == true && online.microSD == true)
+ {
+ systemPrintln("f) Display microSD Files");
+ }
+
+ systemPrintln("h) Debug hardware");
+
+ systemPrintln("n) Debug network");
+
+ systemPrintln("o) Configure RTK operation");
+
+ systemPrintln("p) Configure periodic print messages");
+
+ systemPrintln("r) Reset all settings to default");
+
+ systemPrintf("z) Set time zone offset: %02d:%02d:%02d\r\n", settings.timeZoneHours, settings.timeZoneMinutes,
+ settings.timeZoneSeconds);
+
+ systemPrint("~) Setup button: ");
+ if (settings.disableSetupButton == true)
+ systemPrintln("Disabled");
+ else
+ systemPrintln("Enabled");
+
+ systemPrintln("S) Shut down");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 'b')
+ {
+ // Restart Bluetooth
+ bluetoothStop();
+ if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP)
+ settings.bluetoothRadioType = BLUETOOTH_RADIO_BLE;
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE)
+ settings.bluetoothRadioType = BLUETOOTH_RADIO_OFF;
+ else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_OFF)
+ settings.bluetoothRadioType = BLUETOOTH_RADIO_SPP;
+ bluetoothStart();
+ }
+ else if (incoming == 'c')
+ {
+ systemPrint("Enter time in seconds to shutdown unit if not charging (0 to disable): ");
+ int shutdownNoChargeTimeout_s = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((shutdownNoChargeTimeout_s != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (shutdownNoChargeTimeout_s != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (shutdownNoChargeTimeout_s < 0 ||
+ shutdownNoChargeTimeout_s > 60 * 60 * 24 * 7) // Arbitrary 7 day limit
+ systemPrintln("Error: Time out of range");
+ else
+ settings.shutdownNoChargeTimeout_s =
+ shutdownNoChargeTimeout_s; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 'd')
+ menuDebugSoftware();
+ else if (incoming == 'e')
+ {
+ settings.echoUserInput ^= 1;
+ }
+ else if ((incoming == 'f') && (settings.enableSD == true) && (online.microSD == true))
+ {
+ printFileList();
+ }
+ else if (incoming == 'h')
+ menuDebugHardware();
+ else if (incoming == 'n')
+ menuDebugNetwork();
+ else if (incoming == 'o')
+ menuOperation();
+ else if (incoming == 'p')
+ menuPeriodicPrint();
+ else if (incoming == 'r')
+ {
+ systemPrintln("\r\nResetting to factory defaults. Press 'y' to confirm:");
+ byte bContinue = getCharacterNumber();
+ if (bContinue == 'y')
+ {
+ factoryReset(false); // We do not have the SD semaphore
+ }
+ else
+ systemPrintln("Reset aborted");
+ }
+ else if (incoming == 'z')
+ {
+ systemPrint("Enter time zone hour offset (-23 <= offset <= 23): ");
+ int value = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((value != INPUT_RESPONSE_GETNUMBER_EXIT) && (value != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (value < -23 || value > 23)
+ systemPrintln("Error: -24 < hours < 24");
+ else
+ {
+ settings.timeZoneHours = value;
+
+ systemPrint("Enter time zone minute offset (-59 <= offset <= 59): ");
+ int value = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((value != INPUT_RESPONSE_GETNUMBER_EXIT) && (value != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (value < -59 || value > 59)
+ systemPrintln("Error: -60 < minutes < 60");
+ else
+ {
+ settings.timeZoneMinutes = value;
+
+ systemPrint("Enter time zone second offset (-59 <= offset <= 59): ");
+ int value = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((value != INPUT_RESPONSE_GETNUMBER_EXIT) && (value != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (value < -59 || value > 59)
+ systemPrintln("Error: -60 < seconds < 60");
+ else
+ {
+ settings.timeZoneSeconds = value;
+ online.rtc = false;
+ syncRTCInterval =
+ 1000; // Reset syncRTCInterval to 1000ms (tpISR could have set it to 59000)
+ rtcSyncd = false;
+ updateRTC();
+ } // Succesful seconds
+ }
+ } // Succesful minute
+ }
+ } // Succesful hours
+ }
+ }
+ else if (incoming == '~')
+ {
+ settings.disableSetupButton ^= 1;
+ }
+
+ // Support mode switching
+ else if (incoming == 'B')
+ {
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(STATE_BASE_NOT_STARTED);
+ }
+ else if ((incoming == 'N') && HAS_ETHERNET)
+ {
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(STATE_NTPSERVER_NOT_STARTED);
+ }
+ else if (incoming == 'R')
+ {
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(STATE_ROVER_NOT_STARTED);
+ }
+ else if (incoming == 'W')
+ {
+ forceSystemStateUpdate = true; // Imediately go to this new state
+ changeState(STATE_WIFI_CONFIG_NOT_STARTED);
+ }
+
+ // Menu exit control
+ else if (incoming == 'S')
+ {
+ systemPrintln("Shutting down...");
+ forceDisplayUpdate = true;
+ powerDown(true);
+ }
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Toggle debug settings for hardware
+void menuDebugHardware()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Debug Hardware");
+
+ // Battery
+ systemPrint("1) Print battery status messages: ");
+ systemPrintf("%s\r\n", settings.enablePrintBatteryMessages ? "Enabled" : "Disabled");
+
+ // Bluetooth
+ systemPrintln("2) Run Bluetooth Test");
+
+ // RTC
+ systemPrint("3) Print RTC resyncs: ");
+ systemPrintf("%s\r\n", settings.enablePrintRtcSync ? "Enabled" : "Disabled");
+
+ // SD card
+ systemPrint("4) Print log file messages: ");
+ systemPrintf("%s\r\n", settings.enablePrintLogFileMessages ? "Enabled" : "Disabled");
+
+ 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");
+
+ // Ublox
+ systemPrint("8) Print messages with bad checksums or CRCs: ");
+ systemPrintf("%s\r\n", settings.enablePrintBadMessages ? "Enabled" : "Disabled");
+
+ systemPrint("9) u-blox I2C Debugging Output: ");
+ systemPrintf("%s\r\n", settings.enableI2Cdebug ? "Enabled" : "Disabled");
+
+ systemPrint("10) L-Band Debugging Output: ");
+ systemPrintf("%s\r\n", settings.debugLBand ? "Enabled" : "Disabled");
+
+ systemPrintln("e) Erase LittleFS");
+
+ systemPrintln("t) Test Screen");
+
+ systemPrintln("r) Force system reset");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ settings.enablePrintBatteryMessages ^= 1;
+ else if (incoming == 2)
+ bluetoothTest(true);
+ else if (incoming == 3)
+ settings.enablePrintRtcSync ^= 1;
+ else if (incoming == 4)
+ settings.enablePrintLogFileMessages ^= 1;
+ else if (incoming == 5)
+ settings.enablePrintLogFileStatus ^= 1;
+ else if (incoming == 6)
+ {
+ settings.runLogTest ^= 1;
+
+ logTestState = LOGTEST_START; // Start test
+
+ // Mark current log file as complete to force test start
+ startCurrentLogTime_minutes = systemTime_minutes - settings.maxLogLength_minutes;
+ }
+ else if (incoming == 7)
+ settings.enablePrintSDBuffers ^= 1;
+ else if (incoming == 8)
+ settings.enablePrintBadMessages ^= 1;
+ else if (incoming == 9)
+ {
+ settings.enableI2Cdebug ^= 1;
+
+ if (settings.enableI2Cdebug)
+ {
+#if defined(REF_STN_GNSS_DEBUG)
+ if (ENABLE_DEVELOPER && productVariant == REFERENCE_STATION)
+ theGNSS.enableDebugging(serialGNSS); // Output all debug messages over serialGNSS
+ else
+#endif // REF_STN_GNSS_DEBUG
+ theGNSS.enableDebugging(Serial, true); // Enable only the critical debug messages over Serial
+ }
+ else
+ theGNSS.disableDebugging();
+ }
+ else if (incoming == 10)
+ {
+ settings.debugLBand ^= 1;
+ }
+
+ else if (incoming == 'e')
+ {
+ systemPrintln("Erasing LittleFS and resetting");
+ LittleFS.format();
+ ESP.restart();
+ }
+ else if (incoming == 't')
+ {
+ requestChangeState(STATE_TEST); // We'll enter test mode once exiting all serial menus
+ }
+
+ // Menu exit control
+ else if (incoming == 'r')
+ {
+ recordSystemSettings();
+
+ 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);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Toggle debug settings for the network
+void menuDebugNetwork()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Debug Network");
+
+ // Ethernet
+ systemPrint("1) Print Ethernet diagnostics: ");
+ systemPrintf("%s\r\n", settings.enablePrintEthernetDiag ? "Enabled" : "Disabled");
+
+ // ESP-Now
+ systemPrint("2) ESP-Now Broadcast Override: ");
+ systemPrintf("%s\r\n", settings.espnowBroadcast ? "Enabled" : "Disabled");
+
+ // WiFi
+ systemPrint("3) Debug WiFi state: ");
+ systemPrintf("%s\r\n", settings.debugWifiState ? "Enabled" : "Disabled");
+
+ // Network
+ systemPrint("10) Debug network layer: ");
+ systemPrintf("%s\r\n", settings.debugNetworkLayer ? "Enabled" : "Disabled");
+
+ systemPrint("11) Print network layer status: ");
+ systemPrintf("%s\r\n", settings.printNetworkStatus ? "Enabled" : "Disabled");
+
+ // NTP
+ systemPrint("20) Debug NTP: ");
+ systemPrintf("%s\r\n", settings.debugNtp ? "Enabled" : "Disabled");
+
+ // NTRIP Client
+ systemPrint("21) Debug NTRIP client state: ");
+ systemPrintf("%s\r\n", settings.debugNtripClientState ? "Enabled" : "Disabled");
+
+ systemPrint("22) Debug NTRIP client --> caster GGA messages: ");
+ systemPrintf("%s\r\n", settings.debugNtripClientRtcm ? "Enabled" : "Disabled");
+
+ // NTRIP Server
+ systemPrint("23) Debug NTRIP server state: ");
+ systemPrintf("%s\r\n", settings.debugNtripServerState ? "Enabled" : "Disabled");
+
+ systemPrint("24) Debug caster --> NTRIP server GNSS messages: ");
+ systemPrintf("%s\r\n", settings.debugNtripServerRtcm ? "Enabled" : "Disabled");
+
+ // PVT Client
+ systemPrint("25) Debug PVT client: ");
+ systemPrintf("%s\r\n", settings.debugPvtClient ? "Enabled" : "Disabled");
+
+ // PVT Server
+ systemPrint("26) Debug PVT server: ");
+ systemPrintf("%s\r\n", settings.debugPvtServer ? "Enabled" : "Disabled");
+
+ // PVT Server
+ systemPrint("27) Debug PVT UDP server: ");
+ systemPrintf("%s\r\n", settings.debugPvtUdpServer ? "Enabled" : "Disabled");
+
+ // WiFi Config
+ systemPrint("28) Debug WiFi Config: ");
+ systemPrintf("%s\r\n", settings.debugWiFiConfig ? "Enabled" : "Disabled");
+
+ systemPrintln("r) Force system reset");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ settings.enablePrintEthernetDiag ^= 1;
+ else if (incoming == 2)
+ settings.espnowBroadcast ^= 1;
+ else if (incoming == 3)
+ settings.debugWifiState ^= 1;
+ else if (incoming == 10)
+ settings.debugNetworkLayer ^= 1;
+ else if (incoming == 11)
+ settings.printNetworkStatus ^= 1;
+ else if (incoming == 20)
+ settings.debugNtp ^= 1;
+ else if (incoming == 21)
+ settings.debugNtripClientState ^= 1;
+ else if (incoming == 22)
+ settings.debugNtripClientRtcm ^= 1;
+ else if (incoming == 23)
+ settings.debugNtripServerState ^= 1;
+ else if (incoming == 24)
+ settings.debugNtripServerRtcm ^= 1;
+ else if (incoming == 25)
+ settings.debugPvtClient ^= 1;
+ else if (incoming == 26)
+ settings.debugPvtServer ^= 1;
+ else if (incoming == 27)
+ settings.debugPvtUdpServer ^= 1;
+ else if (incoming == 28)
+ settings.debugWiFiConfig ^= 1;
+
+ // Menu exit control
+ else if (incoming == 'r')
+ {
+ recordSystemSettings();
+
+ 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);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Toggle debug settings for software
+void menuDebugSoftware()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Debug Software");
+
+ // Heap
+ systemPrint("1) Heap Reporting: ");
+ systemPrintf("%s\r\n", settings.enableHeapReport ? "Enabled" : "Disabled");
+
+ // Ring buffer - ZED Tx
+ systemPrint("10) Print ring buffer offsets: ");
+ systemPrintf("%s\r\n", settings.enablePrintRingBufferOffsets ? "Enabled" : "Disabled");
+
+ systemPrint("11) Print ring buffer overruns: ");
+ systemPrintf("%s\r\n", settings.enablePrintBufferOverrun ? "Enabled" : "Disabled");
+
+ systemPrint("12) RTCM message checking: ");
+ systemPrintf("%s\r\n", settings.enableRtcmMessageChecking ? "Enabled" : "Disabled");
+
+ // Rover
+ systemPrint("20) Print Rover accuracy messages: ");
+ systemPrintf("%s\r\n", settings.enablePrintRoverAccuracy ? "Enabled" : "Disabled");
+
+ // RTK
+ systemPrint("30) Print states: ");
+ systemPrintf("%s\r\n", settings.enablePrintStates ? "Enabled" : "Disabled");
+
+ systemPrint("31) Print duplicate states: ");
+ systemPrintf("%s\r\n", settings.enablePrintDuplicateStates ? "Enabled" : "Disabled");
+
+ systemPrint("32) Reboot RTK after uptime reaches: ");
+ if (settings.rebootSeconds > 4294967)
+ systemPrintln("Disabled");
+ else
+ {
+ int days;
+ int hours;
+ int minutes;
+ int seconds;
+
+ seconds = settings.rebootSeconds;
+ days = seconds / SECONDS_IN_A_DAY;
+ seconds -= days * SECONDS_IN_A_DAY;
+ hours = seconds / SECONDS_IN_AN_HOUR;
+ seconds -= hours * SECONDS_IN_AN_HOUR;
+ minutes = seconds / SECONDS_IN_A_MINUTE;
+ seconds -= minutes * SECONDS_IN_A_MINUTE;
+
+ systemPrintf("%d (%d days %d:%02d:%02d)\r\n", settings.rebootSeconds, days, hours, minutes, seconds);
+ }
+
+ systemPrintf("34) Print partition table\r\n");
+
+ // Tasks
+ systemPrint("50) Task Highwater Reporting: ");
+ if (settings.enableTaskReports == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ // Automatic Firmware Update
+ systemPrintf("60) Print firmware update states: %s\r\n", settings.debugFirmwareUpdate ? "Enabled" : "Disabled");
+
+ // Point Perfect
+ systemPrintf("70) Point Perfect certificate management: %s\r\n",
+ settings.debugPpCertificate ? "Enabled" : "Disabled");
+
+ systemPrintln("e) Erase LittleFS");
+
+ systemPrintln("r) Force system reset");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ settings.enableHeapReport ^= 1;
+ else if (incoming == 10)
+ settings.enablePrintRingBufferOffsets ^= 1;
+ else if (incoming == 11)
+ settings.enablePrintBufferOverrun ^= 1;
+ else if (incoming == 12)
+ settings.enableRtcmMessageChecking ^= 1;
+ else if (incoming == 20)
+ settings.enablePrintRoverAccuracy ^= 1;
+ else if (incoming == 30)
+ settings.enablePrintStates ^= 1;
+ else if (incoming == 31)
+ settings.enablePrintDuplicateStates ^= 1;
+ else if (incoming == 32)
+ {
+ systemPrint("Enter uptime seconds before reboot, Disabled = 0, Reboot range (30 - 4294967): ");
+ int rebootSeconds = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((rebootSeconds != INPUT_RESPONSE_GETNUMBER_EXIT) && (rebootSeconds != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (rebootSeconds < 30 || rebootSeconds > 4294967) // Disable the reboot
+ {
+ settings.rebootSeconds = (uint32_t)-1;
+ systemPrintln("Reset is disabled");
+ }
+ else
+ {
+ int days;
+ int hours;
+ int minutes;
+ int seconds;
+
+ // Set the reboot time
+ settings.rebootSeconds = rebootSeconds;
+
+ seconds = settings.rebootSeconds;
+ days = seconds / SECONDS_IN_A_DAY;
+ seconds -= days * SECONDS_IN_A_DAY;
+ hours = seconds / SECONDS_IN_AN_HOUR;
+ seconds -= hours * SECONDS_IN_AN_HOUR;
+ minutes = seconds / SECONDS_IN_A_MINUTE;
+ seconds -= minutes * SECONDS_IN_A_MINUTE;
+
+ systemPrintf("Reboot after uptime reaches %d days %d:%02d:%02d\r\n", days, hours, minutes, seconds);
+ }
+ }
+ }
+ else if (incoming == 34)
+ printPartitionTable();
+ else if (incoming == 50)
+ settings.enableTaskReports ^= 1;
+ else if (incoming == 60)
+ settings.debugFirmwareUpdate ^= 1;
+ else if (incoming == 70)
+ settings.debugPpCertificate ^= 1;
+ else if (incoming == 'e')
+ {
+ systemPrintln("Erasing LittleFS and resetting");
+ LittleFS.format();
+ ESP.restart();
+ }
+
+ // Menu exit control
+ else if (incoming == 'r')
+ {
+ recordSystemSettings();
+
+ 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);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Configure the RTK operation
+void menuOperation()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: RTK Operation");
+
+ // Display
+ systemPrintf("1) Display Reset Counter: %d - ", settings.resetCount);
+ if (settings.enableResetDisplay == true)
+ systemPrintln("Enabled");
+ else
+ systemPrintln("Disabled");
+
+ // GNSS
+ systemPrint("2) GNSS Serial Timeout: ");
+ systemPrintln(settings.serialTimeoutGNSS);
+
+ systemPrint("3) GNSS Handler Buffer Size: ");
+ systemPrintln(settings.gnssHandlerBufferSize);
+
+ systemPrint("4) GNSS Serial RX Full Threshold: ");
+ systemPrintln(settings.serialGNSSRxFullThreshold);
+
+ // L-Band
+ systemPrint("5) Set L-Band RTK Fix Timeout (seconds): ");
+ if (settings.lbandFixTimeout_seconds > 0)
+ systemPrintln(settings.lbandFixTimeout_seconds);
+ else
+ systemPrintln("Disabled - no resets");
+
+ // SPI
+ systemPrint("6) SPI/SD Interface Frequency: ");
+ systemPrint(settings.spiFrequency);
+ systemPrintln(" MHz");
+
+ // SPP
+ systemPrint("7) SPP RX Buffer Size: ");
+ systemPrintln(settings.sppRxQueueSize);
+
+ systemPrint("8) SPP TX Buffer Size: ");
+ systemPrintln(settings.sppTxQueueSize);
+
+ // UART
+ systemPrint("9) UART Receive Buffer Size: ");
+ systemPrintln(settings.uartReceiveBufferSize);
+
+ // ZED
+ systemPrintln("10) Mirror ZED-F9x's UART1 settings to USB");
+
+ systemPrint("11) Use I2C for L-Band Corrections: ");
+ systemPrintf("%s\r\n", settings.useI2cForLbandCorrections ? "Enabled" : "Disabled");
+
+ systemPrintf("12) RTCM timeout before L-Band override (seconds): %d\r\n",
+ settings.rtcmTimeoutBeforeUsingLBand_s);
+
+ systemPrint("13) CONFIG UBLOX USB port: ");
+ systemPrintf("%s\r\n", settings.enableZedUsb ? "Enabled" : "Disabled");
+
+ systemPrintln("---- Interrupts ----");
+ systemPrint("30) Bluetooth Interrupts Core: ");
+ systemPrintln(settings.bluetoothInterruptsCore);
+
+ systemPrint("31) GNSS UART Interrupts Core: ");
+ systemPrintln(settings.gnssUartInterruptsCore);
+
+ systemPrint("32) I2C Interrupts Core: ");
+ systemPrintln(settings.i2cInterruptsCore);
+
+ // Tasks
+ systemPrintln("------- Tasks ------");
+ systemPrint("50) BT Read Task Core: ");
+ systemPrintln(settings.btReadTaskCore);
+ systemPrint("51) BT Read Task Priority: ");
+ systemPrintln(settings.btReadTaskPriority);
+
+ systemPrint("52) GNSS Data Handler Core: ");
+ systemPrintln(settings.handleGnssDataTaskCore);
+ systemPrint("53) GNSS Data Handler Task Priority: ");
+ systemPrintln(settings.handleGnssDataTaskPriority);
+
+ systemPrint("54) GNSS Read Task Core: ");
+ systemPrintln(settings.gnssReadTaskCore);
+ systemPrint("55) GNSS Read Task Priority: ");
+ systemPrintln(settings.gnssReadTaskPriority);
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ {
+ settings.enableResetDisplay ^= 1;
+ if (settings.enableResetDisplay == true)
+ {
+ settings.resetCount = 0;
+ recordSystemSettings(); // Record to NVM
+ }
+ }
+ else if (incoming == 2)
+ {
+ systemPrint("Enter GNSS Serial Timeout in milliseconds (0 to 1000): ");
+ int serialTimeoutGNSS = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((serialTimeoutGNSS != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (serialTimeoutGNSS != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (serialTimeoutGNSS < 0 || serialTimeoutGNSS > 1000) // Arbitrary 1s limit
+ systemPrintln("Error: Timeout is out of range");
+ else
+ settings.serialTimeoutGNSS = serialTimeoutGNSS; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 3)
+ {
+ systemPrintln("Warning: changing the Handler Buffer Size will restart the RTK. Enter 0 to abort");
+ systemPrint("Enter GNSS Handler Buffer Size in Bytes (32 to 65535): ");
+ int queSize = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((queSize != INPUT_RESPONSE_GETNUMBER_EXIT) && (queSize != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (queSize < 32 || queSize > 65535) // Arbitrary 64k limit
+ systemPrintln("Error: Queue size out of range");
+ else
+ {
+ // Stop the UART2 tssks to prevent the system from crashing
+ tasksStopUART2();
+
+ // Update the buffer size
+ settings.gnssHandlerBufferSize = queSize; // Recorded to NVM and file
+ recordSystemSettings();
+
+ // Reboot the system
+ ESP.restart();
+ }
+ }
+ }
+ else if (incoming == 4)
+ {
+ systemPrint("Enter Serial GNSS RX Full Threshold (1 to 127): ");
+ int serialGNSSRxFullThreshold = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((serialGNSSRxFullThreshold != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (serialGNSSRxFullThreshold != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (serialGNSSRxFullThreshold < 1 || serialGNSSRxFullThreshold > 127)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.serialGNSSRxFullThreshold = serialGNSSRxFullThreshold; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 5)
+ {
+ systemPrint("Enter number of seconds in RTK float before hot-start (0-disable to 3600): ");
+ int timeout = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((timeout != INPUT_RESPONSE_GETNUMBER_EXIT) && (timeout != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (timeout < 0 || timeout > 3600) // Arbitrary 60 minute limit
+ systemPrintln("Error: Timeout out of range");
+ else
+ settings.lbandFixTimeout_seconds = timeout; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 6)
+ {
+ systemPrint("Enter SPI frequency in MHz (1 to 16): ");
+ int freq = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((freq != INPUT_RESPONSE_GETNUMBER_EXIT) && (freq != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (freq < 1 || freq > 16) // Arbitrary 16MHz limit
+ systemPrintln("Error: SPI frequency out of range");
+ else
+ settings.spiFrequency = freq; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 7)
+ {
+ systemPrint("Enter SPP RX Queue Size in Bytes (32 to 16384): ");
+ int queSize = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((queSize != INPUT_RESPONSE_GETNUMBER_EXIT) && (queSize != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (queSize < 32 || queSize > 16384) // Arbitrary 16k limit
+ systemPrintln("Error: Queue size out of range");
+ else
+ settings.sppRxQueueSize = queSize; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 8)
+ {
+ systemPrint("Enter SPP TX Queue Size in Bytes (32 to 16384): ");
+ int queSize = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((queSize != INPUT_RESPONSE_GETNUMBER_EXIT) && (queSize != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (queSize < 32 || queSize > 16384) // Arbitrary 16k limit
+ systemPrintln("Error: Queue size out of range");
+ else
+ settings.sppTxQueueSize = queSize; // Recorded to NVM and file at main menu exit
+ }
+ }
+ else if (incoming == 9)
+ {
+ systemPrintln("Warning: changing the Receive Buffer Size will restart the RTK. Enter 0 to abort");
+ systemPrint("Enter UART Receive Buffer Size in Bytes (32 to 16384): ");
+ int queSize = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((queSize != INPUT_RESPONSE_GETNUMBER_EXIT) && (queSize != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (queSize < 32 || queSize > 16384) // Arbitrary 16k limit
+ systemPrintln("Error: Queue size out of range");
+ else
+ {
+ settings.uartReceiveBufferSize = queSize; // Recorded to NVM and file
+ recordSystemSettings();
+ ESP.restart();
+ }
+ }
+ }
+ else if (incoming == 10)
+ {
+ bool response = setMessagesUSB(MAX_SET_MESSAGES_RETRIES);
+
+ if (response == false)
+ systemPrintln(F("Failed to enable USB messages"));
+ else
+ systemPrintln(F("USB messages successfully enabled"));
+ }
+ else if (incoming == 11)
+ {
+ settings.useI2cForLbandCorrectionsConfigured =
+ true; // Record that the user has manually modified the settings.
+ settings.useI2cForLbandCorrections ^= 1;
+ }
+ else if (incoming == 12)
+ {
+ systemPrint("Enter the number of seconds before L-Band is used once RTCM is absent (1 to 255): ");
+ int rtcmTimeoutBeforeUsingLBand_s = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((rtcmTimeoutBeforeUsingLBand_s != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (rtcmTimeoutBeforeUsingLBand_s != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (rtcmTimeoutBeforeUsingLBand_s < 1 || rtcmTimeoutBeforeUsingLBand_s > 255)
+ systemPrintln("Error: RTCM timeout out of range");
+ else
+ settings.rtcmTimeoutBeforeUsingLBand_s = rtcmTimeoutBeforeUsingLBand_s; // Recorded to NVM and file
+ }
+ }
+ else if (incoming == 13)
+ {
+ settings.enableZedUsb ^= 1;
+
+ bool response = true;
+
+ response &= theGNSS.newCfgValset();
+
+ if (settings.enableZedUsb == true)
+ {
+ // The USB port on the ZED may be used for RTCM to/from the computer (as an NTRIP caster or client)
+ // So let's be sure all protocols are on for the USB port
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_NMEA, 1);
+ if (commandSupported(UBLOX_CFG_USBOUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_RTCM3X, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_UBX, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_NMEA, 1);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_RTCM3X, 1);
+ if (commandSupported(UBLOX_CFG_USBINPROT_SPARTN) == true)
+ {
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/713
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_SPARTN, 1);
+ }
+ }
+ else
+ {
+ // Disable all protocols over USB
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_NMEA, 0);
+ if (commandSupported(UBLOX_CFG_USBOUTPROT_RTCM3X) == true)
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBOUTPROT_RTCM3X, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_UBX, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_NMEA, 0);
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_RTCM3X, 0);
+ if (commandSupported(UBLOX_CFG_USBINPROT_SPARTN) == true)
+ {
+ // See issue: https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/713
+ response &= theGNSS.addCfgValset(UBLOX_CFG_USBINPROT_SPARTN, 0);
+ }
+ }
+ response &= theGNSS.sendCfgValset();
+
+ if (response == false)
+ systemPrintln("Failed to set UART2 settings");
+ }
+
+ else if (incoming == 30)
+ {
+ systemPrint("Not yet implemented! - Enter Core used for Bluetooth Interrupts (0 or 1): ");
+ int bluetoothInterruptsCore = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((bluetoothInterruptsCore != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (bluetoothInterruptsCore != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (bluetoothInterruptsCore < 0 || bluetoothInterruptsCore > 1)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.bluetoothInterruptsCore = bluetoothInterruptsCore; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 31)
+ {
+ systemPrint("Enter Core used for GNSS UART Interrupts (0 or 1): ");
+ int gnssUartInterruptsCore = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((gnssUartInterruptsCore != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (gnssUartInterruptsCore != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (gnssUartInterruptsCore < 0 || gnssUartInterruptsCore > 1)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.gnssUartInterruptsCore = gnssUartInterruptsCore; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 32)
+ {
+ systemPrint("Enter Core used for I2C Interrupts (0 or 1): ");
+ int i2cInterruptsCore = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((i2cInterruptsCore != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (i2cInterruptsCore != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (i2cInterruptsCore < 0 || i2cInterruptsCore > 1)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.i2cInterruptsCore = i2cInterruptsCore; // Recorded to NVM and file
+ }
+ }
+ }
+
+ else if (incoming == 50)
+ {
+ systemPrint("Enter BT Read Task Core (0 or 1): ");
+ int btReadTaskCore = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((btReadTaskCore != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (btReadTaskCore != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (btReadTaskCore < 0 || btReadTaskCore > 1)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.btReadTaskCore = btReadTaskCore; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 51)
+ {
+ systemPrint("Enter BT Read Task Priority (0 to 3): ");
+ int btReadTaskPriority = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((btReadTaskPriority != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (btReadTaskPriority != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (btReadTaskPriority < 0 || btReadTaskPriority > 3)
+ systemPrintln("Error: Task priority out of range");
+ else
+ {
+ settings.btReadTaskPriority = btReadTaskPriority; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 52)
+ {
+ systemPrint("Enter GNSS Data Handler Task Core (0 or 1): ");
+ int handleGnssDataTaskCore = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((handleGnssDataTaskCore != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (handleGnssDataTaskCore != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (handleGnssDataTaskCore < 0 || handleGnssDataTaskCore > 1)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.handleGnssDataTaskCore = handleGnssDataTaskCore; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 53)
+ {
+ systemPrint("Enter GNSS Data Handle Task Priority (0 to 3): ");
+ int handleGnssDataTaskPriority = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((handleGnssDataTaskPriority != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (handleGnssDataTaskPriority != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (handleGnssDataTaskPriority < 0 || handleGnssDataTaskPriority > 3)
+ systemPrintln("Error: Task priority out of range");
+ else
+ {
+ settings.handleGnssDataTaskPriority = handleGnssDataTaskPriority; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 54)
+ {
+ systemPrint("Enter GNSS Read Task Core (0 or 1): ");
+ int gnssReadTaskCore = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((gnssReadTaskCore != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (gnssReadTaskCore != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (gnssReadTaskCore < 0 || gnssReadTaskCore > 1)
+ systemPrintln("Error: Core out of range");
+ else
+ {
+ settings.gnssReadTaskCore = gnssReadTaskCore; // Recorded to NVM and file
+ }
+ }
+ }
+ else if (incoming == 55)
+ {
+ systemPrint("Enter GNSS Read Task Priority (0 to 3): ");
+ int gnssReadTaskPriority = getNumber(); // Returns EXIT, TIMEOUT, or long
+ if ((gnssReadTaskPriority != INPUT_RESPONSE_GETNUMBER_EXIT) &&
+ (gnssReadTaskPriority != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ {
+ if (gnssReadTaskPriority < 0 || gnssReadTaskPriority > 3)
+ systemPrintln("Error: Task priority out of range");
+ else
+ {
+ settings.gnssReadTaskPriority = gnssReadTaskPriority; // Recorded to NVM and file
+ }
+ }
+ }
+
+ // Menu exit control
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Toggle periodic print message enables
+void menuPeriodicPrint()
+{
+ while (1)
+ {
+ systemPrintln();
+ systemPrintln("Menu: Periodic Print Messages");
+
+ systemPrintln("----- Hardware -----");
+ systemPrint("1) Bluetooth RX: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_BLUETOOTH_DATA_RX) ? "Enabled" : "Disabled");
+
+ systemPrint("2) Bluetooth TX: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_BLUETOOTH_DATA_TX) ? "Enabled" : "Disabled");
+
+ systemPrint("3) Ethernet IP address: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_ETHERNET_IP_ADDRESS) ? "Enabled" : "Disabled");
+
+ systemPrint("4) Ethernet state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_ETHERNET_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("5) SD log write data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_SD_LOG_WRITE) ? "Enabled" : "Disabled");
+
+ systemPrint("6) WiFi IP Address: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_WIFI_IP_ADDRESS) ? "Enabled" : "Disabled");
+
+ 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("9) ZED TX data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_ZED_DATA_TX) ? "Enabled" : "Disabled");
+
+ systemPrintln("----- Software -----");
+
+ systemPrintf("20) Periodic print: %d (0x%08x)\r\n", settings.periodicDisplay, settings.periodicDisplay);
+
+ systemPrintf("21) Interval (seconds): %d\r\n", settings.periodicDisplayInterval / 1000);
+
+ systemPrint("22) CPU idle time: ");
+ systemPrintf("%s\r\n", settings.enablePrintIdleTime ? "Enabled" : "Disabled");
+
+ systemPrint("23) Network state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NETWORK_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("24) Ring buffer consumer times: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_RING_BUFFER_MILLIS) ? "Enabled" : "Disabled");
+
+ systemPrint("25) RTK position: ");
+ systemPrintf("%s\r\n", settings.enablePrintPosition ? "Enabled" : "Disabled");
+
+ systemPrint("26) RTK state: ");
+ systemPrintf("%s\r\n", settings.enablePrintState ? "Enabled" : "Disabled");
+
+ systemPrintln("------ Clients -----");
+ systemPrint("40) NTP server data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTP_SERVER_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("41) NTP server state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTP_SERVER_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("42) NTRIP client data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTRIP_CLIENT_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("43) NTRIP client GGA writes: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTRIP_CLIENT_GGA) ? "Enabled" : "Disabled");
+
+ systemPrint("44) NTRIP client state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTRIP_CLIENT_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("45) NTRIP server data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTRIP_SERVER_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("46) NTRIP server state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_NTRIP_SERVER_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("47) PVT client data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_PVT_CLIENT_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("48) PVT client state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_PVT_CLIENT_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("49) PVT server client data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_PVT_SERVER_CLIENT_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("50) PVT server data: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_PVT_SERVER_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("51) PVT server state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_PVT_SERVER_STATE) ? "Enabled" : "Disabled");
+
+ systemPrint("52) OTA client state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_OTA_CLIENT_STATE) ? "Enabled" : "Disabled");
+
+ systemPrintln("------- Tasks ------");
+ systemPrint("70) btReadTask state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_TASK_BLUETOOTH_READ) ? "Enabled" : "Disabled");
+
+ systemPrint("71) ButtonCheckTask state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_TASK_BUTTON_CHECK) ? "Enabled" : "Disabled");
+
+ systemPrint("72) gnssReadTask state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_TASK_GNSS_READ) ? "Enabled" : "Disabled");
+
+ systemPrint("73) handleGnssDataTask state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_TASK_HANDLE_GNSS_DATA) ? "Enabled" : "Disabled");
+
+ systemPrint("74) sdSizeCheckTask state: ");
+ systemPrintf("%s\r\n", PERIODIC_SETTING(PD_TASK_SD_SIZE_CHECK) ? "Enabled" : "Disabled");
+
+ systemPrintln("x) Exit");
+
+ byte incoming = getCharacterNumber();
+
+ if (incoming == 1)
+ PERIODIC_TOGGLE(PD_BLUETOOTH_DATA_RX);
+ else if (incoming == 2)
+ PERIODIC_TOGGLE(PD_BLUETOOTH_DATA_TX);
+ else if (incoming == 3)
+ PERIODIC_TOGGLE(PD_ETHERNET_IP_ADDRESS);
+ else if (incoming == 4)
+ PERIODIC_TOGGLE(PD_ETHERNET_STATE);
+ else if (incoming == 5)
+ PERIODIC_TOGGLE(PD_SD_LOG_WRITE);
+ else if (incoming == 6)
+ PERIODIC_TOGGLE(PD_WIFI_IP_ADDRESS);
+ else if (incoming == 7)
+ PERIODIC_TOGGLE(PD_WIFI_STATE);
+ else if (incoming == 8)
+ PERIODIC_TOGGLE(PD_ZED_DATA_RX);
+ else if (incoming == 9)
+ PERIODIC_TOGGLE(PD_ZED_DATA_TX);
+
+ else if (incoming == 20)
+ {
+ int value = getNumber();
+ if ((value != INPUT_RESPONSE_GETNUMBER_EXIT) && (value != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ settings.periodicDisplay = value;
+ }
+ else if (incoming == 21)
+ {
+ int seconds = getNumber();
+ if ((seconds != INPUT_RESPONSE_GETNUMBER_EXIT) && (seconds != INPUT_RESPONSE_GETNUMBER_TIMEOUT))
+ settings.periodicDisplayInterval = seconds * 1000;
+ }
+ else if (incoming == 22)
+ settings.enablePrintIdleTime ^= 1;
+ else if (incoming == 23)
+ PERIODIC_TOGGLE(PD_NETWORK_STATE);
+ else if (incoming == 24)
+ PERIODIC_TOGGLE(PD_RING_BUFFER_MILLIS);
+ else if (incoming == 25)
+ settings.enablePrintPosition ^= 1;
+ else if (incoming == 26)
+ settings.enablePrintState ^= 1;
+
+ else if (incoming == 40)
+ PERIODIC_TOGGLE(PD_NTP_SERVER_DATA);
+ else if (incoming == 41)
+ PERIODIC_TOGGLE(PD_NTP_SERVER_STATE);
+ else if (incoming == 42)
+ PERIODIC_TOGGLE(PD_NTRIP_CLIENT_DATA);
+ else if (incoming == 43)
+ PERIODIC_TOGGLE(PD_NTRIP_CLIENT_GGA);
+ else if (incoming == 44)
+ PERIODIC_TOGGLE(PD_NTRIP_CLIENT_STATE);
+ else if (incoming == 45)
+ PERIODIC_TOGGLE(PD_NTRIP_SERVER_DATA);
+ else if (incoming == 46)
+ PERIODIC_TOGGLE(PD_NTRIP_SERVER_STATE);
+ else if (incoming == 47)
+ PERIODIC_TOGGLE(PD_PVT_CLIENT_DATA);
+ else if (incoming == 48)
+ PERIODIC_TOGGLE(PD_PVT_CLIENT_STATE);
+ else if (incoming == 49)
+ PERIODIC_TOGGLE(PD_PVT_SERVER_CLIENT_DATA);
+ else if (incoming == 50)
+ PERIODIC_TOGGLE(PD_PVT_SERVER_DATA);
+ else if (incoming == 51)
+ PERIODIC_TOGGLE(PD_PVT_SERVER_STATE);
+ else if (incoming == 52)
+ PERIODIC_TOGGLE(PD_OTA_CLIENT_STATE);
+
+ else if (incoming == 70)
+ PERIODIC_TOGGLE(PD_TASK_BLUETOOTH_READ);
+ else if (incoming == 71)
+ PERIODIC_TOGGLE(PD_TASK_BUTTON_CHECK);
+ else if (incoming == 72)
+ PERIODIC_TOGGLE(PD_TASK_GNSS_READ);
+ else if (incoming == 73)
+ PERIODIC_TOGGLE(PD_TASK_HANDLE_GNSS_DATA);
+ else if (incoming == 74)
+ PERIODIC_TOGGLE(PD_TASK_SD_SIZE_CHECK);
+
+ // Menu exit control
+ else if (incoming == 'x')
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY)
+ break;
+ else if (incoming == INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT)
+ break;
+ else
+ printUnknown(incoming);
+ }
+
+ clearBuffer(); // Empty buffer of any newline chars
+}
+
+// Print the current long/lat/alt/HPA/SIV
+// From Example11_GetHighPrecisionPositionUsingDouble
+void printCurrentConditions()
+{
+ if (online.gnss == true)
+ {
+ systemPrint("SIV: ");
+ systemPrint(numSV);
+
+ systemPrint(", HPA (m): ");
+ systemPrint(horizontalAccuracy, 3);
+
+ systemPrint(", Lat: ");
+ systemPrint(latitude, haeNumberOfDecimals);
+ systemPrint(", Lon: ");
+ systemPrint(longitude, haeNumberOfDecimals);
+
+ systemPrint(", Altitude (m): ");
+ systemPrint(altitude, 1);
+
+ systemPrintln();
+ }
+}
+
+void printCurrentRTKState()
+{
+ if (online.gnss == true)
+ {
+ systemPrint("RTK solution: ");
+
+ if (carrSoln == 0) // No RTK
+ systemPrint("NONE");
+
+ else if (carrSoln == 1) // RTK Float
+ systemPrint("FLOAT");
+
+ else if (carrSoln == 2) // RTK Fix
+ systemPrint("FIX");
+
+ else
+ systemPrint("UNKNOWN!");
+
+ systemPrintln();
+ }
+}
+
+void printCurrentConditionsNMEA()
+{
+ if (online.gnss == true)
+ {
+ char systemStatus[100];
+ snprintf(systemStatus, sizeof(systemStatus),
+ "%02d%02d%02d.%02d,%02d%02d%02d,%0.3f,%d,%0.9f,%0.9f,%0.2f,%d,%d,%d", gnssHour, gnssMinute, gnssSecond,
+ mseconds, gnssDay, gnssMonth, gnssYear % 2000, // Limit to 2 digits
+ horizontalAccuracy, numSV, latitude, longitude, altitude, fixType, carrSoln, battLevel);
+
+ char nmeaMessage[100]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_STATUS, nmeaMessage, sizeof(nmeaMessage),
+ systemStatus); // textID, buffer, sizeOfBuffer, text
+ systemPrintln(nmeaMessage);
+ }
+ else
+ {
+ char nmeaMessage[100]; // Max NMEA sentence length is 82
+ createNMEASentence(CUSTOM_NMEA_TYPE_STATUS, nmeaMessage, sizeof(nmeaMessage),
+ (char *)"OFFLINE"); // textID, buffer, sizeOfBuffer, text
+ systemPrintln(nmeaMessage);
+ }
+}
+
+// When called, prints the contents of root folder list of files on SD card
+// This allows us to replace the sd.ls() function to point at Serial and BT outputs
+void printFileList()
+{
+ bool sdCardAlreadyMounted = online.microSD;
+ if (!online.microSD)
+ beginSD();
+
+ // Notify the user if the microSD card is not available
+ if (!online.microSD)
+ systemPrintln("microSD card not online!");
+ else
+ {
+ // Attempt to gain access to the SD card
+ if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS)
+ {
+ markSemaphore(FUNCTION_PRINT_FILE_LIST);
+
+ if (USE_SPI_MICROSD)
+ {
+ SdFile dir;
+ dir.open("/"); // Open root
+ uint16_t fileCount = 0;
+
+ SdFile tempFile;
+
+ systemPrintln("Files found:");
+
+ while (tempFile.openNext(&dir, O_READ))
+ {
+ if (tempFile.isFile())
+ {
+ fileCount++;
+
+ // 2017-05-19 187362648 800_0291.MOV
+
+ // Get File Date from sdFat
+ uint16_t fileDate;
+ uint16_t fileTime;
+ tempFile.getCreateDateTime(&fileDate, &fileTime);
+
+ // Convert sdFat file date fromat into YYYY-MM-DD
+ char fileDateChar[20];
+ snprintf(fileDateChar, sizeof(fileDateChar), "%d-%02d-%02d",
+ ((fileDate >> 9) + 1980), // Year
+ ((fileDate >> 5) & 0b1111), // Month
+ (fileDate & 0b11111) // Day
+ );
+
+ char fileSizeChar[20];
+ String fileSize;
+ stringHumanReadableSize(fileSize, tempFile.fileSize());
+ fileSize.toCharArray(fileSizeChar, sizeof(fileSizeChar));
+
+ char fileName[50]; // Handle long file names
+ tempFile.getName(fileName, sizeof(fileName));
+
+ char fileRecord[100];
+ snprintf(fileRecord, sizeof(fileRecord), "%s\t%s\t%s", fileDateChar, fileSizeChar, fileName);
+
+ systemPrintln(fileRecord);
+ }
+ }
+
+ dir.close();
+ tempFile.close();
+
+ if (fileCount == 0)
+ systemPrintln("No files found");
+ }
+#ifdef COMPILE_SD_MMC
+ else
+ {
+ File dir = SD_MMC.open("/"); // Open root
+ uint16_t fileCount = 0;
+
+ if (dir && dir.isDirectory())
+ {
+ systemPrintln("Files found:");
+
+ File tempFile = dir.openNextFile();
+ while (tempFile)
+ {
+ if (!tempFile.isDirectory())
+ {
+ fileCount++;
+
+ // 2017-05-19 187362648 800_0291.MOV
+
+ // Get time of last write
+ time_t lastWrite = tempFile.getLastWrite();
+
+ struct tm *timeinfo = localtime(&lastWrite);
+
+ char fileDateChar[20];
+ snprintf(fileDateChar, sizeof(fileDateChar), "%.0f-%02.0f-%02.0f",
+ (float)timeinfo->tm_year + 1900, // Year - ESP32 2.0.2 starts the year at 1900...
+ (float)timeinfo->tm_mon + 1, // Month
+ (float)timeinfo->tm_mday // Day
+ );
+
+ char fileSizeChar[20];
+ String fileSize;
+ stringHumanReadableSize(fileSize, tempFile.size());
+ fileSize.toCharArray(fileSizeChar, sizeof(fileSizeChar));
+
+ char fileName[50]; // Handle long file names
+ snprintf(fileName, sizeof(fileName), "%s", tempFile.name());
+
+ char fileRecord[100];
+ snprintf(fileRecord, sizeof(fileRecord), "%s\t%s\t%s", fileDateChar, fileSizeChar,
+ fileName);
+
+ systemPrintln(fileRecord);
+ }
+
+ tempFile.close();
+ tempFile = dir.openNextFile();
+ }
+ }
+
+ dir.close();
+
+ if (fileCount == 0)
+ systemPrintln("No files found");
+ }
+#endif // COMPILE_SD_MMC
+ }
+ else
+ {
+ char semaphoreHolder[50];
+ getSemaphoreFunction(semaphoreHolder);
+
+ // This is an error because the current settings no longer match the settings
+ // on the microSD card, and will not be restored to the expected settings!
+ systemPrintf("sdCardSemaphore failed to yield, held by %s, menuSystem.ino line %d\r\n", semaphoreHolder,
+ __LINE__);
+ }
+
+ // Release the SD card if not originally mounted
+ if (sdCardAlreadyMounted)
+ xSemaphoreGive(sdCardSemaphore);
+ else
+ endSD(true, true);
+ }
+}
diff --git a/Firmware/RTK_Surveyor/menuTest.ino b/Firmware/RTK_Surveyor/menuTest.ino
deleted file mode 100644
index a8844980b..000000000
--- a/Firmware/RTK_Surveyor/menuTest.ino
+++ /dev/null
@@ -1,119 +0,0 @@
-//Production testing
-//Allow operator to output NMEA on radio port for connector testing
-//Scan for display
-void menuTest()
-{
- inTestMode = true; //Reroutes bluetooth bytes
-
- //Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted
- //even if there is no GPS fix. We use it to test serial output.
- i2cGNSS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_UART2, 1); //Enable message every second
-
- while (1)
- {
- Serial.println();
- Serial.println(F("Menu: Test Menu"));
-
- Serial.print(F("Bluetooth broadcasting as: "));
- Serial.println(deviceName);
-
- Serial.println(F("Radio Port is now outputting RTCM"));
-
- if (settings.enableSD && online.microSD)
- {
- Serial.print(F("microSD card detected:"));
- if (createTestFile() == false)
- {
- Serial.print(F(" Failed to create test file. Format SD card with 'SD Card Formatter'."));
- }
- Serial.println();
- }
-
- //0x3D is default on Qwiic board
- if (isConnected(0x3D) == true || isConnected(0x3C) == true)
- Serial.println(F("Qwiic Good. Display detected."));
- else
- Serial.println(F("Qwiic port failed! No display detected."));
-
- Serial.println(F("Any character received over Blueooth connection will be displayed here"));
-
- Serial.println(F("1) Display microSD contents"));
- Serial.println(F("2) Turn on all messages on USB port"));
- Serial.println(F("3) Reset USB Messages to Defaults (NMEAx6)"));
- Serial.println(F("4) Duplicate UART messages to USB"));
-
- Serial.println(F("x) Exit"));
-
- int incoming = getNumber(menuTimeout); //Timeout after x seconds
-
- if (incoming == 1)
- {
- if (settings.enableSD && online.microSD)
- {
- //Attempt to access file system. This avoids collisions with file writing from other functions like recordSystemSettingsToFile() and F9PSerialReadTask()
- if (xSemaphoreTake(xFATSemaphore, fatSemaphore_longWait_ms) == pdPASS)
- {
- Serial.println(F("Files found (date time size name):\n\r"));
- sd.ls(LS_R | LS_DATE | LS_SIZE);
-
- xSemaphoreGive(xFATSemaphore);
- }
- }
- }
- else if (incoming == 2)
- {
-// ubxMsgs usbMessage; //Create temp struct
-// setGNSSMessageRates(usbMessage, 1); //Turn on all messages to report once per fix
-//
-// //Now send that struct
-// bool response = configureGNSSMessageRates(COM_PORT_USB, usbMessage); //Make sure the appropriate messages are enabled
-// if (response == false)
-// Serial.println(F("menuTest: Failed to enable USB messages"));
-// else
-// Serial.println(F("All messages enabled"));
- }
- else if (incoming == 3)
- {
-// ubxMsgs usbMessage; //Create temp struct
-// setGNSSMessageRates(usbMessage, 0); //Turn off all messages to report
-//
-// //Turn on default 6
-// usbMessage.nmea_gga.msgRate = 1;
-// usbMessage.nmea_gsa.msgRate = 1;
-// usbMessage.nmea_gst.msgRate = 1;
-// usbMessage.nmea_gsv.msgRate = 1;
-// usbMessage.nmea_rmc.msgRate = 1;
-// usbMessage.nmea_vtg.msgRate = 1;
-//
-// //Now send that struct
-// bool response = configureGNSSMessageRates(COM_PORT_USB, usbMessage); //Make sure the appropriate messages are enabled
-// if (response == false)
-// Serial.println(F("menuTest: Failed to enable USB messages"));
-// else
-// Serial.println(F("All messages enabled"));
- }
- else if (incoming == 4)
- {
- //Send the current settings to USB
- bool response = configureGNSSMessageRates(COM_PORT_USB, ubxMessages); //Make sure the appropriate messages are enabled
- if (response == false)
- Serial.println(F("menuTest: Failed to enable USB messages"));
- else
- Serial.println(F("USB now matches UART messages"));
- }
-
- else if (incoming == STATUS_PRESSED_X)
- break;
- else if (incoming == STATUS_GETNUMBER_TIMEOUT)
- break;
- else
- printUnknown(incoming);
- }
-
- inTestMode = false; //Reroutes bluetooth bytes
-
- //Disable RTCM sentences
- i2cGNSS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_UART2, 0);
-
- while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
-}
diff --git a/Firmware/RTK_Surveyor/settings.h b/Firmware/RTK_Surveyor/settings.h
index f434e7410..2a792e2e8 100644
--- a/Firmware/RTK_Surveyor/settings.h
+++ b/Firmware/RTK_Surveyor/settings.h
@@ -1,325 +1,1274 @@
-//System can enter a variety of states starting at Rover_No_Fix at power on
+#ifndef __SETTINGS_H__
+#define __SETTINGS_H__
+
+// System can enter a variety of states
+// See statemachine diagram at:
+// https://lucid.app/lucidchart/53519501-9fa5-4352-aa40-673f88ca0c9b/edit?invitationId=inv_ebd4b988-513d-4169-93fd-c291851108f8
typedef enum
{
- STATE_ROVER_NOT_STARTED = 0,
- STATE_ROVER_NO_FIX,
- STATE_ROVER_FIX,
- STATE_ROVER_RTK_FLOAT,
- STATE_ROVER_RTK_FIX,
- STATE_BASE_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_TEMP_WIFI_STARTED,
- STATE_BASE_TEMP_WIFI_CONNECTED,
- STATE_BASE_TEMP_CASTER_STARTED,
- STATE_BASE_TEMP_CASTER_CONNECTED,
- STATE_BASE_FIXED_NOT_STARTED,
- STATE_BASE_FIXED_TRANSMITTING,
- STATE_BASE_FIXED_WIFI_STARTED,
- STATE_BASE_FIXED_WIFI_CONNECTED,
- STATE_BASE_FIXED_CASTER_STARTED,
- STATE_BASE_FIXED_CASTER_CONNECTED,
+ STATE_ROVER_NOT_STARTED = 0,
+ STATE_ROVER_NO_FIX,
+ STATE_ROVER_FIX,
+ STATE_ROVER_RTK_FLOAT,
+ STATE_ROVER_RTK_FIX,
+ 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_BUBBLE_LEVEL,
+ STATE_MARK_EVENT,
+ STATE_DISPLAY_SETUP,
+ STATE_WIFI_CONFIG_NOT_STARTED,
+ STATE_WIFI_CONFIG,
+ STATE_TEST,
+ STATE_TESTING,
+ STATE_PROFILE,
+ STATE_KEYS_STARTED,
+ STATE_KEYS_NEEDED,
+ STATE_KEYS_WIFI_STARTED,
+ STATE_KEYS_WIFI_CONNECTED,
+ STATE_KEYS_WIFI_TIMEOUT,
+ STATE_KEYS_EXPIRED,
+ STATE_KEYS_DAYS_REMAINING,
+ STATE_KEYS_LBAND_CONFIGURE,
+ STATE_KEYS_LBAND_ENCRYPTED,
+ STATE_KEYS_PROVISION_WIFI_STARTED,
+ STATE_KEYS_PROVISION_WIFI_CONNECTED,
+ STATE_ESPNOW_PAIRING_NOT_STARTED,
+ STATE_ESPNOW_PAIRING,
+ 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;
-volatile SystemState systemState = STATE_ROVER_NOT_STARTED;
+volatile SystemState systemState = STATE_NOT_SET;
+SystemState lastSystemState = STATE_NOT_SET;
+SystemState requestedSystemState = STATE_NOT_SET;
+bool newSystemStateRequested = false;
+
+// The setup display can show a limited set of states
+// When user pauses for X amount of time, system will enter that state
+SystemState setupState = STATE_MARK_EVENT;
+
+// Base modes set with RTK_MODE
+#define RTK_MODE_BASE_FIXED 0x0001
+#define RTK_MODE_BASE_SURVEY_IN 0x0002
+#define RTK_MODE_BUBBLE_LEVEL 0x0004
+#define RTK_MODE_ETHERNET_CONFIG 0x0008
+#define RTK_MODE_NTP 0x0010
+#define RTK_MODE_ROVER 0x0020
+#define RTK_MODE_TESTING 0x0040
+#define RTK_MODE_WIFI_CONFIG 0x0080
+
+typedef uint8_t RtkMode_t;
+
+#define RTK_MODE(mode) rtkMode = mode;
+
+#define EQ_RTK_MODE(mode) (rtkMode && (rtkMode == (mode & rtkMode)))
+#define NEQ_RTK_MODE(mode) (rtkMode && (rtkMode != (mode & rtkMode)))
typedef enum
{
- RTK_SURVEYOR = 0,
- RTK_EXPRESS,
- RTK_FACET,
+ RTK_SURVEYOR = 0,
+ RTK_EXPRESS,
+ RTK_FACET,
+ RTK_EXPRESS_PLUS,
+ RTK_FACET_LBAND,
+ REFERENCE_STATION,
+ RTK_FACET_LBAND_DIRECT,
+ // Add new values just above this line
+ RTK_UNKNOWN,
} ProductVariant;
ProductVariant productVariant = RTK_SURVEYOR;
+const char *const productDisplayNames[] = {
+ "Surveyor",
+ "Express",
+ "Facet",
+ "Express+",
+ "Facet LB",
+ "Ref Stn",
+ "Facet LD",
+ // Add new values just above this line
+ "Unknown",
+};
+const int productDisplayNamesEntries = sizeof(productDisplayNames) / sizeof(productDisplayNames[0]);
+
+const char *const platformFilePrefixTable[] = {
+ "SFE_Surveyor",
+ "SFE_Express",
+ "SFE_Facet",
+ "SFE_Express_Plus",
+ "SFE_Facet_LBand",
+ "SFE_Reference_Station",
+ "SFE_Facet_LBand_Direct",
+ // Add new values just above this line
+ "SFE_Unknown",
+};
+const int platformFilePrefixTableEntries = sizeof(platformFilePrefixTable) / sizeof(platformFilePrefixTable[0]);
+
+const char *const platformPrefixTable[] = {
+ "Surveyor",
+ "Express",
+ "Facet",
+ "Express Plus",
+ "Facet L-Band",
+ "Reference Station",
+ "Facet L-Band Direct",
+ // Add new values just above this line
+ "Unknown",
+};
+const int platformPrefixTableEntries = sizeof(platformPrefixTable) / sizeof(platformPrefixTable[0]);
+
+// Macros to show if the GNSS is I2C or SPI
+#define USE_SPI_GNSS (productVariant == REFERENCE_STATION)
+#define USE_I2C_GNSS (!USE_SPI_GNSS)
+
+// Macros to show if the microSD is SPI or SDIO
+#define USE_MMC_MICROSD (productVariant == REFERENCE_STATION)
+#define USE_SPI_MICROSD (!USE_MMC_MICROSD)
+
+// Macro to show if the the RTK variant has Ethernet
+#ifdef COMPILE_ETHERNET
+#define HAS_ETHERNET (productVariant == REFERENCE_STATION)
+#else // COMPILE_ETHERNET
+#define HAS_ETHERNET false
+#endif // COMPILE_ETHERNET
+
+// Macro to show if the the RTK variant has a GNSS TP interrupt - for accurate clock setting
+// The GNSS UBX PVT message is sent ahead of the top-of-second
+// The rising edge of the TP signal indicates the true top-of-second
+#define HAS_GNSS_TP_INT (productVariant == REFERENCE_STATION)
+
+// Macro to show if the the RTK variant has no battery
+#define HAS_NO_BATTERY (productVariant == REFERENCE_STATION)
+#define HAS_BATTERY (!HAS_NO_BATTERY)
+
+// Macro to show if the the RTK variant has antenna short circuit / open circuit detection
+#define HAS_ANTENNA_SHORT_OPEN (productVariant == REFERENCE_STATION)
+
typedef enum
{
- BUTTON_ROVER = 0,
- BUTTON_BASE,
- BUTTON_PRESSED,
- BUTTON_RELEASED,
+ BUTTON_ROVER = 0,
+ BUTTON_BASE,
} ButtonState;
ButtonState buttonPreviousState = BUTTON_ROVER;
-ButtonState setupButtonState = BUTTON_RELEASED; //RTK Express Setup Button
-//Data port mux (RTK Express) can enter one of four different connections
-typedef enum muxConnectionType_e
+// Data port mux (RTK Express) can enter one of four different connections
+typedef enum
{
- MUX_UBLOX_NMEA = 0,
- MUX_PPS_EVENTTRIGGER,
- MUX_I2C,
- MUX_ADC_DAC,
+ MUX_UBLOX_NMEA = 0,
+ MUX_PPS_EVENTTRIGGER,
+ MUX_I2C_WT,
+ MUX_ADC_DAC,
} muxConnectionType_e;
-//User can enter fixed base coordinates in ECEF or degrees
+// User can enter fixed base coordinates in ECEF or degrees
typedef enum
{
- COORD_TYPE_ECEF = 0,
- COORD_TYPE_GEOGRAPHIC,
+ COORD_TYPE_ECEF = 0,
+ COORD_TYPE_GEODETIC,
} coordinateType_e;
-//Freeze and blink LEDs if we hit a bad error
+// User can select output pulse as either falling or rising edge
+typedef enum
+{
+ PULSE_FALLING_EDGE = 0,
+ PULSE_RISING_EDGE,
+} pulseEdgeType_e;
+
+// Custom NMEA sentence types output to the log file
+typedef enum
+{
+ CUSTOM_NMEA_TYPE_RESET_REASON = 0,
+ CUSTOM_NMEA_TYPE_WAYPOINT,
+ CUSTOM_NMEA_TYPE_EVENT,
+ 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,
+ CUSTOM_NMEA_TYPE_ARP_ECEF_XYZH,
+ CUSTOM_NMEA_TYPE_ZED_UNIQUE_ID,
+} customNmeaType_e;
+
+// Freeze and blink LEDs if we hit a bad error
typedef enum
{
- ERROR_NO_I2C = 2, //Avoid 0 and 1 as these are bad blink codes
- ERROR_GPS_CONFIG_FAIL,
+ ERROR_NO_I2C = 2, // Avoid 0 and 1 as these are bad blink codes
+ ERROR_GPS_CONFIG_FAIL,
} t_errorNumber;
-//Radio status LED goes from off (LED off), no connection (blinking), to connected (solid)
-enum RadioState
+// Define the types of network
+enum NetworkTypes
{
- RADIO_OFF = 0,
- BT_ON_NOCONNECTION, //WiFi is off
- BT_CONNECTED,
- WIFI_ON_NOCONNECTION, //BT is off
- WIFI_CONNECTED,
+ NETWORK_TYPE_WIFI = 0,
+ NETWORK_TYPE_ETHERNET,
+ // Last hardware network type
+ NETWORK_TYPE_MAX,
+
+ // Special cases
+ NETWORK_TYPE_USE_DEFAULT = NETWORK_TYPE_MAX,
+ NETWORK_TYPE_ACTIVE,
+ // Last network type
+ NETWORK_TYPE_LAST,
};
-volatile byte radioState = RADIO_OFF;
-//Return values for getByteChoice()
-enum returnStatus {
- STATUS_GETBYTE_TIMEOUT = 255,
- STATUS_GETNUMBER_TIMEOUT = -123455555,
- STATUS_PRESSED_X = 254,
+// Define the states of the network device
+enum NetworkStates
+{
+ NETWORK_STATE_OFF = 0,
+ NETWORK_STATE_DELAY,
+ NETWORK_STATE_CONNECTING,
+ NETWORK_STATE_IN_USE,
+ NETWORK_STATE_WAIT_NO_USERS,
+ // Last network state
+ NETWORK_STATE_MAX
};
-#include //http://librarymanager/All#SparkFun_u-blox_GNSS
+// Define the network users
+enum NetworkUsers
+{
+ NETWORK_USER_NTP_SERVER = 0, // NTP server
+ NETWORK_USER_NTRIP_CLIENT, // NTRIP client
+ NETWORK_USER_OTA_FIRMWARE_UPDATE, // Over-The-Air firmware updates
+ NETWORK_USER_PVT_CLIENT, // PVT client
+ NETWORK_USER_PVT_SERVER, // PVT server
+ NETWORK_USER_PVT_UDP_SERVER, // PVT UDP server
-//Each constellation will have its config key, enable, and a visible name
-typedef struct ubxConstellation
+ // Add new users above this line
+ NETWORK_USER_NTRIP_SERVER, // NTRIP server
+ // Last network user
+ NETWORK_USER_MAX = NETWORK_USER_NTRIP_SERVER + NTRIP_SERVER_MAX
+};
+
+typedef uint16_t NETWORK_USER;
+
+typedef struct _NETWORK_DATA
+{
+ uint8_t requestedNetwork; // Type of network requested
+ uint8_t type; // Type of network
+ NETWORK_USER activeUsers; // Active users of this network device
+ NETWORK_USER userOpens; // Users requesting access to this network
+ uint8_t connectionAttempt; // Number of previous connection attempts
+ bool restart; // Set if restart is allowed
+ bool shutdown; // Network is shutting down
+ uint8_t state; // Current state of the network
+ uint32_t timeout; // Timer timeout value
+ uint32_t timerStart; // Starting millis for the timer
+} NETWORK_DATA;
+
+// Even though WiFi and ESP-Now operate on the same radio, we treat
+// then as different states so that we can leave the radio on if
+// either WiFi or ESP-Now are active
+enum WiFiState
+{
+ WIFI_STATE_OFF = 0,
+ WIFI_STATE_START,
+ WIFI_STATE_CONNECTING,
+ WIFI_STATE_CONNECTED,
+};
+volatile byte wifiState = WIFI_STATE_OFF;
+
+#include "NetworkClient.h" // Built-in - Supports both WiFiClient and EthernetClient
+#include "NetworkUDP.h" //Built-in - Supports both WiFiUdp and EthernetUdp
+
+// NTRIP Server data
+typedef struct _NTRIP_SERVER_DATA
+{
+ // Network connection used to push RTCM to NTRIP caster
+ NetworkClient *networkClient;
+ volatile uint8_t state;
+
+ // Count of bytes sent by the NTRIP server to the NTRIP caster
+ uint32_t bytesSent;
+
+ // 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
+ uint32_t connectionAttemptTimeout;
+ uint32_t lastConnectionAttempt;
+ int connectionAttempts; // Count the number of connection attempts between restarts
+
+ // NTRIP server timer usage:
+ // * Reconnection delay
+ // * Measure the connection response time
+ // * Receive RTCM correction data timeout
+ // * Monitor last RTCM byte received for frame counting
+ uint32_t timer;
+ uint32_t startTime;
+ int connectionAttemptsTotal; // Count the number of connection attempts absolutely
+
+ // Additional count / times for ntripServerProcessRTCM
+ uint32_t zedBytesSent ;
+ uint32_t previousMilliseconds;
+} NTRIP_SERVER_DATA;
+
+typedef enum
+{
+ ESPNOW_OFF,
+ ESPNOW_ON,
+ ESPNOW_PAIRING,
+ ESPNOW_MAC_RECEIVED,
+ ESPNOW_PAIRED,
+} ESPNOWState;
+volatile ESPNOWState espnowState = ESPNOW_OFF;
+
+typedef enum
+{
+ RTCM_TRANSPORT_STATE_WAIT_FOR_PREAMBLE_D3 = 0,
+ RTCM_TRANSPORT_STATE_READ_LENGTH_1,
+ RTCM_TRANSPORT_STATE_READ_LENGTH_2,
+ RTCM_TRANSPORT_STATE_READ_MESSAGE_1,
+ RTCM_TRANSPORT_STATE_READ_MESSAGE_2,
+ RTCM_TRANSPORT_STATE_READ_DATA,
+ RTCM_TRANSPORT_STATE_READ_CRC_1,
+ RTCM_TRANSPORT_STATE_READ_CRC_2,
+ RTCM_TRANSPORT_STATE_READ_CRC_3,
+ RTCM_TRANSPORT_STATE_CHECK_CRC
+} RtcmTransportState;
+
+typedef enum
{
- uint32_t configKey;
- uint8_t gnssID;
- bool enabled;
- char textName[30];
+ RADIO_EXTERNAL = 0,
+ RADIO_ESPNOW,
+} RadioType_e;
+
+typedef enum
+{
+ BLUETOOTH_RADIO_SPP = 0,
+ BLUETOOTH_RADIO_BLE,
+ 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];
+ char password[50];
+} WiFiNetwork;
+
+#define MAX_WIFI_NETWORKS 4
+
+typedef uint16_t RING_BUFFER_OFFSET;
+
+typedef enum
+{
+ ETH_NOT_STARTED = 0,
+ ETH_STARTED_CHECK_CABLE,
+ ETH_STARTED_START_DHCP,
+ ETH_CONNECTED,
+ ETH_CAN_NOT_BEGIN,
+ // Add new states here
+ ETH_MAX_STATE
+} ethernetStatus_e;
+
+const char *const ethernetStates[] = {
+ "ETH_NOT_STARTED", "ETH_STARTED_CHECK_CABLE", "ETH_STARTED_START_DHCP", "ETH_CONNECTED", "ETH_CAN_NOT_BEGIN",
+};
+
+const int ethernetStateEntries = sizeof(ethernetStates) / sizeof(ethernetStates[0]);
+
+// Radio status LED goes from off (LED off), no connection (blinking), to connected (solid)
+typedef enum
+{
+ BT_OFF = 0,
+ BT_NOTCONNECTED,
+ BT_CONNECTED,
+} BTState;
+
+// Return values for getString()
+typedef enum
+{
+ INPUT_RESPONSE_GETNUMBER_EXIT =
+ -9999999, // Less than min ECEF. User may be prompted for number but wants to exit without entering data
+ INPUT_RESPONSE_GETNUMBER_TIMEOUT = -9999998,
+ INPUT_RESPONSE_GETCHARACTERNUMBER_TIMEOUT = 255,
+ INPUT_RESPONSE_GETCHARACTERNUMBER_EMPTY = 254,
+ INPUT_RESPONSE_INVALID = -4,
+ INPUT_RESPONSE_TIMEOUT = -3,
+ INPUT_RESPONSE_OVERFLOW = -2,
+ INPUT_RESPONSE_EMPTY = -1,
+ INPUT_RESPONSE_VALID = 1,
+} InputResponse;
+
+typedef enum
+{
+ PRINT_ENDPOINT_SERIAL = 0,
+ PRINT_ENDPOINT_BLUETOOTH,
+ PRINT_ENDPOINT_ALL,
+} PrintEndpoint;
+PrintEndpoint printEndpoint = PRINT_ENDPOINT_SERIAL; // Controls where the configuration menu gets piped to
+
+typedef enum
+{
+ FUNCTION_NOT_SET = 0,
+ FUNCTION_SYNC,
+ FUNCTION_WRITESD,
+ FUNCTION_FILESIZE,
+ FUNCTION_EVENT,
+ FUNCTION_BEGINSD,
+ FUNCTION_RECORDSETTINGS,
+ FUNCTION_LOADSETTINGS,
+ FUNCTION_MARKEVENT,
+ FUNCTION_GETLINE,
+ FUNCTION_REMOVEFILE,
+ FUNCTION_RECORDLINE,
+ FUNCTION_CREATEFILE,
+ FUNCTION_ENDLOGGING,
+ FUNCTION_FINDLOG,
+ FUNCTION_LOGTEST,
+ FUNCTION_FILELIST,
+ FUNCTION_FILEMANAGER_OPEN1,
+ FUNCTION_FILEMANAGER_OPEN2,
+ FUNCTION_FILEMANAGER_OPEN3,
+ FUNCTION_FILEMANAGER_UPLOAD1,
+ FUNCTION_FILEMANAGER_UPLOAD2,
+ FUNCTION_FILEMANAGER_UPLOAD3,
+ FUNCTION_SDSIZECHECK,
+ FUNCTION_LOG_CLOSURE,
+ FUNCTION_PRINT_FILE_LIST,
+ FUNCTION_NTPEVENT,
+
+} SemaphoreFunction;
+
+#include //http://librarymanager/All#SparkFun_u-blox_GNSS_v3
+
+// Each constellation will have its config key, enable, and a visible name
+typedef struct
+{
+ uint32_t configKey;
+ uint8_t gnssID;
+ bool enabled;
+ char textName[30];
} ubxConstellation;
-//These are the allowable constellations to receive from and log (if enabled)
-//Tested with u-center v21.02
-#define MAX_CONSTELLATIONS 6
-ubxConstellation ubxConstellations[MAX_CONSTELLATIONS] =
-{
- {UBLOX_CFG_SIGNAL_GPS_ENA, SFE_UBLOX_GNSS_ID_GPS, true, "GPS"},
- {UBLOX_CFG_SIGNAL_SBAS_ENA, SFE_UBLOX_GNSS_ID_SBAS, false, "SBAS"}, //Bug in ZED-F9P v1.13 firmware causes RTK LED to not light when RTK Floating with SBAS on.
- {UBLOX_CFG_SIGNAL_GAL_ENA, SFE_UBLOX_GNSS_ID_GALILEO, true, "Galileo"},
- {UBLOX_CFG_SIGNAL_BDS_ENA, SFE_UBLOX_GNSS_ID_BEIDOU, true, "BeiDou"},
- //{UBLOX_CFG_SIGNAL_QZSS_ENA, SFE_UBLOX_GNSS_ID_IMES, false, "IMES"}, //Not yet supported? Config key does not exist?
- {UBLOX_CFG_SIGNAL_QZSS_ENA, SFE_UBLOX_GNSS_ID_QZSS, true, "QZSS"},
- {UBLOX_CFG_SIGNAL_GLO_ENA, SFE_UBLOX_GNSS_ID_GLONASS, true, "GLONASS"},
+// These are the allowable constellations to receive from and log (if enabled)
+// Tested with u-center v21.02
+#define MAX_CONSTELLATIONS 6 //(sizeof(ubxConstellations)/sizeof(ubxConstellation))
+
+// Different ZED modules support different messages (F9P vs F9R vs F9T)
+// Create binary packed struct for different platforms
+typedef enum
+{
+ PLATFORM_F9P = 0b0001,
+ PLATFORM_F9R = 0b0010,
+ PLATFORM_F9T = 0b0100,
+} ubxPlatform;
+
+// Print the base coordinates in different formats, depending on the type the user has entered
+// These are the different supported types
+typedef enum
+{
+ COORDINATE_INPUT_TYPE_DD = 0, // Default DD.ddddddddd
+ COORDINATE_INPUT_TYPE_DDMM, // DDMM.mmmmm
+ COORDINATE_INPUT_TYPE_DD_MM, // DD MM.mmmmm
+ COORDINATE_INPUT_TYPE_DD_MM_DASH, // DD-MM.mmmmm
+ COORDINATE_INPUT_TYPE_DD_MM_SYMBOL, // DD°MM.mmmmmmm'
+ COORDINATE_INPUT_TYPE_DDMMSS, // DD MM SS.ssssss
+ COORDINATE_INPUT_TYPE_DD_MM_SS, // DD MM SS.ssssss
+ COORDINATE_INPUT_TYPE_DD_MM_SS_DASH, // DD-MM-SS.ssssss
+ COORDINATE_INPUT_TYPE_DD_MM_SS_SYMBOL, // DD°MM'SS.ssssss"
+ COORDINATE_INPUT_TYPE_DDMMSS_NO_DECIMAL, // DDMMSS - No decimal
+ COORDINATE_INPUT_TYPE_DD_MM_SS_NO_DECIMAL, // DD MM SS - No decimal
+ COORDINATE_INPUT_TYPE_DD_MM_SS_DASH_NO_DECIMAL, // DD-MM-SS - No decimal
+ COORDINATE_INPUT_TYPE_INVALID_UNKNOWN,
+} CoordinateInputType;
+
+#define UBX_ID_NOT_AVAILABLE 0xFF
+
+// Define the periodic display values
+typedef uint32_t PeriodicDisplay_t;
+
+enum PeriodDisplayValues
+{
+ PD_BLUETOOTH_DATA_RX = 0, // 0
+ PD_BLUETOOTH_DATA_TX, // 1
+
+ PD_ETHERNET_IP_ADDRESS, // 2
+ PD_ETHERNET_STATE, // 3
+
+ PD_NETWORK_STATE, // 4
+
+ PD_NTP_SERVER_DATA, // 5
+ PD_NTP_SERVER_STATE, // 6
+
+ PD_NTRIP_CLIENT_DATA, // 7
+ PD_NTRIP_CLIENT_GGA, // 8
+ PD_NTRIP_CLIENT_STATE, // 9
+
+ PD_NTRIP_SERVER_DATA, // 10
+ PD_NTRIP_SERVER_STATE, // 11
+
+ PD_PVT_CLIENT_DATA, // 12
+ PD_PVT_CLIENT_STATE, // 13
+
+ PD_PVT_SERVER_DATA, // 14
+ PD_PVT_SERVER_STATE, // 15
+ PD_PVT_SERVER_CLIENT_DATA, // 16
+
+ PD_PVT_UDP_SERVER_DATA, // 17
+ PD_PVT_UDP_SERVER_STATE, // 18
+ PD_PVT_UDP_SERVER_BROADCAST_DATA, // 19
+
+ PD_RING_BUFFER_MILLIS, // 20
+
+ PD_SD_LOG_WRITE, // 21
+
+ PD_TASK_BLUETOOTH_READ, // 22
+ PD_TASK_BUTTON_CHECK, // 23
+ PD_TASK_GNSS_READ, // 24
+ PD_TASK_HANDLE_GNSS_DATA, // 25
+ PD_TASK_SD_SIZE_CHECK, // 26
+
+ PD_WIFI_IP_ADDRESS, // 27
+ PD_WIFI_STATE, // 28
+
+ PD_ZED_DATA_RX, // 29
+ PD_ZED_DATA_TX, // 30
+
+ PD_OTA_CLIENT_STATE, // 31
+ // Add new values before this line
};
-//Each message will have a rate, a visible name, and a class
-typedef struct ubxMsg
+#define PERIODIC_MASK(x) (1 << x)
+#define PERIODIC_DISPLAY(x) (periodicDisplay & PERIODIC_MASK(x))
+#define PERIODIC_CLEAR(x) periodicDisplay &= ~PERIODIC_MASK(x)
+#define PERIODIC_SETTING(x) (settings.periodicDisplay & PERIODIC_MASK(x))
+#define PERIODIC_TOGGLE(x) settings.periodicDisplay ^= PERIODIC_MASK(x)
+
+// These are the allowable messages to broadcast and log (if enabled)
+
+// Struct to describe the necessary info for each type of UBX message
+// Each message will have a key, ID, class, visible name, and various info about which platforms the message is
+// supported on Message rates are store within NVM
+typedef struct
{
- uint32_t msgConfigKey;
- uint8_t msgID;
- uint8_t msgClass;
- uint8_t msgRate;
- char msgTextName[30];
+ const uint32_t msgConfigKey;
+ const uint8_t msgID;
+ const uint8_t msgClass;
+ const uint8_t msgDefaultRate;
+ const char msgTextName[20];
+ const uint32_t filterMask;
+ const uint16_t f9pFirmwareVersionSupported; // The minimum version this message is supported. 0 = all versions. 9999
+ // = Not supported
+ const uint16_t f9rFirmwareVersionSupported;
} ubxMsg;
-//These are the allowable messages to broadcast and log (if enabled)
-//Tested with u-center v21.02
-#define MAX_UBX_MSG 67
-ubxMsg ubxMessages[MAX_UBX_MSG] =
-{
- {UBLOX_CFG_MSGOUT_NMEA_ID_DTM_UART1, UBX_NMEA_DTM, UBX_CLASS_NMEA, 0, "UBX_NMEA_DTM"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GBS_UART1, UBX_NMEA_GBS, UBX_CLASS_NMEA, 0, "UBX_NMEA_GBS"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART1, UBX_NMEA_GGA, UBX_CLASS_NMEA, 1, "UBX_NMEA_GGA"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART1, UBX_NMEA_GLL, UBX_CLASS_NMEA, 0, "UBX_NMEA_GLL"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GNS_UART1, UBX_NMEA_GNS, UBX_CLASS_NMEA, 0, "UBX_NMEA_GNS"},
-
- {UBLOX_CFG_MSGOUT_NMEA_ID_GRS_UART1, UBX_NMEA_GRS, UBX_CLASS_NMEA, 0, "UBX_NMEA_GRS"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART1, UBX_NMEA_GSA, UBX_CLASS_NMEA, 1, "UBX_NMEA_GSA"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART1, UBX_NMEA_GST, UBX_CLASS_NMEA, 1, "UBX_NMEA_GST"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART1, UBX_NMEA_GSV, UBX_CLASS_NMEA, 4, "UBX_NMEA_GSV"}, //Default to 1 update every 4 fixes
- {UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART1, UBX_NMEA_RMC, UBX_CLASS_NMEA, 1, "UBX_NMEA_RMC"},
-
- {UBLOX_CFG_MSGOUT_NMEA_ID_VLW_UART1, UBX_NMEA_VLW, UBX_CLASS_NMEA, 0, "UBX_NMEA_VLW"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART1, UBX_NMEA_VTG, UBX_CLASS_NMEA, 0, "UBX_NMEA_VTG"},
- {UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_UART1, UBX_NMEA_ZDA, UBX_CLASS_NMEA, 0, "UBX_NMEA_ZDA"},
-
- // uint8_t nmea_msb = 0; //Not supported by u-center
- // uint8_t nmea_gaq = 0; //Not supported by u-center
- // uint8_t nmea_gbq = 0; //Not supported by u-center
- // uint8_t nmea_glq = 0; //Not supported by u-center
- // uint8_t nmea_gnq = 0; //Not supported by u-center
- // uint8_t nmea_gpq = 0; //Not supported by u-center
- // uint8_t nmea_gqq = 0; //Not supported by u-center
- // uint8_t nmea_rlm = 0; //Not supported by u-center
- // uint8_t nmea_txt = 0; //Not supported by u-center
- // uint8_t nmea_ths = 0; //Not supported by ZED-F9P
-
- {UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_UART1, UBX_NAV_CLOCK, UBX_CLASS_NAV, 0, "UBX_NAV_CLOCK"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_DOP_UART1, UBX_NAV_DOP, UBX_CLASS_NAV, 0, "UBX_NAV_DOP"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_EOE_UART1, UBX_NAV_EOE, UBX_CLASS_NAV, 0, "UBX_NAV_EOE"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_UART1, UBX_NAV_GEOFENCE, UBX_CLASS_NAV, 0, "UBX_NAV_GEOFENCE"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_UART1, UBX_NAV_HPPOSECEF, UBX_CLASS_NAV, 0, "UBX_NAV_HPPOSECEF"},
-
- {UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_UART1, UBX_NAV_HPPOSLLH, UBX_CLASS_NAV, 0, "UBX_NAV_HPPOSLLH"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_ODO_UART1, UBX_NAV_ODO, UBX_CLASS_NAV, 0, "UBX_NAV_ODO"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_ORB_UART1, UBX_NAV_ORB, UBX_CLASS_NAV, 0, "UBX_NAV_ORB"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_UART1, UBX_NAV_POSECEF, UBX_CLASS_NAV, 0, "UBX_NAV_POSECEF"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_UART1, UBX_NAV_POSLLH, UBX_CLASS_NAV, 0, "UBX_NAV_POSLLH"},
-
- {UBLOX_CFG_MSGOUT_UBX_NAV_PVT_UART1, UBX_NAV_PVT, UBX_CLASS_NAV, 0, "UBX_NAV_PVT"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_UART1, UBX_NAV_RELPOSNED, UBX_CLASS_NAV, 0, "UBX_NAV_RELPOSNED"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_SAT_UART1, UBX_NAV_SAT, UBX_CLASS_NAV, 0, "UBX_NAV_SAT"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_SIG_UART1, UBX_NAV_SIG, UBX_CLASS_NAV, 0, "UBX_NAV_SIG"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_UART1, UBX_NAV_STATUS, UBX_CLASS_NAV, 0, "UBX_NAV_STATUS"},
-
- {UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_UART1, UBX_NAV_SVIN, UBX_CLASS_NAV, 0, "UBX_NAV_SVIN"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_UART1, UBX_NAV_TIMEBDS, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEBDS"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_UART1, UBX_NAV_TIMEGAL, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEGAL"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_UART1, UBX_NAV_TIMEGLO, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEGLO"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_UART1, UBX_NAV_TIMEGPS, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEGPS"},
-
- {UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_UART1, UBX_NAV_TIMELS, UBX_CLASS_NAV, 0, "UBX_NAV_TIMELS"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_UART1, UBX_NAV_TIMEUTC, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEUTC"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_UART1, UBX_NAV_VELECEF, UBX_CLASS_NAV, 0, "UBX_NAV_VELECEF"},
- {UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_UART1, UBX_NAV_VELNED, UBX_CLASS_NAV, 0, "UBX_NAV_VELNED"},
-
- // uint8_t nav_aopstatus = 0; //Not supported by library or ZED-F9P
- // uint8_t nav_att = 0; //Not supported by ZED-F9P
- // uint8_t nav_cov = 0; //Not supported by library
- // uint8_t nav_resetodo = 0; //Not supported u-center 21.02
- // uint8_t nav_sbas = 0; //Not supported by library
- // uint8_t nav_slas = 0; //Not supported by library
- // uint8_t nav_timeqzss = 0; //Not supported in library
- // uint8_t nav_dgps = 0; //Not supported by ZED-F9P
- // uint8_t nav_eell = 0; //Not supported in library
- // uint8_t nav_ekfstatus = 0; //Not supported by ZED-F9P
- // uint8_t nav_nmi = 0; //Not supported by ZED-F9P or library
- // uint8_t nav_sol = 0; //Not supported by ZED-F9P or library
- // uint8_t nav_svinfo = 0; //Not supported by ZED-F9P or library
-
- {UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_UART1, UBX_RXM_MEASX, UBX_CLASS_RXM, 0, "UBX_RXM_MEASX"},
- {UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_UART1, UBX_RXM_RAWX, UBX_CLASS_RXM, 0, "UBX_RXM_RAWX"},
- {UBLOX_CFG_MSGOUT_UBX_RXM_RLM_UART1, UBX_RXM_RLM, UBX_CLASS_RXM, 0, "UBX_RXM_RLM"},
- {UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_UART1, UBX_RXM_RTCM, UBX_CLASS_RXM, 0, "UBX_RXM_RTCM"},
- {UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_UART1, UBX_RXM_SFRBX, UBX_CLASS_RXM, 0, "UBX_RXM_SFRBX"},
-
-// uint8_t rxm_alm = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_eph = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_imes = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_pmp = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_raw = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_sfrb = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_spartn = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_svsi = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_tm = 0; //Not supported by library or ZED-F9P
-// uint8_t rxm_pmreq = 0; //Not supported by u-center
-
-// uint8_t hnr_att = 0; //Not supported by ZED-F9P
-// uint8_t hnr_ins = 0; //Not supported by ZED-F9P
-// uint8_t hnr_pvt = 0; //Not supported by ZED-F9P
-
- {UBLOX_CFG_MSGOUT_UBX_MON_COMMS_UART1, UBX_MON_COMMS, UBX_CLASS_MON, 0, "UBX_MON_COMMS"},
- {UBLOX_CFG_MSGOUT_UBX_MON_HW2_UART1, UBX_MON_HW2, UBX_CLASS_MON, 0, "UBX_MON_HW2"},
- {UBLOX_CFG_MSGOUT_UBX_MON_HW3_UART1, UBX_MON_HW3, UBX_CLASS_MON, 0, "UBX_MON_HW3"},
- {UBLOX_CFG_MSGOUT_UBX_MON_HW_UART1, UBX_MON_HW, UBX_CLASS_MON, 0, "UBX_MON_HW"},
- {UBLOX_CFG_MSGOUT_UBX_MON_IO_UART1, UBX_MON_IO, UBX_CLASS_MON, 0, "UBX_MON_IO"},
-
- {UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_UART1, UBX_MON_MSGPP, UBX_CLASS_MON, 0, "UBX_MON_MSGPP"},
- {UBLOX_CFG_MSGOUT_UBX_MON_RF_UART1, UBX_MON_RF, UBX_CLASS_MON, 0, "UBX_MON_RF"},
- {UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_UART1, UBX_MON_RXBUF, UBX_CLASS_MON, 0, "UBX_MON_RXBUF"},
- {UBLOX_CFG_MSGOUT_UBX_MON_RXR_UART1, UBX_MON_RXR, UBX_CLASS_MON, 0, "UBX_MON_RXR"},
- {UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_UART1, UBX_MON_TXBUF, UBX_CLASS_MON, 0, "UBX_MON_TXBUF"},
-
-// uint8_t mon_gnss = 0; //Not supported by u-center
-// uint8_t mon_patch = 0; //Not supported by u-center
- //uint8_t mon_smgr = 0; //Not supported by library or ZED-F9P
- //uint8_t mon_span = 0; //Not supported by library
-// uint8_t mon_ver = 0; //Not supported by u-center
-
- {UBLOX_CFG_MSGOUT_UBX_TIM_TM2_UART1, UBX_TIM_TM2, UBX_CLASS_TIM, 0, "UBX_TIM_TM2"},
- {UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART1, UBX_TIM_TP, UBX_CLASS_TIM, 0, "UBX_TIM_TP"},
- {UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_UART1, UBX_TIM_VRFY, UBX_CLASS_TIM, 0, "UBX_TIM_VRFY"},
-
-// uint8_t tim_dosc = 0; //Not supported by library or ZED-F9P
-// uint8_t tim_fchg = 0; //Not supported by library or ZED-F9P
-// uint8_t tim_smeas = 0; //Not supported by library or ZED-F9P
-// uint8_t tim_svin = 0; //Not supported by library or ZED-F9P
-// uint8_t tim_tos = 0; //Not supported by library or ZED-F9P
-// uint8_t tim_vcocal = 0; //Not supported by library or ZED-F9P
-
-// uint8_t esf_alg = 0; //Not supported by ZED-F9P
-// uint8_t esf_ins = 0; //Not supported by ZED-F9P
-// uint8_t esf_meas = 0; //Not supported by ZED-F9P
-// uint8_t esf_raw = 0; //Not supported by ZED-F9P
-// uint8_t esf_status = 0; //Not supported by ZED-F9P
- //uint8_t esf_resetalg = 0; //Not supported by u-center
-
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_UART1, UBX_RTCM_1005, UBX_RTCM_MSB, 0, "UBX_RTCM_1005"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_UART1, UBX_RTCM_1074, UBX_RTCM_MSB, 0, "UBX_RTCM_1074"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_UART1, UBX_RTCM_1077, UBX_RTCM_MSB, 0, "UBX_RTCM_1077"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_UART1, UBX_RTCM_1084, UBX_RTCM_MSB, 0, "UBX_RTCM_1084"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_UART1, UBX_RTCM_1087, UBX_RTCM_MSB, 0, "UBX_RTCM_1087"},
-
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_UART1, UBX_RTCM_1094, UBX_RTCM_MSB, 0, "UBX_RTCM_1094"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_UART1, UBX_RTCM_1097, UBX_RTCM_MSB, 0, "UBX_RTCM_1097"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_UART1, UBX_RTCM_1124, UBX_RTCM_MSB, 0, "UBX_RTCM_1124"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_UART1, UBX_RTCM_1127, UBX_RTCM_MSB, 0, "UBX_RTCM_1127"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART1, UBX_RTCM_1230, UBX_RTCM_MSB, 0, "UBX_RTCM_1230"},
-
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_UART1, UBX_RTCM_4072_0, UBX_RTCM_MSB, 0, "UBX_RTCM_4072_0"},
- {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_UART1, UBX_RTCM_4072_1, UBX_RTCM_MSB, 0, "UBX_RTCM_4072_1"}
+// Static array containing all the compatible messages
+const ubxMsg ubxMessages[] = {
+ // NMEA
+ {UBLOX_CFG_MSGOUT_NMEA_ID_DTM_UART1, UBX_NMEA_DTM, UBX_CLASS_NMEA, 0, "UBX_NMEA_DTM", SFE_UBLOX_FILTER_NMEA_DTM,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GBS_UART1, UBX_NMEA_GBS, UBX_CLASS_NMEA, 0, "UBX_NMEA_GBS", SFE_UBLOX_FILTER_NMEA_GBS,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART1, UBX_NMEA_GGA, UBX_CLASS_NMEA, 1, "UBX_NMEA_GGA", SFE_UBLOX_FILTER_NMEA_GGA,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART1, UBX_NMEA_GLL, UBX_CLASS_NMEA, 0, "UBX_NMEA_GLL", SFE_UBLOX_FILTER_NMEA_GLL,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GNS_UART1, UBX_NMEA_GNS, UBX_CLASS_NMEA, 0, "UBX_NMEA_GNS", SFE_UBLOX_FILTER_NMEA_GNS,
+ 112, 120},
+
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GRS_UART1, UBX_NMEA_GRS, UBX_CLASS_NMEA, 0, "UBX_NMEA_GRS", SFE_UBLOX_FILTER_NMEA_GRS,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART1, UBX_NMEA_GSA, UBX_CLASS_NMEA, 1, "UBX_NMEA_GSA", SFE_UBLOX_FILTER_NMEA_GSA,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART1, UBX_NMEA_GST, UBX_CLASS_NMEA, 1, "UBX_NMEA_GST", SFE_UBLOX_FILTER_NMEA_GST,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART1, UBX_NMEA_GSV, UBX_CLASS_NMEA, 4, "UBX_NMEA_GSV", SFE_UBLOX_FILTER_NMEA_GSV,
+ 112, 120}, // Default to 1 update every 4 fixes
+ {UBLOX_CFG_MSGOUT_NMEA_ID_RLM_UART1, UBX_NMEA_RLM, UBX_CLASS_NMEA, 0, "UBX_NMEA_RLM", SFE_UBLOX_FILTER_NMEA_RLM,
+ 113, 120}, // No F9P 112 support
+
+ {UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART1, UBX_NMEA_RMC, UBX_CLASS_NMEA, 1, "UBX_NMEA_RMC", SFE_UBLOX_FILTER_NMEA_RMC,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_THS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NMEA, 0, "UBX_NMEA_THS",
+ SFE_UBLOX_FILTER_NMEA_THS, 9999, 120}, // Not supported F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_NMEA_ID_VLW_UART1, UBX_NMEA_VLW, UBX_CLASS_NMEA, 0, "UBX_NMEA_VLW", SFE_UBLOX_FILTER_NMEA_VLW,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART1, UBX_NMEA_VTG, UBX_CLASS_NMEA, 0, "UBX_NMEA_VTG", SFE_UBLOX_FILTER_NMEA_VTG,
+ 112, 120},
+ {UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_UART1, UBX_NMEA_ZDA, UBX_CLASS_NMEA, 0, "UBX_NMEA_ZDA", SFE_UBLOX_FILTER_NMEA_ZDA,
+ 112, 120},
+
+ // NMEA NAV2
+ // F9P not supported 112, 113, 120. Supported starting 130.
+ // F9R not supported 120. Supported starting 130.
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_GGA_UART1, UBX_NMEA_GGA, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_GGA",
+ SFE_UBLOX_FILTER_NMEA_GGA, 130, 130},
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_GLL_UART1, UBX_NMEA_GLL, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_GLL",
+ SFE_UBLOX_FILTER_NMEA_GLL, 130, 130},
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_GNS_UART1, UBX_NMEA_GNS, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_GNS",
+ SFE_UBLOX_FILTER_NMEA_GNS, 130, 130},
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_GSA_UART1, UBX_NMEA_GSA, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_GSA",
+ SFE_UBLOX_FILTER_NMEA_GSA, 130, 130},
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_RMC_UART1, UBX_NMEA_RMC, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_RMC",
+ SFE_UBLOX_FILTER_NMEA_RMC, 130, 130},
+
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_VTG_UART1, UBX_NMEA_VTG, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_VTG",
+ SFE_UBLOX_FILTER_NMEA_VTG, 130, 130},
+ {UBLOX_CFG_MSGOUT_NMEA_NAV2_ID_ZDA_UART1, UBX_NMEA_ZDA, UBX_CLASS_NMEA, 0, "UBX_NMEANAV2_ZDA",
+ SFE_UBLOX_FILTER_NMEA_ZDA, 130, 130},
+
+ // PUBX
+ // F9P support 130
+ {UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_PUBX, 0, "UBX_PUBX_POLYP", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_PUBX, 0, "UBX_PUBX_POLYS", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_PUBX, 0, "UBX_PUBX_POLYT", 0, 112, 120},
+
+ // RTCM
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_UART1, UBX_RTCM_1005, UBX_RTCM_MSB, 1, "UBX_RTCM_1005",
+ SFE_UBLOX_FILTER_RTCM_TYPE1005, 112, 9999}, // Not supported on F9R
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_UART1, UBX_RTCM_1074, UBX_RTCM_MSB, 1, "UBX_RTCM_1074",
+ SFE_UBLOX_FILTER_RTCM_TYPE1074, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_UART1, UBX_RTCM_1077, UBX_RTCM_MSB, 0, "UBX_RTCM_1077",
+ SFE_UBLOX_FILTER_RTCM_TYPE1077, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_UART1, UBX_RTCM_1084, UBX_RTCM_MSB, 1, "UBX_RTCM_1084",
+ SFE_UBLOX_FILTER_RTCM_TYPE1084, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_UART1, UBX_RTCM_1087, UBX_RTCM_MSB, 0, "UBX_RTCM_1087",
+ SFE_UBLOX_FILTER_RTCM_TYPE1087, 112, 9999},
+
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_UART1, UBX_RTCM_1094, UBX_RTCM_MSB, 1, "UBX_RTCM_1094",
+ SFE_UBLOX_FILTER_RTCM_TYPE1094, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_UART1, UBX_RTCM_1097, UBX_RTCM_MSB, 0, "UBX_RTCM_1097",
+ SFE_UBLOX_FILTER_RTCM_TYPE1097, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_UART1, UBX_RTCM_1124, UBX_RTCM_MSB, 1, "UBX_RTCM_1124",
+ SFE_UBLOX_FILTER_RTCM_TYPE1124, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_UART1, UBX_RTCM_1127, UBX_RTCM_MSB, 0, "UBX_RTCM_1127",
+ SFE_UBLOX_FILTER_RTCM_TYPE1127, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART1, UBX_RTCM_1230, UBX_RTCM_MSB, 10, "UBX_RTCM_1230",
+ SFE_UBLOX_FILTER_RTCM_TYPE1230, 112, 9999},
+
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_UART1, UBX_RTCM_4072_0, UBX_RTCM_MSB, 0, "UBX_RTCM_4072_0",
+ SFE_UBLOX_FILTER_RTCM_TYPE4072_0, 112, 9999},
+ {UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_UART1, UBX_RTCM_4072_1, UBX_RTCM_MSB, 0, "UBX_RTCM_4072_1",
+ SFE_UBLOX_FILTER_RTCM_TYPE4072_1, 112, 9999},
+
+ // AID
+
+ // ESF
+ {UBLOX_CFG_MSGOUT_UBX_ESF_ALG_UART1, UBX_ESF_ALG, UBX_CLASS_ESF, 0, "UBX_ESF_ALG", 0, 9999,
+ 120}, // Not supported on F9P
+ {UBLOX_CFG_MSGOUT_UBX_ESF_INS_UART1, UBX_ESF_INS, UBX_CLASS_ESF, 0, "UBX_ESF_INS", 0, 9999, 120},
+ {UBLOX_CFG_MSGOUT_UBX_ESF_MEAS_UART1, UBX_ESF_MEAS, UBX_CLASS_ESF, 0, "UBX_ESF_MEAS", 0, 9999, 120},
+ {UBLOX_CFG_MSGOUT_UBX_ESF_RAW_UART1, UBX_ESF_RAW, UBX_CLASS_ESF, 0, "UBX_ESF_RAW", 0, 9999, 120},
+ {UBLOX_CFG_MSGOUT_UBX_ESF_STATUS_UART1, UBX_ESF_STATUS, UBX_CLASS_ESF, 0, "UBX_ESF_STATUS", 0, 9999, 120},
+
+ // HNR
+
+ // LOG
+ // F9P supports LOG_INFO at 112
+
+ // MON
+ {UBLOX_CFG_MSGOUT_UBX_MON_COMMS_UART1, UBX_MON_COMMS, UBX_CLASS_MON, 0, "UBX_MON_COMMS", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_HW2_UART1, UBX_MON_HW2, UBX_CLASS_MON, 0, "UBX_MON_HW2", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_HW3_UART1, UBX_MON_HW3, UBX_CLASS_MON, 0, "UBX_MON_HW3", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_HW_UART1, UBX_MON_HW, UBX_CLASS_MON, 0, "UBX_MON_HW", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_IO_UART1, UBX_MON_IO, UBX_CLASS_MON, 0, "UBX_MON_IO", 0, 112, 120},
+
+ {UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_UART1, UBX_MON_MSGPP, UBX_CLASS_MON, 0, "UBX_MON_MSGPP", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_RF_UART1, UBX_MON_RF, UBX_CLASS_MON, 0, "UBX_MON_RF", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_UART1, UBX_MON_RXBUF, UBX_CLASS_MON, 0, "UBX_MON_RXBUF", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_RXR_UART1, UBX_MON_RXR, UBX_CLASS_MON, 0, "UBX_MON_RXR", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_MON_SPAN_UART1, UBX_MON_SPAN, UBX_CLASS_MON, 0, "UBX_MON_SPAN", 0, 113,
+ 120}, // Not supported F9P 112
+
+ {UBLOX_CFG_MSGOUT_UBX_MON_SYS_UART1, UBX_MON_SYS, UBX_CLASS_MON, 0, "UBX_MON_SYS", 0, 9999,
+ 130}, // Not supported F9R 121, F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_UART1, UBX_MON_TXBUF, UBX_CLASS_MON, 0, "UBX_MON_TXBUF", 0, 112, 120},
+
+ // NAV2
+ // F9P not supported 112, 113, 120. Supported starting 130. F9P 130, 132 supports all but not EELL, PVAT, TIMENAVIC
+ // F9R not supported 120. Supported starting 130. F9R 130 supports EELL, PVAT but not SVIN, TIMENAVIC.
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_CLOCK_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_CLOCK", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_COV_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_COV", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_DOP_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_DOP", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_EELL_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_EELL", 0, 9999,
+ 130}, // Not supported F9P
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_EOE_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_EOE", 0, 130, 130},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_ODO_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_ODO", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_POSECEF_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_POSECEF", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_POSLLH_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_POSLLH", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_PVAT_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_PVAT", 0, 9999,
+ 130}, // Not supported F9P
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_PVT_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_PVT", 0, 130, 130},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_SAT_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_SAT", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_SBAS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_SBAS", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_SIG_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_SIG", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_SLAS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_SLAS", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_STATUS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_STATUS", 0, 130, 130},
+
+ //{UBLOX_CFG_MSGOUT_UBX_NAV2_SVIN_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_SVIN", 0, 9999, 9999},
+ ////No support yet
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMEBDS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMEBDS", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMEGAL_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMEGAL", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMEGLO_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMEGLO", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMEGPS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMEGPS", 0, 130, 130},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMELS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMELS", 0, 130, 130},
+ //{UBLOX_CFG_MSGOUT_UBX_NAV2_TIMENAVIC_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMENAVIC", 0, 9999,
+ // 9999}, //No support yet
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMEQZSS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMEQZSS", 0, 130,
+ 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_TIMEUTC_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_TIMEUTC", 0, 130, 130},
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_VELECEF_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_VELECEF", 0, 130, 130},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV2_VELNED_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV2_VELNED", 0, 130, 130},
+
+ // NAV
+ //{UBLOX_CFG_MSGOUT_UBX_NAV_AOPSTATUS_UART1, UBX_NAV_AOPSTATUS, UBX_CLASS_NAV, 0, "UBX_NAV_AOPSTATUS", 0, 9999,
+ // 9999}, //Not supported on F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_ATT_UART1, UBX_NAV_ATT, UBX_CLASS_NAV, 0, "UBX_NAV_ATT", 0, 9999,
+ 120}, // Not supported on F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_UART1, UBX_NAV_CLOCK, UBX_CLASS_NAV, 0, "UBX_NAV_CLOCK", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_COV_UART1, UBX_NAV_COV, UBX_CLASS_NAV, 0, "UBX_NAV_COV", 0, 112, 120},
+ //{UBLOX_CFG_MSGOUT_UBX_NAV_DGPS_UART1, UBX_NAV_DGPS, UBX_CLASS_NAV, 0, "UBX_NAV_DGPS", 0, 9999, 9999}, //Not
+ // supported on F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_DOP_UART1, UBX_NAV_DOP, UBX_CLASS_NAV, 0, "UBX_NAV_DOP", 0, 112, 120},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV_EELL_UART1, UBX_NAV_EELL, UBX_CLASS_NAV, 0, "UBX_NAV_EELL", 0, 9999,
+ 120}, // Not supported on F9P
+ {UBLOX_CFG_MSGOUT_UBX_NAV_EOE_UART1, UBX_NAV_EOE, UBX_CLASS_NAV, 0, "UBX_NAV_EOE", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_UART1, UBX_NAV_GEOFENCE, UBX_CLASS_NAV, 0, "UBX_NAV_GEOFENCE", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_UART1, UBX_NAV_HPPOSECEF, UBX_CLASS_NAV, 0, "UBX_NAV_HPPOSECEF", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_UART1, UBX_NAV_HPPOSLLH, UBX_CLASS_NAV, 0, "UBX_NAV_HPPOSLLH", 0, 112, 120},
+
+ //{UBLOX_CFG_MSGOUT_UBX_NAV_NMI_UART1, UBX_NAV_NMI, UBX_CLASS_NAV, 0, "UBX_NAV_NMI", 0, 9999, 9999}, //Not supported
+ // on F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_ODO_UART1, UBX_NAV_ODO, UBX_CLASS_NAV, 0, "UBX_NAV_ODO", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_ORB_UART1, UBX_NAV_ORB, UBX_CLASS_NAV, 0, "UBX_NAV_ORB", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_PL_UART1, UBX_NAV_PL, UBX_CLASS_NAV, 0, "UBX_NAV_PL", 0, 9999,
+ 130}, // Not supported F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_UART1, UBX_NAV_POSECEF, UBX_CLASS_NAV, 0, "UBX_NAV_POSECEF", 0, 112, 120},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_UART1, UBX_NAV_POSLLH, UBX_CLASS_NAV, 0, "UBX_NAV_POSLLH", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_PVAT_UART1, UBX_NAV_PVAT, UBX_CLASS_NAV, 0, "UBX_NAV_PVAT", 0, 9999,
+ 121}, // Not supported on F9P 112, 113, 120, 130, F9R 120
+ {UBLOX_CFG_MSGOUT_UBX_NAV_PVT_UART1, UBX_NAV_PVT, UBX_CLASS_NAV, 0, "UBX_NAV_PVT", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_UART1, UBX_NAV_RELPOSNED, UBX_CLASS_NAV, 0, "UBX_NAV_RELPOSNED", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_SAT_UART1, UBX_NAV_SAT, UBX_CLASS_NAV, 0, "UBX_NAV_SAT", 0, 112, 120},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_UART1, UBX_NAV_SBAS, UBX_CLASS_NAV, 0, "UBX_NAV_SBAS", 0, 113,
+ 120}, // Not supported F9P 112
+ {UBLOX_CFG_MSGOUT_UBX_NAV_SIG_UART1, UBX_NAV_SIG, UBX_CLASS_NAV, 0, "UBX_NAV_SIG", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_UART1, UBX_NAV_SLAS, UBX_CLASS_NAV, 0, "UBX_NAV_SLAS", 0, 113,
+ 130}, // Not supported F9R 121 or F9P 112
+ //{UBLOX_CFG_MSGOUT_UBX_NAV_SOL_UART1, UBX_NAV_SOL, UBX_CLASS_NAV, 0, "UBX_NAV_SOL", 0, 9999, 9999}, //Not
+ // supported F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_UART1, UBX_NAV_STATUS, UBX_CLASS_NAV, 0, "UBX_NAV_STATUS", 0, 112, 120},
+ //{UBLOX_CFG_MSGOUT_UBX_NAV_SVINFO_UART1, UBX_NAV_SVINFO, UBX_CLASS_NAV, 0, "UBX_NAV_SVINFO", 0, 9999, 9999}, //Not
+ // supported F9R 120 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_UART1, UBX_NAV_SVIN, UBX_CLASS_NAV, 0, "UBX_NAV_SVIN", 0, 112,
+ 9999}, // Not supported on F9R 120, 121, 130
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_UART1, UBX_NAV_TIMEBDS, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEBDS", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_UART1, UBX_NAV_TIMEGAL, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEGAL", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_UART1, UBX_NAV_TIMEGLO, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEGLO", 0, 112, 120},
+
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_UART1, UBX_NAV_TIMEGPS, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEGPS", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_UART1, UBX_NAV_TIMELS, UBX_CLASS_NAV, 0, "UBX_NAV_TIMELS", 0, 112, 120},
+ //{UBLOX_CFG_MSGOUT_UBX_NAV_TIMENAVIC_UART1, UBX_NAV_TIMENAVIC, UBX_CLASS_NAV, 0, "UBX_NAV_TIMENAVIC", 0, 9999,
+ // 9999}, //Not supported F9R 121 or F9P 132
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_UART1, UBX_ID_NOT_AVAILABLE, UBX_CLASS_NAV, 0, "UBX_NAV_QZSS", 0, 113,
+ 130}, // Not supported F9R 121 or F9P 112
+ {UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_UART1, UBX_NAV_TIMEUTC, UBX_CLASS_NAV, 0, "UBX_NAV_TIMEUTC", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_UART1, UBX_NAV_VELECEF, UBX_CLASS_NAV, 0, "UBX_NAV_VELECEF", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_UART1, UBX_NAV_VELNED, UBX_CLASS_NAV, 0, "UBX_NAV_VELNED", 0, 112, 120},
+
+ // RXM
+ //{UBLOX_CFG_MSGOUT_UBX_RXM_ALM_UART1, UBX_RXM_ALM, UBX_CLASS_RXM, 0, "UBX_RXM_ALM", 0, 9999, 9999}, //Not supported
+ // F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_RXM_COR_UART1, UBX_RXM_COR, UBX_CLASS_RXM, 0, "UBX_RXM_COR", 0, 9999,
+ 130}, // Not supported F9R 121 or F9P 112, 113, 120, 130, 132
+ //{UBLOX_CFG_MSGOUT_UBX_RXM_EPH_UART1, UBX_RXM_EPH, UBX_CLASS_RXM, 0, "UBX_RXM_EPH", 0, 9999, 9999}, //Not
+ // supported F9R 121 or F9P 112, 113, 120, 130, 132 {UBLOX_CFG_MSGOUT_UBX_RXM_IMES_UART1, UBX_RXM_IMES,
+ // UBX_CLASS_RXM, 0, "UBX_RXM_IMES", 0, 9999, 9999}, //Not supported F9R 121 or F9P 112, 113, 120, 130, 132
+ //{UBLOX_CFG_MSGOUT_UBX_RXM_MEAS20_UART1, UBX_RXM_MEAS20, UBX_CLASS_RXM, 0, "UBX_RXM_MEAS20", 0, 9999, 9999},
+ ////Not supported F9R 121 or F9P 112, 113, 120, 130, 132 {UBLOX_CFG_MSGOUT_UBX_RXM_MEAS50_UART1,
+ // UBX_RXM_MEAS50, UBX_CLASS_RXM, 0, "UBX_RXM_MEAS50", 0, 9999, 9999}, //Not supported F9R 121 or F9P 112,
+ // 113, 120, 130, 132 {UBLOX_CFG_MSGOUT_UBX_RXM_MEASC12_UART1, UBX_RXM_MEASC12, UBX_CLASS_RXM, 0,
+ //"UBX_RXM_MEASC12", 0, 9999, 9999}, //Not supported F9R 121 or F9P 112, 113, 120, 130, 132
+ //{UBLOX_CFG_MSGOUT_UBX_RXM_MEASD12_UART1, UBX_RXM_MEASD12, UBX_CLASS_RXM, 0, "UBX_RXM_MEASD12", 0, 9999,
+ // 9999}, //Not supported F9R 121 or F9P 112, 113, 120, 130, 132
+ {UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_UART1, UBX_RXM_MEASX, UBX_CLASS_RXM, 0, "UBX_RXM_MEASX", 0, 112, 120},
+ //{UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, UBX_RXM_PMP, UBX_CLASS_RXM, 0, "UBX_RXM_PMP", 0, 9999, 9999}, //Not supported
+ // F9R 121 or F9P 112, 113, 120, 130, 132 {UBLOX_CFG_MSGOUT_UBX_RXM_QZSSL6_UART1, UBX_RXM_QZSSL6, UBX_CLASS_RXM, 0,
+ //"UBX_RXM_QZSSL6", 0, 9999, 9999}, //Not supported F9R 121, F9P 112, 113, 120, 130
+ {UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_UART1, UBX_RXM_RAWX, UBX_CLASS_RXM, 0, "UBX_RXM_RAWX", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_RXM_RLM_UART1, UBX_RXM_RLM, UBX_CLASS_RXM, 0, "UBX_RXM_RLM", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_UART1, UBX_RXM_RTCM, UBX_CLASS_RXM, 0, "UBX_RXM_RTCM", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_UART1, UBX_RXM_SFRBX, UBX_CLASS_RXM, 0, "UBX_RXM_SFRBX", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_RXM_SPARTN_UART1, UBX_RXM_SPARTN, UBX_CLASS_RXM, 0, "UBX_RXM_SPARTN", 0, 9999,
+ 121}, // Not supported F9R 120 or F9P 112, 113, 120, 130
+ //{UBLOX_CFG_MSGOUT_UBX_RXM_SVSI_UART1, UBX_RXM_SVSI, UBX_CLASS_RXM, 0, "UBX_RXM_SVSI", 0, 9999, 9999}, //Not
+ // supported F9R 121 or F9P 112, 113, 120, 130, 132 {UBLOX_CFG_MSGOUT_UBX_RXM_TM_UART1, UBX_RXM_TM,
+ // UBX_CLASS_RXM, 0, "UBX_RXM_TM", 0, 9999, 9999}, //Not supported F9R 121 or F9P 112, 113, 120, 130, 132
+
+ // SEC
+ // No support F9P 112.
+
+ // TIM
+ //{UBLOX_CFG_MSGOUT_UBX_TIM_SVIN_UART1, UBX_TIM_SVIN, UBX_CLASS_TIM, 0, "UBX_TIM_SVIN", 0, 9999, 9999}, //Appears on
+ // F9P 132 but not supported
+ {UBLOX_CFG_MSGOUT_UBX_TIM_TM2_UART1, UBX_TIM_TM2, UBX_CLASS_TIM, 0, "UBX_TIM_TM2", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART1, UBX_TIM_TP, UBX_CLASS_TIM, 0, "UBX_TIM_TP", 0, 112, 120},
+ {UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_UART1, UBX_TIM_VRFY, UBX_CLASS_TIM, 0, "UBX_TIM_VRFY", 0, 112, 120},
+
+};
+
+#define MAX_UBX_MSG (sizeof(ubxMessages) / sizeof(ubxMsg))
+#define MAX_UBX_MSG_RTCM (12)
+
+#define MAX_SET_MESSAGES_RETRIES 5 // Try up to five times to set all the messages. Occasionally fails if set to 2
+
+// Struct to describe the necessary info for each UBX command
+// Each command will have a key, and minimum F9P/F9R versions that support that command
+typedef struct
+{
+ const uint32_t cmdKey;
+ const char cmdTextName[30];
+ const uint16_t f9pFirmwareVersionSupported; // The minimum version this message is supported. 0 = all versions. 9999
+ // = Not supported
+ const uint16_t f9rFirmwareVersionSupported;
+} ubxCmd;
+
+// Static array containing all the compatible commands
+const ubxCmd ubxCommands[] = {
+ {UBLOX_CFG_TMODE_MODE, "CFG_TMODE_MODE", 0, 9999}, // Survey mode is only available on ZED-F9P modules
+
+ //The F9R is unique WRT RTCM *output*. u-center can correctly enable/disable the RTCM output, but it cannot
+ //be set with setVal commands. Applies to HPS 120, 121, 130.
+
+ {UBLOX_CFG_UART1OUTPROT_RTCM3X, "CFG_UART1OUTPROT_RTCM3X", 0, 9999}, // F9R: RTCM output not supported
+ {UBLOX_CFG_UART1INPROT_SPARTN, "CFG_UART1INPROT_SPARTN", 120,
+ 121}, // Supported on F9P 120 and up. F9R: SPARTN supported starting HPS 121
+
+ {UBLOX_CFG_UART2OUTPROT_RTCM3X, "CFG_UART2OUTPROT_RTCM3X", 0, 9999}, // F9R: RTCM output not supported
+ {UBLOX_CFG_UART2INPROT_SPARTN, "CFG_UART2INPROT_SPARTN", 120, 121}, // F9R: SPARTN supported starting HPS 121
+
+ {UBLOX_CFG_SPIOUTPROT_RTCM3X, "CFG_SPIOUTPROT_RTCM3X", 0, 9999}, // F9R: RTCM output not supported
+ {UBLOX_CFG_SPIINPROT_SPARTN, "CFG_SPIINPROT_SPARTN", 120, 121}, // F9R: SPARTN supported starting HPS 121
+
+ {UBLOX_CFG_I2COUTPROT_RTCM3X, "CFG_I2COUTPROT_RTCM3X", 0, 9999}, // F9R: RTCM output not supported
+ {UBLOX_CFG_I2CINPROT_SPARTN, "CFG_I2CINPROT_SPARTN", 120, 121}, // F9R: SPARTN supported starting HPS 121
+
+ {UBLOX_CFG_USBOUTPROT_RTCM3X, "CFG_USBOUTPROT_RTCM3X", 0, 9999}, // F9R: RTCM output not supported
+ {UBLOX_CFG_USBINPROT_SPARTN, "CFG_USBINPROT_SPARTN", 120, 121}, // F9R: SPARTN supported starting HPS 121
+
+ {UBLOX_CFG_NAV2_OUT_ENABLED, "CFG_NAV2_OUT_ENABLED", 130,
+ 130}, // Supported on F9P 130 and up. Supported on F9R 130 and up.
+ {UBLOX_CFG_NAVSPG_INFIL_MINCNO, "CFG_NAVSPG_INFIL_MINCNO", 0, 0}, //
+};
+
+#define MAX_UBX_CMD (sizeof(ubxCommands) / sizeof(ubxCmd))
+
+// Regional Support
+// Do we want the user to be able to specify which region they are in?
+// Or do we want to figure it out based on position?
+// If we define a simple 'square' area for each region, we can do both.
+// Note: the best way to obtain the L-Band frequencies would be from the MQTT /pp/frequencies/Lb topic.
+// But it is easier to record them here, in case we don't have access to MQTT...
+// Note: the key distribution topic is provided during ZTP. We don't need to record it here.
+
+typedef struct
+{
+ const double latNorth; // Degrees
+ const double latSouth; // Degrees
+ const double lonEast; // Degrees
+ const double lonWest; // Degrees
+} Regional_Area;
+
+typedef struct
+{
+ const char *name; // As defined in the ZTP subscriptions description: EU, US, KR, AU, Japan
+ const char *topicRegion; // As used in the corrections topic path
+ const Regional_Area area;
+ const uint32_t frequency; // L-Band frequency, Hz, if supported. 0 if not supported
+} Regional_Information;
+
+const Regional_Information Regional_Information_Table[] =
+{
+ { "US", "us", { 50.0, 25.0, -60.0, -125.0}, 1556290000 },
+ { "EU", "eu", { 72.0, 36.0, 32.0, -11.0}, 1545260000 },
+ // Note: we only include regions with L-Band coverage. AU, KR and Japan are not included here.
};
+const int numRegionalAreas = sizeof(Regional_Information_Table) / sizeof(Regional_Information_Table[0]);
+
+// This is all the settings that can be set on RTK Surveyor. It's recorded to NVM and the config file.
+typedef struct
+{
+ int sizeOfSettings = 0; // sizeOfSettings **must** be the first entry and must be int
+ int rtkIdentifier = RTK_IDENTIFIER; // rtkIdentifier **must** be the second entry
+ bool printDebugMessages = false;
+ bool enableSD = true;
+ bool enableDisplay = true;
+ int maxLogTime_minutes = 60 * 24; // Default to 24 hours
+ int observationSeconds = 60; // Default survey in time of 60 seconds
+ float observationPositionAccuracy = 5.0; // Default survey in pos accy of 5m
+ bool fixedBase = false; // Use survey-in by default
+ bool fixedBaseCoordinateType = COORD_TYPE_ECEF;
+ double fixedEcefX = -1280206.568;
+ double fixedEcefY = -4716804.403;
+ double fixedEcefZ = 4086665.484;
+ double fixedLat = 40.09029479;
+ double fixedLong = -105.18505761;
+ double fixedAltitude = 1560.089;
+ uint32_t dataPortBaud =
+ (115200 * 2); // Default to 230400bps. This limits GNSS fixes at 4Hz but allows SD buffer to be reduced to 6k.
+ uint32_t radioPortBaud = 57600; // Default to 57600bps to support connection to SiK1000 type telemetry radios
+ float surveyInStartingAccuracy = 1.0; // Wait for 1m horizontal positional accuracy before starting survey in
+ uint16_t measurementRate = 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 enableI2Cdebug = false; // Turn on to display GNSS library debug messages
+ bool enableHeapReport = false; // Turn on to display free heap
+ bool enableTaskReports = false; // Turn on to display task high water marks
+ muxConnectionType_e dataPortChannel = MUX_UBLOX_NMEA; // Mux default to ublox UART1
+ uint16_t spiFrequency = 16; // By default, use 16MHz SPI
+ bool enableLogging = true; // If an SD card is present, log default sentences
+ bool enableARPLogging = false; // Log the Antenna Reference Position from RTCM 1005/1006 - if available
+ uint16_t ARPLoggingInterval_s = 10; // Log the ARP every 10 seconds - if available
+ uint16_t sppRxQueueSize = 512 * 4;
+ uint16_t sppTxQueueSize = 32;
+ uint8_t dynamicModel = DYN_MODEL_PORTABLE;
+ SystemState lastState = STATE_NOT_SET; // Start unit in last known state
+ bool enableSensorFusion = false; // If IMU is available, avoid using it unless user specifically selects automotive
+ bool autoIMUmountAlignment = true; // Allows unit to automatically establish device orientation in vehicle
+ bool enableResetDisplay = false;
+ uint8_t resetCount = 0;
+ bool enableExternalPulse = true; // Send pulse once lock is achieved
+ uint64_t externalPulseTimeBetweenPulse_us = 1000000; // us between pulses, max of 60s = 60 * 1000 * 1000
+ uint64_t externalPulseLength_us = 100000; // us length of pulse, max of 60s = 60 * 1000 * 1000
+ pulseEdgeType_e externalPulsePolarity = PULSE_RISING_EDGE; // Pulse rises for pulse length, then falls
+ bool enableExternalHardwareEventLogging = false; // Log when INT/TM2 pin goes low
+ bool enableMarksFile = false; // Log marks to the marks file
+ bool enableUART2UBXIn = false; // UBX Protocol In on UART2
+
+ uint8_t ubxMessageRates[MAX_UBX_MSG] = {254}; // Mark first record with key so defaults will be applied.
+
+ // Constellations monitored/used for fix
+ ubxConstellation ubxConstellations[MAX_CONSTELLATIONS] = {
+ {UBLOX_CFG_SIGNAL_GPS_ENA, SFE_UBLOX_GNSS_ID_GPS, true, "GPS"},
+ {UBLOX_CFG_SIGNAL_SBAS_ENA, SFE_UBLOX_GNSS_ID_SBAS, true, "SBAS"},
+ {UBLOX_CFG_SIGNAL_GAL_ENA, SFE_UBLOX_GNSS_ID_GALILEO, true, "Galileo"},
+ {UBLOX_CFG_SIGNAL_BDS_ENA, SFE_UBLOX_GNSS_ID_BEIDOU, true, "BeiDou"},
+ //{UBLOX_CFG_SIGNAL_QZSS_ENA, SFE_UBLOX_GNSS_ID_IMES, false, "IMES"}, //Not yet supported? Config key does not
+ // exist?
+ {UBLOX_CFG_SIGNAL_QZSS_ENA, SFE_UBLOX_GNSS_ID_QZSS, true, "QZSS"},
+ {UBLOX_CFG_SIGNAL_GLO_ENA, SFE_UBLOX_GNSS_ID_GLONASS, true, "GLONASS"},
+ };
+
+ int maxLogLength_minutes = 60 * 24; // Default to 24 hours
+ char profileName[50] = "";
+
+ int16_t serialTimeoutGNSS = 1; // In ms - used during SerialGNSS.begin. Number of ms to pass of no data before
+ // hardware serial reports data available.
+
+ // Point Perfect
+ char pointPerfectDeviceProfileToken[40] = "";
+ bool enablePointPerfectCorrections = true;
+ bool autoKeyRenewal = true; // Attempt to get keys if we get under 28 days from the expiration date
+ char pointPerfectClientID[50] = "";
+ char pointPerfectBrokerHost[50] = ""; // pp.services.u-blox.com
+ char pointPerfectLBandTopic[20] = ""; // /pp/ubx/0236/Lb
+
+ char pointPerfectCurrentKey[33] = ""; // 32 hexadecimal digits = 128 bits = 16 Bytes
+ uint64_t pointPerfectCurrentKeyDuration = 0;
+ uint64_t pointPerfectCurrentKeyStart = 0;
+
+ char pointPerfectNextKey[33] = "";
+ uint64_t pointPerfectNextKeyDuration = 0;
+ uint64_t pointPerfectNextKeyStart = 0;
+
+ uint64_t lastKeyAttempt = 0; // Epoch time of last attempt at obtaining keys
+ bool updateZEDSettings = true; // When in doubt, update the ZED with current settings
+
+ bool debugPpCertificate = false; // Debug Point Perfect certificate management
+
+ // Time Zone - Default to UTC
+ int8_t timeZoneHours = 0;
+ int8_t timeZoneMinutes = 0;
+ int8_t timeZoneSeconds = 0;
-//This is all the settings that can be set on RTK Surveyor. It's recorded to NVM and the config file.
-struct struct_settings {
- int sizeOfSettings = 0; //sizeOfSettings **must** be the first entry and must be int
- int rtkIdentifier = RTK_IDENTIFIER; // rtkIdentifier **must** be the second entry
- bool printDebugMessages = false;
- bool enableSD = true;
- bool enableDisplay = true;
- int maxLogTime_minutes = 60 * 10; //Default to 10 hours
- int observationSeconds = 60; //Default survey in time of 60 seconds
- float observationPositionAccuracy = 5.0; //Default survey in pos accy of 5m
- bool fixedBase = false; //Use survey-in by default
- bool fixedBaseCoordinateType = COORD_TYPE_ECEF;
- double fixedEcefX = 0.0;
- double fixedEcefY = 0.0;
- double fixedEcefZ = 0.0;
- double fixedLat = 0.0;
- double fixedLong = 0.0;
- double fixedAltitude = 0.0;
- uint32_t dataPortBaud = 460800; //Default to 460800bps to support >10Hz update rates
- uint32_t radioPortBaud = 57600; //Default to 57600bps to support connection to SiK1000 radios
- bool enableNtripServer = false;
- char casterHost[50] = "rtk2go.com"; //It's free...
- uint16_t casterPort = 2101;
- char mountPoint[50] = "bldr_dwntwn2";
- char mountPointPW[50] = "WR5wRo4H";
- char wifiSSID[50] = "TRex";
- char wifiPW[50] = "parachutes";
- float surveyInStartingAccuracy = 1.0; //Wait for 1m horizontal positional accuracy before starting survey in
- uint16_t measurementRate = 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).
- ubxMsg ubxMessages; //Report rates for all known messages
- bool enableI2Cdebug = false; //Turn on to display GNSS library debug messages
- bool enableHeapReport = false; //Turn on to display free heap
- bool enableTaskReports = false; //Turn on to display task high water marks
- muxConnectionType_e dataPortChannel = MUX_UBLOX_NMEA; //Mux default to ublox UART1
- uint16_t spiFrequency = 8; //By default, use 8MHz SPI
- bool enableLogging = true; //If an SD card is present, log default sentences
- uint16_t sppRxQueueSize = 2048;
- uint16_t sppTxQueueSize = 512;
- uint8_t dynamicModel = DYN_MODEL_PORTABLE;
- SystemState lastState = STATE_ROVER_NOT_STARTED; //For Express, start unit in last known state
- bool throttleDuringSPPCongestion = true;
- ubxConstellation ubxConstellations; //Constellations monitored/used for fix
-
-} settings;
-
-//These are the devices on board RTK Surveyor that may be on or offline.
-struct struct_online {
- bool microSD = false;
- bool display = false;
- bool gnss = false;
- bool logging = false;
- bool serialOutput = false;
- bool eeprom = false;
- bool rtc = false;
- bool battery = false;
- bool accelerometer = false;
+ // Debug settings
+ bool enablePrintState = false;
+ bool enablePrintPosition = false;
+ bool enablePrintIdleTime = false;
+ bool enablePrintBatteryMessages = true;
+ bool enablePrintRoverAccuracy = true;
+ bool enablePrintBadMessages = false;
+ bool enablePrintLogFileMessages = false;
+ bool enablePrintLogFileStatus = true;
+ bool enablePrintRingBufferOffsets = false;
+ bool enablePrintStates = true;
+ bool enablePrintDuplicateStates = false;
+ bool enablePrintRtcSync = false;
+ RadioType_e radioType = RADIO_EXTERNAL;
+ uint8_t espnowPeers[5][6] = {0}; // Max of 5 peers. Contains the MAC addresses (6 bytes) of paired units
+ uint8_t espnowPeerCount = 0;
+ bool enableRtcmMessageChecking = false;
+ BluetoothRadioType_e bluetoothRadioType = BLUETOOTH_RADIO_SPP;
+ bool runLogTest = false; // When set to true, device will create a series of test logs
+ bool espnowBroadcast = true; // When true, overrides peers and sends all data via broadcast
+ int16_t antennaHeight = 0; // in mm
+ float antennaReferencePoint = 0.0; // in mm
+ bool echoUserInput = true;
+ int uartReceiveBufferSize = 1024 * 2; // This buffer is filled automatically as the UART receives characters.
+ int gnssHandlerBufferSize =
+ 1024 * 4; // This buffer is filled from the UART receive buffer, and is then written to SD
+
+ bool enablePrintBufferOverrun = false;
+ bool enablePrintSDBuffers = false;
+ PeriodicDisplay_t periodicDisplay = (PeriodicDisplay_t)0; // Turn off all periodic debug displays by default.
+ uint32_t periodicDisplayInterval = 15 * 1000;
+
+ uint32_t rebootSeconds = (uint32_t)-1; // Disabled, reboots after uptime reaches this number of seconds
+ bool forceResetOnSDFail = false; // Set to true to reset system if SD is detected but fails to start.
+
+ uint8_t minElev = 10; // Minimum elevation (in deg) for a GNSS satellite to be used in NAV
+ uint8_t ubxMessageRatesBase[MAX_UBX_MSG_RTCM] = {
+ 254}; // Mark first record with key so defaults will be applied. Int value for each supported message - Report
+ // rates for RTCM Base. Default to u-blox recommended rates.
+ uint32_t imuYaw = 0; // User defined IMU mount yaw angle (0 to 36,000) CFG-SFIMU-IMU_MNTALG_YAW
+ int16_t imuPitch = 0; // User defined IMU mount pitch angle (-9000 to 9000) CFG-SFIMU-IMU_MNTALG_PITCH
+ int16_t imuRoll = 0; // User defined IMU mount roll angle (-18000 to 18000) CFG-SFIMU-IMU_MNTALG_ROLL
+ bool sfDisableWheelDirection = false; // CFG-SFODO-DIS_AUTODIRPINPOL
+ bool sfCombineWheelTicks = false; // CFG-SFODO-COMBINE_TICKS
+ uint8_t rateNavPrio = 0; // Output rate of priority nav mode message - CFG-RATE-NAV_PRIO
+ // CFG-SFIMU-AUTO_MNTALG_ENA 0 = autoIMUmountAlignment
+ bool sfUseSpeed = false; // CFG-SFODO-USE_SPEED
+
+ CoordinateInputType coordinateInputType = COORDINATE_INPUT_TYPE_DD; // Default DD.ddddddddd
+ uint16_t lbandFixTimeout_seconds = 180; // Number of seconds of no L-Band fix before resetting ZED
+ int16_t minCNO_F9P = 6; // Minimum satellite signal level for navigation. ZED-F9P default is 6 dBHz
+ int16_t minCNO_F9R = 20; // Minimum satellite signal level for navigation. ZED-F9R default is 20 dBHz
+ uint16_t serialGNSSRxFullThreshold = 50; // RX FIFO full interrupt. Max of ~128. See pinUART2Task().
+ uint8_t btReadTaskPriority = 1; // Read from BT SPP and Write to GNSS. 3 being the highest, and 0 being the lowest
+ uint8_t gnssReadTaskPriority =
+ 1; // Read from ZED-F9x and Write to circular buffer (SD, TCP, BT). 3 being the highest, and 0 being the lowest
+ uint8_t handleGnssDataTaskPriority = 1; // Read from the cicular buffer and dole out to end points (SD, TCP, BT).
+ uint8_t btReadTaskCore = 1; // Core where task should run, 0=core, 1=Arduino
+ uint8_t gnssReadTaskCore = 1; // Core where task should run, 0=core, 1=Arduino
+ uint8_t handleGnssDataTaskCore = 1; // Core where task should run, 0=core, 1=Arduino
+ uint8_t gnssUartInterruptsCore =
+ 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino
+ uint8_t bluetoothInterruptsCore =
+ 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino
+ uint8_t i2cInterruptsCore = 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino
+ uint32_t shutdownNoChargeTimeout_s = 0; // If > 0, shut down unit after timeout if not charging
+ bool disableSetupButton = false; // By default, allow setup through the overlay button(s)
+ bool useI2cForLbandCorrections =
+ true; // Set to false to stop I2C callback. Corrections will require direct ZED to NEO UART2 connections.
+ bool useI2cForLbandCorrectionsConfigured = false; // If a user sets useI2cForLbandCorrections, this goes true.
+
+ // Ethernet
+ bool enablePrintEthernetDiag = false;
+ bool ethernetDHCP = true;
+ IPAddress ethernetIP = {192, 168, 0, 123};
+ IPAddress ethernetDNS = {194, 168, 4, 100};
+ IPAddress ethernetGateway = {192, 168, 0, 1};
+ IPAddress ethernetSubnet = {255, 255, 255, 0};
+ uint16_t httpPort = 80;
+
+ // WiFi
+ bool debugWifiState = false;
+ bool wifiConfigOverAP = true; // Configure device over Access Point or have it connect to WiFi
+ WiFiNetwork wifiNetworks[MAX_WIFI_NETWORKS] = {
+ {"", ""},
+ {"", ""},
+ {"", ""},
+ {"", ""},
+ };
+
+ // Network layer
+ uint8_t defaultNetworkType = NETWORK_TYPE_USE_DEFAULT;
+ bool debugNetworkLayer = false; // Enable debugging of the network layer
+ bool enableNetworkFailover = true; // Enable failover between Ethernet / WiFi
+ bool printNetworkStatus = true; // Print network status (delays, failovers, IP address)
+
+ // Multicast DNS Server
+ bool mdnsEnable = true; // Allows locating of device from browser address 'rtk.local'
+
+ // NTP
+ bool debugNtp = false;
+ uint16_t ethernetNtpPort = 123;
+ bool enableNTPFile = false; // Log NTP requests to file
+ uint8_t ntpPollExponent = 6; // NTPpacket::defaultPollExponent 2^6 = 64 seconds
+ int8_t ntpPrecision = -20; // NTPpacket::defaultPrecision 2^-20 = 0.95us
+ uint32_t ntpRootDelay = 0; // NTPpacket::defaultRootDelay = 0. ntpRootDelay is defined in microseconds.
+ // ntpProcessOneRequest will convert it to seconds and fraction.
+ uint32_t ntpRootDispersion =
+ 1000; // NTPpacket::defaultRootDispersion 1007us = 2^-16 * 66. ntpRootDispersion is defined in microseconds.
+ // ntpProcessOneRequest will convert it to seconds and fraction.
+ char ntpReferenceId[5] = {'G', 'P', 'S', 0,
+ 0}; // NTPpacket::defaultReferenceId. Ref ID is 4 chars. Add one extra for a NULL.
+
+ // NTRIP Client
+ bool debugNtripClientRtcm = false;
+ bool debugNtripClientState = false;
+ bool enableNtripClient = false;
+ char ntripClient_CasterHost[50] = "rtk2go.com"; // It's free...
+ uint16_t ntripClient_CasterPort = 2101;
+ char ntripClient_CasterUser[50] =
+ "test@test.com"; // Some free casters require auth. User must provide their own email address to use RTK2Go
+ char ntripClient_CasterUserPW[50] = "";
+ char ntripClient_MountPoint[50] = "bldr_SparkFun1";
+ char ntripClient_MountPointPW[50] = "";
+ bool ntripClient_TransmitGGA = true;
+
+ // NTRIP Server
+ bool debugNtripServerRtcm = false;
+ bool debugNtripServerState = false;
+ bool enableNtripServer = false;
+ bool ntripServer_StartAtSurveyIn = false; // true = Start WiFi instead of Bluetooth at Survey-In
+ char ntripServer_CasterHost[NTRIP_SERVER_MAX][50] = // It's free...
+ {
+ "rtk2go.com",
+ "",
+ };
+ uint16_t ntripServer_CasterPort[NTRIP_SERVER_MAX] =
+ {
+ 2101,
+ 0,
+ };
+ char ntripServer_CasterUser[NTRIP_SERVER_MAX][50] =
+ {
+ "test@test.com" // Some free casters require auth. User must provide their own email address to use RTK2Go
+ "",
+ };
+ char ntripServer_CasterUserPW[NTRIP_SERVER_MAX][50] =
+ {
+ "",
+ "",
+ };
+ char ntripServer_MountPoint[NTRIP_SERVER_MAX][50] =
+ {
+ "bldr_dwntwn2", // NTRIP Server
+ "",
+ };
+ char ntripServer_MountPointPW[NTRIP_SERVER_MAX][50] =
+ {
+ "WR5wRo4H",
+ "",
+ };
+
+ // TCP Client
+ bool debugPvtClient = false;
+ bool enablePvtClient = false;
+ uint16_t pvtClientPort = 2948; // PVT client port. 2948 is GPS Daemon: http://tcp-udp-ports.com/port-2948.htm
+ char pvtClientHost[50] = "";
+
+ // TCP Server
+ bool debugPvtServer = false;
+ bool enablePvtServer = false;
+ uint16_t pvtServerPort = 2948; // PVT server port, 2948 is GPS Daemon: http://tcp-udp-ports.com/port-2948.htm
+
+ // UDP Server
+ bool debugPvtUdpServer = false;
+ bool enablePvtUdpServer = false;
+ uint16_t pvtUdpServerPort =
+ 10110; // https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=nmea
+
+ uint8_t rtcmTimeoutBeforeUsingLBand_s =
+ 10; // If 10s have passed without RTCM, enable PMP corrections over L-Band if available
+
+ // Automatic Firmware Update
+ bool debugFirmwareUpdate = false;
+ bool enableAutoFirmwareUpdate = false;
+ uint32_t autoFirmwareCheckMinutes = 24 * 60;
+
+ bool debugLBand = false;
+ bool enableCaptivePortal = true;
+ bool enableZedUsb = true; //Can be used to disable ZED USB config
+
+ bool debugWiFiConfig = false;
+
+ int geographicRegion = 0; // Default to US - first entry in Regional_Information_Table
+
+ // Add new settings above <------------------------------------------------------------>
+
+} Settings;
+Settings settings;
+
+// Monitor which devices on the device are on or offline.
+struct struct_online
+{
+ bool microSD = false;
+ bool display = false;
+ bool gnss = false;
+ bool logging = false;
+ bool serialOutput = false;
+ bool fs = false;
+ bool rtc = false;
+ bool battery = false;
+ bool accelerometer = false;
+ bool ntripClient = false;
+ bool ntripServer[NTRIP_SERVER_MAX] = {false, false};
+ bool lband = false;
+ bool lbandCorrections = false;
+ bool i2c = false;
+ bool pvtClient = false;
+ bool pvtServer = false;
+ bool pvtUdpServer = false;
+ ethernetStatus_e ethernetStatus = ETH_NOT_STARTED;
+ bool NTPServer = false; // EthernetUDP
+ bool otaFirmwareUpdate = false;
} online;
+
+#ifdef COMPILE_WIFI
+#ifdef COMPILE_L_BAND
+// AWS certificate for PointPerfect API
+static const char *AWS_PUBLIC_CERT = R"=====(
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
+ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
+9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
+IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
+VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
+93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
+jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
+A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
+U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
+N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
+o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
+5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
+rqXRfboQnoZsG4q5WTP468SQvvG5
+-----END CERTIFICATE-----
+)=====";
+#endif // COMPILE_L_BAND
+#endif // COMPILE_WIFI
+#endif // __SETTINGS_H__
diff --git a/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAddress.cpp b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAddress.cpp
new file mode 100644
index 000000000..7ef1eb1a8
--- /dev/null
+++ b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAddress.cpp
@@ -0,0 +1,96 @@
+/*
+ * BTAddress.cpp
+ *
+ * Created on: Jul 2, 2017
+ * Author: kolban
+ * Ported on: Feb 5, 2021
+ * Author: Thomas M. (ArcticSnowSky)
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
+
+#include "BTAddress.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef ARDUINO_ARCH_ESP32
+#include "esp32-hal-log.h"
+#endif
+
+
+/**
+ * @brief Create an address from the native ESP32 representation.
+ * @param [in] address The native representation.
+ */
+BTAddress::BTAddress(esp_bd_addr_t address) {
+ memcpy(m_address, address, ESP_BD_ADDR_LEN);
+} // BTAddress
+
+
+/**
+ * @brief Create an address from a hex string
+ *
+ * A hex string is of the format:
+ * ```
+ * 00:00:00:00:00:00
+ * ```
+ * which is 17 characters in length.
+ *
+ * @param [in] stringAddress The hex representation of the address.
+ */
+BTAddress::BTAddress(std::string stringAddress) {
+ if (stringAddress.length() != 17) return;
+
+ int data[6];
+ sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]);
+ m_address[0] = (uint8_t) data[0];
+ m_address[1] = (uint8_t) data[1];
+ m_address[2] = (uint8_t) data[2];
+ m_address[3] = (uint8_t) data[3];
+ m_address[4] = (uint8_t) data[4];
+ m_address[5] = (uint8_t) data[5];
+} // BTAddress
+
+
+/**
+ * @brief Determine if this address equals another.
+ * @param [in] otherAddress The other address to compare against.
+ * @return True if the addresses are equal.
+ */
+bool BTAddress::equals(BTAddress otherAddress) {
+ return memcmp(otherAddress.getNative(), m_address, 6) == 0;
+} // equals
+
+
+/**
+ * @brief Return the native representation of the address.
+ * @return The native representation of the address.
+ */
+esp_bd_addr_t *BTAddress::getNative() {
+ return &m_address;
+} // getNative
+
+
+/**
+ * @brief Convert a BT address to a string.
+ *
+ * A string representation of an address is in the format:
+ *
+ * ```
+ * xx:xx:xx:xx:xx:xx
+ * ```
+ *
+ * @return The string representation of the address.
+ */
+std::string BTAddress::toString() {
+ auto size = 18;
+ char *res = (char*)malloc(size);
+ snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[0], m_address[1], m_address[2], m_address[3], m_address[4], m_address[5]);
+ std::string ret(res);
+ free(res);
+ return ret;
+} // toString
+#endif
diff --git a/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAddress.h b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAddress.h
new file mode 100644
index 000000000..6213d01fd
--- /dev/null
+++ b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAddress.h
@@ -0,0 +1,36 @@
+/*
+ * BTAddress.h
+ *
+ * Created on: Jul 2, 2017
+ * Author: kolban
+ * Ported on: Feb 5, 2021
+ * Author: Thomas M. (ArcticSnowSky)
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BTADDRESS_H_
+#define COMPONENTS_CPP_UTILS_BTADDRESS_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
+#include // ESP32 BT
+#include
+
+
+/**
+ * @brief A %BT device address.
+ *
+ * Every %BT device has a unique address which can be used to identify it and form connections.
+ */
+class BTAddress {
+public:
+ BTAddress(esp_bd_addr_t address);
+ BTAddress(std::string stringAddress);
+ bool equals(BTAddress otherAddress);
+ esp_bd_addr_t* getNative();
+ std::string toString();
+
+private:
+ esp_bd_addr_t m_address;
+};
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BTADDRESS_H_ */
diff --git a/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAdvertisedDevice.h b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAdvertisedDevice.h
new file mode 100644
index 000000000..07e93622e
--- /dev/null
+++ b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAdvertisedDevice.h
@@ -0,0 +1,65 @@
+/*
+ * BTAdvertisedDevice.h
+ *
+ * Created on: Feb 5, 2021
+ * Author: Thomas M. (ArcticSnowSky)
+ */
+
+#ifndef __BTADVERTISEDDEVICE_H__
+#define __BTADVERTISEDDEVICE_H__
+
+#include "BTAddress.h"
+
+
+class BTAdvertisedDevice {
+public:
+ virtual ~BTAdvertisedDevice() = default;
+
+ virtual BTAddress getAddress();
+ virtual uint32_t getCOD();
+ virtual std::string getName();
+ virtual int8_t getRSSI();
+
+
+ virtual bool haveCOD();
+ virtual bool haveName();
+ virtual bool haveRSSI();
+
+ virtual std::string toString();
+};
+
+class BTAdvertisedDeviceSet : public virtual BTAdvertisedDevice {
+public:
+ BTAdvertisedDeviceSet();
+ //~BTAdvertisedDeviceSet() = default;
+
+
+ BTAddress getAddress();
+ uint32_t getCOD();
+ std::string getName();
+ int8_t getRSSI();
+
+
+ bool haveCOD();
+ bool haveName();
+ bool haveRSSI();
+
+ std::string toString();
+
+ void setAddress(BTAddress address);
+ void setCOD(uint32_t cod);
+ void setName(std::string name);
+ void setRSSI(int8_t rssi);
+
+ bool m_haveCOD;
+ bool m_haveName;
+ bool m_haveRSSI;
+
+
+ BTAddress m_address = BTAddress((uint8_t*)"\0\0\0\0\0\0");
+ uint32_t m_cod;
+ std::string m_name;
+ int8_t m_rssi;
+};
+
+#endif
\ No newline at end of file
diff --git a/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAdvertisedDeviceSet.cpp b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAdvertisedDeviceSet.cpp
new file mode 100644
index 000000000..c8f28e9c3
--- /dev/null
+++ b/Firmware/RTK_Surveyor/src/BluetoothSerial/BTAdvertisedDeviceSet.cpp
@@ -0,0 +1,78 @@
+/*
+ * BTAdvertisedDeviceSet.cpp
+ *
+ * Created on: Feb 5, 2021
+ * Author: Thomas M. (ArcticSnowSky)
+ */
+
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
+
+//#include