From 6de3458dfcc088ddd7552d434bcbf5af091bd8fc Mon Sep 17 00:00:00 2001 From: xin Date: Tue, 8 Jul 2025 08:54:35 +0800 Subject: [PATCH] v2.9 --- default.csv | 5 + lib/Adafruit BusIO/.github/ISSUE_TEMPLATE.md | 46 + .../.github/PULL_REQUEST_TEMPLATE.md | 26 + .../.github/workflows/githubci.yml | 33 + lib/Adafruit BusIO/.piopm | 1 + .../Adafruit_BusIO_Register.cpp | 365 ++ lib/Adafruit BusIO/Adafruit_BusIO_Register.h | 105 + lib/Adafruit BusIO/Adafruit_I2CDevice.cpp | 317 ++ lib/Adafruit BusIO/Adafruit_I2CDevice.h | 36 + lib/Adafruit BusIO/Adafruit_I2CRegister.h | 10 + lib/Adafruit BusIO/Adafruit_SPIDevice.cpp | 508 ++ lib/Adafruit BusIO/Adafruit_SPIDevice.h | 143 + lib/Adafruit BusIO/CMakeLists.txt | 11 + lib/Adafruit BusIO/LICENSE | 21 + lib/Adafruit BusIO/README.md | 8 + lib/Adafruit BusIO/component.mk | 1 + .../i2c_address_detect/i2c_address_detect.ino | 21 + .../examples/i2c_readwrite/i2c_readwrite.ino | 41 + .../examples/i2c_registers/i2c_registers.ino | 38 + .../i2corspi_register/i2corspi_register.ino | 38 + .../examples/spi_modetest/spi_modetest.ino | 29 + .../examples/spi_readwrite/spi_readwrite.ino | 39 + .../spi_register_bits/spi_register_bits.ino | 192 + .../examples/spi_registers/spi_registers.ino | 34 + lib/Adafruit BusIO/library.properties | 9 + .../.github/ISSUE_TEMPLATE.md | 46 + .../.github/PULL_REQUEST_TEMPLATE.md | 26 + .../.github/workflows/githubci.yml | 32 + lib/Adafruit MLX90614 Library/.piopm | 1 + .../Adafruit_MLX90614.cpp | 178 + .../Adafruit_MLX90614.h | 68 + lib/Adafruit MLX90614 Library/README.md | 51 + .../code-of-conduct.md | 127 + .../mlx_set_emissivity/mlx_set_emissivity.ino | 47 + .../examples/mlxtest/mlxtest.ino | 46 + .../library.properties | 10 + lib/Adafruit MLX90614 Library/license.txt | 26 + lib/ArduinoHttpClient/.gitignore | 5 + lib/ArduinoHttpClient/.piopm | 1 + lib/ArduinoHttpClient/CHANGELOG.md | 33 + lib/ArduinoHttpClient/README.md | 25 + .../examples/BasicAuthGet/BasicAuthGet.ino | 66 + .../examples/BasicAuthGet/arduino_secrets.h | 3 + .../examples/CustomHeader/CustomHeader.ino | 91 + .../examples/CustomHeader/arduino_secrets.h | 3 + .../examples/DweetGet/DweetGet.ino | 103 + .../examples/DweetGet/arduino_secrets.h | 3 + .../examples/DweetPost/DweetPost.ino | 79 + .../examples/DweetPost/arduino_secrets.h | 3 + .../examples/HueBlink/HueBlink.ino | 98 + .../examples/HueBlink/arduino_secrets.h | 3 + .../PostWithHeaders/PostWithHeaders.ino | 77 + .../PostWithHeaders/arduino_secrets.h | 3 + .../examples/SimpleDelete/SimpleDelete.ino | 68 + .../examples/SimpleDelete/arduino_secrets.h | 3 + .../examples/SimpleGet/SimpleGet.ino | 62 + .../examples/SimpleGet/arduino_secrets.h | 3 + .../SimpleHttpExample/SimpleHttpExample.ino | 133 + .../SimpleHttpExample/arduino_secrets.h | 3 + .../examples/SimplePost/SimplePost.ino | 66 + .../examples/SimplePost/arduino_secrets.h | 3 + .../examples/SimplePut/SimplePut.ino | 66 + .../examples/SimplePut/arduino_secrets.h | 3 + .../SimpleWebSocket/SimpleWebSocket.ino | 80 + .../SimpleWebSocket/arduino_secrets.h | 3 + .../examples/node_test_server/package.json | 13 + lib/ArduinoHttpClient/keywords.txt | 67 + lib/ArduinoHttpClient/library.json | 12 + lib/ArduinoHttpClient/library.properties | 10 + lib/ArduinoHttpClient/src/ArduinoHttpClient.h | 12 + lib/ArduinoHttpClient/src/HttpClient.cpp | 867 +++ lib/ArduinoHttpClient/src/HttpClient.h | 392 ++ lib/ArduinoHttpClient/src/URLEncoder.cpp | 53 + lib/ArduinoHttpClient/src/URLEncoder.h | 25 + lib/ArduinoHttpClient/src/WebSocketClient.cpp | 372 ++ lib/ArduinoHttpClient/src/WebSocketClient.h | 99 + lib/ArduinoHttpClient/src/b64.cpp | 72 + lib/ArduinoHttpClient/src/b64.h | 6 + lib/DallasTemperature/.arduino-ci.yml | 15 + lib/DallasTemperature/.gitattributes | 1 + .../.github/workflows/arduino-lint.yml | 13 + .../.github/workflows/arduino_test_runner.yml | 17 + .../.github/workflows/jsoncheck.yml | 18 + lib/DallasTemperature/.gitignore | 16 + lib/DallasTemperature/.piopm | 1 + lib/DallasTemperature/DallasTemperature.cpp | 1122 ++++ lib/DallasTemperature/DallasTemperature.h | 346 ++ lib/DallasTemperature/README.md | 80 + .../examples/Alarm/Alarm.ino | 161 + .../examples/AlarmHandler/AlarmHandler.ino | 143 + .../ExternalPullup/ExternalPullup.ino | 35 + .../Multibus_simple/Multibus_simple.ino | 43 + .../examples/Multiple/Multiple.ino | 148 + .../SaveRecallScratchPad.ino | 106 + .../examples/SetUserData/SetUserData.ino | 47 + .../examples/Simple/Simple.ino | 51 + .../examples/Single/Single.ino | 121 + .../examples/Tester/Tester.ino | 129 + .../examples/Timing/Timing.ino | 77 + .../TwoPin_DS18B20/TwoPin_DS18B20.ino | 45 + .../examples/UserDataDemo/UserDataDemo.ino | 115 + .../UserDataWriteBatch/UserDataWriteBatch.ino | 107 + .../WaitForConversion/WaitForConversion.ino | 66 + .../WaitForConversion2/WaitForConversion2.ino | 80 + .../examples/oneWireSearch/oneWireSearch.ino | 67 + .../readPowerSupply/readPowerSupply.ino | 92 + lib/DallasTemperature/keywords.txt | 87 + lib/DallasTemperature/library.json | 38 + lib/DallasTemperature/library.properties | 10 + lib/EspSoftwareSerial/.gitignore | 33 + lib/EspSoftwareSerial/.piopm | 1 + lib/EspSoftwareSerial/LICENSE | 502 ++ lib/EspSoftwareSerial/README.md | 169 + .../examples/bitpattern/bitpattern.ino | 71 + .../examples/loopback/loopback.ino | 279 + .../examples/onewiretest/onewiretest.ino | 59 + .../examples/repeater/repeater.ino | 199 + .../examples/servoTester/servoTester.ino | 113 + .../examples/swsertest/swsertest.ino | 79 + lib/EspSoftwareSerial/keywords.txt | 43 + lib/EspSoftwareSerial/library.json | 26 + lib/EspSoftwareSerial/library.properties | 9 + lib/EspSoftwareSerial/src/SoftwareSerial.cpp | 679 +++ lib/EspSoftwareSerial/src/SoftwareSerial.h | 301 + .../src/circular_queue/Delegate.h | 2130 +++++++ .../src/circular_queue/MultiDelegate.h | 567 ++ .../src/circular_queue/circular_queue.h | 393 ++ .../src/circular_queue/circular_queue_mp.h | 200 + .../src/circular_queue/ghostl.h | 94 + lib/OneWire/.piopm | 1 + lib/OneWire/OneWire.cpp | 603 ++ lib/OneWire/OneWire.h | 182 + .../DS18x20_Temperature.ino | 112 + .../examples/DS2408_Switch/DS2408_Switch.ino | 74 + .../examples/DS250x_PROM/DS250x_PROM.ino | 75 + lib/OneWire/keywords.txt | 38 + lib/OneWire/library.json | 61 + lib/OneWire/library.properties | 10 + lib/OneWire/util/OneWire_direct_gpio.h | 518 ++ lib/OneWire/util/OneWire_direct_regtype.h | 59 + lib/PubSubClient/.gitignore | 5 + lib/PubSubClient/.piopm | 1 + lib/PubSubClient/.travis.yml | 7 + lib/PubSubClient/CHANGES.txt | 85 + lib/PubSubClient/LICENSE.txt | 20 + lib/PubSubClient/README.md | 50 + .../examples/mqtt_auth/mqtt_auth.ino | 43 + .../examples/mqtt_basic/mqtt_basic.ino | 77 + .../examples/mqtt_esp8266/mqtt_esp8266.ino | 129 + .../mqtt_large_message/mqtt_large_message.ino | 179 + .../mqtt_publish_in_callback.ino | 60 + .../mqtt_reconnect_nonblocking.ino | 67 + .../examples/mqtt_stream/mqtt_stream.ino | 57 + lib/PubSubClient/keywords.txt | 36 + lib/PubSubClient/library.json | 18 + lib/PubSubClient/library.properties | 9 + lib/PubSubClient/src/PubSubClient.cpp | 769 +++ lib/PubSubClient/src/PubSubClient.h | 184 + .../.gitattributes | 2 + .../.gitignore | 58 + lib/SparkFun u-blox Arduino Library/.piopm | 1 + .../CONTRIBUTING.md | 20 + .../ISSUE_TEMPLATE.md | 18 + .../LICENSE.md | 55 + lib/SparkFun u-blox Arduino Library/README.md | 163 + lib/SparkFun u-blox Arduino Library/Theory.md | 39 + .../Example1_calibrateSensor.ino | 66 + .../Example2_getIMUData.ino | 84 + .../Example3_getSensorStatus.ino | 97 + .../Example4_vehicleDynamics.ino | 82 + .../Example5_getHNRData.ino | 90 + .../Example6_getAutoHNRData.ino | 96 + .../Example10_AltitudeMSL.ino | 82 + .../Example1_FactoryDefaultviaI2C.ino | 58 + .../Example2_FactoryDefaultsviaSerial.ino | 95 + .../Example12_UseUart/Example12_UseUart.ino | 98 + .../Example1_AutoPVT/Example1_AutoPVT.ino | 141 + .../Example2_AutoPVT_ExplicitUpdate.ino | 103 + .../Example3_AssumeAutoPVTviaUart.ino | 79 + .../Example14_DebugOutput.ino | 93 + .../Example15_GetDateTime.ino | 107 + .../Example16_Nanosecond_MaxOutput.ino | 106 + .../Example16_PartialSecond_MaxOutput.ino | 116 + .../Example17_Geofence/Example17_Geofence.ino | 169 + .../Example18_PowerSaveMode.ino | 163 + .../Example19_DynamicModel.ino | 117 + .../Example1_BasicNMEARead.ino | 51 + .../Example20_SendCustomCommand.ino | 161 + .../Example21_ModuleInfo.ino | 183 + .../Example22_PowerOff/Example22_PowerOff.ino | 87 + .../Example2_NMEAParsing.ino | 83 + .../Example3_GetPosition.ino | 84 + .../Example4_FixType/Example4_FixType.ino | 94 + .../Example5_SpeedHeadingPrecision.ino | 83 + .../Example6_EnableNMEASentences.ino | 79 + .../Example7_OutputRate.ino | 91 + .../Example8_GetProtocolVersion.ino | 61 + .../Example9_ChangeI2CAddress.ino | 112 + .../Example1_EnableRTCM.ino | 77 + .../Example2_StartRTCMBase.ino | 153 + .../Example3_BaseWithLCD.ino | 185 + ...Example1_GetPositionAndTime_Series_6_7.ino | 109 + ...rkFun_Ublox_Arduino_Library_Series_6_7.cpp | 3477 ++++++++++++ ...parkFun_Ublox_Arduino_Library_Series_6_7.h | 932 ++++ ...10_GetHighPrecisionPositionAndAccuracy.ino | 154 + ...11_GetHighPrecisionPositionUsingDouble.ino | 146 + .../Example12_setStaticPosition.ino | 73 + .../Example13_autoHPPOSLLH.ino | 119 + .../Example14_NTRIPServer.ino | 274 + .../ZED-F9P/Example14_NTRIPServer/secrets.h | 7 + .../Example1_GetPositionAccuracy.ino | 77 + .../Example2_ValConfigurationMethod.ino | 60 + .../Example3_StartRTCMBase.ino | 176 + .../Example4_BaseWithLCD.ino | 200 + ...xample5_RelativePositioningInformation.ino | 161 + .../Example6_GetVal/Example6_GetVal.ino | 88 + .../Example7_SetVal/Example7_SetVal.ino | 76 + .../Example8_GetSetPortSettings.ino | 98 + .../Example9_multiSetVal.ino | 90 + .../CMakeLists.txt | 14 + .../nrf52840dk_nrf52840.overlay | 4 + .../prj.conf | 7 + .../src/SparkFun_Ublox_Zephyr_Interface.cpp | 100 + .../src/SparkFun_Ublox_Zephyr_Interface.h | 29 + .../src/SparkFun_Ublox_Zephyr_Library.cpp | 3492 ++++++++++++ .../src/SparkFun_Ublox_Zephyr_Library.h | 901 +++ .../src/main.c | 102 + .../img/Contributing.JPG | Bin 0 -> 62131 bytes .../keywords.txt | 272 + .../library.properties | 9 + .../src/SparkFun_Ublox_Arduino_Library.cpp | 4875 +++++++++++++++++ .../src/SparkFun_Ublox_Arduino_Library.h | 1098 ++++ .../src/u-blox_config_keys.h | 838 +++ lib/TinyGSM/.piopm | 1 + lib/TinyGSM/LICENSE | 165 + lib/TinyGSM/README.md | 417 ++ .../examples/AllFunctions/AllFunctions.ino | 488 ++ .../examples/BlynkClient/BlynkClient.ino | 110 + .../examples/FileDownload/FileDownload.ino | 356 ++ .../examples/HttpClient/HttpClient.ino | 261 + .../examples/HttpsClient/HttpsClient.ino | 257 + .../examples/MqttClient/MqttClient.ino | 290 + lib/TinyGSM/examples/WebClient/WebClient.ino | 244 + .../examples/example_dependencies.json | 41 + .../more/Hologram_Dash/Hologram_Dash.ino | 136 + .../examples/more/Industruino/Industruino.ino | 161 + .../COMODORSACertificationAuthority.h | 36 + .../more/SIM800_SslSetCert/DSTRootCAX3.der.h | 74 + .../more/SIM800_SslSetCert/DSTRootCAX3.h | 22 + .../SIM800_SslSetCert/SIM800_SslSetCert.ino | 96 + lib/TinyGSM/keywords.txt | 29 + lib/TinyGSM/library.json | 32 + lib/TinyGSM/library.properties | 10 + lib/TinyGSM/src/ArduinoCompat/Client.h | 47 + lib/TinyGSM/src/ArduinoCompat/IPAddress.h | 146 + lib/TinyGSM/src/TinyGSM.h | 6 + lib/TinyGSM/src/TinyGsmBattery.tpp | 98 + lib/TinyGSM/src/TinyGsmBluetooth.tpp | 57 + lib/TinyGSM/src/TinyGsmCalling.tpp | 90 + lib/TinyGSM/src/TinyGsmClient.h | 121 + lib/TinyGSM/src/TinyGsmClientA6.h | 580 ++ lib/TinyGSM/src/TinyGsmClientBG96.h | 727 +++ lib/TinyGSM/src/TinyGsmClientESP8266.h | 485 ++ lib/TinyGSM/src/TinyGsmClientM590.h | 473 ++ lib/TinyGSM/src/TinyGsmClientM95.h | 636 +++ lib/TinyGSM/src/TinyGsmClientMC60.h | 622 +++ lib/TinyGSM/src/TinyGsmClientSIM5360.h | 741 +++ lib/TinyGSM/src/TinyGsmClientSIM7000.h | 534 ++ lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h | 730 +++ lib/TinyGSM/src/TinyGsmClientSIM7080.h | 729 +++ lib/TinyGSM/src/TinyGsmClientSIM70xx.h | 459 ++ lib/TinyGSM/src/TinyGsmClientSIM7600.h | 866 +++ lib/TinyGSM/src/TinyGsmClientSIM800.h | 771 +++ lib/TinyGSM/src/TinyGsmClientSIM808.h | 168 + lib/TinyGSM/src/TinyGsmClientSaraR4.h | 900 +++ lib/TinyGSM/src/TinyGsmClientSequansMonarch.h | 733 +++ lib/TinyGSM/src/TinyGsmClientUBLOX.h | 854 +++ lib/TinyGSM/src/TinyGsmClientXBee.h | 1638 ++++++ lib/TinyGSM/src/TinyGsmCommon.h | 120 + lib/TinyGSM/src/TinyGsmFifo.h | 141 + lib/TinyGSM/src/TinyGsmGPRS.tpp | 171 + lib/TinyGSM/src/TinyGsmGPS.tpp | 82 + lib/TinyGSM/src/TinyGsmGSMLocation.tpp | 149 + lib/TinyGSM/src/TinyGsmModem.tpp | 354 ++ lib/TinyGSM/src/TinyGsmNTP.tpp | 92 + lib/TinyGSM/src/TinyGsmSMS.tpp | 224 + lib/TinyGSM/src/TinyGsmSSL.tpp | 71 + lib/TinyGSM/src/TinyGsmTCP.tpp | 364 ++ lib/TinyGSM/src/TinyGsmTemperature.tpp | 40 + lib/TinyGSM/src/TinyGsmTime.tpp | 106 + lib/TinyGSM/src/TinyGsmWifi.tpp | 49 + lib/Vector/.gitignore | 9 + lib/Vector/.piopm | 1 + lib/Vector/LICENSE | 32 + lib/Vector/README.org | 47 + lib/Vector/examples/PlatformIO/README.org | 45 + lib/Vector/examples/PlatformIO/include/README | 38 + lib/Vector/examples/PlatformIO/lib/README | 45 + lib/Vector/examples/PlatformIO/platformio.ini | 32 + lib/Vector/examples/PlatformIO/src/main.cpp | 45 + lib/Vector/examples/PlatformIO/test/README | 10 + .../examples/PlatformIO/test/test_vector.cpp | 60 + .../examples/VectorTester/VectorTester.ino | 100 + lib/Vector/library.json | 21 + lib/Vector/library.properties | 9 + lib/Vector/src/Vector.h | 99 + lib/Vector/src/Vector/Vector.cpp | 8 + lib/Vector/src/Vector/VectorDefinitions.h | 248 + lib/Vector/src/Vector/VectorIterator.h | 45 + lib/WebServer_ESP32_W5500/.codespellrc | 7 + .../.github/ISSUE_TEMPLATE/bug_report.md | 101 + .../.github/ISSUE_TEMPLATE/feature_request.md | 24 + .../.github/dependabot.yml | 10 + lib/WebServer_ESP32_W5500/.github/stale.yml | 31 + .../.github/workflows/auto-github-actions.yml | 12 + .../.github/workflows/report-size-deltas.yml | 16 + .../.github/workflows/spell-check.yml | 22 + lib/WebServer_ESP32_W5500/.gitignore | 32 + lib/WebServer_ESP32_W5500/.piopm | 1 + lib/WebServer_ESP32_W5500/CONTRIBUTING.md | 81 + lib/WebServer_ESP32_W5500/LICENSE | 674 +++ .../LibraryPatches/esp32/cores/esp32/Server.h | 35 + lib/WebServer_ESP32_W5500/README.md | 800 +++ lib/WebServer_ESP32_W5500/changelog.md | 42 + .../AdvancedWebServer/AdvancedWebServer.ino | 275 + .../examples/HelloServer/HelloServer.ino | 167 + .../examples/HelloServer2/HelloServer2.ino | 190 + .../examples/HttpBasicAuth/HttpBasicAuth.ino | 150 + .../MQTTClient_Auth/MQTTClient_Auth.ino | 245 + .../MQTTClient_Basic/MQTTClient_Basic.ino | 247 + .../MQTT_ThingStream/MQTT_ThingStream.ino | 293 + .../examples/PostServer/PostServer.ino | 215 + .../SimpleAuthentication.ino | 268 + .../examples/UdpNTPClient/UdpNTPClient.ino | 236 + .../UdpSendReceive/UdpSendReceive.ino | 237 + .../examples/WebClient/WebClient.ino | 166 + .../WebClientRepeating/WebClientRepeating.ino | 179 + .../examples/WebServer/WebServer.ino | 190 + .../multiFileProject/multiFileProject.cpp | 14 + .../multiFileProject/multiFileProject.h | 36 + .../multiFileProject/multiFileProject.ino | 37 + lib/WebServer_ESP32_W5500/library.json | 30 + lib/WebServer_ESP32_W5500/library.properties | 11 + .../pics/AdvancedWebServer.png | Bin 0 -> 20395 bytes lib/WebServer_ESP32_W5500/pics/W5500.png | Bin 0 -> 213873 bytes .../pics/W5500_small.png | Bin 0 -> 98333 bytes .../platformio/platformio.ini | 163 + .../src/WebServer_ESP32_W5500.h | 93 + .../src/WebServer_ESP32_W5500.hpp | 88 + .../src/WebServer_ESP32_W5500_Debug.h | 111 + .../src/WebServer_ESP32_W5500_Impl.h | 161 + .../src/w5500/esp32_w5500.cpp | 439 ++ .../src/w5500/esp32_w5500.h | 104 + .../src/w5500/esp_eth/esp_eth_mac_w5500.c | 958 ++++ .../src/w5500/esp_eth/esp_eth_phy_w5500.c | 379 ++ .../src/w5500/esp_eth/esp_eth_spi_w5500.c | 110 + .../src/w5500/esp_eth/esp_eth_w5500.h | 146 + .../src/w5500/esp_eth/w5500.h | 126 + .../utils/astyle_library.conf | 70 + lib/WebServer_ESP32_W5500/utils/restyle.sh | 6 + lib/integrity.dat | 10 + platformio.ini | 40 +- src/Define.h | 11 +- src/GSMMannager.cpp | 34 +- src/GSMMannager.h | 13 +- src/Myhttpserver.cpp | 234 + src/Myhttpserver.h | 28 + src/SDmanger.cpp | 2 +- src/SensorOptoSky.cpp | 16 + src/TCPserver.h | 2 +- src/log.cpp | 14 + src/main.cpp | 312 +- src/slave.cpp | 19 +- src/slave.h | 1 + weatherslave/platformio.ini | 1 + weatherslave/src/main.cpp | 55 +- 376 files changed, 68605 insertions(+), 246 deletions(-) create mode 100644 default.csv create mode 100644 lib/Adafruit BusIO/.github/ISSUE_TEMPLATE.md create mode 100644 lib/Adafruit BusIO/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 lib/Adafruit BusIO/.github/workflows/githubci.yml create mode 100644 lib/Adafruit BusIO/.piopm create mode 100644 lib/Adafruit BusIO/Adafruit_BusIO_Register.cpp create mode 100644 lib/Adafruit BusIO/Adafruit_BusIO_Register.h create mode 100644 lib/Adafruit BusIO/Adafruit_I2CDevice.cpp create mode 100644 lib/Adafruit BusIO/Adafruit_I2CDevice.h create mode 100644 lib/Adafruit BusIO/Adafruit_I2CRegister.h create mode 100644 lib/Adafruit BusIO/Adafruit_SPIDevice.cpp create mode 100644 lib/Adafruit BusIO/Adafruit_SPIDevice.h create mode 100644 lib/Adafruit BusIO/CMakeLists.txt create mode 100644 lib/Adafruit BusIO/LICENSE create mode 100644 lib/Adafruit BusIO/README.md create mode 100644 lib/Adafruit BusIO/component.mk create mode 100644 lib/Adafruit BusIO/examples/i2c_address_detect/i2c_address_detect.ino create mode 100644 lib/Adafruit BusIO/examples/i2c_readwrite/i2c_readwrite.ino create mode 100644 lib/Adafruit BusIO/examples/i2c_registers/i2c_registers.ino create mode 100644 lib/Adafruit BusIO/examples/i2corspi_register/i2corspi_register.ino create mode 100644 lib/Adafruit BusIO/examples/spi_modetest/spi_modetest.ino create mode 100644 lib/Adafruit BusIO/examples/spi_readwrite/spi_readwrite.ino create mode 100644 lib/Adafruit BusIO/examples/spi_register_bits/spi_register_bits.ino create mode 100644 lib/Adafruit BusIO/examples/spi_registers/spi_registers.ino create mode 100644 lib/Adafruit BusIO/library.properties create mode 100644 lib/Adafruit MLX90614 Library/.github/ISSUE_TEMPLATE.md create mode 100644 lib/Adafruit MLX90614 Library/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 lib/Adafruit MLX90614 Library/.github/workflows/githubci.yml create mode 100644 lib/Adafruit MLX90614 Library/.piopm create mode 100644 lib/Adafruit MLX90614 Library/Adafruit_MLX90614.cpp create mode 100644 lib/Adafruit MLX90614 Library/Adafruit_MLX90614.h create mode 100644 lib/Adafruit MLX90614 Library/README.md create mode 100644 lib/Adafruit MLX90614 Library/code-of-conduct.md create mode 100644 lib/Adafruit MLX90614 Library/examples/mlx_set_emissivity/mlx_set_emissivity.ino create mode 100644 lib/Adafruit MLX90614 Library/examples/mlxtest/mlxtest.ino create mode 100644 lib/Adafruit MLX90614 Library/library.properties create mode 100644 lib/Adafruit MLX90614 Library/license.txt create mode 100644 lib/ArduinoHttpClient/.gitignore create mode 100644 lib/ArduinoHttpClient/.piopm create mode 100644 lib/ArduinoHttpClient/CHANGELOG.md create mode 100644 lib/ArduinoHttpClient/README.md create mode 100644 lib/ArduinoHttpClient/examples/BasicAuthGet/BasicAuthGet.ino create mode 100644 lib/ArduinoHttpClient/examples/BasicAuthGet/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/CustomHeader/CustomHeader.ino create mode 100644 lib/ArduinoHttpClient/examples/CustomHeader/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/DweetGet/DweetGet.ino create mode 100644 lib/ArduinoHttpClient/examples/DweetGet/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/DweetPost/DweetPost.ino create mode 100644 lib/ArduinoHttpClient/examples/DweetPost/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/HueBlink/HueBlink.ino create mode 100644 lib/ArduinoHttpClient/examples/HueBlink/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/PostWithHeaders/PostWithHeaders.ino create mode 100644 lib/ArduinoHttpClient/examples/PostWithHeaders/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/SimpleDelete/SimpleDelete.ino create mode 100644 lib/ArduinoHttpClient/examples/SimpleDelete/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/SimpleGet/SimpleGet.ino create mode 100644 lib/ArduinoHttpClient/examples/SimpleGet/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/SimpleHttpExample/SimpleHttpExample.ino create mode 100644 lib/ArduinoHttpClient/examples/SimpleHttpExample/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/SimplePost/SimplePost.ino create mode 100644 lib/ArduinoHttpClient/examples/SimplePost/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/SimplePut/SimplePut.ino create mode 100644 lib/ArduinoHttpClient/examples/SimplePut/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/SimpleWebSocket/SimpleWebSocket.ino create mode 100644 lib/ArduinoHttpClient/examples/SimpleWebSocket/arduino_secrets.h create mode 100644 lib/ArduinoHttpClient/examples/node_test_server/package.json create mode 100644 lib/ArduinoHttpClient/keywords.txt create mode 100644 lib/ArduinoHttpClient/library.json create mode 100644 lib/ArduinoHttpClient/library.properties create mode 100644 lib/ArduinoHttpClient/src/ArduinoHttpClient.h create mode 100644 lib/ArduinoHttpClient/src/HttpClient.cpp create mode 100644 lib/ArduinoHttpClient/src/HttpClient.h create mode 100644 lib/ArduinoHttpClient/src/URLEncoder.cpp create mode 100644 lib/ArduinoHttpClient/src/URLEncoder.h create mode 100644 lib/ArduinoHttpClient/src/WebSocketClient.cpp create mode 100644 lib/ArduinoHttpClient/src/WebSocketClient.h create mode 100644 lib/ArduinoHttpClient/src/b64.cpp create mode 100644 lib/ArduinoHttpClient/src/b64.h create mode 100644 lib/DallasTemperature/.arduino-ci.yml create mode 100644 lib/DallasTemperature/.gitattributes create mode 100644 lib/DallasTemperature/.github/workflows/arduino-lint.yml create mode 100644 lib/DallasTemperature/.github/workflows/arduino_test_runner.yml create mode 100644 lib/DallasTemperature/.github/workflows/jsoncheck.yml create mode 100644 lib/DallasTemperature/.gitignore create mode 100644 lib/DallasTemperature/.piopm create mode 100644 lib/DallasTemperature/DallasTemperature.cpp create mode 100644 lib/DallasTemperature/DallasTemperature.h create mode 100644 lib/DallasTemperature/README.md create mode 100644 lib/DallasTemperature/examples/Alarm/Alarm.ino create mode 100644 lib/DallasTemperature/examples/AlarmHandler/AlarmHandler.ino create mode 100644 lib/DallasTemperature/examples/ExternalPullup/ExternalPullup.ino create mode 100644 lib/DallasTemperature/examples/Multibus_simple/Multibus_simple.ino create mode 100644 lib/DallasTemperature/examples/Multiple/Multiple.ino create mode 100644 lib/DallasTemperature/examples/SaveRecallScratchPad/SaveRecallScratchPad.ino create mode 100644 lib/DallasTemperature/examples/SetUserData/SetUserData.ino create mode 100644 lib/DallasTemperature/examples/Simple/Simple.ino create mode 100644 lib/DallasTemperature/examples/Single/Single.ino create mode 100644 lib/DallasTemperature/examples/Tester/Tester.ino create mode 100644 lib/DallasTemperature/examples/Timing/Timing.ino create mode 100644 lib/DallasTemperature/examples/TwoPin_DS18B20/TwoPin_DS18B20.ino create mode 100644 lib/DallasTemperature/examples/UserDataDemo/UserDataDemo.ino create mode 100644 lib/DallasTemperature/examples/UserDataWriteBatch/UserDataWriteBatch.ino create mode 100644 lib/DallasTemperature/examples/WaitForConversion/WaitForConversion.ino create mode 100644 lib/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.ino create mode 100644 lib/DallasTemperature/examples/oneWireSearch/oneWireSearch.ino create mode 100644 lib/DallasTemperature/examples/readPowerSupply/readPowerSupply.ino create mode 100644 lib/DallasTemperature/keywords.txt create mode 100644 lib/DallasTemperature/library.json create mode 100644 lib/DallasTemperature/library.properties create mode 100644 lib/EspSoftwareSerial/.gitignore create mode 100644 lib/EspSoftwareSerial/.piopm create mode 100644 lib/EspSoftwareSerial/LICENSE create mode 100644 lib/EspSoftwareSerial/README.md create mode 100644 lib/EspSoftwareSerial/examples/bitpattern/bitpattern.ino create mode 100644 lib/EspSoftwareSerial/examples/loopback/loopback.ino create mode 100644 lib/EspSoftwareSerial/examples/onewiretest/onewiretest.ino create mode 100644 lib/EspSoftwareSerial/examples/repeater/repeater.ino create mode 100644 lib/EspSoftwareSerial/examples/servoTester/servoTester.ino create mode 100644 lib/EspSoftwareSerial/examples/swsertest/swsertest.ino create mode 100644 lib/EspSoftwareSerial/keywords.txt create mode 100644 lib/EspSoftwareSerial/library.json create mode 100644 lib/EspSoftwareSerial/library.properties create mode 100644 lib/EspSoftwareSerial/src/SoftwareSerial.cpp create mode 100644 lib/EspSoftwareSerial/src/SoftwareSerial.h create mode 100644 lib/EspSoftwareSerial/src/circular_queue/Delegate.h create mode 100644 lib/EspSoftwareSerial/src/circular_queue/MultiDelegate.h create mode 100644 lib/EspSoftwareSerial/src/circular_queue/circular_queue.h create mode 100644 lib/EspSoftwareSerial/src/circular_queue/circular_queue_mp.h create mode 100644 lib/EspSoftwareSerial/src/circular_queue/ghostl.h create mode 100644 lib/OneWire/.piopm create mode 100644 lib/OneWire/OneWire.cpp create mode 100644 lib/OneWire/OneWire.h create mode 100644 lib/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.ino create mode 100644 lib/OneWire/examples/DS2408_Switch/DS2408_Switch.ino create mode 100644 lib/OneWire/examples/DS250x_PROM/DS250x_PROM.ino create mode 100644 lib/OneWire/keywords.txt create mode 100644 lib/OneWire/library.json create mode 100644 lib/OneWire/library.properties create mode 100644 lib/OneWire/util/OneWire_direct_gpio.h create mode 100644 lib/OneWire/util/OneWire_direct_regtype.h create mode 100644 lib/PubSubClient/.gitignore create mode 100644 lib/PubSubClient/.piopm create mode 100644 lib/PubSubClient/.travis.yml create mode 100644 lib/PubSubClient/CHANGES.txt create mode 100644 lib/PubSubClient/LICENSE.txt create mode 100644 lib/PubSubClient/README.md create mode 100644 lib/PubSubClient/examples/mqtt_auth/mqtt_auth.ino create mode 100644 lib/PubSubClient/examples/mqtt_basic/mqtt_basic.ino create mode 100644 lib/PubSubClient/examples/mqtt_esp8266/mqtt_esp8266.ino create mode 100644 lib/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino create mode 100644 lib/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino create mode 100644 lib/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino create mode 100644 lib/PubSubClient/examples/mqtt_stream/mqtt_stream.ino create mode 100644 lib/PubSubClient/keywords.txt create mode 100644 lib/PubSubClient/library.json create mode 100644 lib/PubSubClient/library.properties create mode 100644 lib/PubSubClient/src/PubSubClient.cpp create mode 100644 lib/PubSubClient/src/PubSubClient.h create mode 100644 lib/SparkFun u-blox Arduino Library/.gitattributes create mode 100644 lib/SparkFun u-blox Arduino Library/.gitignore create mode 100644 lib/SparkFun u-blox Arduino Library/.piopm create mode 100644 lib/SparkFun u-blox Arduino Library/CONTRIBUTING.md create mode 100644 lib/SparkFun u-blox Arduino Library/ISSUE_TEMPLATE.md create mode 100644 lib/SparkFun u-blox Arduino Library/LICENSE.md create mode 100644 lib/SparkFun u-blox Arduino Library/README.md create mode 100644 lib/SparkFun u-blox Arduino Library/Theory.md create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example2_getIMUData/Example2_getIMUData.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example12_UseUart/Example12_UseUart.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example3_AssumeAutoPVTviaUart/Example3_AssumeAutoPVTviaUart.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example14_DebugOutput/Example14_DebugOutput.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example15_GetDateTime/Example15_GetDateTime.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example17_Geofence/Example17_Geofence.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example19_DynamicModel/Example19_DynamicModel.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example22_PowerOff/Example22_PowerOff.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example3_GetPosition/Example3_GetPosition.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example4_FixType/Example4_FixType.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example7_OutputRate/Example7_OutputRate.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/secrets.h create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h create mode 100644 lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c create mode 100644 lib/SparkFun u-blox Arduino Library/img/Contributing.JPG create mode 100644 lib/SparkFun u-blox Arduino Library/keywords.txt create mode 100644 lib/SparkFun u-blox Arduino Library/library.properties create mode 100644 lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.cpp create mode 100644 lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.h create mode 100644 lib/SparkFun u-blox Arduino Library/src/u-blox_config_keys.h create mode 100644 lib/TinyGSM/.piopm create mode 100644 lib/TinyGSM/LICENSE create mode 100644 lib/TinyGSM/README.md create mode 100644 lib/TinyGSM/examples/AllFunctions/AllFunctions.ino create mode 100644 lib/TinyGSM/examples/BlynkClient/BlynkClient.ino create mode 100644 lib/TinyGSM/examples/FileDownload/FileDownload.ino create mode 100644 lib/TinyGSM/examples/HttpClient/HttpClient.ino create mode 100644 lib/TinyGSM/examples/HttpsClient/HttpsClient.ino create mode 100644 lib/TinyGSM/examples/MqttClient/MqttClient.ino create mode 100644 lib/TinyGSM/examples/WebClient/WebClient.ino create mode 100644 lib/TinyGSM/examples/example_dependencies.json create mode 100644 lib/TinyGSM/examples/more/Hologram_Dash/Hologram_Dash.ino create mode 100644 lib/TinyGSM/examples/more/Industruino/Industruino.ino create mode 100644 lib/TinyGSM/examples/more/SIM800_SslSetCert/COMODORSACertificationAuthority.h create mode 100644 lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.der.h create mode 100644 lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.h create mode 100644 lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino create mode 100644 lib/TinyGSM/keywords.txt create mode 100644 lib/TinyGSM/library.json create mode 100644 lib/TinyGSM/library.properties create mode 100644 lib/TinyGSM/src/ArduinoCompat/Client.h create mode 100644 lib/TinyGSM/src/ArduinoCompat/IPAddress.h create mode 100644 lib/TinyGSM/src/TinyGSM.h create mode 100644 lib/TinyGSM/src/TinyGsmBattery.tpp create mode 100644 lib/TinyGSM/src/TinyGsmBluetooth.tpp create mode 100644 lib/TinyGSM/src/TinyGsmCalling.tpp create mode 100644 lib/TinyGSM/src/TinyGsmClient.h create mode 100644 lib/TinyGSM/src/TinyGsmClientA6.h create mode 100644 lib/TinyGSM/src/TinyGsmClientBG96.h create mode 100644 lib/TinyGSM/src/TinyGsmClientESP8266.h create mode 100644 lib/TinyGSM/src/TinyGsmClientM590.h create mode 100644 lib/TinyGSM/src/TinyGsmClientM95.h create mode 100644 lib/TinyGSM/src/TinyGsmClientMC60.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM5360.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM7000.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM7080.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM70xx.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM7600.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM800.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSIM808.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSaraR4.h create mode 100644 lib/TinyGSM/src/TinyGsmClientSequansMonarch.h create mode 100644 lib/TinyGSM/src/TinyGsmClientUBLOX.h create mode 100644 lib/TinyGSM/src/TinyGsmClientXBee.h create mode 100644 lib/TinyGSM/src/TinyGsmCommon.h create mode 100644 lib/TinyGSM/src/TinyGsmFifo.h create mode 100644 lib/TinyGSM/src/TinyGsmGPRS.tpp create mode 100644 lib/TinyGSM/src/TinyGsmGPS.tpp create mode 100644 lib/TinyGSM/src/TinyGsmGSMLocation.tpp create mode 100644 lib/TinyGSM/src/TinyGsmModem.tpp create mode 100644 lib/TinyGSM/src/TinyGsmNTP.tpp create mode 100644 lib/TinyGSM/src/TinyGsmSMS.tpp create mode 100644 lib/TinyGSM/src/TinyGsmSSL.tpp create mode 100644 lib/TinyGSM/src/TinyGsmTCP.tpp create mode 100644 lib/TinyGSM/src/TinyGsmTemperature.tpp create mode 100644 lib/TinyGSM/src/TinyGsmTime.tpp create mode 100644 lib/TinyGSM/src/TinyGsmWifi.tpp create mode 100644 lib/Vector/.gitignore create mode 100644 lib/Vector/.piopm create mode 100644 lib/Vector/LICENSE create mode 100644 lib/Vector/README.org create mode 100644 lib/Vector/examples/PlatformIO/README.org create mode 100644 lib/Vector/examples/PlatformIO/include/README create mode 100644 lib/Vector/examples/PlatformIO/lib/README create mode 100644 lib/Vector/examples/PlatformIO/platformio.ini create mode 100644 lib/Vector/examples/PlatformIO/src/main.cpp create mode 100644 lib/Vector/examples/PlatformIO/test/README create mode 100644 lib/Vector/examples/PlatformIO/test/test_vector.cpp create mode 100644 lib/Vector/examples/VectorTester/VectorTester.ino create mode 100644 lib/Vector/library.json create mode 100644 lib/Vector/library.properties create mode 100644 lib/Vector/src/Vector.h create mode 100644 lib/Vector/src/Vector/Vector.cpp create mode 100644 lib/Vector/src/Vector/VectorDefinitions.h create mode 100644 lib/Vector/src/Vector/VectorIterator.h create mode 100644 lib/WebServer_ESP32_W5500/.codespellrc create mode 100644 lib/WebServer_ESP32_W5500/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 lib/WebServer_ESP32_W5500/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 lib/WebServer_ESP32_W5500/.github/dependabot.yml create mode 100644 lib/WebServer_ESP32_W5500/.github/stale.yml create mode 100644 lib/WebServer_ESP32_W5500/.github/workflows/auto-github-actions.yml create mode 100644 lib/WebServer_ESP32_W5500/.github/workflows/report-size-deltas.yml create mode 100644 lib/WebServer_ESP32_W5500/.github/workflows/spell-check.yml create mode 100644 lib/WebServer_ESP32_W5500/.gitignore create mode 100644 lib/WebServer_ESP32_W5500/.piopm create mode 100644 lib/WebServer_ESP32_W5500/CONTRIBUTING.md create mode 100644 lib/WebServer_ESP32_W5500/LICENSE create mode 100644 lib/WebServer_ESP32_W5500/LibraryPatches/esp32/cores/esp32/Server.h create mode 100644 lib/WebServer_ESP32_W5500/README.md create mode 100644 lib/WebServer_ESP32_W5500/changelog.md create mode 100644 lib/WebServer_ESP32_W5500/examples/AdvancedWebServer/AdvancedWebServer.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/HelloServer/HelloServer.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/HelloServer2/HelloServer2.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/HttpBasicAuth/HttpBasicAuth.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/MQTTClient_Auth/MQTTClient_Auth.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/MQTTClient_Basic/MQTTClient_Basic.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/MQTT_ThingStream/MQTT_ThingStream.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/PostServer/PostServer.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/SimpleAuthentication/SimpleAuthentication.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/UdpNTPClient/UdpNTPClient.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/UdpSendReceive/UdpSendReceive.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/WebClient/WebClient.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/WebClientRepeating/WebClientRepeating.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/WebServer/WebServer.ino create mode 100644 lib/WebServer_ESP32_W5500/examples/multiFileProject/multiFileProject.cpp create mode 100644 lib/WebServer_ESP32_W5500/examples/multiFileProject/multiFileProject.h create mode 100644 lib/WebServer_ESP32_W5500/examples/multiFileProject/multiFileProject.ino create mode 100644 lib/WebServer_ESP32_W5500/library.json create mode 100644 lib/WebServer_ESP32_W5500/library.properties create mode 100644 lib/WebServer_ESP32_W5500/pics/AdvancedWebServer.png create mode 100644 lib/WebServer_ESP32_W5500/pics/W5500.png create mode 100644 lib/WebServer_ESP32_W5500/pics/W5500_small.png create mode 100644 lib/WebServer_ESP32_W5500/platformio/platformio.ini create mode 100644 lib/WebServer_ESP32_W5500/src/WebServer_ESP32_W5500.h create mode 100644 lib/WebServer_ESP32_W5500/src/WebServer_ESP32_W5500.hpp create mode 100644 lib/WebServer_ESP32_W5500/src/WebServer_ESP32_W5500_Debug.h create mode 100644 lib/WebServer_ESP32_W5500/src/WebServer_ESP32_W5500_Impl.h create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp32_w5500.cpp create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp32_w5500.h create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp_eth/esp_eth_mac_w5500.c create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp_eth/esp_eth_phy_w5500.c create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp_eth/esp_eth_spi_w5500.c create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp_eth/esp_eth_w5500.h create mode 100644 lib/WebServer_ESP32_W5500/src/w5500/esp_eth/w5500.h create mode 100644 lib/WebServer_ESP32_W5500/utils/astyle_library.conf create mode 100644 lib/WebServer_ESP32_W5500/utils/restyle.sh create mode 100644 lib/integrity.dat create mode 100644 src/Myhttpserver.cpp create mode 100644 src/Myhttpserver.h diff --git a/default.csv b/default.csv new file mode 100644 index 0000000..878d609 --- /dev/null +++ b/default.csv @@ -0,0 +1,5 @@ +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x140000, +app1, app, ota_1, 0x150000,0x140000, +spiffs, data, spiffs, 0x290000,0x170000, \ No newline at end of file diff --git a/lib/Adafruit BusIO/.github/ISSUE_TEMPLATE.md b/lib/Adafruit BusIO/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..f0e2614 --- /dev/null +++ b/lib/Adafruit BusIO/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,46 @@ +Thank you for opening an issue on an Adafruit Arduino library repository. To +improve the speed of resolution please review the following guidelines and +common troubleshooting steps below before creating the issue: + +- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use + the forums at http://forums.adafruit.com to ask questions and troubleshoot why + something isn't working as expected. In many cases the problem is a common issue + that you will more quickly receive help from the forum community. GitHub issues + are meant for known defects in the code. If you don't know if there is a defect + in the code then start with troubleshooting on the forum first. + +- **If following a tutorial or guide be sure you didn't miss a step.** Carefully + check all of the steps and commands to run have been followed. Consult the + forum if you're unsure or have questions about steps in a guide/tutorial. + +- **For Arduino projects check these very common issues to ensure they don't apply**: + + - For uploading sketches or communicating with the board make sure you're using + a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes + very hard to tell the difference between a data and charge cable! Try using the + cable with other devices or swapping to another cable to confirm it is not + the problem. + + - **Be sure you are supplying adequate power to the board.** Check the specs of + your board and plug in an external power supply. In many cases just + plugging a board into your computer is not enough to power it and other + peripherals. + + - **Double check all soldering joints and connections.** Flakey connections + cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. + + - **Ensure you are using an official Arduino or Adafruit board.** We can't + guarantee a clone board will have the same functionality and work as expected + with this code and don't support them. + +If you're sure this issue is a defect in the code and checked the steps above +please fill in the following fields to provide enough troubleshooting information. +You may delete the guideline and text above to just leave the following details: + +- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** + +- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO + VERSION HERE** + +- List the steps to reproduce the problem below (if possible attach a sketch or + copy the sketch code in too): **LIST REPRO STEPS BELOW** diff --git a/lib/Adafruit BusIO/.github/PULL_REQUEST_TEMPLATE.md b/lib/Adafruit BusIO/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7b641eb --- /dev/null +++ b/lib/Adafruit BusIO/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +Thank you for creating a pull request to contribute to Adafruit's GitHub code! +Before you open the request please review the following guidelines and tips to +help it be more easily integrated: + +- **Describe the scope of your change--i.e. what the change does and what parts + of the code were modified.** This will help us understand any risks of integrating + the code. + +- **Describe any known limitations with your change.** For example if the change + doesn't apply to a supported platform of the library please mention it. + +- **Please run any tests or examples that can exercise your modified code.** We + strive to not break users of the code and running tests/examples helps with this + process. + +Thank you again for contributing! We will try to test and integrate the change +as soon as we can, but be aware we have many GitHub repositories to manage and +can't immediately respond to every request. There is no need to bump or check in +on a pull request (it will clutter the discussion of the request). + +Also don't be worried if the request is closed or not integrated--sometimes the +priorities of Adafruit's GitHub code (education, ease of use) might not match the +priorities of the pull request. Don't fret, the open source community thrives on +forks and GitHub makes it easy to keep your changes in a forked repo. + +After reviewing the guidelines above you can delete this text from the pull request. diff --git a/lib/Adafruit BusIO/.github/workflows/githubci.yml b/lib/Adafruit BusIO/.github/workflows/githubci.yml new file mode 100644 index 0000000..8a57d75 --- /dev/null +++ b/lib/Adafruit BusIO/.github/workflows/githubci.yml @@ -0,0 +1,33 @@ +name: Arduino Library CI + +on: [pull_request, push, repository_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + with: + repository: adafruit/ci-arduino + path: ci + + - name: Install the prerequisites + run: bash ci/actions_install.sh + + - name: Check for correct code formatting with clang-format + run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . + + - name: Check for correct documentation with doxygen + env: + GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} + PRETTYNAME : "Adafruit Bus IO Library" + run: bash ci/doxy_gen_and_deploy.sh + + - name: Test the code on supported platforms + run: python3 ci/build_platform.py main_platforms zero feather32u4 + diff --git a/lib/Adafruit BusIO/.piopm b/lib/Adafruit BusIO/.piopm new file mode 100644 index 0000000..0817b58 --- /dev/null +++ b/lib/Adafruit BusIO/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "Adafruit BusIO", "version": "1.15.0", "spec": {"owner": "adafruit", "id": 6214, "name": "Adafruit BusIO", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/Adafruit BusIO/Adafruit_BusIO_Register.cpp b/lib/Adafruit BusIO/Adafruit_BusIO_Register.cpp new file mode 100644 index 0000000..a28193f --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_BusIO_Register.cpp @@ -0,0 +1,365 @@ +#include + +#if !defined(SPI_INTERFACES_COUNT) || \ + (defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0)) + +/*! + * @brief Create a register we access over an I2C Device (which defines the + * bus and address) + * @param i2cdevice The I2CDevice to use for underlying I2C access + * @param reg_addr The address pointer value for the I2C/SMBus register, can + * be 8 or 16 bits + * @param width The width of the register data itself, defaults to 1 byte + * @param byteorder The byte order of the register (used when width is > 1), + * defaults to LSBFIRST + * @param address_width The width of the register address itself, defaults + * to 1 byte + */ +Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice, + uint16_t reg_addr, + uint8_t width, + uint8_t byteorder, + uint8_t address_width) { + _i2cdevice = i2cdevice; + _spidevice = nullptr; + _addrwidth = address_width; + _address = reg_addr; + _byteorder = byteorder; + _width = width; +} + +/*! + * @brief Create a register we access over an SPI Device (which defines the + * bus and CS pin) + * @param spidevice The SPIDevice to use for underlying SPI access + * @param reg_addr The address pointer value for the SPI register, can + * be 8 or 16 bits + * @param type The method we use to read/write data to SPI (which is not + * as well defined as I2C) + * @param width The width of the register data itself, defaults to 1 byte + * @param byteorder The byte order of the register (used when width is > 1), + * defaults to LSBFIRST + * @param address_width The width of the register address itself, defaults + * to 1 byte + */ +Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice, + uint16_t reg_addr, + Adafruit_BusIO_SPIRegType type, + uint8_t width, + uint8_t byteorder, + uint8_t address_width) { + _spidevice = spidevice; + _spiregtype = type; + _i2cdevice = nullptr; + _addrwidth = address_width; + _address = reg_addr; + _byteorder = byteorder; + _width = width; +} + +/*! + * @brief Create a register we access over an I2C or SPI Device. This is a + * handy function because we can pass in nullptr for the unused interface, + * allowing libraries to mass-define all the registers + * @param i2cdevice The I2CDevice to use for underlying I2C access, if + * nullptr we use SPI + * @param spidevice The SPIDevice to use for underlying SPI access, if + * nullptr we use I2C + * @param reg_addr The address pointer value for the I2C/SMBus/SPI register, + * can be 8 or 16 bits + * @param type The method we use to read/write data to SPI (which is not + * as well defined as I2C) + * @param width The width of the register data itself, defaults to 1 byte + * @param byteorder The byte order of the register (used when width is > 1), + * defaults to LSBFIRST + * @param address_width The width of the register address itself, defaults + * to 1 byte + */ +Adafruit_BusIO_Register::Adafruit_BusIO_Register( + Adafruit_I2CDevice *i2cdevice, Adafruit_SPIDevice *spidevice, + Adafruit_BusIO_SPIRegType type, uint16_t reg_addr, uint8_t width, + uint8_t byteorder, uint8_t address_width) { + _spidevice = spidevice; + _i2cdevice = i2cdevice; + _spiregtype = type; + _addrwidth = address_width; + _address = reg_addr; + _byteorder = byteorder; + _width = width; +} + +/*! + * @brief Write a buffer of data to the register location + * @param buffer Pointer to data to write + * @param len Number of bytes to write + * @return True on successful write (only really useful for I2C as SPI is + * uncheckable) + */ +bool Adafruit_BusIO_Register::write(uint8_t *buffer, uint8_t len) { + + uint8_t addrbuffer[2] = {(uint8_t)(_address & 0xFF), + (uint8_t)(_address >> 8)}; + + if (_i2cdevice) { + return _i2cdevice->write(buffer, len, true, addrbuffer, _addrwidth); + } + if (_spidevice) { + if (_spiregtype == ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE) { + // very special case! + + // pass the special opcode address which we set as the high byte of the + // regaddr + addrbuffer[0] = + (uint8_t)(_address >> 8) & ~0x01; // set bottom bit low to write + // the 'actual' reg addr is the second byte then + addrbuffer[1] = (uint8_t)(_address & 0xFF); + // the address appears to be a byte longer + return _spidevice->write(buffer, len, addrbuffer, _addrwidth + 1); + } + + if (_spiregtype == ADDRBIT8_HIGH_TOREAD) { + addrbuffer[0] &= ~0x80; + } + if (_spiregtype == ADDRBIT8_HIGH_TOWRITE) { + addrbuffer[0] |= 0x80; + } + if (_spiregtype == AD8_HIGH_TOREAD_AD7_HIGH_TOINC) { + addrbuffer[0] &= ~0x80; + addrbuffer[0] |= 0x40; + } + return _spidevice->write(buffer, len, addrbuffer, _addrwidth); + } + return false; +} + +/*! + * @brief Write up to 4 bytes of data to the register location + * @param value Data to write + * @param numbytes How many bytes from 'value' to write + * @return True on successful write (only really useful for I2C as SPI is + * uncheckable) + */ +bool Adafruit_BusIO_Register::write(uint32_t value, uint8_t numbytes) { + if (numbytes == 0) { + numbytes = _width; + } + if (numbytes > 4) { + return false; + } + + // store a copy + _cached = value; + + for (int i = 0; i < numbytes; i++) { + if (_byteorder == LSBFIRST) { + _buffer[i] = value & 0xFF; + } else { + _buffer[numbytes - i - 1] = value & 0xFF; + } + value >>= 8; + } + return write(_buffer, numbytes); +} + +/*! + * @brief Read data from the register location. This does not do any error + * checking! + * @return Returns 0xFFFFFFFF on failure, value otherwise + */ +uint32_t Adafruit_BusIO_Register::read(void) { + if (!read(_buffer, _width)) { + return -1; + } + + uint32_t value = 0; + + for (int i = 0; i < _width; i++) { + value <<= 8; + if (_byteorder == LSBFIRST) { + value |= _buffer[_width - i - 1]; + } else { + value |= _buffer[i]; + } + } + + return value; +} + +/*! + * @brief Read cached data from last time we wrote to this register + * @return Returns 0xFFFFFFFF on failure, value otherwise + */ +uint32_t Adafruit_BusIO_Register::readCached(void) { return _cached; } + +/*! + * @brief Read a buffer of data from the register location + * @param buffer Pointer to data to read into + * @param len Number of bytes to read + * @return True on successful write (only really useful for I2C as SPI is + * uncheckable) + */ +bool Adafruit_BusIO_Register::read(uint8_t *buffer, uint8_t len) { + uint8_t addrbuffer[2] = {(uint8_t)(_address & 0xFF), + (uint8_t)(_address >> 8)}; + + if (_i2cdevice) { + return _i2cdevice->write_then_read(addrbuffer, _addrwidth, buffer, len); + } + if (_spidevice) { + if (_spiregtype == ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE) { + // very special case! + + // pass the special opcode address which we set as the high byte of the + // regaddr + addrbuffer[0] = + (uint8_t)(_address >> 8) | 0x01; // set bottom bit high to read + // the 'actual' reg addr is the second byte then + addrbuffer[1] = (uint8_t)(_address & 0xFF); + // the address appears to be a byte longer + return _spidevice->write_then_read(addrbuffer, _addrwidth + 1, buffer, + len); + } + if (_spiregtype == ADDRBIT8_HIGH_TOREAD) { + addrbuffer[0] |= 0x80; + } + if (_spiregtype == ADDRBIT8_HIGH_TOWRITE) { + addrbuffer[0] &= ~0x80; + } + if (_spiregtype == AD8_HIGH_TOREAD_AD7_HIGH_TOINC) { + addrbuffer[0] |= 0x80 | 0x40; + } + return _spidevice->write_then_read(addrbuffer, _addrwidth, buffer, len); + } + return false; +} + +/*! + * @brief Read 2 bytes of data from the register location + * @param value Pointer to uint16_t variable to read into + * @return True on successful write (only really useful for I2C as SPI is + * uncheckable) + */ +bool Adafruit_BusIO_Register::read(uint16_t *value) { + if (!read(_buffer, 2)) { + return false; + } + + if (_byteorder == LSBFIRST) { + *value = _buffer[1]; + *value <<= 8; + *value |= _buffer[0]; + } else { + *value = _buffer[0]; + *value <<= 8; + *value |= _buffer[1]; + } + return true; +} + +/*! + * @brief Read 1 byte of data from the register location + * @param value Pointer to uint8_t variable to read into + * @return True on successful write (only really useful for I2C as SPI is + * uncheckable) + */ +bool Adafruit_BusIO_Register::read(uint8_t *value) { + if (!read(_buffer, 1)) { + return false; + } + + *value = _buffer[0]; + return true; +} + +/*! + * @brief Pretty printer for this register + * @param s The Stream to print to, defaults to &Serial + */ +void Adafruit_BusIO_Register::print(Stream *s) { + uint32_t val = read(); + s->print("0x"); + s->print(val, HEX); +} + +/*! + * @brief Pretty printer for this register + * @param s The Stream to print to, defaults to &Serial + */ +void Adafruit_BusIO_Register::println(Stream *s) { + print(s); + s->println(); +} + +/*! + * @brief Create a slice of the register that we can address without + * touching other bits + * @param reg The Adafruit_BusIO_Register which defines the bus/register + * @param bits The number of bits wide we are slicing + * @param shift The number of bits that our bit-slice is shifted from LSB + */ +Adafruit_BusIO_RegisterBits::Adafruit_BusIO_RegisterBits( + Adafruit_BusIO_Register *reg, uint8_t bits, uint8_t shift) { + _register = reg; + _bits = bits; + _shift = shift; +} + +/*! + * @brief Read 4 bytes of data from the register + * @return data The 4 bytes to read + */ +uint32_t Adafruit_BusIO_RegisterBits::read(void) { + uint32_t val = _register->read(); + val >>= _shift; + return val & ((1 << (_bits)) - 1); +} + +/*! + * @brief Write 4 bytes of data to the register + * @param data The 4 bytes to write + * @return True on successful write (only really useful for I2C as SPI is + * uncheckable) + */ +bool Adafruit_BusIO_RegisterBits::write(uint32_t data) { + uint32_t val = _register->read(); + + // mask off the data before writing + uint32_t mask = (1 << (_bits)) - 1; + data &= mask; + + mask <<= _shift; + val &= ~mask; // remove the current data at that spot + val |= data << _shift; // and add in the new data + + return _register->write(val, _register->width()); +} + +/*! + * @brief The width of the register data, helpful for doing calculations + * @returns The data width used when initializing the register + */ +uint8_t Adafruit_BusIO_Register::width(void) { return _width; } + +/*! + * @brief Set the default width of data + * @param width the default width of data read from register + */ +void Adafruit_BusIO_Register::setWidth(uint8_t width) { _width = width; } + +/*! + * @brief Set register address + * @param address the address from register + */ +void Adafruit_BusIO_Register::setAddress(uint16_t address) { + _address = address; +} + +/*! + * @brief Set the width of register address + * @param address_width the width for register address + */ +void Adafruit_BusIO_Register::setAddressWidth(uint16_t address_width) { + _addrwidth = address_width; +} + +#endif // SPI exists diff --git a/lib/Adafruit BusIO/Adafruit_BusIO_Register.h b/lib/Adafruit BusIO/Adafruit_BusIO_Register.h new file mode 100644 index 0000000..c6d58de --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_BusIO_Register.h @@ -0,0 +1,105 @@ +#ifndef Adafruit_BusIO_Register_h +#define Adafruit_BusIO_Register_h + +#include + +#if !defined(SPI_INTERFACES_COUNT) || \ + (defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0)) + +#include +#include + +typedef enum _Adafruit_BusIO_SPIRegType { + ADDRBIT8_HIGH_TOREAD = 0, + /*!< + * ADDRBIT8_HIGH_TOREAD + * When reading a register you must actually send the value 0x80 + register + * address to the device. e.g. To read the register 0x0B the register value + * 0x8B is sent and to write 0x0B is sent. + */ + AD8_HIGH_TOREAD_AD7_HIGH_TOINC = 1, + + /*!< + * ADDRBIT8_HIGH_TOWRITE + * When writing to a register you must actually send the value 0x80 + + * the register address to the device. e.g. To write to the register 0x19 the + * register value 0x99 is sent and to read 0x19 is sent. + */ + ADDRBIT8_HIGH_TOWRITE = 2, + + /*!< + * ADDRESSED_OPCODE_LOWBIT_TO_WRITE + * Used by the MCP23S series, we send 0x40 |'rd with the opcode + * Then set the lowest bit to write + */ + ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE = 3, + +} Adafruit_BusIO_SPIRegType; + +/*! + * @brief The class which defines a device register (a location to read/write + * data from) + */ +class Adafruit_BusIO_Register { +public: + Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice, uint16_t reg_addr, + uint8_t width = 1, uint8_t byteorder = LSBFIRST, + uint8_t address_width = 1); + + Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice, uint16_t reg_addr, + Adafruit_BusIO_SPIRegType type, uint8_t width = 1, + uint8_t byteorder = LSBFIRST, + uint8_t address_width = 1); + + Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice, + Adafruit_SPIDevice *spidevice, + Adafruit_BusIO_SPIRegType type, uint16_t reg_addr, + uint8_t width = 1, uint8_t byteorder = LSBFIRST, + uint8_t address_width = 1); + + bool read(uint8_t *buffer, uint8_t len); + bool read(uint8_t *value); + bool read(uint16_t *value); + uint32_t read(void); + uint32_t readCached(void); + bool write(uint8_t *buffer, uint8_t len); + bool write(uint32_t value, uint8_t numbytes = 0); + + uint8_t width(void); + + void setWidth(uint8_t width); + void setAddress(uint16_t address); + void setAddressWidth(uint16_t address_width); + + void print(Stream *s = &Serial); + void println(Stream *s = &Serial); + +private: + Adafruit_I2CDevice *_i2cdevice; + Adafruit_SPIDevice *_spidevice; + Adafruit_BusIO_SPIRegType _spiregtype; + uint16_t _address; + uint8_t _width, _addrwidth, _byteorder; + uint8_t _buffer[4]; // we won't support anything larger than uint32 for + // non-buffered read + uint32_t _cached = 0; +}; + +/*! + * @brief The class which defines a slice of bits from within a device register + * (a location to read/write data from) + */ +class Adafruit_BusIO_RegisterBits { +public: + Adafruit_BusIO_RegisterBits(Adafruit_BusIO_Register *reg, uint8_t bits, + uint8_t shift); + bool write(uint32_t value); + uint32_t read(void); + +private: + Adafruit_BusIO_Register *_register; + uint8_t _bits, _shift; +}; + +#endif // SPI exists +#endif // BusIO_Register_h diff --git a/lib/Adafruit BusIO/Adafruit_I2CDevice.cpp b/lib/Adafruit BusIO/Adafruit_I2CDevice.cpp new file mode 100644 index 0000000..92c1731 --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_I2CDevice.cpp @@ -0,0 +1,317 @@ +#include "Adafruit_I2CDevice.h" + +//#define DEBUG_SERIAL Serial + +/*! + * @brief Create an I2C device at a given address + * @param addr The 7-bit I2C address for the device + * @param theWire The I2C bus to use, defaults to &Wire + */ +Adafruit_I2CDevice::Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire) { + _addr = addr; + _wire = theWire; + _begun = false; +#ifdef ARDUINO_ARCH_SAMD + _maxBufferSize = 250; // as defined in Wire.h's RingBuffer +#elif defined(ESP32) + _maxBufferSize = I2C_BUFFER_LENGTH; +#else + _maxBufferSize = 32; +#endif +} + +/*! + * @brief Initializes and does basic address detection + * @param addr_detect Whether we should attempt to detect the I2C address + * with a scan. 99% of sensors/devices don't mind, but once in a while they + * don't respond well to a scan! + * @return True if I2C initialized and a device with the addr found + */ +bool Adafruit_I2CDevice::begin(bool addr_detect) { + _wire->begin(); + _begun = true; + + if (addr_detect) { + return detected(); + } + return true; +} + +/*! + * @brief De-initialize device, turn off the Wire interface + */ +void Adafruit_I2CDevice::end(void) { + // Not all port implement Wire::end(), such as + // - ESP8266 + // - AVR core without WIRE_HAS_END + // - ESP32: end() is implemented since 2.0.1 which is latest at the moment. + // Temporarily disable for now to give time for user to update. +#if !(defined(ESP8266) || \ + (defined(ARDUINO_ARCH_AVR) && !defined(WIRE_HAS_END)) || \ + defined(ARDUINO_ARCH_ESP32)) + _wire->end(); + _begun = false; +#endif +} + +/*! + * @brief Scans I2C for the address - note will give a false-positive + * if there's no pullups on I2C + * @return True if I2C initialized and a device with the addr found + */ +bool Adafruit_I2CDevice::detected(void) { + // Init I2C if not done yet + if (!_begun && !begin()) { + return false; + } + + // A basic scanner, see if it ACK's + _wire->beginTransmission(_addr); +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("Address 0x")); + DEBUG_SERIAL.print(_addr); +#endif + if (_wire->endTransmission() == 0) { +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println(F(" Detected")); +#endif + return true; + } +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println(F(" Not detected")); +#endif + return false; +} + +/*! + * @brief Write a buffer or two to the I2C device. Cannot be more than + * maxBufferSize() bytes. + * @param buffer Pointer to buffer of data to write. This is const to + * ensure the content of this buffer doesn't change. + * @param len Number of bytes from buffer to write + * @param prefix_buffer Pointer to optional array of data to write before + * buffer. Cannot be more than maxBufferSize() bytes. This is const to + * ensure the content of this buffer doesn't change. + * @param prefix_len Number of bytes from prefix buffer to write + * @param stop Whether to send an I2C STOP signal on write + * @return True if write was successful, otherwise false. + */ +bool Adafruit_I2CDevice::write(const uint8_t *buffer, size_t len, bool stop, + const uint8_t *prefix_buffer, + size_t prefix_len) { + if ((len + prefix_len) > maxBufferSize()) { + // currently not guaranteed to work if more than 32 bytes! + // we will need to find out if some platforms have larger + // I2C buffer sizes :/ +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println(F("\tI2CDevice could not write such a large buffer")); +#endif + return false; + } + + _wire->beginTransmission(_addr); + + // Write the prefix data (usually an address) + if ((prefix_len != 0) && (prefix_buffer != nullptr)) { + if (_wire->write(prefix_buffer, prefix_len) != prefix_len) { +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println(F("\tI2CDevice failed to write")); +#endif + return false; + } + } + + // Write the data itself + if (_wire->write(buffer, len) != len) { +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println(F("\tI2CDevice failed to write")); +#endif + return false; + } + +#ifdef DEBUG_SERIAL + + DEBUG_SERIAL.print(F("\tI2CWRITE @ 0x")); + DEBUG_SERIAL.print(_addr, HEX); + DEBUG_SERIAL.print(F(" :: ")); + if ((prefix_len != 0) && (prefix_buffer != nullptr)) { + for (uint16_t i = 0; i < prefix_len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(prefix_buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + } + } + for (uint16_t i = 0; i < len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + if (i % 32 == 31) { + DEBUG_SERIAL.println(); + } + } + + if (stop) { + DEBUG_SERIAL.print("\tSTOP"); + } +#endif + + if (_wire->endTransmission(stop) == 0) { +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println(); + // DEBUG_SERIAL.println("Sent!"); +#endif + return true; + } else { +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.println("\tFailed to send!"); +#endif + return false; + } +} + +/*! + * @brief Read from I2C into a buffer from the I2C device. + * Cannot be more than maxBufferSize() bytes. + * @param buffer Pointer to buffer of data to read into + * @param len Number of bytes from buffer to read. + * @param stop Whether to send an I2C STOP signal on read + * @return True if read was successful, otherwise false. + */ +bool Adafruit_I2CDevice::read(uint8_t *buffer, size_t len, bool stop) { + size_t pos = 0; + while (pos < len) { + size_t read_len = + ((len - pos) > maxBufferSize()) ? maxBufferSize() : (len - pos); + bool read_stop = (pos < (len - read_len)) ? false : stop; + if (!_read(buffer + pos, read_len, read_stop)) + return false; + pos += read_len; + } + return true; +} + +bool Adafruit_I2CDevice::_read(uint8_t *buffer, size_t len, bool stop) { +#if defined(TinyWireM_h) + size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len); +#elif defined(ARDUINO_ARCH_MEGAAVR) + size_t recv = _wire->requestFrom(_addr, len, stop); +#else + size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len, (uint8_t)stop); +#endif + + if (recv != len) { + // Not enough data available to fulfill our obligation! +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("\tI2CDevice did not receive enough data: ")); + DEBUG_SERIAL.println(recv); +#endif + return false; + } + + for (uint16_t i = 0; i < len; i++) { + buffer[i] = _wire->read(); + } + +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("\tI2CREAD @ 0x")); + DEBUG_SERIAL.print(_addr, HEX); + DEBUG_SERIAL.print(F(" :: ")); + for (uint16_t i = 0; i < len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + if (len % 32 == 31) { + DEBUG_SERIAL.println(); + } + } + DEBUG_SERIAL.println(); +#endif + + return true; +} + +/*! + * @brief Write some data, then read some data from I2C into another buffer. + * Cannot be more than maxBufferSize() bytes. The buffers can point to + * same/overlapping locations. + * @param write_buffer Pointer to buffer of data to write from + * @param write_len Number of bytes from buffer to write. + * @param read_buffer Pointer to buffer of data to read into. + * @param read_len Number of bytes from buffer to read. + * @param stop Whether to send an I2C STOP signal between the write and read + * @return True if write & read was successful, otherwise false. + */ +bool Adafruit_I2CDevice::write_then_read(const uint8_t *write_buffer, + size_t write_len, uint8_t *read_buffer, + size_t read_len, bool stop) { + if (!write(write_buffer, write_len, stop)) { + return false; + } + + return read(read_buffer, read_len); +} + +/*! + * @brief Returns the 7-bit address of this device + * @return The 7-bit address of this device + */ +uint8_t Adafruit_I2CDevice::address(void) { return _addr; } + +/*! + * @brief Change the I2C clock speed to desired (relies on + * underlying Wire support! + * @param desiredclk The desired I2C SCL frequency + * @return True if this platform supports changing I2C speed. + * Not necessarily that the speed was achieved! + */ +bool Adafruit_I2CDevice::setSpeed(uint32_t desiredclk) { +#if defined(__AVR_ATmega328__) || \ + defined(__AVR_ATmega328P__) // fix arduino core set clock + // calculate TWBR correctly + + if ((F_CPU / 18) < desiredclk) { +#ifdef DEBUG_SERIAL + Serial.println(F("I2C.setSpeed too high.")); +#endif + return false; + } + uint32_t atwbr = ((F_CPU / desiredclk) - 16) / 2; + if (atwbr > 16320) { +#ifdef DEBUG_SERIAL + Serial.println(F("I2C.setSpeed too low.")); +#endif + return false; + } + + if (atwbr <= 255) { + atwbr /= 1; + TWSR = 0x0; + } else if (atwbr <= 1020) { + atwbr /= 4; + TWSR = 0x1; + } else if (atwbr <= 4080) { + atwbr /= 16; + TWSR = 0x2; + } else { // if (atwbr <= 16320) + atwbr /= 64; + TWSR = 0x3; + } + TWBR = atwbr; + +#ifdef DEBUG_SERIAL + Serial.print(F("TWSR prescaler = ")); + Serial.println(pow(4, TWSR)); + Serial.print(F("TWBR = ")); + Serial.println(atwbr); +#endif + return true; +#elif (ARDUINO >= 157) && !defined(ARDUINO_STM32_FEATHER) && \ + !defined(TinyWireM_h) + _wire->setClock(desiredclk); + return true; + +#else + (void)desiredclk; + return false; +#endif +} diff --git a/lib/Adafruit BusIO/Adafruit_I2CDevice.h b/lib/Adafruit BusIO/Adafruit_I2CDevice.h new file mode 100644 index 0000000..6bda7ba --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_I2CDevice.h @@ -0,0 +1,36 @@ +#ifndef Adafruit_I2CDevice_h +#define Adafruit_I2CDevice_h + +#include +#include + +///< The class which defines how we will talk to this device over I2C +class Adafruit_I2CDevice { +public: + Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire = &Wire); + uint8_t address(void); + bool begin(bool addr_detect = true); + void end(void); + bool detected(void); + + bool read(uint8_t *buffer, size_t len, bool stop = true); + bool write(const uint8_t *buffer, size_t len, bool stop = true, + const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0); + bool write_then_read(const uint8_t *write_buffer, size_t write_len, + uint8_t *read_buffer, size_t read_len, + bool stop = false); + bool setSpeed(uint32_t desiredclk); + + /*! @brief How many bytes we can read in a transaction + * @return The size of the Wire receive/transmit buffer */ + size_t maxBufferSize() { return _maxBufferSize; } + +private: + uint8_t _addr; + TwoWire *_wire; + bool _begun; + size_t _maxBufferSize; + bool _read(uint8_t *buffer, size_t len, bool stop); +}; + +#endif // Adafruit_I2CDevice_h diff --git a/lib/Adafruit BusIO/Adafruit_I2CRegister.h b/lib/Adafruit BusIO/Adafruit_I2CRegister.h new file mode 100644 index 0000000..186850f --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_I2CRegister.h @@ -0,0 +1,10 @@ +#ifndef _ADAFRUIT_I2C_REGISTER_H_ +#define _ADAFRUIT_I2C_REGISTER_H_ + +#include +#include + +typedef Adafruit_BusIO_Register Adafruit_I2CRegister; +typedef Adafruit_BusIO_RegisterBits Adafruit_I2CRegisterBits; + +#endif diff --git a/lib/Adafruit BusIO/Adafruit_SPIDevice.cpp b/lib/Adafruit BusIO/Adafruit_SPIDevice.cpp new file mode 100644 index 0000000..034dc08 --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_SPIDevice.cpp @@ -0,0 +1,508 @@ +#include "Adafruit_SPIDevice.h" + +//#define DEBUG_SERIAL Serial + +/*! + * @brief Create an SPI device with the given CS pin and settings + * @param cspin The arduino pin number to use for chip select + * @param freq The SPI clock frequency to use, defaults to 1MHz + * @param dataOrder The SPI data order to use for bits within each byte, + * defaults to SPI_BITORDER_MSBFIRST + * @param dataMode The SPI mode to use, defaults to SPI_MODE0 + * @param theSPI The SPI bus to use, defaults to &theSPI + */ +Adafruit_SPIDevice::Adafruit_SPIDevice(int8_t cspin, uint32_t freq, + BusIOBitOrder dataOrder, + uint8_t dataMode, SPIClass *theSPI) { +#ifdef BUSIO_HAS_HW_SPI + _cs = cspin; + _sck = _mosi = _miso = -1; + _spi = theSPI; + _begun = false; + _spiSetting = new SPISettings(freq, dataOrder, dataMode); + _freq = freq; + _dataOrder = dataOrder; + _dataMode = dataMode; +#else + // unused, but needed to suppress compiler warns + (void)cspin; + (void)freq; + (void)dataOrder; + (void)dataMode; + (void)theSPI; +#endif +} + +/*! + * @brief Create an SPI device with the given CS pin and settings + * @param cspin The arduino pin number to use for chip select + * @param sckpin The arduino pin number to use for SCK + * @param misopin The arduino pin number to use for MISO, set to -1 if not + * used + * @param mosipin The arduino pin number to use for MOSI, set to -1 if not + * used + * @param freq The SPI clock frequency to use, defaults to 1MHz + * @param dataOrder The SPI data order to use for bits within each byte, + * defaults to SPI_BITORDER_MSBFIRST + * @param dataMode The SPI mode to use, defaults to SPI_MODE0 + */ +Adafruit_SPIDevice::Adafruit_SPIDevice(int8_t cspin, int8_t sckpin, + int8_t misopin, int8_t mosipin, + uint32_t freq, BusIOBitOrder dataOrder, + uint8_t dataMode) { + _cs = cspin; + _sck = sckpin; + _miso = misopin; + _mosi = mosipin; + +#ifdef BUSIO_USE_FAST_PINIO + csPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(cspin)); + csPinMask = digitalPinToBitMask(cspin); + if (mosipin != -1) { + mosiPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(mosipin)); + mosiPinMask = digitalPinToBitMask(mosipin); + } + if (misopin != -1) { + misoPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(misopin)); + misoPinMask = digitalPinToBitMask(misopin); + } + clkPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(sckpin)); + clkPinMask = digitalPinToBitMask(sckpin); +#endif + + _freq = freq; + _dataOrder = dataOrder; + _dataMode = dataMode; + _begun = false; +} + +/*! + * @brief Release memory allocated in constructors + */ +Adafruit_SPIDevice::~Adafruit_SPIDevice() { + if (_spiSetting) + delete _spiSetting; +} + +/*! + * @brief Initializes SPI bus and sets CS pin high + * @return Always returns true because there's no way to test success of SPI + * init + */ +bool Adafruit_SPIDevice::begin(void) { + if (_cs != -1) { + pinMode(_cs, OUTPUT); + digitalWrite(_cs, HIGH); + } + + if (_spi) { // hardware SPI +#ifdef BUSIO_HAS_HW_SPI + _spi->begin(); +#endif + } else { + pinMode(_sck, OUTPUT); + + if ((_dataMode == SPI_MODE0) || (_dataMode == SPI_MODE1)) { + // idle low on mode 0 and 1 + digitalWrite(_sck, LOW); + } else { + // idle high on mode 2 or 3 + digitalWrite(_sck, HIGH); + } + if (_mosi != -1) { + pinMode(_mosi, OUTPUT); + digitalWrite(_mosi, HIGH); + } + if (_miso != -1) { + pinMode(_miso, INPUT); + } + } + + _begun = true; + return true; +} + +/*! + * @brief Transfer (send/receive) a buffer over hard/soft SPI, without + * transaction management + * @param buffer The buffer to send and receive at the same time + * @param len The number of bytes to transfer + */ +void Adafruit_SPIDevice::transfer(uint8_t *buffer, size_t len) { + // + // HARDWARE SPI + // + if (_spi) { +#ifdef BUSIO_HAS_HW_SPI +#if defined(SPARK) + _spi->transfer(buffer, buffer, len, nullptr); +#elif defined(STM32) + for (size_t i = 0; i < len; i++) { + _spi->transfer(buffer[i]); + } +#else + _spi->transfer(buffer, len); +#endif + return; +#endif + } + + // + // SOFTWARE SPI + // + uint8_t startbit; + if (_dataOrder == SPI_BITORDER_LSBFIRST) { + startbit = 0x1; + } else { + startbit = 0x80; + } + + bool towrite, lastmosi = !(buffer[0] & startbit); + uint8_t bitdelay_us = (1000000 / _freq) / 2; + + for (size_t i = 0; i < len; i++) { + uint8_t reply = 0; + uint8_t send = buffer[i]; + + /* + Serial.print("\tSending software SPI byte 0x"); + Serial.print(send, HEX); + Serial.print(" -> 0x"); + */ + + // Serial.print(send, HEX); + for (uint8_t b = startbit; b != 0; + b = (_dataOrder == SPI_BITORDER_LSBFIRST) ? b << 1 : b >> 1) { + + if (bitdelay_us) { + delayMicroseconds(bitdelay_us); + } + + if (_dataMode == SPI_MODE0 || _dataMode == SPI_MODE2) { + towrite = send & b; + if ((_mosi != -1) && (lastmosi != towrite)) { +#ifdef BUSIO_USE_FAST_PINIO + if (towrite) + *mosiPort = *mosiPort | mosiPinMask; + else + *mosiPort = *mosiPort & ~mosiPinMask; +#else + digitalWrite(_mosi, towrite); +#endif + lastmosi = towrite; + } + +#ifdef BUSIO_USE_FAST_PINIO + *clkPort = *clkPort | clkPinMask; // Clock high +#else + digitalWrite(_sck, HIGH); +#endif + + if (bitdelay_us) { + delayMicroseconds(bitdelay_us); + } + + if (_miso != -1) { +#ifdef BUSIO_USE_FAST_PINIO + if (*misoPort & misoPinMask) { +#else + if (digitalRead(_miso)) { +#endif + reply |= b; + } + } + +#ifdef BUSIO_USE_FAST_PINIO + *clkPort = *clkPort & ~clkPinMask; // Clock low +#else + digitalWrite(_sck, LOW); +#endif + } else { // if (_dataMode == SPI_MODE1 || _dataMode == SPI_MODE3) + +#ifdef BUSIO_USE_FAST_PINIO + *clkPort = *clkPort | clkPinMask; // Clock high +#else + digitalWrite(_sck, HIGH); +#endif + + if (bitdelay_us) { + delayMicroseconds(bitdelay_us); + } + + if (_mosi != -1) { +#ifdef BUSIO_USE_FAST_PINIO + if (send & b) + *mosiPort = *mosiPort | mosiPinMask; + else + *mosiPort = *mosiPort & ~mosiPinMask; +#else + digitalWrite(_mosi, send & b); +#endif + } + +#ifdef BUSIO_USE_FAST_PINIO + *clkPort = *clkPort & ~clkPinMask; // Clock low +#else + digitalWrite(_sck, LOW); +#endif + + if (_miso != -1) { +#ifdef BUSIO_USE_FAST_PINIO + if (*misoPort & misoPinMask) { +#else + if (digitalRead(_miso)) { +#endif + reply |= b; + } + } + } + if (_miso != -1) { + buffer[i] = reply; + } + } + } + return; +} + +/*! + * @brief Transfer (send/receive) one byte over hard/soft SPI, without + * transaction management + * @param send The byte to send + * @return The byte received while transmitting + */ +uint8_t Adafruit_SPIDevice::transfer(uint8_t send) { + uint8_t data = send; + transfer(&data, 1); + return data; +} + +/*! + * @brief Manually begin a transaction (calls beginTransaction if hardware + * SPI) + */ +void Adafruit_SPIDevice::beginTransaction(void) { + if (_spi) { +#ifdef BUSIO_HAS_HW_SPI + _spi->beginTransaction(*_spiSetting); +#endif + } +} + +/*! + * @brief Manually end a transaction (calls endTransaction if hardware SPI) + */ +void Adafruit_SPIDevice::endTransaction(void) { + if (_spi) { +#ifdef BUSIO_HAS_HW_SPI + _spi->endTransaction(); +#endif + } +} + +/*! + * @brief Assert/Deassert the CS pin if it is defined + * @param value The state the CS is set to + */ +void Adafruit_SPIDevice::setChipSelect(int value) { + if (_cs != -1) { + digitalWrite(_cs, value); + } +} + +/*! + * @brief Write a buffer or two to the SPI device, with transaction + * management. + * @brief Manually begin a transaction (calls beginTransaction if hardware + * SPI) with asserting the CS pin + */ +void Adafruit_SPIDevice::beginTransactionWithAssertingCS() { + beginTransaction(); + setChipSelect(LOW); +} + +/*! + * @brief Manually end a transaction (calls endTransaction if hardware SPI) + * with deasserting the CS pin + */ +void Adafruit_SPIDevice::endTransactionWithDeassertingCS() { + setChipSelect(HIGH); + endTransaction(); +} + +/*! + * @brief Write a buffer or two to the SPI device, with transaction + * management. + * @param buffer Pointer to buffer of data to write + * @param len Number of bytes from buffer to write + * @param prefix_buffer Pointer to optional array of data to write before + * buffer. + * @param prefix_len Number of bytes from prefix buffer to write + * @return Always returns true because there's no way to test success of SPI + * writes + */ +bool Adafruit_SPIDevice::write(const uint8_t *buffer, size_t len, + const uint8_t *prefix_buffer, + size_t prefix_len) { + beginTransactionWithAssertingCS(); + + // do the writing +#if defined(ARDUINO_ARCH_ESP32) + if (_spi) { + if (prefix_len > 0) { + _spi->transferBytes((uint8_t *)prefix_buffer, nullptr, prefix_len); + } + if (len > 0) { + _spi->transferBytes((uint8_t *)buffer, nullptr, len); + } + } else +#endif + { + for (size_t i = 0; i < prefix_len; i++) { + transfer(prefix_buffer[i]); + } + for (size_t i = 0; i < len; i++) { + transfer(buffer[i]); + } + } + endTransactionWithDeassertingCS(); + +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("\tSPIDevice Wrote: ")); + if ((prefix_len != 0) && (prefix_buffer != nullptr)) { + for (uint16_t i = 0; i < prefix_len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(prefix_buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + } + } + for (uint16_t i = 0; i < len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + if (i % 32 == 31) { + DEBUG_SERIAL.println(); + } + } + DEBUG_SERIAL.println(); +#endif + + return true; +} + +/*! + * @brief Read from SPI into a buffer from the SPI device, with transaction + * management. + * @param buffer Pointer to buffer of data to read into + * @param len Number of bytes from buffer to read. + * @param sendvalue The 8-bits of data to write when doing the data read, + * defaults to 0xFF + * @return Always returns true because there's no way to test success of SPI + * writes + */ +bool Adafruit_SPIDevice::read(uint8_t *buffer, size_t len, uint8_t sendvalue) { + memset(buffer, sendvalue, len); // clear out existing buffer + + beginTransactionWithAssertingCS(); + transfer(buffer, len); + endTransactionWithDeassertingCS(); + +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("\tSPIDevice Read: ")); + for (uint16_t i = 0; i < len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + if (len % 32 == 31) { + DEBUG_SERIAL.println(); + } + } + DEBUG_SERIAL.println(); +#endif + + return true; +} + +/*! + * @brief Write some data, then read some data from SPI into another buffer, + * with transaction management. The buffers can point to same/overlapping + * locations. This does not transmit-receive at the same time! + * @param write_buffer Pointer to buffer of data to write from + * @param write_len Number of bytes from buffer to write. + * @param read_buffer Pointer to buffer of data to read into. + * @param read_len Number of bytes from buffer to read. + * @param sendvalue The 8-bits of data to write when doing the data read, + * defaults to 0xFF + * @return Always returns true because there's no way to test success of SPI + * writes + */ +bool Adafruit_SPIDevice::write_then_read(const uint8_t *write_buffer, + size_t write_len, uint8_t *read_buffer, + size_t read_len, uint8_t sendvalue) { + beginTransactionWithAssertingCS(); + // do the writing +#if defined(ARDUINO_ARCH_ESP32) + if (_spi) { + if (write_len > 0) { + _spi->transferBytes((uint8_t *)write_buffer, nullptr, write_len); + } + } else +#endif + { + for (size_t i = 0; i < write_len; i++) { + transfer(write_buffer[i]); + } + } + +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("\tSPIDevice Wrote: ")); + for (uint16_t i = 0; i < write_len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(write_buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + if (write_len % 32 == 31) { + DEBUG_SERIAL.println(); + } + } + DEBUG_SERIAL.println(); +#endif + + // do the reading + for (size_t i = 0; i < read_len; i++) { + read_buffer[i] = transfer(sendvalue); + } + +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.print(F("\tSPIDevice Read: ")); + for (uint16_t i = 0; i < read_len; i++) { + DEBUG_SERIAL.print(F("0x")); + DEBUG_SERIAL.print(read_buffer[i], HEX); + DEBUG_SERIAL.print(F(", ")); + if (read_len % 32 == 31) { + DEBUG_SERIAL.println(); + } + } + DEBUG_SERIAL.println(); +#endif + + endTransactionWithDeassertingCS(); + + return true; +} + +/*! + * @brief Write some data and read some data at the same time from SPI + * into the same buffer, with transaction management. This is basicaly a wrapper + * for transfer() with CS-pin and transaction management. This /does/ + * transmit-receive at the same time! + * @param buffer Pointer to buffer of data to write/read to/from + * @param len Number of bytes from buffer to write/read. + * @return Always returns true because there's no way to test success of SPI + * writes + */ +bool Adafruit_SPIDevice::write_and_read(uint8_t *buffer, size_t len) { + beginTransactionWithAssertingCS(); + transfer(buffer, len); + endTransactionWithDeassertingCS(); + + return true; +} diff --git a/lib/Adafruit BusIO/Adafruit_SPIDevice.h b/lib/Adafruit BusIO/Adafruit_SPIDevice.h new file mode 100644 index 0000000..498c583 --- /dev/null +++ b/lib/Adafruit BusIO/Adafruit_SPIDevice.h @@ -0,0 +1,143 @@ +#ifndef Adafruit_SPIDevice_h +#define Adafruit_SPIDevice_h + +#include + +#if !defined(SPI_INTERFACES_COUNT) || \ + (defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0)) +// HW SPI available +#include +#define BUSIO_HAS_HW_SPI +#else +// SW SPI ONLY +enum { SPI_MODE0, SPI_MODE1, SPI_MODE2, _SPI_MODE4 }; +typedef uint8_t SPIClass; +#endif + +// some modern SPI definitions don't have BitOrder enum +#if (defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)) || \ + defined(ESP8266) || defined(TEENSYDUINO) || defined(SPARK) || \ + defined(ARDUINO_ARCH_SPRESENSE) || defined(MEGATINYCORE) || \ + defined(DXCORE) || defined(ARDUINO_AVR_ATmega4809) || \ + defined(ARDUINO_AVR_ATmega4808) || defined(ARDUINO_AVR_ATmega3209) || \ + defined(ARDUINO_AVR_ATmega3208) || defined(ARDUINO_AVR_ATmega1609) || \ + defined(ARDUINO_AVR_ATmega1608) || defined(ARDUINO_AVR_ATmega809) || \ + defined(ARDUINO_AVR_ATmega808) || defined(ARDUINO_ARCH_ARC32) || \ + defined(ARDUINO_ARCH_XMC) || defined(ARDUINO_SILABS) + +typedef enum _BitOrder { + SPI_BITORDER_MSBFIRST = MSBFIRST, + SPI_BITORDER_LSBFIRST = LSBFIRST, +} BusIOBitOrder; + +#elif defined(ESP32) || defined(__ASR6501__) || defined(__ASR6502__) + +// some modern SPI definitions don't have BitOrder enum and have different SPI +// mode defines +typedef enum _BitOrder { + SPI_BITORDER_MSBFIRST = SPI_MSBFIRST, + SPI_BITORDER_LSBFIRST = SPI_LSBFIRST, +} BusIOBitOrder; + +#else +// Some platforms have a BitOrder enum but its named MSBFIRST/LSBFIRST +#define SPI_BITORDER_MSBFIRST MSBFIRST +#define SPI_BITORDER_LSBFIRST LSBFIRST +typedef BitOrder BusIOBitOrder; +#endif + +#if defined(__IMXRT1062__) // Teensy 4.x +// *Warning* I disabled the usage of FAST_PINIO as the set/clear operations +// used in the cpp file are not atomic and can effect multiple IO pins +// and if an interrupt happens in between the time the code reads the register +// and writes out the updated value, that changes one or more other IO pins +// on that same IO port, those change will be clobbered when the updated +// values are written back. A fast version can be implemented that uses the +// ports set and clear registers which are atomic. +// typedef volatile uint32_t BusIO_PortReg; +// typedef uint32_t BusIO_PortMask; +//#define BUSIO_USE_FAST_PINIO + +#elif defined(ARDUINO_ARCH_XMC) +#undef BUSIO_USE_FAST_PINIO + +#elif defined(__AVR__) || defined(TEENSYDUINO) +typedef volatile uint8_t BusIO_PortReg; +typedef uint8_t BusIO_PortMask; +#define BUSIO_USE_FAST_PINIO + +#elif defined(ESP8266) || defined(ESP32) || defined(__SAM3X8E__) || \ + defined(ARDUINO_ARCH_SAMD) +typedef volatile uint32_t BusIO_PortReg; +typedef uint32_t BusIO_PortMask; +#define BUSIO_USE_FAST_PINIO + +#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) && \ + !defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_ARCH_RP2040) && \ + !defined(ARDUINO_SILABS) +typedef volatile uint32_t BusIO_PortReg; +typedef uint32_t BusIO_PortMask; +#if !defined(__ASR6501__) && !defined(__ASR6502__) +#define BUSIO_USE_FAST_PINIO +#endif + +#else +#undef BUSIO_USE_FAST_PINIO +#endif + +/**! The class which defines how we will talk to this device over SPI **/ +class Adafruit_SPIDevice { +public: +#ifdef BUSIO_HAS_HW_SPI + Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000, + BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST, + uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = &SPI); +#else + Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000, + BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST, + uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = nullptr); +#endif + Adafruit_SPIDevice(int8_t cspin, int8_t sck, int8_t miso, int8_t mosi, + uint32_t freq = 1000000, + BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST, + uint8_t dataMode = SPI_MODE0); + ~Adafruit_SPIDevice(); + + bool begin(void); + bool read(uint8_t *buffer, size_t len, uint8_t sendvalue = 0xFF); + bool write(const uint8_t *buffer, size_t len, + const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0); + bool write_then_read(const uint8_t *write_buffer, size_t write_len, + uint8_t *read_buffer, size_t read_len, + uint8_t sendvalue = 0xFF); + bool write_and_read(uint8_t *buffer, size_t len); + + uint8_t transfer(uint8_t send); + void transfer(uint8_t *buffer, size_t len); + void beginTransaction(void); + void endTransaction(void); + void beginTransactionWithAssertingCS(); + void endTransactionWithDeassertingCS(); + +private: +#ifdef BUSIO_HAS_HW_SPI + SPIClass *_spi = nullptr; + SPISettings *_spiSetting = nullptr; +#else + uint8_t *_spi = nullptr; + uint8_t *_spiSetting = nullptr; +#endif + uint32_t _freq; + BusIOBitOrder _dataOrder; + uint8_t _dataMode; + void setChipSelect(int value); + + int8_t _cs, _sck, _mosi, _miso; +#ifdef BUSIO_USE_FAST_PINIO + BusIO_PortReg *mosiPort, *clkPort, *misoPort, *csPort; + BusIO_PortMask mosiPinMask, misoPinMask, clkPinMask, csPinMask; +#endif + bool _begun; +}; + +#endif // Adafruit_SPIDevice_h diff --git a/lib/Adafruit BusIO/CMakeLists.txt b/lib/Adafruit BusIO/CMakeLists.txt new file mode 100644 index 0000000..a7ea6e9 --- /dev/null +++ b/lib/Adafruit BusIO/CMakeLists.txt @@ -0,0 +1,11 @@ +# Adafruit Bus IO Library +# https://github.com/adafruit/Adafruit_BusIO +# MIT License + +cmake_minimum_required(VERSION 3.5) + +idf_component_register(SRCS "Adafruit_I2CDevice.cpp" "Adafruit_BusIO_Register.cpp" "Adafruit_SPIDevice.cpp" + INCLUDE_DIRS "." + REQUIRES arduino) + +project(Adafruit_BusIO) diff --git a/lib/Adafruit BusIO/LICENSE b/lib/Adafruit BusIO/LICENSE new file mode 100644 index 0000000..860e3e2 --- /dev/null +++ b/lib/Adafruit BusIO/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Adafruit Industries + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/lib/Adafruit BusIO/README.md b/lib/Adafruit BusIO/README.md new file mode 100644 index 0000000..1cc06a1 --- /dev/null +++ b/lib/Adafruit BusIO/README.md @@ -0,0 +1,8 @@ +# Adafruit Bus IO Library [![Build Status](https://github.com/adafruit/Adafruit_BusIO/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_BusIO/actions) + + +This is a helper library to abstract away I2C & SPI transactions and registers + +Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! + +MIT license, all text above must be included in any redistribution diff --git a/lib/Adafruit BusIO/component.mk b/lib/Adafruit BusIO/component.mk new file mode 100644 index 0000000..004b18e --- /dev/null +++ b/lib/Adafruit BusIO/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_INCLUDEDIRS = . diff --git a/lib/Adafruit BusIO/examples/i2c_address_detect/i2c_address_detect.ino b/lib/Adafruit BusIO/examples/i2c_address_detect/i2c_address_detect.ino new file mode 100644 index 0000000..b150525 --- /dev/null +++ b/lib/Adafruit BusIO/examples/i2c_address_detect/i2c_address_detect.ino @@ -0,0 +1,21 @@ +#include + +Adafruit_I2CDevice i2c_dev = Adafruit_I2CDevice(0x10); + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("I2C address detection test"); + + if (!i2c_dev.begin()) { + Serial.print("Did not find device at 0x"); + Serial.println(i2c_dev.address(), HEX); + while (1); + } + Serial.print("Device found on address 0x"); + Serial.println(i2c_dev.address(), HEX); +} + +void loop() { + +} diff --git a/lib/Adafruit BusIO/examples/i2c_readwrite/i2c_readwrite.ino b/lib/Adafruit BusIO/examples/i2c_readwrite/i2c_readwrite.ino new file mode 100644 index 0000000..909cf31 --- /dev/null +++ b/lib/Adafruit BusIO/examples/i2c_readwrite/i2c_readwrite.ino @@ -0,0 +1,41 @@ +#include + +#define I2C_ADDRESS 0x60 +Adafruit_I2CDevice i2c_dev = Adafruit_I2CDevice(I2C_ADDRESS); + + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("I2C device read and write test"); + + if (!i2c_dev.begin()) { + Serial.print("Did not find device at 0x"); + Serial.println(i2c_dev.address(), HEX); + while (1); + } + Serial.print("Device found on address 0x"); + Serial.println(i2c_dev.address(), HEX); + + uint8_t buffer[32]; + // Try to read 32 bytes + i2c_dev.read(buffer, 32); + Serial.print("Read: "); + for (uint8_t i=0; i<32; i++) { + Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", "); + } + Serial.println(); + + // read a register by writing first, then reading + buffer[0] = 0x0C; // we'll reuse the same buffer + i2c_dev.write_then_read(buffer, 1, buffer, 2, false); + Serial.print("Write then Read: "); + for (uint8_t i=0; i<2; i++) { + Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", "); + } + Serial.println(); +} + +void loop() { + +} diff --git a/lib/Adafruit BusIO/examples/i2c_registers/i2c_registers.ino b/lib/Adafruit BusIO/examples/i2c_registers/i2c_registers.ino new file mode 100644 index 0000000..41a3043 --- /dev/null +++ b/lib/Adafruit BusIO/examples/i2c_registers/i2c_registers.ino @@ -0,0 +1,38 @@ +#include +#include + +#define I2C_ADDRESS 0x60 +Adafruit_I2CDevice i2c_dev = Adafruit_I2CDevice(I2C_ADDRESS); + + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("I2C device register test"); + + if (!i2c_dev.begin()) { + Serial.print("Did not find device at 0x"); + Serial.println(i2c_dev.address(), HEX); + while (1); + } + Serial.print("Device found on address 0x"); + Serial.println(i2c_dev.address(), HEX); + + Adafruit_BusIO_Register id_reg = Adafruit_BusIO_Register(&i2c_dev, 0x0C, 2, LSBFIRST); + uint16_t id; + id_reg.read(&id); + Serial.print("ID register = 0x"); Serial.println(id, HEX); + + Adafruit_BusIO_Register thresh_reg = Adafruit_BusIO_Register(&i2c_dev, 0x01, 2, LSBFIRST); + uint16_t thresh; + thresh_reg.read(&thresh); + Serial.print("Initial threshold register = 0x"); Serial.println(thresh, HEX); + + thresh_reg.write(~thresh); + + Serial.print("Post threshold register = 0x"); Serial.println(thresh_reg.read(), HEX); +} + +void loop() { + +} \ No newline at end of file diff --git a/lib/Adafruit BusIO/examples/i2corspi_register/i2corspi_register.ino b/lib/Adafruit BusIO/examples/i2corspi_register/i2corspi_register.ino new file mode 100644 index 0000000..992a2e0 --- /dev/null +++ b/lib/Adafruit BusIO/examples/i2corspi_register/i2corspi_register.ino @@ -0,0 +1,38 @@ +#include + +// Define which interface to use by setting the unused interface to NULL! + +#define SPIDEVICE_CS 10 +Adafruit_SPIDevice *spi_dev = NULL; // new Adafruit_SPIDevice(SPIDEVICE_CS); + +#define I2C_ADDRESS 0x5D +Adafruit_I2CDevice *i2c_dev = new Adafruit_I2CDevice(I2C_ADDRESS); + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("I2C or SPI device register test"); + + if (spi_dev && !spi_dev->begin()) { + Serial.println("Could not initialize SPI device"); + } + + if (i2c_dev) { + if (i2c_dev->begin()) { + Serial.print("Device found on I2C address 0x"); + Serial.println(i2c_dev->address(), HEX); + } else { + Serial.print("Did not find I2C device at 0x"); + Serial.println(i2c_dev->address(), HEX); + } + } + + Adafruit_BusIO_Register id_reg = Adafruit_BusIO_Register(i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, 0x0F); + uint8_t id=0; + id_reg.read(&id); + Serial.print("ID register = 0x"); Serial.println(id, HEX); +} + +void loop() { + +} diff --git a/lib/Adafruit BusIO/examples/spi_modetest/spi_modetest.ino b/lib/Adafruit BusIO/examples/spi_modetest/spi_modetest.ino new file mode 100644 index 0000000..10168c5 --- /dev/null +++ b/lib/Adafruit BusIO/examples/spi_modetest/spi_modetest.ino @@ -0,0 +1,29 @@ +#include + +#define SPIDEVICE_CS 10 +Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS, 100000, SPI_BITORDER_MSBFIRST, SPI_MODE1); +//Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS, 13, 12, 11, 100000, SPI_BITORDER_MSBFIRST, SPI_MODE1); + + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("SPI device mode test"); + + if (!spi_dev.begin()) { + Serial.println("Could not initialize SPI device"); + while (1); + } +} + +void loop() { + Serial.println("\n\nTransfer test"); + for (uint16_t x=0; x<=0xFF; x++) { + uint8_t i = x; + Serial.print("0x"); Serial.print(i, HEX); + spi_dev.read(&i, 1, i); + Serial.print("/"); Serial.print(i, HEX); + Serial.print(", "); + delay(25); + } +} \ No newline at end of file diff --git a/lib/Adafruit BusIO/examples/spi_readwrite/spi_readwrite.ino b/lib/Adafruit BusIO/examples/spi_readwrite/spi_readwrite.ino new file mode 100644 index 0000000..6f2c063 --- /dev/null +++ b/lib/Adafruit BusIO/examples/spi_readwrite/spi_readwrite.ino @@ -0,0 +1,39 @@ +#include + +#define SPIDEVICE_CS 10 +Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS); + + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("SPI device read and write test"); + + if (!spi_dev.begin()) { + Serial.println("Could not initialize SPI device"); + while (1); + } + + uint8_t buffer[32]; + + // Try to read 32 bytes + spi_dev.read(buffer, 32); + Serial.print("Read: "); + for (uint8_t i=0; i<32; i++) { + Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", "); + } + Serial.println(); + + // read a register by writing first, then reading + buffer[0] = 0x8F; // we'll reuse the same buffer + spi_dev.write_then_read(buffer, 1, buffer, 2, false); + Serial.print("Write then Read: "); + for (uint8_t i=0; i<2; i++) { + Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", "); + } + Serial.println(); +} + +void loop() { + +} diff --git a/lib/Adafruit BusIO/examples/spi_register_bits/spi_register_bits.ino b/lib/Adafruit BusIO/examples/spi_register_bits/spi_register_bits.ino new file mode 100644 index 0000000..e70a17b --- /dev/null +++ b/lib/Adafruit BusIO/examples/spi_register_bits/spi_register_bits.ino @@ -0,0 +1,192 @@ +/*************************************************** + + This is an example for how to use Adafruit_BusIO_RegisterBits from Adafruit_BusIO library. + + Designed specifically to work with the Adafruit RTD Sensor + ----> https://www.adafruit.com/products/3328 + uisng a MAX31865 RTD-to-Digital Converter + ----> https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf + + This sensor uses SPI to communicate, 4 pins are required to + interface. + A fifth pin helps to detect when a new conversion is ready. + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Example written (2020/3) by Andreas Hardtung/AnHard. + BSD license, all text above must be included in any redistribution + ****************************************************/ + +#include +#include + +#define MAX31865_SPI_SPEED (5000000) +#define MAX31865_SPI_BITORDER (SPI_BITORDER_MSBFIRST) +#define MAX31865_SPI_MODE (SPI_MODE1) + +#define MAX31865_SPI_CS (10) +#define MAX31865_READY_PIN (2) + + +Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice( MAX31865_SPI_CS, MAX31865_SPI_SPEED, MAX31865_SPI_BITORDER, MAX31865_SPI_MODE, &SPI); // Hardware SPI +// Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice( MAX31865_SPI_CS, 13, 12, 11, MAX31865_SPI_SPEED, MAX31865_SPI_BITORDER, MAX31865_SPI_MODE); // Software SPI + +// MAX31865 chip related ********************************************************************************************* +Adafruit_BusIO_Register config_reg = Adafruit_BusIO_Register(&spi_dev, 0x00, ADDRBIT8_HIGH_TOWRITE, 1, MSBFIRST); +Adafruit_BusIO_RegisterBits bias_bit = Adafruit_BusIO_RegisterBits(&config_reg, 1, 7); +Adafruit_BusIO_RegisterBits auto_bit = Adafruit_BusIO_RegisterBits(&config_reg, 1, 6); +Adafruit_BusIO_RegisterBits oneS_bit = Adafruit_BusIO_RegisterBits(&config_reg, 1, 5); +Adafruit_BusIO_RegisterBits wire_bit = Adafruit_BusIO_RegisterBits(&config_reg, 1, 4); +Adafruit_BusIO_RegisterBits faultT_bits = Adafruit_BusIO_RegisterBits(&config_reg, 2, 2); +Adafruit_BusIO_RegisterBits faultR_bit = Adafruit_BusIO_RegisterBits(&config_reg, 1, 1); +Adafruit_BusIO_RegisterBits fi50hz_bit = Adafruit_BusIO_RegisterBits(&config_reg, 1, 0); + +Adafruit_BusIO_Register rRatio_reg = Adafruit_BusIO_Register(&spi_dev, 0x01, ADDRBIT8_HIGH_TOWRITE, 2, MSBFIRST); +Adafruit_BusIO_RegisterBits rRatio_bits = Adafruit_BusIO_RegisterBits(&rRatio_reg, 15, 1); +Adafruit_BusIO_RegisterBits fault_bit = Adafruit_BusIO_RegisterBits(&rRatio_reg, 1, 0); + +Adafruit_BusIO_Register maxRratio_reg = Adafruit_BusIO_Register(&spi_dev, 0x03, ADDRBIT8_HIGH_TOWRITE, 2, MSBFIRST); +Adafruit_BusIO_RegisterBits maxRratio_bits = Adafruit_BusIO_RegisterBits(&maxRratio_reg, 15, 1); + +Adafruit_BusIO_Register minRratio_reg = Adafruit_BusIO_Register(&spi_dev, 0x05, ADDRBIT8_HIGH_TOWRITE, 2, MSBFIRST); +Adafruit_BusIO_RegisterBits minRratio_bits = Adafruit_BusIO_RegisterBits(&minRratio_reg, 15, 1); + +Adafruit_BusIO_Register fault_reg = Adafruit_BusIO_Register(&spi_dev, 0x07, ADDRBIT8_HIGH_TOWRITE, 1, MSBFIRST); +Adafruit_BusIO_RegisterBits range_high_fault_bit = Adafruit_BusIO_RegisterBits(&fault_reg, 1, 7); +Adafruit_BusIO_RegisterBits range_low_fault_bit = Adafruit_BusIO_RegisterBits(&fault_reg, 1, 6); +Adafruit_BusIO_RegisterBits refin_high_fault_bit = Adafruit_BusIO_RegisterBits(&fault_reg, 1, 5); +Adafruit_BusIO_RegisterBits refin_low_fault_bit = Adafruit_BusIO_RegisterBits(&fault_reg, 1, 4); +Adafruit_BusIO_RegisterBits rtdin_low_fault_bit = Adafruit_BusIO_RegisterBits(&fault_reg, 1, 3); +Adafruit_BusIO_RegisterBits voltage_fault_bit = Adafruit_BusIO_RegisterBits(&fault_reg, 1, 2); + +// Print the details of the configuration register. +void printConfig( void ) { + Serial.print("BIAS: "); if (bias_bit.read() ) Serial.print("ON"); else Serial.print("OFF"); + Serial.print(", AUTO: "); if (auto_bit.read() ) Serial.print("ON"); else Serial.print("OFF"); + Serial.print(", ONES: "); if (oneS_bit.read() ) Serial.print("ON"); else Serial.print("OFF"); + Serial.print(", WIRE: "); if (wire_bit.read() ) Serial.print("3"); else Serial.print("2/4"); + Serial.print(", FAULTCLEAR: "); if (faultR_bit.read() ) Serial.print("ON"); else Serial.print("OFF"); + Serial.print(", "); if (fi50hz_bit.read() ) Serial.print("50HZ"); else Serial.print("60HZ"); + Serial.println(); +} + +// Check and print faults. Then clear them. +void checkFaults( void ) { + if (fault_bit.read()) { + Serial.print("MAX: "); Serial.println(maxRratio_bits.read()); + Serial.print("VAL: "); Serial.println( rRatio_bits.read()); + Serial.print("MIN: "); Serial.println(minRratio_bits.read()); + + if (range_high_fault_bit.read() ) Serial.println("Range high fault"); + if ( range_low_fault_bit.read() ) Serial.println("Range low fault"); + if (refin_high_fault_bit.read() ) Serial.println("REFIN high fault"); + if ( refin_low_fault_bit.read() ) Serial.println("REFIN low fault"); + if ( rtdin_low_fault_bit.read() ) Serial.println("RTDIN low fault"); + if ( voltage_fault_bit.read() ) Serial.println("Voltage fault"); + + faultR_bit.write(1); // clear fault + } +} + +void setup() { + #if (MAX31865_1_READY_PIN != -1) + pinMode(MAX31865_READY_PIN ,INPUT_PULLUP); + #endif + + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("SPI Adafruit_BusIO_RegisterBits test on MAX31865"); + + if (!spi_dev.begin()) { + Serial.println("Could not initialize SPI device"); + while (1); + } + + // Set up for automode 50Hz. We don't care about selfheating. We want the highest possible sampling rate. + auto_bit.write(0); // Don't switch filtermode while auto_mode is on. + fi50hz_bit.write(1); // Set filter to 50Hz mode. + faultR_bit.write(1); // Clear faults. + bias_bit.write(1); // In automode we want to have the bias current always on. + delay(5); // Wait until bias current settles down. + // 10.5 time constants of the input RC network is required. + // 10ms worst case for 10kω reference resistor and a 0.1µF capacitor across the RTD inputs. + // Adafruit Module has 0.1µF and only 430/4300ω So here 0.43/4.3ms + auto_bit.write(1); // Now we can set automode. Automatically starting first conversion. + + // Test the READY_PIN + #if (defined( MAX31865_READY_PIN ) && (MAX31865_READY_PIN != -1)) + int i = 0; + while (digitalRead(MAX31865_READY_PIN) && i++ <= 100) { delay(1); } + if (i >= 100) { + Serial.print("ERROR: Max31865 Pin detection does not work. PIN:"); + Serial.println(MAX31865_READY_PIN); + } + #else + delay(100); + #endif + + // Set ratio range. + // Setting the temperatures would need some more calculation - not related to Adafruit_BusIO_RegisterBits. + uint16_t ratio = rRatio_bits.read(); + maxRratio_bits.write( (ratio < 0x8fffu-1000u) ? ratio + 1000u : 0x8fffu ); + minRratio_bits.write( (ratio > 1000u) ? ratio - 1000u : 0u ); + + printConfig(); + checkFaults(); +} + +void loop() { + #if (defined( MAX31865_READY_PIN ) && (MAX31865_1_READY_PIN != -1)) + // Is conversion ready? + if (!digitalRead(MAX31865_READY_PIN)) + #else + // Warant conversion is ready. + delay(21); // 21ms for 50Hz-mode. 19ms in 60Hz-mode. + #endif + { + // Read ratio, calculate temperature, scale, filter and print. + Serial.println( rRatio2C( rRatio_bits.read() ) * 100.0f, 0); // Temperature scaled by 100 + // Check, print, clear faults. + checkFaults(); + } + + // Do something else. + //delay(15000); +} + + +// Module/Sensor related. Here Adafruit PT100 module with a 2_Wire PT100 Class C ***************************** +float rRatio2C(uint16_t ratio) { + // A simple linear conversion. + const float R0 = 100.0f; + const float Rref = 430.0f; + const float alphaPT = 0.003850f; + const float ADCmax = (1u << 15) - 1.0f; + const float rscale = Rref / ADCmax; + // Measured temperature in boiling water 101.08°C with factor a = 1 and b = 0. Rref and MAX at about 22±2°C. + // Measured temperature in ice/water bath 0.76°C with factor a = 1 and b = 0. Rref and MAX at about 22±2°C. + //const float a = 1.0f / (alphaPT * R0); + const float a = (100.0f/101.08f) / (alphaPT * R0); + //const float b = 0.0f; // 101.08 + const float b = -0.76f; // 100.32 > 101.08 + + return filterRing( ((ratio * rscale) - R0) * a + b ); +} + +// General purpose ********************************************************************************************* +#define RINGLENGTH 250 +float filterRing( float newVal ) { + static float ring[RINGLENGTH] = { 0.0 }; + static uint8_t ringIndex = 0; + static bool ringFull = false; + + if ( ringIndex == RINGLENGTH ) { ringFull = true; ringIndex = 0; } + ring[ringIndex] = newVal; + uint8_t loopEnd = (ringFull) ? RINGLENGTH : ringIndex + 1; + float ringSum = 0.0f; + for (uint8_t i = 0; i < loopEnd; i++) ringSum += ring[i]; + ringIndex++; + return ringSum / loopEnd; +} diff --git a/lib/Adafruit BusIO/examples/spi_registers/spi_registers.ino b/lib/Adafruit BusIO/examples/spi_registers/spi_registers.ino new file mode 100644 index 0000000..091a353 --- /dev/null +++ b/lib/Adafruit BusIO/examples/spi_registers/spi_registers.ino @@ -0,0 +1,34 @@ +#include +#include + +#define SPIDEVICE_CS 10 +Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS); + +void setup() { + while (!Serial) { delay(10); } + Serial.begin(115200); + Serial.println("SPI device register test"); + + if (!spi_dev.begin()) { + Serial.println("Could not initialize SPI device"); + while (1); + } + + Adafruit_BusIO_Register id_reg = Adafruit_BusIO_Register(&spi_dev, 0x0F, ADDRBIT8_HIGH_TOREAD); + uint8_t id = 0; + id_reg.read(&id); + Serial.print("ID register = 0x"); Serial.println(id, HEX); + + Adafruit_BusIO_Register thresh_reg = Adafruit_BusIO_Register(&spi_dev, 0x0C, ADDRBIT8_HIGH_TOREAD, 2, LSBFIRST); + uint16_t thresh = 0; + thresh_reg.read(&thresh); + Serial.print("Initial threshold register = 0x"); Serial.println(thresh, HEX); + + thresh_reg.write(~thresh); + + Serial.print("Post threshold register = 0x"); Serial.println(thresh_reg.read(), HEX); +} + +void loop() { + +} diff --git a/lib/Adafruit BusIO/library.properties b/lib/Adafruit BusIO/library.properties new file mode 100644 index 0000000..5a130f9 --- /dev/null +++ b/lib/Adafruit BusIO/library.properties @@ -0,0 +1,9 @@ +name=Adafruit BusIO +version=1.15.0 +author=Adafruit +maintainer=Adafruit +sentence=This is a library for abstracting away I2C and SPI interfacing +paragraph=This is a library for abstracting away I2C and SPI interfacing +category=Signal Input/Output +url=https://github.com/adafruit/Adafruit_BusIO +architectures=* diff --git a/lib/Adafruit MLX90614 Library/.github/ISSUE_TEMPLATE.md b/lib/Adafruit MLX90614 Library/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..f0e2614 --- /dev/null +++ b/lib/Adafruit MLX90614 Library/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,46 @@ +Thank you for opening an issue on an Adafruit Arduino library repository. To +improve the speed of resolution please review the following guidelines and +common troubleshooting steps below before creating the issue: + +- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use + the forums at http://forums.adafruit.com to ask questions and troubleshoot why + something isn't working as expected. In many cases the problem is a common issue + that you will more quickly receive help from the forum community. GitHub issues + are meant for known defects in the code. If you don't know if there is a defect + in the code then start with troubleshooting on the forum first. + +- **If following a tutorial or guide be sure you didn't miss a step.** Carefully + check all of the steps and commands to run have been followed. Consult the + forum if you're unsure or have questions about steps in a guide/tutorial. + +- **For Arduino projects check these very common issues to ensure they don't apply**: + + - For uploading sketches or communicating with the board make sure you're using + a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes + very hard to tell the difference between a data and charge cable! Try using the + cable with other devices or swapping to another cable to confirm it is not + the problem. + + - **Be sure you are supplying adequate power to the board.** Check the specs of + your board and plug in an external power supply. In many cases just + plugging a board into your computer is not enough to power it and other + peripherals. + + - **Double check all soldering joints and connections.** Flakey connections + cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. + + - **Ensure you are using an official Arduino or Adafruit board.** We can't + guarantee a clone board will have the same functionality and work as expected + with this code and don't support them. + +If you're sure this issue is a defect in the code and checked the steps above +please fill in the following fields to provide enough troubleshooting information. +You may delete the guideline and text above to just leave the following details: + +- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** + +- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO + VERSION HERE** + +- List the steps to reproduce the problem below (if possible attach a sketch or + copy the sketch code in too): **LIST REPRO STEPS BELOW** diff --git a/lib/Adafruit MLX90614 Library/.github/PULL_REQUEST_TEMPLATE.md b/lib/Adafruit MLX90614 Library/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7b641eb --- /dev/null +++ b/lib/Adafruit MLX90614 Library/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +Thank you for creating a pull request to contribute to Adafruit's GitHub code! +Before you open the request please review the following guidelines and tips to +help it be more easily integrated: + +- **Describe the scope of your change--i.e. what the change does and what parts + of the code were modified.** This will help us understand any risks of integrating + the code. + +- **Describe any known limitations with your change.** For example if the change + doesn't apply to a supported platform of the library please mention it. + +- **Please run any tests or examples that can exercise your modified code.** We + strive to not break users of the code and running tests/examples helps with this + process. + +Thank you again for contributing! We will try to test and integrate the change +as soon as we can, but be aware we have many GitHub repositories to manage and +can't immediately respond to every request. There is no need to bump or check in +on a pull request (it will clutter the discussion of the request). + +Also don't be worried if the request is closed or not integrated--sometimes the +priorities of Adafruit's GitHub code (education, ease of use) might not match the +priorities of the pull request. Don't fret, the open source community thrives on +forks and GitHub makes it easy to keep your changes in a forked repo. + +After reviewing the guidelines above you can delete this text from the pull request. diff --git a/lib/Adafruit MLX90614 Library/.github/workflows/githubci.yml b/lib/Adafruit MLX90614 Library/.github/workflows/githubci.yml new file mode 100644 index 0000000..6a971ef --- /dev/null +++ b/lib/Adafruit MLX90614 Library/.github/workflows/githubci.yml @@ -0,0 +1,32 @@ +name: Arduino Library CI + +on: [pull_request, push, repository_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + with: + repository: adafruit/ci-arduino + path: ci + + - name: pre-install + run: bash ci/actions_install.sh + + - name: test platforms + run: python3 ci/build_platform.py main_platforms + + - name: clang + run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . + + - name: doxygen + env: + GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} + PRETTYNAME : "Adafruit MLX90614 Arduino Library" + run: bash ci/doxy_gen_and_deploy.sh diff --git a/lib/Adafruit MLX90614 Library/.piopm b/lib/Adafruit MLX90614 Library/.piopm new file mode 100644 index 0000000..67c4d4e --- /dev/null +++ b/lib/Adafruit MLX90614 Library/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "Adafruit MLX90614 Library", "version": "2.1.5", "spec": {"owner": "adafruit", "id": 782, "name": "Adafruit MLX90614 Library", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/Adafruit MLX90614 Library/Adafruit_MLX90614.cpp b/lib/Adafruit MLX90614 Library/Adafruit_MLX90614.cpp new file mode 100644 index 0000000..69573a3 --- /dev/null +++ b/lib/Adafruit MLX90614 Library/Adafruit_MLX90614.cpp @@ -0,0 +1,178 @@ +/*************************************************** + This is a library for the MLX90614 Temp Sensor + + Designed specifically to work with the MLX90614 sensors in the + adafruit shop + ----> https://www.adafruit.com/products/1747 (3V) + ----> https://www.adafruit.com/products/1748 (5V) + + These sensors use I2C to communicate, 2 pins are required to + interface + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ****************************************************/ + +#include "Adafruit_MLX90614.h" + +Adafruit_MLX90614::~Adafruit_MLX90614() { + if (i2c_dev) + delete i2c_dev; +} + +/** + * @brief Begin the I2C connection + * @param addr I2C address for the device. + * @param wire Pointer to Wire instance + * @return True if the device was successfully initialized, otherwise false. + */ +bool Adafruit_MLX90614::begin(uint8_t addr, TwoWire *wire) { + _addr = addr; // needed for CRC + if (i2c_dev) + delete i2c_dev; + i2c_dev = new Adafruit_I2CDevice(addr, wire); + return i2c_dev->begin(); +} + +/** + * @brief Read the raw value from the emissivity register + * + * @return uint16_t The unscaled emissivity value or '0' if reading failed + */ +uint16_t Adafruit_MLX90614::readEmissivityReg(void) { + return read16(MLX90614_EMISS); +} +/** + * @brief Write the raw unscaled emissivity value to the emissivity register + * + * @param ereg The unscaled emissivity value + */ +void Adafruit_MLX90614::writeEmissivityReg(uint16_t ereg) { + write16(MLX90614_EMISS, 0); // erase + + vTaskDelay(10); + write16(MLX90614_EMISS, ereg); + + vTaskDelay(10); +} +/** + * @brief Read the emissivity value from the sensor's register and scale + * + * @return double The emissivity value, ranging from 0.1 - 1.0 or NAN if reading + * failed + */ +double Adafruit_MLX90614::readEmissivity(void) { + uint16_t ereg = read16(MLX90614_EMISS); + if (ereg == 0) + return NAN; + return ((double)ereg) / 65535.0; +} +/** + * @brief Set the emissivity value + * + * @param emissivity The emissivity value to use, between 0.1 and 1.0 + */ +void Adafruit_MLX90614::writeEmissivity(double emissivity) { + uint16_t ereg = (uint16_t)(0xffff * emissivity); + + writeEmissivityReg(ereg); +} + +/** + * @brief Get the current temperature of an object in degrees Farenheit + * + * @return double The temperature in degrees Farenheit or NAN if reading failed + */ +double Adafruit_MLX90614::readObjectTempF(void) { + return (readTemp(MLX90614_TOBJ1) * 9 / 5) + 32; +} +/** + * @brief Get the current ambient temperature in degrees Farenheit + * + * @return double The temperature in degrees Farenheit or NAN if reading failed + */ +double Adafruit_MLX90614::readAmbientTempF(void) { + return (readTemp(MLX90614_TA) * 9 / 5) + 32; +} + +/** + * @brief Get the current temperature of an object in degrees Celcius + * + * @return double The temperature in degrees Celcius or NAN if reading failed + */ +double Adafruit_MLX90614::readObjectTempC(void) { + return readTemp(MLX90614_TOBJ1); +} + +/** + * @brief Get the current ambient temperature in degrees Celcius + * + * @return double The temperature in degrees Celcius or NAN if reading failed + */ +double Adafruit_MLX90614::readAmbientTempC(void) { + return readTemp(MLX90614_TA); +} + +float Adafruit_MLX90614::readTemp(uint8_t reg) { + float temp; + + temp = read16(reg); + if (temp == 0) + return NAN; + temp *= .02; + temp -= 273.15; + return temp; +} + +/*********************************************************************/ + +uint16_t Adafruit_MLX90614::read16(uint8_t a) { + uint8_t buffer[3]; + buffer[0] = a; + // read two bytes of data + pec + bool status = i2c_dev->write_then_read(buffer, 1, buffer, 3); + if (!status) + return 0; + // return data, ignore pec + return uint16_t(buffer[0]) | (uint16_t(buffer[1]) << 8); +} + +byte Adafruit_MLX90614::crc8(byte *addr, byte len) +// The PEC calculation includes all bits except the START, REPEATED START, STOP, +// ACK, and NACK bits. The PEC is a CRC-8 with polynomial X8+X2+X1+1. +{ + byte crc = 0; + while (len--) { + byte inbyte = *addr++; + for (byte i = 8; i; i--) { + byte carry = (crc ^ inbyte) & 0x80; + crc <<= 1; + if (carry) + crc ^= 0x7; + inbyte <<= 1; + } + } + return crc; +} + +void Adafruit_MLX90614::write16(uint8_t a, uint16_t v) { + uint8_t buffer[4]; + + buffer[0] = _addr << 1; + buffer[1] = a; + buffer[2] = v & 0xff; + buffer[3] = v >> 8; + + uint8_t pec = crc8(buffer, 4); + + buffer[0] = buffer[1]; + buffer[1] = buffer[2]; + buffer[2] = buffer[3]; + buffer[3] = pec; + + i2c_dev->write(buffer, 4); + +} diff --git a/lib/Adafruit MLX90614 Library/Adafruit_MLX90614.h b/lib/Adafruit MLX90614 Library/Adafruit_MLX90614.h new file mode 100644 index 0000000..8b88d0f --- /dev/null +++ b/lib/Adafruit MLX90614 Library/Adafruit_MLX90614.h @@ -0,0 +1,68 @@ +/*************************************************** + This is a library for the MLX90614 Temp Sensor + + Designed specifically to work with the MLX90614 sensors in the + adafruit shop + ----> https://www.adafruit.com/products/1747 (3V) + ----> https://www.adafruit.com/products/1748 (5V) + + These sensors use I2C to communicate, 2 pins are required to + interface + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit in any redistribution + ****************************************************/ + +#include +#include + +#define MLX90614_I2CADDR 0x5A + +// RAM +#define MLX90614_RAWIR1 0x04 +#define MLX90614_RAWIR2 0x05 +#define MLX90614_TA 0x06 +#define MLX90614_TOBJ1 0x07 +#define MLX90614_TOBJ2 0x08 +// EEPROM +#define MLX90614_TOMAX 0x20 +#define MLX90614_TOMIN 0x21 +#define MLX90614_PWMCTRL 0x22 +#define MLX90614_TARANGE 0x23 +#define MLX90614_EMISS 0x24 +#define MLX90614_CONFIG 0x25 +#define MLX90614_ADDR 0x2E +#define MLX90614_ID1 0x3C +#define MLX90614_ID2 0x3D +#define MLX90614_ID3 0x3E +#define MLX90614_ID4 0x3F + +/** + * @brief Class to read from and control a MLX90614 Temp Sensor + * + */ +class Adafruit_MLX90614 { +public: + ~Adafruit_MLX90614(); + bool begin(uint8_t addr = MLX90614_I2CADDR, TwoWire *wire = &Wire); + + double readObjectTempC(void); + double readAmbientTempC(void); + double readObjectTempF(void); + double readAmbientTempF(void); + uint16_t readEmissivityReg(void); + void writeEmissivityReg(uint16_t ereg); + double readEmissivity(void); + void writeEmissivity(double emissivity); + +private: + Adafruit_I2CDevice *i2c_dev = NULL; ///< Pointer to I2C bus interface + float readTemp(uint8_t reg); + + uint16_t read16(uint8_t addr); + void write16(uint8_t addr, uint16_t data); + byte crc8(byte *addr, byte len); + uint8_t _addr; +}; diff --git a/lib/Adafruit MLX90614 Library/README.md b/lib/Adafruit MLX90614 Library/README.md new file mode 100644 index 0000000..26ad469 --- /dev/null +++ b/lib/Adafruit MLX90614 Library/README.md @@ -0,0 +1,51 @@ +# Adafruit-MLX90614-Library [![Build Status](https://github.com/adafruit/Adafruit-MLX90614-Library/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit-MLX90614-Library/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit-MLX90614-Library/html/index.html) + +This is a library for the MLX90614 temperature sensor + + + +Designed and tested to work with the MLX90614 sensors in the adafruit shop + * https://www.adafruit.com/products/1747 3V version + * https://www.adafruit.com/products/1748 5V version + +Check out the links above for our tutorials and wiring diagrams + +Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! + +# Installation +To install, use the Arduino Library Manager and search for "Adafruit-MLX90614-Library" and install the library. + +# Contributing + +Contributions are welcome! Please read our [Code of Conduct](https://github.com/adafruit/Adafruit-MLX90614-Library/blob/master/CODE_OF_CONDUCT.md>) +before contributing to help this project stay welcoming. + +## Documentation and doxygen +Documentation is produced by doxygen. Contributions should include documentation for any new code added. + +Some examples of how to use doxygen can be found in these guide pages: + +https://learn.adafruit.com/the-well-automated-arduino-library/doxygen + +https://learn.adafruit.com/the-well-automated-arduino-library/doxygen-tips + +## Formatting and clang-format +This library uses [`clang-format`](https://releases.llvm.org/download.html) to standardize the formatting of `.cpp` and `.h` files. +Contributions should be formatted using `clang-format`: + +The `-i` flag will make the changes to the file. +```bash +clang-format -i *.cpp *.h +``` +If you prefer to make the changes yourself, running `clang-format` without the `-i` flag will print out a formatted version of the file. You can save this to a file and diff it against the original to see the changes. + +Note that the formatting output by `clang-format` is what the automated formatting checker will expect. Any diffs from this formatting will result in a failed build until they are addressed. Using the `-i` flag is highly recommended. + +### clang-format resources + * [Binary builds and source available on the LLVM downloads page](https://releases.llvm.org/download.html) + * [Documentation and IDE integration](https://clang.llvm.org/docs/ClangFormat.html) + +## About this Driver +Written by Limor Fried for Adafruit Industries. +BSD license, check license.txt for more information +All text above must be included in any redistribution diff --git a/lib/Adafruit MLX90614 Library/code-of-conduct.md b/lib/Adafruit MLX90614 Library/code-of-conduct.md new file mode 100644 index 0000000..8ee6e44 --- /dev/null +++ b/lib/Adafruit MLX90614 Library/code-of-conduct.md @@ -0,0 +1,127 @@ +# Adafruit Community Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and leaders pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level or type of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +We are committed to providing a friendly, safe and welcoming environment for +all. + +Examples of behavior that contributes to creating a positive environment +include: + +* Be kind and courteous to others +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Collaborating with other community members +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and sexual attention or advances +* The use of inappropriate images, including in a community member's avatar +* The use of inappropriate language, including in a community member's nickname +* Any spamming, flaming, baiting or other attention-stealing behavior +* Excessive or unwelcome helping; answering outside the scope of the question + asked +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate + +The goal of the standards and moderation guidelines outlined here is to build +and maintain a respectful community. We ask that you don’t just aim to be +"technically unimpeachable", but rather try to be your best self. + +We value many things beyond technical expertise, including collaboration and +supporting others within our community. Providing a positive experience for +other community members can have a much more significant impact than simply +providing the correct answer. + +## Our Responsibilities + +Project leaders are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project leaders have the right and responsibility to remove, edit, or +reject messages, comments, commits, code, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any community member for other behaviors that they deem +inappropriate, threatening, offensive, or harmful. + +## Moderation + +Instances of behaviors that violate the Adafruit Community Code of Conduct +may be reported by any member of the community. Community members are +encouraged to report these situations, including situations they witness +involving other community members. + +You may report in the following ways: + +In any situation, you may send an email to . + +On the Adafruit Discord, you may send an open message from any channel +to all Community Helpers by tagging @community helpers. You may also send an +open message from any channel, or a direct message to @kattni#1507, +@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or +@Andon#8175. + +Email and direct message reports will be kept confidential. + +In situations on Discord where the issue is particularly egregious, possibly +illegal, requires immediate action, or violates the Discord terms of service, +you should also report the message directly to Discord. + +These are the steps for upholding our community’s standards of conduct. + +1. Any member of the community may report any situation that violates the +Adafruit Community Code of Conduct. All reports will be reviewed and +investigated. +2. If the behavior is an egregious violation, the community member who +committed the violation may be banned immediately, without warning. +3. Otherwise, moderators will first respond to such behavior with a warning. +4. Moderators follow a soft "three strikes" policy - the community member may +be given another chance, if they are receptive to the warning and change their +behavior. +5. If the community member is unreceptive or unreasonable when warned by a +moderator, or the warning goes unheeded, they may be banned for a first or +second offense. Repeated offenses will result in the community member being +banned. + +## Scope + +This Code of Conduct and the enforcement policies listed above apply to all +Adafruit Community venues. This includes but is not limited to any community +spaces (both public and private), the entire Adafruit Discord server, and +Adafruit GitHub repositories. Examples of Adafruit Community spaces include +but are not limited to meet-ups, audio chats on the Adafruit Discord, or +interaction at a conference. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. As a community +member, you are representing our community, and are expected to behave +accordingly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +, +and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). + +For other projects adopting the Adafruit Community Code of +Conduct, please contact the maintainers of those projects for enforcement. +If you wish to use this code of conduct for your own project, consider +explicitly mentioning your moderation policy or making a copy with your +own moderation policy so as to avoid confusion. diff --git a/lib/Adafruit MLX90614 Library/examples/mlx_set_emissivity/mlx_set_emissivity.ino b/lib/Adafruit MLX90614 Library/examples/mlx_set_emissivity/mlx_set_emissivity.ino new file mode 100644 index 0000000..7914071 --- /dev/null +++ b/lib/Adafruit MLX90614 Library/examples/mlx_set_emissivity/mlx_set_emissivity.ino @@ -0,0 +1,47 @@ +/* + * See app note: + * https://www.melexis.com/en/documents/documentation/application-notes/application-note-mlx90614-changing-emissivity-setting + * + * 1. Write 0x0000 to address 0x04 (erase the EEPROM cell) + * 2. Write the new value to address 0x04 + * 3. Read the value in address 0x04 in order to check that the correct value is stored + * 4. Restart the module + * + */ + +#include + +//== CHANGE THIS ============ +double new_emissivity = 0.95; +//=========================== + +Adafruit_MLX90614 mlx = Adafruit_MLX90614(); + +void setup() { + // Serial.begin(9600); + // while (!Serial); + + Serial.println("Adafruit MLX90614 Emissivity Setter.\n"); + + // init sensor + if (!mlx.begin()) { + Serial.println("Error connecting to MLX sensor. Check wiring."); + while (1); + }; + + // read current emissivity + Serial.print("Current emissivity = "); Serial.println(mlx.readEmissivity()); + + // set new emissivity + Serial.print("Setting emissivity = "); Serial.println(new_emissivity); + mlx.writeEmissivity(new_emissivity); // this does the 0x0000 erase write + + // read back + Serial.print("New emissivity = "); Serial.println(mlx.readEmissivity()); + + // done + Serial.print("DONE. Restart the module."); +} + +void loop() { +} \ No newline at end of file diff --git a/lib/Adafruit MLX90614 Library/examples/mlxtest/mlxtest.ino b/lib/Adafruit MLX90614 Library/examples/mlxtest/mlxtest.ino new file mode 100644 index 0000000..594d8fe --- /dev/null +++ b/lib/Adafruit MLX90614 Library/examples/mlxtest/mlxtest.ino @@ -0,0 +1,46 @@ +/*************************************************** + This is a library example for the MLX90614 Temp Sensor + + Designed specifically to work with the MLX90614 sensors in the + adafruit shop + ----> https://www.adafruit.com/products/1747 3V version + ----> https://www.adafruit.com/products/1748 5V version + + These sensors use I2C to communicate, 2 pins are required to + interface + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ****************************************************/ + +#include + +Adafruit_MLX90614 mlx = Adafruit_MLX90614(); + +void setup() { + Serial.begin(9600); + while (!Serial); + + Serial.println("Adafruit MLX90614 test"); + + if (!mlx.begin()) { + Serial.println("Error connecting to MLX sensor. Check wiring."); + while (1); + }; + + Serial.print("Emissivity = "); Serial.println(mlx.readEmissivity()); + Serial.println("================================================"); +} + +void loop() { + Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempC()); + Serial.print("*C\tObject = "); Serial.print(mlx.readObjectTempC()); Serial.println("*C"); + Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempF()); + Serial.print("*F\tObject = "); Serial.print(mlx.readObjectTempF()); Serial.println("*F"); + + Serial.println(); + delay(500); +} diff --git a/lib/Adafruit MLX90614 Library/library.properties b/lib/Adafruit MLX90614 Library/library.properties new file mode 100644 index 0000000..7e2294b --- /dev/null +++ b/lib/Adafruit MLX90614 Library/library.properties @@ -0,0 +1,10 @@ +name=Adafruit MLX90614 Library +version=2.1.5 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for the MLX90614 sensors in the Adafruit shop +paragraph=Arduino library for the MLX90614 sensors in the Adafruit shop +category=Sensors +url=https://github.com/adafruit/Adafruit-MLX90614-Library +architectures=* +depends=Adafruit BusIO diff --git a/lib/Adafruit MLX90614 Library/license.txt b/lib/Adafruit MLX90614 Library/license.txt new file mode 100644 index 0000000..8aafa28 --- /dev/null +++ b/lib/Adafruit MLX90614 Library/license.txt @@ -0,0 +1,26 @@ +Software License Agreement (BSD License) + +Copyright (c) 2020 Limor Fried for Adafruit Industries +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/ArduinoHttpClient/.gitignore b/lib/ArduinoHttpClient/.gitignore new file mode 100644 index 0000000..653acab --- /dev/null +++ b/lib/ArduinoHttpClient/.gitignore @@ -0,0 +1,5 @@ +.development +examples/node_test_server/node_modules/ +*.DS_Store +*/.DS_Store +examples/.DS_Store diff --git a/lib/ArduinoHttpClient/.piopm b/lib/ArduinoHttpClient/.piopm new file mode 100644 index 0000000..06188a7 --- /dev/null +++ b/lib/ArduinoHttpClient/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "ArduinoHttpClient", "version": "0.4.0", "spec": {"owner": "arduino-libraries", "id": 798, "name": "ArduinoHttpClient", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/ArduinoHttpClient/CHANGELOG.md b/lib/ArduinoHttpClient/CHANGELOG.md new file mode 100644 index 0000000..ac78d18 --- /dev/null +++ b/lib/ArduinoHttpClient/CHANGELOG.md @@ -0,0 +1,33 @@ +## ArduinoHttpClient 0.4.0 - 2019.04.09 + +* Added URLEncoder helper + +## ArduinoHttpClient 0.3.2 - 2019.02.04 + +* Changed Flush return value resulting in compilation error. Thanks @forGGe + +## ArduinoHttpClient 0.3.1 - 2017.09.25 + +* Changed examples to support Arduino Create secret tabs +* Increase WebSocket secrect-key length to 24 characters + +## ArduinoHttpClient 0.3.0 - 2017.04.20 + +* Added support for PATCH operations +* Added support for chunked response bodies +* Added new beginBody API + +## ArduinoHttpClient 0.2.0 - 2017.01.12 + +* Added PATCH method +* Added basic auth example +* Added custom header example + +## ArduinoHttpClient 0.1.1 - 2016.12.16 + +* More robust response parser + +## ArduinoHttpClient 0.1.0 - 2016.07.05 + +* Initial release + diff --git a/lib/ArduinoHttpClient/README.md b/lib/ArduinoHttpClient/README.md new file mode 100644 index 0000000..1b95559 --- /dev/null +++ b/lib/ArduinoHttpClient/README.md @@ -0,0 +1,25 @@ +# ArduinoHttpClient + +ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. + +Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) + +## Dependencies + +- Requires a networking hardware and a library that provides transport specific `Client` instance, such as: + - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA) + - [WiFi101](https://github.com/arduino-libraries/WiFi101) + - [Ethernet](https://github.com/arduino-libraries/Ethernet) + - [MKRGSM](https://github.com/arduino-libraries/MKRGSM) + - [MKRNB](https://github.com/arduino-libraries/MKRNB) + - [WiFi](https://github.com/arduino-libraries/WiFi) + - [GSM](https://github.com/arduino-libraries/GSM) + +## Usage + +In normal usage, handles the outgoing request and Host header. The returned status code is parsed for you, as is the Content-Length header (if present). + +Because it expects an object of type Client, you can use it with any of the networking classes that derive from that. Which means it will work with WiFiClient, EthernetClient and GSMClient. + +See the examples for more detail on how the library is used. + diff --git a/lib/ArduinoHttpClient/examples/BasicAuthGet/BasicAuthGet.ino b/lib/ArduinoHttpClient/examples/BasicAuthGet/BasicAuthGet.ino new file mode 100644 index 0000000..ca801b7 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/BasicAuthGet/BasicAuthGet.ino @@ -0,0 +1,66 @@ +/* + GET client with HTTP basic authentication for ArduinoHttpClient library + Connects to server once every five seconds, sends a GET request + + created 14 Feb 2016 + by Tom Igoe + modified 3 Jan 2017 to add HTTP basic authentication + by Sandeep Mistry + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making GET request with HTTP basic authentication"); + client.beginRequest(); + client.get("/secure"); + client.sendBasicAuth("username", "password"); // send the username and password for authentication + client.endRequest(); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/BasicAuthGet/arduino_secrets.h b/lib/ArduinoHttpClient/examples/BasicAuthGet/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/BasicAuthGet/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/CustomHeader/CustomHeader.ino b/lib/ArduinoHttpClient/examples/CustomHeader/CustomHeader.ino new file mode 100644 index 0000000..e2131eb --- /dev/null +++ b/lib/ArduinoHttpClient/examples/CustomHeader/CustomHeader.ino @@ -0,0 +1,91 @@ +/* + Custom request header example for the ArduinoHttpClient + library. This example sends a GET and a POST request with a custom header every 5 seconds. + + based on SimpleGet example by Tom Igoe + header modifications by Todd Treece + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain +*/ + +#include +#include + +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making GET request"); + client.beginRequest(); + client.get("/"); + client.sendHeader("X-CUSTOM-HEADER", "custom_value"); + client.endRequest(); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("GET Status code: "); + Serial.println(statusCode); + Serial.print("GET Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); + + Serial.println("making POST request"); + String postData = "name=Alice&age=12"; + client.beginRequest(); + client.post("/"); + client.sendHeader(HTTP_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); + client.sendHeader(HTTP_HEADER_CONTENT_LENGTH, postData.length()); + client.sendHeader("X-CUSTOM-HEADER", "custom_value"); + client.endRequest(); + client.write((const byte*)postData.c_str(), postData.length()); + // note: the above line can also be achieved with the simpler line below: + //client.print(postData); + + // read the status code and body of the response + statusCode = client.responseStatusCode(); + response = client.responseBody(); + + Serial.print("POST Status code: "); + Serial.println(statusCode); + Serial.print("POST Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/CustomHeader/arduino_secrets.h b/lib/ArduinoHttpClient/examples/CustomHeader/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/CustomHeader/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/DweetGet/DweetGet.ino b/lib/ArduinoHttpClient/examples/DweetGet/DweetGet.ino new file mode 100644 index 0000000..d191285 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/DweetGet/DweetGet.ino @@ -0,0 +1,103 @@ +/* + Dweet.io GET client for ArduinoHttpClient library + Connects to dweet.io once every ten seconds, + sends a GET request and a request body. Uses SSL + + Shows how to use Strings to assemble path and parse content + from response. dweet.io expects: + https://dweet.io/get/latest/dweet/for/thingName + + For more on dweet.io, see https://dweet.io/play/ + + created 15 Feb 2016 + updated 22 Jan 2019 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include + +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +const char serverAddress[] = "dweet.io"; // server address +int port = 80; +String dweetName = "scandalous-cheese-hoarder"; // use your own thing name here + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while (!Serial); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + // assemble the path for the GET message: + String path = "/get/latest/dweet/for/" + dweetName; + + // send the GET request + Serial.println("making GET request"); + client.get(path); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + /* + Typical response is: + {"this":"succeeded", + "by":"getting", + "the":"dweets", + "with":[{"thing":"my-thing-name", + "created":"2016-02-16T05:10:36.589Z", + "content":{"sensorValue":456}}]} + + You want "content": numberValue + */ + // now parse the response looking for "content": + int labelStart = response.indexOf("content\":"); + // find the first { after "content": + int contentStart = response.indexOf("{", labelStart); + // find the following } and get what's between the braces: + int contentEnd = response.indexOf("}", labelStart); + String content = response.substring(contentStart + 1, contentEnd); + Serial.println(content); + + // now get the value after the colon, and convert to an int: + int valueStart = content.indexOf(":"); + String valueString = content.substring(valueStart + 1); + int number = valueString.toInt(); + Serial.print("Value string: "); + Serial.println(valueString); + Serial.print("Actual value: "); + Serial.println(number); + + Serial.println("Wait ten seconds\n"); + delay(10000); +} diff --git a/lib/ArduinoHttpClient/examples/DweetGet/arduino_secrets.h b/lib/ArduinoHttpClient/examples/DweetGet/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/DweetGet/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/DweetPost/DweetPost.ino b/lib/ArduinoHttpClient/examples/DweetPost/DweetPost.ino new file mode 100644 index 0000000..f1da2b9 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/DweetPost/DweetPost.ino @@ -0,0 +1,79 @@ +/* + Dweet.io POST client for ArduinoHttpClient library + Connects to dweet.io once every ten seconds, + sends a POST request and a request body. + + Shows how to use Strings to assemble path and body + + created 15 Feb 2016 + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include + +#include "arduino_secrets.h" +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +const char serverAddress[] = "dweet.io"; // server address +int port = 80; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while(!Serial); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + // assemble the path for the POST message: + String dweetName = "scandalous-cheese-hoarder"; + String path = "/dweet/for/" + dweetName; + String contentType = "application/json"; + + // assemble the body of the POST message: + int sensorValue = analogRead(A0); + String postData = "{\"sensorValue\":\""; + postData += sensorValue; + postData += "\"}"; + + Serial.println("making POST request"); + + // send the POST request + client.post(path, contentType, postData); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait ten seconds\n"); + delay(10000); +} diff --git a/lib/ArduinoHttpClient/examples/DweetPost/arduino_secrets.h b/lib/ArduinoHttpClient/examples/DweetPost/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/DweetPost/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/HueBlink/HueBlink.ino b/lib/ArduinoHttpClient/examples/HueBlink/HueBlink.ino new file mode 100644 index 0000000..eab74fe --- /dev/null +++ b/lib/ArduinoHttpClient/examples/HueBlink/HueBlink.ino @@ -0,0 +1,98 @@ +/* HueBlink example for ArduinoHttpClient library + + Uses ArduinoHttpClient library to control Philips Hue + For more on Hue developer API see http://developer.meethue.com + + To control a light, the Hue expects a HTTP PUT request to: + + http://hue.hub.address/api/hueUserName/lights/lightNumber/state + + The body of the PUT request looks like this: + {"on": true} or {"on":false} + + This example shows how to concatenate Strings to assemble the + PUT request and the body of the request. + + modified 15 Feb 2016 + by Tom Igoe (tigoe) to match new API +*/ + +#include +#include +#include +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +int status = WL_IDLE_STATUS; // the Wifi radio's status + +char hueHubIP[] = "192.168.0.3"; // IP address of the HUE bridge +String hueUserName = "huebridgeusername"; // hue bridge username + +// make a wifi instance and a HttpClient instance: +WiFiClient wifi; +HttpClient httpClient = HttpClient(wifi, hueHubIP); + + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial); // wait for serial port to connect. + + // attempt to connect to Wifi network: + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to WPA SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // you're connected now, so print out the data: + Serial.print("You're connected to the network IP = "); + IPAddress ip = WiFi.localIP(); + Serial.println(ip); +} + +void loop() { + sendRequest(3, "on", "true"); // turn light on + delay(2000); // wait 2 seconds + sendRequest(3, "on", "false"); // turn light off + delay(2000); // wait 2 seconds +} + +void sendRequest(int light, String cmd, String value) { + // make a String for the HTTP request path: + String request = "/api/" + hueUserName; + request += "/lights/"; + request += light; + request += "/state/"; + + String contentType = "application/json"; + + // make a string for the JSON command: + String hueCmd = "{\"" + cmd; + hueCmd += "\":"; + hueCmd += value; + hueCmd += "}"; + // see what you assembled to send: + Serial.print("PUT request to server: "); + Serial.println(request); + Serial.print("JSON command to server: "); + + // make the PUT request to the hub: + httpClient.put(request, contentType, hueCmd); + + // read the status code and body of the response + int statusCode = httpClient.responseStatusCode(); + String response = httpClient.responseBody(); + + Serial.println(hueCmd); + Serial.print("Status code from server: "); + Serial.println(statusCode); + Serial.print("Server response: "); + Serial.println(response); + Serial.println(); +} \ No newline at end of file diff --git a/lib/ArduinoHttpClient/examples/HueBlink/arduino_secrets.h b/lib/ArduinoHttpClient/examples/HueBlink/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/HueBlink/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/PostWithHeaders/PostWithHeaders.ino b/lib/ArduinoHttpClient/examples/PostWithHeaders/PostWithHeaders.ino new file mode 100644 index 0000000..c2b1f0a --- /dev/null +++ b/lib/ArduinoHttpClient/examples/PostWithHeaders/PostWithHeaders.ino @@ -0,0 +1,77 @@ +/* + POST with headers client for ArduinoHttpClient library + Connects to server once every five seconds, sends a POST request + with custome headers and a request body + + created 14 Feb 2016 + by Tom Igoe + modified 18 Mar 2017 + by Sandeep Mistry + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain + */ +#include +#include + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making POST request"); + String postData = "name=Alice&age=12"; + + client.beginRequest(); + client.post("/"); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", postData.length()); + client.sendHeader("X-Custom-Header", "custom-header-value"); + client.beginBody(); + client.print(postData); + client.endRequest(); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/PostWithHeaders/arduino_secrets.h b/lib/ArduinoHttpClient/examples/PostWithHeaders/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/PostWithHeaders/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/SimpleDelete/SimpleDelete.ino b/lib/ArduinoHttpClient/examples/SimpleDelete/SimpleDelete.ino new file mode 100644 index 0000000..120f8d4 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleDelete/SimpleDelete.ino @@ -0,0 +1,68 @@ +/* + Simple DELETE client for ArduinoHttpClient library + Connects to server once every five seconds, sends a DELETE request + and a request body + + created 14 Feb 2016 + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain + */ +#include +#include + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making DELETE request"); + String contentType = "application/x-www-form-urlencoded"; + String delData = "name=light&age=46"; + + client.del("/", contentType, delData); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/SimpleDelete/arduino_secrets.h b/lib/ArduinoHttpClient/examples/SimpleDelete/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleDelete/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/SimpleGet/SimpleGet.ino b/lib/ArduinoHttpClient/examples/SimpleGet/SimpleGet.ino new file mode 100644 index 0000000..90d1f68 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleGet/SimpleGet.ino @@ -0,0 +1,62 @@ +/* + Simple GET client for ArduinoHttpClient library + Connects to server once every five seconds, sends a GET request + + created 14 Feb 2016 + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain + */ +#include +#include + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making GET request"); + client.get("/"); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/SimpleGet/arduino_secrets.h b/lib/ArduinoHttpClient/examples/SimpleGet/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleGet/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/SimpleHttpExample/SimpleHttpExample.ino b/lib/ArduinoHttpClient/examples/SimpleHttpExample/SimpleHttpExample.ino new file mode 100644 index 0000000..f64b9ba --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -0,0 +1,133 @@ +// (c) Copyright 2010-2012 MCQN Ltd. +// Released under Apache License, version 2.0 +// +// Simple example to show how to use the HttpClient library +// Get's the web page given at http:// and +// outputs the content to the serial port + +#include +#include +#include + +// This example downloads the URL "http://arduino.cc/" + +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + + + +// Name of the server we want to connect to +const char kHostname[] = "arduino.cc"; +// Path to download (this is the bit after the hostname in the URL +// that you want to download +const char kPath[] = "/"; + +// Number of milliseconds to wait without receiving any data before we give up +const int kNetworkTimeout = 30*1000; +// Number of milliseconds to wait if no data is available before trying again +const int kNetworkDelay = 1000; + +WiFiClient c; +HttpClient http(c, kHostname); + +void setup() +{ + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } + + // attempt to connect to Wifi network: + Serial.print("Attempting to connect to WPA SSID: "); + Serial.println(ssid); + while (WiFi.begin(ssid, pass) != WL_CONNECTED) { + // unsuccessful, retry in 4 seconds + Serial.print("failed ... "); + delay(4000); + Serial.print("retrying ... "); + } + + Serial.println("connected"); +} + +void loop() +{ + int err =0; + + err = http.get(kPath); + if (err == 0) + { + Serial.println("startedRequest ok"); + + err = http.responseStatusCode(); + if (err >= 0) + { + Serial.print("Got status code: "); + Serial.println(err); + + // Usually you'd check that the response code is 200 or a + // similar "success" code (200-299) before carrying on, + // but we'll print out whatever response we get + + // If you are interesting in the response headers, you + // can read them here: + //while(http.headerAvailable()) + //{ + // String headerName = http.readHeaderName(); + // String headerValue = http.readHeaderValue(); + //} + + int bodyLen = http.contentLength(); + Serial.print("Content length is: "); + Serial.println(bodyLen); + Serial.println(); + Serial.println("Body returned follows:"); + + // Now we've got to the body, so we can print it out + unsigned long timeoutStart = millis(); + char c; + // Whilst we haven't timed out & haven't reached the end of the body + while ( (http.connected() || http.available()) && + (!http.endOfBodyReached()) && + ((millis() - timeoutStart) < kNetworkTimeout) ) + { + if (http.available()) + { + c = http.read(); + // Print out this character + Serial.print(c); + + // We read something, reset the timeout counter + timeoutStart = millis(); + } + else + { + // We haven't got any data, so let's pause to allow some to + // arrive + delay(kNetworkDelay); + } + } + } + else + { + Serial.print("Getting response failed: "); + Serial.println(err); + } + } + else + { + Serial.print("Connect failed: "); + Serial.println(err); + } + http.stop(); + + // And just stop, now that we've tried a download + while(1); +} + + diff --git a/lib/ArduinoHttpClient/examples/SimpleHttpExample/arduino_secrets.h b/lib/ArduinoHttpClient/examples/SimpleHttpExample/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleHttpExample/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/SimplePost/SimplePost.ino b/lib/ArduinoHttpClient/examples/SimplePost/SimplePost.ino new file mode 100644 index 0000000..a4704e5 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimplePost/SimplePost.ino @@ -0,0 +1,66 @@ +/* + Simple POST client for ArduinoHttpClient library + Connects to server once every five seconds, sends a POST request + and a request body + + created 14 Feb 2016 + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making POST request"); + String contentType = "application/x-www-form-urlencoded"; + String postData = "name=Alice&age=12"; + + client.post("/", contentType, postData); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/SimplePost/arduino_secrets.h b/lib/ArduinoHttpClient/examples/SimplePost/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimplePost/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/SimplePut/SimplePut.ino b/lib/ArduinoHttpClient/examples/SimplePut/SimplePut.ino new file mode 100644 index 0000000..99af49f --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimplePut/SimplePut.ino @@ -0,0 +1,66 @@ +/* + Simple PUT client for ArduinoHttpClient library + Connects to server once every five seconds, sends a PUT request + and a request body + + created 14 Feb 2016 + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making PUT request"); + String contentType = "application/x-www-form-urlencoded"; + String putData = "name=light&age=46"; + + client.put("/", contentType, putData); + + // read the status code and body of the response + int statusCode = client.responseStatusCode(); + String response = client.responseBody(); + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/lib/ArduinoHttpClient/examples/SimplePut/arduino_secrets.h b/lib/ArduinoHttpClient/examples/SimplePut/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimplePut/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/SimpleWebSocket/SimpleWebSocket.ino b/lib/ArduinoHttpClient/examples/SimpleWebSocket/SimpleWebSocket.ino new file mode 100644 index 0000000..b20d74b --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleWebSocket/SimpleWebSocket.ino @@ -0,0 +1,80 @@ +/* + Simple WebSocket client for ArduinoHttpClient library + Connects to the WebSocket server, and sends a hello + message every 5 seconds + + created 28 Jun 2016 + by Sandeep Mistry + modified 22 Jan 2019 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include +#include "arduino_secrets.h" + +///////please enter your sensitive data in the Secret tab/arduino_secrets.h +/////// Wifi Settings /////// +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; + +char serverAddress[] = "echo.websocket.org"; // server address +int port = 80; + +WiFiClient wifi; +WebSocketClient client = WebSocketClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +int count = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("starting WebSocket client"); + client.begin(); + + while (client.connected()) { + Serial.print("Sending hello "); + Serial.println(count); + + // send a hello # + client.beginMessage(TYPE_TEXT); + client.print("hello "); + client.print(count); + client.endMessage(); + + // increment count for next message + count++; + + // check if a message is available to be received + int messageSize = client.parseMessage(); + + if (messageSize > 0) { + Serial.println("Received a message:"); + Serial.println(client.readString()); + } + + // wait 5 seconds + delay(5000); + } + + Serial.println("disconnected"); +} diff --git a/lib/ArduinoHttpClient/examples/SimpleWebSocket/arduino_secrets.h b/lib/ArduinoHttpClient/examples/SimpleWebSocket/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/lib/ArduinoHttpClient/examples/SimpleWebSocket/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/lib/ArduinoHttpClient/examples/node_test_server/package.json b/lib/ArduinoHttpClient/examples/node_test_server/package.json new file mode 100644 index 0000000..09f2d8b --- /dev/null +++ b/lib/ArduinoHttpClient/examples/node_test_server/package.json @@ -0,0 +1,13 @@ +{ + "name": "node_test_server", + "version": "0.0.1", + "author": { + "name": "Tom Igoe" + }, + "dependencies": { + "body-parser": ">=1.11.0", + "express": ">=4.0.0", + "multer": "*", + "ws": "^1.1.1" + } +} diff --git a/lib/ArduinoHttpClient/keywords.txt b/lib/ArduinoHttpClient/keywords.txt new file mode 100644 index 0000000..1b4bd2c --- /dev/null +++ b/lib/ArduinoHttpClient/keywords.txt @@ -0,0 +1,67 @@ +####################################### +# Syntax Coloring Map For HttpClient +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ArduinoHttpClient KEYWORD1 +HttpClient KEYWORD1 +WebSocketClient KEYWORD1 +URLEncoder KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +get KEYWORD2 +post KEYWORD2 +put KEYWORD2 +patch KEYWORD2 +startRequest KEYWORD2 +beginRequest KEYWORD2 +beginBody KEYWORD2 +sendHeader KEYWORD2 +sendBasicAuth KEYWORD2 +endRequest KEYWORD2 +responseStatusCode KEYWORD2 +readHeader KEYWORD2 +skipResponseHeaders KEYWORD2 +endOfHeadersReached KEYWORD2 +endOfBodyReached KEYWORD2 +completed KEYWORD2 +contentLength KEYWORD2 +isResponseChunked KEYWORD2 +connectionKeepAlive KEYWORD2 +noDefaultRequestHeaders KEYWORD2 +headerAvailable KEYWORD2 +readHeaderName KEYWORD2 +readHeaderValue KEYWORD2 +responseBody KEYWORD2 + +beginMessage KEYWORD2 +endMessage KEYWORD2 +parseMessage KEYWORD2 +messageType KEYWORD2 +isFinal KEYWORD2 +readString KEYWORD2 +ping KEYWORD2 + +encode KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +HTTP_SUCCESS LITERAL1 +HTTP_ERROR_CONNECTION_FAILED LITERAL1 +HTTP_ERROR_API LITERAL1 +HTTP_ERROR_TIMED_OUT LITERAL1 +HTTP_ERROR_INVALID_RESPONSE LITERAL1 + +TYPE_CONTINUATION LITERAL1 +TYPE_TEXT LITERAL1 +TYPE_BINARY LITERAL1 +TYPE_CONNECTION_CLOSE LITERAL1 +TYPE_PING LITERAL1 +TYPE_PONG LITERAL1 diff --git a/lib/ArduinoHttpClient/library.json b/lib/ArduinoHttpClient/library.json new file mode 100644 index 0000000..28952cf --- /dev/null +++ b/lib/ArduinoHttpClient/library.json @@ -0,0 +1,12 @@ +{ + "name": "ArduinoHttpClient", + "keywords": "http, web, client, ethernet, wifi, GSM", + "description": "Easily interact with web servers from Arduino, using HTTP and WebSocket's.", + "repository": { + "type": "git", + "url": "https://github.com/arduino-libraries/ArduinoHttpClient.git" + }, + "frameworks": "arduino", + "platforms": "*", + "version": "0.4.0" +} \ No newline at end of file diff --git a/lib/ArduinoHttpClient/library.properties b/lib/ArduinoHttpClient/library.properties new file mode 100644 index 0000000..632910e --- /dev/null +++ b/lib/ArduinoHttpClient/library.properties @@ -0,0 +1,10 @@ +name=ArduinoHttpClient +version=0.4.0 +author=Arduino +maintainer=Arduino +sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSocket's. +paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to a web server. It also supports exchanging messages with WebSocket servers. Based on Adrian McEwen's HttpClient library. +category=Communication +url=https://github.com/arduino-libraries/ArduinoHttpClient +architectures=* +includes=ArduinoHttpClient.h diff --git a/lib/ArduinoHttpClient/src/ArduinoHttpClient.h b/lib/ArduinoHttpClient/src/ArduinoHttpClient.h new file mode 100644 index 0000000..abb8494 --- /dev/null +++ b/lib/ArduinoHttpClient/src/ArduinoHttpClient.h @@ -0,0 +1,12 @@ +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#ifndef ArduinoHttpClient_h +#define ArduinoHttpClient_h + +#include "HttpClient.h" +#include "WebSocketClient.h" +#include "URLEncoder.h" + +#endif diff --git a/lib/ArduinoHttpClient/src/HttpClient.cpp b/lib/ArduinoHttpClient/src/HttpClient.cpp new file mode 100644 index 0000000..0d0b316 --- /dev/null +++ b/lib/ArduinoHttpClient/src/HttpClient.cpp @@ -0,0 +1,867 @@ +// Class to simplify HTTP fetching on Arduino +// (c) Copyright 2010-2011 MCQN Ltd +// Released under Apache License, version 2.0 + +#include "HttpClient.h" +#include "b64.h" +#define LOGGING +// Initialize constants +const char* HttpClient::kUserAgent = "Arduino/2.2.0"; +const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; +const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED; + +HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) + : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), + iConnectionClose(true), iSendDefaultRequestHeaders(true) +{ + Serial.println("aaaa"+String(iServerName)); + resetState(); + Serial.println("aaaa"+String(iServerName)); +} + +HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName.c_str(), aServerPort) +{ +} + +HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) + : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort), + iConnectionClose(true), iSendDefaultRequestHeaders(true) +{ + resetState(); +} + +void HttpClient::resetState() +{ + iState = eIdle; + iStatusCode = 0; + iContentLength = kNoContentLengthHeader; + iBodyLengthConsumed = 0; + iContentLengthPtr = kContentLengthPrefix; + iTransferEncodingChunkedPtr = kTransferEncodingChunked; + iIsChunked = false; + iChunkLength = 0; + iHttpResponseTimeout = kHttpResponseTimeout; +} +void HttpClient::stop() + +{ + iClient->stop(); + resetState(); +} + +void HttpClient::connectionKeepAlive() +{ + iConnectionClose = false; +} + +void HttpClient::noDefaultRequestHeaders() +{ + iSendDefaultRequestHeaders = false; +} + +void HttpClient::beginRequest() +{ + iState = eRequestStarted; +} + +int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, + const char* aContentType, int aContentLength, const byte aBody[]) +{ + // iServerName="82.156.1.111"; + if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk) + { + flushClientRx(); + + resetState(); + } +//Serial.println("here !"); + tHttpState initialState = iState; + + if ((eIdle != iState) && (eRequestStarted != iState)) + { + return HTTP_ERROR_API; + } +//Serial.println("here !!"); + if (iConnectionClose || !iClient->connected()) + { + if (iServerName) + { + if (!iClient->connect(iServerName, iServerPort) > 0) + { +#ifdef LOGGING + Serial.println("Connection failed"+String(iServerName)+":"+String(iServerPort)+String(aURLPath)); +#endif + return HTTP_ERROR_CONNECTION_FAILED; + } + } + else + { + if (!iClient->connect(iServerAddress, iServerPort) > 0) + { +#ifdef LOGGING + Serial.println("Connection failed"+String(iServerName)+":"+String(iServerPort)+String(aURLPath)); +#endif + return HTTP_ERROR_CONNECTION_FAILED; + } + } + } + else + { +#ifdef LOGGING + Serial.println("Connection already open"); +#endif + } + + // Now we're connected, send the first part of the request + int ret = sendInitialHeaders(aURLPath, aHttpMethod); + + if (HTTP_SUCCESS == ret) + { + if (aContentType) + { + sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType); + } + + if (aContentLength > 0) + { + sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength); + } + + bool hasBody = (aBody && aContentLength > 0); + + if (initialState == eIdle || hasBody) + { + // This was a simple version of the API, so terminate the headers now + finishHeaders(); + } + // else we'll call it in endRequest or in the first call to print, etc. + + if (hasBody) + { + write(aBody, aContentLength); + } + } + + return ret; +} + +int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod) +{ +#ifdef LOGGING + Serial.println("Connected"); +#endif + // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0" + iClient->print(aHttpMethod); + iClient->print(" "); + + iClient->print(aURLPath); + iClient->println(" HTTP/1.1"); + if (iSendDefaultRequestHeaders) + { + // The host header, if required + if (iServerName) + { + iClient->print("Host: "); + iClient->print(iServerName); + if (iServerPort != kHttpPort) + { + iClient->print(":"); + iClient->print(iServerPort); + } + iClient->println(); + } + // And user-agent string + sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); + } + + if (iConnectionClose) + { + // Tell the server to + // close this connection after we're done + sendHeader(HTTP_HEADER_CONNECTION, "close"); + } + + // Everything has gone well + iState = eRequestStarted; + return HTTP_SUCCESS; +} + +void HttpClient::sendHeader(const char* aHeader) +{ + iClient->println(aHeader); +} + +void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue) +{ + iClient->print(aHeaderName); + iClient->print(": "); + iClient->println(aHeaderValue); +} + +void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue) +{ + iClient->print(aHeaderName); + iClient->print(": "); + iClient->println(aHeaderValue); +} + +void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword) +{ + // Send the initial part of this header line + iClient->print("Authorization: Basic "); + // Now Base64 encode "aUser:aPassword" and send that + // This seems trickier than it should be but it's mostly to avoid either + // (a) some arbitrarily sized buffer which hopes to be big enough, or + // (b) allocating and freeing memory + // ...so we'll loop through 3 bytes at a time, outputting the results as we + // go. + // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data + unsigned char input[3]; + unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print + int userLen = strlen(aUser); + int passwordLen = strlen(aPassword); + int inputOffset = 0; + for (int i = 0; i < (userLen+1+passwordLen); i++) + { + // Copy the relevant input byte into the input + if (i < userLen) + { + input[inputOffset++] = aUser[i]; + } + else if (i == userLen) + { + input[inputOffset++] = ':'; + } + else + { + input[inputOffset++] = aPassword[i-(userLen+1)]; + } + // See if we've got a chunk to encode + if ( (inputOffset == 3) || (i == userLen+passwordLen) ) + { + // We've either got to a 3-byte boundary, or we've reached then end + b64_encode(input, inputOffset, output, 4); + // NUL-terminate the output string + output[4] = '\0'; + // And write it out + iClient->print((char*)output); +// FIXME We might want to fill output with '=' characters if b64_encode doesn't +// FIXME do it for us when we're encoding the final chunk + inputOffset = 0; + } + } + // And end the header we've sent + iClient->println(); +} + +void HttpClient::finishHeaders() +{ + iClient->println(); + iState = eRequestSent; +} + +void HttpClient::flushClientRx() +{ + while (iClient->available()) + { + iClient->read(); + } +} + +void HttpClient::endRequest() +{ + beginBody(); +} + +void HttpClient::beginBody() +{ + if (iState < eRequestSent) + { + // We still need to finish off the headers + finishHeaders(); + } + // else the end of headers has already been sent, so nothing to do here +} + +int HttpClient::get(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_GET); +} + +int HttpClient::get(const String& aURLPath) +{ + return get(aURLPath.c_str()); +} + +int HttpClient::post(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_POST); +} + +int HttpClient::post(const String& aURLPath) +{ + return post(aURLPath.c_str()); +} + +int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody); +} + +int HttpClient::put(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_PUT); +} + +int HttpClient::put(const String& aURLPath) +{ + return put(aURLPath.c_str()); +} + +int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return put(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); +} + +int HttpClient::patch(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_PATCH); +} + +int HttpClient::patch(const String& aURLPath) +{ + return patch(aURLPath.c_str()); +} + +int HttpClient::patch(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return patch(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::patch(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody); +} + +int HttpClient::del(const char* aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_DELETE); +} + +int HttpClient::del(const String& aURLPath) +{ + return del(aURLPath.c_str()); +} + +int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody) +{ + return del(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); +} + +int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody) +{ + return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); +} + +int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody); +} + +int HttpClient::responseStatusCode() +{ + if (iState < eRequestSent) + { + return HTTP_ERROR_API; + } + // The first line will be of the form Status-Line: + // HTTP-Version SP Status-Code SP Reason-Phrase CRLF + // Where HTTP-Version is of the form: + // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT + + int c = '\0'; + do + { + // Make sure the status code is reset, and likewise the state. This + // lets us easily cope with 1xx informational responses by just + // ignoring them really, and reading the next line for a proper response + iStatusCode = 0; + iState = eRequestSent; + + unsigned long timeoutStart = millis(); + // Psuedo-regexp we're expecting before the status-code + const char* statusPrefix = "HTTP/*.* "; + const char* statusPtr = statusPrefix; + // Whilst we haven't timed out & haven't reached the end of the headers + while ((c != '\n') && + ( (millis() - timeoutStart) < iHttpResponseTimeout )) + { + if (available()) + { + c = read(); + if (c != -1) + { + switch(iState) + { + case eRequestSent: + // We haven't reached the status code yet + if ( (*statusPtr == '*') || (*statusPtr == c) ) + { + // This character matches, just move along + statusPtr++; + if (*statusPtr == '\0') + { + // We've reached the end of the prefix + iState = eReadingStatusCode; + } + } + else + { + return HTTP_ERROR_INVALID_RESPONSE; + } + break; + case eReadingStatusCode: + if (isdigit(c)) + { + // This assumes we won't get more than the 3 digits we + // want + iStatusCode = iStatusCode*10 + (c - '0'); + } + else + { + // We've reached the end of the status code + // We could sanity check it here or double-check for ' ' + // rather than anything else, but let's be lenient + iState = eStatusCodeRead; + } + break; + case eStatusCodeRead: + // We're just waiting for the end of the line now + break; + + default: + break; + }; + // We read something, reset the timeout counter + timeoutStart = millis(); + } + } + else + { + // We haven't got any data, so let's pause to allow some to + // arrive + delay(kHttpWaitForDataDelay); + } + } + if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) ) + { + // We've reached the end of an informational status line + c = '\0'; // Clear c so we'll go back into the data reading loop + } + } + // If we've read a status code successfully but it's informational (1xx) + // loop back to the start + while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) ); + + if ( (c == '\n') && (iState == eStatusCodeRead) ) + { + // We've read the status-line successfully + return iStatusCode; + } + else if (c != '\n') + { + // We must've timed out before we reached the end of the line + return HTTP_ERROR_TIMED_OUT; + } + else + { + // This wasn't a properly formed status line, or at least not one we + // could understand + return HTTP_ERROR_INVALID_RESPONSE; + } +} + +int HttpClient::skipResponseHeaders() +{ + // Just keep reading until we finish reading the headers or time out + unsigned long timeoutStart = millis(); + // Whilst we haven't timed out & haven't reached the end of the headers + while ((!endOfHeadersReached()) && + ( (millis() - timeoutStart) < iHttpResponseTimeout )) + { + if (available()) + { + (void)readHeader(); + // We read something, reset the timeout counter + timeoutStart = millis(); + } + else + { + // We haven't got any data, so let's pause to allow some to + // arrive + delay(kHttpWaitForDataDelay); + } + } + if (endOfHeadersReached()) + { + // Success + return HTTP_SUCCESS; + } + else + { + // We must've timed out + return HTTP_ERROR_TIMED_OUT; + } +} + +bool HttpClient::endOfHeadersReached() +{ + return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk); +}; + +int HttpClient::contentLength() +{ + // skip the response headers, if they haven't been read already + if (!endOfHeadersReached()) + { + skipResponseHeaders(); + } + + return iContentLength; +} + +String HttpClient::responseBody() +{ + int bodyLength = contentLength(); + String response; + + if (bodyLength > 0) + { + // try to reserve bodyLength bytes + if (response.reserve(bodyLength) == 0) { + // String reserve failed + return String((const char*)NULL); + } + } + + // keep on timedRead'ing, until: + // - we have a content length: body length equals consumed or no bytes + // available + // - no content length: no bytes are available + while (iBodyLengthConsumed != bodyLength) + { + int c = timedRead(); + + if (c == -1) { + // read timed out, done + break; + } + + if (!response.concat((char)c)) { + // adding char failed + return String((const char*)NULL); + } + } + + if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) { + // failure, we did not read in reponse content length bytes + return String((const char*)NULL); + } + + return response; +} + +bool HttpClient::endOfBodyReached() +{ + if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) + { + // We've got to the body and we know how long it will be + return (iBodyLengthConsumed >= contentLength()); + } + return false; +} + +int HttpClient::available() +{ + if (iState == eReadingChunkLength) + { + while (iClient->available()) + { + char c = iClient->read(); + + if (c == '\n') + { + iState = eReadingBodyChunk; + break; + } + else if (c == '\r') + { + // no-op + } + else if (isHexadecimalDigit(c)) + { + char digit[2] = {c, '\0'}; + + iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16); + } + } + } + + if (iState == eReadingBodyChunk && iChunkLength == 0) + { + iState = eReadingChunkLength; + } + + if (iState == eReadingChunkLength) + { + return 0; + } + + int clientAvailable = iClient->available(); + + if (iState == eReadingBodyChunk) + { + return min(clientAvailable, iChunkLength); + } + else + { + return clientAvailable; + } +} + + +int HttpClient::read() +{ + if (iIsChunked && !available()) + { + return -1; + } + + int ret = iClient->read(); + if (ret >= 0) + { + if (endOfHeadersReached() && iContentLength > 0) + { + // We're outputting the body now and we've seen a Content-Length header + // So keep track of how many bytes are left + iBodyLengthConsumed++; + } + + if (iState == eReadingBodyChunk) + { + iChunkLength--; + + if (iChunkLength == 0) + { + iState = eReadingChunkLength; + } + } + } + return ret; +} + +bool HttpClient::headerAvailable() +{ + // clear the currently store header line + iHeaderLine = ""; + int i = 0; + while (!endOfHeadersReached()) + { + // read a byte from the header + int c = readHeader(); + + if (c == '\r' || c == '\n'||c==-1) + { + if (iHeaderLine.length()) + { + // end of the line, all done + break; + } + else + { + // ignore any CR or LF characters + continue; + } + } + + // append byte to header line + iHeaderLine += (char)c; + i++; + if(i>20000) return true; + } + + return (iHeaderLine.length() > 0); +} + +String HttpClient::readHeaderName() +{ + int colonIndex = iHeaderLine.indexOf(':'); + + if (colonIndex == -1) + { + return ""; + } + + return iHeaderLine.substring(0, colonIndex); +} + +String HttpClient::readHeaderValue() +{ + int colonIndex = iHeaderLine.indexOf(':'); + int startIndex = colonIndex + 1; + + if (colonIndex == -1) + { + return ""; + } + + // trim any leading whitespace + while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex])) + { + startIndex++; + } + + return iHeaderLine.substring(startIndex); +} + +int HttpClient::read(uint8_t *buf, size_t size) +{ + int ret =iClient->read(buf, size); + if (endOfHeadersReached() && iContentLength > 0) + { + // We're outputting the body now and we've seen a Content-Length header + // So keep track of how many bytes are left + if (ret >= 0) + { + iBodyLengthConsumed += ret; + } + } + return ret; +} + +int HttpClient::readHeader() +{ + char c = read(); + + if (endOfHeadersReached()) + { + // We've passed the headers, but rather than return an error, we'll just + // act as a slightly less efficient version of read() + return c; + } + + // Whilst reading out the headers to whoever wants them, we'll keep an + // eye out for the "Content-Length" header + switch(iState) + { + case eStatusCodeRead: + // We're at the start of a line, or somewhere in the middle of reading + // the Content-Length prefix + if (*iContentLengthPtr == c) + { + // This character matches, just move along + iContentLengthPtr++; + if (*iContentLengthPtr == '\0') + { + // We've reached the end of the prefix + iState = eReadingContentLength; + // Just in case we get multiple Content-Length headers, this + // will ensure we just get the value of the last one + iContentLength = 0; + iBodyLengthConsumed = 0; + } + } + else if (*iTransferEncodingChunkedPtr == c) + { + // This character matches, just move along + iTransferEncodingChunkedPtr++; + if (*iTransferEncodingChunkedPtr == '\0') + { + // We've reached the end of the Transfer Encoding: chunked header + iIsChunked = true; + iState = eSkipToEndOfHeader; + } + } + else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r')) + { + // We've found a '\r' at the start of a line, so this is probably + // the end of the headers + iState = eLineStartingCRFound; + } + else + { + // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line + iState = eSkipToEndOfHeader; + } + break; + case eReadingContentLength: + if (isdigit(c)) + { + iContentLength = iContentLength*10 + (c - '0'); + } + else + { + // We've reached the end of the content length + // We could sanity check it here or double-check for "\r\n" + // rather than anything else, but let's be lenient + iState = eSkipToEndOfHeader; + } + break; + case eLineStartingCRFound: + if (c == '\n') + { + if (iIsChunked) + { + iState = eReadingChunkLength; + iChunkLength = 0; + } + else + { + iState = eReadingBody; + } + } + break; + default: + // We're just waiting for the end of the line now + break; + }; + + if ( (c == '\n') && !endOfHeadersReached() ) + { + // We've got to the end of this line, start processing again + iState = eStatusCodeRead; + iContentLengthPtr = kContentLengthPrefix; + iTransferEncodingChunkedPtr = kTransferEncodingChunked; + } + // And return the character read to whoever wants it + return c; +} + + + diff --git a/lib/ArduinoHttpClient/src/HttpClient.h b/lib/ArduinoHttpClient/src/HttpClient.h new file mode 100644 index 0000000..de697f0 --- /dev/null +++ b/lib/ArduinoHttpClient/src/HttpClient.h @@ -0,0 +1,392 @@ +// Class to simplify HTTP fetching on Arduino +// (c) Copyright MCQN Ltd. 2010-2012 +// Released under Apache License, version 2.0 + +#ifndef HttpClient_h +#define HttpClient_h + +#include +#include +#include "Client.h" + +static const int HTTP_SUCCESS =0; +// The end of the headers has been reached. This consumes the '\n' +// Could not connect to the server +static const int HTTP_ERROR_CONNECTION_FAILED =-1; +// This call was made when the HttpClient class wasn't expecting it +// to be called. Usually indicates your code is using the class +// incorrectly +static const int HTTP_ERROR_API =-2; +// Spent too long waiting for a reply +static const int HTTP_ERROR_TIMED_OUT =-3; +// The response from the server is invalid, is it definitely an HTTP +// server? +static const int HTTP_ERROR_INVALID_RESPONSE =-4; + +// Define some of the common methods and headers here +// That lets other code reuse them without having to declare another copy +// of them, so saves code space and RAM +#define HTTP_METHOD_GET "GET" +#define HTTP_METHOD_POST "POST" +#define HTTP_METHOD_PUT "PUT" +#define HTTP_METHOD_PATCH "PATCH" +#define HTTP_METHOD_DELETE "DELETE" +#define HTTP_HEADER_CONTENT_LENGTH "Content-Length" +#define HTTP_HEADER_CONTENT_TYPE "Content-Type" +#define HTTP_HEADER_CONNECTION "Connection" +#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding" +#define HTTP_HEADER_USER_AGENT "User-Agent" +#define HTTP_HEADER_VALUE_CHUNKED "chunked" + +class HttpClient : public Client +{ +public: + static const int kNoContentLengthHeader =-1; + static const int kHttpPort =80; + static const char* kUserAgent; + +// FIXME Write longer API request, using port and user-agent, example +// FIXME Update tempToPachube example to calculate Content-Length correctly + + HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort); + HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); + HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); + + /** Start a more complex request. + Use this when you need to send additional headers in the request, + but you will also need to call endRequest() when you are finished. + */ + void beginRequest(); + + /** End a more complex request. + Use this when you need to have sent additional headers in the request, + but you will also need to call beginRequest() at the start. + */ + void endRequest(); + + /** Start the body of a more complex request. + Use this when you need to send the body after additional headers + in the request, but can optionally call endRequest() when + you are finished. + */ + void beginBody(); + + /** Connect to the server and start to send a GET request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int get(const char* aURLPath); + int get(const String& aURLPath); + + /** Connect to the server and start to send a POST request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int post(const char* aURLPath); + int post(const String& aURLPath); + + /** Connect to the server and send a POST request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int post(const char* aURLPath, const char* aContentType, const char* aBody); + int post(const String& aURLPath, const String& aContentType, const String& aBody); + int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send a PUT request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int put(const char* aURLPath); + int put(const String& aURLPath); + + /** Connect to the server and send a PUT request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int put(const char* aURLPath, const char* aContentType, const char* aBody); + int put(const String& aURLPath, const String& aContentType, const String& aBody); + int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send a PATCH request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int patch(const char* aURLPath); + int patch(const String& aURLPath); + + /** Connect to the server and send a PATCH request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int patch(const char* aURLPath, const char* aContentType, const char* aBody); + int patch(const String& aURLPath, const String& aContentType, const String& aBody); + int patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send a DELETE request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int del(const char* aURLPath); + int del(const String& aURLPath); + + /** Connect to the server and send a DELETE request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int del(const char* aURLPath, const char* aContentType, const char* aBody); + int del(const String& aURLPath, const String& aContentType, const String& aBody); + int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send the request. + If a body is provided, the entire request (including headers and body) will be sent + @param aURLPath Url to request + @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. + @param aContentType Content type of request body (optional) + @param aContentLength Length of request body (optional) + @param aBody Body of request (optional) + @return 0 if successful, else error + */ + int startRequest(const char* aURLPath, + const char* aHttpMethod, + const char* aContentType = NULL, + int aContentLength = -1, + const byte aBody[] = NULL); + + /** Send an additional header line. This can only be called in between the + calls to beginRequest and endRequest. + @param aHeader Header line to send, in its entirety (but without the + trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" + */ + void sendHeader(const char* aHeader); + + void sendHeader(const String& aHeader) + { sendHeader(aHeader.c_str()); } + + /** Send an additional header line. This is an alternate form of + sendHeader() which takes the header name and content as separate strings. + The call will add the ": " to separate the header, so for example, to + send a XXXXXX header call sendHeader("XXXXX", "Something") + @param aHeaderName Type of header being sent + @param aHeaderValue Value for that header + */ + void sendHeader(const char* aHeaderName, const char* aHeaderValue); + + void sendHeader(const String& aHeaderName, const String& aHeaderValue) + { sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); } + + /** Send an additional header line. This is an alternate form of + sendHeader() which takes the header name and content separately but where + the value is provided as an integer. + The call will add the ": " to separate the header, so for example, to + send a XXXXXX header call sendHeader("XXXXX", 123) + @param aHeaderName Type of header being sent + @param aHeaderValue Value for that header + */ + void sendHeader(const char* aHeaderName, const int aHeaderValue); + + void sendHeader(const String& aHeaderName, const int aHeaderValue) + { sendHeader(aHeaderName.c_str(), aHeaderValue); } + + /** Send a basic authentication header. This will encode the given username + and password, and send them in suitable header line for doing Basic + Authentication. + @param aUser Username for the authorization + @param aPassword Password for the user aUser + */ + void sendBasicAuth(const char* aUser, const char* aPassword); + + void sendBasicAuth(const String& aUser, const String& aPassword) + { sendBasicAuth(aUser.c_str(), aPassword.c_str()); } + + /** Get the HTTP status code contained in the response. + For example, 200 for successful request, 404 for file not found, etc. + */ + int responseStatusCode(); + + /** Check if a header is available to be read. + Use readHeaderName() to read header name, and readHeaderValue() to + read the header value + MUST be called after responseStatusCode() and before contentLength() + */ + bool headerAvailable(); + + /** Read the name of the current response header. + Returns empty string if a header is not available. + */ + String readHeaderName(); + + /** Read the vallue of the current response header. + Returns empty string if a header is not available. + */ + String readHeaderValue(); + + /** Read the next character of the response headers. + This functions in the same way as read() but to be used when reading + through the headers. Check whether or not the end of the headers has + been reached by calling endOfHeadersReached(), although after that point + this will still return data as read() would, but slightly less efficiently + MUST be called after responseStatusCode() and before contentLength() + @return The next character of the response headers + */ + int readHeader(); + + /** Skip any response headers to get to the body. + Use this if you don't want to do any special processing of the headers + returned in the response. You can also use it after you've found all of + the headers you're interested in, and just want to get on with processing + the body. + MUST be called after responseStatusCode() + @return HTTP_SUCCESS if successful, else an error code + */ + int skipResponseHeaders(); + + /** Test whether all of the response headers have been consumed. + @return true if we are now processing the response body, else false + */ + bool endOfHeadersReached(); + + /** Test whether the end of the body has been reached. + Only works if the Content-Length header was returned by the server + @return true if we are now at the end of the body, else false + */ + bool endOfBodyReached(); + virtual bool endOfStream() { return endOfBodyReached(); }; + virtual bool completed() { return endOfBodyReached(); }; + + /** Return the length of the body. + Also skips response headers if they have not been read already + MUST be called after responseStatusCode() + @return Length of the body, in bytes, or kNoContentLengthHeader if no + Content-Length header was returned by the server + */ + int contentLength(); + + /** Returns if the response body is chunked + @return true if response body is chunked, false otherwise + */ + int isResponseChunked() { return iIsChunked; } + + /** Return the response body as a String + Also skips response headers if they have not been read already + MUST be called after responseStatusCode() + @return response body of request as a String + */ + String responseBody(); + + /** Enables connection keep-alive mode + */ + void connectionKeepAlive(); + + /** Disables sending the default request headers (Host and User Agent) + */ + void noDefaultRequestHeaders(); + + // Inherited from Print + // Note: 1st call to these indicates the user is sending the body, so if need + // Note: be we should finish the header first + virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); }; + virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); }; + // Inherited from Stream + virtual int available(); + /** Read the next byte from the server. + @return Byte read or -1 if there are no bytes available. + */ + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek() { return iClient->peek(); }; + virtual void flush() { iClient->flush(); }; + + // Inherited from Client + virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); }; + virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); }; + virtual void stop(); + virtual uint8_t connected() { return iClient->connected(); }; + virtual operator bool() { return bool(iClient); }; + virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; }; + virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; }; +public: + /** Reset internal state data back to the "just initialised" state + */ + void resetState(); + + /** Send the first part of the request and the initial headers. + @param aURLPath Url to request + @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. + @return 0 if successful, else error + */ + int sendInitialHeaders(const char* aURLPath, + const char* aHttpMethod); + + /* Let the server know that we've reached the end of the headers + */ + void finishHeaders(); + + /** Reading any pending data from the client (used in connection keep alive mode) + */ + void flushClientRx(); + + // Number of milliseconds that we wait each time there isn't any data + // available to be read (during status code and header processing) + static const int kHttpWaitForDataDelay = 1000; + // Number of milliseconds that we'll wait in total without receiveing any + // data before returning HTTP_ERROR_TIMED_OUT (during status code and header + // processing) + static const int kHttpResponseTimeout = 30*1000; + static const char* kContentLengthPrefix; + static const char* kTransferEncodingChunked; + typedef enum { + eIdle, + eRequestStarted, + eRequestSent, + eReadingStatusCode, + eStatusCodeRead, + eReadingContentLength, + eSkipToEndOfHeader, + eLineStartingCRFound, + eReadingBody, + eReadingChunkLength, + eReadingBodyChunk + } tHttpState; + // Client we're using + Client* iClient; + // Server we are connecting to + const char* iServerName; + IPAddress iServerAddress; + // Port of server we are connecting to + uint16_t iServerPort; + // Current state of the finite-state-machine + tHttpState iState; + // Stores the status code for the response, once known + int iStatusCode; + // Stores the value of the Content-Length header, if present + int iContentLength; + // How many bytes of the response body have been read by the user + int iBodyLengthConsumed; + // How far through a Content-Length header prefix we are + const char* iContentLengthPtr; + // How far through a Transfer-Encoding chunked header we are + const char* iTransferEncodingChunkedPtr; + // Stores if the response body is chunked + bool iIsChunked; + // Stores the value of the current chunk length, if present + int iChunkLength; + uint32_t iHttpResponseTimeout; + bool iConnectionClose; + bool iSendDefaultRequestHeaders; + String iHeaderLine; +}; + +#endif diff --git a/lib/ArduinoHttpClient/src/URLEncoder.cpp b/lib/ArduinoHttpClient/src/URLEncoder.cpp new file mode 100644 index 0000000..7baf5a9 --- /dev/null +++ b/lib/ArduinoHttpClient/src/URLEncoder.cpp @@ -0,0 +1,53 @@ +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2019 +// Released under Apache License, version 2.0 + +#include "URLEncoder.h" + +URLEncoderClass::URLEncoderClass() +{ +} + +URLEncoderClass::~URLEncoderClass() +{ +} + +String URLEncoderClass::encode(const char* str) +{ + return encode(str, strlen(str)); +} + +String URLEncoderClass::encode(const String& str) +{ + return encode(str.c_str(), str.length()); +} + +String URLEncoderClass::encode(const char* str, int length) +{ + String encoded; + + encoded.reserve(length); + + for (int i = 0; i < length; i++) { + char c = str[i]; + + const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) { + encoded += c; + } else { + char s[4]; + + s[0] = '%'; + s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf]; + s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)]; + s[3] = 0; + + encoded += s; + } + } + + return encoded; +} + +URLEncoderClass URLEncoder; diff --git a/lib/ArduinoHttpClient/src/URLEncoder.h b/lib/ArduinoHttpClient/src/URLEncoder.h new file mode 100644 index 0000000..bce5f17 --- /dev/null +++ b/lib/ArduinoHttpClient/src/URLEncoder.h @@ -0,0 +1,25 @@ +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2019 +// Released under Apache License, version 2.0 + +#ifndef URL_ENCODER_H +#define URL_ENCODER_H + +#include + +class URLEncoderClass +{ +public: + URLEncoderClass(); + virtual ~URLEncoderClass(); + + static String encode(const char* str); + static String encode(const String& str); + +private: + static String encode(const char* str, int length); +}; + +extern URLEncoderClass URLEncoder; + +#endif diff --git a/lib/ArduinoHttpClient/src/WebSocketClient.cpp b/lib/ArduinoHttpClient/src/WebSocketClient.cpp new file mode 100644 index 0000000..ab41b0a --- /dev/null +++ b/lib/ArduinoHttpClient/src/WebSocketClient.cpp @@ -0,0 +1,372 @@ +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#include "b64.h" + +#include "WebSocketClient.h" + +WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName, aServerPort), + iTxStarted(false), + iRxSize(0) +{ +} + +WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName, aServerPort), + iTxStarted(false), + iRxSize(0) +{ +} + +WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) + : HttpClient(aClient, aServerAddress, aServerPort), + iTxStarted(false), + iRxSize(0) +{ +} + +int WebSocketClient::begin(const char* aPath) +{ + // start the GET request + beginRequest(); + connectionKeepAlive(); + int status = get(aPath); + + if (status == 0) + { + uint8_t randomKey[16]; + char base64RandomKey[25]; + + // create a random key for the connection upgrade + for (int i = 0; i < (int)sizeof(randomKey); i++) + { + randomKey[i] = random(0x01, 0xff); + } + memset(base64RandomKey, 0x00, sizeof(base64RandomKey)); + b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey)); + + // start the connection upgrade sequence + sendHeader("Upgrade", "websocket"); + sendHeader("Connection", "Upgrade"); + sendHeader("Sec-WebSocket-Key", base64RandomKey); + sendHeader("Sec-WebSocket-Version", "13"); + endRequest(); + + status = responseStatusCode(); + + if (status > 0) + { + skipResponseHeaders(); + } + } + + iRxSize = 0; + + // status code of 101 means success + return (status == 101) ? 0 : status; +} + +int WebSocketClient::begin(const String& aPath) +{ + return begin(aPath.c_str()); +} + +int WebSocketClient::beginMessage(int aType) +{ + if (iTxStarted) + { + // fail TX already started + return 1; + } + + iTxStarted = true; + iTxMessageType = (aType & 0xf); + iTxSize = 0; + + return 0; +} + +int WebSocketClient::endMessage() +{ + if (!iTxStarted) + { + // fail TX not started + return 1; + } + + // send FIN + the message type (opcode) + HttpClient::write(0x80 | iTxMessageType); + + // the message is masked (0x80) + // send the length + if (iTxSize < 126) + { + HttpClient::write(0x80 | (uint8_t)iTxSize); + } + else if (iTxSize < 0xffff) + { + HttpClient::write(0x80 | 126); + HttpClient::write((iTxSize >> 8) & 0xff); + HttpClient::write((iTxSize >> 0) & 0xff); + } + else + { + HttpClient::write(0x80 | 127); + HttpClient::write((iTxSize >> 56) & 0xff); + HttpClient::write((iTxSize >> 48) & 0xff); + HttpClient::write((iTxSize >> 40) & 0xff); + HttpClient::write((iTxSize >> 32) & 0xff); + HttpClient::write((iTxSize >> 24) & 0xff); + HttpClient::write((iTxSize >> 16) & 0xff); + HttpClient::write((iTxSize >> 8) & 0xff); + HttpClient::write((iTxSize >> 0) & 0xff); + } + + uint8_t maskKey[4]; + + // create a random mask for the data and send + for (int i = 0; i < (int)sizeof(maskKey); i++) + { + maskKey[i] = random(0xff); + } + HttpClient::write(maskKey, sizeof(maskKey)); + + // mask the data and send + for (int i = 0; i < (int)iTxSize; i++) { + iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)]; + } + + size_t txSize = iTxSize; + + iTxStarted = false; + iTxSize = 0; + + return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1; +} + +size_t WebSocketClient::write(uint8_t aByte) +{ + return write(&aByte, sizeof(aByte)); +} + +size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) +{ + if (iState < eReadingBody) + { + // have not upgraded the connection yet + return HttpClient::write(aBuffer, aSize); + } + + if (!iTxStarted) + { + // fail TX not started + return 0; + } + + // check if the write size, fits in the buffer + if ((iTxSize + aSize) > sizeof(iTxBuffer)) + { + aSize = sizeof(iTxSize) - iTxSize; + } + + // copy data into the buffer + memcpy(iTxBuffer + iTxSize, aBuffer, aSize); + + iTxSize += aSize; + + return aSize; +} + +int WebSocketClient::parseMessage() +{ + flushRx(); + + // make sure 2 bytes (opcode + length) + // are available + if (HttpClient::available() < 2) + { + return 0; + } + + // read open code and length + uint8_t opcode = HttpClient::read(); + int length = HttpClient::read(); + + if ((opcode & 0x0f) == 0) + { + // continuation, use previous opcode and update flags + iRxOpCode |= opcode; + } + else + { + iRxOpCode = opcode; + } + + iRxMasked = (length & 0x80); + length &= 0x7f; + + // read the RX size + if (length < 126) + { + iRxSize = length; + } + else if (length == 126) + { + iRxSize = (HttpClient::read() << 8) | HttpClient::read(); + } + else + { + iRxSize = ((uint64_t)HttpClient::read() << 56) | + ((uint64_t)HttpClient::read() << 48) | + ((uint64_t)HttpClient::read() << 40) | + ((uint64_t)HttpClient::read() << 32) | + ((uint64_t)HttpClient::read() << 24) | + ((uint64_t)HttpClient::read() << 16) | + ((uint64_t)HttpClient::read() << 8) | + (uint64_t)HttpClient::read(); + } + + // read in the mask, if present + if (iRxMasked) + { + for (int i = 0; i < (int)sizeof(iRxMaskKey); i++) + { + iRxMaskKey[i] = HttpClient::read(); + } + } + + iRxMaskIndex = 0; + + if (TYPE_CONNECTION_CLOSE == messageType()) + { + flushRx(); + stop(); + iRxSize = 0; + } + else if (TYPE_PING == messageType()) + { + beginMessage(TYPE_PONG); + while(available()) + { + write(read()); + } + endMessage(); + + iRxSize = 0; + } + else if (TYPE_PONG == messageType()) + { + flushRx(); + iRxSize = 0; + } + + return iRxSize; +} + +int WebSocketClient::messageType() +{ + return (iRxOpCode & 0x0f); +} + +bool WebSocketClient::isFinal() +{ + return ((iRxOpCode & 0x80) != 0); +} + +String WebSocketClient::readString() +{ + int avail = available(); + String s; + + if (avail > 0) + { + s.reserve(avail); + + for (int i = 0; i < avail; i++) + { + s += (char)read(); + } + } + + return s; +} + +int WebSocketClient::ping() +{ + uint8_t pingData[16]; + + // create random data for the ping + for (int i = 0; i < (int)sizeof(pingData); i++) + { + pingData[i] = random(0xff); + } + + beginMessage(TYPE_PING); + write(pingData, sizeof(pingData)); + return endMessage(); +} + +int WebSocketClient::available() +{ + if (iState < eReadingBody) + { + return HttpClient::available(); + } + + return iRxSize; +} + +int WebSocketClient::read() +{ + byte b; + + if (read(&b, sizeof(b))) + { + return b; + } + + return -1; +} + +int WebSocketClient::read(uint8_t *aBuffer, size_t aSize) +{ + int readCount = HttpClient::read(aBuffer, aSize); + + if (readCount > 0) + { + iRxSize -= readCount; + + // unmask the RX data if needed + if (iRxMasked) + { + for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) + { + aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; + } + } + } + + return readCount; +} + +int WebSocketClient::peek() +{ + int p = HttpClient::peek(); + + if (p != -1 && iRxMasked) + { + // unmask the RX data if needed + p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; + } + + return p; +} + +void WebSocketClient::flushRx() +{ + while(available()) + { + read(); + } +} diff --git a/lib/ArduinoHttpClient/src/WebSocketClient.h b/lib/ArduinoHttpClient/src/WebSocketClient.h new file mode 100644 index 0000000..4b009e6 --- /dev/null +++ b/lib/ArduinoHttpClient/src/WebSocketClient.h @@ -0,0 +1,99 @@ +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#ifndef WebSocketClient_h +#define WebSocketClient_h + +#include + +#include "HttpClient.h" + +static const int TYPE_CONTINUATION = 0x0; +static const int TYPE_TEXT = 0x1; +static const int TYPE_BINARY = 0x2; +static const int TYPE_CONNECTION_CLOSE = 0x8; +static const int TYPE_PING = 0x9; +static const int TYPE_PONG = 0xa; + +class WebSocketClient : public HttpClient +{ +public: + WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort); + WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort); + WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort); + + /** Start the Web Socket connection to the specified path + @param aURLPath Path to use in request (optional, "/" is used by default) + @return 0 if successful, else error + */ + int begin(const char* aPath = "/"); + int begin(const String& aPath); + + /** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY) + Use the write or Stream API's to set message content, followed by endMessage + to complete the message. + @param aURLPath Path to use in request + @return 0 if successful, else error + */ + int beginMessage(int aType); + + /** Completes sending of a message started by beginMessage + @return 0 if successful, else error + */ + int endMessage(); + + /** Try to parse an incoming messages + @return 0 if no message available, else size of parsed message + */ + int parseMessage(); + + /** Returns type of current parsed message + @return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY) + */ + int messageType(); + + /** Returns if the current message is the final chunk of a split + message + @return true for final message, false otherwise + */ + bool isFinal(); + + /** Read the current messages as a string + @return current message as a string + */ + String readString(); + + /** Send a ping + @return 0 if successful, else error + */ + int ping(); + + // Inherited from Print + virtual size_t write(uint8_t aByte); + virtual size_t write(const uint8_t *aBuffer, size_t aSize); + // Inherited from Stream + virtual int available(); + /** Read the next byte from the server. + @return Byte read or -1 if there are no bytes available. + */ + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek(); + +private: + void flushRx(); + +private: + bool iTxStarted; + uint8_t iTxMessageType; + uint8_t iTxBuffer[128]; + uint64_t iTxSize; + + uint8_t iRxOpCode; + uint64_t iRxSize; + bool iRxMasked; + int iRxMaskIndex; + uint8_t iRxMaskKey[4]; +}; + +#endif diff --git a/lib/ArduinoHttpClient/src/b64.cpp b/lib/ArduinoHttpClient/src/b64.cpp new file mode 100644 index 0000000..683d60a --- /dev/null +++ b/lib/ArduinoHttpClient/src/b64.cpp @@ -0,0 +1,72 @@ +// Simple Base64 code +// (c) Copyright 2010 MCQN Ltd. +// Released under Apache License, version 2.0 + +#include "b64.h" + +/* Simple test program +#include +void main() +{ + char* in = "amcewen"; + char out[22]; + + b64_encode(in, 15, out, 22); + out[21] = '\0'; + + printf(out); +} +*/ + +int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen) +{ + // Work out if we've got enough space to encode the input + // Every 6 bits of input becomes a byte of output + if (aOutputLen < (aInputLen*8)/6) + { + // FIXME Should we return an error here, or just the length + return (aInputLen*8)/6; + } + + // If we get here we've got enough space to do the encoding + + const char* b64_dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (aInputLen == 3) + { + aOutput[0] = b64_dictionary[aInput[0] >> 2]; + aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)]; + aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2|(aInput[2]>>6)]; + aOutput[3] = b64_dictionary[aInput[2]&0x3F]; + } + else if (aInputLen == 2) + { + aOutput[0] = b64_dictionary[aInput[0] >> 2]; + aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)]; + aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2]; + aOutput[3] = '='; + } + else if (aInputLen == 1) + { + aOutput[0] = b64_dictionary[aInput[0] >> 2]; + aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4]; + aOutput[2] = '='; + aOutput[3] = '='; + } + else + { + // Break the input into 3-byte chunks and process each of them + int i; + for (i = 0; i < aInputLen/3; i++) + { + b64_encode(&aInput[i*3], 3, &aOutput[i*4], 4); + } + if (aInputLen % 3 > 0) + { + // It doesn't fit neatly into a 3-byte chunk, so process what's left + b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4)); + } + } + + return ((aInputLen+2)/3)*4; +} + diff --git a/lib/ArduinoHttpClient/src/b64.h b/lib/ArduinoHttpClient/src/b64.h new file mode 100644 index 0000000..cdb1226 --- /dev/null +++ b/lib/ArduinoHttpClient/src/b64.h @@ -0,0 +1,6 @@ +#ifndef b64_h +#define b64_h + +int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen); + +#endif diff --git a/lib/DallasTemperature/.arduino-ci.yml b/lib/DallasTemperature/.arduino-ci.yml new file mode 100644 index 0000000..cbdcfdc --- /dev/null +++ b/lib/DallasTemperature/.arduino-ci.yml @@ -0,0 +1,15 @@ +compile: + # Choosing to run compilation tests on 2 different Arduino platforms + platforms: + - uno + - due + # - zero # SAMD covered by M4 + # - leonardo # AVR covered by UNO + - m4 + # - esp32 # errors on OneWire => util/crc16.h vs rom/crc.h + - esp8266 + # - mega2560 # AVR covered by UNO +unittest: + # These dependent libraries will be installed + libraries: + - "OneWire" diff --git a/lib/DallasTemperature/.gitattributes b/lib/DallasTemperature/.gitattributes new file mode 100644 index 0000000..57798ba --- /dev/null +++ b/lib/DallasTemperature/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/lib/DallasTemperature/.github/workflows/arduino-lint.yml b/lib/DallasTemperature/.github/workflows/arduino-lint.yml new file mode 100644 index 0000000..5a84495 --- /dev/null +++ b/lib/DallasTemperature/.github/workflows/arduino-lint.yml @@ -0,0 +1,13 @@ + +name: Arduino-lint + +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update + # compliance: strict diff --git a/lib/DallasTemperature/.github/workflows/arduino_test_runner.yml b/lib/DallasTemperature/.github/workflows/arduino_test_runner.yml new file mode 100644 index 0000000..096b975 --- /dev/null +++ b/lib/DallasTemperature/.github/workflows/arduino_test_runner.yml @@ -0,0 +1,17 @@ +--- +name: Arduino CI + +on: [push, pull_request] + +jobs: + runTest: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 + - run: | + gem install arduino_ci + arduino_ci.rb diff --git a/lib/DallasTemperature/.github/workflows/jsoncheck.yml b/lib/DallasTemperature/.github/workflows/jsoncheck.yml new file mode 100644 index 0000000..04603d0 --- /dev/null +++ b/lib/DallasTemperature/.github/workflows/jsoncheck.yml @@ -0,0 +1,18 @@ +name: JSON check + +on: + push: + paths: + - '**.json' + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: json-syntax-check + uses: limitusus/json-syntax-check@v1 + with: + pattern: "\\.json$" + diff --git a/lib/DallasTemperature/.gitignore b/lib/DallasTemperature/.gitignore new file mode 100644 index 0000000..75c20ac --- /dev/null +++ b/lib/DallasTemperature/.gitignore @@ -0,0 +1,16 @@ +.idea +classes +target +out +build +*.iml +*.ipr +*.iws +*.log +*.war +.idea +.project +.classpath +.settings +.gradle +.vscode diff --git a/lib/DallasTemperature/.piopm b/lib/DallasTemperature/.piopm new file mode 100644 index 0000000..1e39f17 --- /dev/null +++ b/lib/DallasTemperature/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "DallasTemperature", "version": "3.11.0", "spec": {"owner": "milesburton", "id": 54, "name": "DallasTemperature", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/DallasTemperature/DallasTemperature.cpp b/lib/DallasTemperature/DallasTemperature.cpp new file mode 100644 index 0000000..13c0614 --- /dev/null +++ b/lib/DallasTemperature/DallasTemperature.cpp @@ -0,0 +1,1122 @@ +// 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. + +#include "DallasTemperature.h" + +// for Particle support +// yield() is not a standard function, but instead wraps Particle process +// https://community.particle.io/t/syscall-yield-operation/40708/2 +#if defined(PLATFORM_ID) // Only defined if a Particle device +inline void yield() { + Particle.process(); +} +#elif ARDUINO >= 100 +#include "Arduino.h" +#else +extern "C" { +#include "WConstants.h" +} +#endif + +// OneWire commands +#define STARTCONVO 0x44 // Tells device to take a temperature reading and put it on the scratchpad +#define COPYSCRATCH 0x48 // Copy scratchpad to EEPROM +#define READSCRATCH 0xBE // Read from scratchpad +#define WRITESCRATCH 0x4E // Write to scratchpad +#define RECALLSCRATCH 0xB8 // Recall from EEPROM to scratchpad +#define READPOWERSUPPLY 0xB4 // Determine if device needs parasite power +#define ALARMSEARCH 0xEC // Query bus for devices with an alarm condition + +// Scratchpad locations +#define TEMP_LSB 0 +#define TEMP_MSB 1 +#define HIGH_ALARM_TEMP 2 +#define LOW_ALARM_TEMP 3 +#define CONFIGURATION 4 +#define INTERNAL_BYTE 5 +#define COUNT_REMAIN 6 +#define COUNT_PER_C 7 +#define SCRATCHPAD_CRC 8 + +// DSROM FIELDS +#define DSROM_FAMILY 0 +#define DSROM_CRC 7 + +// Device resolution +#define TEMP_9_BIT 0x1F // 9 bit +#define TEMP_10_BIT 0x3F // 10 bit +#define TEMP_11_BIT 0x5F // 11 bit +#define TEMP_12_BIT 0x7F // 12 bit + +#define MAX_CONVERSION_TIMEOUT 750 + +// Alarm handler +#define NO_ALARM_HANDLER ((AlarmHandler *)0) + + +DallasTemperature::DallasTemperature() { +#if REQUIRESALARMS + setAlarmHandler(NO_ALARM_HANDLER); +#endif + useExternalPullup = false; +} + +DallasTemperature::DallasTemperature(OneWire* _oneWire) : DallasTemperature() { + setOneWire(_oneWire); +} + +bool DallasTemperature::validFamily(const uint8_t* deviceAddress) { + switch (deviceAddress[DSROM_FAMILY]) { + case DS18S20MODEL: + case DS18B20MODEL: + case DS1822MODEL: + case DS1825MODEL: + case DS28EA00MODEL: + return true; + default: + return false; + } +} + +/* + * Constructs DallasTemperature with strong pull-up turned on. Strong pull-up is mandated in DS18B20 datasheet for parasitic + * power (2 wires) setup. (https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf, p. 7, section 'Powering the DS18B20'). + */ +DallasTemperature::DallasTemperature(OneWire* _oneWire, uint8_t _pullupPin) : DallasTemperature(_oneWire) { + setPullupPin(_pullupPin); +} + +void DallasTemperature::setPullupPin(uint8_t _pullupPin) { + useExternalPullup = true; + pullupPin = _pullupPin; + pinMode(pullupPin, OUTPUT); + deactivateExternalPullup(); +} + +void DallasTemperature::setOneWire(OneWire* _oneWire) { + + _wire = _oneWire; + devices = 0; + ds18Count = 0; + parasite = false; + bitResolution = 9; + waitForConversion = true; + checkForConversion = true; + autoSaveScratchPad = true; + +} + +// initialise the bus +void DallasTemperature::begin(void) { + + DeviceAddress deviceAddress; + + _wire->reset_search(); + devices = 0; // Reset the number of devices when we enumerate wire devices + ds18Count = 0; // Reset number of DS18xxx Family devices + + while (_wire->search(deviceAddress)) { + + if (validAddress(deviceAddress)) { + devices++; + + if (validFamily(deviceAddress)) { + ds18Count++; + + if (!parasite && readPowerSupply(deviceAddress)) + parasite = true; + + uint8_t b = getResolution(deviceAddress); + if (b > bitResolution) bitResolution = b; + } + } + } +} + +// returns the number of devices found on the bus +uint8_t DallasTemperature::getDeviceCount(void) { + return devices; +} + +uint8_t DallasTemperature::getDS18Count(void) { + return ds18Count; +} + +// returns true if address is valid +bool DallasTemperature::validAddress(const uint8_t* deviceAddress) { + return (_wire->crc8((uint8_t*)deviceAddress, 7) == deviceAddress[DSROM_CRC]); +} + +// finds an address at a given index on the bus +// returns true if the device was found +bool DallasTemperature::getAddress(uint8_t* deviceAddress, uint8_t index) { + + uint8_t depth = 0; + + _wire->reset_search(); + + while (depth <= index && _wire->search(deviceAddress)) { + if (depth == index && validAddress(deviceAddress)) + return true; + depth++; + } + + return false; + +} + +// attempt to determine if the device at the given address is connected to the bus +bool DallasTemperature::isConnected(const uint8_t* deviceAddress) { + + ScratchPad scratchPad; + return isConnected(deviceAddress, scratchPad); + +} + +// attempt to determine if the device at the given address is connected to the bus +// also allows for updating the read scratchpad +bool DallasTemperature::isConnected(const uint8_t* deviceAddress, + uint8_t* scratchPad) { + bool b = readScratchPad(deviceAddress, scratchPad); + return b && !isAllZeros(scratchPad) && (_wire->crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]); +} + +bool DallasTemperature::readScratchPad(const uint8_t* deviceAddress, + uint8_t* scratchPad) { + + // send the reset command and fail fast + int b = _wire->reset(); + if (b == 0) + return false; + + _wire->select(deviceAddress); + _wire->write(READSCRATCH); + + // Read all registers in a simple loop + // byte 0: temperature LSB + // byte 1: temperature MSB + // byte 2: high alarm temp + // byte 3: low alarm temp + // byte 4: DS18S20: store for crc + // DS18B20 & DS1822: configuration register + // byte 5: internal use & crc + // byte 6: DS18S20: COUNT_REMAIN + // DS18B20 & DS1822: store for crc + // byte 7: DS18S20: COUNT_PER_C + // DS18B20 & DS1822: store for crc + // byte 8: SCRATCHPAD_CRC + for (uint8_t i = 0; i < 9; i++) { + scratchPad[i] = _wire->read(); + } + + b = _wire->reset(); + return (b == 1); +} + +void DallasTemperature::writeScratchPad(const uint8_t* deviceAddress, + const uint8_t* scratchPad) { + + _wire->reset(); + _wire->select(deviceAddress); + _wire->write(WRITESCRATCH); + _wire->write(scratchPad[HIGH_ALARM_TEMP]); // high alarm temp + _wire->write(scratchPad[LOW_ALARM_TEMP]); // low alarm temp + + // DS1820 and DS18S20 have no configuration register + if (deviceAddress[DSROM_FAMILY] != DS18S20MODEL) + _wire->write(scratchPad[CONFIGURATION]); + + if (autoSaveScratchPad) + saveScratchPad(deviceAddress); + else + _wire->reset(); +} + +// returns true if parasite mode is used (2 wire) +// returns false if normal mode is used (3 wire) +// if no address is given (or nullptr) it checks if any device on the bus +// uses parasite mode. +// See issue #145 +bool DallasTemperature::readPowerSupply(const uint8_t* deviceAddress) +{ + bool parasiteMode = false; + _wire->reset(); + if (deviceAddress == nullptr) + _wire->skip(); + else + _wire->select(deviceAddress); + + _wire->write(READPOWERSUPPLY); + if (_wire->read_bit() == 0) + parasiteMode = true; + _wire->reset(); + return parasiteMode; +} + +// set resolution of all devices to 9, 10, 11, or 12 bits +// if new resolution is out of range, it is constrained. +void DallasTemperature::setResolution(uint8_t newResolution) { + + bitResolution = constrain(newResolution, 9, 12); + DeviceAddress deviceAddress; + _wire->reset_search(); + for (uint8_t i = 0; i < devices; i++) { + if(_wire->search(deviceAddress) && validAddress(deviceAddress)) { + setResolution(deviceAddress, bitResolution, true); + } + } +} + +/* PROPOSAL */ + +// set resolution of a device to 9, 10, 11, or 12 bits +// if new resolution is out of range, 9 bits is used. +bool DallasTemperature::setResolution(const uint8_t* deviceAddress, + uint8_t newResolution, bool skipGlobalBitResolutionCalculation) { + + bool success = false; + + // DS1820 and DS18S20 have no resolution configuration register + if (deviceAddress[DSROM_FAMILY] == DS18S20MODEL) { + success = true; + } else { + + // handle the sensors with configuration register + newResolution = constrain(newResolution, 9, 12); + uint8_t newValue = 0; + ScratchPad scratchPad; + + // we can only update the sensor if it is connected + if (isConnected(deviceAddress, scratchPad)) { + // MAX31850 has no resolution configuration register + // this is also a hack as the MAX31850 Coversion time is 100ms max. + // use a low res (~10 by spec, but 9 might work) for faster blocking read times. + if (deviceAddress[DSROM_FAMILY] == DS1825MODEL && scratchPad[CONFIGURATION] & 0x80 ) { + success = true; + } else { + switch (newResolution) { + case 12: + newValue = TEMP_12_BIT; + break; + case 11: + newValue = TEMP_11_BIT; + break; + case 10: + newValue = TEMP_10_BIT; + break; + case 9: + default: + newValue = TEMP_9_BIT; + break; + } + + // if it needs to be updated we write the new value + if (scratchPad[CONFIGURATION] != newValue) { + scratchPad[CONFIGURATION] = newValue; + writeScratchPad(deviceAddress, scratchPad); + } + // done + success = true; + } + } + } + + // do we need to update the max resolution used? + if (skipGlobalBitResolutionCalculation == false) { + bitResolution = newResolution; + if (devices > 1) { + DeviceAddress deviceAddr; + _wire->reset_search(); + for (uint8_t i = 0; i < devices; i++) { + if (bitResolution == 12) break; + if (_wire->search(deviceAddr) && validAddress(deviceAddr)) { + uint8_t b = getResolution(deviceAddr); + if (b > bitResolution) bitResolution = b; + } + } + } + } + + return success; +} + + +// returns the global resolution +uint8_t DallasTemperature::getResolution() { + return bitResolution; +} + +// returns the current resolution of the device, 9-12 +// returns 0 if device not found +uint8_t DallasTemperature::getResolution(const uint8_t* deviceAddress) { + + // DS1820 and DS18S20 have no resolution configuration register + if (deviceAddress[DSROM_FAMILY] == DS18S20MODEL) + return 12; + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) { + + // MAX31850 has no resolution configuration register + if (deviceAddress[DSROM_FAMILY] == DS1825MODEL && scratchPad[CONFIGURATION] & 0x80) + return 12; + + switch (scratchPad[CONFIGURATION]) { + case TEMP_12_BIT: + return 12; + + case TEMP_11_BIT: + return 11; + + case TEMP_10_BIT: + return 10; + + case TEMP_9_BIT: + return 9; + } + } + return 0; + +} + + +// sets the value of the waitForConversion flag +// TRUE : function requestTemperature() etc returns when conversion is ready +// FALSE: function requestTemperature() etc returns immediately (USE WITH CARE!!) +// (1) programmer has to check if the needed delay has passed +// (2) but the application can do meaningful things in that time +void DallasTemperature::setWaitForConversion(bool flag) { + waitForConversion = flag; +} + +// gets the value of the waitForConversion flag +bool DallasTemperature::getWaitForConversion() { + return waitForConversion; +} + +// sets the value of the checkForConversion flag +// TRUE : function requestTemperature() etc will 'listen' to an IC to determine whether a conversion is complete +// FALSE: function requestTemperature() etc will wait a set time (worst case scenario) for a conversion to complete +void DallasTemperature::setCheckForConversion(bool flag) { + checkForConversion = flag; +} + +// gets the value of the waitForConversion flag +bool DallasTemperature::getCheckForConversion() { + return checkForConversion; +} + +bool DallasTemperature::isConversionComplete() { + uint8_t b = _wire->read_bit(); + return (b == 1); +} + +// sends command for all devices on the bus to perform a temperature conversion +DallasTemperature::request_t DallasTemperature::requestTemperatures() { + DallasTemperature::request_t req = {}; + req.result = true; + + _wire->reset(); + _wire->skip(); + _wire->write(STARTCONVO, parasite); + + // ASYNC mode? + req.timestamp = millis(); + if (!waitForConversion) + return req; + blockTillConversionComplete(bitResolution, req.timestamp); + return req; +} + +// sends command for one device to perform a temperature by address +// returns FALSE if device is disconnected +// returns TRUE otherwise +DallasTemperature::request_t DallasTemperature::requestTemperaturesByAddress(const uint8_t* deviceAddress) { + DallasTemperature::request_t req = {}; + uint8_t bitResolution = getResolution(deviceAddress); + if (bitResolution == 0) { + req.result = false; + return req; //Device disconnected + } + + _wire->reset(); + _wire->select(deviceAddress); + _wire->write(STARTCONVO, parasite); + + req.timestamp = millis(); + // ASYNC mode? + req.result = true; + if (!waitForConversion) + return req; + + blockTillConversionComplete(bitResolution, req.timestamp); + + return req; + +} + +// Continue to check if the IC has responded with a temperature +void DallasTemperature::blockTillConversionComplete(uint8_t bitResolution, unsigned long start) { + if (checkForConversion && !parasite) { + while (!isConversionComplete() && (millis() - start < MAX_CONVERSION_TIMEOUT)) + yield(); + } else { + unsigned long delms = millisToWaitForConversion(bitResolution); + activateExternalPullup(); + delay(delms); + deactivateExternalPullup(); + } + +} + +// Continue to check if the IC has responded with a temperature +void DallasTemperature::blockTillConversionComplete(uint8_t bitResolution) { + unsigned long start = millis(); + blockTillConversionComplete(bitResolution, start); +} + +// Continue to check if the IC has responded with a temperature +void DallasTemperature::blockTillConversionComplete(uint8_t bitResolution, DallasTemperature::request_t req) { + if (req.result) + blockTillConversionComplete(bitResolution, req.timestamp); +} + +// returns number of milliseconds to wait till conversion is complete (based on IC datasheet) +uint16_t DallasTemperature::millisToWaitForConversion(uint8_t bitResolution) { + + switch (bitResolution) { + case 9: + return 94; + case 10: + return 188; + case 11: + return 375; + default: + return 750; + } + +} + +// returns number of milliseconds to wait till conversion is complete (based on IC datasheet) +uint16_t DallasTemperature::millisToWaitForConversion() { + return millisToWaitForConversion(bitResolution); +} + +// Sends command to one device to save values from scratchpad to EEPROM by index +// Returns true if no errors were encountered, false indicates failure +bool DallasTemperature::saveScratchPadByIndex(uint8_t deviceIndex) { + + DeviceAddress deviceAddress; + if (!getAddress(deviceAddress, deviceIndex)) return false; + + return saveScratchPad(deviceAddress); + +} + +// Sends command to one or more devices to save values from scratchpad to EEPROM +// If optional argument deviceAddress is omitted the command is send to all devices +// Returns true if no errors were encountered, false indicates failure +bool DallasTemperature::saveScratchPad(const uint8_t* deviceAddress) { + + if (_wire->reset() == 0) + return false; + + if (deviceAddress == nullptr) + _wire->skip(); + else + _wire->select(deviceAddress); + + _wire->write(COPYSCRATCH, parasite); + + // Specification: NV Write Cycle Time is typically 2ms, max 10ms + // Waiting 20ms to allow for sensors that take longer in practice + if (!parasite) { + delay(20); + } else { + activateExternalPullup(); + delay(20); + deactivateExternalPullup(); + } + + return _wire->reset() == 1; + +} + +// Sends command to one device to recall values from EEPROM to scratchpad by index +// Returns true if no errors were encountered, false indicates failure +bool DallasTemperature::recallScratchPadByIndex(uint8_t deviceIndex) { + + DeviceAddress deviceAddress; + if (!getAddress(deviceAddress, deviceIndex)) return false; + + return recallScratchPad(deviceAddress); + +} + +// Sends command to one or more devices to recall values from EEPROM to scratchpad +// If optional argument deviceAddress is omitted the command is send to all devices +// Returns true if no errors were encountered, false indicates failure +bool DallasTemperature::recallScratchPad(const uint8_t* deviceAddress) { + + if (_wire->reset() == 0) + return false; + + if (deviceAddress == nullptr) + _wire->skip(); + else + _wire->select(deviceAddress); + + _wire->write(RECALLSCRATCH, parasite); + + // Specification: Strong pullup only needed when writing to EEPROM (and temp conversion) + unsigned long start = millis(); + while (_wire->read_bit() == 0) { + // Datasheet doesn't specify typical/max duration, testing reveals typically within 1ms + if (millis() - start > 20) return false; + yield(); + } + + return _wire->reset() == 1; + +} + +// Sets the autoSaveScratchPad flag +void DallasTemperature::setAutoSaveScratchPad(bool flag) { + autoSaveScratchPad = flag; +} + +// Gets the autoSaveScratchPad flag +bool DallasTemperature::getAutoSaveScratchPad() { + return autoSaveScratchPad; +} + +void DallasTemperature::activateExternalPullup() { + if (useExternalPullup) + digitalWrite(pullupPin, LOW); +} + +void DallasTemperature::deactivateExternalPullup() { + if (useExternalPullup) + digitalWrite(pullupPin, HIGH); +} + +// sends command for one device to perform a temp conversion by index +DallasTemperature::request_t DallasTemperature::requestTemperaturesByIndex(uint8_t deviceIndex) { + + DeviceAddress deviceAddress; + getAddress(deviceAddress, deviceIndex); + + return requestTemperaturesByAddress(deviceAddress); + +} + +// Fetch temperature for device index +float DallasTemperature::getTempCByIndex(uint8_t deviceIndex) { + + DeviceAddress deviceAddress; + if (!getAddress(deviceAddress, deviceIndex)) { + return DEVICE_DISCONNECTED_C; + } + return getTempC((uint8_t*) deviceAddress); +} + +// Fetch temperature for device index +float DallasTemperature::getTempFByIndex(uint8_t deviceIndex) { + + DeviceAddress deviceAddress; + + if (!getAddress(deviceAddress, deviceIndex)) { + return DEVICE_DISCONNECTED_F; + } + + return getTempF((uint8_t*) deviceAddress); + +} + +// reads scratchpad and returns fixed-point temperature, scaling factor 2^-7 +int32_t DallasTemperature::calculateTemperature(const uint8_t* deviceAddress, + uint8_t* scratchPad) { + + int32_t fpTemperature = 0; + + // looking thru the spec sheets of all supported devices, bit 15 is always the signing bit + // Detected if signed + int32_t neg = 0x0; + if (scratchPad[TEMP_MSB] & 0x80) + neg = 0xFFF80000; + + // detect MAX31850 + // The temp range on a MAX31850 can far exceed other models, causing an overrun @ 256C + // Based on the spec sheets for the MAX31850, bit 7 is always 1 + // Whereas the DS1825 bit 7 is always 0 + // DS1825 - https://datasheets.maximintegrated.com/en/ds/DS1825.pdf + // MAX31850 - https://datasheets.maximintegrated.com/en/ds/MAX31850-MAX31851.pdf + + if (deviceAddress[DSROM_FAMILY] == DS1825MODEL && scratchPad[CONFIGURATION] & 0x80 ) { + //Serial.print(" Detected MAX31850"); + if (scratchPad[TEMP_LSB] & 1) { // Fault Detected + if (scratchPad[HIGH_ALARM_TEMP] & 1) { + //Serial.println("open detected"); + return DEVICE_FAULT_OPEN_RAW; + } + else if (scratchPad[HIGH_ALARM_TEMP] >> 1 & 1) { + //Serial.println("short to ground detected"); + return DEVICE_FAULT_SHORTGND_RAW; + } + else if (scratchPad[HIGH_ALARM_TEMP] >> 2 & 1) { + //Serial.println("short to Vdd detected"); + return DEVICE_FAULT_SHORTVDD_RAW; + } + else { + // We don't know why there's a fault, exit with disconnect value + return DEVICE_DISCONNECTED_RAW; + } + } + // We must mask out bit 1 (reserved) and 0 (fault) on TEMP_LSB + fpTemperature = (((int32_t) scratchPad[TEMP_MSB]) << 11) + | (((int32_t) scratchPad[TEMP_LSB] & 0xFC) << 3) + | neg; + } else { + fpTemperature = (((int16_t) scratchPad[TEMP_MSB]) << 11) + | (((int16_t) scratchPad[TEMP_LSB]) << 3) + | neg; + } + + /* + DS1820 and DS18S20 have a 9-bit temperature register. + + Resolutions greater than 9-bit can be calculated using the data from + the temperature, and COUNT REMAIN and COUNT PER °C registers in the + scratchpad. The resolution of the calculation depends on the model. + + While the COUNT PER °C register is hard-wired to 16 (10h) in a + DS18S20, it changes with temperature in DS1820. + + After reading the scratchpad, the TEMP_READ value is obtained by + truncating the 0.5°C bit (bit 0) from the temperature data. The + extended resolution temperature can then be calculated using the + following equation: + + COUNT_PER_C - COUNT_REMAIN + TEMPERATURE = TEMP_READ - 0.25 + -------------------------- + COUNT_PER_C + + Hagai Shatz simplified this to integer arithmetic for a 12 bits + value for a DS18S20, and James Cameron added legacy DS1820 support. + + See - http://myarduinotoy.blogspot.co.uk/2013/02/12bit-result-from-ds18s20.html + */ + + if ((deviceAddress[DSROM_FAMILY] == DS18S20MODEL) && (scratchPad[COUNT_PER_C] != 0)) { + fpTemperature = ((fpTemperature & 0xfff0) << 3) - 32 + + (((scratchPad[COUNT_PER_C] - scratchPad[COUNT_REMAIN]) << 7) + / scratchPad[COUNT_PER_C]); + } + + return fpTemperature; +} + +// returns temperature in 1/128 degrees C or DEVICE_DISCONNECTED_RAW if the +// device's scratch pad cannot be read successfully. +// the numeric value of DEVICE_DISCONNECTED_RAW is defined in +// DallasTemperature.h. It is a large negative number outside the +// operating range of the device +int32_t DallasTemperature::getTemp(const uint8_t* deviceAddress) { + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) + return calculateTemperature(deviceAddress, scratchPad); + return DEVICE_DISCONNECTED_RAW; + +} + +// returns temperature in degrees C or DEVICE_DISCONNECTED_C if the +// device's scratch pad cannot be read successfully. +// the numeric value of DEVICE_DISCONNECTED_C is defined in +// DallasTemperature.h. It is a large negative number outside the +// operating range of the device +float DallasTemperature::getTempC(const uint8_t* deviceAddress) { + return rawToCelsius(getTemp(deviceAddress)); +} + +// returns temperature in degrees F or DEVICE_DISCONNECTED_F if the +// device's scratch pad cannot be read successfully. +// the numeric value of DEVICE_DISCONNECTED_F is defined in +// DallasTemperature.h. It is a large negative number outside the +// operating range of the device +float DallasTemperature::getTempF(const uint8_t* deviceAddress) { + return rawToFahrenheit(getTemp(deviceAddress)); +} + +// returns true if the bus requires parasite power +bool DallasTemperature::isParasitePowerMode(void) { + return parasite; +} + +// IF alarm is not used one can store a 16 bit int of userdata in the alarm +// registers. E.g. an ID of the sensor. +// See github issue #29 + +// note if device is not connected it will fail writing the data. +void DallasTemperature::setUserData(const uint8_t* deviceAddress, + int16_t data) { + // return when stored value == new value + if (getUserData(deviceAddress) == data) + return; + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) { + scratchPad[HIGH_ALARM_TEMP] = data >> 8; + scratchPad[LOW_ALARM_TEMP] = data & 255; + writeScratchPad(deviceAddress, scratchPad); + } +} + +int16_t DallasTemperature::getUserData(const uint8_t* deviceAddress) { + int16_t data = 0; + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) { + data = scratchPad[HIGH_ALARM_TEMP] << 8; + data += scratchPad[LOW_ALARM_TEMP]; + } + return data; +} + +// note If address cannot be found no error will be reported. +int16_t DallasTemperature::getUserDataByIndex(uint8_t deviceIndex) { + DeviceAddress deviceAddress; + getAddress(deviceAddress, deviceIndex); + return getUserData((uint8_t*) deviceAddress); +} + +void DallasTemperature::setUserDataByIndex(uint8_t deviceIndex, int16_t data) { + DeviceAddress deviceAddress; + getAddress(deviceAddress, deviceIndex); + setUserData((uint8_t*) deviceAddress, data); +} + +// Convert float Celsius to Fahrenheit +float DallasTemperature::toFahrenheit(float celsius) { + return (celsius * 1.8f) + 32.0f; +} + +// Convert float Fahrenheit to Celsius +float DallasTemperature::toCelsius(float fahrenheit) { + return (fahrenheit - 32.0f) * 0.555555556f; +} + +// convert from raw to Celsius +float DallasTemperature::rawToCelsius(int32_t raw) { + + if (raw <= DEVICE_DISCONNECTED_RAW) + return DEVICE_DISCONNECTED_C; + // C = RAW/128 + return (float) raw * 0.0078125f; + +} + +// Convert from Celsius to raw returns temperature in raw integer format. +// The rounding error in the conversion is smaller than 0.01°C +// where the resolution of the sensor is at best 0.0625°C (in 12 bit mode). +// Rounding error can be verified by running: +// for (float t=-55.; t<125.; t+=0.01) +// { +// Serial.println( DallasTemperature::rawToCelsius(DallasTemperature::celsiusToRaw(t))-t, 4 ); +// } +int16_t DallasTemperature::celsiusToRaw(float celsius) { + + return static_cast( celsius * 128.f ); +} + +// convert from raw to Fahrenheit +float DallasTemperature::rawToFahrenheit(int32_t raw) { + + if (raw <= DEVICE_DISCONNECTED_RAW) + return DEVICE_DISCONNECTED_F; + // C = RAW/128 + // F = (C*1.8)+32 = (RAW/128*1.8)+32 = (RAW*0.0140625)+32 + return ((float) raw * 0.0140625f) + 32.0f; + +} + +// Returns true if all bytes of scratchPad are '\0' +bool DallasTemperature::isAllZeros(const uint8_t * const scratchPad, const size_t length) { + for (size_t i = 0; i < length; i++) { + if (scratchPad[i] != 0) { + return false; + } + } + + return true; +} + +#if REQUIRESALARMS + +/* + + ALARMS: + + TH and TL Register Format + + BIT 7 BIT 6 BIT 5 BIT 4 BIT 3 BIT 2 BIT 1 BIT 0 + S 2^6 2^5 2^4 2^3 2^2 2^1 2^0 + + Only bits 11 through 4 of the temperature register are used + in the TH and TL comparison since TH and TL are 8-bit + registers. If the measured temperature is lower than or equal + to TL or higher than or equal to TH, an alarm condition exists + and an alarm flag is set inside the DS18B20. This flag is + updated after every temperature measurement; therefore, if the + alarm condition goes away, the flag will be turned off after + the next temperature conversion. + + */ + +// sets the high alarm temperature for a device in degrees Celsius +// accepts a float, but the alarm resolution will ignore anything +// after a decimal point. valid range is -55C - 125C +void DallasTemperature::setHighAlarmTemp(const uint8_t* deviceAddress, + int8_t celsius) { + + // return when stored value == new value + if (getHighAlarmTemp(deviceAddress) == celsius) + return; + + // make sure the alarm temperature is within the device's range + if (celsius > 125) + celsius = 125; + else if (celsius < -55) + celsius = -55; + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) { + scratchPad[HIGH_ALARM_TEMP] = (uint8_t) celsius; + writeScratchPad(deviceAddress, scratchPad); + } + +} + +// sets the low alarm temperature for a device in degrees Celsius +// accepts a float, but the alarm resolution will ignore anything +// after a decimal point. valid range is -55C - 125C +void DallasTemperature::setLowAlarmTemp(const uint8_t* deviceAddress, + int8_t celsius) { + + // return when stored value == new value + if (getLowAlarmTemp(deviceAddress) == celsius) + return; + + // make sure the alarm temperature is within the device's range + if (celsius > 125) + celsius = 125; + else if (celsius < -55) + celsius = -55; + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) { + scratchPad[LOW_ALARM_TEMP] = (uint8_t) celsius; + writeScratchPad(deviceAddress, scratchPad); + } + +} + +// returns a int8_t with the current high alarm temperature or +// DEVICE_DISCONNECTED for an address +int8_t DallasTemperature::getHighAlarmTemp(const uint8_t* deviceAddress) { + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) + return (int8_t) scratchPad[HIGH_ALARM_TEMP]; + return DEVICE_DISCONNECTED_C; + +} + +// returns a int8_t with the current low alarm temperature or +// DEVICE_DISCONNECTED for an address +int8_t DallasTemperature::getLowAlarmTemp(const uint8_t* deviceAddress) { + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) + return (int8_t) scratchPad[LOW_ALARM_TEMP]; + return DEVICE_DISCONNECTED_C; + +} + +// resets internal variables used for the alarm search +void DallasTemperature::resetAlarmSearch() { + + alarmSearchJunction = -1; + alarmSearchExhausted = 0; + for (uint8_t i = 0; i < 7; i++) { + alarmSearchAddress[i] = 0; + } + +} + +// This is a modified version of the OneWire::search method. +// +// Also added the OneWire search fix documented here: +// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295 +// +// Perform an alarm search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use +// DallasTemperature::resetAlarmSearch() to start over. +bool DallasTemperature::alarmSearch(uint8_t* newAddr) { + + uint8_t i; + int8_t lastJunction = -1; + uint8_t done = 1; + + if (alarmSearchExhausted) + return false; + if (!_wire->reset()) + return false; + + // send the alarm search command + _wire->write(0xEC, 0); + + for (i = 0; i < 64; i++) { + + uint8_t a = _wire->read_bit(); + uint8_t nota = _wire->read_bit(); + uint8_t ibyte = i / 8; + uint8_t ibit = 1 << (i & 7); + + // I don't think this should happen, this means nothing responded, but maybe if + // something vanishes during the search it will come up. + if (a && nota) + return false; + + if (!a && !nota) { + if (i == alarmSearchJunction) { + // this is our time to decide differently, we went zero last time, go one. + a = 1; + alarmSearchJunction = lastJunction; + } else if (i < alarmSearchJunction) { + + // take whatever we took last time, look in address + if (alarmSearchAddress[ibyte] & ibit) { + a = 1; + } else { + // Only 0s count as pending junctions, we've already exhausted the 0 side of 1s + a = 0; + done = 0; + lastJunction = i; + } + } else { + // we are blazing new tree, take the 0 + a = 0; + alarmSearchJunction = i; + done = 0; + } + // OneWire search fix + // See: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295 + } + + if (a) + alarmSearchAddress[ibyte] |= ibit; + else + alarmSearchAddress[ibyte] &= ~ibit; + + _wire->write_bit(a); + } + + if (done) + alarmSearchExhausted = 1; + for (i = 0; i < 8; i++) + newAddr[i] = alarmSearchAddress[i]; + return true; + +} + +// returns true if device address might have an alarm condition +// (only an alarm search can verify this) +bool DallasTemperature::hasAlarm(const uint8_t* deviceAddress) { + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) { + + int8_t temp = calculateTemperature(deviceAddress, scratchPad) >> 7; + + // check low alarm + if (temp <= (int8_t) scratchPad[LOW_ALARM_TEMP]) + return true; + + // check high alarm + if (temp >= (int8_t) scratchPad[HIGH_ALARM_TEMP]) + return true; + } + + // no alarm + return false; + +} + +// returns true if any device is reporting an alarm condition on the bus +bool DallasTemperature::hasAlarm(void) { + + DeviceAddress deviceAddress; + resetAlarmSearch(); + return alarmSearch(deviceAddress); +} + +// runs the alarm handler for all devices returned by alarmSearch() +// unless there no _AlarmHandler exist. +void DallasTemperature::processAlarms(void) { + + if (!hasAlarmHandler()) { + return; + } + + resetAlarmSearch(); + DeviceAddress alarmAddr; + + while (alarmSearch(alarmAddr)) { + if (validAddress(alarmAddr)) { + _AlarmHandler(alarmAddr); + } + } +} + +// sets the alarm handler +void DallasTemperature::setAlarmHandler(const AlarmHandler *handler) { + _AlarmHandler = handler; +} + +// checks if AlarmHandler has been set. +bool DallasTemperature::hasAlarmHandler() +{ + return _AlarmHandler != NO_ALARM_HANDLER; +} + +#endif + +#if REQUIRESNEW + +// MnetCS - Allocates memory for DallasTemperature. Allows us to instance a new object +void* DallasTemperature::operator new(unsigned int size) { // Implicit NSS obj size + + void * p;// void pointer + p = malloc(size);// Allocate memory + memset((DallasTemperature*)p, 0, size); // Initialise memory + + //!!! CANT EXPLICITLY CALL CONSTRUCTOR - workaround by using an init() methodR - workaround by using an init() method + return (DallasTemperature*) p;// Cast blank region to NSS pointer +} + +// MnetCS 2009 - Free the memory used by this instance +void DallasTemperature::operator delete(void* p) { + + DallasTemperature* pNss = (DallasTemperature*) p; // Cast to NSS pointer + pNss->~DallasTemperature();// Destruct the object + + free(p);// Free the memory +} + +#endif diff --git a/lib/DallasTemperature/DallasTemperature.h b/lib/DallasTemperature/DallasTemperature.h new file mode 100644 index 0000000..789880d --- /dev/null +++ b/lib/DallasTemperature/DallasTemperature.h @@ -0,0 +1,346 @@ +#ifndef DallasTemperature_h +#define DallasTemperature_h + +#define DALLASTEMPLIBVERSION "3.8.1" // To be deprecated -> TODO remove in 4.0.0 + +// 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. + +// set to true to include code for new and delete operators +#ifndef REQUIRESNEW +#define REQUIRESNEW false +#endif + +// set to true to include code implementing alarm search functions +#ifndef REQUIRESALARMS +#define REQUIRESALARMS true +#endif + +#include +#ifdef __STM32F1__ +#include +#else +#include +#endif + +// Model IDs +#define DS18S20MODEL 0x10 // also DS1820 +#define DS18B20MODEL 0x28 // also MAX31820 +#define DS1822MODEL 0x22 +#define DS1825MODEL 0x3B // also MAX31850 +#define DS28EA00MODEL 0x42 + +// Error Codes +// See https://github.com/milesburton/Arduino-Temperature-Control-Library/commit/ac1eb7f56e3894e855edc3353be4bde4aa838d41#commitcomment-75490966 for the 16bit implementation. Reverted due to microcontroller resource constraints. +#define DEVICE_DISCONNECTED_C -127 +#define DEVICE_DISCONNECTED_F -196.6 +#define DEVICE_DISCONNECTED_RAW -7040 + +#define DEVICE_FAULT_OPEN_C -254 +#define DEVICE_FAULT_OPEN_F -425.199982 +#define DEVICE_FAULT_OPEN_RAW -32512 + +#define DEVICE_FAULT_SHORTGND_C -253 +#define DEVICE_FAULT_SHORTGND_F -423.399994 +#define DEVICE_FAULT_SHORTGND_RAW -32384 + +#define DEVICE_FAULT_SHORTVDD_C -252 +#define DEVICE_FAULT_SHORTVDD_F -421.599976 +#define DEVICE_FAULT_SHORTVDD_RAW -32256 + +// For readPowerSupply on oneWire bus +// definition of nullptr for C++ < 11, using official workaround: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf +#if __cplusplus < 201103L +const class +{ +public: + template + operator T *() const { + return 0; + } + + template + operator T C::*() const { + return 0; + } + +private: + void operator&() const; +} nullptr = {}; +#endif + +typedef uint8_t DeviceAddress[8]; + +class DallasTemperature { +public: + + DallasTemperature(); + DallasTemperature(OneWire*); + DallasTemperature(OneWire*, uint8_t); + + void setOneWire(OneWire*); + + void setPullupPin(uint8_t); + + // initialise bus + void begin(void); + + // returns the number of devices found on the bus + uint8_t getDeviceCount(void); + + // returns the number of DS18xxx Family devices on bus + uint8_t getDS18Count(void); + + // returns true if address is valid + bool validAddress(const uint8_t*); + + // returns true if address is of the family of sensors the lib supports. + bool validFamily(const uint8_t* deviceAddress); + + // finds an address at a given index on the bus + bool getAddress(uint8_t*, uint8_t); + + // attempt to determine if the device at the given address is connected to the bus + bool isConnected(const uint8_t*); + + // attempt to determine if the device at the given address is connected to the bus + // also allows for updating the read scratchpad + bool isConnected(const uint8_t*, uint8_t*); + + // read device's scratchpad + bool readScratchPad(const uint8_t*, uint8_t*); + + // write device's scratchpad + void writeScratchPad(const uint8_t*, const uint8_t*); + + // read device's power requirements + bool readPowerSupply(const uint8_t* deviceAddress = nullptr); + + // get global resolution + uint8_t getResolution(); + + // set global resolution to 9, 10, 11, or 12 bits + void setResolution(uint8_t); + + // returns the device resolution: 9, 10, 11, or 12 bits + uint8_t getResolution(const uint8_t*); + + // set resolution of a device to 9, 10, 11, or 12 bits + bool setResolution(const uint8_t*, uint8_t, + bool skipGlobalBitResolutionCalculation = false); + + // sets/gets the waitForConversion flag + void setWaitForConversion(bool); + bool getWaitForConversion(void); + + // sets/gets the checkForConversion flag + void setCheckForConversion(bool); + bool getCheckForConversion(void); + + struct request_t { + bool result; + unsigned long timestamp; + + operator bool() { + return result; + } + }; + + // sends command for all devices on the bus to perform a temperature conversion + request_t requestTemperatures(void); + + // sends command for one device to perform a temperature conversion by address + request_t requestTemperaturesByAddress(const uint8_t*); + + // sends command for one device to perform a temperature conversion by index + request_t requestTemperaturesByIndex(uint8_t); + + // returns temperature raw value (12 bit integer of 1/128 degrees C) + int32_t getTemp(const uint8_t*); + + // returns temperature in degrees C + float getTempC(const uint8_t*); + + // returns temperature in degrees F + float getTempF(const uint8_t*); + + // Get temperature for device index (slow) + float getTempCByIndex(uint8_t); + + // Get temperature for device index (slow) + float getTempFByIndex(uint8_t); + + // returns true if the bus requires parasite power + bool isParasitePowerMode(void); + + // Is a conversion complete on the wire? Only applies to the first sensor on the wire. + bool isConversionComplete(void); + + static uint16_t millisToWaitForConversion(uint8_t); + + uint16_t millisToWaitForConversion(); + + // Sends command to one device to save values from scratchpad to EEPROM by index + // Returns true if no errors were encountered, false indicates failure + bool saveScratchPadByIndex(uint8_t); + + // Sends command to one or more devices to save values from scratchpad to EEPROM + // Returns true if no errors were encountered, false indicates failure + bool saveScratchPad(const uint8_t* = nullptr); + + // Sends command to one device to recall values from EEPROM to scratchpad by index + // Returns true if no errors were encountered, false indicates failure + bool recallScratchPadByIndex(uint8_t); + + // Sends command to one or more devices to recall values from EEPROM to scratchpad + // Returns true if no errors were encountered, false indicates failure + bool recallScratchPad(const uint8_t* = nullptr); + + // Sets the autoSaveScratchPad flag + void setAutoSaveScratchPad(bool); + + // Gets the autoSaveScratchPad flag + bool getAutoSaveScratchPad(void); + +#if REQUIRESALARMS + + typedef void AlarmHandler(const uint8_t*); + + // sets the high alarm temperature for a device + // accepts a int8_t. valid range is -55C - 125C + void setHighAlarmTemp(const uint8_t*, int8_t); + + // sets the low alarm temperature for a device + // accepts a int8_t. valid range is -55C - 125C + void setLowAlarmTemp(const uint8_t*, int8_t); + + // returns a int8_t with the current high alarm temperature for a device + // in the range -55C - 125C + int8_t getHighAlarmTemp(const uint8_t*); + + // returns a int8_t with the current low alarm temperature for a device + // in the range -55C - 125C + int8_t getLowAlarmTemp(const uint8_t*); + + // resets internal variables used for the alarm search + void resetAlarmSearch(void); + + // search the wire for devices with active alarms + bool alarmSearch(uint8_t*); + + // returns true if ia specific device has an alarm + bool hasAlarm(const uint8_t*); + + // returns true if any device is reporting an alarm on the bus + bool hasAlarm(void); + + // runs the alarm handler for all devices returned by alarmSearch() + void processAlarms(void); + + // sets the alarm handler + void setAlarmHandler(const AlarmHandler *); + + // returns true if an AlarmHandler has been set + bool hasAlarmHandler(); + +#endif + + // if no alarm handler is used the two bytes can be used as user data + // example of such usage is an ID. + // note if device is not connected it will fail writing the data. + // note if address cannot be found no error will be reported. + // in short use carefully + void setUserData(const uint8_t*, int16_t); + void setUserDataByIndex(uint8_t, int16_t); + int16_t getUserData(const uint8_t*); + int16_t getUserDataByIndex(uint8_t); + + // convert from Celsius to Fahrenheit + static float toFahrenheit(float); + + // convert from Fahrenheit to Celsius + static float toCelsius(float); + + // convert from raw to Celsius + static float rawToCelsius(int32_t); + + // convert from Celsius to raw + static int16_t celsiusToRaw(float); + + // convert from raw to Fahrenheit + static float rawToFahrenheit(int32_t); + +#if REQUIRESNEW + + // initialize memory area + void* operator new(unsigned int); + + // delete memory reference + void operator delete(void*); + +#endif + + void blockTillConversionComplete(uint8_t); + void blockTillConversionComplete(uint8_t, unsigned long); + void blockTillConversionComplete(uint8_t, request_t); + +private: + typedef uint8_t ScratchPad[9]; + + // parasite power on or off + bool parasite; + + // external pullup + bool useExternalPullup; + uint8_t pullupPin; + + // used to determine the delay amount needed to allow for the + // temperature conversion to take place + uint8_t bitResolution; + + // used to requestTemperature with or without delay + bool waitForConversion; + + // used to requestTemperature to dynamically check if a conversion is complete + bool checkForConversion; + + // used to determine if values will be saved from scratchpad to EEPROM on every scratchpad write + bool autoSaveScratchPad; + + // count of devices on the bus + uint8_t devices; + + // count of DS18xxx Family devices on bus + uint8_t ds18Count; + + // Take a pointer to one wire instance + OneWire* _wire; + + // reads scratchpad and returns the raw temperature + int32_t calculateTemperature(const uint8_t*, uint8_t*); + + + // Returns true if all bytes of scratchPad are '\0' + bool isAllZeros(const uint8_t* const scratchPad, const size_t length = 9); + + // External pullup control + void activateExternalPullup(void); + void deactivateExternalPullup(void); + +#if REQUIRESALARMS + + // required for alarmSearch + uint8_t alarmSearchAddress[8]; + int8_t alarmSearchJunction; + uint8_t alarmSearchExhausted; + + // the alarm handler function pointer + AlarmHandler *_AlarmHandler; + +#endif + +}; +#endif diff --git a/lib/DallasTemperature/README.md b/lib/DallasTemperature/README.md new file mode 100644 index 0000000..e8f4920 --- /dev/null +++ b/lib/DallasTemperature/README.md @@ -0,0 +1,80 @@ +[![Arduino CI](https://github.com/milesburton/Arduino-Temperature-Control-Library/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci) +[![Arduino-lint](https://github.com/milesburton/Arduino-Temperature-Control-Library/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/AS5600/actions/workflows/arduino-lint.yml) +[![JSON check](https://github.com/milesburton/Arduino-Temperature-Control-Library/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/AS5600/actions/workflows/jsoncheck.yml) +[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/milesburton/Arduino-Temperature-Control-Library/blob/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/milesburton/Arduino-Temperature-Control-Library.svg?maxAge=3600)](https://github.com/milesburton/Arduino-Temperature-Control-Library/releases) + + +# Arduino Library for Maxim Temperature Integrated Circuits + +## Usage + +This library supports the following devices : + + +* DS18B20 +* DS18S20 - Please note there appears to be an issue with this series. +* DS1822 +* DS1820 +* MAX31820 +* MAX31850 + + +You will need a pull-up resistor of about 5 KOhm between the 1-Wire data line +and your 5V power. If you are using the DS18B20, ground pins 1 and 3. The +centre pin is the data line '1-wire'. + +In case of temperature conversion problems (result is `-85`), strong pull-up setup may be necessary. See section +_Powering the DS18B20_ in +[DS18B20 datasheet](https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf) (page 7) +and use `DallasTemperature(OneWire*, uint8_t)` constructor. + +We have included a "REQUIRESNEW" and "REQUIRESALARMS" definition. If you +want to slim down the code feel free to use either of these by including + + + + #define REQUIRESNEW + +or + + #define REQUIRESALARMS + + +at the top of DallasTemperature.h + +Finally, please include OneWire from Paul Stoffregen in the library manager before you begin. + +## Credits + +The OneWire code has been derived from +http://www.arduino.cc/playground/Learning/OneWire. +Miles Burton originally developed this library. +Tim Newsome added support for multiple sensors on +the same bus. +Guil Barros [gfbarros@bappos.com] added getTempByAddress (v3.5) + Note: these are implemented as getTempC(address) and getTempF(address) +Rob Tillaart [rob.tillaart@gmail.com] added async modus (v3.7.0) + + +## Website + + +Additional documentation may be found here +https://www.milesburton.com/Dallas_Temperature_Control_Library + +# License + +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 diff --git a/lib/DallasTemperature/examples/Alarm/Alarm.ino b/lib/DallasTemperature/examples/Alarm/Alarm.ino new file mode 100644 index 0000000..d94a0b8 --- /dev/null +++ b/lib/DallasTemperature/examples/Alarm/Alarm.ino @@ -0,0 +1,161 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // locate devices on the bus + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // search for devices on the bus and assign based on an index. + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + + Serial.print("Device 0 Alarms: "); + printAlarms(insideThermometer); + Serial.println(); + + Serial.print("Device 1 Address: "); + printAddress(outsideThermometer); + Serial.println(); + + Serial.print("Device 1 Alarms: "); + printAlarms(outsideThermometer); + Serial.println(); + + Serial.println("Setting alarm temps..."); + + // alarm when temp is higher than 30C + sensors.setHighAlarmTemp(insideThermometer, 30); + + // alarm when temp is lower than -10C + sensors.setLowAlarmTemp(insideThermometer, -10); + + // alarm when temp is higher than 31C + sensors.setHighAlarmTemp(outsideThermometer, 31); + + // alarn when temp is lower than 27C + sensors.setLowAlarmTemp(outsideThermometer, 27); + + Serial.print("New Device 0 Alarms: "); + printAlarms(insideThermometer); + Serial.println(); + + Serial.print("New Device 1 Alarms: "); + printAlarms(outsideThermometer); + Serial.println(); +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + float tempC = sensors.getTempC(deviceAddress); + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.print(DallasTemperature::toFahrenheit(tempC)); +} + +void printAlarms(uint8_t deviceAddress[]) +{ + char temp; + temp = sensors.getHighAlarmTemp(deviceAddress); + Serial.print("High Alarm: "); + Serial.print(temp, DEC); + Serial.print("C/"); + Serial.print(DallasTemperature::toFahrenheit(temp)); + Serial.print("F | Low Alarm: "); + temp = sensors.getLowAlarmTemp(deviceAddress); + Serial.print(temp, DEC); + Serial.print("C/"); + Serial.print(DallasTemperature::toFahrenheit(temp)); + Serial.print("F"); +} + +// main function to print information about a device +void printData(DeviceAddress deviceAddress) +{ + Serial.print("Device Address: "); + printAddress(deviceAddress); + Serial.print(" "); + printTemperature(deviceAddress); + Serial.println(); +} + +void checkAlarm(DeviceAddress deviceAddress) +{ + if (sensors.hasAlarm(deviceAddress)) + { + Serial.print("ALARM: "); + printData(deviceAddress); + } +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); + Serial.println("DONE"); + + // Method 1: + // check each address individually for an alarm condition + checkAlarm(insideThermometer); + checkAlarm(outsideThermometer); +/* + // Alternate method: + // Search the bus and iterate through addresses of devices with alarms + + // space for the alarm device's address + DeviceAddress alarmAddr; + + Serial.println("Searching for alarms..."); + + // resetAlarmSearch() must be called before calling alarmSearch() + sensors.resetAlarmSearch(); + + // alarmSearch() returns 0 when there are no devices with alarms + while (sensors.alarmSearch(alarmAddr)) + { + Serial.print("ALARM: "); + printData(alarmAddr); + } +*/ + +} diff --git a/lib/DallasTemperature/examples/AlarmHandler/AlarmHandler.ino b/lib/DallasTemperature/examples/AlarmHandler/AlarmHandler.ino new file mode 100644 index 0000000..11a1bd9 --- /dev/null +++ b/lib/DallasTemperature/examples/AlarmHandler/AlarmHandler.ino @@ -0,0 +1,143 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; + +// function that will be called when an alarm condition exists during DallasTemperatures::processAlarms(); +void newAlarmHandler(const uint8_t* deviceAddress) +{ + Serial.println("Alarm Handler Start"); + printAlarmInfo(deviceAddress); + printTemp(deviceAddress); + Serial.println(); + Serial.println("Alarm Handler Finish"); +} + +void printCurrentTemp(DeviceAddress deviceAddress) +{ + printAddress(deviceAddress); + printTemp(deviceAddress); + Serial.println(); +} + +void printAddress(const DeviceAddress deviceAddress) +{ + Serial.print("Address: "); + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } + Serial.print(" "); +} + +void printTemp(const DeviceAddress deviceAddress) +{ + float tempC = sensors.getTempC(deviceAddress); + if (tempC != DEVICE_DISCONNECTED_C) + { + Serial.print("Current Temp C: "); + Serial.print(tempC); + } + else Serial.print("DEVICE DISCONNECTED"); + Serial.print(" "); +} + +void printAlarmInfo(const DeviceAddress deviceAddress) +{ + char temp; + printAddress(deviceAddress); + temp = sensors.getHighAlarmTemp(deviceAddress); + Serial.print("High Alarm: "); + Serial.print(temp, DEC); + Serial.print("C"); + Serial.print(" Low Alarm: "); + temp = sensors.getLowAlarmTemp(deviceAddress); + Serial.print(temp, DEC); + Serial.print("C"); + Serial.print(" "); +} + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // locate devices on the bus + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // search for devices on the bus and assign based on an index + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + Serial.print("Device insideThermometer "); + printAlarmInfo(insideThermometer); + Serial.println(); + + Serial.print("Device outsideThermometer "); + printAlarmInfo(outsideThermometer); + Serial.println(); + + // set alarm ranges + Serial.println("Setting alarm temps..."); + sensors.setHighAlarmTemp(insideThermometer, 26); + sensors.setLowAlarmTemp(insideThermometer, 22); + sensors.setHighAlarmTemp(outsideThermometer, 25); + sensors.setLowAlarmTemp(outsideThermometer, 21); + + Serial.print("New insideThermometer "); + printAlarmInfo(insideThermometer); + Serial.println(); + + Serial.print("New outsideThermometer "); + printAlarmInfo(outsideThermometer); + Serial.println(); + + // attach alarm handler + sensors.setAlarmHandler(&newAlarmHandler); + +} + +void loop(void) +{ + // ask the devices to measure the temperature + sensors.requestTemperatures(); + + // if an alarm condition exists as a result of the most recent + // requestTemperatures() request, it exists until the next time + // requestTemperatures() is called AND there isn't an alarm condition + // on the device + if (sensors.hasAlarm()) + { + Serial.println("Oh noes! There is at least one alarm on the bus."); + } + + // call alarm handler function defined by sensors.setAlarmHandler + // for each device reporting an alarm + sensors.processAlarms(); + + if (!sensors.hasAlarm()) + { + // just print out the current temperature + printCurrentTemp(insideThermometer); + printCurrentTemp(outsideThermometer); + } + + delay(1000); +} diff --git a/lib/DallasTemperature/examples/ExternalPullup/ExternalPullup.ino b/lib/DallasTemperature/examples/ExternalPullup/ExternalPullup.ino new file mode 100644 index 0000000..5063a3a --- /dev/null +++ b/lib/DallasTemperature/examples/ExternalPullup/ExternalPullup.ino @@ -0,0 +1,35 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino, while external pullup P-MOSFET gate into port 3 +#define ONE_WIRE_BUS 2 +#define ONE_WIRE_PULLUP 3 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire, ONE_WIRE_PULLUP); + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + + for (int i = 0; i < sensors.getDeviceCount(); i++) { + Serial.println("Temperature for Device " + String(i) + " is: " + String(sensors.getTempCByIndex(i))); + } +} diff --git a/lib/DallasTemperature/examples/Multibus_simple/Multibus_simple.ino b/lib/DallasTemperature/examples/Multibus_simple/Multibus_simple.ino new file mode 100644 index 0000000..fc7497e --- /dev/null +++ b/lib/DallasTemperature/examples/Multibus_simple/Multibus_simple.ino @@ -0,0 +1,43 @@ +#include +#include + +OneWire ds18x20[] = { 3, 7 }; +const int oneWireCount = sizeof(ds18x20) / sizeof(OneWire); +DallasTemperature sensor[oneWireCount]; + +void setup(void) { + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature Multiple Bus Control Library Simple Demo"); + Serial.print("============Ready with "); + Serial.print(oneWireCount); + Serial.println(" Sensors================"); + + // Start up the library on all defined bus-wires + DeviceAddress deviceAddress; + for (int i = 0; i < oneWireCount; i++) { + sensor[i].setOneWire(&ds18x20[i]); + sensor[i].begin(); + if (sensor[i].getAddress(deviceAddress, 0)) sensor[i].setResolution(deviceAddress, 12); + } +} + +void loop(void) { + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + for (int i = 0; i < oneWireCount; i++) { + sensor[i].requestTemperatures(); + } + Serial.println("DONE"); + + delay(1000); + for (int i = 0; i < oneWireCount; i++) { + float temperature = sensor[i].getTempCByIndex(0); + Serial.print("Temperature for the sensor "); + Serial.print(i); + Serial.print(" is "); + Serial.println(temperature); + } + Serial.println(); +} diff --git a/lib/DallasTemperature/examples/Multiple/Multiple.ino b/lib/DallasTemperature/examples/Multiple/Multiple.ino new file mode 100644 index 0000000..0a20cc2 --- /dev/null +++ b/lib/DallasTemperature/examples/Multiple/Multiple.ino @@ -0,0 +1,148 @@ +// Include the libraries we need +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 +#define TEMPERATURE_PRECISION 9 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; + +// Assign address manually. The addresses below will need to be changed +// to valid device addresses on your bus. Device address can be retrieved +// by using either oneWire.search(deviceAddress) or individually via +// sensors.getAddress(deviceAddress, index) +// DeviceAddress insideThermometer = { 0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0 }; +// DeviceAddress outsideThermometer = { 0x28, 0x3F, 0x1C, 0x31, 0x2, 0x0, 0x0, 0x2 }; + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // locate devices on the bus + Serial.print("Locating devices..."); + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.isParasitePowerMode()) Serial.println("ON"); + else Serial.println("OFF"); + + // Search for devices on the bus and assign based on an index. Ideally, + // you would do this to initially discover addresses on the bus and then + // use those addresses and manually assign them (see above) once you know + // the devices on your bus (and assuming they don't change). + // + // method 1: by index + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + // method 2: search() + // search() looks for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are no devices, + // or you have already retrieved all of them. It might be a good idea to + // check the CRC to make sure you didn't get garbage. The order is + // deterministic. You will always get the same devices in the same order + // + // Must be called before search() + //oneWire.reset_search(); + // assigns the first address found to insideThermometer + //if (!oneWire.search(insideThermometer)) Serial.println("Unable to find address for insideThermometer"); + // assigns the seconds address found to outsideThermometer + //if (!oneWire.search(outsideThermometer)) Serial.println("Unable to find address for outsideThermometer"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + + Serial.print("Device 1 Address: "); + printAddress(outsideThermometer); + Serial.println(); + + // set the resolution to 9 bit per device + sensors.setResolution(insideThermometer, TEMPERATURE_PRECISION); + sensors.setResolution(outsideThermometer, TEMPERATURE_PRECISION); + + Serial.print("Device 0 Resolution: "); + Serial.print(sensors.getResolution(insideThermometer), DEC); + Serial.println(); + + Serial.print("Device 1 Resolution: "); + Serial.print(sensors.getResolution(outsideThermometer), DEC); + Serial.println(); +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + // zero pad the address if necessary + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + float tempC = sensors.getTempC(deviceAddress); + if (tempC == DEVICE_DISCONNECTED_C) + { + Serial.println("Error: Could not read temperature data"); + return; + } + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.print(DallasTemperature::toFahrenheit(tempC)); +} + +// function to print a device's resolution +void printResolution(DeviceAddress deviceAddress) +{ + Serial.print("Resolution: "); + Serial.print(sensors.getResolution(deviceAddress)); + Serial.println(); +} + +// main function to print information about a device +void printData(DeviceAddress deviceAddress) +{ + Serial.print("Device Address: "); + printAddress(deviceAddress); + Serial.print(" "); + printTemperature(deviceAddress); + Serial.println(); +} + +/* + Main function, calls the temperatures in a loop. +*/ +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); + Serial.println("DONE"); + + // print the device information + printData(insideThermometer); + printData(outsideThermometer); +} diff --git a/lib/DallasTemperature/examples/SaveRecallScratchPad/SaveRecallScratchPad.ino b/lib/DallasTemperature/examples/SaveRecallScratchPad/SaveRecallScratchPad.ino new file mode 100644 index 0000000..7a7bb2f --- /dev/null +++ b/lib/DallasTemperature/examples/SaveRecallScratchPad/SaveRecallScratchPad.ino @@ -0,0 +1,106 @@ +// +// FILE: SaveRecallScratchPad.ino +// AUTHOR: GitKomodo +// VERSION: 0.0.1 +// PURPOSE: Show DallasTemperature lib functionality to +// save/recall ScratchPad values to/from EEPROM +// +// HISTORY: +// 0.0.1 = 2020-02-18 initial version +// + +#include +#include + +#define ONE_WIRE_BUS 2 + +OneWire oneWire(ONE_WIRE_BUS); +DallasTemperature sensors(&oneWire); +DeviceAddress deviceAddress; + +void setup() +{ + Serial.begin(9600); + Serial.println(__FILE__); + Serial.println("Dallas Temperature Demo"); + + sensors.begin(); + + // Get ID of first sensor (at index 0) + sensors.getAddress(deviceAddress, 0); + + // By default configuration and alarm/userdata registers are also saved to EEPROM + // when they're changed. Sensors recall these values automatically when powered up. + + // Turn OFF automatic saving of configuration and alarm/userdata registers to EEPROM + sensors.setAutoSaveScratchPad(false); + + // Change configuration and alarm/userdata registers on the scratchpad + int8_t resolution = 12; + sensors.setResolution(deviceAddress, resolution); + int16_t userdata = 24680; + sensors.setUserData(deviceAddress, userdata); + + // Save configuration and alarm/userdata registers to EEPROM + sensors.saveScratchPad(deviceAddress); + + // saveScratchPad can also be used without a parameter to save the configuration + // and alarm/userdata registers of ALL connected sensors to EEPROM: + // + // sensors.saveScratchPad(); + // + // Or the configuration and alarm/userdata registers of a sensor can be saved to + // EEPROM by index: + // + // sensors.saveScratchPadByIndex(0); + + // Print current values on the scratchpad (resolution = 12, userdata = 24680) + printValues(); + +} + +void loop() { + + // Change configuration and alarm/userdata registers on the scratchpad + int8_t resolution = 10; + sensors.setResolution(deviceAddress, resolution); + int16_t userdata = 12345; + sensors.setUserData(deviceAddress, userdata); + + // Print current values on the scratchpad (resolution = 10, userdata = 12345) + printValues(); + + delay(2000); + + // Recall configuration and alarm/userdata registers from EEPROM + sensors.recallScratchPad(deviceAddress); + + // recallScratchPad can also be used without a parameter to recall the configuration + // and alarm/userdata registers of ALL connected sensors from EEPROM: + // + // sensors.recallScratchPad(); + // + // Or the configuration and alarm/userdata registers of a sensor can be recalled + // from EEPROM by index: + // + // sensors.recallScratchPadByIndex(0); + + // Print current values on the scratchpad (resolution = 12, userdata = 24680) + printValues(); + + delay(2000); + +} + +void printValues() { + + Serial.println(); + Serial.println("Current values on the scratchpad:"); + + Serial.print("Resolution:\t"); + Serial.println(sensors.getResolution(deviceAddress)); + + Serial.print("User data:\t"); + Serial.println(sensors.getUserData(deviceAddress)); + +} diff --git a/lib/DallasTemperature/examples/SetUserData/SetUserData.ino b/lib/DallasTemperature/examples/SetUserData/SetUserData.ino new file mode 100644 index 0000000..52d3f7f --- /dev/null +++ b/lib/DallasTemperature/examples/SetUserData/SetUserData.ino @@ -0,0 +1,47 @@ +// +// This sketch does not use the ALARM registers and uses those 2 bytes as a counter +// these 2 bytes can be used for other purposes as well e.g. last temperature or +// a specific ID. +// + +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +int count = 0; + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + + Serial.print("Temperature for the device 1 (index 0) is: "); + Serial.println(sensors.getTempCByIndex(0)); + + count++; + sensors.setUserDataByIndex(0, count); + int x = sensors.getUserDataByIndex(0); + Serial.println(count); +} diff --git a/lib/DallasTemperature/examples/Simple/Simple.ino b/lib/DallasTemperature/examples/Simple/Simple.ino new file mode 100644 index 0000000..1d7afab --- /dev/null +++ b/lib/DallasTemperature/examples/Simple/Simple.ino @@ -0,0 +1,51 @@ +// Include the libraries we need +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +/* + * The setup function. We only start the sensors here + */ +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); +} + +/* + * Main function, get and show the temperature + */ +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + // After we got the temperatures, we can print them here. + // We use the function ByIndex, and as an example get the temperature from the first sensor only. + float tempC = sensors.getTempCByIndex(0); + + // Check if reading was successful + if (tempC != DEVICE_DISCONNECTED_C) + { + Serial.print("Temperature for the device 1 (index 0) is: "); + Serial.println(tempC); + } + else + { + Serial.println("Error: Could not read temperature data"); + } +} diff --git a/lib/DallasTemperature/examples/Single/Single.ino b/lib/DallasTemperature/examples/Single/Single.ino new file mode 100644 index 0000000..100514b --- /dev/null +++ b/lib/DallasTemperature/examples/Single/Single.ino @@ -0,0 +1,121 @@ +// Include the libraries we need +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device address +DeviceAddress insideThermometer; + +/* + * Setup function. Here we do the basics + */ +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // locate devices on the bus + Serial.print("Locating devices..."); + sensors.begin(); + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.isParasitePowerMode()) Serial.println("ON"); + else Serial.println("OFF"); + + // Assign address manually. The addresses below will beed to be changed + // to valid device addresses on your bus. Device address can be retrieved + // by using either oneWire.search(deviceAddress) or individually via + // sensors.getAddress(deviceAddress, index) + // Note that you will need to use your specific address here + //insideThermometer = { 0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0 }; + + // Method 1: + // Search for devices on the bus and assign based on an index. Ideally, + // you would do this to initially discover addresses on the bus and then + // use those addresses and manually assign them (see above) once you know + // the devices on your bus (and assuming they don't change). + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + + // method 2: search() + // search() looks for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are no devices, + // or you have already retrieved all of them. It might be a good idea to + // check the CRC to make sure you didn't get garbage. The order is + // deterministic. You will always get the same devices in the same order + // + // Must be called before search() + //oneWire.reset_search(); + // assigns the first address found to insideThermometer + //if (!oneWire.search(insideThermometer)) Serial.println("Unable to find address for insideThermometer"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + + // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions) + sensors.setResolution(insideThermometer, 9); + + Serial.print("Device 0 Resolution: "); + Serial.print(sensors.getResolution(insideThermometer), DEC); + Serial.println(); +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + // method 1 - slower + //Serial.print("Temp C: "); + //Serial.print(sensors.getTempC(deviceAddress)); + //Serial.print(" Temp F: "); + //Serial.print(sensors.getTempF(deviceAddress)); // Makes a second call to getTempC and then converts to Fahrenheit + + // method 2 - faster + float tempC = sensors.getTempC(deviceAddress); + if (tempC == DEVICE_DISCONNECTED_C) + { + Serial.println("Error: Could not read temperature data"); + return; + } + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.println(DallasTemperature::toFahrenheit(tempC)); // Converts tempC to Fahrenheit +} +/* + * Main function. It will request the tempC from the sensors and display on Serial. + */ +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + + // It responds almost immediately. Let's print out the data + printTemperature(insideThermometer); // Use a simple function to print out the data +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} diff --git a/lib/DallasTemperature/examples/Tester/Tester.ino b/lib/DallasTemperature/examples/Tester/Tester.ino new file mode 100644 index 0000000..35d2618 --- /dev/null +++ b/lib/DallasTemperature/examples/Tester/Tester.ino @@ -0,0 +1,129 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 +#define TEMPERATURE_PRECISION 9 // Lower resolution + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +int numberOfDevices; // Number of temperature devices found + +DeviceAddress tempDeviceAddress; // We'll use this variable to store a found device address + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // Grab a count of devices on the wire + numberOfDevices = sensors.getDeviceCount(); + + // locate devices on the bus + Serial.print("Locating devices..."); + + Serial.print("Found "); + Serial.print(numberOfDevices, DEC); + Serial.println(" devices."); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.isParasitePowerMode()) Serial.println("ON"); + else Serial.println("OFF"); + + // Loop through each device, print out address + for (int i = 0; i < numberOfDevices; i++) + { + // Search the wire for address + if (sensors.getAddress(tempDeviceAddress, i)) + { + Serial.print("Found device "); + Serial.print(i, DEC); + Serial.print(" with address: "); + printAddress(tempDeviceAddress); + Serial.println(); + + Serial.print("Setting resolution to "); + Serial.println(TEMPERATURE_PRECISION, DEC); + + // set the resolution to TEMPERATURE_PRECISION bit (Each Dallas/Maxim device is capable of several different resolutions) + sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION); + + Serial.print("Resolution actually set to: "); + Serial.print(sensors.getResolution(tempDeviceAddress), DEC); + Serial.println(); + } else { + Serial.print("Found ghost device at "); + Serial.print(i, DEC); + Serial.print(" but could not detect address. Check power and cabling"); + } + } + +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + // method 1 - slower + //Serial.print("Temp C: "); + //Serial.print(sensors.getTempC(deviceAddress)); + //Serial.print(" Temp F: "); + //Serial.print(sensors.getTempF(deviceAddress)); // Makes a second call to getTempC and then converts to Fahrenheit + + // method 2 - faster + float tempC = sensors.getTempC(deviceAddress); + if (tempC == DEVICE_DISCONNECTED_C) + { + Serial.println("Error: Could not read temperature data"); + return; + } + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.println(DallasTemperature::toFahrenheit(tempC)); // Converts tempC to Fahrenheit +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + + + // Loop through each device, print out temperature data + for (int i = 0; i < numberOfDevices; i++) + { + // Search the wire for address + if (sensors.getAddress(tempDeviceAddress, i)) + { + // Output the device ID + Serial.print("Temperature for device: "); + Serial.println(i, DEC); + + // It responds almost immediately. Let's print out the data + printTemperature(tempDeviceAddress); // Use a simple function to print out the data + } + //else ghost device! Check your power requirements and cabling + + } +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} diff --git a/lib/DallasTemperature/examples/Timing/Timing.ino b/lib/DallasTemperature/examples/Timing/Timing.ino new file mode 100644 index 0000000..89ffcb1 --- /dev/null +++ b/lib/DallasTemperature/examples/Timing/Timing.ino @@ -0,0 +1,77 @@ +// +// FILE: Timing.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.0.3 +// PURPOSE: show performance of DallasTemperature lib +// compared to datasheet times per resolution +// +// HISTORY: +// 0.0.1 2017-07-25 initial version +// 0.0.2 2020-02-13 updates to work with current lib version +// 0.0.3 2020-02-20 added timing measurement of setResolution + +#include +#include + +#define ONE_WIRE_BUS 2 + +OneWire oneWire(ONE_WIRE_BUS); +DallasTemperature sensor(&oneWire); + +uint32_t start, stop; + + +void setup() +{ + Serial.begin(9600); + Serial.println(__FILE__); + Serial.print("DallasTemperature Library version: "); + Serial.println(DALLASTEMPLIBVERSION); + + sensor.begin(); +} + +void loop() +{ + float ti[4] = { 94, 188, 375, 750 }; + + Serial.println(); + Serial.println("Test takes about 30 seconds for 4 resolutions"); + Serial.println("RES\tTIME\tACTUAL\tGAIN"); + for (int r = 9; r < 13; r++) + { + start = micros(); + sensor.setResolution(r); + Serial.println(micros() - start); + + start = micros(); + sensor.setResolution(r); + Serial.println(micros() - start); + + uint32_t duration = run(20); + float avgDuration = duration / 20.0; + + Serial.print(r); + Serial.print("\t"); + Serial.print(ti[r - 9]); + Serial.print("\t"); + Serial.print(avgDuration, 2); + Serial.print("\t"); + Serial.print(avgDuration * 100 / ti[r - 9], 1); + Serial.println("%"); + } + delay(1000); +} + +uint32_t run(int runs) +{ + float t; + start = millis(); + for (int i = 0; i < runs; i++) + { + sensor.requestTemperatures(); + t = sensor.getTempCByIndex(0); + } + stop = millis(); + return stop - start; +} diff --git a/lib/DallasTemperature/examples/TwoPin_DS18B20/TwoPin_DS18B20.ino b/lib/DallasTemperature/examples/TwoPin_DS18B20/TwoPin_DS18B20.ino new file mode 100644 index 0000000..61ba8cc --- /dev/null +++ b/lib/DallasTemperature/examples/TwoPin_DS18B20/TwoPin_DS18B20.ino @@ -0,0 +1,45 @@ +// +// FILE: TwoPin_DS18B20.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.1.00 +// PURPOSE: two pins for two sensors demo +// DATE: 2014-06-13 +// URL: http://forum.arduino.cc/index.php?topic=216835.msg1764333#msg1764333 +// +// Released to the public domain +// + +#include +#include + +#define ONE_WIRE_BUS_1 2 +#define ONE_WIRE_BUS_2 4 + +OneWire oneWire_in(ONE_WIRE_BUS_1); +OneWire oneWire_out(ONE_WIRE_BUS_2); + +DallasTemperature sensor_inhouse(&oneWire_in); +DallasTemperature sensor_outhouse(&oneWire_out); + +void setup(void) +{ + Serial.begin(9600); + Serial.println("Dallas Temperature Control Library Demo - TwoPin_DS18B20"); + + sensor_inhouse.begin(); + sensor_outhouse.begin(); +} + +void loop(void) +{ + Serial.print("Requesting temperatures..."); + sensor_inhouse.requestTemperatures(); + sensor_outhouse.requestTemperatures(); + Serial.println(" done"); + + Serial.print("Inhouse: "); + Serial.println(sensor_inhouse.getTempCByIndex(0)); + + Serial.print("Outhouse: "); + Serial.println(sensor_outhouse.getTempCByIndex(0)); +} \ No newline at end of file diff --git a/lib/DallasTemperature/examples/UserDataDemo/UserDataDemo.ino b/lib/DallasTemperature/examples/UserDataDemo/UserDataDemo.ino new file mode 100644 index 0000000..152dcf2 --- /dev/null +++ b/lib/DallasTemperature/examples/UserDataDemo/UserDataDemo.ino @@ -0,0 +1,115 @@ +// +// FILE: UserDataDemo.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.1.0 +// PURPOSE: use of alarm field as user identification demo +// DATE: 2019-12-23 +// URL: +// +// Released to the public domain +// + +#include +#include + +#define ONE_WIRE_BUS 2 + +OneWire oneWire(ONE_WIRE_BUS); +DallasTemperature sensors(&oneWire); + +uint8_t deviceCount = 0; + +// Add 4 prepared sensors to the bus +// use the UserDataWriteBatch demo to prepare 4 different labeled sensors +struct +{ + int id; + DeviceAddress addr; +} T[4]; + +float getTempByID(int id) +{ + for (uint8_t index = 0; index < deviceCount; index++) + { + if (T[index].id == id) + { + return sensors.getTempC(T[index].addr); + } + } + return -999; +} + +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + // zero pad the address if necessary + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + +void setup(void) +{ + Serial.begin(115200); + Serial.println(__FILE__); + Serial.println("Dallas Temperature Demo"); + + sensors.begin(); + + // count devices + deviceCount = sensors.getDeviceCount(); + Serial.print("#devices: "); + Serial.println(deviceCount); + + // Read ID's per sensor + // and put them in T array + for (uint8_t index = 0; index < deviceCount; index++) + { + // go through sensors + sensors.getAddress(T[index].addr, index); + T[index].id = sensors.getUserData(T[index].addr); + } + + // Check all 4 sensors are set + for (uint8_t index = 0; index < deviceCount; index++) + { + Serial.println(); + Serial.println(T[index].id); + printAddress(T[index].addr); + Serial.println(); + } + Serial.println(); + +} + + +void loop(void) +{ + Serial.println(); + Serial.print(millis()); + Serial.println("\treq temp"); + sensors.requestTemperatures(); + + Serial.print(millis()); + Serial.println("\tGet temp by address"); + for (int i = 0; i < 4; i++) + { + Serial.print(millis()); + Serial.print("\t temp:\t"); + Serial.println(sensors.getTempC(T[i].addr)); + } + + Serial.print(millis()); + Serial.println("\tGet temp by ID"); // assume ID = 0, 1, 2, 3 + for (int id = 0; id < 4; id++) + { + Serial.print(millis()); + Serial.print("\t temp:\t"); + Serial.println(getTempByID(id)); + } + + delay(1000); +} + +// END OF FILE \ No newline at end of file diff --git a/lib/DallasTemperature/examples/UserDataWriteBatch/UserDataWriteBatch.ino b/lib/DallasTemperature/examples/UserDataWriteBatch/UserDataWriteBatch.ino new file mode 100644 index 0000000..e0eb6de --- /dev/null +++ b/lib/DallasTemperature/examples/UserDataWriteBatch/UserDataWriteBatch.ino @@ -0,0 +1,107 @@ +// +// FILE: UserDataWriteBatch.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.1.0 +// PURPOSE: use of alarm field as user identification demo +// DATE: 2019-12-23 +// URL: +// +// Released to the public domain +// + +#include +#include + +#define ONE_WIRE_BUS 2 + +OneWire oneWire(ONE_WIRE_BUS); +DallasTemperature sensors(&oneWire); + +uint8_t deviceCount = 0; + +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + // zero pad the address if necessary + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + + + +void setup(void) +{ + Serial.begin(115200); + Serial.println(__FILE__); + Serial.println("Write user ID to DS18B20\n"); + + sensors.begin(); + + // count devices + deviceCount = sensors.getDeviceCount(); + Serial.print("#devices: "); + Serial.println(deviceCount); + + Serial.println(); + Serial.println("current ID's"); + for (uint8_t index = 0; index < deviceCount; index++) + { + DeviceAddress t; + sensors.getAddress(t, index); + printAddress(t); + Serial.print("\t\tID: "); + int id = sensors.getUserData(t); + Serial.println(id); + } + + Serial.println(); + Serial.print("Enter ID for batch: "); + int c = 0; + int id = 0; + while (c != '\n' && c != '\r') + { + c = Serial.read(); + switch (c) + { + case '0'...'9': + id *= 10; + id += (c - '0'); + break; + default: + break; + } + } + Serial.println(); + Serial.println(id); + Serial.println(); + + Serial.println("Start labeling ..."); + for (uint8_t index = 0; index < deviceCount; index++) + { + Serial.print("."); + DeviceAddress t; + sensors.getAddress(t, index); + sensors.setUserData(t, id); + } + Serial.println(); + + Serial.println(); + Serial.println("Show results ..."); + for (uint8_t index = 0; index < deviceCount; index++) + { + DeviceAddress t; + sensors.getAddress(t, index); + printAddress(t); + Serial.print("\t\tID: "); + int id = sensors.getUserData(t); + Serial.println(id); + } + Serial.println("Done ..."); + +} + +void loop(void) {} + +// END OF FILE \ No newline at end of file diff --git a/lib/DallasTemperature/examples/WaitForConversion/WaitForConversion.ino b/lib/DallasTemperature/examples/WaitForConversion/WaitForConversion.ino new file mode 100644 index 0000000..4dd629d --- /dev/null +++ b/lib/DallasTemperature/examples/WaitForConversion/WaitForConversion.ino @@ -0,0 +1,66 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +void setup(void) +{ + // start serial port + Serial.begin(115200); + Serial.println("Dallas Temperature Control Library - Async Demo"); + Serial.println("\nDemo shows the difference in length of the call\n\n"); + + // Start up the library + sensors.begin(); +} + +void loop(void) +{ + // Request temperature conversion (traditional) + Serial.println("Before blocking requestForConversion"); + unsigned long start = millis(); + + sensors.requestTemperatures(); + + unsigned long stop = millis(); + Serial.println("After blocking requestForConversion"); + Serial.print("Time used: "); + Serial.println(stop - start); + + // get temperature + Serial.print("Temperature: "); + Serial.println(sensors.getTempCByIndex(0)); + Serial.println("\n"); + + // Request temperature conversion - non-blocking / async + Serial.println("Before NON-blocking/async requestForConversion"); + start = millis(); + sensors.setWaitForConversion(false); // makes it async + sensors.requestTemperatures(); + sensors.setWaitForConversion(true); + stop = millis(); + Serial.println("After NON-blocking/async requestForConversion"); + Serial.print("Time used: "); + Serial.println(stop - start); + + + // 9 bit resolution by default + // Note the programmer is responsible for the right delay + // we could do something usefull here instead of the delay + int resolution = 9; + delay(750 / (1 << (12 - resolution))); + + // get temperature + Serial.print("Temperature: "); + Serial.println(sensors.getTempCByIndex(0)); + Serial.println("\n\n\n\n"); + + delay(5000); +} diff --git a/lib/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.ino b/lib/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.ino new file mode 100644 index 0000000..3409a74 --- /dev/null +++ b/lib/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.ino @@ -0,0 +1,80 @@ +// +// Sample of using Async reading of Dallas Temperature Sensors +// +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +DeviceAddress tempDeviceAddress; + +int resolution = 12; +unsigned long lastTempRequest = 0; +int delayInMillis = 0; +float temperature = 0.0; +int idle = 0; +// +// SETUP +// +void setup(void) +{ + Serial.begin(115200); + Serial.println("Dallas Temperature Control Library - Async Demo"); + Serial.print("Library Version: "); + Serial.println(DALLASTEMPLIBVERSION); + Serial.println("\n"); + + sensors.begin(); + sensors.getAddress(tempDeviceAddress, 0); + sensors.setResolution(tempDeviceAddress, resolution); + + sensors.setWaitForConversion(false); + sensors.requestTemperatures(); + delayInMillis = 750 / (1 << (12 - resolution)); + lastTempRequest = millis(); + + pinMode(13, OUTPUT); +} + +void loop(void) +{ + + if (millis() - lastTempRequest >= delayInMillis) // waited long enough?? + { + digitalWrite(13, LOW); + Serial.print(" Temperature: "); + temperature = sensors.getTempCByIndex(0); + Serial.println(temperature, resolution - 8); + Serial.print(" Resolution: "); + Serial.println(resolution); + Serial.print("Idle counter: "); + Serial.println(idle); + Serial.println(); + + idle = 0; + + // immediately after fetching the temperature we request a new sample + // in the async modus + // for the demo we let the resolution change to show differences + resolution++; + if (resolution > 12) resolution = 9; + + sensors.setResolution(tempDeviceAddress, resolution); + sensors.requestTemperatures(); + delayInMillis = 750 / (1 << (12 - resolution)); + lastTempRequest = millis(); + } + + digitalWrite(13, HIGH); + // we can do usefull things here + // for the demo we just count the idle time in millis + delay(1); + idle++; +} diff --git a/lib/DallasTemperature/examples/oneWireSearch/oneWireSearch.ino b/lib/DallasTemperature/examples/oneWireSearch/oneWireSearch.ino new file mode 100644 index 0000000..44da619 --- /dev/null +++ b/lib/DallasTemperature/examples/oneWireSearch/oneWireSearch.ino @@ -0,0 +1,67 @@ +// +// FILE: oneWireSearch.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.1.02 +// PURPOSE: scan for 1-Wire devices + code snippet generator +// DATE: 2015-june-30 +// URL: http://forum.arduino.cc/index.php?topic=333923 +// +// inspired by http://www.hacktronics.com/Tutorials/arduino-1-wire-address-finder.html +// +// Released to the public domain +// +// 0.1.00 initial version +// 0.1.01 first published version +// 0.1.02 small output changes + +#include + +void setup() +{ + Serial.begin(115200); + Serial.println("//\n// Start oneWireSearch.ino \n//"); + + for (uint8_t pin = 2; pin < 13; pin++) + { + findDevices(pin); + } + Serial.println("\n//\n// End oneWireSearch.ino \n//"); +} + +void loop() +{ +} + +uint8_t findDevices(int pin) +{ + OneWire ow(pin); + + uint8_t address[8]; + uint8_t count = 0; + + + if (ow.search(address)) + { + Serial.print("\nuint8_t pin"); + Serial.print(pin, DEC); + Serial.println("[][8] = {"); + do { + count++; + Serial.println(" {"); + for (uint8_t i = 0; i < 8; i++) + { + Serial.print("0x"); + if (address[i] < 0x10) Serial.print("0"); + Serial.print(address[i], HEX); + if (i < 7) Serial.print(", "); + } + Serial.println(" },"); + } while (ow.search(address)); + + Serial.println("};"); + Serial.print("// nr devices found: "); + Serial.println(count); + } + + return count; +} diff --git a/lib/DallasTemperature/examples/readPowerSupply/readPowerSupply.ino b/lib/DallasTemperature/examples/readPowerSupply/readPowerSupply.ino new file mode 100644 index 0000000..39a3aeb --- /dev/null +++ b/lib/DallasTemperature/examples/readPowerSupply/readPowerSupply.ino @@ -0,0 +1,92 @@ +// +// FILE: readPowerSupply.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.1.0 +// PURPOSE: demo +// DATE: 2020-02-10 +// +// Released to the public domain +// + +// Include the libraries we need +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; +// Assign address manually. The addresses below will beed to be changed +// to valid device addresses on your bus. Device address can be retrieved +// by using either oneWire.search(deviceAddress) or individually via +// sensors.getAddress(deviceAddress, index) +// DeviceAddress insideThermometer = { 0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0 }; +// DeviceAddress outsideThermometer = { 0x28, 0x3F, 0x1C, 0x31, 0x2, 0x0, 0x0, 0x2 }; + +int devCount = 0; + +/* + * The setup function. We only start the sensors here + */ +void setup(void) +{ + Serial.begin(115200); + Serial.println("Arduino Temperature Control Library Demo - readPowerSupply"); + + sensors.begin(); + + devCount = sensors.getDeviceCount(); + Serial.print("#devices: "); + Serial.println(devCount); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.readPowerSupply()) Serial.println("ON"); // no address means "scan all devices for parasite mode" + else Serial.println("OFF"); + + // Search for devices on the bus and assign based on an index. + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + Serial.print("Power = parasite: "); + Serial.println(sensors.readPowerSupply(insideThermometer)); + Serial.println(); + Serial.println(); + + Serial.print("Device 1 Address: "); + printAddress(outsideThermometer); + Serial.println(); + Serial.print("Power = parasite: "); + Serial.println(sensors.readPowerSupply(outsideThermometer)); + Serial.println(); + Serial.println(); +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + // zero pad the address if necessary + if (deviceAddress[i] < 0x10) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + +// empty on purpose +void loop(void) +{ +} + +// END OF FILE \ No newline at end of file diff --git a/lib/DallasTemperature/keywords.txt b/lib/DallasTemperature/keywords.txt new file mode 100644 index 0000000..a471c3e --- /dev/null +++ b/lib/DallasTemperature/keywords.txt @@ -0,0 +1,87 @@ +####################################### +# Syntax Coloring Map For DallasTemperature +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +DallasTemperature KEYWORD1 +OneWire KEYWORD1 +AlarmHandler KEYWORD1 +DeviceAddress KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setOneWire KEYWORD2 +setPullupPin KEYWORD2 +setResolution KEYWORD2 +getResolution KEYWORD2 +getTemp KEYWORD2 +getTempC KEYWORD2 +toFahrenheit KEYWORD2 +getTempF KEYWORD2 +getTempCByIndex KEYWORD2 +getTempFByIndex KEYWORD2 +rawToCelsius KEYWORD2 +rawToFahrenheit KEYWORD2 +setWaitForConversion KEYWORD2 +getWaitForConversion KEYWORD2 +requestTemperatures KEYWORD2 +requestTemperaturesByAddress KEYWORD2 +requestTemperaturesByIndex KEYWORD2 +setCheckForConversion KEYWORD2 +getCheckForConversion KEYWORD2 +isConversionComplete KEYWORD2 +millisToWaitForConversion KEYWORD2 +isParasitePowerMode KEYWORD2 +begin KEYWORD2 +getDeviceCount KEYWORD2 +getDS18Count KEYWORD2 +getAddress KEYWORD2 +validAddress KEYWORD2 +validFamily KEYWORD2 +isConnected KEYWORD2 +readScratchPad KEYWORD2 +writeScratchPad KEYWORD2 +readPowerSupply KEYWORD2 +saveScratchPadByIndex KEYWORD2 +saveScratchPad KEYWORD2 +recallScratchPadByIndex KEYWORD2 +recallScratchPad KEYWORD2 +setAutoSaveScratchPad KEYWORD2 +getAutoSaveScratchPad KEYWORD2 +setHighAlarmTemp KEYWORD2 +setLowAlarmTemp KEYWORD2 +getHighAlarmTemp KEYWORD2 +getLowAlarmTemp KEYWORD2 +resetAlarmSearch KEYWORD2 +alarmSearch KEYWORD2 +hasAlarm KEYWORD2 +toCelsius KEYWORD2 +processAlarms KEYWORD2 +setAlarmHandler KEYWORD2 +hasAlarmHandler KEYWORD2 +setUserData KEYWORD2 +setUserDataByIndex KEYWORD2 +getUserData KEYWORD2 +getUserDataByIndex KEYWORD2 +calculateTemperature KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +DEVICE_DISCONNECTED_C LITERAL1 +DEVICE_DISCONNECTED_F LITERAL1 +DEVICE_DISCONNECTED_RAW LITERAL1 +DEVICE_FAULT_OPEN_C LITERAL1 +DEVICE_FAULT_OPEN_F LITERAL1 +DEVICE_FAULT_OPEN_RAW LITERAL1 +DEVICE_FAULT_SHORTGND_C LITERAL1 +DEVICE_FAULT_SHORTGND_F LITERAL1 +DEVICE_FAULT_SHORTGND_RAW LITERAL1 +DEVICE_FAULT_SHORTVDD_C LITERAL1 +DEVICE_FAULT_SHORTVDD_F LITERAL1 +DEVICE_FAULT_SHORTVDD_RAW LITERAL1 diff --git a/lib/DallasTemperature/library.json b/lib/DallasTemperature/library.json new file mode 100644 index 0000000..e88bd16 --- /dev/null +++ b/lib/DallasTemperature/library.json @@ -0,0 +1,38 @@ +{ + "name": "DallasTemperature", + "keywords": "onewire, 1-wire, bus, sensor, temperature", + "description": "Arduino Library for Dallas Temperature ICs (DS18B20, DS18S20, DS1822, DS1820, MAX31850)", + "repository": + { + "type": "git", + "url": "https://github.com/milesburton/Arduino-Temperature-Control-Library.git" + }, + "authors": + [ + { + "name": "Miles Burton", + "email": "miles@mnetcs.com", + "url": "http://www.milesburton.com", + "maintainer": true + }, + { + "name": "Tim Newsome", + "email": "nuisance@casualhacker.net" + }, + { + "name": "Guil Barros", + "email": "gfbarros@bappos.com" + }, + { + "name": "Rob Tillaart", + "email": "rob.tillaart@gmail.com" + } + ], + "dependencies": + { + "paulstoffregen/OneWire": "^2.3.5" + }, + "version": "3.11.0", + "frameworks": "arduino", + "platforms": "*" +} diff --git a/lib/DallasTemperature/library.properties b/lib/DallasTemperature/library.properties new file mode 100644 index 0000000..ef3c89f --- /dev/null +++ b/lib/DallasTemperature/library.properties @@ -0,0 +1,10 @@ +name=DallasTemperature +version=3.11.0 +author=Miles Burton , Tim Newsome , Guil Barros , Rob Tillaart +maintainer=Miles Burton +sentence=Arduino Library for Dallas Temperature ICs +paragraph=Supports DS18B20, DS18S20, DS1822, DS1820, MAX31850 +category=Sensors +url=https://github.com/milesburton/Arduino-Temperature-Control-Library +architectures=* +depends=OneWire diff --git a/lib/EspSoftwareSerial/.gitignore b/lib/EspSoftwareSerial/.gitignore new file mode 100644 index 0000000..01820e2 --- /dev/null +++ b/lib/EspSoftwareSerial/.gitignore @@ -0,0 +1,33 @@ + +#Ignore thumbnails created by Windows +Thumbs.db +#Ignore files built by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +.vs/ +#Nuget packages folder +packages/ +__vm/ diff --git a/lib/EspSoftwareSerial/.piopm b/lib/EspSoftwareSerial/.piopm new file mode 100644 index 0000000..64406ea --- /dev/null +++ b/lib/EspSoftwareSerial/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "EspSoftwareSerial", "version": "6.17.1", "spec": {"owner": "plerup", "id": 168, "name": "EspSoftwareSerial", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/EspSoftwareSerial/LICENSE b/lib/EspSoftwareSerial/LICENSE new file mode 100644 index 0000000..f166cc5 --- /dev/null +++ b/lib/EspSoftwareSerial/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/lib/EspSoftwareSerial/README.md b/lib/EspSoftwareSerial/README.md new file mode 100644 index 0000000..052d9c7 --- /dev/null +++ b/lib/EspSoftwareSerial/README.md @@ -0,0 +1,169 @@ +# EspSoftwareSerial + +## Implementation of the Arduino software serial library for the ESP8266 / ESP32 family + +This fork implements interrupt service routine best practice. +In the receive interrupt, instead of blocking for whole bytes +at a time - voiding any near-realtime behavior of the CPU - only level +change and timestamp are recorded. The more time consuming phase +detection and byte assembly are done in the main code. + +Except at high bitrates, depending on other ongoing activity, +interrupts in particular, this software serial adapter +supports full duplex receive and send. At high bitrates (115200bps) +send bit timing can be improved at the expense of blocking concurrent +full duplex receives, with the `SoftwareSerial::enableIntTx(false)` function call. + +The same functionality is given as the corresponding AVR library but +several instances can be active at the same time. Speed up to 115200 baud +is supported. Besides a constructor compatible to the AVR SoftwareSerial class, +and updated constructor that takes no arguments exists, instead the `begin()` +function can handle the pin assignments and logic inversion. +It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer. +This way, it is a better drop-in replacement for the hardware serial APIs on the ESP MCUs. + +Please note that due to the fact that the ESPs always have other activities +ongoing, there will be some inexactness in interrupt timings. This may +lead to inevitable, but few, bit errors when having heavy data traffic +at high baud rates. + +This library supports ESP8266, ESP32, ESP32-S2 and ESP32-C3 devices. + +## Resource optimization + +The memory footprint can be optimized to just fit the amount of expected +incoming asynchronous data. +For this, the `SoftwareSerial` constructor provides two arguments. First, the +octet buffer capacity for assembled received octets can be set. Read calls are +satisfied from this buffer, freeing it in return. +Second, the signal edge detection buffer of 32bit fields can be resized. +One octet may require up to to 10 fields, but fewer may be needed, +depending on the bit pattern. Any read or write calls check this buffer +to assemble received octets, thus promoting completed octets to the octet +buffer, freeing fields in the edge detection buffer. + +Look at the swsertest.ino example. There, on reset, ASCII characters ' ' to 'z' +are sent. This happens not as a block write, but in a single write call per +character. As the example uses a local loopback wire, every outgoing bit is +immediately received back. Therefore, any single write call causes up to +10 fields - depending on the exact bit pattern - to be occupied in the signal +edge detection buffer. In turn, as explained before, each single write call +also causes received bit assembly to be performed, promoting these bits from +the signal edge detection buffer to the octet buffer as soon as possible. +Explaining by way of contrast, if during a a single write call, perhaps because +of using block writing, more than a single octet is received, there will be a +need for more than 10 fields in the signal edge detection buffer. +The necessary capacity of the octet buffer only depends on the amount of incoming +data until the next read call. + +For the swsertest.ino example, this results in the following optimized +constructor arguments to spend only the minimum RAM on buffers required: + +The octet buffer capacity (`bufCapacity`) is 95 (93 characters net plus two tolerance). +The signal edge detection buffer capacity (`isrBufCapacity`) is 11, as each +single octet can have up to 11 bits on the wire, +which are immediately received during the write, and each +write call causes the signal edge detection to promote the previously sent and +received bits to the octet buffer. + +In a more generalized scenario, calculate the bits (use message size in octets +times 10) that may be asynchronously received to determine the value for +`isrBufCapacity` in the constructor. Also use the number of received octets +that must be buffered for reading as the value of `bufCapacity`. +The more frequently your code calls write or read functions, the greater the +chances are that you can reduce the `isrBufCapacity` footprint without losing data, +and each time you call read to fetch from the octet buffer, you reduce the +need for space there. + +## SoftwareSerialConfig and parity +The configuration of the data stream is done via a `SoftwareSerialConfig` +argument to `begin()`. Word lengths can be set to between 5 and 8 bits, parity +can be N(one), O(dd) or E(ven) and 1 or 2 stop bits can be used. The default is +`SWSERIAL_8N1` using 8 bits, no parity and 1 stop bit but any combination can +be used, e.g. `SWSERIAL_7E2`. If using EVEN or ODD parity, any parity errors +can be detected with the `readParity()` and `parityEven()` or `parityOdd()` +functions respectively. Note that the result of `readParity()` always applies +to the preceding `read()` or `peek()` call, and is undefined if they report +no data or an error. + +To allow flexible 9-bit and data/addressing protocols, the additional parity +modes MARK and SPACE are also available. Furthermore, the parity mode can be +individually set in each call to `write()`. + +This allows a simple implementation of protocols where the parity bit is used to +distinguish between data and addresses/commands ("9-bit" protocols). First set +up SoftwareSerial with parity mode SPACE, e.g. `SWSERIAL_8S1`. This will add a +parity bit to every byte sent, setting it to logical zero (SPACE parity). + +To detect incoming bytes with the parity bit set (MARK parity), use the +`readParity()` function. To send a byte with the parity bit set, just add +`MARK` as the second argument when writing, e.g. `write(ch, SWSERIAL_PARITY_MARK)`. + +## Checking for correct pin selection / configuration +In general, most pins on the ESP8266 and ESP32 devices can be used by SoftwareSerial, +however each device has a number of pins that have special functions or require careful +handling to prevent undesirable situations, for example they are connected to the +on-board SPI flash memory or they are used to determine boot and programming modes +after powerup or brownouts. These pins are not able to be configured by this library. + +The exact list for each device can be found in the +[ESP32 data sheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf) +in sections 2.2 (Pin Descriptions) and 2.4 (Strapping pins). There is a discussion +dedicated to the use of GPIO12 in this +[note about GPIO12](https://github.com/espressif/esp-idf/tree/release/v3.2/examples/storage/sd_card#note-about-gpio12). +Refer to the `isValidGPIOpin()`, `isValidRxGPIOpin()` and `isValidTxGPIOpin()` +functions for the GPIO restrictions enforced by this library by default. + +The easiest and safest method is to test the object returned at runtime, to see if +it is valid. For example: + +``` +#include + +#define MYPORT_TX 12 +#define MYPORT_RX 13 + +SoftwareSerial myPort; + +[...] + +Serial.begin(115200); // Standard hardware serial port + +myPort.begin(38400, SWSERIAL_8N1, MYPORT_RX, MYPORT_TX, false); +if (!myPort) { // If the object did not initialize, then its configuration is invalid + Serial.println("Invalid SoftwareSerial pin configuration, check config"); + while (1) { // Don't continue with invalid configuration + delay (1000); + } +} + +[...] +``` + +## Using and updating EspSoftwareSerial in the esp8266com/esp8266 Arduino build environment + +EspSoftwareSerial is both part of the BSP download for ESP8266 in Arduino, +and it is set up as a Git submodule in the esp8266 source tree, +specifically in `.../esp8266/libraries/SoftwareSerial` when using a Github +repository clone in your Arduino sketchbook hardware directory. +This supersedes any version of EspSoftwareSerial installed for instance via +the Arduino library manager, it is not required to install EspSoftwareSerial +for the ESP8266 separately at all, but doing so has ill effect. + +The responsible maintainer of the esp8266 repository has kindly shared the +following command line instructions to use, if one wishes to manually +update EspSoftwareSerial to a newer release than pulled in via the ESP8266 Arduino BSP: + +To update esp8266/arduino SoftwareSerial submodule to lastest master: + +Clean it (optional): +```shell +$ rm -rf libraries/SoftwareSerial +$ git submodule update --init +``` +Now update it: +```shell +$ cd libraries/SoftwareSerial +$ git checkout master +$ git pull +``` diff --git a/lib/EspSoftwareSerial/examples/bitpattern/bitpattern.ino b/lib/EspSoftwareSerial/examples/bitpattern/bitpattern.ino new file mode 100644 index 0000000..8908065 --- /dev/null +++ b/lib/EspSoftwareSerial/examples/bitpattern/bitpattern.ino @@ -0,0 +1,71 @@ +#include "SoftwareSerial.h" + +#ifndef D5 +#if defined(ESP8266) +#define D8 (15) +#define D5 (14) +#define D7 (13) +#define D6 (12) +#define RX (3) +#define TX (1) +#elif defined(ESP32) +#define D8 (5) +#define D5 (18) +#define D7 (23) +#define D6 (19) +#define RX (3) +#define TX (1) +#endif +#endif + +SoftwareSerial swSer; +#ifdef ESP8266 +auto logSer = SoftwareSerial(-1, TX); +auto hwSer = Serial; +#else +auto logSer = Serial; +auto hwSer = Serial1; +#endif + +constexpr uint32_t TESTBPS = 115200; + +void setup() { + delay(2000); +#ifdef ESP8266 + hwSer.begin(TESTBPS, SERIAL_8N1); + hwSer.swap(); +#else + hwSer.begin(TESTBPS, SERIAL_8N1, D6, D5); +#endif + logSer.begin(115200); + logSer.println(PSTR("\nOne Wire Half Duplex Bitpattern and Datarate Test")); + swSer.begin(TESTBPS, SWSERIAL_8N1, D6, D5); + swSer.enableIntTx(true); + logSer.println(PSTR("Tx on swSer")); +} + +uint8_t val = 0xff; + +void loop() { + swSer.write((uint8_t)0x00); + swSer.write(val); + swSer.write(val); + auto start = ESP.getCycleCount(); + int rxCnt = 0; + while (ESP.getCycleCount() - start < ESP.getCpuFreqMHz() * 1000000 / 10) { + if (hwSer.available()) { + auto rxVal = hwSer.read(); + if ((!rxCnt && rxVal) || (rxCnt && rxVal != val)) { + logSer.printf(PSTR("Rx bit error: tx = 0x%02x, rx = 0x%02x\n"), val, rxVal); + } + ++rxCnt; + } + } + if (rxCnt != 3) { + logSer.printf(PSTR("Rx cnt error, tx = 0x%02x\n"), val); + } + ++val; + if (!val) { + logSer.println("Starting over"); + } +} diff --git a/lib/EspSoftwareSerial/examples/loopback/loopback.ino b/lib/EspSoftwareSerial/examples/loopback/loopback.ino new file mode 100644 index 0000000..85b9c34 --- /dev/null +++ b/lib/EspSoftwareSerial/examples/loopback/loopback.ino @@ -0,0 +1,279 @@ +#include + +// On ESP8266: +// Local SoftwareSerial loopback, connect D5 (rx) and D6 (tx). +// For local hardware loopback, connect D5 to D8 (tx), D6 to D7 (rx). +// For hardware send/sink, connect D7 (rx) and D8 (tx). +// Hint: The logger is run at 9600bps such that enableIntTx(true) can remain unchanged. Blocking +// interrupts severely impacts the ability of the SoftwareSerial devices to operate concurrently +// and/or in duplex mode. +// Operating in software serial full duplex mode, runs at 19200bps and few errors (~2.5%). +// Operating in software serial half duplex mode (both loopback and repeater), +// runs at 57600bps with nearly no errors. +// Operating loopback in full duplex, and repeater in half duplex, runs at 38400bps with nearly no errors. +// On ESP32: +// For SoftwareSerial or hardware send/sink, connect D5 (rx) and D6 (tx). +// Hardware Serial2 defaults to D4 (rx), D3 (tx). +// For local hardware loopback, connect D5 (rx) to D3 (tx), D6 (tx) to D4 (rx). + +#ifndef D5 +#if defined(ESP8266) +#define D8 (15) +#define D5 (14) +#define D7 (13) +#define D6 (12) +#define RX (3) +#define TX (1) +#elif defined(ESP32) +#define D8 (5) +#define D5 (18) +#define D7 (23) +#define D6 (19) +#define RX (3) +#define TX (1) +#endif +#endif + +// Pick only one of HWLOOPBACK, HWSOURCESWSINK, or HWSOURCESINK +//#define HWLOOPBACK 1 +//#define HWSOURCESWSINK 1 +//#define HWSOURCESINK 1 +#define HALFDUPLEX 1 + +#ifdef ESP32 +constexpr int IUTBITRATE = 19200; +#else +constexpr int IUTBITRATE = 19200; +#endif + +#if defined(ESP8266) +constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8E1; +constexpr SerialConfig hwSerialConfig = SERIAL_8E1; +#elif defined(ESP32) +constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8E1; +constexpr uint32_t hwSerialConfig = SERIAL_8E1; +#else +constexpr unsigned swSerialConfig = 3; +#endif +constexpr bool invert = false; + +constexpr int BLOCKSIZE = 16; // use fractions of 256 + +unsigned long start; +const char effTxTxt[] PROGMEM = "eff. tx: "; +const char effRxTxt[] PROGMEM = "eff. rx: "; +int txCount; +int rxCount; +int expected; +int rxErrors; +int rxParityErrors; +constexpr int ReportInterval = IUTBITRATE / 8; + +#if defined(ESP8266) +#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK) +HardwareSerial& hwSerial(Serial); +SoftwareSerial serialIUT; +SoftwareSerial logger; +#elif defined(HWSOURCESINK) +HardwareSerial& serialIUT(Serial); +SoftwareSerial logger; +#else +SoftwareSerial serialIUT; +HardwareSerial& logger(Serial); +#endif +#elif defined(ESP32) +#if defined(HWLOOPBACK) || defined (HWSOURCESWSINK) +HardwareSerial& hwSerial(Serial2); +SoftwareSerial serialIUT; +#elif defined(HWSOURCESINK) +HardwareSerial& serialIUT(Serial2); +#else +SoftwareSerial serialIUT; +#endif +HardwareSerial& logger(Serial); +#else +SoftwareSerial serialIUT(14, 12); +HardwareSerial& logger(Serial); +#endif + +void setup() { +#if defined(ESP8266) +#if defined(HWLOOPBACK) || defined(HWSOURCESINK) || defined(HWSOURCESWSINK) + Serial.begin(IUTBITRATE, hwSerialConfig, SERIAL_FULL, 1, invert); + Serial.swap(); + Serial.setRxBufferSize(2 * BLOCKSIZE); + logger.begin(9600, SWSERIAL_8N1, -1, TX); +#else + logger.begin(9600); +#endif +#if !defined(HWSOURCESINK) + serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6, invert, 2 * BLOCKSIZE); +#ifdef HALFDUPLEX + serialIUT.enableIntTx(false); +#endif +#endif +#elif defined(ESP32) +#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK) + Serial2.begin(IUTBITRATE, hwSerialConfig, D4, D3, invert); + Serial2.setRxBufferSize(2 * BLOCKSIZE); +#elif defined(HWSOURCESINK) + serialIUT.begin(IUTBITRATE, hwSerialConfig, D5, D6, invert); + serialIUT.setRxBufferSize(2 * BLOCKSIZE); +#endif +#if !defined(HWSOURCESINK) + serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6, invert, 2 * BLOCKSIZE); +#ifdef HALFDUPLEX + serialIUT.enableIntTx(false); +#endif +#endif + logger.begin(9600); +#else +#if !defined(HWSOURCESINK) + serialIUT.begin(IUTBITRATE); +#endif + logger.begin(9600); +#endif + + logger.println(PSTR("Loopback example for EspSoftwareSerial")); + + start = micros(); + txCount = 0; + rxCount = 0; + rxErrors = 0; + rxParityErrors = 0; + expected = -1; +} + +unsigned char c = 0; + +void loop() { +#ifdef HALFDUPLEX + char block[BLOCKSIZE]; +#endif + char inBuf[BLOCKSIZE]; + for (int i = 0; i < BLOCKSIZE; ++i) { +#ifndef HALFDUPLEX +#ifdef HWSOURCESWSINK + hwSerial.write(c); +#else + serialIUT.write(c); +#endif +#ifdef HWLOOPBACK + int avail = hwSerial.available(); + while ((0 == (i % 8)) && avail > 0) { + int inCnt = hwSerial.read(inBuf, min(avail, min(BLOCKSIZE, hwSerial.availableForWrite()))); + hwSerial.write(inBuf, inCnt); + avail -= inCnt; + } +#endif +#else + block[i] = c; +#endif + c = (c + 1) % 256; + ++txCount; + } +#ifdef HALFDUPLEX +#ifdef HWSOURCESWSINK + hwSerial.write(block, BLOCKSIZE); +#else + serialIUT.write(block, BLOCKSIZE); +#endif +#endif +#ifdef HWSOURCESINK +#if defined(ESP8266) + if (serialIUT.hasOverrun()) { logger.println(PSTR("serialIUT.overrun")); } +#endif +#else + if (serialIUT.overflow()) { logger.println(PSTR("serialIUT.overflow")); } +#endif + + int inCnt; + uint32_t deadlineStart; + +#ifdef HWLOOPBACK + // starting deadline for the first bytes to become readable + deadlineStart = ESP.getCycleCount(); + inCnt = 0; + while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 24 * ESP.getCpuFreqMHz()) { + int avail = hwSerial.available(); + inCnt += hwSerial.read(&inBuf[inCnt], min(avail, min(BLOCKSIZE - inCnt, hwSerial.availableForWrite()))); + if (inCnt >= BLOCKSIZE) { break; } + // wait for more outstanding bytes to trickle in + if (avail) deadlineStart = ESP.getCycleCount(); + } + hwSerial.write(inBuf, inCnt); +#endif + + // starting deadline for the first bytes to come in + deadlineStart = ESP.getCycleCount(); + inCnt = 0; + while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 8 * ESP.getCpuFreqMHz()) { + int avail; + if (0 != (swSerialConfig & 070)) + avail = serialIUT.available(); + else + avail = serialIUT.read(inBuf, BLOCKSIZE); + for (int i = 0; i < avail; ++i) + { + unsigned char r; + if (0 != (swSerialConfig & 070)) + r = serialIUT.read(); + else + r = inBuf[i]; + if (expected == -1) { expected = r; } + else { + expected = (expected + 1) % (1UL << (5 + swSerialConfig % 4)); + } + if (r != expected) { + ++rxErrors; + expected = -1; + } +#ifndef HWSOURCESINK + if (serialIUT.readParity() != (static_cast(swSerialConfig & 010) ? serialIUT.parityOdd(r) : serialIUT.parityEven(r))) + { + ++rxParityErrors; + } +#elif defined(ESP8266) + // current ESP8266 API does not flag parity errors separately + if (serialIUT.hasRxError()) + { + ++rxParityErrors; + } +#endif + ++rxCount; + ++inCnt; + } + + if (inCnt >= BLOCKSIZE) { break; } + // wait for more outstanding bytes to trickle in + if (avail) deadlineStart = ESP.getCycleCount(); + } + + const uint32_t interval = micros() - start; + if (txCount >= ReportInterval && interval) { + uint8_t wordBits = (5 + swSerialConfig % 4) + static_cast(swSerialConfig & 070) + 1 + ((swSerialConfig & 0300) ? 1 : 0); + logger.println(String(PSTR("tx/rx: ")) + txCount + PSTR("/") + rxCount); + const long txCps = txCount * (1000000.0 / interval); + const long rxCps = rxCount * (1000000.0 / interval); + logger.print(String(FPSTR(effTxTxt)) + wordBits * txCps + PSTR("bps, ") + + effRxTxt + wordBits * rxCps + PSTR("bps, ") + + rxErrors + PSTR(" errors (") + 100.0 * rxErrors / (!rxErrors ? 1 : rxCount) + PSTR("%)")); + if (0 != (swSerialConfig & 070)) + { + logger.print(PSTR(" (")); logger.print(rxParityErrors); logger.println(PSTR(" parity errors)")); + } + else + { + logger.println(); + } + txCount = 0; + rxCount = 0; + rxErrors = 0; + rxParityErrors = 0; + expected = -1; + // resync + delay(1000UL * 12 * BLOCKSIZE / IUTBITRATE * 16); + serialIUT.flush(); + start = micros(); + } +} diff --git a/lib/EspSoftwareSerial/examples/onewiretest/onewiretest.ino b/lib/EspSoftwareSerial/examples/onewiretest/onewiretest.ino new file mode 100644 index 0000000..3af8301 --- /dev/null +++ b/lib/EspSoftwareSerial/examples/onewiretest/onewiretest.ino @@ -0,0 +1,59 @@ +#include "SoftwareSerial.h" + +#ifndef D5 +#if defined(ESP8266) +#define D5 (14) +#define D6 (12) +#elif defined(ESP32) +#define D5 (18) +#define D6 (19) +#endif +#endif + +SoftwareSerial swSer1; +SoftwareSerial swSer2; + +void setup() { + delay(2000); + Serial.begin(115200); + Serial.println(PSTR("\nOne Wire Half Duplex Serial Tester")); + swSer1.begin(115200, SWSERIAL_8N1, D6, D6, false, 256); + // high speed half duplex, turn off interrupts during tx + swSer1.enableIntTx(false); + swSer2.begin(115200, SWSERIAL_8N1, D5, D5, false, 256); + // high speed half duplex, turn off interrupts during tx + swSer2.enableIntTx(false); +} + +void loop() { + Serial.println(PSTR("\n\nTesting on swSer1")); + Serial.print(PSTR("Enter something to send using swSer1.")); + checkSwSerial(&swSer1); + + Serial.println(PSTR("\n\nTesting on swSer2")); + Serial.print(PSTR("Enter something to send using swSer2.")); + checkSwSerial(&swSer2); + +} + +void checkSwSerial(SoftwareSerial* ss) { + byte ch; + while (!Serial.available()); + ss->enableTx(true); + while (Serial.available()) { + ch = Serial.read(); + ss->write(ch); + } + ss->enableTx(false); + // wait 1 second for the reply from SOftwareSerial if any + delay(1000); + if (ss->available()) { + Serial.print(PSTR("\nResult:")); + while (ss->available()) { + ch = (byte)ss->read(); + Serial.print(ch < 0x10 ? PSTR(" 0") : PSTR(" ")); + Serial.print(ch, HEX); + } + Serial.println(); + } +} diff --git a/lib/EspSoftwareSerial/examples/repeater/repeater.ino b/lib/EspSoftwareSerial/examples/repeater/repeater.ino new file mode 100644 index 0000000..b390d57 --- /dev/null +++ b/lib/EspSoftwareSerial/examples/repeater/repeater.ino @@ -0,0 +1,199 @@ +#include + +// On ESP8266: +// SoftwareSerial loopback for remote source (loopback.ino), or hardware loopback. +// Connect source D5 (rx) to local D8 (tx), source D6 (tx) to local D7 (rx). +// Hint: The logger is run at 9600bps such that enableIntTx(true) can remain unchanged. Blocking +// interrupts severely impacts the ability of the SoftwareSerial devices to operate concurrently +// and/or in duplex mode. +// On ESP32: +// For software or hardware loopback, connect source rx to local D8 (tx), source tx to local D7 (rx). + +#ifndef D5 +#if defined(ESP8266) +#define D8 (15) +#define D5 (14) +#define D7 (13) +#define D6 (12) +#define RX (3) +#define TX (1) +#elif defined(ESP32) +#define D8 (5) +#define D5 (18) +#define D7 (23) +#define D6 (19) +#define RX (3) +#define TX (1) +#endif +#endif + +#define HWLOOPBACK 1 +#define HALFDUPLEX 1 + +#ifdef ESP32 +constexpr int IUTBITRATE = 19200; +#else +constexpr int IUTBITRATE = 19200; +#endif + +#if defined(ESP8266) +constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8E1; +constexpr SerialConfig hwSerialConfig = SERIAL_8E1; +#elif defined(ESP32) +constexpr SoftwareSerialConfig swSerialConfig = SWSERIAL_8E1; +constexpr uint32_t hwSerialConfig = SERIAL_8E1; +#else +constexpr unsigned swSerialConfig = 3; +#endif +constexpr bool invert = false; + +constexpr int BLOCKSIZE = 16; // use fractions of 256 + +unsigned long start; +const char bitRateTxt[] PROGMEM = "Effective data rate: "; +int rxCount; +int seqErrors; +int parityErrors; +int expected; +constexpr int ReportInterval = IUTBITRATE / 8; + +#if defined(ESP8266) +#if defined(HWLOOPBACK) +HardwareSerial& repeater(Serial); +SoftwareSerial logger; +#else +SoftwareSerial repeater; +HardwareSerial& logger(Serial); +#endif +#elif defined(ESP32) +#if defined(HWLOOPBACK) +HardwareSerial& repeater(Serial2); +#else +SoftwareSerial repeater; +#endif +HardwareSerial& logger(Serial); +#else +SoftwareSerial repeater(14, 12); +HardwareSerial& logger(Serial); +#endif + +void setup() { +#if defined(ESP8266) +#if defined(HWLOOPBACK) + repeater.begin(IUTBITRATE, hwSerialConfig, SERIAL_FULL, 1, invert); + repeater.swap(); + repeater.setRxBufferSize(2 * BLOCKSIZE); + logger.begin(9600, SWSERIAL_8N1, -1, TX); +#else + repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, invert, 4 * BLOCKSIZE); +#ifdef HALFDUPLEX + repeater.enableIntTx(false); +#endif + logger.begin(9600); +#endif +#elif defined(ESP32) +#if defined(HWLOOPBACK) + repeater.begin(IUTBITRATE, hwSerialConfig, D7, D8, invert); + repeater.setRxBufferSize(2 * BLOCKSIZE); +#else + repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, invert, 4 * BLOCKSIZE); +#ifdef HALFDUPLEX + repeater.enableIntTx(false); +#endif +#endif + logger.begin(9600); +#else + repeater.begin(IUTBITRATE); + logger.begin(9600); +#endif + + logger.println(PSTR("Repeater example for EspSoftwareSerial")); + start = micros(); + rxCount = 0; + seqErrors = 0; + parityErrors = 0; + expected = -1; +} + +void loop() { +#ifdef HWLOOPBACK +#if defined(ESP8266) + if (repeater.hasOverrun()) { logger.println(PSTR("repeater.overrun")); } +#endif +#else + if (repeater.overflow()) { logger.println(PSTR("repeater.overflow")); } +#endif + +#ifdef HALFDUPLEX + char block[BLOCKSIZE]; +#endif + // starting deadline for the first bytes to come in + uint32_t deadlineStart = ESP.getCycleCount(); + int inCnt = 0; + while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 24 * ESP.getCpuFreqMHz()) { + int avail = repeater.available(); + for (int i = 0; i < avail; ++i) + { + int r = repeater.read(); + if (r == -1) { logger.println(PSTR("read() == -1")); } + if (expected == -1) { expected = r; } + else { + expected = (expected + 1) % (1UL << (5 + swSerialConfig % 4)); + } + if (r != expected) { + ++seqErrors; + expected = -1; + } +#ifndef HWLOOPBACK + if (repeater.readParity() != (static_cast(swSerialConfig & 010) ? repeater.parityOdd(r) : repeater.parityEven(r))) + { + ++parityErrors; + } +#elif defined(ESP8266) + // current ESP8266 API does not flag parity errors separately + if (repeater.hasRxError()) + { + ++parityErrors; + } +#endif + ++rxCount; +#ifdef HALFDUPLEX + block[inCnt] = r; +#else + repeater.write(r); +#endif + if (++inCnt >= BLOCKSIZE) { break; } + } + if (inCnt >= BLOCKSIZE) { break; } + // wait for more outstanding bytes to trickle in + if (avail) deadlineStart = ESP.getCycleCount(); + } + +#ifdef HALFDUPLEX + repeater.write(block, inCnt); +#endif + + if (rxCount >= ReportInterval) { + auto end = micros(); + unsigned long interval = end - start; + long cps = rxCount * (1000000.0 / interval); + long seqErrorsps = seqErrors * (1000000.0 / interval); + logger.print(String(FPSTR(bitRateTxt)) + 10 * cps + PSTR("bps, ") + + seqErrorsps + PSTR("cps seq. errors (") + 100.0 * seqErrors / rxCount + PSTR("%)")); +#ifndef HWLOOPBACK + if (0 != (swSerialConfig & 070)) + { + logger.print(PSTR(" (")); logger.print(parityErrors); logger.println(PSTR(" parity errors)")); + } + else +#endif + { + logger.println(); + } + start = end; + rxCount = 0; + seqErrors = 0; + parityErrors = 0; + expected = -1; + } +} diff --git a/lib/EspSoftwareSerial/examples/servoTester/servoTester.ino b/lib/EspSoftwareSerial/examples/servoTester/servoTester.ino new file mode 100644 index 0000000..44f6b01 --- /dev/null +++ b/lib/EspSoftwareSerial/examples/servoTester/servoTester.ino @@ -0,0 +1,113 @@ +#include + +SoftwareSerial swSer; + +byte buf[10] = { 0xFA, 0xAF,0x00,0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0xED }; +const byte cmd[10] PROGMEM = { 0xFA, 0xAF,0x00,0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0xED }; + + +void setup() { + delay(2000); + Serial.begin(115200); + Serial.println(PSTR("\nAlpha 1S Servo Tester")); + swSer.begin(115200, SWSERIAL_8N1, 12, 12, false, 256); +} + +void loop() { + for (int i = 1; i <= 32; i++) { + GetVersion(i); + delay(100); + } + SetLED(1, 0); + GoPos(1, 0, 50); + delay(1000); + GoPos(1, 90, 50); + delay(1000); + GoPos(1, 100, 50); + delay(1000); + SetLED(1, 1); + delay(2000); +} + + + + +void GetVersion(byte id) { + memcpy_P(buf, cmd, 10); + buf[0] = 0xFC; + buf[1] = 0xCF; + buf[2] = id; + buf[3] = 0x01; + SendCommand(); +} + + +void GoPos(byte id, byte Pos, byte Time) { + memcpy_P(buf, cmd, 10); + buf[2] = id; + buf[3] = 0x01; + buf[4] = Pos; + buf[5] = Time; + buf[6] = 0x00; + buf[7] = Time; + SendCommand(); +} + +void GetPos(byte id) { + memcpy_P(buf, cmd, 10); + buf[2] = id; + buf[3] = 0x02; + SendCommand(); +} + + +void SetLED(byte id, byte mode) { + memcpy_P(buf, cmd, 10); + buf[2] = id; + buf[3] = 0x04; + buf[4] = mode; + SendCommand(); +} + +void SendCommand() { + SendCommand(true); +} + +void SendCommand(bool checkResult) { + byte sum = 0; + for (int i = 2; i < 8; i++) { + sum += buf[i]; + } + buf[8] = sum; + ShowCommand(); + swSer.flush(); + swSer.enableTx(true); + swSer.write(buf, 10); + swSer.enableTx(false); + if (checkResult) checkReturn(); +} + +void ShowCommand() { + Serial.print(millis()); + Serial.print(PSTR(" OUT>>")); + for (int i = 0; i < 10; i++) { + Serial.print(buf[i] < 0x10 ? PSTR(" 0") : PSTR(" ")); + Serial.print(buf[i], HEX); + } + Serial.println(); +} + +void checkReturn() { + unsigned long startMs = millis(); + while (((millis() - startMs) < 500) && (!swSer.available())); + if (swSer.available()) { + Serial.print(millis()); + Serial.print(PSTR(" IN>>>")); + while (swSer.available()) { + byte ch = (byte)swSer.read(); + Serial.print((ch < 0x10 ? PSTR(" 0") : PSTR(" "))); + Serial.print(ch, HEX); + } + Serial.println(); + } +} diff --git a/lib/EspSoftwareSerial/examples/swsertest/swsertest.ino b/lib/EspSoftwareSerial/examples/swsertest/swsertest.ino new file mode 100644 index 0000000..447bdfe --- /dev/null +++ b/lib/EspSoftwareSerial/examples/swsertest/swsertest.ino @@ -0,0 +1,79 @@ +// On ESP8266: +// At 80MHz runs up 57600ps, and at 160MHz CPU frequency up to 115200bps with only negligible errors. +// Connect pin 13 to 15. +// For verification and as a example for how to use SW serial on the USB to PC connection, +// which allows the use of HW Serial on GPIO13 and GPIO15 instead, #define SWAPSERIAL below. +// Notice how the bitrates are also swapped then between RX/TX and GPIO13/GPIO15. +// Builtin debug output etc. must be stopped on HW Serial in this case, as it would interfere with the +// external communication on GPIO13/GPIO15. + +#include + +#ifndef D5 +#if defined(ESP8266) +#define D8 (15) +#define D5 (14) +#define D7 (13) +#define D6 (12) +#define RX (3) +#define TX (1) +#elif defined(ESP32) +#define D8 (5) +#define D5 (18) +#define D7 (23) +#define D6 (19) +#define RX (3) +#define TX (1) +#endif +#endif + +#ifdef ESP32 +#define BAUD_RATE 57600 +#else +#define BAUD_RATE 57600 +#endif + +#undef SWAPSERIAL + +#ifndef SWAPSERIAL +auto& usbSerial = Serial; +SoftwareSerial testSerial; +#else +SoftwareSerial usbSerial; +auto& testSerial = Serial; +#endif + +void setup() { +#ifndef SWAPSERIAL + usbSerial.begin(115200); + // Important: the buffer size optimizations here, in particular the isrBufSize (11) that is only sufficiently + // large to hold a single word (up to start - 8 data - parity - stop), are on the basis that any char written + // to the loopback SoftwareSerial adapter gets read before another write is performed. + // Block writes with a size greater than 1 would usually fail. Do not copy this into your own project without + // reading the documentation. + testSerial.begin(BAUD_RATE, SWSERIAL_8N1, D7, D8, false, 95, 11); +#else + testSerial.begin(115200); + testSerial.setDebugOutput(false); + testSerial.swap(); + usbSerial.begin(BAUD_RATE, SWSERIAL_8N1, RX, TX, false, 95); +#endif + + usbSerial.println(PSTR("\nSoftware serial test started")); + + for (char ch = ' '; ch <= 'z'; ch++) { + testSerial.write(ch); + } + testSerial.println(); +} + +void loop() { + while (testSerial.available() > 0) { + usbSerial.write(testSerial.read()); + yield(); + } + while (usbSerial.available() > 0) { + testSerial.write(usbSerial.read()); + yield(); + } +} diff --git a/lib/EspSoftwareSerial/keywords.txt b/lib/EspSoftwareSerial/keywords.txt new file mode 100644 index 0000000..52d48ab --- /dev/null +++ b/lib/EspSoftwareSerial/keywords.txt @@ -0,0 +1,43 @@ +####################################### +# Syntax Coloring Map for SoftwareSerial +# (esp8266) +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SoftwareSerial KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +baudRate KEYWORD2 +setTransmitEnablePin KEYWORD2 +enableIntTx KEYWORD2 +overflow KEYWORD2 +available KEYWORD2 +peek KEYWORD2 +read KEYWORD2 +flush KEYWORD2 +write KEYWORD2 +enableRx KEYWORD2 +enableTx KEYWORD2 +listen KEYWORD2 +end KEYWORD2 +isListening KEYWORD2 +stopListening KEYWORD2 +onReceive KEYWORD2 +perform_work KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SW_SERIAL_UNUSED_PIN LITERAL1 +SWSERIAL_5N1 LITERAL1 +SWSERIAL_6N1 LITERAL1 +SWSERIAL_7N1 LITERAL1 +SWSERIAL_8N1 LITERAL1 diff --git a/lib/EspSoftwareSerial/library.json b/lib/EspSoftwareSerial/library.json new file mode 100644 index 0000000..f6b50b5 --- /dev/null +++ b/lib/EspSoftwareSerial/library.json @@ -0,0 +1,26 @@ +{ + "name": "EspSoftwareSerial", + "version": "6.17.1", + "description": "Implementation of the Arduino software serial for ESP8266/ESP32.", + "keywords": [ + "serial", "io", "softwareserial" + ], + "repository": + { + "type": "git", + "url": "https://github.com/plerup/espsoftwareserial" + }, + "authors": [ + { + "name": "Dirk Kaar" + }, + { + "name": "Peter Lerup" + } + ], + "license": "LGPL-2.1+", + "frameworks": "arduino", + "platforms": [ + "espressif8266", "espressif32" + ] +} diff --git a/lib/EspSoftwareSerial/library.properties b/lib/EspSoftwareSerial/library.properties new file mode 100644 index 0000000..833e99b --- /dev/null +++ b/lib/EspSoftwareSerial/library.properties @@ -0,0 +1,9 @@ +name=EspSoftwareSerial +version=6.17.1 +author=Dirk Kaar, Peter Lerup +maintainer=Dirk Kaar +sentence=Implementation of the Arduino software serial for ESP8266/ESP32. +paragraph= +category=Signal Input/Output +url=https://github.com/plerup/espsoftwareserial/ +architectures=esp8266,esp32 diff --git a/lib/EspSoftwareSerial/src/SoftwareSerial.cpp b/lib/EspSoftwareSerial/src/SoftwareSerial.cpp new file mode 100644 index 0000000..73f56a1 --- /dev/null +++ b/lib/EspSoftwareSerial/src/SoftwareSerial.cpp @@ -0,0 +1,679 @@ +/* + +SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights 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 + +*/ + +#include "SoftwareSerial.h" +#include + +#ifndef ESP32 +uint32_t SoftwareSerial::m_savedPS = 0; +#else +portMUX_TYPE SoftwareSerial::m_interruptsMux = portMUX_INITIALIZER_UNLOCKED; +#endif + +inline void IRAM_ATTR SoftwareSerial::disableInterrupts() +{ +#ifndef ESP32 + m_savedPS = xt_rsil(15); +#else + taskENTER_CRITICAL(&m_interruptsMux); +#endif +} + +inline void IRAM_ATTR SoftwareSerial::restoreInterrupts() +{ +#ifndef ESP32 + xt_wsr_ps(m_savedPS); +#else + taskEXIT_CRITICAL(&m_interruptsMux); +#endif +} + +constexpr uint8_t BYTE_ALL_BITS_SET = ~static_cast(0); + +SoftwareSerial::SoftwareSerial() { + m_isrOverflow = false; + m_rxGPIOPullUpEnabled = true; + m_txGPIOOpenDrain = false; +} + +SoftwareSerial::SoftwareSerial(int8_t rxPin, int8_t txPin, bool invert) +{ + m_isrOverflow = false; + m_rxGPIOPullUpEnabled = true; + m_txGPIOOpenDrain = false; + m_rxPin = rxPin; + m_txPin = txPin; + m_invert = invert; +} + +SoftwareSerial::~SoftwareSerial() { + end(); +} + +#if __GNUC__ >= 10 +constexpr +#endif +bool SoftwareSerial::isValidGPIOpin(int8_t pin) const { +#if defined(ESP8266) + return (pin >= 0 && pin <= 16) && !isFlashInterfacePin(pin); +#elif defined(ESP32) + // Remove the strapping pins as defined in the datasheets, they affect bootup and other critical operations + // Remmove the flash memory pins on related devices, since using these causes memory access issues. +#ifdef CONFIG_IDF_TARGET_ESP32 + // Datasheet https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf, + // Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32/_images/esp32-devkitC-v4-pinout.jpg + return (pin == 1) || (pin >= 3 && pin <= 5) || + (pin >= 12 && pin <= 15) || + (!psramFound() && pin >= 16 && pin <= 17) || + (pin >= 18 && pin <= 19) || + (pin >= 21 && pin <= 23) || (pin >= 25 && pin <= 27) || (pin >= 32 && pin <= 39); +#elif CONFIG_IDF_TARGET_ESP32S2 + // Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf, + // Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/_images/esp32-s2_saola1-pinout.jpg + return (pin >= 1 && pin <= 21) || (pin >= 33 && pin <= 44); +#elif CONFIG_IDF_TARGET_ESP32C3 + // Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf, + // Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/_images/esp32-c3-devkitm-1-v1-pinout.jpg + return (pin >= 0 && pin <= 1) || (pin >= 3 && pin <= 7) || (pin >= 18 && pin <= 21); +#else + return pin >= 0; +#endif +#else + return pin >= 0; +#endif +} + +#if __GNUC__ >= 10 +constexpr +#endif +bool SoftwareSerial::isValidRxGPIOpin(int8_t pin) const { + return isValidGPIOpin(pin) +#if defined(ESP8266) + && (pin != 16) +#endif + ; +} + +#if __GNUC__ >= 10 +constexpr +#endif +bool SoftwareSerial::isValidTxGPIOpin(int8_t pin) const { + return isValidGPIOpin(pin) +#if defined(ESP32) +#ifdef CONFIG_IDF_TARGET_ESP32 + && (pin < 34) +#elif CONFIG_IDF_TARGET_ESP32S2 + && (pin <= 45) +#elif CONFIG_IDF_TARGET_ESP32C3 + // no restrictions +#endif +#endif + ; +} + +#if __GNUC__ >= 10 +constexpr +#endif +bool SoftwareSerial::hasRxGPIOPullUp(int8_t pin) const { +#if defined(ESP32) + return !(pin >= 34 && pin <= 39); +#else + (void)pin; + return true; +#endif +} + +void SoftwareSerial::setRxGPIOPinMode() { + if (m_rxValid) { + pinMode(m_rxPin, hasRxGPIOPullUp(m_rxPin) && m_rxGPIOPullUpEnabled ? INPUT_PULLUP : INPUT); + } +} + +void SoftwareSerial::setTxGPIOPinMode() { + if (m_txValid) { + pinMode(m_txPin, m_txGPIOOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); + } +} + +void SoftwareSerial::begin(uint32_t baud, SoftwareSerialConfig config, + int8_t rxPin, int8_t txPin, + bool invert, int bufCapacity, int isrBufCapacity) { + if (-1 != rxPin) m_rxPin = rxPin; + if (-1 != txPin) m_txPin = txPin; + m_oneWire = (m_rxPin == m_txPin); + m_invert = invert; + m_dataBits = 5 + (config & 07); + m_parityMode = static_cast(config & 070); + m_stopBits = 1 + ((config & 0300) ? 1 : 0); + m_pduBits = m_dataBits + static_cast(m_parityMode) + m_stopBits; + m_bitTicks = (microsToTicks(1000000UL) + baud / 2) / baud; + m_intTxEnabled = true; + if (isValidRxGPIOpin(m_rxPin)) { + m_rxReg = portInputRegister(digitalPinToPort(m_rxPin)); + m_rxBitMask = digitalPinToBitMask(m_rxPin); + m_buffer.reset(new circular_queue((bufCapacity > 0) ? bufCapacity : 64)); + if (m_parityMode) + { + m_parityBuffer.reset(new circular_queue((m_buffer->capacity() + 7) / 8)); + m_parityInPos = m_parityOutPos = 1; + } + m_isrBuffer.reset(new circular_queue((isrBufCapacity > 0) ? + isrBufCapacity : m_buffer->capacity() * (2 + m_dataBits + static_cast(m_parityMode)))); + if (m_buffer && (!m_parityMode || m_parityBuffer) && m_isrBuffer) { + m_rxValid = true; + setRxGPIOPinMode(); + } + } + if (isValidTxGPIOpin(m_txPin)) { +#if !defined(ESP8266) + m_txReg = portOutputRegister(digitalPinToPort(m_txPin)); +#endif + m_txBitMask = digitalPinToBitMask(m_txPin); + m_txValid = true; + if (!m_oneWire) { + setTxGPIOPinMode(); + digitalWrite(m_txPin, !m_invert); + } + } + enableRx(true); +} + +void SoftwareSerial::end() +{ + enableRx(false); + m_txValid = false; + if (m_buffer) { + m_buffer.reset(); + } + m_parityBuffer.reset(); + if (m_isrBuffer) { + m_isrBuffer.reset(); + } +} + +uint32_t SoftwareSerial::baudRate() { + return 1000000UL / ticksToMicros(m_bitTicks); +} + +void SoftwareSerial::setTransmitEnablePin(int8_t txEnablePin) { + if (isValidTxGPIOpin(txEnablePin)) { + m_txEnableValid = true; + m_txEnablePin = txEnablePin; + pinMode(m_txEnablePin, OUTPUT); + digitalWrite(m_txEnablePin, LOW); + } + else { + m_txEnableValid = false; + } +} + +void SoftwareSerial::enableIntTx(bool on) { + m_intTxEnabled = on; +} + +void SoftwareSerial::enableRxGPIOPullUp(bool on) { + m_rxGPIOPullUpEnabled = on; + setRxGPIOPinMode(); +} + +void SoftwareSerial::enableTxGPIOOpenDrain(bool on) { + m_txGPIOOpenDrain = on; + setTxGPIOPinMode(); +} + +void SoftwareSerial::enableTx(bool on) { + if (m_txValid && m_oneWire) { + if (on) { + enableRx(false); + setTxGPIOPinMode(); + digitalWrite(m_txPin, !m_invert); + } + else { + setRxGPIOPinMode(); + enableRx(true); + } + } +} + +void SoftwareSerial::enableRx(bool on) { + if (m_rxValid && on != m_rxEnabled) { + if (on) { + m_rxLastBit = m_pduBits - 1; + // Init to stop bit level and current tick + m_isrLastTick = (microsToTicks(micros()) | 1) ^ m_invert; + if (m_bitTicks >= microsToTicks(1000000UL / 74880UL)) + attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast(rxBitISR), this, CHANGE); + else + attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast(rxBitSyncISR), this, m_invert ? RISING : FALLING); + } + else { + detachInterrupt(digitalPinToInterrupt(m_rxPin)); + } + m_rxEnabled = on; + } +} + +int SoftwareSerial::read() { + if (!m_rxValid) { return -1; } + if (!m_buffer->available()) { + rxBits(); + if (!m_buffer->available()) { return -1; } + } + auto val = m_buffer->pop(); + if (m_parityBuffer) + { + m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos; + m_parityOutPos <<= 1; + if (!m_parityOutPos) + { + m_parityOutPos = 1; + m_parityBuffer->pop(); + } + } + return val; +} + +int SoftwareSerial::read(uint8_t* buffer, size_t size) { + if (!m_rxValid) { return 0; } + int avail; + if (0 == (avail = m_buffer->pop_n(buffer, size))) { + rxBits(); + avail = m_buffer->pop_n(buffer, size); + } + if (!avail) return 0; + if (m_parityBuffer) { + uint32_t parityBits = avail; + while (m_parityOutPos >>= 1) ++parityBits; + m_parityOutPos = (1 << (parityBits % 8)); + m_parityBuffer->pop_n(nullptr, parityBits / 8); + } + return avail; +} + +size_t SoftwareSerial::readBytes(uint8_t* buffer, size_t size) { + if (!m_rxValid || !size) { return 0; } + size_t count = 0; + auto start = millis(); + do { + auto readCnt = read(&buffer[count], size - count); + count += readCnt; + if (count >= size) break; + if (readCnt) { + start = millis(); + } + else { + optimistic_yield(1000UL); + } + } while (millis() - start < _timeout); + return count; +} + +int SoftwareSerial::available() { + if (!m_rxValid) { return 0; } + rxBits(); + int avail = m_buffer->available(); + if (!avail) { + optimistic_yield(10000UL); + } + return avail; +} + +void SoftwareSerial::lazyDelay() { + // Reenable interrupts while delaying to avoid other tasks piling up + if (!m_intTxEnabled) { restoreInterrupts(); } + const auto expired = microsToTicks(micros()) - m_periodStart; + const int32_t remaining = m_periodDuration - expired; + const int32_t ms = remaining > 0 ? static_cast(ticksToMicros(remaining) / 1000L) : 0; + if (ms > 0) + { + delay(ms); + } + else + { + optimistic_yield(10000UL); + } + // Assure that below-ms part of delays are not elided + preciseDelay(); + // Disable interrupts again if applicable + if (!m_intTxEnabled) { disableInterrupts(); } +} + +void IRAM_ATTR SoftwareSerial::preciseDelay() { + uint32_t ticks; + uint32_t expired; + do { + ticks = microsToTicks(micros()); + expired = ticks - m_periodStart; + } while (static_cast(m_periodDuration - expired) > 0); + m_periodDuration = 0; + m_periodStart = ticks; +} + +void IRAM_ATTR SoftwareSerial::writePeriod( + uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) { + preciseDelay(); + if (dutyCycle) + { +#if defined(ESP8266) + if (16 == m_txPin) { + GP16O = 1; + } + else { + GPOS = m_txBitMask; + } +#else + *m_txReg |= m_txBitMask; +#endif + m_periodDuration += dutyCycle; + if (offCycle || (withStopBit && !m_invert)) { + if (!withStopBit || m_invert) { + preciseDelay(); + } + else { + lazyDelay(); + } + } + } + if (offCycle) + { +#if defined(ESP8266) + if (16 == m_txPin) { + GP16O = 0; + } + else { + GPOC = m_txBitMask; + } +#else + *m_txReg &= ~m_txBitMask; +#endif + m_periodDuration += offCycle; + if (withStopBit && m_invert) lazyDelay(); + } +} + +size_t SoftwareSerial::write(uint8_t byte) { + return write(&byte, 1); +} + +size_t SoftwareSerial::write(uint8_t byte, SoftwareSerialParity parity) { + return write(&byte, 1, parity); +} + +size_t SoftwareSerial::write(const uint8_t* buffer, size_t size) { + return write(buffer, size, m_parityMode); +} + +size_t IRAM_ATTR SoftwareSerial::write(const uint8_t* buffer, size_t size, SoftwareSerialParity parity) { + if (m_rxValid) { rxBits(); } + if (!m_txValid) { return -1; } + + if (m_txEnableValid) { + digitalWrite(m_txEnablePin, HIGH); + } + // Stop bit: if inverted, LOW, otherwise HIGH + bool b = !m_invert; + uint32_t dutyCycle = 0; + uint32_t offCycle = 0; + if (!m_intTxEnabled) { + // Disable interrupts in order to get a clean transmit timing + disableInterrupts(); + } + const uint32_t dataMask = ((1UL << m_dataBits) - 1); + bool withStopBit = true; + m_periodDuration = 0; + m_periodStart = microsToTicks(micros()); + for (size_t cnt = 0; cnt < size; ++cnt) { + uint8_t byte = pgm_read_byte(buffer + cnt) & dataMask; + // push LSB start-data-parity-stop bit pattern into uint32_t + // Stop bits: HIGH + uint32_t word = ~0UL; + // inverted parity bit, performance tweak for xor all-bits-set word + if (parity && m_parityMode) + { + uint32_t parityBit; + switch (parity) + { + case SWSERIAL_PARITY_EVEN: + // from inverted, so use odd parity + parityBit = byte; + parityBit ^= parityBit >> 4; + parityBit &= 0xf; + parityBit = (0x9669 >> parityBit) & 1; + break; + case SWSERIAL_PARITY_ODD: + // from inverted, so use even parity + parityBit = byte; + parityBit ^= parityBit >> 4; + parityBit &= 0xf; + parityBit = (0x6996 >> parityBit) & 1; + break; + case SWSERIAL_PARITY_MARK: + parityBit = 0; + break; + case SWSERIAL_PARITY_SPACE: + // suppresses warning parityBit uninitialized + default: + parityBit = 1; + break; + } + word ^= parityBit; + } + word <<= m_dataBits; + word |= byte; + // Start bit: LOW + word <<= 1; + if (m_invert) word = ~word; + for (int i = 0; i <= m_pduBits; ++i) { + bool pb = b; + b = word & (1UL << i); + if (!pb && b) { + writePeriod(dutyCycle, offCycle, withStopBit); + withStopBit = false; + dutyCycle = offCycle = 0; + } + if (b) { + dutyCycle += m_bitTicks; + } + else { + offCycle += m_bitTicks; + } + } + withStopBit = true; + } + writePeriod(dutyCycle, offCycle, true); + if (!m_intTxEnabled) { + // restore the interrupt state if applicable + restoreInterrupts(); + } + if (m_txEnableValid) { + digitalWrite(m_txEnablePin, LOW); + } + return size; +} + +void SoftwareSerial::flush() { + if (!m_rxValid) { return; } + m_buffer->flush(); + if (m_parityBuffer) + { + m_parityInPos = m_parityOutPos = 1; + m_parityBuffer->flush(); + } +} + +bool SoftwareSerial::overflow() { + bool res = m_overflow; + m_overflow = false; + return res; +} + +int SoftwareSerial::peek() { + if (!m_rxValid) { return -1; } + if (!m_buffer->available()) { + rxBits(); + if (!m_buffer->available()) return -1; + } + auto val = m_buffer->peek(); + if (m_parityBuffer) m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos; + return val; +} + +void SoftwareSerial::rxBits() { +#ifdef ESP8266 + if (m_isrOverflow.load()) { + m_overflow = true; + m_isrOverflow.store(false); + } +#else + if (m_isrOverflow.exchange(false)) { + m_overflow = true; + } +#endif + + m_isrBuffer->for_each(m_isrBufferForEachDel); + + // A stop bit can go undetected if leading data bits are at same level + // and there was also no next start bit yet, so one word may be pending. + // Check that there was no new ISR data received in the meantime, inserting an + // extraneous stop level bit out of sequence breaks rx. + if (m_rxLastBit < m_pduBits - 1) { + const uint32_t detectionTicks = (m_pduBits - 1 - m_rxLastBit) * m_bitTicks; + if (!m_isrBuffer->available() && microsToTicks(micros()) - m_isrLastTick > detectionTicks) { + // Produce faux stop bit level, prevents start bit maldetection + // tick's LSB is repurposed for the level bit + rxBits(((m_isrLastTick + detectionTicks) | 1) ^ m_invert); + } + } +} + +void SoftwareSerial::rxBits(const uint32_t isrTick) { + const bool level = (m_isrLastTick & 1) ^ m_invert; + + // error introduced by edge value in LSB of isrTick is negligible + uint32_t ticks = isrTick - m_isrLastTick; + m_isrLastTick = isrTick; + + uint32_t bits = ticks / m_bitTicks; + if (ticks % m_bitTicks > (m_bitTicks >> 1)) ++bits; + while (bits > 0) { + // start bit detection + if (m_rxLastBit >= (m_pduBits - 1)) { + // leading edge of start bit? + if (level) break; + m_rxLastBit = -1; + --bits; + continue; + } + // data bits + if (m_rxLastBit < (m_dataBits - 1)) { + uint8_t dataBits = min(bits, static_cast(m_dataBits - 1 - m_rxLastBit)); + m_rxLastBit += dataBits; + bits -= dataBits; + m_rxCurByte >>= dataBits; + if (level) { m_rxCurByte |= (BYTE_ALL_BITS_SET << (8 - dataBits)); } + continue; + } + // parity bit + if (m_parityMode && m_rxLastBit == (m_dataBits - 1)) { + ++m_rxLastBit; + --bits; + m_rxCurParity = level; + continue; + } + // stop bits + // Store the received value in the buffer unless we have an overflow + // if not high stop bit level, discard word + if (bits >= static_cast(m_pduBits - 1 - m_rxLastBit) && level) { + m_rxCurByte >>= (sizeof(uint8_t) * 8 - m_dataBits); + if (!m_buffer->push(m_rxCurByte)) { + m_overflow = true; + } + else { + if (m_parityBuffer) + { + if (m_rxCurParity) { + m_parityBuffer->pushpeek() |= m_parityInPos; + } + else { + m_parityBuffer->pushpeek() &= ~m_parityInPos; + } + m_parityInPos <<= 1; + if (!m_parityInPos) + { + m_parityBuffer->push(); + m_parityInPos = 1; + } + } + } + } + m_rxLastBit = m_pduBits - 1; + // reset to 0 is important for masked bit logic + m_rxCurByte = 0; + m_rxCurParity = false; + break; + } +} + +void IRAM_ATTR SoftwareSerial::rxBitISR(SoftwareSerial* self) { + uint32_t curTick = microsToTicks(micros()); + bool level = *self->m_rxReg & self->m_rxBitMask; + + // Store level and tick in the buffer unless we have an overflow + // tick's LSB is repurposed for the level bit + if (!self->m_isrBuffer->push((curTick | 1U) ^ !level)) self->m_isrOverflow.store(true); +} + +void IRAM_ATTR SoftwareSerial::rxBitSyncISR(SoftwareSerial* self) { + uint32_t start = microsToTicks(micros()); + uint32_t wait = self->m_bitTicks - microsToTicks(2U); + + bool level = self->m_invert; + // Store level and tick in the buffer unless we have an overflow + // tick's LSB is repurposed for the level bit + if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ !level)) self->m_isrOverflow.store(true); + + for (uint32_t i = 0; i < self->m_pduBits; ++i) { + while (microsToTicks(micros()) - start < wait) {}; + wait += self->m_bitTicks; + + // Store level and tick in the buffer unless we have an overflow + // tick's LSB is repurposed for the level bit + if (static_cast(*self->m_rxReg & self->m_rxBitMask) != level) + { + if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true); + level = !level; + } + } +} + +void SoftwareSerial::onReceive(Delegate handler) { + receiveHandler = handler; +} + +void SoftwareSerial::perform_work() { + if (!m_rxValid) { return; } + rxBits(); + if (receiveHandler) { + int avail = m_buffer->available(); + if (avail) { receiveHandler(avail); } + } +} diff --git a/lib/EspSoftwareSerial/src/SoftwareSerial.h b/lib/EspSoftwareSerial/src/SoftwareSerial.h new file mode 100644 index 0000000..de5a89c --- /dev/null +++ b/lib/EspSoftwareSerial/src/SoftwareSerial.h @@ -0,0 +1,301 @@ +/* +SoftwareSerial.h + +SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights 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 __SoftwareSerial_h +#define __SoftwareSerial_h + +#include "circular_queue/circular_queue.h" +#include + +enum SoftwareSerialParity : uint8_t { + SWSERIAL_PARITY_NONE = 000, + SWSERIAL_PARITY_EVEN = 020, + SWSERIAL_PARITY_ODD = 030, + SWSERIAL_PARITY_MARK = 040, + SWSERIAL_PARITY_SPACE = 070, +}; + +enum SoftwareSerialConfig { + SWSERIAL_5N1 = SWSERIAL_PARITY_NONE, + SWSERIAL_6N1, + SWSERIAL_7N1, + SWSERIAL_8N1, + SWSERIAL_5E1 = SWSERIAL_PARITY_EVEN, + SWSERIAL_6E1, + SWSERIAL_7E1, + SWSERIAL_8E1, + SWSERIAL_5O1 = SWSERIAL_PARITY_ODD, + SWSERIAL_6O1, + SWSERIAL_7O1, + SWSERIAL_8O1, + SWSERIAL_5M1 = SWSERIAL_PARITY_MARK, + SWSERIAL_6M1, + SWSERIAL_7M1, + SWSERIAL_8M1, + SWSERIAL_5S1 = SWSERIAL_PARITY_SPACE, + SWSERIAL_6S1, + SWSERIAL_7S1, + SWSERIAL_8S1, + SWSERIAL_5N2 = 0200 | SWSERIAL_PARITY_NONE, + SWSERIAL_6N2, + SWSERIAL_7N2, + SWSERIAL_8N2, + SWSERIAL_5E2 = 0200 | SWSERIAL_PARITY_EVEN, + SWSERIAL_6E2, + SWSERIAL_7E2, + SWSERIAL_8E2, + SWSERIAL_5O2 = 0200 | SWSERIAL_PARITY_ODD, + SWSERIAL_6O2, + SWSERIAL_7O2, + SWSERIAL_8O2, + SWSERIAL_5M2 = 0200 | SWSERIAL_PARITY_MARK, + SWSERIAL_6M2, + SWSERIAL_7M2, + SWSERIAL_8M2, + SWSERIAL_5S2 = 0200 | SWSERIAL_PARITY_SPACE, + SWSERIAL_6S2, + SWSERIAL_7S2, + SWSERIAL_8S2, +}; + +/// This class is compatible with the corresponding AVR one, however, +/// the constructor takes no arguments, for compatibility with the +/// HardwareSerial class. +/// Instead, the begin() function handles pin assignments and logic inversion. +/// It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer. +/// Bitrates up to at least 115200 can be used. +class SoftwareSerial : public Stream { +public: + SoftwareSerial(); + /// Ctor to set defaults for pins. + /// @param rxPin the GPIO pin used for RX + /// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX + SoftwareSerial(int8_t rxPin, int8_t txPin = -1, bool invert = false); + SoftwareSerial(const SoftwareSerial&) = delete; + SoftwareSerial& operator= (const SoftwareSerial&) = delete; + virtual ~SoftwareSerial(); + /// Configure the SoftwareSerial object for use. + /// @param baud the TX/RX bitrate + /// @param config sets databits, parity, and stop bit count + /// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor + /// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor + /// @param invert true: uses invert line level logic + /// @param bufCapacity the capacity for the received bytes buffer + /// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous + /// bit receive buffer, a suggested size is bufCapacity times the sum of + /// start, data, parity and stop bit count. + void begin(uint32_t baud, SoftwareSerialConfig config, + int8_t rxPin, int8_t txPin, bool invert, + int bufCapacity = 64, int isrBufCapacity = 0); + void begin(uint32_t baud, SoftwareSerialConfig config, + int8_t rxPin, int8_t txPin) { + begin(baud, config, rxPin, txPin, m_invert); + } + void begin(uint32_t baud, SoftwareSerialConfig config, + int8_t rxPin) { + begin(baud, config, rxPin, m_txPin, m_invert); + } + void begin(uint32_t baud, SoftwareSerialConfig config = SWSERIAL_8N1) { + begin(baud, config, m_rxPin, m_txPin, m_invert); + } + + uint32_t baudRate(); + /// Transmit control pin. + void setTransmitEnablePin(int8_t txEnablePin); + /// Enable (default) or disable interrupts during tx. + void enableIntTx(bool on); + /// Enable (default) or disable internal rx GPIO pull-up. + void enableRxGPIOPullUp(bool on); + /// Enable or disable (default) tx GPIO output mode. + void enableTxGPIOOpenDrain(bool on); + + bool overflow(); + + int available() override; +#if defined(ESP8266) + int availableForWrite() override { +#else + int availableForWrite() { +#endif + if (!m_txValid) return 0; + return 1; + } + int peek() override; + int read() override; + /// @returns The verbatim parity bit associated with the last successful read() or peek() call + bool readParity() + { + return m_lastReadParity; + } + /// @returns The calculated bit for even parity of the parameter byte + static bool parityEven(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x6996 >> byte) & 1; + } + /// @returns The calculated bit for odd parity of the parameter byte + static bool parityOdd(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x9669 >> byte) & 1; + } + /// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout + int read(uint8_t* buffer, size_t size) +#if defined(ESP8266) + override +#endif + ; + /// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout + int read(char* buffer, size_t size) { + return read(reinterpret_cast(buffer), size); + } + /// @returns The number of bytes read into buffer, up to size. Times out if the limit set through + /// Stream::setTimeout() is reached. + size_t readBytes(uint8_t* buffer, size_t size) override; + /// @returns The number of bytes read into buffer, up to size. Times out if the limit set through + /// Stream::setTimeout() is reached. + size_t readBytes(char* buffer, size_t size) override { + return readBytes(reinterpret_cast(buffer), size); + } + void flush() override; + size_t write(uint8_t byte) override; + size_t write(uint8_t byte, SoftwareSerialParity parity); + size_t write(const uint8_t* buffer, size_t size) override; + size_t write(const char* buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } + size_t write(const uint8_t* buffer, size_t size, SoftwareSerialParity parity); + size_t write(const char* buffer, size_t size, SoftwareSerialParity parity) { + return write(reinterpret_cast(buffer), size, parity); + } + operator bool() const { + return (-1 == m_rxPin || m_rxValid) && (-1 == m_txPin || m_txValid) && !(-1 == m_rxPin && m_oneWire); + } + + /// Disable or enable interrupts on the rx pin. + void enableRx(bool on); + /// One wire control. + void enableTx(bool on); + + // AVR compatibility methods. + bool listen() { enableRx(true); return true; } + void end(); + bool isListening() { return m_rxEnabled; } + bool stopListening() { enableRx(false); return true; } + + /// Set an event handler for received data. + void onReceive(Delegate handler); + + /// Run the internal processing and event engine. Can be iteratively called + /// from loop, or otherwise scheduled. + void perform_work(); + + using Print::write; + +private: + // It's legal to exceed the deadline, for instance, + // by enabling interrupts. + void lazyDelay(); + // Synchronous precise delay + void preciseDelay(); + // If withStopBit is set, either cycle contains a stop bit. + // If dutyCycle == 0, the level is not forced to HIGH. + // If offCycle == 0, the level remains unchanged from dutyCycle. + void writePeriod( + uint32_t dutyCycle, uint32_t offCycle, bool withStopBit); + constexpr bool isValidGPIOpin(int8_t pin) const; + constexpr bool isValidRxGPIOpin(int8_t pin) const; + constexpr bool isValidTxGPIOpin(int8_t pin) const; + // result is only defined for a valid Rx GPIO pin + constexpr bool hasRxGPIOPullUp(int8_t pin) const; + // safely set the pin mode for the Rx GPIO pin + void setRxGPIOPinMode(); + // safely set the pin mode for the Tx GPIO pin + void setTxGPIOPinMode(); + /* check m_rxValid that calling is safe */ + void rxBits(); + void rxBits(const uint32_t isrTick); + static void disableInterrupts(); + static void restoreInterrupts(); + + static void rxBitISR(SoftwareSerial* self); + static void rxBitSyncISR(SoftwareSerial* self); + + static inline uint32_t microsToTicks(uint32_t micros) { + return micros << 1; + } + static inline uint32_t ticksToMicros(uint32_t ticks) { + return ticks >> 1; + } + + // Member variables + int8_t m_rxPin = -1; + volatile uint32_t* m_rxReg; + uint32_t m_rxBitMask; + int8_t m_txPin = -1; +#if !defined(ESP8266) + volatile uint32_t* m_txReg; +#endif + uint32_t m_txBitMask; + int8_t m_txEnablePin = -1; + uint8_t m_dataBits; + bool m_oneWire; + bool m_rxValid = false; + bool m_rxEnabled = false; + bool m_txValid = false; + bool m_txEnableValid = false; + bool m_invert; + /// PDU bits include data, parity and stop bits; the start bit is not counted. + uint8_t m_pduBits; + bool m_intTxEnabled; + bool m_rxGPIOPullUpEnabled; + bool m_txGPIOOpenDrain; + SoftwareSerialParity m_parityMode; + uint8_t m_stopBits; + bool m_lastReadParity; + bool m_overflow = false; + uint32_t m_bitTicks; + uint8_t m_parityInPos; + uint8_t m_parityOutPos; + int8_t m_rxLastBit; // 0 thru (m_pduBits - m_stopBits - 1): data/parity bits. -1: start bit. (m_pduBits - 1): stop bit. + uint8_t m_rxCurByte = 0; + std::unique_ptr > m_buffer; + std::unique_ptr > m_parityBuffer; + uint32_t m_periodStart; + uint32_t m_periodDuration; +#ifndef ESP32 + static uint32_t m_savedPS; +#else + static portMUX_TYPE m_interruptsMux; +#endif + // the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement): + // 1 = positive including 0, 0 = negative. + std::unique_ptr > m_isrBuffer; + const Delegate m_isrBufferForEachDel = { [](SoftwareSerial* self, uint32_t&& isrTick) { self->rxBits(isrTick); }, this }; + std::atomic m_isrOverflow; + uint32_t m_isrLastTick; + bool m_rxCurParity = false; + Delegate receiveHandler; +}; + +#endif // __SoftwareSerial_h diff --git a/lib/EspSoftwareSerial/src/circular_queue/Delegate.h b/lib/EspSoftwareSerial/src/circular_queue/Delegate.h new file mode 100644 index 0000000..193ca8a --- /dev/null +++ b/lib/EspSoftwareSerial/src/circular_queue/Delegate.h @@ -0,0 +1,2130 @@ +/* +Delegate.h - An efficient interchangeable C function ptr and C++ std::function delegate +Copyright (c) 2019 Dirk O. Kaar. All rights 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 __Delegate_h +#define __Delegate_h + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#else +#define IRAM_ATTR +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) +#include +#include +#else +#include "circular_queue/ghostl.h" +#endif + +namespace +{ + + template + R IRAM_ATTR vPtrToFunPtrExec(void* fn, P... args) + { + using target_type = R(P...); + return reinterpret_cast(fn)(std::forward(args...)); + } + +} + +namespace delegate +{ + namespace detail + { + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); + using FunctionType = std::function; + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegatePImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + else if (FPA == kind) + obj.~A(); + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(std::move(del.obj)); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + new (&this->obj) A(obj); + } + + DelegatePImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + new (&this->obj) A(std::move(obj)); + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = del.functional; + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return fnA; + } + else + { + return functional ? true : false; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) + { + return static_cast(self)->fnA( + static_cast(self)->obj, + std::forward(args...)); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else if (FPA == kind) + { + return vPtrToFunAPtrExec; + } + else + { + return [](void* self, P... args) -> R + { + return static_cast(self)->functional(std::forward(args...)); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return [this](P... args) { return fnA(obj, std::forward(args...)); }; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + return fn(std::forward(args...)); + } + else if (FPA == kind) + { + return fnA(obj, std::forward(args...)); + } + else + { + return functional(std::forward(args...)); + } + } + + protected: + union { + FunctionType functional; + FunPtr fn; + struct { + FunAPtr fnA; + A obj; + }; + }; + enum { FUNC, FP, FPA } kind; + }; +#else + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + this->obj = obj; + } + + DelegatePImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + this->obj = std::move(obj); + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FP; + fn = std::forward(functional); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return fnA; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) + { + return static_cast(self)->fnA( + static_cast(self)->obj, + std::forward(args...)); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else + { + return vPtrToFunAPtrExec; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + return fn(std::forward(args...)); + } + else + { + return fnA(obj, std::forward(args...)); + } + } + + protected: + union { + FunPtr fn; + FunAPtr fnA; + }; + A obj; + enum { FP, FPA } kind; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunctionType = std::function; + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegatePImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = del.functional; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + kind = FP; + } + DelegatePImpl::fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional ? true : false; + } + } + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else + { + return [](void* self, P... args) -> R + { + return static_cast(self)->functional(std::forward(args...)); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + return fn(std::forward(args...)); + } + else + { + return functional(std::forward(args...)); + } + } + + protected: + union { + FunctionType functional; + FunPtr fn; + }; + enum { FUNC, FP } kind; + }; +#else + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + fn = nullptr; + } + + DelegatePImpl(const DelegatePImpl& del) + { + fn = del.fn; + } + + DelegatePImpl(DelegatePImpl&& del) + { + fn = std::move(del.fn); + } + + DelegatePImpl(FunPtr fn) + { + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F fn) + { + DelegatePImpl::fn = std::forward(fn); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + fn = del.fn; + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + fn = std::move(del.fn); + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + DelegatePImpl::fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + fn = nullptr; + return *this; + } + + operator bool() const + { + return fn; + } + + operator FunVPPtr() const + { + return vPtrToFunPtrExec; + } + + void* arg() const + { + return reinterpret_cast(fn); + } + + R IRAM_ATTR operator()(P... args) const + { + return fn(std::forward(args...)); + } + + protected: + FunPtr fn; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunctionType = std::function; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegateImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + else if (FPA == kind) + obj.~A(); + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(std::move(del.obj)); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + new (&this->obj) A(obj); + } + + DelegateImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + new (&this->obj) A(std::move(obj)); + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = del.functional; + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return fnA; + } + else + { + return functional ? true : false; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self) + { + return static_cast(self)->fnA( + static_cast(self)->obj); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else if (FPA == kind) + { + return vPtrToFunAPtrExec; + } + else + { + return [](void* self) -> R + { + return static_cast(self)->functional(); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return [this]() { return fnA(obj); }; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + return fn(); + } + else if (FPA == kind) + { + return fnA(obj); + } + else + { + return functional(); + } + } + + protected: + union { + FunctionType functional; + FunPtr fn; + struct { + FunAPtr fnA; + A obj; + }; + }; + enum { FUNC, FP, FPA } kind; + }; +#else + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + this->obj = obj; + } + + DelegateImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + this->obj = std::move(obj); + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F fn) + { + kind = FP; + DelegateImpl::fn = std::forward(fn); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return fnA; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self) + { + return static_cast(self)->fnA( + static_cast(self)->obj); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return vPtrToFunAPtrExec; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + return fn(); + } + else + { + return fnA(obj); + } + } + + protected: + union { + FunPtr fn; + FunAPtr fnA; + }; + A obj; + enum { FP, FPA } kind; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunctionType = std::function; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegateImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = del.functional; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + kind = FP; + } + DelegateImpl::fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional ? true : false; + } + } + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return [](void* self) -> R + { + return static_cast(self)->functional(); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + return fn(); + } + else + { + return functional(); + } + } + + protected: + union { + FunctionType functional; + FunPtr fn; + }; + enum { FUNC, FP } kind; + }; +#else + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + fn = nullptr; + } + + DelegateImpl(const DelegateImpl& del) + { + fn = del.fn; + } + + DelegateImpl(DelegateImpl&& del) + { + fn = std::move(del.fn); + } + + DelegateImpl(FunPtr fn) + { + DelegateImpl::fn = fn; + } + + template DelegateImpl(F fn) + { + DelegateImpl::fn = std::forward(fn); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + fn = del.fn; + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + fn = std::move(del.fn); + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + DelegateImpl::fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + fn = nullptr; + return *this; + } + + operator bool() const + { + return fn; + } + + operator FunVPPtr() const + { + return reinterpret_cast(fn); + } + + void* arg() const + { + return nullptr; + } + + R IRAM_ATTR operator()() const + { + return fn(); + } + + protected: + FunPtr fn; + }; +#endif + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::arg; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() { return detail::DelegatePImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, const A& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} + + Delegate(FunAPtr fnA, A&& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, std::move(obj)) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A*, P...); + using FunVPPtr = R(*)(void*, P...); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() const + { + if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) + { + return reinterpret_cast(detail::DelegatePImpl::fnA); + } + else + { + return detail::DelegatePImpl::operator FunVPPtr(); + } + } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + void* arg() const + { + if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) + { + return detail::DelegatePImpl::obj; + } + else + { + return detail::DelegatePImpl::arg(); + } + } + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, A* obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + using FunVPPtr = R(*)(void*, P...); + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::arg; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() const { return detail::DelegatePImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunVPPtr = R(*)(void*); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::arg; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() { return detail::DelegateImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, const A& obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} + + Delegate(FunAPtr fnA, A&& obj) : detail::DelegateImpl::DelegateImpl(fnA, std::move(obj)) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A*); + using FunVPPtr = R(*)(void*); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() const + { + if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) + { + return reinterpret_cast(detail::DelegateImpl::fnA); + } + else + { + return detail::DelegateImpl::operator FunVPPtr(); + } + } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + void* arg() const + { + if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) + { + return detail::DelegateImpl::obj; + } + else + { + return detail::DelegateImpl::arg(); + } + } + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, A* obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + using FunVPPtr = R(*)(void*); + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::arg; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() const { return detail::DelegateImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + } +} + +template class Delegate; +template class Delegate : public delegate::detail::Delegate +{ +public: + Delegate() : delegate::detail::Delegate::Delegate() {} + + Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} + + Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( + std::move(static_cast&>(del))) {} + + Delegate(typename delegate::detail::Delegate::FunAPtr fnA, const A& obj) : delegate::detail::Delegate::Delegate(fnA, obj) {} + + Delegate(typename delegate::detail::Delegate::FunAPtr fnA, A&& obj) : delegate::detail::Delegate::Delegate(fnA, std::move(obj)) {} + + Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} + + template Delegate(F functional) : delegate::detail::Delegate::Delegate(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + delegate::detail::Delegate::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + delegate::detail::Delegate::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { + delegate::detail::Delegate::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + delegate::detail::Delegate::operator=(nullptr); + return *this; + } +}; + +template class Delegate : public delegate::detail::Delegate +{ +public: + Delegate() : delegate::detail::Delegate::Delegate() {} + + Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} + + Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( + std::move(static_cast&>(del))) {} + + Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} + + template Delegate(F functional) : delegate::detail::Delegate::Delegate(std::forward(functional)) {} + + Delegate& operator=(const Delegate& del) { + delegate::detail::Delegate::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + delegate::detail::Delegate::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { + delegate::detail::Delegate::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + delegate::detail::Delegate::operator=(nullptr); + return *this; + } +}; + +#endif // __Delegate_h diff --git a/lib/EspSoftwareSerial/src/circular_queue/MultiDelegate.h b/lib/EspSoftwareSerial/src/circular_queue/MultiDelegate.h new file mode 100644 index 0000000..36cbd94 --- /dev/null +++ b/lib/EspSoftwareSerial/src/circular_queue/MultiDelegate.h @@ -0,0 +1,567 @@ +/* +MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate +class +Copyright (c) 2019-2020 Dirk O. Kaar. All rights 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 __MULTIDELEGATE_H +#define __MULTIDELEGATE_H + +#include +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +#include +#else +#include "circular_queue/ghostl.h" +#endif + +#if defined(ESP8266) +#include +using esp8266::InterruptLock; +#elif defined(ARDUINO) +class InterruptLock { +public: + InterruptLock() { + noInterrupts(); + } + ~InterruptLock() { + interrupts(); + } +}; +#else +#include +#endif + +namespace +{ + + template< typename Delegate, typename R, bool ISQUEUE = false, typename... P> + struct CallP + { + static R execute(Delegate& del, P... args) + { + return del(std::forward(args...)); + } + }; + + template< typename Delegate, bool ISQUEUE, typename... P> + struct CallP + { + static bool execute(Delegate& del, P... args) + { + del(std::forward(args...)); + return true; + } + }; + + template< typename Delegate, typename R, bool ISQUEUE = false> + struct Call + { + static R execute(Delegate& del) + { + return del(); + } + }; + + template< typename Delegate, bool ISQUEUE> + struct Call + { + static bool execute(Delegate& del) + { + del(); + return true; + } + }; + +} + +namespace delegate +{ + namespace detail + { + + template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P> + class MultiDelegatePImpl + { + public: + MultiDelegatePImpl() = default; + ~MultiDelegatePImpl() + { + *this = nullptr; + } + + MultiDelegatePImpl(const MultiDelegatePImpl&) = delete; + MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete; + + MultiDelegatePImpl(MultiDelegatePImpl&& md) + { + first = md.first; + last = md.last; + unused = md.unused; + nodeCount = md.nodeCount; + md.first = nullptr; + md.last = nullptr; + md.unused = nullptr; + md.nodeCount = 0; + } + + MultiDelegatePImpl(const Delegate& del) + { + add(del); + } + + MultiDelegatePImpl(Delegate&& del) + { + add(std::move(del)); + } + + MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md) + { + first = md.first; + last = md.last; + unused = md.unused; + nodeCount = md.nodeCount; + md.first = nullptr; + md.last = nullptr; + md.unused = nullptr; + md.nodeCount = 0; + return *this; + } + + MultiDelegatePImpl& operator=(std::nullptr_t) + { + if (last) + last->mNext = unused; + if (first) + unused = first; + while (unused) + { + auto to_delete = unused; + unused = unused->mNext; + delete(to_delete); + } + return *this; + } + + MultiDelegatePImpl& operator+=(const Delegate& del) + { + add(del); + return *this; + } + + MultiDelegatePImpl& operator+=(Delegate&& del) + { + add(std::move(del)); + return *this; + } + + protected: + struct Node_t + { + ~Node_t() + { + mDelegate = nullptr; // special overload in Delegate + } + Node_t* mNext = nullptr; + Delegate mDelegate; + }; + + Node_t* first = nullptr; + Node_t* last = nullptr; + Node_t* unused = nullptr; + size_t nodeCount = 0; + + // Returns a pointer to an unused Node_t, + // or if none are available allocates a new one, + // or nullptr if limit is reached + Node_t* IRAM_ATTR get_node_unsafe() + { + Node_t* result = nullptr; + // try to get an item from unused items list + if (unused) + { + result = unused; + unused = unused->mNext; + } + // if no unused items, and count not too high, allocate a new one + else if (nodeCount < QUEUE_CAPACITY) + { +#if defined(ESP8266) || defined(ESP32) + result = new (std::nothrow) Node_t; +#else + result = new Node_t; +#endif + if (result) + ++nodeCount; + } + return result; + } + + void recycle_node_unsafe(Node_t* node) + { + node->mDelegate = nullptr; // special overload in Delegate + node->mNext = unused; + unused = node; + } + +#ifndef ARDUINO + std::mutex mutex_unused; +#endif + public: + class iterator : public std::iterator + { + public: + Node_t* current = nullptr; + Node_t* prev = nullptr; + const Node_t* stop = nullptr; + + iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {} + iterator() = default; + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; + iterator& operator=(iterator&&) = default; + operator bool() const + { + return current && stop; + } + bool operator==(const iterator& rhs) const + { + return current == rhs.current; + } + bool operator!=(const iterator& rhs) const + { + return !operator==(rhs); + } + Delegate& operator*() const + { + return current->mDelegate; + } + Delegate* operator->() const + { + return ¤t->mDelegate; + } + iterator& operator++() // prefix + { + if (current && stop != current) + { + prev = current; + current = current->mNext; + } + else + current = nullptr; // end + return *this; + } + iterator& operator++(int) // postfix + { + iterator tmp(*this); + operator++(); + return tmp; + } + }; + + iterator begin() + { + return iterator(*this); + } + iterator end() const + { + return iterator(); + } + + const Delegate* IRAM_ATTR add(const Delegate& del) + { + return add(Delegate(del)); + } + + const Delegate* IRAM_ATTR add(Delegate&& del) + { + if (!del) + return nullptr; + +#ifdef ARDUINO + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(mutex_unused); +#endif + + Node_t* item = ISQUEUE ? get_node_unsafe() : +#if defined(ESP8266) || defined(ESP32) + new (std::nothrow) Node_t; +#else + new Node_t; +#endif + if (!item) + return nullptr; + + item->mDelegate = std::move(del); + item->mNext = nullptr; + + if (last) + last->mNext = item; + else + first = item; + last = item; + + return &item->mDelegate; + } + + iterator erase(iterator it) + { + if (!it) + return end(); +#ifdef ARDUINO + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(mutex_unused); +#endif + auto to_recycle = it.current; + + if (last == it.current) + last = it.prev; + it.current = it.current->mNext; + if (it.prev) + { + it.prev->mNext = it.current; + } + else + { + first = it.current; + } + if (ISQUEUE) + recycle_node_unsafe(to_recycle); + else + delete to_recycle; + return it; + } + + bool erase(const Delegate* const del) + { + auto it = begin(); + while (it) + { + if (del == &(*it)) + { + erase(it); + return true; + } + ++it; + } + return false; + } + + operator bool() const + { + return first; + } + + R operator()(P... args) + { + auto it = begin(); + if (!it) + return {}; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return {}; + fence.store(true); +#else + if (fence.exchange(true)) return {}; +#endif + + R result; + do + { + result = CallP::execute(*it, args...); + if (result && ISQUEUE) + it = erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + return result; + } + }; + + template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> + class MultiDelegateImpl : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + + R operator()() + { + auto it = this->begin(); + if (!it) + return {}; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return {}; + fence.store(true); +#else + if (fence.exchange(true)) return {}; +#endif + + R result; + do + { + result = Call::execute(*it); + if (result && ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + return result; + } + }; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> + class MultiDelegate : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + }; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY> + class MultiDelegate : public MultiDelegateImpl + { + public: + using MultiDelegateImpl::MultiDelegateImpl; + }; + + template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> + class MultiDelegate : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + + void operator()(P... args) + { + auto it = this->begin(); + if (!it) + return; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return; + fence.store(true); +#else + if (fence.exchange(true)) return; +#endif + + do + { + CallP::execute(*it, args...); + if (ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + } + }; + + template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY> + class MultiDelegate : public MultiDelegateImpl + { + public: + using MultiDelegateImpl::MultiDelegateImpl; + + void operator()() + { + auto it = this->begin(); + if (!it) + return; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return; + fence.store(true); +#else + if (fence.exchange(true)) return; +#endif + + do + { + Call::execute(*it); + if (ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + } + }; + + } + +} + +/** +The MultiDelegate class template can be specialized to either a queue or an event multiplexer. +It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function. +@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on. +@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true), + the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain. + This is exploited to minimize the use of new and delete by reusing already allocated items, thus + reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are + used for allocation of the event handler items. + If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue + removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until + explicitly removed. + If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue + the type-conversion to bool of that result determines if the item is immediately removed or kept + after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event + handlers until they are explicitly removed. +@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically + allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate + instance during its own lifetime for efficiency. +*/ +template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> +class MultiDelegate : public delegate::detail::MultiDelegate +{ +public: + using delegate::detail::MultiDelegate::MultiDelegate; +}; + +#endif // __MULTIDELEGATE_H diff --git a/lib/EspSoftwareSerial/src/circular_queue/circular_queue.h b/lib/EspSoftwareSerial/src/circular_queue/circular_queue.h new file mode 100644 index 0000000..dc5c0d2 --- /dev/null +++ b/lib/EspSoftwareSerial/src/circular_queue/circular_queue.h @@ -0,0 +1,393 @@ +/* +circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights 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 __circular_queue_h +#define __circular_queue_h + +#ifdef ARDUINO +#include +#endif + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +#include +#include +#include +#include "Delegate.h" +using std::min; +#else +#include "ghostl.h" +#endif + +#if !defined(ESP32) && !defined(ESP8266) +#define IRAM_ATTR +#endif + +/*! + @brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producer and consumer for the available(), peek(), + pop(), and push() type functions. +*/ +template< typename T, typename ForEachArg = void > +class circular_queue +{ +public: + /*! + @brief Constructs a valid, but zero-capacity dummy queue. + */ + circular_queue() : m_bufSize(1) + { + m_inPos.store(0); + m_outPos.store(0); + } + /*! + @brief Constructs a queue of the given maximum capacity. + */ + circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize]) + { + m_inPos.store(0); + m_outPos.store(0); + } + circular_queue(circular_queue&& cq) : + m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load()) + {} + ~circular_queue() + { + m_buffer.reset(); + } + circular_queue(const circular_queue&) = delete; + circular_queue& operator=(circular_queue&& cq) + { + m_bufSize = cq.m_bufSize; + m_buffer = cq.m_buffer; + m_inPos.store(cq.m_inPos.load()); + m_outPos.store(cq.m_outPos.load()); + } + circular_queue& operator=(const circular_queue&) = delete; + + /*! + @brief Get the numer of elements the queue can hold at most. + */ + size_t capacity() const + { + return m_bufSize - 1; + } + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free and concurrent producer or consumer access + will lead to corruption. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap); + + /*! + @brief Discard all data in the queue. + */ + void flush() + { + m_outPos.store(m_inPos.load()); + } + + /*! + @brief Get a snapshot number of elements that can be retrieved by pop. + */ + size_t available() const + { + int avail = static_cast(m_inPos.load() - m_outPos.load()); + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Get the remaining free elementes for pushing. + */ + size_t available_for_push() const + { + int avail = static_cast(m_outPos.load() - m_inPos.load()) - 1; + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Peek at the next element pop will return without removing it from the queue. + @return An rvalue copy of the next element that can be popped. If the queue is empty, + return an rvalue copy of the element that is pending the next push. + */ + T peek() const + { + const auto outPos = m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[outPos]; + } + + /*! + @brief Peek at the next pending input value. + @return A reference to the next element that can be pushed. + */ + inline T& IRAM_ATTR pushpeek() __attribute__((always_inline)) + { + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[inPos]; + } + + /*! + @brief Release the next pending input value, accessible by pushpeek(), into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + inline bool IRAM_ATTR push() __attribute__((always_inline)) + { + const auto inPos = m_inPos.load(std::memory_order_acquire); + const size_t next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_inPos.store(next, std::memory_order_release); + return true; + } + + /*! + @brief Move the rvalue parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + inline bool IRAM_ATTR push(T&& val) __attribute__((always_inline)) + { + const auto inPos = m_inPos.load(std::memory_order_acquire); + const size_t next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_buffer[inPos] = std::move(val); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return true; + } + + /*! + @brief Push a copy of the parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + inline bool IRAM_ATTR push(const T& val) __attribute__((always_inline)) + { + T v(val); + return push(std::move(v)); + } + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size); +#endif + + /*! + @brief Pop the next available element from the queue. + @return An rvalue copy of the popped element, or a default + value of type T if the queue is empty. + */ + T pop(); + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + /*! + @brief Pop multiple elements in ordered sequence from the queue to a buffer. + If buffer is nullptr, simply discards up to size elements from the queue. + @return The number of elements actually popped from the queue to + buffer. + */ + size_t pop_n(T* buffer, size_t size); +#endif + + /*! + @brief Iterate over and remove each available element from queue, + calling back fun with an rvalue reference of every single element. + */ +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + void for_each(const Delegate& fun); +#else + void for_each(Delegate fun); +#endif + + /*! + @brief In reverse order, iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + bool for_each_rev_requeue(const Delegate& fun); +#else + bool for_each_rev_requeue(Delegate fun); +#endif + +protected: + const T defaultValue = {}; + size_t m_bufSize; +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + std::unique_ptr m_buffer; +#else + std::unique_ptr m_buffer; +#endif + std::atomic m_inPos; + std::atomic m_outPos; +}; + +template< typename T, typename ForEachArg > +bool circular_queue::capacity(const size_t cap) +{ + if (cap + 1 == m_bufSize) return true; + else if (available() > cap) return false; + std::unique_ptr buffer(new T[cap + 1]); + const auto available = pop_n(buffer, cap); + m_buffer.reset(buffer); + m_bufSize = cap + 1; + std::atomic_thread_fence(std::memory_order_release); + m_inPos.store(available, std::memory_order_relaxed); + m_outPos.store(0, std::memory_order_release); + return true; +} + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +template< typename T, typename ForEachArg > +size_t circular_queue::push_n(const T* buffer, size_t size) +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const auto outPos = m_outPos.load(std::memory_order_relaxed); + + size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos; + blockSize = min(size, blockSize); + if (!blockSize) return 0; + int next = (inPos + blockSize) % m_bufSize; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto dest = m_buffer.get() + inPos; + std::copy_n(std::make_move_iterator(buffer), blockSize, dest); + size = min(size - blockSize, outPos > 1 ? static_cast(outPos - next - 1) : 0); + next += size; + dest = m_buffer.get(); + std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return blockSize + size; +} +#endif + +template< typename T, typename ForEachArg > +T circular_queue::pop() +{ + const auto outPos = m_outPos.load(std::memory_order_acquire); + if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto val = std::move(m_buffer[outPos]); + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release); + return val; +} + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +template< typename T, typename ForEachArg > +size_t circular_queue::pop_n(T* buffer, size_t size) { + size_t avail = size = min(size, available()); + if (!avail) return 0; + const auto outPos = m_outPos.load(std::memory_order_acquire); + size_t n = min(avail, static_cast(m_bufSize - outPos)); + + std::atomic_thread_fence(std::memory_order_acquire); + + if (buffer) { + buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer); + avail -= n; + std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer); + } + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release); + return size; +} +#endif + +template< typename T, typename ForEachArg > +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +void circular_queue::for_each(const Delegate& fun) +#else +void circular_queue::for_each(Delegate fun) +#endif +{ + auto outPos = m_outPos.load(std::memory_order_acquire); + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + while (outPos != inPos) + { + fun(std::move(m_buffer[outPos])); + std::atomic_thread_fence(std::memory_order_release); + outPos = (outPos + 1) % m_bufSize; + m_outPos.store(outPos, std::memory_order_release); + } +} + +template< typename T, typename ForEachArg > +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +bool circular_queue::for_each_rev_requeue(const Delegate& fun) +#else +bool circular_queue::for_each_rev_requeue(Delegate fun) +#endif +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + auto pos = inPos0; + auto outPos1 = inPos0; + const auto posDecr = circular_queue::m_bufSize - 1; + do { + pos = (pos + posDecr) % circular_queue::m_bufSize; + T&& val = std::move(circular_queue::m_buffer[pos]); + if (fun(val)) + { + outPos1 = (outPos1 + posDecr) % circular_queue::m_bufSize; + if (outPos1 != pos) circular_queue::m_buffer[outPos1] = std::move(val); + } + } while (pos != outPos); + circular_queue::m_outPos.store(outPos1, std::memory_order_release); + return true; +} + +#endif // __circular_queue_h diff --git a/lib/EspSoftwareSerial/src/circular_queue/circular_queue_mp.h b/lib/EspSoftwareSerial/src/circular_queue/circular_queue_mp.h new file mode 100644 index 0000000..ba37689 --- /dev/null +++ b/lib/EspSoftwareSerial/src/circular_queue/circular_queue_mp.h @@ -0,0 +1,200 @@ +/* +circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights 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 __circular_queue_mp_h +#define __circular_queue_mp_h + +#include "circular_queue.h" + +#ifdef ESP8266 +#include "interrupts.h" +#else +#include +#endif + +/*! + @brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producers and consumer for the available(), peek(), + pop(), and push() type functions, but is guarded to safely allow only a single producer + at any instant. +*/ +template< typename T, typename ForEachArg = void > +class circular_queue_mp : protected circular_queue +{ +public: + circular_queue_mp() = default; + circular_queue_mp(const size_t capacity) : circular_queue(capacity) + {} + circular_queue_mp(circular_queue&& cq) : circular_queue(std::move(cq)) + {} + using circular_queue::operator=; + using circular_queue::capacity; + using circular_queue::flush; + using circular_queue::available; + using circular_queue::available_for_push; + using circular_queue::peek; + using circular_queue::pop; + using circular_queue::pop_n; + using circular_queue::for_each; + using circular_queue::for_each_rev_requeue; + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free, but safe, concurrent producer or consumer access + is guarded. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::capacity(cap); + } + + bool IRAM_ATTR push() = delete; + + /*! + @brief Move the rvalue parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(std::move(val)); + } + + /*! + @brief Push a copy of the parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(val); + } + + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. This is guarded for + multiple producers, push_n() is atomic. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push_n(buffer, size); + } + + /*! + @brief Pops the next available element from the queue, requeues + it immediately. + @return A reference to the just requeued element, or the default + value of type T if the queue is empty. + */ + T& pop_requeue(); + + /*! + @brief Iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ + bool for_each_requeue(const Delegate& fun); + +#ifndef ESP8266 +protected: + std::mutex m_pushMtx; +#endif +}; + +template< typename T, typename ForEachArg > +T& circular_queue_mp::pop_requeue() +{ +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + const auto outPos = circular_queue::m_outPos.load(std::memory_order_acquire); + const auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (inPos == outPos) return circular_queue::defaultValue; + T& val = circular_queue::m_buffer[inPos] = std::move(circular_queue::m_buffer[outPos]); + const auto bufSize = circular_queue::m_bufSize; + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_outPos.store((outPos + 1) % bufSize, std::memory_order_relaxed); + circular_queue::m_inPos.store((inPos + 1) % bufSize, std::memory_order_release); + return val; +} + +template< typename T, typename ForEachArg > +bool circular_queue_mp::for_each_requeue(const Delegate& fun) +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + do { + T&& val = std::move(circular_queue::m_buffer[outPos]); + if (fun(val)) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + std::atomic_thread_fence(std::memory_order_release); + auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + circular_queue::m_buffer[inPos] = std::move(val); + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_inPos.store((inPos + 1) % circular_queue::m_bufSize, std::memory_order_release); + } + else + { + std::atomic_thread_fence(std::memory_order_release); + } + outPos = (outPos + 1) % circular_queue::m_bufSize; + circular_queue::m_outPos.store(outPos, std::memory_order_release); + } while (outPos != inPos0); + return true; +} + +#endif // __circular_queue_mp_h diff --git a/lib/EspSoftwareSerial/src/circular_queue/ghostl.h b/lib/EspSoftwareSerial/src/circular_queue/ghostl.h new file mode 100644 index 0000000..50f522c --- /dev/null +++ b/lib/EspSoftwareSerial/src/circular_queue/ghostl.h @@ -0,0 +1,94 @@ +/* +ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell + that allows building some Arduino ESP8266/ESP32 + libraries on Aruduino AVR. +Copyright (c) 2019 Dirk O. Kaar. All rights 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 __ghostl_h +#define __ghostl_h + +#if defined(ARDUINO_ARCH_SAMD) +#include +#endif + +using size_t = decltype(sizeof(char)); + +namespace std +{ +#if !defined(ARDUINO_ARCH_SAMD) + typedef enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_seq_cst + } memory_order; + template< typename T > class atomic { + private: + T value; + public: + atomic() {} + atomic(T desired) { value = desired; } + void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; } + T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; } + }; + inline void atomic_thread_fence(std::memory_order order) noexcept {} + template< typename T > T&& move(T& t) noexcept { return static_cast(t); } +#endif + + template< typename T, size_t long N > struct array + { + T _M_elems[N]; + decltype(sizeof(0)) size() const { return N; } + T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; } + const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; } + }; + + template< typename T > class unique_ptr + { + public: + using pointer = T*; + unique_ptr() noexcept : ptr(nullptr) {} + unique_ptr(pointer p) : ptr(p) {} + pointer operator->() const noexcept { return ptr; } + T& operator[](decltype(sizeof(0)) i) const { return ptr[i]; } + void reset(pointer p = pointer()) noexcept + { + delete ptr; + ptr = p; + } + T& operator*() const { return *ptr; } + private: + pointer ptr; + }; + + template< typename T > using function = T*; + using nullptr_t = decltype(nullptr); + + template + struct identity { + typedef T type; + }; + + template + inline T&& forward(typename identity::type& t) noexcept + { + return static_cast::type&&>(t); + } +} + +#endif // __ghostl_h diff --git a/lib/OneWire/.piopm b/lib/OneWire/.piopm new file mode 100644 index 0000000..8c538b8 --- /dev/null +++ b/lib/OneWire/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "OneWire", "version": "2.3.8", "spec": {"owner": "paulstoffregen", "id": 1, "name": "OneWire", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/OneWire/OneWire.cpp b/lib/OneWire/OneWire.cpp new file mode 100644 index 0000000..cb0d670 --- /dev/null +++ b/lib/OneWire/OneWire.cpp @@ -0,0 +1,603 @@ +/* +Copyright (c) 2007, Jim Studt (original old version - many contributors since) + +The latest version of this library may be found at: + http://www.pjrc.com/teensy/td_libs_OneWire.html + +OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since +January 2010. + +DO NOT EMAIL for technical support, especially not for ESP chips! +All project support questions must be posted on public forums +relevant to the board or chips used. If using Arduino, post on +Arduino's forum. If using ESP, post on the ESP community forums. +There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL! + +Github's issue tracker for OneWire should be used only to report +specific bugs. DO NOT request project support via Github. All +project and tech support questions must be posted on forums, not +github issues. If you experience a problem and you are not +absolutely sure it's an issue with the library, ask on a forum +first. Only use github to report issues after experts have +confirmed the issue is with OneWire rather than your project. + +Back in 2010, OneWire was in need of many bug fixes, but had +been abandoned the original author (Jim Studt). None of the known +contributors were interested in maintaining OneWire. Paul typically +works on OneWire every 6 to 12 months. Patches usually wait that +long. If anyone is interested in more actively maintaining OneWire, +please contact Paul (this is pretty much the only reason to use +private email about OneWire). + +OneWire is now very mature code. No changes other than adding +definitions for newer hardware support are anticipated. + + ESP32 mods authored by stickbreaker: + @stickbreaker 30APR2018 add IRAM_ATTR to read_bit() write_bit() to solve ICache miss timing failure. + thanks @everslick re: https://github.com/espressif/arduino-esp32/issues/1335 + Altered by garyd9 for clean merge with Paul Stoffregen's source + +Version 2.3: + Unknown chip fallback mode, Roger Clark + Teensy-LC compatibility, Paul Stoffregen + Search bug fix, Love Nystrom + +Version 2.2: + Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com + Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 + Fix DS18B20 example negative temperature + Fix DS18B20 example's low res modes, Ken Butcher + Improve reset timing, Mark Tillotson + Add const qualifiers, Bertrik Sikken + Add initial value input to crc16, Bertrik Sikken + Add target_search() function, Scott Roberts + +Version 2.1: + Arduino 1.0 compatibility, Paul Stoffregen + Improve temperature example, Paul Stoffregen + DS250x_PROM example, Guillermo Lovato + PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com + Improvements from Glenn Trewitt: + - crc16() now works + - check_crc16() does all of calculation/checking work. + - Added read_bytes() and write_bytes(), to reduce tedious loops. + - Added ds2408 example. + Delete very old, out-of-date readme file (info is here) + +Version 2.0: Modifications by Paul Stoffregen, January 2010: +http://www.pjrc.com/teensy/td_libs_OneWire.html + Search fix from Robin James + http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + Use direct optimized I/O in all cases + Disable interrupts during timing critical sections + (this solves many random communication errors) + Disable interrupts during read-modify-write I/O + Reduce RAM consumption by eliminating unnecessary + variables and trimming many to 8 bits + Optimize both crc8 - table version moved to flash + +Modified to work with larger numbers of devices - avoids loop. +Tested in Arduino 11 alpha with 12 sensors. +26 Sept 2008 -- Robin James +http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + +Updated to work with arduino-0008 and to include skip() as of +2007/07/06. --RJL20 + +Modified to calculate the 8-bit CRC directly, avoiding the need for +the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 +-- Tom Pollard, Jan 23, 2008 + +Jim Studt's original library was modified by Josh Larios. + +Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Much of the code was inspired by Derek Yerger's code, though I don't +think much of that remains. In any event that was.. + (copyleft) 2006 by Derek Yerger - Free to distribute freely. + +The CRC code was excerpted and inspired by the Dallas Semiconductor +sample code bearing this copyright. +//--------------------------------------------------------------------------- +// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name of Dallas Semiconductor +// shall not be used except as stated in the Dallas Semiconductor +// Branding Policy. +//-------------------------------------------------------------------------- +*/ + +#include +#include "OneWire.h" +#include "util/OneWire_direct_gpio.h" + +#ifdef ARDUINO_ARCH_ESP32 +// due to the dual core esp32, a critical section works better than disabling interrupts +# define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux) +# define interrupts() portEXIT_CRITICAL(&mux);} +// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html +# define CRIT_TIMING IRAM_ATTR +#else +# define CRIT_TIMING +#endif + + +void OneWire::begin(uint8_t pin) +{ + pinMode(pin, INPUT); + bitmask = PIN_TO_BITMASK(pin); + baseReg = PIN_TO_BASEREG(pin); +#if ONEWIRE_SEARCH + reset_search(); +#endif +} + + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return a 0; +// +// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// +uint8_t CRIT_TIMING OneWire::reset(void) +{ + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + __attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; + uint8_t r; + uint8_t retries = 125; + + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); + interrupts(); + // wait until the wire is high... just in case + do { + if (--retries == 0) return 0; + delayMicroseconds(2); + } while ( !DIRECT_READ(reg, mask)); + + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + interrupts(); + delayMicroseconds(480); + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); // allow it to float + delayMicroseconds(70); + r = !DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(410); + return r; +} + +// +// Write a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +void CRIT_TIMING OneWire::write_bit(uint8_t v) +{ + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + __attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; + + if (v & 1) { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(10); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(55); + } else { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(65); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(5); + } +} + +// +// Read a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +uint8_t CRIT_TIMING OneWire::read_bit(void) +{ + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + __attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; + uint8_t r; + + noInterrupts(); + DIRECT_MODE_OUTPUT(reg, mask); + DIRECT_WRITE_LOW(reg, mask); + delayMicroseconds(3); + DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise + delayMicroseconds(10); + r = DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(53); + return r; +} + +// +// Write a byte. The writing code uses the active drivers to raise the +// pin high, if you need power after the write (e.g. DS18S20 in +// parasite power mode) then set 'power' to 1, otherwise the pin will +// go tri-state at the end of the write to avoid heating in a short or +// other mishap. +// +void OneWire::write(uint8_t v, uint8_t power /* = 0 */) { + uint8_t bitMask; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + OneWire::write_bit( (bitMask & v)?1:0); + } + if ( !power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) { + for (uint16_t i = 0 ; i < count ; i++) + write(buf[i]); + if (!power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +// +// Read a byte +// +uint8_t OneWire::read() { + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if ( OneWire::read_bit()) r |= bitMask; + } + return r; +} + +void OneWire::read_bytes(uint8_t *buf, uint16_t count) { + for (uint16_t i = 0 ; i < count ; i++) + buf[i] = read(); +} + +// +// Do a ROM select +// +void OneWire::select(const uint8_t rom[8]) +{ + uint8_t i; + + write(0x55); // Choose ROM + + for (i = 0; i < 8; i++) write(rom[i]); +} + +// +// Do a ROM skip +// +void OneWire::skip() +{ + write(0xCC); // Skip ROM +} + +void OneWire::depower() +{ + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + interrupts(); +} + +#if ONEWIRE_SEARCH + +// +// You need to use this function to start a search again from the beginning. +// You do not need to do it for the first search, though you could. +// +void OneWire::reset_search() +{ + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + for(int i = 7; ; i--) { + ROM_NO[i] = 0; + if ( i == 0) break; + } +} + +// Setup the search to find the device type 'family_code' on the next call +// to search(*newAddr) if it is present. +// +void OneWire::target_search(uint8_t family_code) +{ + // set the search state to find SearchFamily type devices + ROM_NO[0] = family_code; + for (uint8_t i = 1; i < 8; i++) + ROM_NO[i] = 0; + LastDiscrepancy = 64; + LastFamilyDiscrepancy = 0; + LastDeviceFlag = false; +} + +// +// Perform a search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return TRUE : device found, ROM number in ROM_NO buffer +// FALSE : device not found, end of search +// +bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */) +{ + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number; + bool search_result; + uint8_t id_bit, cmp_id_bit; + + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = false; + + // if the last call was not the last one + if (!LastDeviceFlag) { + // 1-Wire reset + if (!reset()) { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + return false; + } + + // issue the search command + if (search_mode == true) { + write(0xF0); // NORMAL SEARCH + } else { + write(0xEC); // CONDITIONAL SEARCH + } + + // loop to do the search + do + { + // read a bit and its complement + id_bit = read_bit(); + cmp_id_bit = read_bit(); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) { + break; + } else { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) { + search_direction = id_bit; // bit write value for search + } else { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) { + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + } else { + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + } + // if 0 was picked then record its position in LastZero + if (search_direction == 0) { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) + LastFamilyDiscrepancy = last_zero; + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) + ROM_NO[rom_byte_number] |= rom_byte_mask; + else + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + write_bit(search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } + while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) { + LastDeviceFlag = true; + } + search_result = true; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !ROM_NO[0]) { + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + search_result = false; + } else { + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; + } + return search_result; + } + +#endif + +#if ONEWIRE_CRC +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#if ONEWIRE_CRC8_TABLE +// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0 +// Tiny 2x16 entry CRC table created by Arjen Lentz +// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table +static const uint8_t PROGMEM dscrc2x16_table[] = { + 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, + 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, + 0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8, + 0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74 +}; + +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (Use tiny 2x16 entry CRC table) +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + crc = *addr++ ^ crc; // just re-using crc as intermediate + crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^ + pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f)); + } + + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but a little smaller, than the lookup table. +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { +#if defined(__AVR__) + crc = _crc_ibutton_update(crc, *addr++); +#else + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } +#endif + } + return crc; +} +#endif + +#if ONEWIRE_CRC16 +bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc) +{ + crc = ~crc16(input, len, crc); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) +{ +#if defined(__AVR__) + for (uint16_t i = 0 ; i < len ; i++) { + crc = _crc16_update(crc, input[i]); + } +#else + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + for (uint16_t i = 0 ; i < len ; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } +#endif + return crc; +} +#endif + +#endif + +// undef defines for no particular reason +#ifdef ARDUINO_ARCH_ESP32 +# undef noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux) +# undef interrupts() portEXIT_CRITICAL(&mux);} +#endif +// for info on this, search "IRAM_ATTR" at https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/general-notes.html +#undef CRIT_TIMING diff --git a/lib/OneWire/OneWire.h b/lib/OneWire/OneWire.h new file mode 100644 index 0000000..a7bfab7 --- /dev/null +++ b/lib/OneWire/OneWire.h @@ -0,0 +1,182 @@ +#ifndef OneWire_h +#define OneWire_h + +#ifdef __cplusplus + +#include + +#if defined(__AVR__) +#include +#endif + +#if ARDUINO >= 100 +#include // for delayMicroseconds, digitalPinToBitMask, etc +#else +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc +#endif + +// You can exclude certain features from OneWire. In theory, this +// might save some space. In practice, the compiler automatically +// removes unused code (technically, the linker, using -fdata-sections +// and -ffunction-sections when compiling, and Wl,--gc-sections +// when linking), so most of these will not result in any code size +// reduction. Well, unless you try to use the missing features +// and redesign your program to not need them! ONEWIRE_CRC8_TABLE +// is the exception, because it selects a fast but large algorithm +// or a small but slow algorithm. + +// you can exclude onewire_search by defining that to 0 +#ifndef ONEWIRE_SEARCH +#define ONEWIRE_SEARCH 1 +#endif + +// You can exclude CRC checks altogether by defining this to 0 +#ifndef ONEWIRE_CRC +#define ONEWIRE_CRC 1 +#endif + +// Select the table-lookup method of computing the 8-bit CRC +// by setting this to 1. The lookup table enlarges code size by +// about 250 bytes. It does NOT consume RAM (but did in very +// old versions of OneWire). If you disable this, a slower +// but very compact algorithm is used. +#ifndef ONEWIRE_CRC8_TABLE +#define ONEWIRE_CRC8_TABLE 1 +#endif + +// You can allow 16-bit CRC checks by defining this to 1 +// (Note that ONEWIRE_CRC must also be 1.) +#ifndef ONEWIRE_CRC16 +#define ONEWIRE_CRC16 1 +#endif + +// Board-specific macros for direct GPIO +#include "util/OneWire_direct_regtype.h" + +class OneWire +{ + private: + IO_REG_TYPE bitmask; + volatile IO_REG_TYPE *baseReg; + +#if ONEWIRE_SEARCH + // global search state + unsigned char ROM_NO[8]; + uint8_t LastDiscrepancy; + uint8_t LastFamilyDiscrepancy; + bool LastDeviceFlag; +#endif + + public: + OneWire() { } + OneWire(uint8_t pin) { begin(pin); } + void begin(uint8_t pin); + + // Perform a 1-Wire reset cycle. Returns 1 if a device responds + // with a presence pulse. Returns 0 if there is no device or the + // bus is shorted or otherwise held low for more than 250uS + uint8_t reset(void); + + // Issue a 1-Wire rom select command, you do the reset first. + void select(const uint8_t rom[8]); + + // Issue a 1-Wire rom skip command, to address all on bus. + void skip(void); + + // Write a byte. If 'power' is one then the wire is held high at + // the end for parasitically powered devices. You are responsible + // for eventually depowering it by calling depower() or doing + // another read or write. + void write(uint8_t v, uint8_t power = 0); + + void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); + + // Read a byte. + uint8_t read(void); + + void read_bytes(uint8_t *buf, uint16_t count); + + // Write a bit. The bus is always left powered at the end, see + // note in write() about that. + void write_bit(uint8_t v); + + // Read a bit. + uint8_t read_bit(void); + + // Stop forcing power onto the bus. You only need to do this if + // you used the 'power' flag to write() or used a write_bit() call + // and aren't about to do another read or write. You would rather + // not leave this powered if you don't have to, just in case + // someone shorts your bus. + void depower(void); + +#if ONEWIRE_SEARCH + // Clear the search state so that if will start from the beginning again. + void reset_search(); + + // Setup the search to find the device type 'family_code' on the next call + // to search(*newAddr) if it is present. + void target_search(uint8_t family_code); + + // Look for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are + // no devices, or you have already retrieved all of them. It + // might be a good idea to check the CRC to make sure you didn't + // get garbage. The order is deterministic. You will always get + // the same devices in the same order. + bool search(uint8_t *newAddr, bool search_mode = true); +#endif + +#if ONEWIRE_CRC + // Compute a Dallas Semiconductor 8 bit CRC, these are used in the + // ROM and scratchpad registers. + static uint8_t crc8(const uint8_t *addr, uint8_t len); + +#if ONEWIRE_CRC16 + // Compute the 1-Wire CRC16 and compare it against the received CRC. + // Example usage (reading a DS2408): + // // Put everything in a buffer so we can compute the CRC easily. + // uint8_t buf[13]; + // buf[0] = 0xF0; // Read PIO Registers + // buf[1] = 0x88; // LSB address + // buf[2] = 0x00; // MSB address + // WriteBytes(net, buf, 3); // Write 3 cmd bytes + // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + // if (!CheckCRC16(buf, 11, &buf[11])) { + // // Handle error. + // } + // + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param inverted_crc - The two CRC16 bytes in the received data. + // This should just point into the received data, + // *not* at a 16-bit integer. + // @param crc - The crc starting value (optional) + // @return True, iff the CRC matches. + static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0); + + // Compute a Dallas Semiconductor 16 bit CRC. This is required to check + // the integrity of data received from many 1-Wire devices. Note that the + // CRC computed here is *not* what you'll get from the 1-Wire network, + // for two reasons: + // 1) The CRC is transmitted bitwise inverted. + // 2) Depending on the endian-ness of your processor, the binary + // representation of the two-byte return value may have a different + // byte order than the two bytes you get from 1-Wire. + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param crc - The crc starting value (optional) + // @return The CRC16, as defined by Dallas Semiconductor. + static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0); +#endif +#endif +}; + +// Prevent this name from leaking into Arduino sketches +#ifdef IO_REG_TYPE +#undef IO_REG_TYPE +#endif + +#endif // __cplusplus +#endif // OneWire_h diff --git a/lib/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.ino b/lib/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.ino new file mode 100644 index 0000000..34a5e44 --- /dev/null +++ b/lib/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.ino @@ -0,0 +1,112 @@ +#include + +// OneWire DS18S20, DS18B20, DS1822 Temperature Example +// +// http://www.pjrc.com/teensy/td_libs_OneWire.html +// +// The DallasTemperature library can do all this work for you! +// https://github.com/milesburton/Arduino-Temperature-Control-Library + +OneWire ds(10); // on pin 10 (a 4.7K resistor is necessary) + +void setup(void) { + Serial.begin(9600); +} + +void loop(void) { + byte i; + byte present = 0; + byte type_s; + byte data[9]; + byte addr[8]; + float celsius, fahrenheit; + + if ( !ds.search(addr)) { + Serial.println("No more addresses."); + Serial.println(); + ds.reset_search(); + delay(250); + return; + } + + Serial.print("ROM ="); + for( i = 0; i < 8; i++) { + Serial.write(' '); + Serial.print(addr[i], HEX); + } + + if (OneWire::crc8(addr, 7) != addr[7]) { + Serial.println("CRC is not valid!"); + return; + } + Serial.println(); + + // the first ROM byte indicates which chip + switch (addr[0]) { + case 0x10: + Serial.println(" Chip = DS18S20"); // or old DS1820 + type_s = 1; + break; + case 0x28: + Serial.println(" Chip = DS18B20"); + type_s = 0; + break; + case 0x22: + Serial.println(" Chip = DS1822"); + type_s = 0; + break; + default: + Serial.println("Device is not a DS18x20 family device."); + return; + } + + ds.reset(); + ds.select(addr); + ds.write(0x44, 1); // start conversion, with parasite power on at the end + + delay(1000); // maybe 750ms is enough, maybe not + // we might do a ds.depower() here, but the reset will take care of it. + + present = ds.reset(); + ds.select(addr); + ds.write(0xBE); // Read Scratchpad + + Serial.print(" Data = "); + Serial.print(present, HEX); + Serial.print(" "); + for ( i = 0; i < 9; i++) { // we need 9 bytes + data[i] = ds.read(); + Serial.print(data[i], HEX); + Serial.print(" "); + } + Serial.print(" CRC="); + Serial.print(OneWire::crc8(data, 8), HEX); + Serial.println(); + + // Convert the data to actual temperature + // because the result is a 16 bit signed integer, it should + // be stored to an "int16_t" type, which is always 16 bits + // even when compiled on a 32 bit processor. + int16_t raw = (data[1] << 8) | data[0]; + if (type_s) { + raw = raw << 3; // 9 bit resolution default + if (data[7] == 0x10) { + // "count remain" gives full 12 bit resolution + raw = (raw & 0xFFF0) + 12 - data[6]; + } + } else { + byte cfg = (data[4] & 0x60); + // at lower res, the low bits are undefined, so let's zero them + if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms + else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms + else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms + //// default is 12 bit resolution, 750 ms conversion time + } + celsius = (float)raw / 16.0; + fahrenheit = celsius * 1.8 + 32.0; + Serial.print(" Temperature = "); + Serial.print(celsius); + Serial.print(" Celsius, "); + Serial.print(fahrenheit); + Serial.println(" Fahrenheit"); +} diff --git a/lib/OneWire/examples/DS2408_Switch/DS2408_Switch.ino b/lib/OneWire/examples/DS2408_Switch/DS2408_Switch.ino new file mode 100644 index 0000000..b70eda9 --- /dev/null +++ b/lib/OneWire/examples/DS2408_Switch/DS2408_Switch.ino @@ -0,0 +1,74 @@ +#include + +/* + * DS2408 8-Channel Addressable Switch + * + * Writte by Glenn Trewitt, glenn at trewitt dot org + * + * Some notes about the DS2408: + * - Unlike most input/output ports, the DS2408 doesn't have mode bits to + * set whether the pins are input or output. If you issue a read command, + * they're inputs. If you write to them, they're outputs. + * - For reading from a switch, you should use 10K pull-up resisters. + */ + +OneWire net(10); // on pin 10 + + +void PrintBytes(const uint8_t* addr, uint8_t count, bool newline=false) { + for (uint8_t i = 0; i < count; i++) { + Serial.print(addr[i]>>4, HEX); + Serial.print(addr[i]&0x0f, HEX); + } + if (newline) + Serial.println(); +} + + +void setup(void) { + Serial.begin(9600); +} + +void loop(void) { + byte addr[8]; + + if (!net.search(addr)) { + Serial.print("No more addresses.\n"); + net.reset_search(); + delay(1000); + return; + } + + if (OneWire::crc8(addr, 7) != addr[7]) { + Serial.print("CRC is not valid!\n"); + return; + } + + if (addr[0] != 0x29) { + PrintBytes(addr, 8); + Serial.print(" is not a DS2408.\n"); + return; + } + + Serial.print(" Reading DS2408 "); + PrintBytes(addr, 8); + Serial.println(); + + uint8_t buf[13]; // Put everything in the buffer so we can compute CRC easily. + buf[0] = 0xF0; // Read PIO Registers + buf[1] = 0x88; // LSB address + buf[2] = 0x00; // MSB address + net.write_bytes(buf, 3); + net.read_bytes(buf+3, 10); // 3 cmd bytes, 6 data bytes, 2 0xFF, 2 CRC16 + net.reset(); + + if (!OneWire::check_crc16(buf, 11, &buf[11])) { + Serial.print("CRC failure in DS2408 at "); + PrintBytes(addr, 8, true); + return; + } + Serial.print(" DS2408 data = "); + // First 3 bytes contain command, register address. + Serial.println(buf[3], BIN); +} + diff --git a/lib/OneWire/examples/DS250x_PROM/DS250x_PROM.ino b/lib/OneWire/examples/DS250x_PROM/DS250x_PROM.ino new file mode 100644 index 0000000..db95a5e --- /dev/null +++ b/lib/OneWire/examples/DS250x_PROM/DS250x_PROM.ino @@ -0,0 +1,75 @@ +/* +DS250x add-only programmable memory reader w/SKIP ROM. + + The DS250x is a 512/1024bit add-only PROM(you can add data but cannot change the old one) that's used mainly for device identification purposes + like serial number, mfgr data, unique identifiers, etc. It uses the Maxim 1-wire bus. + + This sketch will use the SKIP ROM function that skips the 1-Wire search phase since we only have one device connected in the bus on digital pin 6. + If more than one device is connected to the bus, it will fail. + Sketch will not verify if device connected is from the DS250x family since the skip rom function effectively skips the family-id byte readout. + thus it is possible to run this sketch with any Maxim OneWire device in which case the command CRC will most likely fail. + Sketch will only read the first page of memory(32bits) starting from the lower address(0000h), if more than 1 device is present, then use the sketch with search functions. + Remember to put a 4.7K pullup resistor between pin 6 and +Vcc + + To change the range or ammount of data to read, simply change the data array size, LSB/MSB addresses and for loop iterations + + This example code is in the public domain and is provided AS-IS. + + Built with Arduino 0022 and PJRC OneWire 2.0 library http://www.pjrc.com/teensy/td_libs_OneWire.html + + created by Guillermo Lovato + march/2011 + + */ + +#include +OneWire ds(6); // OneWire bus on digital pin 6 +void setup() { + Serial.begin (9600); +} + +void loop() { + byte i; // This is for the for loops + boolean present; // device present var + byte data[32]; // container for the data from device + byte leemem[3] = { // array with the commands to initiate a read, DS250x devices expect 3 bytes to start a read: command,LSB&MSB adresses + 0xF0 , 0x00 , 0x00 }; // 0xF0 is the Read Data command, followed by 00h 00h as starting address(the beginning, 0000h) + byte ccrc; // Variable to store the command CRC + byte ccrc_calc; + + present = ds.reset(); // OneWire bus reset, always needed to start operation on the bus, returns a 1/TRUE if there's a device present. + ds.skip(); // Skip ROM search + + if (present == true) { // We only try to read the data if there's a device present + Serial.println("DS250x device present"); + ds.write(leemem[0],1); // Read data command, leave ghost power on + ds.write(leemem[1],1); // LSB starting address, leave ghost power on + ds.write(leemem[2],1); // MSB starting address, leave ghost power on + + ccrc = ds.read(); // DS250x generates a CRC for the command we sent, we assign a read slot and store it's value + ccrc_calc = OneWire::crc8(leemem, 3); // We calculate the CRC of the commands we sent using the library function and store it + + if ( ccrc_calc != ccrc) { // Then we compare it to the value the ds250x calculated, if it fails, we print debug messages and abort + Serial.println("Invalid command CRC!"); + Serial.print("Calculated CRC:"); + Serial.println(ccrc_calc,HEX); // HEX makes it easier to observe and compare + Serial.print("DS250x readback CRC:"); + Serial.println(ccrc,HEX); + return; // Since CRC failed, we abort the rest of the loop and start over + } + Serial.println("Data is: "); // For the printout of the data + for ( i = 0; i < 32; i++) { // Now it's time to read the PROM data itself, each page is 32 bytes so we need 32 read commands + data[i] = ds.read(); // we store each read byte to a different position in the data array + Serial.print(data[i]); // printout in ASCII + Serial.print(" "); // blank space + } + Serial.println(); + delay(5000); // Delay so we don't saturate the serial output + } + else { // Nothing is connected in the bus + Serial.println("Nothing connected"); + delay(3000); + } +} + + diff --git a/lib/OneWire/keywords.txt b/lib/OneWire/keywords.txt new file mode 100644 index 0000000..bee5d90 --- /dev/null +++ b/lib/OneWire/keywords.txt @@ -0,0 +1,38 @@ +####################################### +# Syntax Coloring Map For OneWire +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +OneWire KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +reset KEYWORD2 +write_bit KEYWORD2 +read_bit KEYWORD2 +write KEYWORD2 +write_bytes KEYWORD2 +read KEYWORD2 +read_bytes KEYWORD2 +select KEYWORD2 +skip KEYWORD2 +depower KEYWORD2 +reset_search KEYWORD2 +search KEYWORD2 +crc8 KEYWORD2 +crc16 KEYWORD2 +check_crc16 KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/lib/OneWire/library.json b/lib/OneWire/library.json new file mode 100644 index 0000000..c540133 --- /dev/null +++ b/lib/OneWire/library.json @@ -0,0 +1,61 @@ +{ + "name": "OneWire", + "description": "Control 1-Wire protocol (DS18S20, DS18B20, DS2408 and etc)", + "keywords": "onewire, 1-wire, bus, sensor, temperature, ibutton", + "authors": [ + { + "name": "Paul Stoffregen", + "email": "paul@pjrc.com", + "url": "http://www.pjrc.com", + "maintainer": true + }, + { + "name": "Jim Studt" + }, + { + "name": "Tom Pollard", + "email": "pollard@alum.mit.edu" + }, + { + "name": "Derek Yerger" + }, + { + "name": "Josh Larios" + }, + { + "name": "Robin James" + }, + { + "name": "Glenn Trewitt" + }, + { + "name": "Jason Dangel", + "email": "dangel.jason AT gmail.com" + }, + { + "name": "Guillermo Lovato" + }, + { + "name": "Ken Butcher" + }, + { + "name": "Mark Tillotson" + }, + { + "name": "Bertrik Sikken" + }, + { + "name": "Scott Roberts" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/PaulStoffregen/OneWire" + }, + "version": "2.3.8", + "homepage": "https://www.pjrc.com/teensy/td_libs_OneWire.html", + "frameworks": "Arduino", + "examples": [ + "examples/*/*.pde" + ] +} diff --git a/lib/OneWire/library.properties b/lib/OneWire/library.properties new file mode 100644 index 0000000..f4c596f --- /dev/null +++ b/lib/OneWire/library.properties @@ -0,0 +1,10 @@ +name=OneWire +version=2.3.8 +author=Jim Studt, Tom Pollard, Robin James, Glenn Trewitt, Jason Dangel, Guillermo Lovato, Paul Stoffregen, Scott Roberts, Bertrik Sikken, Mark Tillotson, Ken Butcher, Roger Clark, Love Nystrom +maintainer=Paul Stoffregen +sentence=Access 1-wire temperature sensors, memory and other chips. +paragraph= +category=Communication +url=http://www.pjrc.com/teensy/td_libs_OneWire.html +architectures=* + diff --git a/lib/OneWire/util/OneWire_direct_gpio.h b/lib/OneWire/util/OneWire_direct_gpio.h new file mode 100644 index 0000000..3bce942 --- /dev/null +++ b/lib/OneWire/util/OneWire_direct_gpio.h @@ -0,0 +1,518 @@ +#ifndef OneWire_Direct_GPIO_h +#define OneWire_Direct_GPIO_h + +// This header should ONLY be included by OneWire.cpp. These defines are +// meant to be private, used within OneWire.cpp, but not exposed to Arduino +// sketches or other libraries which may include OneWire.h. + +#include + +// Platform specific I/O definitions + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_BASE_ATTR asm("r30") +#define IO_REG_MASK_ATTR +#if defined(__AVR_ATmega4809__) +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask)) +#else +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) +#endif + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) +#define IO_REG_TYPE uint8_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR __attribute__ ((unused)) +#define DIRECT_READ(base, mask) (*((base)+512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) + +#elif defined(__MKL26Z64__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) + +#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask)) + +#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) +// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. +// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 +// If you have trouble with OneWire on Arduino Due, please check the +// status of delayMicroseconds() before reporting a bug in OneWire! +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const uint8_t *)(addr)) +#endif + +#elif defined(__PIC32MX__) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#elif defined(ARDUINO_ARCH_ESP8266) +// Special note: I depend on the ESP community to maintain these definitions and +// submit good pull requests. I can not answer any ESP questions or help you +// resolve any problems related to ESP chips. Please do not contact me and please +// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked +// on ESP community forums. +#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) +#define PIN_TO_BITMASK(pin) (1UL << (pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE mask) +{ + if(mask > 0x8000) + { + GP16FFS(GPFFS_GPIO(16)); + GPC16 = 0; + GP16E &= ~1; + } + else + { + GPE &= ~(mask); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE mask) +{ + if(mask > 0x8000) + { + GP16FFS(GPFFS_GPIO(16)); + GPC16 = 0; + GP16E |= 1; + } + else + { + GPE |= (mask); + } +} +static inline __attribute__((always_inline)) +bool directRead(IO_REG_TYPE mask) +{ + if(mask > 0x8000) + return GP16I & 0x01; + else + return ((GPI & (mask)) ? true : false); +} + +#define DIRECT_READ(base, mask) directRead(mask) +#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) +#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) +#define DIRECT_WRITE_LOW(base, mask) (mask > 0x8000) ? GP16O &= ~1 : (GPOC = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (mask > 0x8000) ? GP16O |= 1 : (GPOS = (mask)) + +#elif defined(ARDUINO_ARCH_ESP32) +#include +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + return (GPIO.in.val >> pin) & 0x1; +#else // plain ESP32 + if ( pin < 32 ) + return (GPIO.in >> pin) & 0x1; + else if ( pin < 46 ) + return (GPIO.in1.val >> (pin - 32)) & 0x1; +#endif + + return 0; +} + +static inline __attribute__((always_inline)) +void directWriteLow(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.out_w1tc.val = ((uint32_t)1 << pin); +#else // plain ESP32 + if ( pin < 32 ) + GPIO.out_w1tc = ((uint32_t)1 << pin); + else if ( pin < 46 ) + GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); +#endif +} + +static inline __attribute__((always_inline)) +void directWriteHigh(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.out_w1ts.val = ((uint32_t)1 << pin); +#else // plain ESP32 + if ( pin < 32 ) + GPIO.out_w1ts = ((uint32_t)1 << pin); + else if ( pin < 46 ) + GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); +#endif +} + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.enable_w1tc.val = ((uint32_t)1 << (pin)); +#else + if ( digitalPinIsValid(pin) ) + { +#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4 + uint32_t rtc_reg(rtc_gpio_desc[pin].reg); + + if ( rtc_reg ) // RTC pins PULL settings + { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } +#endif + // Input + if ( pin < 32 ) + GPIO.enable_w1tc = ((uint32_t)1 << pin); + else + GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); + } +#endif +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE pin) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + GPIO.enable_w1ts.val = ((uint32_t)1 << (pin)); +#else + if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs + { +#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4 + uint32_t rtc_reg(rtc_gpio_desc[pin].reg); + + if ( rtc_reg ) // RTC pins PULL settings + { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } +#endif + // Output + if ( pin < 32 ) + GPIO.enable_w1ts = ((uint32_t)1 << pin); + else // already validated to pins <= 33 + GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); + } +#endif +} + +#define DIRECT_READ(base, pin) directRead(pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) +// https://github.com/PaulStoffregen/OneWire/pull/47 +// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c +#ifdef interrupts +#undef interrupts +#endif +#ifdef noInterrupts +#undef noInterrupts +#endif +#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux) +#define interrupts() portEXIT_CRITICAL(&mux);} +//#warning "ESP32 OneWire testing" + +#elif defined(ARDUINO_ARCH_STM32) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0)) +#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0)) + +#elif defined(__SAMD21G18A__) +#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) + +#elif defined(__ASR6501__) +#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT) +#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT) +#define PORT_OFFSET(port) (PORT_REG_SHFIT * port) +#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin))) + +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin)) +#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin)) +#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin)) +#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ) +#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG) + +#elif defined(RBL_NRF51822) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) +#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) +#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) +#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) +#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) + +#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ + +#include "scss_registers.h" +#include "portable.h" +#include "avr/pgmspace.h" + +#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) +#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) +#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) +#define DIR_OFFSET_SS 0x01 +#define DIR_OFFSET_SOC 0x04 +#define EXT_PORT_OFFSET_SS 0x0A +#define EXT_PORT_OFFSET_SOC 0x50 + +/* GPIO registers base address */ +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) +#define PIN_TO_BITMASK(pin) pin +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + IO_REG_TYPE ret; + if (SS_GPIO == GPIO_TYPE(pin)) { + ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); + } else { + ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); + } + return ((ret >> GPIO_ID(pin)) & 0x01); +} + +static inline __attribute__((always_inline)) +void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); + } +} + +#define DIRECT_READ(base, pin) directRead(base, pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) + +#elif defined(__riscv) + +/* + * Tested on highfive1 + * + * Stable results are achieved operating in the + * two high speed modes of the highfive1. It + * seems to be less reliable in slow mode. + */ +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(IO_REG_TYPE mask) +{ + return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0; +} + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; + + GPIO_REG(GPIO_INPUT_EN) |= mask; + GPIO_REG(GPIO_OUTPUT_EN) &= ~mask; +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; + + GPIO_REG(GPIO_INPUT_EN) &= ~mask; + GPIO_REG(GPIO_OUTPUT_EN) |= mask; +} + +static inline __attribute__((always_inline)) +void directWriteLow(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask; +} + +static inline __attribute__((always_inline)) +void directWriteHigh(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_VAL) |= mask; +} + +#define DIRECT_READ(base, mask) directRead(mask) +#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) +#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) +#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) +#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) + +#elif defined(__MBED__) + +#include "platform/mbed_critical.h" +#include "DigitalInOut.h" +#include +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (new mbed::DigitalInOut(digitalPinToPinName(pin))) +#define IO_REG_TYPE mbed::DigitalInOut* +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) (*pin) +#define DIRECT_WRITE_LOW(base, pin) (*pin = 0) +#define DIRECT_WRITE_HIGH(base, pin) (*pin = 1) +#define DIRECT_MODE_INPUT(base, pin) (pin->input()) +#define DIRECT_MODE_OUTPUT(base, pin) (pin->output()) +#undef interrupts +#undef noInterrupts +#define noInterrupts() osThreadSetPriority(osThreadGetId(), osPriorityRealtime) //core_util_critical_section_enter() +#define interrupts() osThreadSetPriority(osThreadGetId(), osPriorityNormal) //core_util_critical_section_exit() + +#elif defined(ARDUINO_ARCH_MBED_RP2040)|| defined(ARDUINO_ARCH_RP2040) +#define delayMicroseconds(time) busy_wait_us(time) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE unsigned int +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#warning "OneWire. RP2040 in Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite." + +#else +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE unsigned int +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." + +#endif + +#endif diff --git a/lib/OneWire/util/OneWire_direct_regtype.h b/lib/OneWire/util/OneWire_direct_regtype.h new file mode 100644 index 0000000..8e30813 --- /dev/null +++ b/lib/OneWire/util/OneWire_direct_regtype.h @@ -0,0 +1,59 @@ +#ifndef OneWire_Direct_RegType_h +#define OneWire_Direct_RegType_h + +#include + +// Platform specific I/O register type + +#if defined(__AVR__) +#define IO_REG_TYPE uint8_t + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) +#define IO_REG_TYPE uint8_t + +#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) +#define IO_REG_TYPE uint32_t + +#elif defined(__MKL26Z64__) +#define IO_REG_TYPE uint8_t + +#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) +#define IO_REG_TYPE uint32_t + +#elif defined(__PIC32MX__) +#define IO_REG_TYPE uint32_t + +#elif defined(ARDUINO_ARCH_ESP8266) +#define IO_REG_TYPE uint32_t + +#elif defined(ARDUINO_ARCH_ESP32) +#define IO_REG_TYPE uint32_t +#define IO_REG_MASK_ATTR + +#elif defined(ARDUINO_ARCH_STM32) +#define IO_REG_TYPE uint32_t + +#elif defined(__SAMD21G18A__) +#define IO_REG_TYPE uint32_t + +#elif defined(__ASR6501__) +#define IO_REG_TYPE uint32_t + +#elif defined(RBL_NRF51822) +#define IO_REG_TYPE uint32_t + +#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ +#define IO_REG_TYPE uint32_t + +#elif defined(__MBED__) +#include "DigitalInOut.h" +#define IO_REG_TYPE mbed::DigitalInOut* + +#elif defined(__riscv) +#define IO_REG_TYPE uint32_t + +#else +#define IO_REG_TYPE unsigned int + +#endif +#endif diff --git a/lib/PubSubClient/.gitignore b/lib/PubSubClient/.gitignore new file mode 100644 index 0000000..a42cc40 --- /dev/null +++ b/lib/PubSubClient/.gitignore @@ -0,0 +1,5 @@ +tests/bin +.pioenvs +.piolibdeps +.clang_complete +.gcc-flags.json diff --git a/lib/PubSubClient/.piopm b/lib/PubSubClient/.piopm new file mode 100644 index 0000000..47ae377 --- /dev/null +++ b/lib/PubSubClient/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "PubSubClient", "version": "2.8.0", "spec": {"owner": "knolleary", "id": 89, "name": "PubSubClient", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/PubSubClient/.travis.yml b/lib/PubSubClient/.travis.yml new file mode 100644 index 0000000..e7b28cb --- /dev/null +++ b/lib/PubSubClient/.travis.yml @@ -0,0 +1,7 @@ +sudo: false +language: cpp +compiler: + - g++ +script: cd tests && make && make test +os: + - linux diff --git a/lib/PubSubClient/CHANGES.txt b/lib/PubSubClient/CHANGES.txt new file mode 100644 index 0000000..e23d531 --- /dev/null +++ b/lib/PubSubClient/CHANGES.txt @@ -0,0 +1,85 @@ +2.8 + * Add setBufferSize() to override MQTT_MAX_PACKET_SIZE + * Add setKeepAlive() to override MQTT_KEEPALIVE + * Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT + * Added check to prevent subscribe/unsubscribe to empty topics + * Declare wifi mode prior to connect in ESP example + * Use `strnlen` to avoid overruns + * Support pre-connected Client objects + +2.7 + * Fix remaining-length handling to prevent buffer overrun + * Add large-payload API - beginPublish/write/publish/endPublish + * Add yield call to improve reliability on ESP + * Add Clean Session flag to connect options + * Add ESP32 support for functional callback signature + * Various other fixes + +2.4 + * Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely + whilst waiting for inbound data + * Fixed return code when publishing >256 bytes + +2.3 + * Add publish(topic,payload,retained) function + +2.2 + * Change code layout to match Arduino Library reqs + +2.1 + * Add MAX_TRANSFER_SIZE def to chunk messages if needed + * Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE + +2.0 + * Add (and default to) MQTT 3.1.1 support + * Fix PROGMEM handling for Intel Galileo/ESP8266 + * Add overloaded constructors for convenience + * Add chainable setters for server/callback/client/stream + * Add state function to return connack return code + +1.9 + * Do not split MQTT packets over multiple calls to _client->write() + * API change: All constructors now require an instance of Client + to be passed in. + * Fixed example to match 1.8 api changes - dpslwk + * Added username/password support - WilHall + * Added publish_P - publishes messages from PROGMEM - jobytaffey + +1.8 + * KeepAlive interval is configurable in PubSubClient.h + * Maximum packet size is configurable in PubSubClient.h + * API change: Return boolean rather than int from various functions + * API change: Length parameter in message callback changed + from int to unsigned int + * Various internal tidy-ups around types +1.7 + * Improved keepalive handling + * Updated to the Arduino-1.0 API +1.6 + * Added the ability to publish a retained message + +1.5 + * Added default constructor + * Fixed compile error when used with arduino-0021 or later + +1.4 + * Fixed connection lost handling + +1.3 + * Fixed packet reading bug in PubSubClient.readPacket + +1.2 + * Fixed compile error when used with arduino-0016 or later + + +1.1 + * Reduced size of library + * Added support for Will messages + * Clarified licensing - see LICENSE.txt + + +1.0 + * Only Quality of Service (QOS) 0 messaging is supported + * The maximum message size, including header, is 128 bytes + * The keepalive interval is set to 30 seconds + * No support for Will messages diff --git a/lib/PubSubClient/LICENSE.txt b/lib/PubSubClient/LICENSE.txt new file mode 100644 index 0000000..12c1689 --- /dev/null +++ b/lib/PubSubClient/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2020 Nicholas O'Leary + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/PubSubClient/README.md b/lib/PubSubClient/README.md new file mode 100644 index 0000000..2e13171 --- /dev/null +++ b/lib/PubSubClient/README.md @@ -0,0 +1,50 @@ +# Arduino Client for MQTT + +This library provides a client for doing simple publish/subscribe messaging with +a server that supports MQTT. + +## Examples + +The library comes with a number of example sketches. See File > Examples > PubSubClient +within the Arduino application. + +Full API documentation is available here: https://pubsubclient.knolleary.net + +## Limitations + + - It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1. + - The maximum message size, including header, is **256 bytes** by default. This + is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed + by calling `PubSubClient::setBufferSize(size)`. + - The keepalive interval is set to 15 seconds by default. This is configurable + via `MQTT_KEEPALIVE` in `PubSubClient.h` or can be changed by calling + `PubSubClient::setKeepAlive(keepAlive)`. + - The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by + changing value of `MQTT_VERSION` in `PubSubClient.h`. + + +## Compatible Hardware + +The library uses the Arduino Ethernet Client api for interacting with the +underlying network hardware. This means it Just Works with a growing number of +boards and shields, including: + + - Arduino Ethernet + - Arduino Ethernet Shield + - Arduino YUN – use the included `YunClient` in place of `EthernetClient`, and + be sure to do a `Bridge.begin()` first + - Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield, + enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`. + - Sparkfun WiFly Shield – [library](https://github.com/dpslwk/WiFly) + - TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library) + - Intel Galileo/Edison + - ESP8266 + - ESP32 + +The library cannot currently be used with hardware based on the ENC28J60 chip – +such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an +[alternative library](https://github.com/njh/NanodeMQTT) available. + +## License + +This code is released under the MIT License. diff --git a/lib/PubSubClient/examples/mqtt_auth/mqtt_auth.ino b/lib/PubSubClient/examples/mqtt_auth/mqtt_auth.ino new file mode 100644 index 0000000..04bd7bb --- /dev/null +++ b/lib/PubSubClient/examples/mqtt_auth/mqtt_auth.ino @@ -0,0 +1,43 @@ +/* + Basic MQTT example with Authentication + + - connects to an MQTT server, providing username + and password + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +void setup() +{ + Ethernet.begin(mac, ip); + // Note - the default maximum packet size is 128 bytes. If the + // combined length of clientId, username and password exceed this use the + // following to increase the buffer size: + // client.setBufferSize(255); + + if (client.connect("arduinoClient", "testuser", "testpass")) { + client.publish("outTopic","hello world"); + client.subscribe("inTopic"); + } +} + +void loop() +{ + client.loop(); +} diff --git a/lib/PubSubClient/examples/mqtt_basic/mqtt_basic.ino b/lib/PubSubClient/examples/mqtt_basic/mqtt_basic.ino new file mode 100644 index 0000000..f545ade --- /dev/null +++ b/lib/PubSubClient/examples/mqtt_basic/mqtt_basic.ino @@ -0,0 +1,77 @@ +/* + Basic MQTT example + + This sketch demonstrates the basic capabilities of the library. + It connects to an MQTT server then: + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i=0;i Preferences -> Additional Boards Manager URLs": + http://arduino.esp8266.com/stable/package_esp8266com_index.json + - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" + - Select your ESP8266 in "Tools -> Board" +*/ + +#include +#include + +// Update these with values suitable for your network. + +const char* ssid = "........"; +const char* password = "........"; +const char* mqtt_server = "broker.mqtt-dashboard.com"; + +WiFiClient espClient; +PubSubClient client(espClient); +unsigned long lastMsg = 0; +#define MSG_BUFFER_SIZE (50) +char msg[MSG_BUFFER_SIZE]; +int value = 0; + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + randomSeed(micros()); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + } + Serial.println(); + + // Switch on the LED if an 1 was received as first character + if ((char)payload[0] == '1') { + digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level + // but actually the LED is on; this is because + // it is active low on the ESP-01) + } else { + digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH + } + +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP8266Client-"; + clientId += String(random(0xffff), HEX); + // Attempt to connect + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + // Once connected, publish an announcement... + client.publish("outTopic", "hello world"); + // ... and resubscribe + client.subscribe("inTopic"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() { + pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + Serial.begin(115200); + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); + + unsigned long now = millis(); + if (now - lastMsg > 2000) { + lastMsg = now; + ++value; + snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value); + Serial.print("Publish message: "); + Serial.println(msg); + client.publish("outTopic", msg); + } +} diff --git a/lib/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino b/lib/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino new file mode 100644 index 0000000..e048c3e --- /dev/null +++ b/lib/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino @@ -0,0 +1,179 @@ +/* + Long message ESP8266 MQTT example + + This sketch demonstrates sending arbitrarily large messages in combination + with the ESP8266 board/library. + + It connects to an MQTT server then: + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "greenBottles/#", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + - If the sub-topic is a number, it publishes a "greenBottles/lyrics" message + with a payload consisting of the lyrics to "10 green bottles", replacing + 10 with the number given in the sub-topic. + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + + To install the ESP8266 board, (using Arduino 1.6.4+): + - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs": + http://arduino.esp8266.com/stable/package_esp8266com_index.json + - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" + - Select your ESP8266 in "Tools -> Board" + +*/ + +#include +#include + +// Update these with values suitable for your network. + +const char* ssid = "........"; +const char* password = "........"; +const char* mqtt_server = "broker.mqtt-dashboard.com"; + +WiFiClient espClient; +PubSubClient client(espClient); +long lastMsg = 0; +char msg[50]; +int value = 0; + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + randomSeed(micros()); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + } + Serial.println(); + + // Find out how many bottles we should generate lyrics for + String topicStr(topic); + int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic + if (topicStr.indexOf('/') >= 0) { + // The topic includes a '/', we'll try to read the number of bottles from just after that + topicStr.remove(0, topicStr.indexOf('/')+1); + // Now see if there's a number of bottles after the '/' + bottleCount = topicStr.toInt(); + } + + if (bottleCount > 0) { + // Work out how big our resulting message will be + int msgLen = 0; + for (int i = bottleCount; i > 0; i--) { + String numBottles(i); + msgLen += 2*numBottles.length(); + if (i == 1) { + msgLen += 2*String(" green bottle, standing on the wall\n").length(); + } else { + msgLen += 2*String(" green bottles, standing on the wall\n").length(); + } + msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length(); + switch (i) { + case 1: + msgLen += String("no green bottles, standing on the wall\n\n").length(); + break; + case 2: + msgLen += String("1 green bottle, standing on the wall\n\n").length(); + break; + default: + numBottles = i-1; + msgLen += numBottles.length(); + msgLen += String(" green bottles, standing on the wall\n\n").length(); + break; + }; + } + + // Now we can start to publish the message + client.beginPublish("greenBottles/lyrics", msgLen, false); + for (int i = bottleCount; i > 0; i--) { + for (int j = 0; j < 2; j++) { + client.print(i); + if (i == 1) { + client.print(" green bottle, standing on the wall\n"); + } else { + client.print(" green bottles, standing on the wall\n"); + } + } + client.print("And if one green bottle should accidentally fall\nThere'll be "); + switch (i) { + case 1: + client.print("no green bottles, standing on the wall\n\n"); + break; + case 2: + client.print("1 green bottle, standing on the wall\n\n"); + break; + default: + client.print(i-1); + client.print(" green bottles, standing on the wall\n\n"); + break; + }; + } + // Now we're done! + client.endPublish(); + } +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP8266Client-"; + clientId += String(random(0xffff), HEX); + // Attempt to connect + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + // Once connected, publish an announcement... + client.publish("outTopic", "hello world"); + // ... and resubscribe + client.subscribe("greenBottles/#"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() { + pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + Serial.begin(115200); + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); +} diff --git a/lib/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino b/lib/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino new file mode 100644 index 0000000..42afb2a --- /dev/null +++ b/lib/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino @@ -0,0 +1,60 @@ +/* + Publishing in the callback + + - connects to an MQTT server + - subscribes to the topic "inTopic" + - when a message is received, republishes it to "outTopic" + + This example shows how to publish messages within the + callback function. The callback function header needs to + be declared before the PubSubClient constructor and the + actual callback defined afterwards. + This ensures the client reference in the callback function + is valid. + +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +// Callback function header +void callback(char* topic, byte* payload, unsigned int length); + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +// Callback function +void callback(char* topic, byte* payload, unsigned int length) { + // In order to republish this payload, a copy must be made + // as the orignal payload buffer will be overwritten whilst + // constructing the PUBLISH packet. + + // Allocate the correct amount of memory for the payload copy + byte* p = (byte*)malloc(length); + // Copy the payload to the new buffer + memcpy(p,payload,length); + client.publish("outTopic", p, length); + // Free the memory + free(p); +} + +void setup() +{ + + Ethernet.begin(mac, ip); + if (client.connect("arduinoClient")) { + client.publish("outTopic","hello world"); + client.subscribe("inTopic"); + } +} + +void loop() +{ + client.loop(); +} diff --git a/lib/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino b/lib/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino new file mode 100644 index 0000000..080b739 --- /dev/null +++ b/lib/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino @@ -0,0 +1,67 @@ +/* + Reconnecting MQTT example - non-blocking + + This sketch demonstrates how to keep the client connected + using a non-blocking reconnect function. If the client loses + its connection, it attempts to reconnect every 5 seconds + without blocking the main loop. + +*/ + +#include +#include +#include + +// Update these with values suitable for your hardware/network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(ethClient); + +long lastReconnectAttempt = 0; + +boolean reconnect() { + if (client.connect("arduinoClient")) { + // Once connected, publish an announcement... + client.publish("outTopic","hello world"); + // ... and resubscribe + client.subscribe("inTopic"); + } + return client.connected(); +} + +void setup() +{ + client.setServer(server, 1883); + client.setCallback(callback); + + Ethernet.begin(mac, ip); + delay(1500); + lastReconnectAttempt = 0; +} + + +void loop() +{ + if (!client.connected()) { + long now = millis(); + if (now - lastReconnectAttempt > 5000) { + lastReconnectAttempt = now; + // Attempt to reconnect + if (reconnect()) { + lastReconnectAttempt = 0; + } + } + } else { + // Client connected + + client.loop(); + } + +} diff --git a/lib/PubSubClient/examples/mqtt_stream/mqtt_stream.ino b/lib/PubSubClient/examples/mqtt_stream/mqtt_stream.ino new file mode 100644 index 0000000..67c2287 --- /dev/null +++ b/lib/PubSubClient/examples/mqtt_stream/mqtt_stream.ino @@ -0,0 +1,57 @@ +/* + Example of using a Stream object to store the message payload + + Uses SRAM library: https://github.com/ennui2342/arduino-sram + but could use any Stream based class such as SD + + - connects to an MQTT server + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +SRAM sram(4, SRAM_1024); + +void callback(char* topic, byte* payload, unsigned int length) { + sram.seek(1); + + // do something with the message + for(uint8_t i=0; i +maintainer=Nick O'Leary +sentence=A client library for MQTT messaging. +paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000. +category=Communication +url=http://pubsubclient.knolleary.net +architectures=* diff --git a/lib/PubSubClient/src/PubSubClient.cpp b/lib/PubSubClient/src/PubSubClient.cpp new file mode 100644 index 0000000..2b48d2b --- /dev/null +++ b/lib/PubSubClient/src/PubSubClient.cpp @@ -0,0 +1,769 @@ +/* + + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#include "PubSubClient.h" +#include "Arduino.h" + +PubSubClient::PubSubClient() { + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + setCallback(NULL); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::~PubSubClient() { + free(this->buffer); +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { + if (!connected()) { + int result = 0; + + + if(_client->connected()) { + result = 1; + } else { + if (domain != NULL) { + result = _client->connect(this->domain, this->port); + } else { + result = _client->connect(this->ip, this->port); + } + } + + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0;jbuffer[length++] = d[j]; + } + + uint8_t v; + if (willTopic) { + v = 0x04|(willQos<<3)|(willRetain<<5); + } else { + v = 0x00; + } + if (cleanSession) { + v = v|0x02; + } + + if(user != NULL) { + v = v|0x80; + + if(pass != NULL) { + v = v|(0x80>>1); + } + } + this->buffer[length++] = v; + + this->buffer[length++] = ((this->keepAlive) >> 8); + this->buffer[length++] = ((this->keepAlive) & 0xFF); + + CHECK_STRING_LENGTH(length,id) + length = writeString(id,this->buffer,length); + if (willTopic) { + CHECK_STRING_LENGTH(length,willTopic) + length = writeString(willTopic,this->buffer,length); + CHECK_STRING_LENGTH(length,willMessage) + length = writeString(willMessage,this->buffer,length); + } + + if(user != NULL) { + CHECK_STRING_LENGTH(length,user) + length = writeString(user,this->buffer,length); + if(pass != NULL) { + CHECK_STRING_LENGTH(length,pass) + length = writeString(pass,this->buffer,length); + } + } + + write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE); + + lastInActivity = lastOutActivity = millis(); + + while (!_client->available()) { + unsigned long t = millis(); + if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint32_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; + } else { + _state = buffer[3]; + } + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; + } + return false; + } + return true; +} + +// reads a byte into result +boolean PubSubClient::readByte(uint8_t * result) { + uint32_t previousMillis = millis(); + while(!_client->available()) { + yield(); + uint32_t currentMillis = millis(); + if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){ + return false; + } + } + *result = _client->read(); + return true; +} + +// reads a byte into result[*index] and increments index +boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ + uint16_t current_index = *index; + uint8_t * write_address = &(result[current_index]); + if(readByte(write_address)){ + *index = current_index + 1; + return true; + } + return false; +} + +uint32_t PubSubClient::readPacket(uint8_t* lengthLength) { + uint16_t len = 0; + if(!readByte(this->buffer, &len)) return 0; + bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint32_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint32_t start = 0; + + do { + if (len == 5) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; + } + if(!readByte(&digit)) return 0; + this->buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier <<=7; //multiplier *= 128 + } while ((digit & 128) != 0); + *lengthLength = len-1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if(!readByte(this->buffer, &len)) return 0; + if(!readByte(this->buffer, &len)) return 0; + skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2]; + start = 2; + if (this->buffer[0]&MQTTQOS1) { + // skip message id + skip += 2; + } + } + uint32_t idx = len; + + for (uint32_t i = start;istream) { + if (isPublish && idx-*lengthLength-2>skip) { + this->stream->write(digit); + } + } + + if (len < this->bufferSize) { + this->buffer[len] = digit; + len++; + } + idx++; + } + + if (!this->stream && idx > this->bufferSize) { + len = 0; // This will cause the packet to be ignored. + } + return len; +} + +boolean PubSubClient::loop() { + if (connected()) { + unsigned long t = millis(); + if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + this->buffer[0] = MQTTPINGREQ; + this->buffer[1] = 0; + _client->write(this->buffer,2); + lastOutActivity = t; + lastInActivity = t; + pingOutstanding = true; + } + } + if (_client->available()) { + uint8_t llen; + uint16_t len = readPacket(&llen); + uint16_t msgId = 0; + uint8_t *payload; + if (len > 0) { + lastInActivity = t; + uint8_t type = this->buffer[0]&0xF0; + if (type == MQTTPUBLISH) { + if (callback) { + uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */ + memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */ + this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) this->buffer+llen+2; + // msgId only present for QOS>0 + if ((this->buffer[0]&0x06) == MQTTQOS1) { + msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1]; + payload = this->buffer+llen+3+tl+2; + callback(topic,payload,len-llen-3-tl-2); + + this->buffer[0] = MQTTPUBACK; + this->buffer[1] = 2; + this->buffer[2] = (msgId >> 8); + this->buffer[3] = (msgId & 0xFF); + _client->write(this->buffer,4); + lastOutActivity = t; + + } else { + payload = this->buffer+llen+3+tl; + callback(topic,payload,len-llen-3-tl); + } + } + } else if (type == MQTTPINGREQ) { + this->buffer[0] = MQTTPINGRESP; + this->buffer[1] = 0; + _client->write(this->buffer,2); + } else if (type == MQTTPINGRESP) { + pingOutstanding = false; + } + } else if (!connected()) { + // readPacket has closed the connection + return false; + } + } + return true; + } + return false; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + if (connected()) { + if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,this->buffer,length); + + // Add payload + uint16_t i; + for (i=0;ibuffer[length++] = payload[i]; + } + + // Write the header + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) { + return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); +} + +boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + uint8_t llen = 0; + uint8_t digit; + unsigned int rc = 0; + uint16_t tlen; + unsigned int pos = 0; + unsigned int i; + uint8_t header; + unsigned int len; + int expectedLength; + + if (!connected()) { + return false; + } + + tlen = strnlen(topic, this->bufferSize); + + header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + this->buffer[pos++] = header; + len = plength + 2 + tlen; + do { + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; + } + this->buffer[pos++] = digit; + llen++; + } while(len>0); + + pos = writeString(topic,this->buffer,pos); + + rc += _client->write(this->buffer,pos); + + for (i=0;iwrite((char)pgm_read_byte_near(payload + i)); + } + + lastOutActivity = millis(); + + expectedLength = 1 + llen + 2 + tlen + plength; + + return (rc == expectedLength); +} + +boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { + if (connected()) { + // Send the header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,this->buffer,length); + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE); + uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); + lastOutActivity = millis(); + return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); + } + return false; +} + +int PubSubClient::endPublish() { + return 1; +} + +size_t PubSubClient::write(uint8_t data) { + lastOutActivity = millis(); + return _client->write(data); +} + +size_t PubSubClient::write(const uint8_t *buffer, size_t size) { + lastOutActivity = millis(); + return _client->write(buffer,size); +} + +size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint16_t len = length; + do { + + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while(len>0); + + buf[4-llen] = header; + for (int i=0;i 0) && result) { + bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; + rc = _client->write(writeBuf,bytesToWrite); + result = (rc == bytesToWrite); + bytesRemaining -= rc; + writeBuf += rc; + } + return result; +#else + rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); + lastOutActivity = millis(); + return (rc == hlen+length); +#endif +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (qos > 1) { + return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*)topic, this->buffer,length); + this->buffer[length++] = qos; + return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::unsubscribe(const char* topic) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, this->buffer,length); + return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +void PubSubClient::disconnect() { + this->buffer[0] = MQTTDISCONNECT; + this->buffer[1] = 0; + _client->write(this->buffer,2); + _state = MQTT_DISCONNECTED; + _client->flush(); + _client->stop(); + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos-i-2] = (i >> 8); + buf[pos-i-1] = (i & 0xFF); + return pos; +} + + +boolean PubSubClient::connected() { + boolean rc; + if (_client == NULL ) { + rc = false; + } else { + rc = (int)_client->connected(); + if (!rc) { + if (this->_state == MQTT_CONNECTED) { + this->_state = MQTT_CONNECTION_LOST; + _client->flush(); + _client->stop(); + } + } else { + return this->_state == MQTT_CONNECTED; + } + } + return rc; +} + +PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { + IPAddress addr(ip[0],ip[1],ip[2],ip[3]); + return setServer(addr,port); +} + +PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { + this->ip = ip; + this->port = port; + this->domain = NULL; + return *this; +} + +PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; +} + +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +PubSubClient& PubSubClient::setClient(Client& client){ + this->_client = &client; + return *this; +} + +PubSubClient& PubSubClient::setStream(Stream& stream){ + this->stream = &stream; + return *this; +} + +int PubSubClient::state() { + return this->_state; +} + +boolean PubSubClient::setBufferSize(uint16_t size) { + if (size == 0) { + // Cannot set it back to 0 + return false; + } + if (this->bufferSize == 0) { + this->buffer = (uint8_t*)malloc(size); + } else { + uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); + if (newBuffer != NULL) { + this->buffer = newBuffer; + } else { + return false; + } + } + this->bufferSize = size; + return (this->buffer != NULL); +} + +uint16_t PubSubClient::getBufferSize() { + return this->bufferSize; +} +PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { + this->keepAlive = keepAlive; + return *this; +} +PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { + this->socketTimeout = timeout; + return *this; +} diff --git a/lib/PubSubClient/src/PubSubClient.h b/lib/PubSubClient/src/PubSubClient.h new file mode 100644 index 0000000..c70d9fd --- /dev/null +++ b/lib/PubSubClient/src/PubSubClient.h @@ -0,0 +1,184 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#include "IPAddress.h" +#include "Client.h" +#include "Stream.h" + +#define MQTT_VERSION_3_1 3 +#define MQTT_VERSION_3_1_1 4 + +// MQTT_VERSION : Pick the version +//#define MQTT_VERSION MQTT_VERSION_3_1 +#ifndef MQTT_VERSION +#define MQTT_VERSION MQTT_VERSION_3_1_1 +#endif + +// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize(). +#ifndef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 256 +#endif + +// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive() +#ifndef MQTT_KEEPALIVE +#define MQTT_KEEPALIVE 15 +#endif + +// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout() +#ifndef MQTT_SOCKET_TIMEOUT +#define MQTT_SOCKET_TIMEOUT 15 +#endif + +// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client +// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to +// pass the entire MQTT packet in each write call. +//#define MQTT_MAX_TRANSFER_SIZE 80 + +// Possible values for client.state() +#define MQTT_CONNECTION_TIMEOUT -4 +#define MQTT_CONNECTION_LOST -3 +#define MQTT_CONNECT_FAILED -2 +#define MQTT_DISCONNECTED -1 +#define MQTT_CONNECTED 0 +#define MQTT_CONNECT_BAD_PROTOCOL 1 +#define MQTT_CONNECT_BAD_CLIENT_ID 2 +#define MQTT_CONNECT_UNAVAILABLE 3 +#define MQTT_CONNECT_BAD_CREDENTIALS 4 +#define MQTT_CONNECT_UNAUTHORIZED 5 + +#define MQTTCONNECT 1 << 4 // Client request to connect to Server +#define MQTTCONNACK 2 << 4 // Connect Acknowledgment +#define MQTTPUBLISH 3 << 4 // Publish message +#define MQTTPUBACK 4 << 4 // Publish Acknowledgment +#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) +#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) +#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) +#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request +#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment +#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request +#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment +#define MQTTPINGREQ 12 << 4 // PING Request +#define MQTTPINGRESP 13 << 4 // PING Response +#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting +#define MQTTReserved 15 << 4 // Reserved + +#define MQTTQOS0 (0 << 1) +#define MQTTQOS1 (1 << 1) +#define MQTTQOS2 (2 << 1) + +// Maximum size of fixed header and variable length size header +#define MQTT_MAX_HEADER_SIZE 5 + +#if defined(ESP8266) || defined(ESP32) +#include +#define MQTT_CALLBACK_SIGNATURE std::function callback +#else +#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int) +#endif + +#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;} + +class PubSubClient : public Print { +private: + Client* _client; + uint8_t* buffer; + uint16_t bufferSize; + uint16_t keepAlive; + uint16_t socketTimeout; + uint16_t nextMsgId; + unsigned long lastOutActivity; + unsigned long lastInActivity; + bool pingOutstanding; + MQTT_CALLBACK_SIGNATURE; + uint32_t readPacket(uint8_t*); + boolean readByte(uint8_t * result); + boolean readByte(uint8_t * result, uint16_t * index); + boolean write(uint8_t header, uint8_t* buf, uint16_t length); + uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); + // Build up the header ready to send + // Returns the size of the header + // Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start + // (MQTT_MAX_HEADER_SIZE - ) bytes into the buffer + size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length); + IPAddress ip; + const char* domain; + uint16_t port; + Stream* stream; + int _state; +public: + PubSubClient(); + PubSubClient(Client& client); + PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, Client& client); + PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(const char*, uint16_t, Client& client); + PubSubClient(const char*, uint16_t, Client& client, Stream&); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + + ~PubSubClient(); + + PubSubClient& setServer(IPAddress ip, uint16_t port); + PubSubClient& setServer(uint8_t * ip, uint16_t port); + PubSubClient& setServer(const char * domain, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Client& client); + PubSubClient& setStream(Stream& stream); + PubSubClient& setKeepAlive(uint16_t keepAlive); + PubSubClient& setSocketTimeout(uint16_t timeout); + + boolean setBufferSize(uint16_t size); + uint16_t getBufferSize(); + + boolean connect(const char* id); + boolean connect(const char* id, const char* user, const char* pass); + boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); + void disconnect(); + boolean publish(const char* topic, const char* payload); + boolean publish(const char* topic, const char* payload, boolean retained); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean publish_P(const char* topic, const char* payload, boolean retained); + boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + // Start to publish a message. + // This API: + // beginPublish(...) + // one or more calls to write(...) + // endPublish() + // Allows for arbitrarily large payloads to be sent without them having to be copied into + // a new buffer and held in memory at one time + // Returns 1 if the message was started successfully, 0 if there was an error + boolean beginPublish(const char* topic, unsigned int plength, boolean retained); + // Finish off this publish message (started with beginPublish) + // Returns 1 if the packet was sent successfully, 0 if there was an error + int endPublish(); + // Write a single byte of payload (only to be used with beginPublish/endPublish) + virtual size_t write(uint8_t); + // Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish) + // Returns the number of bytes written + virtual size_t write(const uint8_t *buffer, size_t size); + boolean subscribe(const char* topic); + boolean subscribe(const char* topic, uint8_t qos); + boolean unsubscribe(const char* topic); + boolean loop(); + boolean connected(); + int state(); + +}; + + +#endif diff --git a/lib/SparkFun u-blox Arduino Library/.gitattributes b/lib/SparkFun u-blox Arduino Library/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/lib/SparkFun u-blox Arduino Library/.gitignore b/lib/SparkFun u-blox Arduino Library/.gitignore new file mode 100644 index 0000000..58b4bef --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/.gitignore @@ -0,0 +1,58 @@ +# Visual Studio Code files +.vscode + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# VIM backup files +*~ +[._]*.un~ +*.swp + +# Zephyr build files +examples/Zephyr/*/build/* \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/.piopm b/lib/SparkFun u-blox Arduino Library/.piopm new file mode 100644 index 0000000..90b31e4 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "SparkFun u-blox Arduino Library", "version": "1.8.11", "spec": {"owner": "sparkfun", "id": 5746, "name": "SparkFun u-blox Arduino Library", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/CONTRIBUTING.md b/lib/SparkFun u-blox Arduino Library/CONTRIBUTING.md new file mode 100644 index 0000000..ff79aea --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# How to Contribute + +Thank you so *much* for offering to help out. We truly appreciate it. + +If you'd like to contribute, start by searching through the [issues](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues) and [pull requests](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/pulls) to see whether someone else has raised a similar idea or question. +Please check the [closed issues](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues?q=is%3Aissue+is%3Aclosed) +and [closed pull requests](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/pulls?q=is%3Apr+is%3Aclosed) too - you may find that your issue or feature has already been discussed. + +If you decide to add a feature to this library, please create a PR and follow these best practices: + +* Change as little as possible. Do not submit a PR that changes 100 lines of whitespace. Break up into multiple PRs if necessary. +* If you've added a new feature document it with a simple example sketch. This serves both as a test of your PR and as a quick way for users to quickly learn how to use your new feature. +* If you add new functions also add them to _keywords.txt_ so that they are properly highlighted in Arduino. [Read more](https://www.arduino.cc/en/Hacking/libraryTutorial). +* **Important:** Please submit your PR using the [release_candidate branch](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/tree/release_candidate). That way, we can merge and test your PR quickly without changing the _master_ branch + +![Contributing.JPG](./img/Contributing.JPG) + +## Style guide + +Please read and follow the [Arduino API style guide](https://www.arduino.cc/en/Reference/APIStyleGuide). Also read and consider the [Arduino style guide](https://www.arduino.cc/en/Reference/StyleGuide). diff --git a/lib/SparkFun u-blox Arduino Library/ISSUE_TEMPLATE.md b/lib/SparkFun u-blox Arduino Library/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..84ae55b --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ +### Subject of the issue +Describe your issue here. If you reference a datasheet please specify which one and in which section (ie, the protocol manual, section 5.1.2). Additionally, screenshots are easy to paste into github. + +### Your workbench +* What development board or microcontroller are you using? +* What version of hardware or breakout board are you using? +* How is the breakout board wired to your microcontroller? +* How is everything being powered? +* Are there any additional details that may help us help you? + +### Steps to reproduce +Tell us how to reproduce this issue. Please post stripped down example code demonstrating your issue. + +### Expected behavior +Tell us what should happen + +### Actual behavior +Tell us what happens instead diff --git a/lib/SparkFun u-blox Arduino Library/LICENSE.md b/lib/SparkFun u-blox Arduino Library/LICENSE.md new file mode 100644 index 0000000..e64bd4e --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/LICENSE.md @@ -0,0 +1,55 @@ +SparkFun License Information +============================ + +SparkFun uses two different licenses for our files — one for hardware and one for code. + +Hardware +--------- + +**SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** + +Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). + +You are free to: + +Share — copy and redistribute the material in any medium or format +Adapt — remix, transform, and build upon the material +for any purpose, even commercially. +The licensor cannot revoke these freedoms as long as you follow the license terms. +Under the following terms: + +Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. +No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. +Notices: + +You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. +No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. + + +Code +-------- + +**SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** + +The MIT License (MIT) + +Copyright (c) 2016 SparkFun Electronics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/SparkFun u-blox Arduino Library/README.md b/lib/SparkFun u-blox Arduino Library/README.md new file mode 100644 index 0000000..b25ecf3 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/README.md @@ -0,0 +1,163 @@ +SparkFun u-blox Arduino Library +=========================================================== + +**Please note: this library is now deprecated. Please migrate to the new [SparkFun u-blox GNSS Arduino Library](https://github.com/sparkfun/SparkFun_u-blox_GNSS_Arduino_Library).** + +**You can find details on how to migrate to v2.0 [here](https://github.com/sparkfun/SparkFun_u-blox_GNSS_Arduino_Library#migrating-to-v20).** + +You can install v2.0 via the Arduino Library Manager. Search for **SparkFun u-blox GNSS**. + + + + + + + + + + + + + + + + +
SparkFun GPS-RTK2 - ZED-F9P (GPS-15136)SparkFun GPS-RTK - NEO-M8P-2 (GPS-15005)SparkFun ZOE-M8Q Breakout (GPS-15193)SparkFun SAM-M8Q Breakout (GPS-15210)SparkFun NEO-M9N Breakout (GPS-15733)
+ +U-blox makes some incredible GPS receivers covering everything from low-cost, highly configurable modules such as the SAM-M8Q all the way up to the surveyor grade ZED-F9P with precision of the diameter of a dime. This library focuses on configuration and control of u-blox devices over I2C (called DDC by u-blox) and Serial. The UBX protocol is supported over both I2C and serial, and is a much easier and lighterweight interface to a GPS module. Stop parsing NMEA data! And simply ask for the datums you need. + +This library can be installed via the Arduino Library manager. Search for **SparkFun u-blox GNSS**. + +Although not an integrated part of the library, you will find an example of how to communicate with the older series 6 and 7 modules in the [examples folder](./examples/Series_6_7). + +Max (400kHz) I2C Support +------------------- + +To achieve 400kHz I2C speed please be sure to remove all pull-ups on the I2C bus. Most, if not all, u-blox modules include pull ups on the I2C lines (sometimes called DDC in their manuals). Cut all I2C pull up jumpers and/or remove them from peripheral boards. Otherwise, various data glitches can occur. See issues [38](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/38) and [40](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40) for more information. If possible, run the I2C bus at 100kHz. + +------------------- + +Want to help? Please do! We are always looking for ways to improve and build out features of this library. + +* We are always interested in adding SPI support with a checkUbloxSPI() function + +Thanks to: + +* [trycoon](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/pull/7) for fixing the lack of I2C buffer length defines. +* [tve](https://github.com/tve) for building out serial additions and examples. +* [Redstoned](https://github.com/Redstoned) and [davidallenmann](https://github.com/davidallenmann) for adding PVT date and time. +* [wittend](https://forum.sparkfun.com/viewtopic.php?t=49874) for pointing out the RTCM print bug. +* Big thanks to [PaulZC](https://github.com/PaulZC) for implementing the combined key ValSet method, geofence functions, better saveConfig handling, as well as a bunch of small fixes. +* [RollieRowland](https://github.com/RollieRowland) for adding HPPOSLLH (High Precision Geodetic Position). +* [tedder](https://github.com/tedder) for moving iTOW to PVT instead of HPPOS and comment cleanup. +* [grexjmo](https://github.com/grexjmo) for pushing for a better NMEA sentence configuration method. +* [averywallis](https://github.com/averywallis) for adding good comments to the various constants. +* [blazczak](https://github.com/blazczak) and [geeksville](https://github.com/geeksville) for adding support for the series 6 and 7 modules. +* [bjorn@unsurv](https://github.com/unsurv) for adding powerOff and powerOffWithInterrupt. +* [dotMorten](https://github.com/dotMorten) for the MSGOUT keys, autoHPPOSLLH, autoDOP and upgrades to autoPVT. +* [markuckermann](https://github.com/markuckermann) for spotting the config layer gremlins +* [vid553](https://github.com/vid553) for the Zephyr port +* [balamuruganky](https://github.com/balamuruganky) for the NAV-PVT velocity parameters, getSpeedAccEst, getHeadingAccEst, getInvalidLlh, getHeadVeh, getMagDec and getMagAcc +* [nelarsen](https://github.com/nelarsen) for the buffer overrun improvements +* [mstranne](https://github.com/mstranne) and [shaneperera](https://github.com/shaneperera) for the pushRawData suggestion +* [rubienr](https://github.com/rubienr) for spotting the logical AND issues + +Need a Python version for Raspberry Pi? Checkout the [Qwiic Ublox GPS Py module](https://github.com/sparkfun/Qwiic_Ublox_Gps_Py). + +Need a library for the u-blox and Particle? Checkout the [Particle library](https://github.com/aseelye/SparkFun_Ublox_Particle_Library) fork. + +Need a C++ version which runs on Linux? Checkout the [Ublox_Linux_Library](https://github.com/balamuruganky/Ublox_Linux_Library). + +Contributing +-------------- + +If you would like to contribute to this library: please do, we truly appreciate it, but please follow [these guidelines](./CONTRIBUTING.md). Thanks! + +Repository Contents +------------------- + +* **/examples** - Example sketches for the library (.ino). Run these from the Arduino IDE. +* **/src** - Source files for the library (.cpp, .h). +* **[keywords.txt](./keywords.txt)** - Keywords from this library that will be highlighted in the Arduino IDE. +* **[library.properties](./library.properties)** - General library properties for the Arduino package manager. +* **[CONTRIBUTING.md](./CONTRIBUTING.md)** - Guidelines on how to contribute to this library. +* **[Theory.md](./Theory.md)** - provides detail on how data is processed by the library. + +Documentation +-------------- + +* **[Installing an Arduino Library Guide](https://learn.sparkfun.com/tutorials/installing-an-arduino-library)** - Basic information on how to install an Arduino library. + +Polling vs. Auto-Reporting +-------------------------- + +This library supports two modes of operation for getting navigation information with the `getPVT` +function (based on the `UBX_NAV_PVT` protocol packet): polling and auto-reporting. + +The standard method is for the sketch to call `getPVT` (or one of the `getLatitude`, `getLongitude`, +etc. methods) when it needs a fresh navigation solution. At that point the library sends a request +to the GPS to produce a fresh solution. The GPS then waits until the next measurement occurs (e.g. +once per second or as set using `setNavigationFrequency`) and then sends the fresh data. +The advantage of this method is that the data received is always fresh, the downside is that getPVT +can block until the next measurement is made by the GPS, e.g. up to 1 second if the nav frequency is +set to one second. + +An alternate method can be chosen using `setAutoPVT(true)` which instructs the GPS to send the +navigation information (`UBX_NAV_PVT` packet) as soon as it is produced. This is the way the older +NMEA navigation data has been used for years. The sketch continues to call `getPVT` as before but +under the hood the library returns the data of the last solution received from the GPS, which may be +a bit out of date (how much depends on the `setNavigationFrequency` value). + +The advantage of this method is that getPVT does not block: it returns true if new data is available +and false otherwise. The disadvantages are that the data may be a bit old and that buffering for +these spontaneus `UBX_NAV_PVT` packets is required (100 bytes each). When using Serial the buffering +is an issue because the std serial buffer is 32 or 64 bytes long depending on Arduino version. When +using I2C the buffering is not an issue because the GPS device has at least 1KB of internal buffering +(possibly as large as 4KB). + +As an example, assume that the GPS is set to produce 5 navigation +solutions per second and that the sketch only calls getPVT once a second, then the GPS will queue 5 +packets in its internal buffer (about 500 bytes) and the library will read those when getPVT is +called, update its internal copy of the nav data 5 times, and return `true` to the sketch. The +sketch calls `getLatitude`, etc. and retrieve the data of the most recent of those 5 packets. + +The library also supports: +* `autoHPPOSLLH` +* `autoDOP` +* `autoHNRAtt` +* `autoHNRDyn` +* `autoHNRPVT` + +Memory Usage +--------------------------------- + +Version 1.8.9 introduced support for `autoHNR` on the NEO-M8U, and that tipped the balance in terms of RAM use on the ATmega328. +The library does still run on the ATmega328 but you will see _**Low memory available, stability problems may occur**_ warnings +as the global variables now occupy 1540 bytes of RAM. If you do want to run this library on the ATmega328, you may need to regress +to Version 1.8.8 via the Library Manager. + +Products That Use This Library +--------------------------------- +* [GPS-16481](https://www.sparkfun.com/products/16481) - SparkFun GPS-RTK-SMA Breakout - ZED-F9P (Qwiic) +* [GPS-15136](https://www.sparkfun.com/products/15136) - SparkFun GPS-RTK2 Board - ZED-F9P (Qwiic) +* [GPS-15005](https://www.sparkfun.com/products/15005) - SparkFun GPS-RTK Board - NEO-M8P-2 (Qwiic) +* [GPS-15210](https://www.sparkfun.com/products/15210) - SparkFun GPS Breakout - Chip Antenna, SAM-M8Q (Qwiic) +* [GPS-15193](https://www.sparkfun.com/products/15193) - SparkFun GPS Breakout - Chip Antenna, ZOE-M8Q (Qwiic) +* [GPS-15733](https://www.sparkfun.com/products/15733) - SparkFun GPS Breakout - NEO-M9N, Chip Antenna (Qwiic) +* [GPS-15712](https://www.sparkfun.com/products/15712) - SparkFun GPS Breakout - NEO-M9N, U.FL (Qwiic) +* [GPS-16329](https://www.sparkfun.com/products/16329) - SparkFun GPS Dead Reckoning Breakout - NEO-M8U (Qwiic) +* [SPX-14980](https://www.sparkfun.com/products/14980) - SparkX GPS-RTK Black +* [SPX-15106](https://www.sparkfun.com/products/15106) - SparkX SAM-M8Q + +License Information +------------------- + +This product is _**open source**_! + +Various bits of the code have different licenses applied. Anything SparkFun wrote is beerware; if you see me (or any other SparkFun employee) at the local, and you've found our code helpful, please buy us a round! + +Please use, reuse, and modify these files as you see fit. Please maintain attribution to SparkFun Electronics and release anything derivative under the same license. + +Distributed as-is; no warranty is given. + +- Your friends at SparkFun. diff --git a/lib/SparkFun u-blox Arduino Library/Theory.md b/lib/SparkFun u-blox Arduino Library/Theory.md new file mode 100644 index 0000000..6469b3e --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/Theory.md @@ -0,0 +1,39 @@ +How I2C (aka DDC) communication works with a u-blox module +=========================================================== + +When the user calls one of the methods the library will poll the u-blox module for new data. + +* Wait for a minimum of 25 ms between polls (configured dynamically when update rate is set) +* Write 0xFD to module +* Read two bytes (0xFD and 0xFE) for bytes available +* If 0x7F or 0xFF then no bytes are available +* Otherwise, read number of bytes and process into NMEA, UBX, or RTCM frame. +* If checksum is valid, flag frame as complete. + +This library was originally written to use the I2C interface but Serial has been implemented as well. + +How data is processed by this library +=========================================================== + +A method will call **sendCommand()**. This will begin waiting for a response with either **waitForACKResponse()** or **waitForNoACKResponse()** depending on the command we have sent (CFG commands generate an ACK where others like PVT do not). + +Once **waitForACKResponse()** or **waitForNoACKResponse()** is called the library will start checking the u-blox module for new bytes. These bytes may be part of a NMEA sentence, an RTCM sentence, or a UBX packet. The library will file each byte into the appropriate container. Once a given sentence or packet is complete, the appropriate processUBX(), processNMEA() will be called. These functions deal with specific processing for each type. + +Note: When interfacing to a u-blox module over I2C **checkUbloxI2C()** will read all bytes currently sitting in the I2C buffer. This may pick up multiple UBX packets. For example, an ACK for a VALSET may be mixed in with an **AutoPVT** response. We cannot tell **checkUbloxI2C()** to stop once a given ACK is found because we run the risk of leaving unprocessed bytes in the I2C buffer and losing them. We don't have this issue with **checkUbloxSerial()**. + +**processUBX()** will check the CRC of the UBX packet. If validated, the packet will be marked as valid. Once a packet is marked as valid then **processUBXpacket()** is called to extract the contents. This is most commonly used to get the position, velocity, and time (PVT) out of the packet but is also used to check the nature of an ACK packet. + +Once a packet has been processed, **waitForACKResponse()/waitForNoACKResponse()** makes the appropriate decision what to do with it. If a packet satisfies the CLS/ID and characteristics of what **waitForACKResponse()/waitForNoACKResponse()** is waiting for, then it returns back to **sendCommand()**. If the packet didn't match or was invalid then **waitForACKResponse()/waitForNoACKResponse()** will continue to wait until the correct packet is received or we time out. **sendCommand()** then returns with a value from the **sfe_ublox_status_e** enum depending on the success of **waitForACKResponse()/waitForNoACKResponse()**. + +If we are getting / polling data from the module, **sendCommand()** will return **SFE_UBLOX_STATUS_DATA_RECEIVED** if the get was successful. + +If we are setting / writing data to the module, **sendCommand()** will return **SFE_UBLOX_STATUS_DATA_SENT** if the set was successful. + +We are proud that this library still compiles and runs on the original RedBoard (ATmega328P). We achieve that by being very careful about how much RAM we allocate to packet storage. We use only three buffers or containers to store the incoming data: +- **packetBuf** (packetBuffer) - is small and is used to store only the head (and tail) of incoming UBX packets until we know they are. If the packet is _expected_ (i.e. it matches the Class and ID in the packet passed in **sendCommand()**) then the incoming bytes are diverted into **packetCfg** or **packetAck**. Unexpected packets are ignored. +- **packetCfg** (packetConfiguration) - is used to store an _expected_ incoming UBX packet of up to 256 bytes. E.g. **getProtocolVersion()** returns about 220 bytes. Message data requested by a higher function is returned in packetCfg. +- **packetAck** (packetAcknowledge) - is small and is used to store the ACK or NACK accompanying any _expected_ packetCfg. + +**AutoPVT**, **AutoHPPOSLLH** and **AutoDOP** packets can arrive at any time. They too _have_ to be stored and processed in **packetCfg**. This means there are circumstances where the library can get the data it is expecting from the module, but it is overwritten (e.g. by an **AutoPVT** packet) before **sendCommand()** is able to return. In this case, **sendCommand()** will return the error **SFE_UBLOX_STATUS_DATA_OVERWRITTEN**. We should simply call the library function again, but we will need to reset the packet contents first as they will indeed have been overwritten as the error implies. + +Need a command that is not currently "built-in" to the library? You can do that using a Custom Command. Check out [Example20_SendCustomCommand](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino) for further details. Note: this will of course increase your RAM use. diff --git a/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino new file mode 100644 index 0000000..d3448dd --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino @@ -0,0 +1,66 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + To take advantage of the internal IMU of either the Dead Reckoning GPS + boards (ZED-F9R, NEO-M8U), you must first calibrate it. This includes securing the GPS module + to your vehicle so that it is stable within 2 degrees and that the frame of + reference of the board is consistent with the picture outlined in the + Receiver-Description-Prot-Spec Datasheet under Automotive/Untethered Dead + Reckoning. You may also check either the ZED-F9R or NEO-M8U Hookup Guide for + more information. After the board is secure, you'll need to put the module + through certain conditions for proper calibration: acceleration, turning, + stopping for a few minutes, getting to a speed over 30km/h all under a clear sky + with good GNSS signal. This example simply looks at the + "fusionMode" status which indicates whether the SparkFun Dead Reckoning is + not-calibrated - 0, or calibrated - 1. +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun Ublox Example")); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) +} + +void loop() +{ + + if (myGPS.getEsfInfo()){ + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.imuMeas.fusionMode); + if (myGPS.imuMeas.fusionMode == 1) + Serial.println(F("Sensor is calibrated!")); + } + + delay(250); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example2_getIMUData/Example2_getIMUData.ino b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example2_getIMUData/Example2_getIMUData.ino new file mode 100644 index 0000000..e023f1d --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example2_getIMUData/Example2_getIMUData.ino @@ -0,0 +1,84 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + After calibrating the module, also known as "Fusion Mode", you can get + data directly from the IMU. This data is integrated directly into the GNSS + output, but is provided by the module as well. + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun Ublox Example")); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + if (myGPS.getEsfInfo()){ + + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.imuMeas.fusionMode); + + if (myGPS.imuMeas.fusionMode == 1){ + Serial.println(F("Fusion Mode is Initialized!")); + } + else { + Serial.println(F("Fusion Mode is either disabled or not initialized - Freezing!")); + Serial.println(F("Please see Example 1 description at top for more information.")); + } + } +} + +void loop() +{ + + if (myGPS.getEsfIns()) + { + Serial.print(F("X: ")); + Serial.println(myGPS.imuMeas.xAngRate); + Serial.print(F("Y: ")); + Serial.println(myGPS.imuMeas.yAngRate); + Serial.print(F("Z: ")); + Serial.println(myGPS.imuMeas.zAngRate); + Serial.print(F("X Acceleration: ")); + Serial.println(myGPS.imuMeas.xAccel); + Serial.print(F("Y Acceleration: ")); + Serial.println(myGPS.imuMeas.yAccel); + Serial.print(F("Z Acceleration: ")); + Serial.println(myGPS.imuMeas.zAccel); + // These values also have "validity checks" that can be provided by the + // ublox library, add "Vald" to values: e.g. xAngRateVald or xAccelVald. + } + + delay(250); +} + diff --git a/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino new file mode 100644 index 0000000..24d8ca3 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino @@ -0,0 +1,97 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + After calibrating the module, also known as "Fusion Mode", you can get + data directly from the IMU. This example code walks you through trouble + shooting or identifying the different states of any individual + "external" (which include internal) sensors you've hooked up (vehicle speed + sensor) or the internal IMU used by the modules. You can see if the sensor is + being used, if it's calibrated, ready, what data type it returns, the state + of the measurement etc. + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun Ublox Example")); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // GetEsfInfo also gets the number of sensors used by the ublox module, this + // includes (in the case of the ZED-F9R) wheel tick input from the vehicle + // speed sensor attached to the module. + if (myGPS.getEsfInfo()){ + + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.imuMeas.fusionMode); + + if (myGPS.imuMeas.fusionMode == 1){ + Serial.println(F("Fusion Mode is Initialized!")); + } + else { + Serial.println(F("Fusion Mode is either disabled or not initialized - Freezing!")); + Serial.println(F("Please see Example 1 description at top for more information.")); + } + } +} + +void loop() +{ + + for(int i=1; i<=myGPS.ubloxSen.numSens; i++){ + myGPS.getSensState(i); // Give the sensor you want to check on. + Serial.print(F("Sensor Data Type: ")); //See ublox receiver description + //or our hookup guide for information on the + //return value. + Serial.println(myGPS.ubloxSen.senType); + Serial.print(F("Being Used: ")); + Serial.println(myGPS.ubloxSen.isUsed); + Serial.print(F("Is Ready: ")); + Serial.println(myGPS.ubloxSen.isReady); + Serial.print(F("Calibration Status: ")); + Serial.println(myGPS.ubloxSen.calibStatus); + Serial.print(F("Time Status: ")); + Serial.println(myGPS.ubloxSen.timeStatus); + Serial.print(F("Bad Measure: ")); + Serial.println(myGPS.ubloxSen.timeStatus); + Serial.print(F("Bad Time Tag: ")); + Serial.println(myGPS.ubloxSen.badTag); + Serial.print(F("Missed Measure : ")); + Serial.println(myGPS.ubloxSen.missMeas); + Serial.print(F("Noisy Measure: ")); + Serial.println(myGPS.ubloxSen.noisyMeas); + } + +} + + diff --git a/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino new file mode 100644 index 0000000..2225348 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino @@ -0,0 +1,82 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + After calibrating the module and securing it to your vehicle such that it's + stable within 2 degrees, and the board is oriented correctly with regards to + the vehicle's frame, you can now read the vehicle's "attitude". The attitude + includes the vehicle's heading, pitch, and roll. You can also check the + accuracy of those readings. + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun Ublox Example")); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + if (myGPS.getEsfInfo()){ + + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.imuMeas.fusionMode); + + if (myGPS.imuMeas.fusionMode == 1){ + Serial.println(F("Fusion Mode is Initialized!")); + } + else { + Serial.println(F("Fusion Mode is either disabled or not initialized - Freezing!")); + Serial.println(F("Please see Example 1 description at top for more information.")); + } + } +} + +void loop() +{ + myGPS.getVehAtt(); // Give the sensor you want to check on. + Serial.print(F("Roll: ")); + Serial.println(myGPS.vehAtt.roll); + Serial.print(F("Pitch: ")); + Serial.println(myGPS.vehAtt.pitch); + Serial.print(F("Heading: ")); + Serial.println(myGPS.vehAtt.heading); + Serial.print(F("Roll Accuracy: ")); + Serial.println(myGPS.vehAtt.accRoll); + Serial.print(F("Pitch Accuracy: ")); + Serial.println(myGPS.vehAtt.accPitch); + Serial.print(F("Heading Accuracy: ")); + Serial.println(myGPS.vehAtt.accHeading); + + delay(250); +} + + diff --git a/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino new file mode 100644 index 0000000..5acbf28 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino @@ -0,0 +1,90 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + polls and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example polls the high rate data. + (The next example uses "autoHNR" to receive the HNR data automatically.) + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Warning! u-blox GPS did not begin correctly.")); + Serial.println(F("(This may be because the I2C port is busy with HNR messages.)")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); +} + +void loop() +{ + // Poll and print selected HNR data + if (myGPS.getHNRAtt(125) == true) // Request HNR Att data using a 125ms timeout + { + Serial.print(F("Roll: ")); + Serial.print(myGPS.hnrAtt.roll); + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.hnrAtt.pitch); + Serial.print(F(" Heading: ")); + Serial.println(myGPS.hnrAtt.heading); + } + if (myGPS.getHNRDyn(125) == true) // Request HNR Dyn data using a 125ms timeout + { + Serial.print(F("xAccel: ")); + Serial.print(myGPS.hnrVehDyn.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(myGPS.hnrVehDyn.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(myGPS.hnrVehDyn.zAccel); + } + if (myGPS.getHNRPVT(125) == true) // Request HNR PVT data using a 125ms timeout + { + Serial.print(F("ns: ")); + Serial.print(myGPS.hnrPVT.nano); + Serial.print(F(" Lat: ")); + Serial.print(myGPS.hnrPVT.lat); + Serial.print(F(" Lon: ")); + Serial.println(myGPS.hnrPVT.lon); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino new file mode 100644 index 0000000..7180742 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino @@ -0,0 +1,96 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + reads and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example uses "autoHNR" to receive the HNR data automatically. + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +boolean usingAutoHNRAtt = false; +boolean usingAutoHNRDyn = false; +boolean usingAutoHNRPVT = false; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Warning! u-blox GPS did not begin correctly.")); + Serial.println(F("(This may be because the I2C port is busy with HNR messages.)")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); + + usingAutoHNRAtt = myGPS.setAutoHNRAtt(true); //Attempt to enable auto HNR attitude messages + usingAutoHNRDyn = myGPS.setAutoHNRDyn(true); //Attempt to enable auto HNR vehicle dynamics messages + usingAutoHNRPVT = myGPS.setAutoHNRPVT(true); //Attempt to enable auto HNR PVT messages +} + +void loop() +{ + if (usingAutoHNRAtt && (myGPS.getHNRAtt() == true)) // If setAutoHNRAtt was successful and new data is available + { + Serial.print(F("Roll: ")); // Print selected data + Serial.print(myGPS.hnrAtt.roll); + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.hnrAtt.pitch); + Serial.print(F(" Heading: ")); + Serial.println(myGPS.hnrAtt.heading); + } + if (usingAutoHNRDyn && (myGPS.getHNRDyn() == true)) // If setAutoHNRDyn was successful and new data is available + { + Serial.print(F("xAccel: ")); // Print selected data + Serial.print(myGPS.hnrVehDyn.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(myGPS.hnrVehDyn.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(myGPS.hnrVehDyn.zAccel); + } + if (usingAutoHNRPVT && (myGPS.getHNRPVT() == true)) // If setAutoHNRPVT was successful and new data is available + { + Serial.print(F("ns: ")); // Print selected data + Serial.print(myGPS.hnrPVT.nano); + Serial.print(F(" Lat: ")); + Serial.print(myGPS.hnrPVT.lat); + Serial.print(F(" Lon: ")); + Serial.println(myGPS.hnrPVT.lon); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino b/lib/SparkFun u-blox Arduino Library/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino new file mode 100644 index 0000000..e435a8b --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino @@ -0,0 +1,82 @@ +/* + Reading two altitudes - Mean Sea Level and Ellipsode + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for its lat/long/altitude. + + getAltitude() reports mm above ellipsode model of the globe. There are some + instances where altitude above Mean Sea Level is better. This example shows how + to use getAltitudeMSL(). The difference varies but is ~20m. + Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html + Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Tracks the passing of 2000ms (2 seconds) + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + long altitudeMSL = myGPS.getAltitudeMSL(); + Serial.print(F(" AltMSL: ")); + Serial.print(altitudeMSL); + Serial.print(F(" (mm)")); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino b/lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino new file mode 100644 index 0000000..0400379 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino @@ -0,0 +1,58 @@ +/* + Send command to reset module over I2C + By: Nathan Seidle + Date: January 29rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to reset the U-Blox module to factory defaults over I2C. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + while (Serial.available()) Serial.read(); //Trash any incoming chars + Serial.println("Press a key to reset module to factory defaults"); + while (Serial.available() == false) ; //Wait for user to send character + + myGPS.factoryReset(); //Reset everything: baud rate, I2C address, update rate, everything. + + if (myGPS.begin() == false) //Attempt to re-connect + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + Serial.println("Unit has now been factory reset. Freezing..."); + while(1); +} + +void loop() +{ + +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino b/lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino new file mode 100644 index 0000000..4773f72 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino @@ -0,0 +1,95 @@ +/* + Test baud rate changes on serial, factory reset, and hard reset. + By: Thorsten von Eicken + Date: January 29rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to reset the U-Blox module to factory defaults over serial. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Connect the U-Blox serial port to Serial1 + If you're using an Uno or don't have a 2nd serial port (Serial1), consider using software serial + Open the serial monitor at 115200 baud to see the output +*/ + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +int state = 0; // steps through auto-baud, reset, etc states + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); +} + +void loop() +{ + Serial.print("===== STATE "); + Serial.println(state); + switch (state) { + case 0: // auto-baud connection, then switch to 38400 and save config + do { + Serial.println("GPS: trying 38400 baud"); + Serial1.begin(38400); + if (myGPS.begin(Serial1)) break; + + delay(100); + Serial.println("GPS: trying 9600 baud"); + Serial1.begin(9600); + if (myGPS.begin(Serial1)) { + Serial.println("GPS: connected at 9600 baud, switching to 38400"); + myGPS.setSerialRate(38400); + delay(100); + } else { + delay(2000); //Wait a bit before trying again to limit the Serial output flood + } + } while(1); + myGPS.setUART1Output(COM_TYPE_UBX); //Set the UART port to output UBX only + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + Serial.println("GPS serial connected, saved config"); + state++; + break; + case 1: // hardReset, expect to see GPS back at 38400 baud + Serial.println("Issuing hardReset (cold start)"); + myGPS.hardReset(); + delay(1000); + Serial1.begin(38400); + if (myGPS.begin(Serial1)) { + Serial.println("Success."); + state++; + } else { + Serial.println("*** GPS did not respond at 38400 baud, starting over."); + state = 0; + } + break; + case 2: // factoryReset, expect to see GPS back at 9600 baud + Serial.println("Issuing factoryReset"); + myGPS.factoryReset(); + delay(2000); // takes more than one second... a loop to resync would be best + Serial1.begin(9600); + if (myGPS.begin(Serial1)) { + Serial.println("Success."); + state++; + } else { + Serial.println("*** GPS did not come back at 9600 baud, starting over."); + state = 0; + } + break; + case 3: // print version info + Serial.print("GPS protocol version: "); + Serial.print(myGPS.getProtocolVersionHigh()); + Serial.print('.'); + Serial.print(myGPS.getProtocolVersionLow()); + state = 0; + } + delay(1000); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example12_UseUart/Example12_UseUart.ino b/lib/SparkFun u-blox Arduino Library/examples/Example12_UseUart/Example12_UseUart.ino new file mode 100644 index 0000000..05bf3b0 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example12_UseUart/Example12_UseUart.ino @@ -0,0 +1,98 @@ +/* + Reading lat and long via UBX binary commands using UART @38400 baud - free from I2C + By: Nathan Seidle, Adapted from Example3_GetPosition by Thorsten von Eicken + SparkFun Electronics + Date: January 28rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the library and U-Blox for serial port use as well as + switching the module from the default 9600 baud to 38400. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Connect the U-Blox serial TX pin to Uno pin 10 + Connect the U-Blox serial RX pin to Uno pin 11 + Open the serial monitor at 115200 baud to see the output +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include +SoftwareSerial mySerial(10, 11); // RX, TX. Pin 10 on Uno goes to TX pin on GPS module. + +long lastTime = 0; //Simple local timer. Limits amount of I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + //Assume that the U-Blox GPS is running at 9600 baud (the default) or at 38400 baud. + //Loop until we're in sync and then ensure it's at 38400 baud. + do { + Serial.println("GPS: trying 38400 baud"); + mySerial.begin(38400); + if (myGPS.begin(mySerial) == true) break; + + delay(100); + Serial.println("GPS: trying 9600 baud"); + mySerial.begin(9600); + if (myGPS.begin(mySerial) == true) { + Serial.println("GPS: connected at 9600 baud, switching to 38400"); + myGPS.setSerialRate(38400); + delay(100); + } else { + //myGPS.factoryReset(); + delay(2000); //Wait a bit before trying again to limit the Serial output + } + } while(1); + Serial.println("GPS serial connected"); + + myGPS.setUART1Output(COM_TYPE_UBX); //Set the UART port to output UBX only + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino b/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino new file mode 100644 index 0000000..168f8ac --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino @@ -0,0 +1,141 @@ +/* + Configuring the GPS to automatically send position reports over I2C + By: Nathan Seidle and Thorsten von Eicken + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GPS the send navigation reports automatically + and retrieving the latest one via getPVT. This eliminates the blocking in getPVT while the GPS + produces a fresh navigation solution at the expense of returning a slighly old solution. + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GPS + simply outputs the UBX_NAV_PVT packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(2); //Produce two solutions per second + myGPS.setAutoPVT(true); //Tell the GPS to "send" each solution + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + // Calling getPVT returns true if there actually is a fresh navigation solution available. + // Start the reading only when valid LLH is available + if (myGPS.getPVT() && (myGPS.getInvalidLlh() == false)) + { + Serial.println(); + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + int PDOP = myGPS.getPDOP(); + Serial.print(F(" PDOP: ")); + Serial.print(PDOP); + Serial.print(F(" (10^-2)")); + + int nedNorthVel = myGPS.getNedNorthVel(); + Serial.print(F(" VelN: ")); + Serial.print(nedNorthVel); + Serial.print(F(" (mm/s)")); + + int nedEastVel = myGPS.getNedEastVel(); + Serial.print(F(" VelE: ")); + Serial.print(nedEastVel); + Serial.print(F(" (mm/s)")); + + int nedDownVel = myGPS.getNedDownVel(); + Serial.print(F(" VelD: ")); + Serial.print(nedDownVel); + Serial.print(F(" (mm/s)")); + + int verticalAccEst = myGPS.getVerticalAccEst(); + Serial.print(F(" VAccEst: ")); + Serial.print(verticalAccEst); + Serial.print(F(" (mm)")); + + int horizontalAccEst = myGPS.getHorizontalAccEst(); + Serial.print(F(" HAccEst: ")); + Serial.print(horizontalAccEst); + Serial.print(F(" (mm)")); + + int speedAccEst = myGPS.getSpeedAccEst(); + Serial.print(F(" SpeedAccEst: ")); + Serial.print(speedAccEst); + Serial.print(F(" (mm/s)")); + + int headAccEst = myGPS.getHeadingAccEst(); + Serial.print(F(" HeadAccEst: ")); + Serial.print(headAccEst); + Serial.print(F(" (degrees * 10^-5)")); + + if (myGPS.getHeadVehValid() == true) { + int headVeh = myGPS.getHeadVeh(); + Serial.print(F(" HeadVeh: ")); + Serial.print(headVeh); + Serial.print(F(" (degrees * 10^-5)")); + + int magDec = myGPS.getMagDec(); + Serial.print(F(" MagDec: ")); + Serial.print(magDec); + Serial.print(F(" (degrees * 10^-2)")); + + int magAcc = myGPS.getMagAcc(); + Serial.print(F(" MagAcc: ")); + Serial.print(magAcc); + Serial.print(F(" (degrees * 10^-2)")); + } + + Serial.println(); + } else { + Serial.print("."); + delay(50); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino b/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino new file mode 100644 index 0000000..97df056 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino @@ -0,0 +1,103 @@ +/* + Configuring the GPS to automatically send position reports over I2C, with explicit data parsing calls + By: Nathan Seidle Thorsten von Eicken and Felix Jirka + SparkFun Electronics + Date: July 1st, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GPS the send navigation reports automatically + and retrieving the latest one via checkUblox when available. + This eliminates the implicit update in getPVT when accessing data fields twice. + Also this reduces the memory overhead of a separate buffer while introducing a slight error by inconsistencies because of the unsynchronized updates (on a multi core system). + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GPS + simply outputs the UBX_NAV_PVT packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(2); //Produce two solutions per second + myGPS.setAutoPVT(true, false); //Tell the GPS to "send" each solution and the lib not to update stale data implicitly + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +/* + Calling getPVT would return false now (compare to previous example where it would return true), so we just use the data provided + If you are using a threaded OS eg. FreeRTOS on an ESP32, the explicit mode of autoPVT allows you to use the data provided on both cores and inside multiple threads + The data update in background creates an inconsistent state, but that should not cause issues for most applications as they usually won't change the GPS location significantly within a 2Hz - 5Hz update rate. + Also you could oversample (10Hz - 20Hz) the data to smooth out such issues... +*/ +void loop() +{ + static uint16_t counter = 0; + + if (counter % 10 == 0) + { + // update your AHRS filter here for a ~100Hz update rate + // GPS data will be quasi static but data from your IMU will be changing + } + // debug output each half second + if (counter % 500 == 0) + { + Serial.println(); + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } + // call checkUblox all 50ms to capture the gps data + if (counter % 50 == 0) + { + myGPS.checkUblox(); + } + delay(1); + counter++; +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example3_AssumeAutoPVTviaUart/Example3_AssumeAutoPVTviaUart.ino b/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example3_AssumeAutoPVTviaUart/Example3_AssumeAutoPVTviaUart.ino new file mode 100644 index 0000000..6b77290 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example13_PVT/Example3_AssumeAutoPVTviaUart/Example3_AssumeAutoPVTviaUart.ino @@ -0,0 +1,79 @@ +/* + Reading lat and long via UBX binary commands using an RX-only UART + By: Nathan Seidle, Adapted from Example11 by Felix Jirka + SparkFun Electronics + Date: July 2nd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the library for serial port use with a single wire connection using the assumeAutoPVT method. + Saving your pins for other stuff :-) + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Preconditions: + U-Blox module is configured to send cyclical PVT message + Hardware Connections: + Connect the U-Blox serial TX pin to Rx of Serial2 (default: GPIO16) on your ESP32 + Open the serial monitor at 115200 baud to see the output +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example 17"); + + //Use any Serial port with at least a Rx Pin connected or a receive only version of SoftwareSerial here + //Assume that the U-Blox GPS is running at 9600 baud (the default) + Serial2.begin(9600); + // no need to check return value as internal call to isConnected() will not succeed + myGPS.begin(Serial2); + + // tell lib, we are expecting the module to send PVT messages by itself to our Rx pin + // you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call checkUblox cyclically) + myGPS.assumeAutoPVT(true, true); + +} + +void loop() +{ + // if implicit updates are allowed, this will trigger parsing the incoming messages + // and be true once a PVT message has been parsed + // In case you want to use explicit updates, wrap this in a timer and call checkUblox as often as needed, not to overflow your UART buffers + if (myGPS.getPVT()) + { + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } + else { + Serial.println(F("Wait for GPS data")); + delay(500); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example14_DebugOutput/Example14_DebugOutput.ino b/lib/SparkFun u-blox Arduino Library/examples/Example14_DebugOutput/Example14_DebugOutput.ino new file mode 100644 index 0000000..50c87d3 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example14_DebugOutput/Example14_DebugOutput.ino @@ -0,0 +1,93 @@ +/* + Reading lat and long via UBX binary commands using UART @38400 baud - free from I2C + By: Nathan Seidle, Adapted from Example3_GetPosition by Thorsten von Eicken + SparkFun Electronics + Date: January 28rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the debug output from the library. + Debug shows various packet and status outputs. These prints can be directed + towards Serial (as in Serial.print) or any other port (Serial1, SerialUSB, etc). + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Connect the U-Blox serial TX pin to Uno pin 10 + Connect the U-Blox serial RX pin to Uno pin 11 + Open the serial monitor at 115200 baud to see the output +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.println(myGPS.getSecond()); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example15_GetDateTime/Example15_GetDateTime.ino b/lib/SparkFun u-blox Arduino Library/examples/Example15_GetDateTime/Example15_GetDateTime.ino new file mode 100644 index 0000000..5e5b943 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example15_GetDateTime/Example15_GetDateTime.ino @@ -0,0 +1,107 @@ +/* + Getting time and date using Ublox commands + By: davidallenmann + SparkFun Electronics + Date: April 16th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for the current time and date. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.print(myGPS.getSecond()); + + Serial.print(" Time is "); + if (myGPS.getTimeValid() == false) + { + Serial.print("not "); + } + Serial.print("valid Date is "); + if (myGPS.getDateValid() == false) + { + Serial.print("not "); + } + Serial.print("valid"); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino b/lib/SparkFun u-blox Arduino Library/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino new file mode 100644 index 0000000..533b82d --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino @@ -0,0 +1,106 @@ +/* + Getting time and date using Ublox commands + By: davidallenmann + SparkFun Electronics + Date: April 16th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for the current time and date. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(500000); //Increase serial speed to maximize + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + Wire.setClock(400000); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + //myGPS.enableDebugging(); //Enable debug messages over Serial (default) + + myGPS.setNavigationFrequency(10); //Set output to 10 times a second + byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate:"); + Serial.println(rate); +} + +void loop() +{ + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if (myGPS.getPVT()) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.print(" "); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.print(myGPS.getSecond()); + Serial.print("."); + Serial.print(myGPS.getNanosecond()); + + myGPS.flushPVT(); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino b/lib/SparkFun u-blox Arduino Library/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino new file mode 100644 index 0000000..04b5602 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino @@ -0,0 +1,116 @@ +/* + Getting time and date using Ublox commands + By: Nathan Seidle + SparkFun Electronics + Date: April 16th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to use the Millisecond and Nanosecond output as well as increase the + I2C speed (100 to 400kHz), and serial output (115200 to 500kbps). + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(500000); //Increase serial speed to maximize + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + Wire.setClock(400000); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + //myGPS.enableDebugging(); //Enable debug messages over Serial (default) + + myGPS.setNavigationFrequency(10); //Set output to 10 times a second + byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate:"); + Serial.println(rate); + + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + pinMode(2, OUTPUT); //For debug capture + digitalWrite(2, HIGH); +} + +void loop() +{ + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if (myGPS.getPVT()) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.print(" "); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.print(myGPS.getSecond()); + Serial.print("."); + //Pretty print leading zeros + int mseconds = myGPS.getMillisecond(); + if (mseconds < 100) + Serial.print("0"); + if (mseconds < 10) + Serial.print("0"); + Serial.print(mseconds); + + Serial.print(" nanoSeconds: "); + Serial.print(myGPS.getNanosecond()); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example17_Geofence/Example17_Geofence.ino b/lib/SparkFun u-blox Arduino Library/examples/Example17_Geofence/Example17_Geofence.ino new file mode 100644 index 0000000..0592601 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example17_Geofence/Example17_Geofence.ino @@ -0,0 +1,169 @@ +/* + u-blox M8 geofence example + + Written by Paul Clark (PaulZC) + 10th December 2019 + + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example demonstrates how to use the addGeofence and getGeofenceState functions + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15210 + ZOE-M8Q: https://www.sparkfun.com/products/15193 + + This example powers up the GPS and reads the fix. + Once a valid 3D fix has been found, the code reads the latitude and longitude. + The code then sets four geofences around that position with a radii of 5m, 10m, 15m and 20m with 95% confidence. + The code then monitors the geofence status. + The LED will be illuminated if you are inside the _combined_ geofence (i.e. within the 20m radius). + + This code has been tested on the ZOE-M8Q. +*/ + +#define LED LED_BUILTIN // Change this if your LED is on a different pin + +#include // Needed for I2C + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + pinMode(LED, OUTPUT); + + // Set up the I2C pins + Wire.begin(); + + // Start the console serial port + Serial.begin(115200); + while (!Serial); // Wait for the user to open the serial monitor + delay(100); + Serial.println(); + Serial.println(); + Serial.println(F("u-blox M8 geofence example")); + Serial.println(); + Serial.println(); + + delay(1000); // Let the GPS power up + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + //myGPS.enableDebugging(); // Enable debug messages + myGPS.setI2COutput(COM_TYPE_UBX); // Limit I2C output to UBX (disable the NMEA noise) + + Serial.println(F("Waiting for a 3D fix...")); + + byte fixType = 0; + + while (fixType < 3) + { + fixType = myGPS.getFixType(); // Get the fix type + Serial.print(F("Fix: ")); // Print it + Serial.print(fixType); + if(fixType == 0) Serial.print(F(" = No fix")); + else if(fixType == 1) Serial.print(F(" = Dead reckoning")); + else if(fixType == 2) Serial.print(F(" = 2D")); + else if(fixType == 3) Serial.print(F(" = 3D")); + else if(fixType == 4) Serial.print(F(" = GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F(" = Time only")); + Serial.println(); + delay(1000); + } + + Serial.println(F("3D fix found!")); + + long latitude = myGPS.getLatitude(); // Get the latitude in degrees * 10^-7 + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); // Get the longitude in degrees * 10^-7 + Serial.print(F(" Long: ")); + Serial.println(longitude); + + uint32_t radius = 500; // Set the radius to 5m (radius is in m * 10^-2 i.e. cm) + + byte confidence = 2; // Set the confidence level: 0=none, 1=68%, 2=95%, 3=99.7%, 4=99.99% + + // Call clearGeofences() to clear all existing geofences. + Serial.print(F("Clearing any existing geofences. clearGeofences returned: ")); + Serial.println(myGPS.clearGeofences()); + + // It is possible to define up to four geofences. + // Call addGeofence up to four times to define them. + Serial.println(F("Setting the geofences:")); + + Serial.print(F("addGeofence for geofence 1 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); + + radius = 1000; // 10m + Serial.print(F("addGeofence for geofence 2 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); + + radius = 1500; // 15m + Serial.print(F("addGeofence for geofence 3 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); + + radius = 2000; // 20m + Serial.print(F("addGeofence for geofence 4 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); +} + +void loop() +{ + geofenceState currentGeofenceState; // Create storage for the geofence state + + boolean result = myGPS.getGeofenceState(currentGeofenceState); + + Serial.print(F("getGeofenceState returned: ")); // Print the combined state + Serial.print(result); // Get the geofence state + + if (!result) // If getGeofenceState did not return true + { + Serial.println(F(".")); // Tidy up + return; // and go round the loop again + } + + Serial.print(F(". status is: ")); // Print the status + Serial.print(currentGeofenceState.status); + + Serial.print(F(". numFences is: ")); // Print the numFences + Serial.print(currentGeofenceState.numFences); + + Serial.print(F(". combState is: ")); // Print the combined state + Serial.print(currentGeofenceState.combState); + + if (currentGeofenceState.combState == 0) + { + Serial.print(F(" = Unknown")); + digitalWrite(LED, LOW); + } + if (currentGeofenceState.combState == 1) + { + Serial.print(F(" = Inside")); + digitalWrite(LED, HIGH); + } + else if (currentGeofenceState.combState == 2) + { + Serial.print(F(" = Outside")); + digitalWrite(LED, LOW); + } + + Serial.print(F(". The individual states are: ")); // Print the state of each geofence + for(int i = 0; i < currentGeofenceState.numFences; i++) + { + if (i > 0) Serial.print(F(",")); + Serial.print(currentGeofenceState.states[i]); + } + Serial.println(); + + delay(1000); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino b/lib/SparkFun u-blox Arduino Library/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino new file mode 100644 index 0000000..03dd5cb --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino @@ -0,0 +1,163 @@ +/* + Power Save Mode + By: Paul Clark (PaulZC) + Date: April 22nd, 2020 + + Based extensively on Example3_GetPosition + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to put the Ublox module into power save mode and then + query its lat/long/altitude. We also turn off the NMEA output on the I2C port. + This decreases the amount of I2C traffic dramatically. + + ** When it is able to ** the module will reduce its current draw. + For the ZOE-M8Q with a passive antenna, you should see the current drop + from (approx.) 25-28mA to (approx.) 9mA when power save mode kicks in. + + Note: this will fail on the ZED (protocol version >= 27) as UBX-CFG-RXM is not supported + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + //myGPS.saveConfiguration(); //Uncomment this line to save the current settings to flash and BBR + + Serial.println("Power save example."); + Serial.println("1) Enable power saving"); + Serial.println("2) Disable power saving"); +} + +void loop() +{ + if (Serial.available()) + { + byte incoming = Serial.read(); + + if (incoming == '1') + { + // Put the GNSS into power save mode + // (If you want to disable power save mode, call myGPS.powerSaveMode(false) instead) + // This will fail on the ZED (protocol version >= 27) as UBX-CFG-RXM is not supported + if (myGPS.powerSaveMode()) // Defaults to true + Serial.println(F("Power Save Mode enabled.")); + else + Serial.println(F("***!!! Power Save Mode FAILED !!!***")); + } + else if (incoming == '2') + { + //Go to normal power mode (not power saving mode) + if (myGPS.powerSaveMode(false)) + Serial.println(F("Power Save Mode disabled.")); + else + Serial.println(F("***!!! Power Save Disable FAILED !!!***")); + } + + // Read and print the new low power mode + uint8_t lowPowerMode = myGPS.getPowerSaveMode(); + if (lowPowerMode == 255) + { + Serial.println(F("***!!! getPowerSaveMode FAILED !!!***")); + } + else + { + Serial.print(F("The low power mode is: ")); + Serial.print(lowPowerMode); + if (lowPowerMode == 0) + { + Serial.println(F(" (Continuous)")); + } + else if (lowPowerMode == 1) + { + Serial.println(F(" (Power Save)")); + } + else if (lowPowerMode == 4) + { + Serial.println(F(" (Continuous)")); + } + else + { + Serial.println(F(" (Unknown!)")); + } + } + } + + //Query module every 10 seconds so it is easier to monitor the current draw + if (millis() - lastTime > 10000) + { + lastTime = millis(); //Update the timer + + byte fixType = myGPS.getFixType(); // Get the fix type + Serial.print(F("Fix: ")); + Serial.print(fixType); + if (fixType == 0) + Serial.print(F("(No fix)")); + else if (fixType == 1) + Serial.print(F("(Dead reckoning)")); + else if (fixType == 2) + Serial.print(F("(2D)")); + else if (fixType == 3) + Serial.print(F("(3D)")); + else if (fixType == 4) + Serial.print(F("(GNSS + Dead reckoning)")); + + long latitude = myGPS.getLatitude(); + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example19_DynamicModel/Example19_DynamicModel.ino b/lib/SparkFun u-blox Arduino Library/examples/Example19_DynamicModel/Example19_DynamicModel.ino new file mode 100644 index 0000000..20cbad6 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example19_DynamicModel/Example19_DynamicModel.ino @@ -0,0 +1,117 @@ +/* + Set Dynamic Model + By: Paul Clark (PaulZC) + Date: April 22nd, 2020 + + Based extensively on Example3_GetPosition + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to change the Ublox module's dynamic platform model and then + query its lat/long/altitude. We also turn off the NMEA output on the I2C port. + This decreases the amount of I2C traffic dramatically. + + Possible values for the dynamic model are: PORTABLE, STATIONARY, PEDESTRIAN, AUTOMOTIVE, + SEA, AIRBORNE1g, AIRBORNE2g, AIRBORNE4g, WRIST, BIKE + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun Ublox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // If we are going to change the dynamic platform model, let's do it here. + // Possible values are: + // PORTABLE, STATIONARY, PEDESTRIAN, AUTOMOTIVE, SEA, AIRBORNE1g, AIRBORNE2g, AIRBORNE4g, WRIST, BIKE + + if (myGPS.setDynamicModel(DYN_MODEL_PORTABLE) == false) // Set the dynamic model to PORTABLE + { + Serial.println(F("***!!! Warning: setDynamicModel failed !!!***")); + } + else + { + Serial.println(F("Dynamic platform model changed successfully!")); + } + + // Let's read the new dynamic model to see if it worked + uint8_t newDynamicModel = myGPS.getDynamicModel(); + if (newDynamicModel == 255) + { + Serial.println(F("***!!! Warning: getDynamicModel failed !!!***")); + } + else + { + Serial.print(F("The new dynamic model is: ")); + Serial.println(newDynamicModel); + } + + //myGPS.saveConfigSelective(VAL_CFG_SUBSEC_NAVCONF); //Uncomment this line to save only the NAV settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino b/lib/SparkFun u-blox Arduino Library/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino new file mode 100644 index 0000000..2f1d22f --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino @@ -0,0 +1,51 @@ +/* + Read NMEA sentences over I2C using Ublox module SAM-M8Q, NEO-M8P, ZED-F9P, etc + By: Nathan Seidle + SparkFun Electronics + Date: August 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads the NMEA setences from the Ublox module over I2c and outputs + them to the serial port + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + //This will pipe all NMEA sentences to the serial port so we can see them + myGPS.setNMEAOutputPort(Serial); +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino b/lib/SparkFun u-blox Arduino Library/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino new file mode 100644 index 0000000..82ef4a8 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino @@ -0,0 +1,161 @@ +/* + Send Custom Command + By: Paul Clark (PaulZC) + Date: April 20th, 2020 + + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how you can create and send a custom UBX packet + using the SparkFun u-blox library. + + Previously it was possible to create and send a custom packet + through the library but it would always appear to timeout as + some of the internal functions referred to the internal private + struct packetCfg. + The most recent version of the library allows sendCommand to + use a custom packet as if it were packetCfg and so: + - sendCommand will return a sfe_ublox_status_e enum as if + it had been called from within the library + - the custom packet will be updated with data returned by the module + (previously this was not possible from outside the library) + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#define NAV_RATE 20 // The new navigation rate in Hz (measurements per second) + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // Let's configure the module's navigation rate as if we were using setNavigationFrequency + + // 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}; + + // The structure of ubxPacket is: + // uint8_t cls : The message Class + // uint8_t id : The message ID + // uint16_t len : Length of the payload. Does not include cls, id, or checksum bytes + // uint16_t counter : Keeps track of number of overall bytes received. Some responses are larger than 255 bytes. + // uint16_t startingSpot : The counter value needed to go past before we begin recording into payload array + // uint8_t *payload : The payload + // uint8_t checksumA : Given to us by the module. Checked against the rolling calculated A/B checksums. + // uint8_t checksumB + // sfe_ublox_packet_validity_e valid : Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked + // sfe_ublox_packet_validity_e classAndIDmatch : Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID + + // sendCommand will return: + // SFE_UBLOX_STATUS_DATA_RECEIVED if the data we requested was read / polled successfully + // SFE_UBLOX_STATUS_DATA_SENT if the data we sent was writted successfully (ACK'd) + // Other values indicate errors. Please see the sfe_ublox_status_e enum for further details. + + // Referring to the u-blox M8 Receiver Description and Protocol Specification we see that + // the navigation rate is configured using the UBX-CFG-RATE message. So let's load our + // custom packet with the correct information so we can read (poll / get) the current settings. + + customCfg.cls = UBX_CLASS_CFG; // This is the message Class + customCfg.id = UBX_CFG_RATE; // 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) + + // We also need to tell sendCommand how long it should wait for a reply + uint16_t maxWait = 250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100) + + // Now let's read the current navigation rate. The results will be loaded into customCfg. + if (myGPS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + { + Serial.println(F("sendCommand (poll / get) failed! Freezing...")); + while (1) + ; + } + + // Referring to the message definition for UBX-CFG-RATE we see that the measurement rate + // is stored in payload bytes 0 and 1 as a uint16_t in LSB-first (little endian) format + + uint16_t rate = (customPayload[1] << 8) | customPayload[0]; // Extract the current rate (ms) + float f_rate = 1000.0 / ((float)rate); // Convert the navigation rate to Hz (measurements per second) + + // Print the current measurement rate + Serial.print(F("The current measurement rate is: ")); + Serial.println(f_rate, 1); + + // Let's change it + rate = 1000 / NAV_RATE; // Load the new value into rate + customPayload[0] = rate & 0xFF; // Store it in the payload + customPayload[1] = rate >> 8; + + // Print the new measurement rate + Serial.print(F("The new measurement rate will be: ")); + Serial.println(NAV_RATE); + + // We don't need to update customCfg.len as it will have been set to 6 + // when sendCommand read the data + + // Now we write the custom packet back again to change the setting + if (myGPS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_SENT) // This time we are only expecting an ACK + { + Serial.println(F("sendCommand (set) failed! Freezing.")); + while (1) + ; + } + else + { + Serial.println(F("Navigation rate updated. Here we go...")); + } + + myGPS.setAutoPVT(true); // Enable AutoPVT. The module will generate measurements automatically without being polled. + + //myGPS.saveConfigSelective(VAL_CFG_SUBSEC_NAVCONF); //Uncomment this line to save only the NAV settings to flash and BBR +} + +void loop() +{ + //Query the module as fast as possible + int32_t latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + int32_t longitude = myGPS.getLongitude(); + Serial.print(F(" Lon: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + int32_t altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.println(F(" (mm)")); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino b/lib/SparkFun u-blox Arduino Library/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino new file mode 100644 index 0000000..ca487c3 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino @@ -0,0 +1,183 @@ +/* + Module Info - extracts and prints the full module information from UBX_MON_VER + using a custom command. + By: @mayopan + Date: May 9th, 2020 + + Based on: + Send Custom Command + By: Paul Clark (PaulZC) + Date: April 20th, 2020 + + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Previously it was possible to create and send a custom packet + through the library but it would always appear to timeout as + some of the internal functions referred to the internal private + struct packetCfg. + The most recent version of the library allows sendCommand to + use a custom packet as if it were packetCfg and so: + - sendCommand will return a sfe_ublox_status_e enum as if + it had been called from within the library + - the custom packet will be updated with data returned by the module + (previously this was not possible from outside the library) + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#define MAX_PAYLOAD_SIZE 384 // Override MAX_PAYLOAD_SIZE for getModuleInfo which can return up to 348 bytes + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS + +// Extend the class for getModuleInfo +class SFE_UBLOX_GPS_ADD : public SFE_UBLOX_GPS +{ +public: + boolean getModuleInfo(uint16_t maxWait = 1100); //Queries module, texts + + struct minfoStructure // Structure to hold the module info (uses 341 bytes of RAM) + { + char swVersion[30]; + char hwVersion[10]; + uint8_t extensionNo = 0; + char extension[10][30]; + } minfo; +}; + +SFE_UBLOX_GPS_ADD myGPS; + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun Ublox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + Serial.print(F("Polling module info")); + if (myGPS.getModuleInfo(1100) == false) // Try to get the module info + { + Serial.print(F("getModuleInfo failed! Freezing...")); + while (1) + ; + } + + Serial.println(); + Serial.println(); + Serial.println(F("Module Info : ")); + Serial.print(F("Soft version: ")); + Serial.println(myGPS.minfo.swVersion); + Serial.print(F("Hard version: ")); + Serial.println(myGPS.minfo.hwVersion); + Serial.print(F("Extensions:")); + Serial.println(myGPS.minfo.extensionNo); + for (int i = 0; i < myGPS.minfo.extensionNo; i++) + { + Serial.print(" "); + Serial.println(myGPS.minfo.extension[i]); + } + Serial.println(); + Serial.println(F("Done!")); +} + +void loop() +{ +} + +boolean SFE_UBLOX_GPS_ADD::getModuleInfo(uint16_t maxWait) +{ + myGPS.minfo.hwVersion[0] = 0; + myGPS.minfo.swVersion[0] = 0; + for (int i = 0; i < 10; i++) + myGPS.minfo.extension[i][0] = 0; + myGPS.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}; + + // The structure of ubxPacket is: + // uint8_t cls : The message Class + // uint8_t id : The message ID + // uint16_t len : Length of the payload. Does not include cls, id, or checksum bytes + // uint16_t counter : Keeps track of number of overall bytes received. Some responses are larger than 255 bytes. + // uint16_t startingSpot : The counter value needed to go past before we begin recording into payload array + // uint8_t *payload : The payload + // uint8_t checksumA : Given to us by the module. Checked against the rolling calculated A/B checksums. + // uint8_t checksumB + // sfe_ublox_packet_validity_e valid : Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked + // sfe_ublox_packet_validity_e classAndIDmatch : Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID + + // sendCommand will return: + // SFE_UBLOX_STATUS_DATA_RECEIVED if the data we requested was read / polled successfully + // SFE_UBLOX_STATUS_DATA_SENT if the data we sent was writted successfully (ACK'd) + // Other values indicate errors. Please see the sfe_ublox_status_e enum for further details. + + // Referring to the u-blox M8 Receiver Description and Protocol Specification we see that + // the module information can be read using the UBX-MON-VER message. So let's load our + // custom packet with the correct information so we can read (poll / get) the module information. + + 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! +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example22_PowerOff/Example22_PowerOff.ino b/lib/SparkFun u-blox Arduino Library/examples/Example22_PowerOff/Example22_PowerOff.ino new file mode 100644 index 0000000..1cd5a8c --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example22_PowerOff/Example22_PowerOff.ino @@ -0,0 +1,87 @@ +/* + Powering off a ublox GPS module + By: bjorn + unsurv.org + Date: July 20th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows you how to turn off the ublox module to lower the power consumption. + There are two functions: one just specifies a duration in milliseconds the other also specifies a pin on the GPS device to wake it up with. + By driving a voltage from LOW to HIGH or HIGH to LOW on the chosen module pin you wake the device back up. + Note: Doing so on the INT0 pin when using the regular powerOff(durationInMs) function will wake the device anyway. (tested on SAM-M8Q) + Note: While powered off, you should not query the device for data or it might wake up. This can be used to wake the device but is not recommended. + Works best when also putting your microcontroller to sleep. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard. + To force the device to wake up you need to connect to a pin (for example INT0) seperately on the module. + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS + +// define a digital pin capable of driving HIGH and LOW +#define WAKEUP_PIN 5 + +// Possible GNSS interrupt pins for powerOffWithInterrupt are: +// VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX = uartrx +// VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = extint0 (default) +// VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = extint1 +// VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS = spics +// These values can be or'd (|) together to enable interrupts on multiple pins + +void wakeUp() { + + Serial.print("-- waking up module via pin " + String(WAKEUP_PIN)); + Serial.println(" on your microcontroller --"); + + digitalWrite(WAKEUP_PIN, LOW); + delay(1000); + digitalWrite(WAKEUP_PIN, HIGH); + delay(1000); + digitalWrite(WAKEUP_PIN, LOW); +} + + +void setup() { + + pinMode(WAKEUP_PIN, OUTPUT); + digitalWrite(WAKEUP_PIN, LOW); + + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Enable debug messages + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + // Powering off for 20s, you should see the power consumption drop. + Serial.println("-- Powering off module for 20s --"); + + myGPS.powerOff(20000); + //myGPS.powerOffWithInterrupt(20000, VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0); + + delay(10000); + + // After 10 seconds wake the device via the specified pin on your microcontroller and module. + wakeUp(); +} + +void loop() { + //Do nothing +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino b/lib/SparkFun u-blox Arduino Library/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino new file mode 100644 index 0000000..84e3f51 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino @@ -0,0 +1,83 @@ +/* + Read NMEA sentences over I2C using Ublox module SAM-M8Q, NEO-M8P, etc + By: Nathan Seidle + SparkFun Electronics + Date: August 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads the NMEA characters over I2C and pipes them to MicroNMEA + This example will output your current long/lat and satellites in view + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + For more MicroNMEA info see https://github.com/stevemarple/MicroNMEA + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + Go outside! Wait ~25 seconds and you should see your lat/long +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include //http://librarymanager/All#MicroNMEA +char nmeaBuffer[100]; +MicroNMEA nmea(nmeaBuffer, sizeof(nmeaBuffer)); + +void setup() +{ + Serial.begin(115200); + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + if(nmea.isValid() == true) + { + long latitude_mdeg = nmea.getLatitude(); + long longitude_mdeg = nmea.getLongitude(); + + Serial.print("Latitude (deg): "); + Serial.println(latitude_mdeg / 1000000., 6); + Serial.print("Longitude (deg): "); + Serial.println(longitude_mdeg / 1000000., 6); + } + else + { + Serial.print("No Fix - "); + Serial.print("Num. satellites: "); + Serial.println(nmea.getNumSatellites()); + } + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun Ublox Arduino Library +//As each NMEA character comes in you can specify what to do with it +//Useful for passing to other libraries like tinyGPS, MicroNMEA, or even +//a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //Take the incoming char from the Ublox I2C port and pass it on to the MicroNMEA lib + //for sentence cracking + nmea.process(incoming); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example3_GetPosition/Example3_GetPosition.ino b/lib/SparkFun u-blox Arduino Library/examples/Example3_GetPosition/Example3_GetPosition.ino new file mode 100644 index 0000000..b87e6eb --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example3_GetPosition/Example3_GetPosition.ino @@ -0,0 +1,84 @@ +/* + Reading lat and long via UBX binary commands - no more NMEA parsing! + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for its lat/long/altitude. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example4_FixType/Example4_FixType.ino b/lib/SparkFun u-blox Arduino Library/examples/Example4_FixType/Example4_FixType.ino new file mode 100644 index 0000000..1fd99db --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example4_FixType/Example4_FixType.ino @@ -0,0 +1,94 @@ +/* + Get fix type and RTK fix type if available + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for fix type and RTK fix type. + The fix type is as follows: + 0 = no fix + 1 = dead reckoning (requires external sensors) + 2 = 2D (not quite enough satellites in view) + 3 = 3D (the standard fix) + 4 = GNSS + dead reckoning (requires external sensors) + 5 = Time fix only + + Additionally, if we are doing RTK, we can figure out if we have a floating + RTK solution or if we have been able to resolve a fixec solution (better precision). + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + Wire.setClock(400000); //Optional. Increase I2C clock speed to 400kHz. + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + + byte fixType = myGPS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + else if(fixType == 1) Serial.print(F("Dead reckoning")); + else if(fixType == 2) Serial.print(F("2D")); + else if(fixType == 3) Serial.print(F("3D")); + else if(fixType == 4) Serial.print(F("GNSS+Dead reckoning")); + + byte RTK = myGPS.getCarrierSolutionType(); + Serial.print(" RTK: "); + Serial.print(RTK); + if (RTK == 1) Serial.print(F("High precision float fix!")); + if (RTK == 2) Serial.print(F("High precision fix!")); + + Serial.println(); + } + +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino b/lib/SparkFun u-blox Arduino Library/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino new file mode 100644 index 0000000..0c5d996 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino @@ -0,0 +1,83 @@ +/* + Get Speed/Heading and dilution of precision via UBX binary commands - no more NMEA parsing! + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for its lat/long/altitude. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 1,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + + long speed = myGPS.getGroundSpeed(); + Serial.print(F(" Speed: ")); + Serial.print(speed); + Serial.print(F(" (mm/s)")); + + long heading = myGPS.getHeading(); + Serial.print(F(" Heading: ")); + Serial.print(heading); + Serial.print(F(" (degrees * 10^-5)")); + + int pDOP = myGPS.getPDOP(); + Serial.print(F(" pDOP: ")); + Serial.print(pDOP / 100.0, 2); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino b/lib/SparkFun u-blox Arduino Library/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino new file mode 100644 index 0000000..7ba4b2e --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino @@ -0,0 +1,79 @@ +/* + Turn on/off various NMEA sentences. + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to turn on/off the NMEA sentences being output + over UART1. We use the I2C interface on the Ublox module for configuration + but you won't see any output from this sketch. You'll need to hook up + a Serial Basic or other USB to Serial device to UART1 on your Ublox module + to see the output. + + This example turns off all sentences except for the GPGGA and GPVTG sentences. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a RedBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + Hookup a Serial Basic (https://www.sparkfun.com/products/15096) to UART1 on the Ublox module. Open a terminal at 57600bps + and see GPGGA and GPVTG sentences. +*/ +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +unsigned long lastGPSSend = 0; + +void setup() +{ + Serial.begin(115200); // Serial debug output over USB visible from Arduino IDE + Serial.println("Example showing how to enable/disable certain NMEA sentences"); + + Wire.begin(); + + if (myGPS.begin() == false) + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + //Disable or enable various NMEA sentences over the UART1 interface + myGPS.disableNMEAMessage(UBX_NMEA_GLL, COM_PORT_UART1); //Several of these are on by default on virgin ublox board so let's disable them + myGPS.disableNMEAMessage(UBX_NMEA_GSA, COM_PORT_UART1); + myGPS.disableNMEAMessage(UBX_NMEA_GSV, COM_PORT_UART1); + myGPS.disableNMEAMessage(UBX_NMEA_RMC, COM_PORT_UART1); + myGPS.enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_UART1); //Only leaving GGA/VTG enabled at current navigation rate + myGPS.enableNMEAMessage(UBX_NMEA_VTG, COM_PORT_UART1); + + //Here's the advanced configure method + //Some of the other examples in this library enable the PVT message so let's disable it + myGPS.configureMessage(UBX_CLASS_NAV, UBX_NAV_PVT, COM_PORT_UART1, 0); //Message Class, ID, and port we want to configure, sendRate of 0 (disable). + + myGPS.setUART1Output(COM_TYPE_NMEA); //Turn off UBX and RTCM sentences on the UART1 interface + + myGPS.setSerialRate(57600); //Set UART1 to 57600bps. + + myGPS.saveConfiguration(); //Save these settings to NVM + + Serial.println(F("Messages configured. NMEA now being output over the UART1 port on the Ublox module at 57600bps.")); +} + +void loop() +{ + if (millis() - lastGPSSend > 200) + { + myGPS.checkUblox(); //See if new data is available, but we don't want to get NMEA here. Go check UART1. + lastGPSSend = millis(); + } +} \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example7_OutputRate/Example7_OutputRate.ino b/lib/SparkFun u-blox Arduino Library/examples/Example7_OutputRate/Example7_OutputRate.ino new file mode 100644 index 0000000..4ad90ee --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example7_OutputRate/Example7_OutputRate.ino @@ -0,0 +1,91 @@ +/* + Set update rate to 10Hz + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to increase the output of the module from 1Hz to 4Hz. + The max output rate various from model to model. RTFM! But you cannot do harm + to the module. + + We also disable NMEA output on the I2C bus and use only UBX. This dramatically + decreases the amount of data that needs to be transmitted. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. +long startTime = 0; //Used to calc the actual update rate. +long updateCount = 0; //Used to calc the actual update rate. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + Wire.setClock(400000); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(10); //Set output to 10 times a second + + byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate:"); + Serial.println(rate); + + startTime = millis(); +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available. This is defined + //by the update freq. + if (millis() - lastTime > 25) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + + updateCount++; + + //Calculate the actual update rate based on the sketch start time and the + //number of updates we've received. + Serial.print(F(" Rate: ")); + Serial.print( updateCount / ((millis() - startTime) / 1000.0), 2); + Serial.print(F("Hz")); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino b/lib/SparkFun u-blox Arduino Library/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino new file mode 100644 index 0000000..ec35235 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino @@ -0,0 +1,61 @@ +/* + Reading the protocol version of a Ublox module + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for its protocol version. + + Various modules have various protocol version. We've seen v18 up to v27. Depending + on the protocol version there are different commands available. This is a handy + way to predict which commands will or won't work. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + Serial.print(F("Version: ")); + byte versionHigh = myGPS.getProtocolVersionHigh(); + Serial.print(versionHigh); + Serial.print("."); + byte versionLow = myGPS.getProtocolVersionLow(); + Serial.print(versionLow); +} + +void loop() +{ + //Do nothing +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino b/lib/SparkFun u-blox Arduino Library/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino new file mode 100644 index 0000000..19a2c33 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino @@ -0,0 +1,112 @@ +/* + Change the I2C address of a Ublox module using I2C + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to change the I2C address of a Ublox module + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Tracks the passing of 2000ms (2 seconds) + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + byte oldAddress = 0x42; //The default address for Ublox modules is 0x42 + byte newAddress = 0x3F; //Address you want to change to. Valid is 0x08 to 0x77. + + while (Serial.available()) Serial.read(); //Trash any incoming chars + Serial.print("Press a key to change address to 0x"); + Serial.println(newAddress, HEX); + while (Serial.available() == false) ; //Wait for user to send character + + if (myGPS.begin(Wire, oldAddress) == true) //Connect to the Ublox module using Wire port and the old address + { + Serial.print("GPS found at address 0x"); + Serial.println(oldAddress, HEX); + + myGPS.setI2CAddress(newAddress); //Change I2C address of this device + //Device's I2C address is stored to memory and loaded on each power-on + + if (myGPS.begin(Wire, newAddress) == true) + { + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + Serial.print("Address successfully changed to 0x"); + Serial.println(newAddress, HEX); + Serial.print("Now load another example sketch using .begin(Wire, 0x"); + Serial.print(newAddress, HEX); + Serial.println(") to use this GPS module"); + Serial.println("Freezing..."); + while (1); + } + } + + //Something went wrong, begin looking for the I2C device + Serial.println("Address change failed. Beginning an I2C scan."); + + Wire.begin(); +} + +void loop() { + + byte address; + int nDevices; + + Serial.println("Scanning..."); + + nDevices = 0; + for (address = 1; address < 127; address++ ) + { + Wire.beginTransmission(address); + byte error = Wire.endTransmission(); + + if (error == 0) + { + Serial.print("I2C device found at address 0x"); + if (address < 16) + Serial.print("0"); + Serial.print(address, HEX); + Serial.println(" !"); + + nDevices++; + } + else if (error == 4) + { + Serial.print("Unknown error at address 0x"); + if (address < 16) + Serial.print("0"); + Serial.println(address, HEX); + } + } + + if (nDevices == 0) + Serial.println("No I2C devices found\n"); + else + Serial.println("done\n"); + + delay(5000); // wait 5 seconds for next scan +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino b/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino new file mode 100644 index 0000000..368cb8d --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino @@ -0,0 +1,77 @@ +/* + Send UBX binary commands to enable RTCM sentences on Ublox NEO-M8P module + By: Nathan Seidle + SparkFun Electronics + Date: September 7th, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example sends the command to enable the four RTCM messages needed for RTK. This + is the first part of a larger tutorial and example to setup an RTK base station. + These commands are only accepted by the NEO-M8P module. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while(!Serial); //Wait for user to open terminal + Serial.println("Ublox RTCM Enable Example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + while(Serial.available()) Serial.read(); //Clear any latent chars in serial buffer + Serial.println("Press any key to send commands to enable RTCM 3.x"); + while(Serial.available() == 0) ; //Wait for user to press a key + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + boolean response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1077, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1087, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == true) + { + Serial.println("RTCM messages enabled"); + } + else + { + Serial.println("RTCM failed to enable. Are you sure you have an NEO-M8P?"); + while(1); //Freeze + } + + //RTCM is now enabled but we haven't done a 'survey-in' + //See example 4 for the full Base RTK setup +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino b/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino new file mode 100644 index 0000000..b54d9cb --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino @@ -0,0 +1,153 @@ +/* + Send UBX binary commands to enable RTCM sentences on Ublox NEO-M8P-2 module + By: Nathan Seidle + SparkFun Electronics + Date: September 7th, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a NEO-M8P-2 as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable four RTCM messages + Begin outputting RTCM bytes + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("Ublox NEO-M8P-2 base station example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + while (Serial.available()) Serial.read(); //Clear any latent chars in serial buffer + Serial.println("Press any key to send commands to begin Survey-In"); + while (Serial.available() == 0) ; //Wait for user to press a key + + boolean response; + + //Check if Survey is in Progress before initiating one + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + if (response == false) + { + Serial.println("Failed to get Survey In status"); + while (1); //Freeze + } + + if (myGPS.svin.active == true) + { + Serial.print("Survey already in progress."); + } + else + { + //Start survey + response = myGPS.enableSurveyMode(300, 2.000); //Enable Survey in, 300 seconds, 2.0m + if (response == false) + { + Serial.println("Survey start failed"); + while (1); + } + Serial.println("Survey started. This will run until 300s has passed and less than 2m accuracy is achieved."); + } + + while(Serial.available()) Serial.read(); //Clear buffer + + //Begin waiting for survey to complete + while (myGPS.svin.valid == false) + { + if(Serial.available()) + { + byte incoming = Serial.read(); + if(incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println("Survey stopped"); + break; + } + } + + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) + { + Serial.print("Press x to end survey - "); + Serial.print("Time elapsed: "); + Serial.print((String)myGPS.svin.observationTime); + + Serial.print(" Accuracy: "); + Serial.print((String)myGPS.svin.meanAccuracy); + Serial.println(); + } + else + { + Serial.println("SVIN request failed"); + } + + delay(1000); + } + Serial.println("Survey valid!"); + + response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1077, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1087, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == true) + { + Serial.println("RTCM messages enabled"); + } + else + { + Serial.println("RTCM failed to enable. Are you sure you have an NEO-M8P?"); + while (1); //Freeze + } + + Serial.println("Base survey complete! RTCM now broadcasting."); +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun Ublox 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_GPS::processRTCM(uint8_t incoming) +{ + //Let's just pretty-print the HEX values for now + if (myGPS.rtcmFrameCounter % 16 == 0) Serial.println(); + Serial.print(" "); + if (incoming < 0x10) Serial.print("0"); + Serial.print(incoming, HEX); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino b/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino new file mode 100644 index 0000000..b3163e8 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino @@ -0,0 +1,185 @@ +/* + Send UBX binary commands to enable RTCM sentences on Ublox NEO-M8P-2 module + By: Nathan Seidle + SparkFun Electronics + Date: September 7th, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a NEO-M8P-2 as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable four RTCM messages + Begin outputting RTCM bytes + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + Plug a SerLCD onto the Qwiic bus + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Watch the output on the LCD or open the serial monitor at 115200 baud to see the output +*/ + +#define STAT_LED 13 + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include //Click here to get the library: http://librarymanager/All#SparkFun_SerLCD +SerLCD lcd; // Initialize the library with default I2C address 0x72 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("Ublox GPS I2C Test"); + + Wire.begin(); + + pinMode(STAT_LED, OUTPUT); + digitalWrite(STAT_LED, LOW); + + lcd.begin(Wire); //Set up the LCD for Serial communication at 9600bps + lcd.setBacklight(0x4B0082); //indigo, a kind of dark purplish blue + lcd.clear(); + lcd.print(F("LCD Ready")); + + myGPS.begin(Wire); + if (myGPS.isConnected() == false) + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + lcd.setCursor(0, 1); + lcd.print(F("No GPS detected")); + while (1); + } + + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + lcd.setCursor(0, 1); + lcd.print("GPS Detected"); + + //Check if Survey is in Progress before initiating one + boolean response; + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + if (response == false) + { + Serial.println(F("Failed to get Survey In status")); + while (1); //Freeze + } + + if (myGPS.svin.active == true) + { + Serial.print(F("Survey already in progress.")); + lcd.setCursor(0, 2); + lcd.print(F("Survey already going")); + } + else + { + //Start survey + response = myGPS.enableSurveyMode(300, 2.000); //Enable Survey in, 300 seconds, 2.0m + if (response == false) + { + Serial.println(F("Survey start failed")); + lcd.setCursor(0, 3); + lcd.print(F("Survey start failed")); + while (1); + } + Serial.println(F("Survey started. This will run until 300s has passed and less than 2m accuracy is achieved.")); + } + + while (Serial.available()) Serial.read(); //Clear buffer + + lcd.clear(); + lcd.print(F("Survey in progress")); + + //Begin waiting for survey to complete + while (myGPS.svin.valid == false) + { + if (Serial.available()) + { + byte incoming = Serial.read(); + if (incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println(F("Survey stopped")); + break; + } + } + + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) + { + Serial.print(F("Press x to end survey - ")); + Serial.print(F("Time elapsed: ")); + Serial.print((String)myGPS.svin.observationTime); + + lcd.setCursor(0, 1); + lcd.print(F("Elapsed: ")); + lcd.print((String)myGPS.svin.observationTime); + + Serial.print(F(" Accuracy: ")); + Serial.print((String)myGPS.svin.meanAccuracy); + Serial.println(); + + lcd.setCursor(0, 2); + lcd.print(F("Accuracy: ")); + lcd.print((String)myGPS.svin.meanAccuracy); + } + else + { + Serial.println(F("SVIN request failed")); + } + + delay(1000); + } + Serial.println(F("Survey valid!")); + + response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, UBX_RTCM_I2C_PORT, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1077, UBX_RTCM_I2C_PORT, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1087, UBX_RTCM_I2C_PORT, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, UBX_RTCM_I2C_PORT, 10); //Enable message every 10 seconds + + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an NEO-M8P?")); + while (1); //Freeze + } + + Serial.println(F("Base survey complete! RTCM now broadcasting.")); + lcd.clear(); + lcd.print(F("Transmitting RTCM")); +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + //Do anything you want. Call checkUblox() every second. NEO-M8P-2 has TX buffer of 4k bytes. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun Ublox 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_GPS::processRTCM(uint8_t incoming) +{ + //Let's just pretty-print the HEX values for now + if (myGPS.rtcmFrameCounter % 16 == 0) Serial.println(); + Serial.print(" "); + if (incoming < 0x10) Serial.print("0"); + Serial.print(incoming, HEX); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino b/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino new file mode 100644 index 0000000..158e57d --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino @@ -0,0 +1,109 @@ +/* + Reading lat, long and UTC time via UBX binary commands - no more NMEA parsing! + By: Paul Clark and Nathan Seidle + Using the library modifications provided by @blazczak and @geeksville + + SparkFun Electronics + Date: June 16th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a Ublox module for its lat/long/altitude. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library_Series_6_7.h" +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + Serial.print(F(" Time: ")); + + byte Hour = myGPS.getHour(); + if (Hour < 10) + { + Serial.print(F("0")); + } + Serial.print(Hour); + Serial.print(F(":")); + + byte Minute = myGPS.getMinute(); + if (Minute < 10) + { + Serial.print(F("0")); + } + Serial.print(Minute); + Serial.print(F(":")); + + byte Second = myGPS.getSecond(); + if (Second < 10) + { + Serial.print(F("0")); + } + Serial.print(Second); + + Serial.println(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp b/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp new file mode 100644 index 0000000..a6c04b6 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp @@ -0,0 +1,3477 @@ +/* + This is a library written for the Ublox ZED-F9P and NEO-M8P-2 + + Updated: June 16th, 2020 + + This copy includes changes by @blazczak and @geeksville to + provide support for the older series 6 and 7 modules. + + Disclaimer: SparkFun has not verified this copy of the library on either series 6 or 7. + It should work, it looks like it will work, but we have no way of confirming this. + We cannot guarantee that it will work reliably in your application. + + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original library written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a Ublox GPS module. Works with most modules from Ublox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "SparkFun_Ublox_Arduino_Library_Series_6_7.h" + +SFE_UBLOX_GPS::SFE_UBLOX_GPS(void) +{ + // Constructor + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + moduleQueried.versionNumber = false; + + if (checksumFailurePin >= 0) + { + pinMode((uint8_t)checksumFailurePin, OUTPUT); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(TwoWire &wirePort, uint8_t deviceAddress) +{ + commType = COMM_TYPE_I2C; + _i2cPort = &wirePort; //Grab which port the user wants us to use + + //We expect caller to begin their I2C port, with the speed of their choice external to the library + //But if they forget, we start the hardware here. + + //We're moving away from the practice of starting Wire hardware in a library. This is to avoid cross platform issues. + //ie, there are some platforms that don't handle multiple starts to the wire hardware. Also, every time you start the wire + //hardware the clock speed reverts back to 100kHz regardless of previous Wire.setClocks(). + //_i2cPort->begin(); + + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + + return (isConnected()); +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(Stream &serialPort) +{ + commType = COMM_TYPE_SERIAL; + _serialPort = &serialPort; //Grab which port the user wants us to use + + return (isConnected()); +} + +//Enable or disable the printing of sent/response HEX values. +//Use this in conjunction with 'Transport Logging' from the Universal Reader Assistant to see what they're doing that we're not +void SFE_UBLOX_GPS::enableDebugging(Stream &debugPort, boolean printLimitedDebug) +{ + _debugSerial = &debugPort; //Grab which port the user wants us to use for debugging + if (printLimitedDebug == false) + { + _printDebug = true; //Should we print the commands we send? Good for debugging + } + else + { + _printLimitedDebug = true; //Should we print limited debug messages? Good for debugging high navigation rates + } +} +void SFE_UBLOX_GPS::disableDebugging(void) +{ + _printDebug = false; //Turn off extra print statements + _printLimitedDebug = false; +} + +//Safely print messages +void SFE_UBLOX_GPS::debugPrint(char *message) +{ + if (_printDebug == true) + { + _debugSerial->print(message); + } +} +//Safely print messages +void SFE_UBLOX_GPS::debugPrintln(char *message) +{ + if (_printDebug == true) + { + _debugSerial->println(message); + } +} + +const char *SFE_UBLOX_GPS::statusString(sfe_ublox_status_e stat) +{ + switch (stat) + { + case SFE_UBLOX_STATUS_SUCCESS: + return "Success"; + break; + case SFE_UBLOX_STATUS_FAIL: + return "General Failure"; + break; + case SFE_UBLOX_STATUS_CRC_FAIL: + return "CRC Fail"; + break; + case SFE_UBLOX_STATUS_TIMEOUT: + return "Timeout"; + break; + case SFE_UBLOX_STATUS_COMMAND_NACK: + return "Command not acknowledged (NACK)"; + break; + case SFE_UBLOX_STATUS_OUT_OF_RANGE: + return "Out of range"; + break; + case SFE_UBLOX_STATUS_INVALID_ARG: + return "Invalid Arg"; + break; + case SFE_UBLOX_STATUS_INVALID_OPERATION: + return "Invalid operation"; + break; + case SFE_UBLOX_STATUS_MEM_ERR: + return "Memory Error"; + break; + case SFE_UBLOX_STATUS_HW_ERR: + return "Hardware Error"; + break; + case SFE_UBLOX_STATUS_DATA_SENT: + return "Data Sent"; + break; + case SFE_UBLOX_STATUS_DATA_RECEIVED: + return "Data Received"; + break; + case SFE_UBLOX_STATUS_I2C_COMM_FAILURE: + return "I2C Comm Failure"; + break; + case SFE_UBLOX_STATUS_DATA_OVERWRITTEN: + return "Data Packet Overwritten"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + +void SFE_UBLOX_GPS::factoryReset() +{ + // Copy default settings to permanent + // Note: this does not load the permanent configuration into the current configuration. Calling factoryDefault() will do that. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 13; + packetCfg.startingSpot = 0; + for (uint8_t i = 0; i < 4; i++) + { + payloadCfg[0 + i] = 0xff; // clear mask: copy default config to permanent config + payloadCfg[4 + i] = 0x00; // save mask: don't save current to permanent + payloadCfg[8 + i] = 0x00; // load mask: don't copy permanent config to current + } + payloadCfg[12] = 0xff; // all forms of permanent memory + sendCommand(&packetCfg, 0); // don't expect ACK + hardReset(); // cause factory default config to actually be loaded and used cleanly +} + +void SFE_UBLOX_GPS::hardReset() +{ + // Issue hard reset + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RST; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + payloadCfg[0] = 0xff; // cold start + payloadCfg[1] = 0xff; // cold start + payloadCfg[2] = 0; // 0=HW reset + payloadCfg[3] = 0; // reserved + sendCommand(&packetCfg, 0); // don't expect ACK +} + +//Changes the serial baud rate of the Ublox module, can't return success/fail 'cause ACK from modem +//is lost due to baud rate change +void SFE_UBLOX_GPS::setSerialRate(uint32_t baudrate, uint8_t uartPort, uint16_t maxWait) +{ + //Get the current config values for the UART port + getPortSettings(uartPort, maxWait); //This will load the payloadCfg array with current port settings + + if (_printDebug == true) + { + _debugSerial->print(F("Current baud rate: ")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[8] = baudrate; + payloadCfg[9] = baudrate >> 8; + payloadCfg[10] = baudrate >> 16; + payloadCfg[11] = baudrate >> 24; + + if (_printDebug == true) + { + _debugSerial->print(F("New baud rate:")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("setSerialRate: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } +} + +//Changes the I2C address that the Ublox module responds to +//0x42 is the default but can be changed with this command +boolean SFE_UBLOX_GPS::setI2CAddress(uint8_t deviceAddress, uint16_t maxWait) +{ + //Get the current config values for the I2C port + getPortSettings(COM_PORT_I2C, maxWait); //This will load the payloadCfg array with current port settings + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[4] = deviceAddress << 1; //DDC mode LSB + + if (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT) // We are only expecting an ACK + { + //Success! Now change our internal global. + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + return (true); + } + return (false); +} + +//Want to see the NMEA messages on the Serial port? Here's how +void SFE_UBLOX_GPS::setNMEAOutputPort(Stream &nmeaOutputPort) +{ + _nmeaOutputPort = &nmeaOutputPort; //Store the port from user +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUblox(uint8_t requestedClass, uint8_t requestedID) +{ + return checkUbloxInternal(&packetCfg, requestedClass, requestedID); +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (commType == COMM_TYPE_I2C) + return (checkUbloxI2C(incomingUBX, requestedClass, requestedID)); + else if (commType == COMM_TYPE_SERIAL) + return (checkUbloxSerial(incomingUBX, requestedClass, requestedID)); + return false; +} + +//Polls I2C for data, passing any new bytes to process() +//Returns true if new bytes are available +boolean SFE_UBLOX_GPS::checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (millis() - lastCheck >= i2cPollingWait) + { + //Get the number of bytes available from the module + uint16_t bytesAvailable = 0; + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFD); //0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)2); + if (_i2cPort->available()) + { + uint8_t msb = _i2cPort->read(); + uint8_t lsb = _i2cPort->read(); + if (lsb == 0xFF) + { + //I believe this is a Ublox bug. Device should never present an 0xFF. + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxI2C: Ublox bug, length lsb is 0xFF")); + } + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + bytesAvailable = (uint16_t)msb << 8 | lsb; + } + + if (bytesAvailable == 0) + { + if (_printDebug == true) + { + _debugSerial->println(F("checkUbloxI2C: OK, zero bytes available")); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + + //Check for undocumented bit error. We found this doing logic scans. + //This error is rare but if we incorrectly interpret the first bit of the two 'data available' bytes as 1 + //then we have far too many bytes to check. May be related to I2C setup time violations: https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40 + if (bytesAvailable & ((uint16_t)1 << 15)) + { + //Clear the MSbit + bytesAvailable &= ~((uint16_t)1 << 15); + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->print(F("checkUbloxI2C: Bytes available error:")); + _debugSerial->println(bytesAvailable); + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + } + } + + if (bytesAvailable > 100) + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Large packet of ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes received")); + } + } + else + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Reading ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes")); + } + } + + while (bytesAvailable) + { + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFF); //0xFF is the register to read data from + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + //Limit to 32 bytes or whatever the buffer limit is for given platform + uint16_t bytesToRead = bytesAvailable; + if (bytesToRead > I2C_BUFFER_LENGTH) + bytesToRead = I2C_BUFFER_LENGTH; + + TRY_AGAIN: + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)bytesToRead); + if (_i2cPort->available()) + { + for (uint16_t x = 0; x < bytesToRead; x++) + { + uint8_t incoming = _i2cPort->read(); //Grab the actual character + + //Check to see if the first read is 0x7F. If it is, the module is not ready + //to respond. Stop, wait, and try again + if (x == 0) + { + if (incoming == 0x7F) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxU2C: Ublox error, module not ready with data")); + } + delay(5); //In logic analyzation, the module starting responding after 1.48ms + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + goto TRY_AGAIN; + } + } + + process(incoming, incomingUBX, requestedClass, requestedID); //Process this valid character + } + } + else + return (false); //Sensor did not respond + + bytesAvailable -= bytesToRead; + } + } + + return (true); + +} //end checkUbloxI2C() + +//Checks Serial for data, passing any new bytes to process() +boolean SFE_UBLOX_GPS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + while (_serialPort->available()) + { + process(_serialPort->read(), incomingUBX, requestedClass, requestedID); + } + return (true); + +} //end checkUbloxSerial() + +//Processes NMEA and UBX binary sentences one byte at a time +//Take a given byte and file it into the proper array +void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if ((currentSentence == NONE) || (currentSentence == NMEA)) + { + if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + { + //This is the start of a binary sentence. Reset flags. + //We still don't know the response class + ubxFrameCounter = 0; + currentSentence = UBX; + //Reset the packetBuf.counter even though we will need to reset it again when ubxFrameCounter == 2 + packetBuf.counter = 0; + ignoreThisPayload = false; //We should not ignore this payload - yet + //Store data in packetBuf until we know if we have a requested class and ID match + activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + } + else if (incoming == '$') + { + currentSentence = NMEA; + } + else if (incoming == 0xD3) //RTCM frames start with 0xD3 + { + rtcmFrameCounter = 0; + currentSentence = RTCM; + } + else + { + //This character is unknown or we missed the previous start of a sentence + } + } + + //Depending on the sentence, pass the character to the individual processor + if (currentSentence == UBX) + { + //Decide what type of response this is + if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + currentSentence = NONE; //Something went wrong. Reset. + else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + currentSentence = NONE; //Something went wrong. Reset. + // Note to future self: + // There may be some duplication / redundancy in the next few lines as processUBX will also + // load information into packetBuf, but we'll do it here too for clarity + else if (ubxFrameCounter == 2) //Class + { + // Record the class in packetBuf until we know what to do with it + packetBuf.cls = incoming; // (Duplication) + rollingChecksumA = 0; //Reset our rolling checksums here (not when we receive the 0xB5) + rollingChecksumB = 0; + packetBuf.counter = 0; //Reset the packetBuf.counter (again) + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // Reset the packet validity (redundant?) + packetBuf.startingSpot = incomingUBX->startingSpot; //Copy the startingSpot + } + else if (ubxFrameCounter == 3) //ID + { + // Record the ID in packetBuf until we know what to do with it + packetBuf.id = incoming; // (Duplication) + //We can now identify the type of response + //If the packet we are receiving is not an ACK then check for a class and ID match + if (packetBuf.cls != UBX_CLASS_ACK) + { + //This is not an ACK so check for a class and ID match + if ((packetBuf.cls == requestedClass) && (packetBuf.id == requestedID)) + { + //This is not an ACK and we have a class and ID match + //So start diverting data into incomingUBX (usually packetCfg) + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + else + { + //This is not an ACK and we do not have a class and ID match + //so we should keep diverting data into packetBuf and ignore the payload + ignoreThisPayload = true; + } + } + else + { + // This is an ACK so it is to early to do anything with it + // We need to wait until we have received the length and data bytes + // So we should keep diverting data into packetBuf + } + } + else if (ubxFrameCounter == 4) //Length LSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len = incoming; // (Duplication) + } + else if (ubxFrameCounter == 5) //Length MSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len |= incoming << 8; // (Duplication) + } + else if (ubxFrameCounter == 6) //This should be the first byte of the payload unless .len is zero + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + if (_printDebug == true) + { + _debugSerial->print(F("process: ZERO LENGTH packet received: Class: 0x")); + _debugSerial->print(packetBuf.cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + //If length is zero (!) this will be the first byte of the checksum so record it + packetBuf.checksumA = incoming; + } + else + { + //The length is not zero so record this byte in the payload + packetBuf.payload[0] = incoming; + } + } + else if (ubxFrameCounter == 7) //This should be the second byte of the payload unless .len is zero or one + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + //If length is zero (!) this will be the second byte of the checksum so record it + packetBuf.checksumB = incoming; + } + else if (packetBuf.len == 1) // Check if length is one + { + //The length is one so this is the first byte of the checksum + packetBuf.checksumA = incoming; + } + else // Length is >= 2 so this must be a payload byte + { + packetBuf.payload[1] = incoming; + } + // Now that we have received two payload bytes, we can check for a matching ACK/NACK + if ((activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) // If we are not already processing a data packet + && (packetBuf.cls == UBX_CLASS_ACK) // and if this is an ACK/NACK + && (packetBuf.payload[0] == requestedClass) // and if the class matches + && (packetBuf.payload[1] == requestedID)) // and if the ID matches + { + if (packetBuf.len == 2) // Check if .len is 2 + { + // Then this is a matching ACK so copy it into packetAck + activePacketBuffer = SFE_UBLOX_PACKET_PACKETACK; + packetAck.cls = packetBuf.cls; + packetAck.id = packetBuf.id; + packetAck.len = packetBuf.len; + packetAck.counter = packetBuf.counter; + packetAck.payload[0] = packetBuf.payload[0]; + packetAck.payload[1] = packetBuf.payload[1]; + } + else // Length is not 2 (hopefully this is impossible!) + { + if (_printDebug == true) + { + _debugSerial->print(F("process: ACK received with .len != 2: Class: 0x")); + _debugSerial->print(packetBuf.payload[0], HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->print(packetBuf.payload[1], HEX); + _debugSerial->print(F(" len: ")); + _debugSerial->println(packetBuf.len); + } + } + } + } + + //Divert incoming into the correct buffer + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETACK) + processUBX(incoming, &packetAck, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + processUBX(incoming, incomingUBX, requestedClass, requestedID); + else // if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) + processUBX(incoming, &packetBuf, requestedClass, requestedID); + + //Finally, increment the frame counter + ubxFrameCounter++; + } + else if (currentSentence == NMEA) + { + processNMEA(incoming); //Process each NMEA character + } + else if (currentSentence == RTCM) + { + processRTCMframe(incoming); //Deal with RTCM bytes + } +} + +//This is the default or generic NMEA processor. We're only going to pipe the data to serial port so we can see it. +//User could overwrite this function to pipe characters to nmea.process(c) of tinyGPS or MicroNMEA +//Or user could pipe each character to a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //If user has assigned an output port then pipe the characters there + if (_nmeaOutputPort != NULL) + _nmeaOutputPort->write(incoming); //Echo this byte to the serial port +} + +//We need to be able to identify an RTCM packet and then the length +//so that we know when the RTCM message is completely received and we then start +//listening for other sentences (like NMEA or UBX) +//RTCM packet structure is very odd. I never found RTCM STANDARD 10403.2 but +//http://d1.amobbs.com/bbs_upload782111/files_39/ourdev_635123CK0HJT.pdf is good +//https://dspace.cvut.cz/bitstream/handle/10467/65205/F3-BP-2016-Shkalikava-Anastasiya-Prenos%20polohove%20informace%20prostrednictvim%20datove%20site.pdf?sequence=-1 +//Lead me to: https://forum.u-blox.com/index.php/4348/how-to-read-rtcm-messages-from-neo-m8p +//RTCM 3.2 bytes look like this: +//Byte 0: Always 0xD3 +//Byte 1: 6-bits of zero +//Byte 2: 10-bits of length of this packet including the first two-ish header bytes, + 6. +//byte 3 + 4 bits: Msg type 12 bits +//Example: D3 00 7C 43 F0 ... / 0x7C = 124+6 = 130 bytes in this packet, 0x43F = Msg type 1087 +void SFE_UBLOX_GPS::processRTCMframe(uint8_t incoming) +{ + if (rtcmFrameCounter == 1) + { + rtcmLen = (incoming & 0x03) << 8; //Get the last two bits of this byte. Bits 8&9 of 10-bit length + } + else if (rtcmFrameCounter == 2) + { + rtcmLen |= incoming; //Bits 0-7 of packet length + rtcmLen += 6; //There are 6 additional bytes of what we presume is header, msgType, CRC, and stuff + } + /*else if (rtcmFrameCounter == 3) + { + rtcmMsgType = incoming << 4; //Message Type, MS 4 bits + } + else if (rtcmFrameCounter == 4) + { + rtcmMsgType |= (incoming >> 4); //Message Type, bits 0-7 + }*/ + + rtcmFrameCounter++; + + processRTCM(incoming); //Here is where we expose this byte to the user + + if (rtcmFrameCounter == rtcmLen) + { + //We're done! + currentSentence = NONE; //Reset and start looking for next sentence type + } +} + +//This function is called for each byte of an RTCM frame +//Ths user can overwrite this function and process the RTCM frame as they please +//Bytes can be piped to Serial or other interface. The consumer could be a radio or the internet (Ntrip broadcaster) +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Radio.sendReliable((String)incoming); //An example of passing this byte to a radio + + //_debugSerial->write(incoming); //An example of passing this byte out the serial port + + //Debug printing + // _debugSerial->print(F(" ")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // _debugSerial->print(incoming, HEX); + // if(rtcmFrameCounter % 16 == 0) _debugSerial->println(); +} + +//Given a character, file it away into the uxb packet structure +//Set valid to VALID or NOT_VALID once sentence is completely received and passes or fails CRC +//The payload portion of the packet can be 100s of bytes but the max array +//size is MAX_PAYLOAD_SIZE bytes. startingSpot can be set so we only record +//a subset of bytes within a larger packet. +void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + //Add all incoming bytes to the rolling checksum + //Stop at len+4 as this is the checksum bytes to that should not be added to the rolling checksum + if (incomingUBX->counter < incomingUBX->len + 4) + addToChecksum(incoming); + + if (incomingUBX->counter == 0) + { + incomingUBX->cls = incoming; + } + else if (incomingUBX->counter == 1) + { + incomingUBX->id = incoming; + } + else if (incomingUBX->counter == 2) //Len LSB + { + incomingUBX->len = incoming; + } + else if (incomingUBX->counter == 3) //Len MSB + { + incomingUBX->len |= incoming << 8; + } + else if (incomingUBX->counter == incomingUBX->len + 4) //ChecksumA + { + incomingUBX->checksumA = incoming; + } + else if (incomingUBX->counter == incomingUBX->len + 5) //ChecksumB + { + incomingUBX->checksumB = incoming; + + currentSentence = NONE; //We're done! Reset the sentence to being looking for a new start char + + //Validate this sentence + if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + + // Let's check if the class and ID match the requestedClass and requestedID + // Remember - this could be a data packet or an ACK packet + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is a NACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_NACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_NOTACKNOWLEDGED; // If we have a match, set the classAndIDmatch flag to NOTACKNOWLEDGED + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: NACK received: Requested Class: 0x")); + _debugSerial->print(incomingUBX->payload[0], HEX); + _debugSerial->print(F(" Requested ID: 0x")); + _debugSerial->println(incomingUBX->payload[1], HEX); + } + } + + if (_printDebug == true) + { + _debugSerial->print(F("Incoming: Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); + + if (incomingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg now valid")); + } + if (packetAck.valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck now valid")); + } + if (incomingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg classAndIDmatch")); + } + if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck classAndIDmatch")); + } + } + + //We've got a valid packet, now do something with it but only if ignoreThisPayload is false + if (ignoreThisPayload == false) + { + processUBXpacket(incomingUBX); + } + } + else // Checksum failure + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; + + // Let's check if the class and ID match the requestedClass and requestedID. + // This is potentially risky as we are saying that we saw the requested Class and ID + // but that the packet checksum failed. Potentially it could be the class or ID bytes + // that caused the checksum error! + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + //Drive an external pin to allow for easier logic analyzation + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + + _debugSerial->print(F("Checksum failed:")); + _debugSerial->print(F(" checksumA: ")); + _debugSerial->print(incomingUBX->checksumA); + _debugSerial->print(F(" checksumB: ")); + _debugSerial->print(incomingUBX->checksumB); + + _debugSerial->print(F(" rollingChecksumA: ")); + _debugSerial->print(rollingChecksumA); + _debugSerial->print(F(" rollingChecksumB: ")); + _debugSerial->print(rollingChecksumB); + _debugSerial->println(); + + _debugSerial->print(F("Failed : ")); + _debugSerial->print(F("Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); + } + } + } + else //Load this byte into the payload array + { + //If a UBX_NAV_PVT packet comes in asynchronously, we need to fudge the startingSpot + uint16_t startingSpot = incomingUBX->startingSpot; + if (incomingUBX->cls == UBX_CLASS_NAV && incomingUBX->id == UBX_NAV_PVT) + startingSpot = 0; + //Begin recording if counter goes past startingSpot + if ((incomingUBX->counter - 4) >= startingSpot) + { + //Check to see if we have room for this byte + if (((incomingUBX->counter - 4) - startingSpot) < MAX_PAYLOAD_SIZE) //If counter = 208, starting spot = 200, we're good to record. + { + // Check if this is payload data which should be ignored + if (ignoreThisPayload == false) + { + incomingUBX->payload[incomingUBX->counter - 4 - startingSpot] = incoming; //Store this byte into payload array + } + } + } + } + + //Increment the counter + incomingUBX->counter++; + + if (incomingUBX->counter == MAX_PAYLOAD_SIZE) + { + //Something has gone very wrong + currentSentence = NONE; //Reset the sentence to being looking for a new start char + if (_printDebug == true) + { + _debugSerial->println(F("processUBX: counter hit MAX_PAYLOAD_SIZE")); + } + } +} + +//Once a packet has been received and validated, identify this packet's class/id and update internal flags +//Note: if the user requests a PVT or a HPPOSLLH message using a custom packet, the data extraction will +// not work as expected beacuse extractLong etc are hardwired to packetCfg payloadCfg. Ideally +// extractLong etc should be updated so they receive a pointer to the packet buffer. +void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) +{ + switch (msg->cls) + { + case UBX_CLASS_NAV: + //u-blox8 length == 92 + //u-blox7 length == 84 + if ((msg->id == UBX_NAV_PVT) && ((msg->len == 92) || (msg->len == 84))) + { + //Parse various byte fields into global vars + constexpr int startingSpot = 0; //fixed value used in processUBX + + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + gpsYear = extractInt(4); + gpsMonth = extractByte(6); + gpsDay = extractByte(7); + gpsHour = extractByte(8); + gpsMinute = extractByte(9); + gpsSecond = extractByte(10); + gpsNanosecond = extractLong(16); //Includes milliseconds + + fixType = extractByte(20 - startingSpot); + //Note: the u-blox7 does not support carrSoln. carrierSolution will be zero. + carrierSolution = extractByte(21 - startingSpot) >> 6; //Get 6th&7th bits of this byte + SIV = extractByte(23 - startingSpot); + longitude = extractLong(24 - startingSpot); + latitude = extractLong(28 - startingSpot); + altitude = extractLong(32 - startingSpot); + altitudeMSL = extractLong(36 - startingSpot); + groundSpeed = extractLong(60 - startingSpot); + headingOfMotion = extractLong(64 - startingSpot); + pDOP = extractInt(76 - startingSpot); + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = true; + moduleQueried.gpsYear = true; + moduleQueried.gpsMonth = true; + moduleQueried.gpsDay = true; + moduleQueried.gpsHour = true; + moduleQueried.gpsMinute = true; + moduleQueried.gpsSecond = true; + moduleQueried.gpsNanosecond = true; + + moduleQueried.all = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + moduleQueried.SIV = true; + moduleQueried.fixType = true; + moduleQueried.carrierSolution = true; + moduleQueried.groundSpeed = true; + moduleQueried.headingOfMotion = true; + moduleQueried.pDOP = true; + } + else if (msg->id == UBX_NAV_TIMEUTC && msg->len == 20) + { + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + // extractLong(4); // time accuracy estimate + gpsYear = extractInt(12); + gpsMonth = extractByte(14); + gpsDay = extractByte(15); + gpsHour = extractByte(16); + gpsMinute = extractByte(17); + gpsSecond = extractByte(18); + gpsNanosecond = extractLong(8); //Includes milliseconds + uint8_t valid = extractByte(19); + bool gotTime = (valid & 4) ? true : false; // assume all other fields filled once we have TUTC + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = gotTime; // valid tow + moduleQueried.gpsYear = gotTime; // valid week num + moduleQueried.gpsMonth = gotTime; + moduleQueried.gpsDay = gotTime; // valid UTC + moduleQueried.gpsHour = gotTime; + moduleQueried.gpsMinute = gotTime; + moduleQueried.gpsSecond = gotTime; + moduleQueried.gpsNanosecond = gotTime; + } + else if (msg->id == UBX_NAV_POSLLH && msg->len == 28) + { + timeOfWeek = extractLong(0); + longitude = extractLong(4); + latitude = extractLong(8); + altitude = extractLong(12); + altitudeMSL = extractLong(16); + horizontalAccuracy = extractLong(20); + verticalAccuracy = extractLong(24); + + moduleQueried.gpsiTOW = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + } + else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == 36) + { + timeOfWeek = extractLong(4); + highResLongitude = extractLong(8); + highResLatitude = extractLong(12); + elipsoid = extractLong(16); + meanSeaLevel = extractLong(20); + highResLongitudeHp = extractSignedChar(24); + highResLatitudeHp = extractSignedChar(25); + elipsoidHp = extractSignedChar(26); + meanSeaLevelHp = extractSignedChar(27); + horizontalAccuracy = extractLong(28); + verticalAccuracy = extractLong(32); + + highResModuleQueried.all = true; + highResModuleQueried.highResLatitude = true; + highResModuleQueried.highResLatitudeHp = true; + highResModuleQueried.highResLongitude = true; + highResModuleQueried.highResLongitudeHp = true; + highResModuleQueried.elipsoid = true; + highResModuleQueried.elipsoidHp = true; + highResModuleQueried.meanSeaLevel = true; + highResModuleQueried.meanSeaLevelHp = true; + highResModuleQueried.geoidSeparation = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + moduleQueried.gpsiTOW = true; // this can arrive via HPPOS too. + + if (_printDebug == true) + { + _debugSerial->print(F("Sec: ")); + _debugSerial->print(((float)extractLong(4)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LON: ")); + _debugSerial->print(((float)(int32_t)extractLong(8)) / 10000000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LAT: ")); + _debugSerial->print(((float)(int32_t)extractLong(12)) / 10000000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("ELI M: ")); + _debugSerial->print(((float)(int32_t)extractLong(16)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("MSL M: ")); + _debugSerial->print(((float)(int32_t)extractLong(20)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LON HP: ")); + _debugSerial->print(extractSignedChar(24)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LAT HP: ")); + _debugSerial->print(extractSignedChar(25)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("ELI HP: ")); + _debugSerial->print(extractSignedChar(26)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("MSL HP: ")); + _debugSerial->print(extractSignedChar(27)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("HA 2D M: ")); + _debugSerial->print(((float)(int32_t)extractLong(28)) / 10000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("VERT M: ")); + _debugSerial->println(((float)(int32_t)extractLong(32)) / 10000.0f); + } + } + else { + if (_printDebug == true) + { + _debugSerial->print(F("Unexpected nav packet ")); + _debugSerial->println(msg->id); + } + } + break; + } +} + +//Given a packet and payload, send everything including CRC bytes via I2C port +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + sfe_ublox_status_e retVal = SFE_UBLOX_STATUS_SUCCESS; + + calcChecksum(outgoingUBX); //Sets checksum A and B bytes of the packet + + if (_printDebug == true) + { + _debugSerial->print(F("\nSending: ")); + printPacket(outgoingUBX); + } + + if (commType == COMM_TYPE_I2C) + { + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + if (_printDebug == true) + { + _debugSerial->println(F("Send I2C Command failed")); + } + return retVal; + } + } + else if (commType == COMM_TYPE_SERIAL) + { + sendSerialCommand(outgoingUBX); + } + + if (maxWait > 0) + { + //Depending on what we just sent, either we need to look for an ACK or not + if (outgoingUBX->cls == UBX_CLASS_CFG) + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for ACK response")); + } + retVal = waitForACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for No ACK response")); + } + retVal = waitForNoACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + } + return retVal; +} + +//Returns false if sensor fails to respond to I2C traffic +sfe_ublox_status_e SFE_UBLOX_GPS::sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + //Point at 0xFF data register + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); //There is no register to write to, we just begin writing data bytes + _i2cPort->write(0xFF); + if (_i2cPort->endTransmission() != 0) //Don't release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //Write header bytes + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); //There is no register to write to, we just begin writing data bytes + _i2cPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _i2cPort->write(UBX_SYNCH_2); //b + _i2cPort->write(outgoingUBX->cls); + _i2cPort->write(outgoingUBX->id); + _i2cPort->write(outgoingUBX->len & 0xFF); //LSB + _i2cPort->write(outgoingUBX->len >> 8); //MSB + if (_i2cPort->endTransmission(false) != 0) //Do not release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //Write payload. Limit the sends into 32 byte chunks + //This code based on ublox: https://forum.u-blox.com/index.php/20528/how-to-use-i2c-to-get-the-nmea-frames + uint16_t bytesToSend = outgoingUBX->len; + + //"The number of data bytes must be at least 2 to properly distinguish + //from the write access to set the address counter in random read accesses." + uint16_t startSpot = 0; + while (bytesToSend > 1) + { + uint8_t len = bytesToSend; + if (len > I2C_BUFFER_LENGTH) + len = I2C_BUFFER_LENGTH; + + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + //_i2cPort->write(outgoingUBX->payload, len); //Write a portion of the payload to the bus + + for (uint16_t x = 0; x < len; x++) + _i2cPort->write(outgoingUBX->payload[startSpot + x]); //Write a portion of the payload to the bus + + if (_i2cPort->endTransmission(false) != 0) //Don't release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //*outgoingUBX->payload += len; //Move the pointer forward + startSpot += len; //Move the pointer forward + bytesToSend -= len; + } + + //Write checksum + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (bytesToSend == 1) + _i2cPort->write(outgoingUBX->payload, 1); + _i2cPort->write(outgoingUBX->checksumA); + _i2cPort->write(outgoingUBX->checksumB); + + //All done transmitting bytes. Release bus. + if (_i2cPort->endTransmission() != 0) + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + return (SFE_UBLOX_STATUS_SUCCESS); +} + +//Given a packet and payload, send everything including CRC bytesA via Serial port +void SFE_UBLOX_GPS::sendSerialCommand(ubxPacket *outgoingUBX) +{ + //Write header bytes + _serialPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _serialPort->write(UBX_SYNCH_2); //b + _serialPort->write(outgoingUBX->cls); + _serialPort->write(outgoingUBX->id); + _serialPort->write(outgoingUBX->len & 0xFF); //LSB + _serialPort->write(outgoingUBX->len >> 8); //MSB + + //Write payload. + for (int i = 0; i < outgoingUBX->len; i++) + { + _serialPort->write(outgoingUBX->payload[i]); + } + + //Write checksum + _serialPort->write(outgoingUBX->checksumA); + _serialPort->write(outgoingUBX->checksumB); +} + +//Returns true if I2C device ack's +boolean SFE_UBLOX_GPS::isConnected(uint16_t maxWait) +{ + if (commType == COMM_TYPE_I2C) + { + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (_i2cPort->endTransmission() != 0) + return false; //Sensor did not ack + } + + // Query navigation rate to see whether we get a meaningful response + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are polling the RATE so we expect data and an ACK +} + +//Given a message, calc and store the two byte "8-Bit Fletcher" checksum over the entirety of the message +//This is called before we send a command message +void SFE_UBLOX_GPS::calcChecksum(ubxPacket *msg) +{ + msg->checksumA = 0; + msg->checksumB = 0; + + msg->checksumA += msg->cls; + msg->checksumB += msg->checksumA; + + msg->checksumA += msg->id; + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len & 0xFF); + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len >> 8); + msg->checksumB += msg->checksumA; + + for (uint16_t i = 0; i < msg->len; i++) + { + msg->checksumA += msg->payload[i]; + msg->checksumB += msg->checksumA; + } +} + +//Given a message and a byte, add to rolling "8-Bit Fletcher" checksum +//This is used when receiving messages from module +void SFE_UBLOX_GPS::addToChecksum(uint8_t incoming) +{ + rollingChecksumA += incoming; + rollingChecksumB += rollingChecksumA; +} + +//Pretty prints the current ubxPacket +void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) +{ + if (_printDebug == true) + { + _debugSerial->print(F("CLS:")); + if (packet->cls == UBX_CLASS_NAV) //1 + _debugSerial->print(F("NAV")); + else if (packet->cls == UBX_CLASS_ACK) //5 + _debugSerial->print(F("ACK")); + else if (packet->cls == UBX_CLASS_CFG) //6 + _debugSerial->print(F("CFG")); + else if (packet->cls == UBX_CLASS_MON) //0x0A + _debugSerial->print(F("MON")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->cls, HEX); + } + + _debugSerial->print(F(" ID:")); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + _debugSerial->print(F("PVT")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_RATE) + _debugSerial->print(F("RATE")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_CFG) + _debugSerial->print(F("SAVE")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->id, HEX); + } + + _debugSerial->print(F(" Len: 0x")); + _debugSerial->print(packet->len, HEX); + + // Only print the payload is ignoreThisPayload is false otherwise + // we could be printing gibberish from beyond the end of packetBuf + if (ignoreThisPayload == false) + { + _debugSerial->print(F(" Payload:")); + + for (int x = 0; x < packet->len; x++) + { + _debugSerial->print(F(" ")); + _debugSerial->print(packet->payload[x], HEX); + } + } + else + { + _debugSerial->print(F(" Payload: IGNORED")); + } + _debugSerial->println(); + } +} + +//=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//When messages from the class CFG are sent to the receiver, the receiver will send an "acknowledge"(UBX - ACK - ACK) or a +//"not acknowledge"(UBX-ACK-NAK) message back to the sender, depending on whether or not the message was processed correctly. +//Some messages from other classes also use the same acknowledgement mechanism. + +//When we poll or get a setting, we will receive _both_ a config packet and an ACK +//If the poll or get request is not valid, we will receive _only_ a NACK + +//If we are trying to get or poll a setting, then packetCfg.len will be 0 or 1 when the packetCfg is _sent_. +//If we poll the setting for a particular port using UBX-CFG-PRT then .len will be 1 initially +//For all other gets or polls, .len will be 0 initially +//(It would be possible for .len to be 2 _if_ we were using UBX-CFG-MSG to poll the settings for a particular message - but we don't use that (currently)) + +//If the get or poll _fails_, i.e. is NACK'd, then packetCfg.len could still be 0 or 1 after the NACK is received +//But if the get or poll is ACK'd, then packetCfg.len will have been updated by the incoming data and will always be at least 2 + +//If we are going to set the value for a setting, then packetCfg.len will be at least 3 when the packetCfg is _sent_. +//(UBX-CFG-MSG appears to have the shortest set length of 3 bytes) + +//We need to think carefully about how interleaved PVT packets affect things. +//It is entirely possible that our packetCfg and packetAck were received successfully +//but while we are still in the "if (checkUblox() == true)" loop a PVT packet is processed +//or _starts_ to arrive (remember that Serial data can arrive very slowly). + +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got an ACK and a valid packetCfg (module is responding with register content) +//Returns SFE_UBLOX_STATUS_DATA_SENT if we got an ACK and no packetCfg (no valid packetCfg needed, module absorbs new register data) +//Returns SFE_UBLOX_STATUS_FAIL if something very bad happens (e.g. a double checksum failure) +//Returns SFE_UBLOX_STATUS_COMMAND_NACK if the packet was not-acknowledged (NACK) +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we had a checksum failure +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an ACK and a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data and valid ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and a correct ACK! + } + + // We can be confident that the data packet (if we are going to get one) will always arrive + // before the matching ACK. So if we sent a config packet which only produces an ACK + // then outgoingUBX->classAndIDmatch will be NOT_DEFINED and the packetAck.classAndIDmatch will VALID. + // We should not check outgoingUBX->valid, outgoingUBX->cls or outgoingUBX->id + // as these may have been changed by a PVT packet. + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: no data and valid ACK after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_SENT); //We got an ACK but no data... + } + + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If packetAck.classAndIDmatch is VALID but both outgoingUBX->valid and outgoingUBX->classAndIDmatch + // are NOT_VALID then we can be confident we have had a checksum failure on the data packet + else if ((packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: CRC failed after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //Checksum fail + } + + // If our packet was not-acknowledged (NACK) we do not receive a data packet - we only get the NACK. + // So you would expect outgoingUBX->valid and outgoingUBX->classAndIDmatch to still be NOT_DEFINED + // But if a full PVT packet arrives afterwards outgoingUBX->valid could be VALID (or just possibly NOT_VALID) + // but outgoingUBX->cls and outgoingUBX->id would not match... + // So I think this is telling us we need a special state for packetAck.classAndIDmatch to tell us + // the packet was definitely NACK'd otherwise we are possibly just guessing... + // Note: the addition of packetBuf changes the logic of this, but we'll leave the code as is for now. + else if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_NOTACKNOWLEDGED) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data was NOTACKNOWLEDGED (NACK) after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_COMMAND_NACK); //We received a NACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID but the packetAck.classAndIDmatch is NOT_VALID + // then the ack probably had a checksum error. We will take a gamble and return DATA_RECEIVED. + // If we were playing safe, we should return FAIL instead + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: VALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID and the packetAck.classAndIDmatch is NOT_VALID + // then we return a FAIL. This must be a double checksum failure? + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: INVALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_FAIL); //We received invalid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID and the packetAck.classAndIDmatch is NOT_DEFINED + // then the ACK has not yet been received and we should keep waiting for it + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. Waiting for ACK.")); + } + } + + } //checkUbloxInternal == true + + delayMicroseconds(500); + } //while (millis() - startTime < maxTime) + + // We have timed out... + // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED + // even though we did not get an ACK + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT with valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. ")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data... But no ACK! + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//For non-CFG queries no ACK is sent so we use this function +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we got a corrupt config packet that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + + // If outgoingUBX->classAndIDmatch is VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: valid data with CLS/ID match after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data! + } + + // If the outgoingUBX->classAndIDmatch is VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If outgoingUBX->classAndIDmatch is NOT_DEFINED + // and outgoingUBX->valid is VALID then this must be (e.g.) a PVT packet + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: valid but UNWANTED data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->print(F(" msec. Class: ")); + _debugSerial->print(outgoingUBX->cls); + _debugSerial->print(F(" ID: ")); + _debugSerial->print(outgoingUBX->id); + } + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID then we return CRC failure + else if (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: CLS/ID match but failed CRC after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //We received invalid data + } + } + + delayMicroseconds(500); + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. No packet received.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//Save current configuration to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfiguration(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = 0xFF; //Set any bit in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save the selected configuration sub-sections to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfigSelective(uint32_t configMask, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = configMask & 0xFF; //Set the appropriate bits in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = (configMask >> 8) & 0xFF; + packetCfg.payload[6] = (configMask >> 16) & 0xFF; + packetCfg.payload[7] = (configMask >> 24) & 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Reset module to factory defaults +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::factoryDefault(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[0] = 0xFF; //Set any bit in the clearMask field to clear saved config + packetCfg.payload[1] = 0xFF; + packetCfg.payload[8] = 0xFF; //Set any bit in the loadMask field to discard current config and rebuild from lower non-volatile memory layers + packetCfg.payload[9] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a group, ID and size, return the value of this config spot +//The 32-bit key is put together from group/ID/size. See other getVal to send key directly. +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + //Create key + uint32_t key = 0; + key |= (uint32_t)id; + key |= (uint32_t)group << 16; + key |= (uint32_t)size << 28; + + if (_printDebug == true) + { + _debugSerial->print(F("key: 0x")); + _debugSerial->print(key, HEX); + _debugSerial->println(); + } + + return getVal8(key, layer, maxWait); +} + +//Given a key, return its value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALGET; + packetCfg.len = 4 + 4 * 1; //While multiple keys are allowed, we will send only one key at a time + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //VALGET uses different memory layer definitions to VALSET + //because it can only return the value for one layer. + //So we need to fiddle the layer here. + //And just to complicate things further, the ZED-F9P only responds + //correctly to layer 0 (RAM) and layer 7 (Default)! + uint8_t getLayer = 7; // 7 is the "Default Layer" + if ((layer & VAL_LAYER_RAM) == VAL_LAYER_RAM) // Did the user request the RAM layer? + { + getLayer = 0; // Layer 0 is RAM + } + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = getLayer; //Layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + if (_printDebug == true) + { + _debugSerial->print(F("key: 0x")); + _debugSerial->print(key, HEX); + _debugSerial->println(); + } + + //Send VALGET command with this key + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("getVal8: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } + if (retVal != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (0); //If command send fails then bail + + //Verify the response is the correct length as compared to what the user called (did the module respond with 8-bits but the user called getVal32?) + //Response is 8 bytes plus cfg data + //if(packet->len > 8+1) + + //Pull the requested value from the response + //Response starts at 4+1*N with the 32-bit key so the actual data we're looking for is at 8+1*N + return (extractByte(8)); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + return setVal16(key, value, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal16(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set an 8-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal8(uint32_t key, uint8_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set a 32-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal32(uint32_t key, uint32_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset32(uint32_t key, uint32_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset16(uint32_t key, uint16_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset8(uint32_t key, uint8_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset32(uint32_t key, uint32_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + payloadCfg[packetCfg.len + 6] = value >> 8 * 2; + payloadCfg[packetCfg.len + 7] = value >> 8 * 3; + + //Update packet length: 4 byte key ID, 4 bytes of value + packetCfg.len = packetCfg.len + 4 + 4; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset16(uint32_t key, uint16_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + + //Update packet length: 4 byte key ID, 2 bytes of value + packetCfg.len = packetCfg.len + 4 + 2; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset8(uint32_t key, uint8_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value; //Value + + //Update packet length: 4 byte key ID, 1 byte value + packetCfg.len = packetCfg.len + 4 + 1; + + //All done + return (true); +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset32(uint32_t key, uint32_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset32(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset16(uint32_t key, uint16_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset16(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset8(uint32_t key, uint8_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset8(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the current TimeMode3 settings - these contain survey in statuses +boolean SFE_UBLOX_GPS::getSurveyMode(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Control Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + if (getSurveyMode(maxWait) == false) //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 40; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //payloadCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = mode; //Set mode. Survey-In and Disabled are most common. Use ECEF (not LAT/LON/ALT). + + //svinMinDur is U4 (uint32_t) but we'll only use a uint16_t (waiting more than 65535 seconds seems excessive!) + payloadCfg[24] = observationTime & 0xFF; //svinMinDur in seconds + payloadCfg[25] = observationTime >> 8; //svinMinDur in seconds + payloadCfg[26] = 0; //Truncate to 16 bits + payloadCfg[27] = 0; //Truncate to 16 bits + + //svinAccLimit is U4 (uint32_t) in 0.1mm. + uint32_t svinAccLimit = (uint32_t)(requiredAccuracy * 10000.0); //Convert m to 0.1mm + payloadCfg[28] = svinAccLimit & 0xFF; //svinAccLimit in 0.1mm increments + payloadCfg[29] = svinAccLimit >> 8; + payloadCfg[30] = svinAccLimit >> 16; + payloadCfg[31] = svinAccLimit >> 24; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Begin Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_ENABLE, observationTime, requiredAccuracy, maxWait)); +} + +//Stop Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::disableSurveyMode(uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_DISABLE, 0, 0, maxWait)); +} + +//Reads survey in status and sets the global variables +//for status, position valid, observation time, and mean 3D StdDev +//Returns true if commands was successful +boolean SFE_UBLOX_GPS::getSurveyStatus(uint16_t maxWait) +{ + //Reset variables + svin.active = false; + svin.valid = false; + svin.observationTime = 0; + svin.meanAccuracy = 0; + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SVIN; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if ((sendCommand(&packetCfg, maxWait)) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //We got a response, now parse the bits into the svin structure + + //dur (Passed survey-in observation time) is U4 (uint32_t) seconds. We truncate to 16 bits + //(waiting more than 65535 seconds (18.2 hours) seems excessive!) + uint32_t tmpObsTime = extractLong(8); + if (tmpObsTime <= 0xFFFF) + { + svin.observationTime = (uint16_t)tmpObsTime; + } + else + { + svin.observationTime = 0xFFFF; + } + + // meanAcc is U4 (uint32_t) in 0.1mm. We convert this to float. + uint32_t tempFloat = extractLong(28); + svin.meanAccuracy = ((float)tempFloat) / 10000.0; //Convert 0.1mm to m + + svin.valid = payloadCfg[36]; //1 if survey-in position is valid, 0 otherwise + svin.active = payloadCfg[37]; //1 if survey-in in progress, 0 otherwise + + return (true); +} + +//Loads the payloadCfg array with the current protocol bits located the UBX-CFG-PRT register for a given port +boolean SFE_UBLOX_GPS::getPortSettings(uint8_t portID, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 1; + packetCfg.startingSpot = 0; + + payloadCfg[0] = portID; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[14] = outStreamSettings; //OutProtocolMask LSB - Set outStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + //This will load the payloadCfg array with current port settings + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[12] = inStreamSettings; //InProtocolMask LSB - Set inStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a port to output UBX, NMEA, RTCM3 or a combination thereof +boolean SFE_UBLOX_GPS::setI2COutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_I2C, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART1Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART1, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART2Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART2, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUSBOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_USB, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setSPIOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_SPI, comSettings, maxWait)); +} + +//Set the rate at which the module will give us an updated navigation solution +//Expects a number that is the updates per second. For example 1 = 1Hz, 2 = 2Hz, etc. +//Max is 40Hz(?!) +boolean SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) +{ + //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values + + //Adjust the I2C polling timeout based on update rate + i2cPollingWait = 1000 / (navFreq * 4); //This is the number of ms to wait between checks for new I2C data + + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 1000 / navFreq; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[0] = measurementRate & 0xFF; //measRate LSB + payloadCfg[1] = measurementRate >> 8; //measRate MSB + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the rate at which the module is outputting nav solutions +uint8_t SFE_UBLOX_GPS::getNavigationFrequency(uint16_t maxWait) +{ + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 0; + + //payloadCfg is now loaded with current bytes. Get what we need + measurementRate = extractInt(0); //Pull from payloadCfg at measRate LSB + + measurementRate = 1000 / measurementRate; //This may return an int when it's a float, but I'd rather not return 4 bytes + return (measurementRate); +} + +//In case no config access to the GPS is possible and PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoPVT(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoPVT != enabled || autoPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoPVT = enabled; + autoPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, uint16_t maxWait) +{ + return setAutoPVT(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoPVT = enable; + autoPVTImplicitUpdate = implicitUpdate; + } + moduleQueried.all = false; + return ok; +} + +//Configure a given message type for a given port (UART1, I2C, SPI, etc) +boolean SFE_UBLOX_GPS::configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + //Poll for the current settings for a given message + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + payloadCfg[0] = msgClass; + payloadCfg[1] = msgID; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //Now send it back with new mods + packetCfg.len = 8; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[2 + portID] = sendRate; //Send rate is relative to the event a message is registered on. For example, if the rate of a navigation message is set to 2, the message is sent every 2nd navigation solution. + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable a given message type, default of 1 per update rate (usually 1 per second) +boolean SFE_UBLOX_GPS::enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, rate, maxWait)); +} +//Disable a given message type on a given port +boolean SFE_UBLOX_GPS::disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, 0, maxWait)); +} + +boolean SFE_UBLOX_GPS::enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(UBX_CLASS_NMEA, msgID, portID, rate, maxWait)); +} +boolean SFE_UBLOX_GPS::disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (enableNMEAMessage(msgID, portID, 0, maxWait)); +} + +//Given a message number turns on a message ID for output over a given portID (UART, I2C, SPI, USB, etc) +//To disable a message, set secondsBetween messages to 0 +//Note: This function will return false if the message is already enabled +//For base station RTK output we need to enable various sentences + +//NEO-M8P has four: +//1005 = 0xF5 0x05 - Stationary RTK reference ARP +//1077 = 0xF5 0x4D - GPS MSM7 +//1087 = 0xF5 0x57 - GLONASS MSM7 +//1230 = 0xF5 0xE6 - GLONASS code-phase biases, set to once every 10 seconds + +//ZED-F9P has six: +//1005, 1074, 1084, 1094, 1124, 1230 + +//Much of this configuration is not documented and instead discerned from u-center binary console +boolean SFE_UBLOX_GPS::enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + return (configureMessage(UBX_RTCM_MSB, messageNumber, portID, sendRate, maxWait)); +} + +//Disable a given message on a given port by setting secondsBetweenMessages to zero +boolean SFE_UBLOX_GPS::disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait) +{ + return (enableRTCMmessage(messageNumber, portID, 0, maxWait)); +} + +//Add a new geofence using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence, byte pinPolarity, byte pin, uint16_t maxWait) +{ + if (currentGeofenceParams.numFences >= 4) + return (false); // Quit if we already have four geofences defined + + // Store the new geofence parameters + currentGeofenceParams.lats[currentGeofenceParams.numFences] = latitude; + currentGeofenceParams.longs[currentGeofenceParams.numFences] = longitude; + currentGeofenceParams.rads[currentGeofenceParams.numFences] = radius; + currentGeofenceParams.numFences = currentGeofenceParams.numFences + 1; // Increment the number of fences + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = (currentGeofenceParams.numFences * 12) + 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = currentGeofenceParams.numFences; // numFences + payloadCfg[2] = confidence; // confLvl = Confidence level 0-4 (none, 68%, 95%, 99.7%, 99.99%) + payloadCfg[3] = 0; // reserved1 + if (pin > 0) + { + payloadCfg[4] = 1; // enable PIO combined fence state + } + else + { + payloadCfg[4] = 0; // disable PIO combined fence state + } + payloadCfg[5] = pinPolarity; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = pin; // PIO pin + payloadCfg[7] = 0; //reserved2 + payloadCfg[8] = currentGeofenceParams.lats[0] & 0xFF; + payloadCfg[9] = currentGeofenceParams.lats[0] >> 8; + payloadCfg[10] = currentGeofenceParams.lats[0] >> 16; + payloadCfg[11] = currentGeofenceParams.lats[0] >> 24; + payloadCfg[12] = currentGeofenceParams.longs[0] & 0xFF; + payloadCfg[13] = currentGeofenceParams.longs[0] >> 8; + payloadCfg[14] = currentGeofenceParams.longs[0] >> 16; + payloadCfg[15] = currentGeofenceParams.longs[0] >> 24; + payloadCfg[16] = currentGeofenceParams.rads[0] & 0xFF; + payloadCfg[17] = currentGeofenceParams.rads[0] >> 8; + payloadCfg[18] = currentGeofenceParams.rads[0] >> 16; + payloadCfg[19] = currentGeofenceParams.rads[0] >> 24; + if (currentGeofenceParams.numFences >= 2) + { + payloadCfg[20] = currentGeofenceParams.lats[1] & 0xFF; + payloadCfg[21] = currentGeofenceParams.lats[1] >> 8; + payloadCfg[22] = currentGeofenceParams.lats[1] >> 16; + payloadCfg[23] = currentGeofenceParams.lats[1] >> 24; + payloadCfg[24] = currentGeofenceParams.longs[1] & 0xFF; + payloadCfg[25] = currentGeofenceParams.longs[1] >> 8; + payloadCfg[26] = currentGeofenceParams.longs[1] >> 16; + payloadCfg[27] = currentGeofenceParams.longs[1] >> 24; + payloadCfg[28] = currentGeofenceParams.rads[1] & 0xFF; + payloadCfg[29] = currentGeofenceParams.rads[1] >> 8; + payloadCfg[30] = currentGeofenceParams.rads[1] >> 16; + payloadCfg[31] = currentGeofenceParams.rads[1] >> 24; + } + if (currentGeofenceParams.numFences >= 3) + { + payloadCfg[32] = currentGeofenceParams.lats[2] & 0xFF; + payloadCfg[33] = currentGeofenceParams.lats[2] >> 8; + payloadCfg[34] = currentGeofenceParams.lats[2] >> 16; + payloadCfg[35] = currentGeofenceParams.lats[2] >> 24; + payloadCfg[36] = currentGeofenceParams.longs[2] & 0xFF; + payloadCfg[37] = currentGeofenceParams.longs[2] >> 8; + payloadCfg[38] = currentGeofenceParams.longs[2] >> 16; + payloadCfg[39] = currentGeofenceParams.longs[2] >> 24; + payloadCfg[40] = currentGeofenceParams.rads[2] & 0xFF; + payloadCfg[41] = currentGeofenceParams.rads[2] >> 8; + payloadCfg[42] = currentGeofenceParams.rads[2] >> 16; + payloadCfg[43] = currentGeofenceParams.rads[2] >> 24; + } + if (currentGeofenceParams.numFences >= 4) + { + payloadCfg[44] = currentGeofenceParams.lats[3] & 0xFF; + payloadCfg[45] = currentGeofenceParams.lats[3] >> 8; + payloadCfg[46] = currentGeofenceParams.lats[3] >> 16; + payloadCfg[47] = currentGeofenceParams.lats[3] >> 24; + payloadCfg[48] = currentGeofenceParams.longs[3] & 0xFF; + payloadCfg[49] = currentGeofenceParams.longs[3] >> 8; + payloadCfg[50] = currentGeofenceParams.longs[3] >> 16; + payloadCfg[51] = currentGeofenceParams.longs[3] >> 24; + payloadCfg[52] = currentGeofenceParams.rads[3] & 0xFF; + payloadCfg[53] = currentGeofenceParams.rads[3] >> 8; + payloadCfg[54] = currentGeofenceParams.rads[3] >> 16; + payloadCfg[55] = currentGeofenceParams.rads[3] >> 24; + } + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear all geofences using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::clearGeofences(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = 0; // numFences + payloadCfg[2] = 0; // confLvl + payloadCfg[3] = 0; // reserved1 + payloadCfg[4] = 0; // disable PIO combined fence state + payloadCfg[5] = 0; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = 0; // PIO pin + payloadCfg[7] = 0; //reserved2 + + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear the antenna control settings using UBX-CFG-ANT +//This function is hopefully redundant but may be needed to release +//any PIO pins pre-allocated for antenna functions +boolean SFE_UBLOX_GPS::clearAntPIO(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_ANT; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x10; // Antenna flag mask: set the recovery bit + payloadCfg[1] = 0; + payloadCfg[2] = 0xFF; // Antenna pin configuration: set pinSwitch and pinSCD to 31 + payloadCfg[3] = 0xFF; // Antenna pin configuration: set pinOCD to 31, set reconfig bit + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Returns the combined geofence state using UBX-NAV-GEOFENCE +boolean SFE_UBLOX_GPS::getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_GEOFENCE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the geofence status. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + currentGeofenceState.status = payloadCfg[5]; // Extract the status + currentGeofenceState.numFences = payloadCfg[6]; // Extract the number of geofences + currentGeofenceState.combState = payloadCfg[7]; // Extract the combined state of all geofences + if (currentGeofenceState.numFences > 0) + currentGeofenceState.states[0] = payloadCfg[8]; // Extract geofence 1 state + if (currentGeofenceState.numFences > 1) + currentGeofenceState.states[1] = payloadCfg[10]; // Extract geofence 2 state + if (currentGeofenceState.numFences > 2) + currentGeofenceState.states[2] = payloadCfg[12]; // Extract geofence 3 state + if (currentGeofenceState.numFences > 3) + currentGeofenceState.states[3] = payloadCfg[14]; // Extract geofence 4 state + + return (true); +} + +//Power Save Mode +//Enables/Disables Low Power Mode using UBX-CFG-RXM +boolean SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (false); + } + + // Now let's change the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + if (power_save) + { + payloadCfg[1] = 1; // Power Save Mode + } + else + { + payloadCfg[1] = 0; // Continuous Mode + } + + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// Get Power Save Mode +// Returns the current Low Power Mode using UBX-CFG-RXM +// Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getPowerSaveMode(uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (255); + } + + // Now let's read the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[1]); // Return the low power mode +} + +//Change the dynamic platform model using UBX-CFG-NAV5 +//Possible values are: +//PORTABLE,STATIONARY,PEDESTRIAN,AUTOMOTIVE,SEA, +//AIRBORNE1g,AIRBORNE2g,AIRBORNE4g,WRIST,BIKE +//WRIST is not supported in protocol versions less than 18 +//BIKE is supported in protocol versions 19.2 +boolean SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) + payloadCfg[1] = 0x00; // mask + payloadCfg[2] = newDynamicModel; // dynModel + + packetCfg.len = 36; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the dynamic platform model using UBX-CFG-NAV5 +//Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getDynamicModel(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[2]); // Return the dynamic model +} + +//Given a spot in the payload array, extract four bytes and build a long +uint32_t SFE_UBLOX_GPS::extractLong(uint8_t spotToStart) +{ + uint32_t val = 0; + val |= (uint32_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint32_t)payloadCfg[spotToStart + 1] << 8 * 1; + val |= (uint32_t)payloadCfg[spotToStart + 2] << 8 * 2; + val |= (uint32_t)payloadCfg[spotToStart + 3] << 8 * 3; + return (val); +} + +//Given a spot in the payload array, extract two bytes and build an int +uint16_t SFE_UBLOX_GPS::extractInt(uint8_t spotToStart) +{ + uint16_t val = 0; + val |= (uint16_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint16_t)payloadCfg[spotToStart + 1] << 8 * 1; + return (val); +} + +//Given a spot, extract a byte from the payload +uint8_t SFE_UBLOX_GPS::extractByte(uint8_t spotToStart) +{ + return (payloadCfg[spotToStart]); +} + +//Given a spot, extract a signed 8-bit value from the payload +int8_t SFE_UBLOX_GPS::extractSignedChar(uint8_t spotToStart) +{ + return ((int8_t)payloadCfg[spotToStart]); +} + +//Get the current year +uint16_t SFE_UBLOX_GPS::getYear(uint16_t maxWait) +{ + if (moduleQueried.gpsYear == false) + getTimeData(maxWait); + moduleQueried.gpsYear = false; //Since we are about to give this to user, mark this data as stale + return (gpsYear); +} + +//Get the current month +uint8_t SFE_UBLOX_GPS::getMonth(uint16_t maxWait) +{ + if (moduleQueried.gpsMonth == false) + getTimeData(maxWait); + moduleQueried.gpsMonth = false; //Since we are about to give this to user, mark this data as stale + return (gpsMonth); +} + +//Get the current day +uint8_t SFE_UBLOX_GPS::getDay(uint16_t maxWait) +{ + if (moduleQueried.gpsDay == false) + getTimeData(maxWait); + moduleQueried.gpsDay = false; //Since we are about to give this to user, mark this data as stale + return (gpsDay); +} + +//Get the current hour +uint8_t SFE_UBLOX_GPS::getHour(uint16_t maxWait) +{ + if (moduleQueried.gpsHour == false) + getTimeData(maxWait); + moduleQueried.gpsHour = false; //Since we are about to give this to user, mark this data as stale + return (gpsHour); +} + +//Get the current minute +uint8_t SFE_UBLOX_GPS::getMinute(uint16_t maxWait) +{ + if (moduleQueried.gpsMinute == false) + getTimeData(maxWait); + moduleQueried.gpsMinute = false; //Since we are about to give this to user, mark this data as stale + return (gpsMinute); +} + +//Get the current second +uint8_t SFE_UBLOX_GPS::getSecond(uint16_t maxWait) +{ + if (moduleQueried.gpsSecond == false) + getTimeData(maxWait); + moduleQueried.gpsSecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsSecond); +} + +//Get the current millisecond +uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) +{ + if (moduleQueried.gpsiTOW == false) + getTimeData(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (gpsMillisecond); +} + +//Get the current nanoseconds - includes milliseconds +int32_t SFE_UBLOX_GPS::getNanosecond(uint16_t maxWait) +{ + if (moduleQueried.gpsNanosecond == false) + getTimeData(maxWait); + moduleQueried.gpsNanosecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsNanosecond); +} + +//Get the latest Position/Velocity/Time solution and fill all global variables +boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) +{ + if (autoPVT && autoPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_PVT); + return moduleQueried.all; + } + else if (autoPVT && !autoPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Polling")); + } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_PVT; + packetCfg.len = 0; + //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+MAX_PAYLOAD_SIZE = 84 bytes Note:now hard-coded in processUBX + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + _debugSerial->print(F("getPVT retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } +} + +// Update time info (using appropriate call for the chip series) +boolean SFE_UBLOX_GPS::getTimeData(uint16_t maxWait) +{ + return getProtocolVersionHigh(maxWait) < 15 ? getTIMEUTC(maxWait) : getPVT(maxWait); +} + +// Update position info (using appropriate call for the chip series) +boolean SFE_UBLOX_GPS::getPositionData(uint16_t maxWait) +{ + return getProtocolVersionHigh(maxWait) < 20 ? getPOSLLH(maxWait) : getPVT(maxWait); +} + +//Get time (for use on chips with protocol version 14 and earlier) +boolean SFE_UBLOX_GPS::getTIMEUTC(uint16_t maxWait) +{ + debugPrintln((char *)F("getTIMEUTC: Polling")); + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_TIMEUTC; + packetCfg.len = 0; + //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+MAX_PAYLOAD_SIZE = 84 bytes Note:now hard-coded in processUBX + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + _debugSerial->print(F("getTIMEUTC retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); +} + +//Get posllh (fos use on chips with protocol version 19 and earlier) +boolean SFE_UBLOX_GPS::getPOSLLH(uint16_t maxWait) +{ + debugPrintln((char *)F("getPOSLLH: Polling")); + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_POSLLH; + packetCfg.len = 0; + //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+MAX_PAYLOAD_SIZE = 84 bytes Note:now hard-coded in processUBX + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + _debugSerial->print(F("getPOSLLH retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); +} + +uint32_t SFE_UBLOX_GPS::getTimeOfWeek(uint16_t maxWait /* = 250*/) +{ + if (moduleQueried.gpsiTOW == false) + getTimeData(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (timeOfWeek); +} + +int32_t SFE_UBLOX_GPS::getHighResLatitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLatitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitudeHp); +} + +int32_t SFE_UBLOX_GPS::getHighResLongitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLongitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitudeHp); +} + +int32_t SFE_UBLOX_GPS::getElipsoid(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoid == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoid = false; //Since we are about to give this to user, mark this data as stale + return (elipsoid); +} + +int8_t SFE_UBLOX_GPS::getElipsoidHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoidHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoidHp = false; //Since we are about to give this to user, mark this data as stale + return (elipsoidHp); +} + +int32_t SFE_UBLOX_GPS::getMeanSeaLevel(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevel == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevel = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevel); +} + +int8_t SFE_UBLOX_GPS::getMeanSeaLevelHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevelHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevelHp = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevelHp); +} + +// getGeoidSeparation is currently redundant. The geoid separation seems to only be provided in NMEA GGA and GNS messages. +int32_t SFE_UBLOX_GPS::getGeoidSeparation(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.geoidSeparation == false) + getHPPOSLLH(maxWait); + highResModuleQueried.geoidSeparation = false; //Since we are about to give this to user, mark this data as stale + return (geoidSeparation); +} + +uint32_t SFE_UBLOX_GPS::getHorizontalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.horizontalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.horizontalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (horizontalAccuracy); +} + +uint32_t SFE_UBLOX_GPS::getVerticalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.verticalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.verticalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (verticalAccuracy); +} + +boolean SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) +{ + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSLLH; + packetCfg.len = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are only expecting data (no ACK) +} + +//Get the current 3D high precision positional accuracy - a fun thing to watch +//Returns a long representing the 3D accuracy in millimeters +uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSECEF; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (0); //If command send fails then bail + + uint32_t tempAccuracy = extractLong(24); //We got a response, now extract a long beginning at a given position + + if ((tempAccuracy % 10) >= 5) + tempAccuracy += 5; //Round fraction of mm up to next mm if .5 or above + tempAccuracy /= 10; //Convert 0.1mm units to mm + + return (tempAccuracy); +} + +//Get the current latitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLatitude(uint16_t maxWait) +{ + if (moduleQueried.latitude == false) + getPositionData(maxWait); + moduleQueried.latitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (latitude); +} + +//Get the current longitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLongitude(uint16_t maxWait) +{ + if (moduleQueried.longitude == false) + getPositionData(maxWait); + moduleQueried.longitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (longitude); +} + +//Get the current altitude in mm according to ellipsoid model +int32_t SFE_UBLOX_GPS::getAltitude(uint16_t maxWait) +{ + if (moduleQueried.altitude == false) + getPositionData(maxWait); + moduleQueried.altitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitude); +} + +//Get the current altitude in mm according to mean sea level +//Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html +//Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ +int32_t SFE_UBLOX_GPS::getAltitudeMSL(uint16_t maxWait) +{ + if (moduleQueried.altitudeMSL == false) + getPositionData(maxWait); + moduleQueried.altitudeMSL = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitudeMSL); +} + +//Get the number of satellites used in fix +uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) +{ + if (moduleQueried.SIV == false) + getPVT(maxWait); + moduleQueried.SIV = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (SIV); +} + +//Get the current fix type +//0=no fix, 1=dead reckoning, 2=2D, 3=3D, 4=GNSS, 5=Time fix +uint8_t SFE_UBLOX_GPS::getFixType(uint16_t maxWait) +{ + if (moduleQueried.fixType == false) + { + getPVT(maxWait); + } + moduleQueried.fixType = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (fixType); +} + +//Get the carrier phase range solution status +//Useful when querying module to see if it has high-precision RTK fix +//0=No solution, 1=Float solution, 2=Fixed solution +uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) +{ + if (moduleQueried.carrierSolution == false) + getPVT(maxWait); + moduleQueried.carrierSolution = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (carrierSolution); +} + +//Get the ground speed in mm/s +int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) +{ + if (moduleQueried.groundSpeed == false) + getPVT(maxWait); + moduleQueried.groundSpeed = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (groundSpeed); +} + +//Get the heading of motion (as opposed to heading of car) in degrees * 10^-5 +int32_t SFE_UBLOX_GPS::getHeading(uint16_t maxWait) +{ + if (moduleQueried.headingOfMotion == false) + getPVT(maxWait); + moduleQueried.headingOfMotion = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headingOfMotion); +} + +//Get the positional dillution of precision * 10^-2 (dimensionless) +uint16_t SFE_UBLOX_GPS::getPDOP(uint16_t maxWait) +{ + if (moduleQueried.pDOP == false) + getPVT(maxWait); + moduleQueried.pDOP = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (pDOP); +} + +//Get the current protocol version of the Ublox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionHigh(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionHigh); +} + +//Get the current protocol version of the Ublox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionLow(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionLow); +} + +//Get the current protocol version of the Ublox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +boolean SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) +{ + //Send packet with only CLS and ID, length of zero. This will cause the module to respond with the contents of that CLS/ID. + packetCfg.cls = UBX_CLASS_MON; + packetCfg.id = UBX_MON_VER; + + packetCfg.len = 0; + packetCfg.startingSpot = 40; //Start at first "extended software information" string + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //Payload should now contain ~220 characters (depends on module type) + + // if (_printDebug == true) + // { + // _debugSerial->print(F("MON VER Payload:")); + // for (int location = 0; location < packetCfg.len; location++) + // { + // if (location % 30 == 0) + // _debugSerial->println(); + // _debugSerial->write(payloadCfg[location]); + // } + // _debugSerial->println(); + // } + + //We will step through the payload looking at each extension field of 30 bytes + for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) + { + //Now we need to find "PROTVER=18.00" in the incoming byte stream + if (payloadCfg[(30 * extensionNumber) + 0] == 'P' && payloadCfg[(30 * extensionNumber) + 6] == 'R') + { + versionHigh = (payloadCfg[(30 * extensionNumber) + 8] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 9] - '0'); //Convert '18' to 18 + versionLow = (payloadCfg[(30 * extensionNumber) + 11] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 12] - '0'); //Convert '00' to 00 + moduleQueried.versionNumber = true; //Mark this data as new + + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version: ")); + _debugSerial->print(versionHigh); + _debugSerial->print(F(".")); + _debugSerial->println(versionLow); + } + return (true); //Success! + } + } + + return (false); //We failed +} + +//Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushPVT() +{ + //Mark all datums as stale (read before) + moduleQueried.gpsiTOW = false; + moduleQueried.gpsYear = false; + moduleQueried.gpsMonth = false; + moduleQueried.gpsDay = false; + moduleQueried.gpsHour = false; + moduleQueried.gpsMinute = false; + moduleQueried.gpsSecond = false; + moduleQueried.gpsNanosecond = false; + + moduleQueried.all = false; + moduleQueried.longitude = false; + moduleQueried.latitude = false; + moduleQueried.altitude = false; + moduleQueried.altitudeMSL = false; + moduleQueried.SIV = false; + moduleQueried.fixType = false; + moduleQueried.carrierSolution = false; + moduleQueried.groundSpeed = false; + moduleQueried.headingOfMotion = false; + moduleQueried.pDOP = false; +} + +//Relative Positioning Information in NED frame +//Returns true if commands was successful +boolean SFE_UBLOX_GPS::getRELPOSNED(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RELPOSNED; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //We got a response, now parse the bits + + uint16_t refStationID = extractInt(2); + //_debugSerial->print(F("refStationID: ")); + //_debugSerial->println(refStationID)); + + int32_t tempRelPos; + + tempRelPos = extractLong(8); + relPosInfo.relPosN = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(12); + relPosInfo.relPosE = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(16); + relPosInfo.relPosD = tempRelPos / 100.0; //Convert cm to m + + relPosInfo.relPosLength = extractLong(20); + relPosInfo.relPosHeading = extractLong(24); + + relPosInfo.relPosHPN = payloadCfg[32]; + relPosInfo.relPosHPE = payloadCfg[33]; + relPosInfo.relPosHPD = payloadCfg[34]; + relPosInfo.relPosHPLength = payloadCfg[35]; + + uint32_t tempAcc; + + tempAcc = extractLong(36); + relPosInfo.accN = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(40); + relPosInfo.accE = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(44); + relPosInfo.accD = tempAcc / 10000.0; //Convert 0.1 mm to m + + uint8_t flags = payloadCfg[60]; + + relPosInfo.gnssFixOk = flags & (1 << 0); + relPosInfo.diffSoln = flags & (1 << 1); + relPosInfo.relPosValid = flags & (1 << 2); + relPosInfo.carrSoln = (flags & (0b11 << 3)) >> 3; + relPosInfo.isMoving = flags & (1 << 5); + relPosInfo.refPosMiss = flags & (1 << 6); + relPosInfo.refObsMiss = flags & (1 << 7); + + return (true); +} +boolean SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) +{ + // Requesting Data from the receiver + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // payload should be loaded. + imuMeas.version = extractByte(4); + imuMeas.fusionMode = extractByte(12); + ubloxSen.numSens = extractByte(15); + + // Individual Status Sensor in different function + return (true); +} + +// +boolean SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_INS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // Validity of each sensor value below + uint32_t validity = extractLong(0); + + imuMeas.xAngRateVald = (validity && 0x0080) >> 8; + imuMeas.yAngRateVald = (validity && 0x0100) >> 9; + imuMeas.zAngRateVald = (validity && 0x0200) >> 10; + imuMeas.xAccelVald = (validity && 0x0400) >> 11; + imuMeas.yAccelVald = (validity && 0x0800) >> 12; + imuMeas.zAccelVald = (validity && 0x1000) >> 13; + + imuMeas.xAngRate = extractLong(12); // deg/s + imuMeas.yAngRate = extractLong(16); // deg/s + imuMeas.zAngRate = extractLong(20); // deg/s + + imuMeas.xAccel = extractLong(24); // m/s + imuMeas.yAccel = extractLong(28); // m/s + imuMeas.zAccel = extractLong(32); // m/s + + return (true); +} + +// +boolean SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_MEAS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t timeStamp = extractLong(0); + uint32_t flags = extractInt(4); + + uint8_t timeSent = (flags && 0x01) >> 1; + uint8_t timeEdge = (flags && 0x02) >> 2; + uint8_t tagValid = (flags && 0x04) >> 3; + uint8_t numMeas = (flags && 0x1000) >> 15; + + if (numMeas > DEF_NUM_SENS) + numMeas = DEF_NUM_SENS; + + uint8_t byteOffset = 4; + + for (uint8_t i = 0; i < numMeas; i++) + { + + uint32_t bitField = extractLong(4 + byteOffset * i); + imuMeas.dataType[i] = (bitField && 0xFF000000) >> 23; + imuMeas.data[i] = (bitField && 0xFFFFFF); + imuMeas.dataTStamp[i] = extractLong(8 + byteOffset * i); + } + + return (true); +} + +boolean SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) +{ + + // Need to know the number of sensor to get the correct data + // Rate selected in UBX-CFG-MSG is not respected + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RAW; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t bitField = extractLong(4); + imuMeas.rawDataType = (bitField && 0xFF000000) >> 23; + imuMeas.rawData = (bitField && 0xFFFFFF); + imuMeas.rawTStamp = extractLong(8); + + return (true); +} + +sfe_ublox_status_e SFE_UBLOX_GPS::getSensState(uint8_t sensor, uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + ubloxSen.numSens = extractByte(15); + + if (sensor > ubloxSen.numSens) + return (SFE_UBLOX_STATUS_OUT_OF_RANGE); + + checkUblox(); + + uint8_t offset = 4; + + // Only the last sensor value checked will remain. + for (uint8_t i = 0; i < sensor; i++) + { + + uint8_t sensorFieldOne = extractByte(16 + offset * i); + uint8_t sensorFieldTwo = extractByte(17 + offset * i); + ubloxSen.freq = extractByte(18 + offset * i); + uint8_t sensorFieldThr = extractByte(19 + offset * i); + + ubloxSen.senType = (sensorFieldOne && 0x10) >> 5; + ubloxSen.isUsed = (sensorFieldOne && 0x20) >> 6; + ubloxSen.isReady = (sensorFieldOne && 0x30) >> 7; + + ubloxSen.calibStatus = sensorFieldTwo && 0x03; + ubloxSen.timeStatus = (sensorFieldTwo && 0xC) >> 2; + + ubloxSen.badMeas = (sensorFieldThr && 0x01); + ubloxSen.badTag = (sensorFieldThr && 0x02) >> 1; + ubloxSen.missMeas = (sensorFieldThr && 0x04) >> 2; + ubloxSen.noisyMeas = (sensorFieldThr && 0x08) >> 3; + } + + return (SFE_UBLOX_STATUS_SUCCESS); +} + +boolean SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_ATT; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + checkUblox(); + + vehAtt.roll = extractLong(8); + vehAtt.pitch = extractLong(12); + vehAtt.heading = extractLong(16); + + vehAtt.accRoll = extractLong(20); + vehAtt.accPitch = extractLong(24); + vehAtt.accHeading = extractLong(28); + + return (true); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h b/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h new file mode 100644 index 0000000..159ea46 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h @@ -0,0 +1,932 @@ +/* + This is a library written for the Ublox ZED-F9P and NEO-M8P-2 + + Updated: June 16th, 2020 + + This copy includes changes by @blazczak and @geeksville to + provide support for the older series 6 and 7 modules. + + Disclaimer: SparkFun has not verified this copy of the library on either series 6 or 7. + It should work, it looks like it will work, but we have no way of confirming this. + We cannot guarantee that it will work reliably in your application. + + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original library written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a Ublox GPS module. Works with most modules from Ublox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SPARKFUN_UBLOX_ARDUINO_LIBRARY_H +#define SPARKFUN_UBLOX_ARDUINO_LIBRARY_H + +#if (ARDUINO >= 100) +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include + +//Platform specific configurations + +//Define the size of the I2C buffer based on the platform the user has +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + +//I2C_BUFFER_LENGTH is defined in Wire.H +#define I2C_BUFFER_LENGTH BUFFER_LENGTH + +#elif defined(__SAMD21G18A__) + +//SAMD21 uses RingBuffer.h +#define I2C_BUFFER_LENGTH SERIAL_BUFFER_SIZE + +//#elif __MK20DX256__ +//Teensy + +#endif + +#ifndef I2C_BUFFER_LENGTH + +//The catch-all default is 32 +#define I2C_BUFFER_LENGTH 32 +//#define I2C_BUFFER_LENGTH 16 //For testing on Artemis + +#endif + +// Define Serial for SparkFun SAMD based boards. +// Boards like the RedBoard Turbo use SerialUSB (not Serial). +// But other boards like the SAMD51 Thing Plus use Serial (not SerialUSB). +// The next nine lines let the code compile cleanly on as many SAMD boards as possible. +#if defined(ARDUINO_ARCH_SAMD) // Is this a SAMD board? + #if defined(USB_VID) // Is the USB Vendor ID defined? + #if (USB_VID == 0x1B4F) // Is this a SparkFun board? + #if !defined(ARDUINO_SAMD51_THING_PLUS) // If it is not a SAMD51 Thing Plus + #define Serial SerialUSB // Define Serial as SerialUSB + #endif + #endif + #endif +#endif +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Define a digital pin to aid checksum failure capture and analysis +//Leave set to -1 if not needed +const int checksumFailurePin = -1; + +// Global Status Returns +typedef enum +{ + SFE_UBLOX_STATUS_SUCCESS, + SFE_UBLOX_STATUS_FAIL, + SFE_UBLOX_STATUS_CRC_FAIL, + SFE_UBLOX_STATUS_TIMEOUT, + SFE_UBLOX_STATUS_COMMAND_NACK, // Indicates that the command was unrecognised, invalid or that the module is too busy to respond + SFE_UBLOX_STATUS_OUT_OF_RANGE, + SFE_UBLOX_STATUS_INVALID_ARG, + SFE_UBLOX_STATUS_INVALID_OPERATION, + SFE_UBLOX_STATUS_MEM_ERR, + SFE_UBLOX_STATUS_HW_ERR, + SFE_UBLOX_STATUS_DATA_SENT, // This indicates that a 'set' was successful + SFE_UBLOX_STATUS_DATA_RECEIVED, // This indicates that a 'get' (poll) was successful + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, + SFE_UBLOX_STATUS_DATA_OVERWRITTEN // This is an error - the data was valid but has been or _is being_ overwritten by another packet +} sfe_ublox_status_e; + +// ubxPacket validity +typedef enum +{ + SFE_UBLOX_PACKET_VALIDITY_NOT_VALID, + SFE_UBLOX_PACKET_VALIDITY_VALID, + SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, + SFE_UBLOX_PACKET_NOTACKNOWLEDGED // This indicates that we received a NACK +} sfe_ublox_packet_validity_e; + +// Identify which packet buffer is in use: +// packetCfg (or a custom packet), packetAck or packetBuf +typedef enum +{ + SFE_UBLOX_PACKET_PACKETCFG, + SFE_UBLOX_PACKET_PACKETACK, + SFE_UBLOX_PACKET_PACKETBUF +} sfe_ublox_packet_buffer_e; + +//Registers +const uint8_t UBX_SYNCH_1 = 0xB5; +const uint8_t UBX_SYNCH_2 = 0x62; + +//The following are UBX Class IDs. Descriptions taken from ZED-F9P Interface Description Document page 32, NEO-M8P Interface Description page 145 +const uint8_t UBX_CLASS_NAV = 0x01; //Navigation Results Messages: Position, Speed, Time, Acceleration, Heading, DOP, SVs used +const uint8_t UBX_CLASS_RXM = 0x02; //Receiver Manager Messages: Satellite Status, RTC Status +const uint8_t UBX_CLASS_INF = 0x04; //Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice +const uint8_t UBX_CLASS_ACK = 0x05; //Ack/Nak Messages: Acknowledge or Reject messages to UBX-CFG input messages +const uint8_t UBX_CLASS_CFG = 0x06; //Configuration Input Messages: Configure the receiver. +const uint8_t UBX_CLASS_UPD = 0x09; //Firmware Update Messages: Memory/Flash erase/write, Reboot, Flash identification, etc. +const uint8_t UBX_CLASS_MON = 0x0A; //Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status +const uint8_t UBX_CLASS_AID = 0x0B; //(NEO-M8P ONLY!!!) AssistNow Aiding Messages: Ephemeris, Almanac, other A-GPS data input +const uint8_t UBX_CLASS_TIM = 0x0D; //Timing Messages: Time Pulse Output, Time Mark Results +const uint8_t UBX_CLASS_ESF = 0x10; //(NEO-M8P ONLY!!!) External Sensor Fusion Messages: External Sensor Measurements and Status Information +const uint8_t UBX_CLASS_MGA = 0x13; //Multiple GNSS Assistance Messages: Assistance data for various GNSS +const uint8_t UBX_CLASS_LOG = 0x21; //Logging Messages: Log creation, deletion, info and retrieval +const uint8_t UBX_CLASS_SEC = 0x27; //Security Feature Messages +const uint8_t UBX_CLASS_HNR = 0x28; //(NEO-M8P ONLY!!!) High Rate Navigation Results Messages: High rate time, position speed, heading +const uint8_t UBX_CLASS_NMEA = 0xF0; //NMEA Strings: standard NMEA strings + +//The following are used for configuration. Descriptions are from the ZED-F9P Interface Description pg 33-34 and NEO-M9N Interface Description pg 47-48 +const uint8_t UBX_CFG_ANT = 0x13; //Antenna Control Settings. Used to configure the antenna control settings +const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. +const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration +const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum +const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence +const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration +const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration +const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration +const uint8_t UBX_CFG_MSG = 0x01; //Poll a message configuration, or Set Message Rate(s), or Set Message Rate +const uint8_t UBX_CFG_NAV5 = 0x24; //Navigation Engine Settings. Used to configure the navigation engine including the dynamic model. +const uint8_t UBX_CFG_NAVX5 = 0x23; //Navigation Engine Expert Settings +const uint8_t UBX_CFG_NMEA = 0x17; //Extended NMEA protocol configuration V1 +const uint8_t UBX_CFG_ODO = 0x1E; //Odometer, Low-speed COG Engine Settings +const uint8_t UBX_CFG_PM2 = 0x3B; //Extended power management configuration +const uint8_t UBX_CFG_PMS = 0x86; //Power mode setup +const uint8_t UBX_CFG_PRT = 0x00; //Used to configure port specifics. Polls the configuration for one I/O Port, or Port configuration for UART ports, or Port configuration for USB port, or Port configuration for SPI port, or Port configuration for DDC port +const uint8_t UBX_CFG_PWR = 0x57; //Put receiver in a defined power state +const uint8_t UBX_CFG_RATE = 0x08; //Navigation/Measurement Rate Settings. Used to set port baud rates. +const uint8_t UBX_CFG_RINV = 0x34; //Contents of Remote Inventory +const uint8_t UBX_CFG_RST = 0x04; //Reset Receiver / Clear Backup Data Structures. Used to reset device. +const uint8_t UBX_CFG_RXM = 0x11; //RXM configuration +const uint8_t UBX_CFG_SBAS = 0x16; //SBAS configuration +const uint8_t UBX_CFG_TMODE3 = 0x71; //Time Mode Settings 3. Used to enable Survey In Mode +const uint8_t UBX_CFG_TP5 = 0x31; //Time Pulse Parameters +const uint8_t UBX_CFG_USB = 0x1B; //USB Configuration +const uint8_t UBX_CFG_VALDEL = 0x8C; //Used for config of higher version Ublox modules (ie protocol v27 and above). Deletes values corresponding to provided keys/ provided keys with a transaction +const uint8_t UBX_CFG_VALGET = 0x8B; //Used for config of higher version Ublox modules (ie protocol v27 and above). Configuration Items +const uint8_t UBX_CFG_VALSET = 0x8A; //Used for config of higher version Ublox modules (ie protocol v27 and above). Sets values corresponding to provided key-value pairs/ provided key-value pairs within a transaction. + +//The following are used to enable NMEA messages. Descriptions come from the NMEA messages overview in the ZED-F9P Interface Description +const uint8_t UBX_NMEA_MSB = 0xF0; //All NMEA enable commands have 0xF0 as MSB +const uint8_t UBX_NMEA_DTM = 0x0A; //GxDTM (datum reference) +const uint8_t UBX_NMEA_GAQ = 0x45; //GxGAQ (poll a standard message (if the current talker ID is GA)) +const uint8_t UBX_NMEA_GBQ = 0x44; //GxGBQ (poll a standard message (if the current Talker ID is GB)) +const uint8_t UBX_NMEA_GBS = 0x09; //GxGBS (GNSS satellite fault detection) +const uint8_t UBX_NMEA_GGA = 0x00; //GxGGA (Global positioning system fix data) +const uint8_t UBX_NMEA_GLL = 0x01; //GxGLL (latitude and long, whith time of position fix and status) +const uint8_t UBX_NMEA_GLQ = 0x43; //GxGLQ (poll a standard message (if the current Talker ID is GL)) +const uint8_t UBX_NMEA_GNQ = 0x42; //GxGNQ (poll a standard message (if the current Talker ID is GN)) +const uint8_t UBX_NMEA_GNS = 0x0D; //GxGNS (GNSS fix data) +const uint8_t UBX_NMEA_GPQ = 0x040; //GxGPQ (poll a standard message (if the current Talker ID is GP)) +const uint8_t UBX_NMEA_GRS = 0x06; //GxGRS (GNSS range residuals) +const uint8_t UBX_NMEA_GSA = 0x02; //GxGSA (GNSS DOP and Active satellites) +const uint8_t UBX_NMEA_GST = 0x07; //GxGST (GNSS Pseudo Range Error Statistics) +const uint8_t UBX_NMEA_GSV = 0x03; //GxGSV (GNSS satellites in view) +const uint8_t UBX_NMEA_RMC = 0x04; //GxRMC (Recommended minimum data) +const uint8_t UBX_NMEA_TXT = 0x41; //GxTXT (text transmission) +const uint8_t UBX_NMEA_VLW = 0x0F; //GxVLW (dual ground/water distance) +const uint8_t UBX_NMEA_VTG = 0x05; //GxVTG (course over ground and Ground speed) +const uint8_t UBX_NMEA_ZDA = 0x08; //GxZDA (Time and Date) + +//The following are used to configure the NMEA protocol main talker ID and GSV talker ID +const uint8_t UBX_NMEA_MAINTALKERID_NOTOVERRIDDEN = 0x00; //main talker ID is system dependent +const uint8_t UBX_NMEA_MAINTALKERID_GP = 0x01; //main talker ID is GPS +const uint8_t UBX_NMEA_MAINTALKERID_GL = 0x02; //main talker ID is GLONASS +const uint8_t UBX_NMEA_MAINTALKERID_GN = 0x03; //main talker ID is combined receiver +const uint8_t UBX_NMEA_MAINTALKERID_GA = 0x04; //main talker ID is Galileo +const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou +const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) +const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID + +//The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class +const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents +const uint8_t UBX_INF_ERROR = 0x00; //ASCII output with error contents +const uint8_t UBX_INF_NOTICE = 0x02; //ASCII output with informational contents +const uint8_t UBX_INF_TEST = 0x03; //ASCII output with test contents +const uint8_t UBX_INF_WARNING = 0x01; //ASCII output with warning contents + +//The following are used to configure LOG UBX messages (loggings messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_LOG_CREATE = 0x07; //Create Log File +const uint8_t UBX_LOG_ERASE = 0x03; //Erase Logged Data +const uint8_t UBX_LOG_FINDTIME = 0x0E; //Find index of a log entry based on a given time, or response to FINDTIME requested +const uint8_t UBX_LOG_INFO = 0x08; //Poll for log information, or Log information +const uint8_t UBX_LOG_RETRIEVEPOSEXTRA = 0x0F; //Odometer log entry +const uint8_t UBX_LOG_RETRIEVEPOS = 0x0B; //Position fix log entry +const uint8_t UBX_LOG_RETRIEVESTRING = 0x0D; //Byte string log entry +const uint8_t UBX_LOG_RETRIEVE = 0x09; //Request log data +const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board flash + +//The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance +const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance +const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance +const uint8_t UBX_MGA_BDS_UTC = 0x03; //BDS UTC Assistance +const uint8_t UBX_MGA_BDS_IONO = 0x03; //BDS Ionospheric Assistance +const uint8_t UBX_MGA_DBD = 0x80; //Either: Poll the Navigation Database, or Navigation Database Dump Entry +const uint8_t UBX_MGA_GAL_EPH = 0x02; //Galileo Ephemeris Assistance +const uint8_t UBX_MGA_GAL_ALM = 0x02; //Galileo Almanac Assitance +const uint8_t UBX_MGA_GAL_TIMOFFSET = 0x02; //Galileo GPS time offset assistance +const uint8_t UBX_MGA_GAL_UTC = 0x02; //Galileo UTC Assistance +const uint8_t UBX_MGA_GLO_EPH = 0x06; //GLONASS Ephemeris Assistance +const uint8_t UBX_MGA_GLO_ALM = 0x06; //GLONASS Almanac Assistance +const uint8_t UBX_MGA_GLO_TIMEOFFSET = 0x06; //GLONASS Auxiliary Time Offset Assistance +const uint8_t UBX_MGA_GPS_EPH = 0x00; //GPS Ephemeris Assistance +const uint8_t UBX_MGA_GPS_ALM = 0x00; //GPS Almanac Assistance +const uint8_t UBX_MGA_GPS_HEALTH = 0x00; //GPS Health Assistance +const uint8_t UBX_MGA_GPS_UTC = 0x00; //GPS UTC Assistance +const uint8_t UBX_MGA_GPS_IONO = 0x00; //GPS Ionosphere Assistance +const uint8_t UBX_MGA_INI_POS_XYZ = 0x40; //Initial Position Assistance +const uint8_t UBX_MGA_INI_POS_LLH = 0x40; //Initial Position Assitance +const uint8_t UBX_MGA_INI_TIME_UTC = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_TIME_GNSS = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_CLKD = 0x40; //Initial Clock Drift Assitance +const uint8_t UBX_MGA_INI_FREQ = 0x40; //Initial Frequency Assistance +const uint8_t UBX_MGA_INI_EOP = 0x40; //Earth Orientation Parameters Assistance +const uint8_t UBX_MGA_QZSS_EPH = 0x05; //QZSS Ephemeris Assistance +const uint8_t UBX_MGA_QZSS_ALM = 0x05; //QZSS Almanac Assistance +const uint8_t UBX_MGA_QZAA_HEALTH = 0x05; //QZSS Health Assistance + +//The following are used to configure the MON UBX messages (monitoring messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35) +const uint8_t UBX_MON_COMMS = 0x36; //Comm port information +const uint8_t UBX_MON_GNSS = 0x28; //Information message major GNSS selection +const uint8_t UBX_MON_HW2 = 0x0B; //Extended Hardware Status +const uint8_t UBX_MON_HW3 = 0x37; //HW I/O pin information +const uint8_t UBX_MON_HW = 0x09; //Hardware Status +const uint8_t UBX_MON_IO = 0x02; //I/O Subsystem Status +const uint8_t UBX_MON_MSGPP = 0x06; //Message Parse and Process Status +const uint8_t UBX_MON_PATCH = 0x27; //Output information about installed patches +const uint8_t UBX_MON_RF = 0x38; //RF information +const uint8_t UBX_MON_RXBUF = 0x07; //Receiver Buffer Status +const uint8_t UBX_MON_RXR = 0x21; //Receiver Status Information +const uint8_t UBX_MON_TXBUF = 0x08; //Transmitter Buffer Status. Used for query tx buffer size/state. +const uint8_t UBX_MON_VER = 0x04; //Receiver/Software Version. Used for obtaining Protocol Version. + +//The following are used to configure the NAV UBX messages (navigation results messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35-36) +const uint8_t UBX_NAV_ATT = 0x05; //Vehicle "Attitude" Solution +const uint8_t UBX_NAV_CLOCK = 0x22; //Clock Solution +const uint8_t UBX_NAV_DOP = 0x04; //Dilution of precision +const uint8_t UBX_NAV_EOE = 0x61; //End of Epoch +const uint8_t UBX_NAV_GEOFENCE = 0x39; //Geofencing status. Used to poll the geofence status +const uint8_t UBX_NAV_HPPOSECEF = 0x13; //High Precision Position Solution in ECEF. Used to find our positional accuracy (high precision). +const uint8_t UBX_NAV_HPPOSLLH = 0x14; //High Precision Geodetic Position Solution. Used for obtaining lat/long/alt in high precision +const uint8_t UBX_NAV_ODO = 0x09; //Odometer Solution +const uint8_t UBX_NAV_ORB = 0x34; //GNSS Orbit Database Info +const uint8_t UBX_NAV_POSECEF = 0x01; //Position Solution in ECEF +const uint8_t UBX_NAV_POSLLH = 0x02; //Geodetic Position Solution +const uint8_t UBX_NAV_PVT = 0x07; //All the things! Position, velocity, time, PDOP, height, h/v accuracies, number of satellites. Navigation Position Velocity Time Solution. +const uint8_t UBX_NAV_RELPOSNED = 0x3C; //Relative Positioning Information in NED frame +const uint8_t UBX_NAV_RESETODO = 0x10; //Reset odometer +const uint8_t UBX_NAV_SAT = 0x35; //Satellite Information +const uint8_t UBX_NAV_SIG = 0x43; //Signal Information +const uint8_t UBX_NAV_STATUS = 0x03; //Receiver Navigation Status +const uint8_t UBX_NAV_SVIN = 0x3B; //Survey-in data. Used for checking Survey In status +const uint8_t UBX_NAV_TIMEBDS = 0x24; //BDS Time Solution +const uint8_t UBX_NAV_TIMEGAL = 0x25; //Galileo Time Solution +const uint8_t UBX_NAV_TIMEGLO = 0x23; //GLO Time Solution +const uint8_t UBX_NAV_TIMEGPS = 0x20; //GPS Time Solution +const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information +const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution +const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF +const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED + +//The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_RXM_MEASX = 0x14; //Satellite Measurements for RRLP +const uint8_t UBX_RXM_PMREQ = 0x41; //Requests a Power Management task (two differenent packet sizes) +const uint8_t UBX_RXM_RAWX = 0x15; //Multi-GNSS Raw Measurement Data +const uint8_t UBX_RXM_RLM = 0x59; //Galileo SAR Short-RLM report (two different packet sizes) +const uint8_t UBX_RXM_RTCM = 0x32; //RTCM input status +const uint8_t UBX_RXM_SFRBX = 0x13; //Boradcast Navigation Data Subframe + +//The following are used to configure the SEC UBX messages (security feature messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_SEC_UNIQID = 0x03; //Unique chip ID + +//The following are used to configure the TIM UBX messages (timing messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_TIM_TM2 = 0x03; //Time mark data +const uint8_t UBX_TIM_TP = 0x01; //Time Pulse Timedata +const uint8_t UBX_TIM_VRFY = 0x06; //Sourced Time Verification + +//The following are used to configure the UPD UBX messages (firmware update messages). Descriptions from UBX messages overview (ZED-F9P Interface Description Document page 36) +const uint8_t UBX_UPD_SOS = 0x14; //Poll Backup Fil Restore Status, Create Backup File in Flash, Clear Backup File in Flash, Backup File Creation Acknowledge, System Restored from Backup + +//The following are used to enable RTCM messages +const uint8_t UBX_RTCM_MSB = 0xF5; //All RTCM enable commands have 0xF5 as MSB +const uint8_t UBX_RTCM_1005 = 0x05; //Stationary RTK reference ARP +const uint8_t UBX_RTCM_1074 = 0x4A; //GPS MSM4 +const uint8_t UBX_RTCM_1077 = 0x4D; //GPS MSM7 +const uint8_t UBX_RTCM_1084 = 0x54; //GLONASS MSM4 +const uint8_t UBX_RTCM_1087 = 0x57; //GLONASS MSM7 +const uint8_t UBX_RTCM_1094 = 0x5E; //Galileo MSM4 +const uint8_t UBX_RTCM_1097 = 0x61; //Galileo MSM7 +const uint8_t UBX_RTCM_1124 = 0x7C; //BeiDou MSM4 +const uint8_t UBX_RTCM_1127 = 0x7F; //BeiDou MSM7 +const uint8_t UBX_RTCM_1230 = 0xE6; //GLONASS code-phase biases, set to once every 10 seconds +const uint8_t UBX_RTCM_4072_0 = 0xFE; //Reference station PVT (ublox proprietary RTCM message) +const uint8_t UBX_RTCM_4072_1 = 0xFD; //Additional reference station information (ublox proprietary RTCM message) + +const uint8_t UBX_ACK_NACK = 0x00; +const uint8_t UBX_ACK_ACK = 0x01; +const uint8_t UBX_ACK_NONE = 0x02; //Not a real value + +// The following constants are used to get External Sensor Measurements and Status +// Information. +const uint8_t UBX_ESF_MEAS = 0x02; +const uint8_t UBX_ESF_RAW = 0x03; +const uint8_t UBX_ESF_STATUS = 0x10; +const uint8_t UBX_ESF_INS = 0x15; //36 bytes + +const uint8_t SVIN_MODE_DISABLE = 0x00; +const uint8_t SVIN_MODE_ENABLE = 0x01; + +//The following consts are used to configure the various ports and streams for those ports. See -CFG-PRT. +const uint8_t COM_PORT_I2C = 0; +const uint8_t COM_PORT_UART1 = 1; +const uint8_t COM_PORT_UART2 = 2; +const uint8_t COM_PORT_USB = 3; +const uint8_t COM_PORT_SPI = 4; + +const uint8_t COM_TYPE_UBX = (1 << 0); +const uint8_t COM_TYPE_NMEA = (1 << 1); +const uint8_t COM_TYPE_RTCM3 = (1 << 5); + +//The following consts are used to generate KEY values for the advanced protocol functions of VELGET/SET/DEL +const uint8_t VAL_SIZE_1 = 0x01; //One bit +const uint8_t VAL_SIZE_8 = 0x02; //One byte +const uint8_t VAL_SIZE_16 = 0x03; //Two bytes +const uint8_t VAL_SIZE_32 = 0x04; //Four bytes +const uint8_t VAL_SIZE_64 = 0x05; //Eight bytes + +//These are the Bitfield layers definitions for the UBX-CFG-VALSET message (not to be confused with Bitfield deviceMask in UBX-CFG-CFG) +const uint8_t VAL_LAYER_RAM = (1 << 0); +const uint8_t VAL_LAYER_BBR = (1 << 1); +const uint8_t VAL_LAYER_FLASH = (1 << 2); + +//Below are various Groups, IDs, and sizes for various settings +//These can be used to call getVal/setVal/delVal +const uint8_t VAL_GROUP_I2COUTPROT = 0x72; +const uint8_t VAL_GROUP_I2COUTPROT_SIZE = VAL_SIZE_1; //All fields in I2C group are currently 1 bit + +const uint8_t VAL_ID_I2COUTPROT_UBX = 0x01; +const uint8_t VAL_ID_I2COUTPROT_NMEA = 0x02; +const uint8_t VAL_ID_I2COUTPROT_RTCM3 = 0x03; + +const uint8_t VAL_GROUP_I2C = 0x51; +const uint8_t VAL_GROUP_I2C_SIZE = VAL_SIZE_8; //All fields in I2C group are currently 1 byte + +const uint8_t VAL_ID_I2C_ADDRESS = 0x01; + +// Configuration Sub-Section mask definitions for saveConfigSelective (UBX-CFG-CFG) +const uint32_t VAL_CFG_SUBSEC_IOPORT = 0x00000001; // ioPort - communications port settings (causes IO system reset!) +const uint32_t VAL_CFG_SUBSEC_MSGCONF = 0x00000002; // msgConf - message configuration +const uint32_t VAL_CFG_SUBSEC_INFMSG = 0x00000004; // infMsg - INF message configuration +const uint32_t VAL_CFG_SUBSEC_NAVCONF = 0x00000008; // navConf - navigation configuration +const uint32_t VAL_CFG_SUBSEC_RXMCONF = 0x00000010; // rxmConf - receiver manager configuration +const uint32_t VAL_CFG_SUBSEC_SENCONF = 0x00000100; // senConf - sensor interface configuration (requires protocol 19+) +const uint32_t VAL_CFG_SUBSEC_RINVCONF = 0x00000200; // rinvConf - remove inventory configuration +const uint32_t VAL_CFG_SUBSEC_ANTCONF = 0x00000400; // antConf - antenna configuration +const uint32_t VAL_CFG_SUBSEC_LOGCONF = 0x00000800; // logConf - logging configuration +const uint32_t VAL_CFG_SUBSEC_FTSCONF = 0x00001000; // ftsConf - FTS configuration (FTS products only) + +enum dynModel // Possible values for the dynamic platform model, which provide more accuract position output for the situation. Description extracted from ZED-F9P Integration Manual +{ + DYN_MODEL_PORTABLE = 0, //Applications with low acceleration, e.g. portable devices. Suitable for most situations. + // 1 is not defined + DYN_MODEL_STATIONARY = 2, //Used in timing applications (antenna must be stationary) or other stationary applications. Velocity restricted to 0 m/s. Zero dynamics assumed. + DYN_MODEL_PEDESTRIAN, //Applications with low acceleration and speed, e.g. how a pedestrian would move. Low acceleration assumed. + DYN_MODEL_AUTOMOTIVE, //Used for applications with equivalent dynamics to those of a passenger car. Low vertical acceleration assumed + DYN_MODEL_SEA, //Recommended for applications at sea, with zero vertical velocity. Zero vertical velocity assumed. Sea level assumed. + DYN_MODEL_AIRBORNE1g, //Airborne <1g acceleration. Used for applications with a higher dynamic range and greater vertical acceleration than a passenger car. No 2D position fixes supported. + DYN_MODEL_AIRBORNE2g, //Airborne <2g acceleration. Recommended for typical airborne environments. No 2D position fixes supported. + DYN_MODEL_AIRBORNE4g, //Airborne <4g acceleration. Only recommended for extremely dynamic environments. No 2D position fixes supported. + DYN_MODEL_WRIST, // Not supported in protocol versions less than 18. Only recommended for wrist worn applications. Receiver will filter out arm motion. + DYN_MODEL_BIKE, // Supported in protocol versions 19.2 +}; + +#ifndef MAX_PAYLOAD_SIZE + +#define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules +//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values + +#endif + +//-=-=-=-=- UBX binary specific variables +typedef struct +{ + uint8_t cls; + uint8_t id; + uint16_t len; //Length of the payload. Does not include cls, id, or checksum bytes + uint16_t counter; //Keeps track of number of overall bytes received. Some responses are larger than 255 bytes. + uint16_t startingSpot; //The counter value needed to go past before we begin recording into payload array + uint8_t *payload; + uint8_t checksumA; //Given to us from module. Checked against the rolling calculated A/B checksums. + uint8_t checksumB; + sfe_ublox_packet_validity_e valid; //Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked + sfe_ublox_packet_validity_e classAndIDmatch; // Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID +} ubxPacket; + +// Struct to hold the results returned by getGeofenceState (returned by UBX-NAV-GEOFENCE) +typedef struct +{ + uint8_t status; // Geofencing status: 0 - Geofencing not available or not reliable; 1 - Geofencing active + uint8_t numFences; // Number of geofences + uint8_t combState; // Combined (logical OR) state of all geofences: 0 - Unknown; 1 - Inside; 2 - Outside + uint8_t states[4]; // Geofence states: 0 - Unknown; 1 - Inside; 2 - Outside +} geofenceState; + +// Struct to hold the current geofence parameters +typedef struct +{ + uint8_t numFences; // Number of active geofences + int32_t lats[4]; // Latitudes of geofences (in degrees * 10^-7) + int32_t longs[4]; // Longitudes of geofences (in degrees * 10^-7) + uint32_t rads[4]; // Radii of geofences (in m * 10^-2) +} geofenceParams; + +class SFE_UBLOX_GPS +{ +public: + SFE_UBLOX_GPS(void); + +// A default of 250ms for maxWait seems fine for I2C but is not enough for SerialUSB. +// If you know you are only going to be using I2C / Qwiic communication, you can +// safely reduce defaultMaxWait to 250. +#ifndef defaultMaxWait // Let's allow the user to define their own value if they want to +#define defaultMaxWait 1100 +#endif + + //By default use the default I2C address, and use Wire port + boolean begin(TwoWire &wirePort = Wire, uint8_t deviceAddress = 0x42); //Returns true if module is detected + //serialPort needs to be perviously initialized to correct baud rate + boolean begin(Stream &serialPort); //Returns true if module is detected + + //Returns true if device answers on _gpsI2Caddress address or via Serial + //maxWait is only used for Serial + boolean isConnected(uint16_t maxWait = 1100); + + //Changed in V1.8.1: provides backward compatibility for the examples that call checkUblox directly + //Will default to using packetCfg to look for explicit autoPVT packets so they get processed correctly by processUBX + boolean checkUblox(uint8_t requestedClass = UBX_CLASS_NAV, uint8_t requestedID = UBX_NAV_PVT); //Checks module with user selected commType + + boolean checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process() + boolean checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process() + + void process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Processes NMEA and UBX binary sentences one byte at a time + void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure + void processRTCMframe(uint8_t incoming); //Monitor the incoming bytes for start and length bytes + void processRTCM(uint8_t incoming) __attribute__((weak)); //Given rtcm byte, do something with it. User can overwrite if desired to pipe bytes to radio, internet, etc. + + void processUBXpacket(ubxPacket *msg); //Once a packet has been received and validated, identify this packet's class/id and update internal flags + void processNMEA(char incoming) __attribute__((weak)); //Given a NMEA character, do something with it. User can overwrite if desired to use something like tinyGPS or MicroNMEA libraries + + void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages + sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait); //Given a packet and payload, send everything including CRC bytes, return true if we got a response + sfe_ublox_status_e sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait = 250); + void sendSerialCommand(ubxPacket *outgoingUBX); + + void printPacket(ubxPacket *packet); //Useful for debugging + + void factoryReset(); //Send factory reset sequence (i.e. load "default" configuration and perform hardReset) + void hardReset(); //Perform a reset leading to a cold start (zero info start-up) + + boolean setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = 250); //Changes the I2C address of the Ublox module + void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the Ublox module, uartPort should be COM_PORT_UART1/2 + void setNMEAOutputPort(Stream &nmeaOutputPort); //Sets the internal variable for the port to direct NMEA characters to + + boolean setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second + uint8_t getNavigationFrequency(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + boolean saveConfiguration(uint16_t maxWait = defaultMaxWait); //Save current configuration to flash and BBR (battery backed RAM) + boolean factoryDefault(uint16_t maxWait = defaultMaxWait); //Reset module to factory defaults + boolean saveConfigSelective(uint32_t configMask, uint16_t maxWait = defaultMaxWait); //Save the selected configuration sub-sections to flash and BBR (battery backed RAM) + + sfe_ublox_status_e waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet and an ACK is received + sfe_ublox_status_e waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet is received + +// getPVT will only return data once in each navigation cycle. By default, that is once per second. +// Therefore we should set getPVTmaxWait to slightly longer than that. +// If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) +// then you should use a shorter maxWait for getPVT. 300msec would be about right: getPVT(300) +// The same is true for getHPPOSLLH. +#define getPVTmaxWait 1100 // Default maxWait for getPVT and all functions which call it +#define getHPPOSLLHmaxWait 1100 // Default maxWait for getHPPOSLLH and all functions which call it + + boolean assumeAutoPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already + boolean setAutoPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency + boolean getPVT(uint16_t maxWait = getPVTmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. + boolean getTimeData(uint16_t maxWait = getPVTmaxWait); //Query module for latest time data. Calls getPVT or getTIMEUTC depending on which module is attached. + boolean getPositionData(uint16_t maxWait = getPVTmaxWait); //Query module for latest position data. Calls getPVT or getPOSLLH depending on which module is attached. + boolean getTIMEUTC(uint16_t maxWait = getPVTmaxWait); //Query module for current time (for use with older chip series). Returns true if new data is available. + boolean getPOSLLH(uint16_t maxWait = getPVTmaxWait); //Query module for current position (for use with older chip series). Returns true if new data is available. + + boolean setAutoPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHPPOSLLH(uint16_t maxWait = getHPPOSLLHmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. + void flushPVT(); //Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure + + int32_t getLatitude(uint16_t maxWait = getPVTmaxWait); //Returns the current latitude in degrees * 10^-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getLongitude(uint16_t maxWait = getPVTmaxWait); //Returns the current longitude in degrees * 10-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getAltitude(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above ellipsoid + int32_t getAltitudeMSL(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above mean sea level + uint8_t getSIV(uint16_t maxWait = getPVTmaxWait); //Returns number of sats used in fix + uint8_t getFixType(uint16_t maxWait = getPVTmaxWait); //Returns the type of fix: 0=no, 3=3D, 4=GNSS+Deadreckoning + uint8_t getCarrierSolutionType(uint16_t maxWait = getPVTmaxWait); //Returns RTK solution: 0=no, 1=float solution, 2=fixed solution + int32_t getGroundSpeed(uint16_t maxWait = getPVTmaxWait); //Returns speed in mm/s + int32_t getHeading(uint16_t maxWait = getPVTmaxWait); //Returns heading in degrees * 10^-7 + uint16_t getPDOP(uint16_t maxWait = getPVTmaxWait); //Returns positional dillution of precision * 10^-2 (dimensionless) + uint16_t getYear(uint16_t maxWait = getPVTmaxWait); + uint8_t getMonth(uint16_t maxWait = getPVTmaxWait); + uint8_t getDay(uint16_t maxWait = getPVTmaxWait); + uint8_t getHour(uint16_t maxWait = getPVTmaxWait); + uint8_t getMinute(uint16_t maxWait = getPVTmaxWait); + uint8_t getSecond(uint16_t maxWait = getPVTmaxWait); + uint16_t getMillisecond(uint16_t maxWait = getPVTmaxWait); + int32_t getNanosecond(uint16_t maxWait = getPVTmaxWait); + uint32_t getTimeOfWeek(uint16_t maxWait = getPVTmaxWait); + + int32_t getHighResLatitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLatitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getHighResLongitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLongitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getElipsoid(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getElipsoidHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getMeanSeaLevel(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getMeanSeaLevelHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getGeoidSeparation(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getHorizontalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getVerticalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + + //Port configurations + boolean setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof + boolean getPortSettings(uint8_t portID, uint16_t maxWait = defaultMaxWait); //Returns the current protocol bits in the UBX-CFG-PRT command for a given port + + boolean setI2COutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUSBOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setSPIOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof + + //Functions to turn on/off message types for a given port ID (see COM_PORT_I2C, etc above) + boolean configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); + boolean enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); //Given a message number turns on a message ID for output over given PortID + boolean disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); //Turn off given RTCM message from a given port + + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) + //It is probably safe to assume that users of the ZED-F9P will be using I2C / Qwiic. + //If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 32-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); //Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); //Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); //Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); //Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + + //Functions used for RTK and base station setup + //It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. + boolean getSurveyMode(uint16_t maxWait = 250); //Get the current TimeMode3 settings + boolean setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Control survey in mode + boolean enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Begin Survey-In for NEO-M8P + boolean disableSurveyMode(uint16_t maxWait = 250); //Stop Survey-In mode + + boolean getSurveyStatus(uint16_t maxWait); //Reads survey in status and sets the global variables + + uint32_t getPositionAccuracy(uint16_t maxWait = 1100); //Returns the 3D accuracy of the current high-precision fix, in mm. Supported on NEO-M8P, ZED-F9P, + + uint8_t getProtocolVersionHigh(uint16_t maxWait = 500); //Returns the PROTVER XX.00 from UBX-MON-VER register + uint8_t getProtocolVersionLow(uint16_t maxWait = 500); //Returns the PROTVER 00.XX from UBX-MON-VER register + boolean getProtocolVersion(uint16_t maxWait = 500); //Queries module, loads low/high bytes + + boolean getRELPOSNED(uint16_t maxWait = 1100); //Get Relative Positioning Information of the NED frame + + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + void disableDebugging(void); //Turn off debug statements + void debugPrint(char *message); //Safely print debug statements + void debugPrintln(char *message); //Safely print debug statements + const char *statusString(sfe_ublox_status_e stat); //Pretty print the return value + + //Support for geofences + boolean addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence = 0, byte pinPolarity = 0, byte pin = 0, uint16_t maxWait = 1100); // Add a new geofence + boolean clearGeofences(uint16_t maxWait = 1100); //Clears all geofences + boolean getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = 1100); //Returns the combined geofence state + boolean clearAntPIO(uint16_t maxWait = 1100); //Clears the antenna control pin settings to release the PIOs + geofenceParams currentGeofenceParams; // Global to store the geofence parameters + + boolean powerSaveMode(bool power_save = true, uint16_t maxWait = 1100); + uint8_t getPowerSaveMode(uint16_t maxWait = 1100); // Returns 255 if the sendCommand fails + + //Change the dynamic platform model using UBX-CFG-NAV5 + boolean setDynamicModel(dynModel newDynamicModel = DYN_MODEL_PORTABLE, uint16_t maxWait = 1100); + uint8_t getDynamicModel(uint16_t maxWait = 1100); // Get the dynamic model - returns 255 if the sendCommand fails + + boolean getEsfInfo(uint16_t maxWait = 1100); + boolean getEsfIns(uint16_t maxWait = 1100); + boolean getEsfDataInfo(uint16_t maxWait = 1100); + boolean getEsfRawDataInfo(uint16_t maxWait = 1100); + sfe_ublox_status_e getSensState(uint8_t sensor, uint16_t maxWait = 1100); + boolean getVehAtt(uint16_t maxWait = 1100); + + //Survey-in specific controls + struct svinStructure + { + boolean active; + boolean valid; + uint16_t observationTime; + float meanAccuracy; + } svin; + + //Relative Positioning Info in NED frame specific controls + struct frelPosInfoStructure + { + uint16_t refStationID; + + float relPosN; + float relPosE; + float relPosD; + + long relPosLength; + long relPosHeading; + + int8_t relPosHPN; + int8_t relPosHPE; + int8_t relPosHPD; + int8_t relPosHPLength; + + float accN; + float accE; + float accD; + + bool gnssFixOk; + bool diffSoln; + bool relPosValid; + uint8_t carrSoln; + bool isMoving; + bool refPosMiss; + bool refObsMiss; + } relPosInfo; + + //The major datums we want to globally store + uint16_t gpsYear; + uint8_t gpsMonth; + uint8_t gpsDay; + uint8_t gpsHour; + uint8_t gpsMinute; + uint8_t gpsSecond; + uint16_t gpsMillisecond; + int32_t gpsNanosecond; + + int32_t latitude; //Degrees * 10^-7 (more accurate than floats) + int32_t longitude; //Degrees * 10^-7 (more accurate than floats) + int32_t altitude; //Number of mm above ellipsoid + int32_t altitudeMSL; //Number of mm above Mean Sea Level + uint8_t SIV; //Number of satellites used in position solution + uint8_t fixType; //Tells us when we have a solution aka lock + uint8_t carrierSolution; //Tells us when we have an RTK float/fixed solution + int32_t groundSpeed; //mm/s + int32_t headingOfMotion; //degrees * 10^-5 + uint16_t pDOP; //Positional dilution of precision * 10^-2 (dimensionless) + uint8_t versionLow; //Loaded from getProtocolVersion(). + uint8_t versionHigh; + + uint32_t timeOfWeek; // ms + int32_t highResLatitude; // Degrees * 10^-7 + int32_t highResLongitude; // Degrees * 10^-7 + int32_t elipsoid; // Height above ellipsoid in mm (Typo! Should be eLLipsoid! **Uncorrected for backward-compatibility.**) + int32_t meanSeaLevel; // Height above mean sea level in mm + int32_t geoidSeparation; // This seems to only be provided in NMEA GGA and GNS messages + uint32_t horizontalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + uint32_t verticalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + int8_t elipsoidHp; // High precision component of the height above ellipsoid in mm * 10^-1 (Deliberate typo! Should be eLLipsoidHp!) + int8_t meanSeaLevelHp; // High precision component of Height above mean sea level in mm * 10^-1 + int8_t highResLatitudeHp; // High precision component of latitude: Degrees * 10^-9 + int8_t highResLongitudeHp; // High precision component of longitude: Degrees * 10^-9 + + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame + +#define DEF_NUM_SENS 7 + struct deadReckData + { + uint8_t version; + uint8_t fusionMode; + + uint8_t xAngRateVald; + uint8_t yAngRateVald; + uint8_t zAngRateVald; + uint8_t xAccelVald; + uint8_t yAccelVald; + uint8_t zAccelVald; + + int32_t xAngRate; + int32_t yAngRate; + int32_t zAngRate; + + int32_t xAccel; + int32_t yAccel; + int32_t zAccel; + + // The array size is based on testing directly on M8U and F9R + uint32_t rawData; + uint32_t rawDataType; + uint32_t rawTStamp; + + uint32_t data[DEF_NUM_SENS]; + uint32_t dataType[DEF_NUM_SENS]; + uint32_t dataTStamp[DEF_NUM_SENS]; + } imuMeas; + + struct indivImuData + { + + uint8_t numSens; + + uint8_t senType; + boolean isUsed; + boolean isReady; + uint8_t calibStatus; + uint8_t timeStatus; + + uint8_t freq; // Hz + + boolean badMeas; + boolean badTag; + boolean missMeas; + boolean noisyMeas; + } ubloxSen; + + struct vehicleAttitude + { + // All values in degrees + int32_t roll; + int32_t pitch; + int32_t heading; + uint32_t accRoll; + uint32_t accPitch; + uint32_t accHeading; + } vehAtt; + +private: + //Depending on the sentence type the processor will load characters into different arrays + enum SentenceTypes + { + NONE = 0, + NMEA, + UBX, + RTCM + } currentSentence = NONE; + + //Depending on the ubx binary response class, store binary responses into different places + enum classTypes + { + CLASS_NONE = 0, + CLASS_ACK, + CLASS_NOT_AN_ACK + } ubxFrameClass = CLASS_NONE; + + enum commTypes + { + COMM_TYPE_I2C = 0, + COMM_TYPE_SERIAL, + COMM_TYPE_SPI + } commType = COMM_TYPE_I2C; //Controls which port we look to for incoming bytes + + //Functions + boolean checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType + uint32_t extractLong(uint8_t spotToStart); //Combine four bytes from payload into long + uint16_t extractInt(uint8_t spotToStart); //Combine two bytes from payload into int + uint8_t extractByte(uint8_t spotToStart); //Get byte from payload + int8_t extractSignedChar(uint8_t spotToStart); //Get signed 8-bit value from payload + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + + //Variables + TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + Stream *_serialPort; //The generic connection to user's chosen Serial hardware + Stream *_nmeaOutputPort = NULL; //The user can assign an output port to print NMEA sentences if they wish + Stream *_debugSerial; //The stream to send debug messages to if enabled + + uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series + //This can be changed using the ublox configuration software + + boolean _printDebug = false; //Flag to print the serial commands we are sending to the Serial port for debug + boolean _printLimitedDebug = false; //Flag to print limited debug messages. Useful for I2C debugging or high navigation rates + + //The packet buffers + //These are pointed at from within the ubxPacket + uint8_t payloadAck[2]; // Holds the requested ACK/NACK + uint8_t payloadCfg[MAX_PAYLOAD_SIZE]; // Holds the requested data packet + uint8_t payloadBuf[2]; // Temporary buffer used to screen incoming packets or dump unrequested packets + + //Init the packet structures and init them with pointers to the payloadAck, payloadCfg and payloadBuf arrays + ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetCfg = {0, 0, 0, 0, 0, payloadCfg, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + //Flag if this packet is unrequested (and so should be ignored and not copied into packetCfg or packetAck) + boolean ignoreThisPayload = false; + + //Identify which buffer is in use + //Data is stored in packetBuf until the requested class and ID can be validated + //If a match is seen, data is diverted into packetAck or packetCfg + sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + + //Limit checking of new data to every X ms + //If we are expecting an update every X Hz then we should check every half that amount of time + //Otherwise we may block ourselves from seeing new data + uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() + + unsigned long lastCheck = 0; + boolean autoPVT = false; //Whether autoPVT is enabled or not + boolean autoPVTImplicitUpdate = true; // Whether autoPVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] + + uint8_t rollingChecksumA; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + + //Create bit field for staleness of each datum in PVT we want to monitor + //moduleQueried.latitude goes true each time we call getPVT() + //This reduces the number of times we have to call getPVT as this can take up to ~1s per read + //depending on update rate + struct + { + uint32_t gpsiTOW : 1; + uint32_t gpsYear : 1; + uint32_t gpsMonth : 1; + uint32_t gpsDay : 1; + uint32_t gpsHour : 1; + uint32_t gpsMinute : 1; + uint32_t gpsSecond : 1; + uint32_t gpsNanosecond : 1; + + uint32_t all : 1; + uint32_t longitude : 1; + uint32_t latitude : 1; + uint32_t altitude : 1; + uint32_t altitudeMSL : 1; + uint32_t SIV : 1; + uint32_t fixType : 1; + uint32_t carrierSolution : 1; + uint32_t groundSpeed : 1; + uint32_t headingOfMotion : 1; + uint32_t pDOP : 1; + uint32_t versionNumber : 1; + } moduleQueried; + + struct + { + uint16_t all : 1; + uint16_t timeOfWeek : 1; + uint16_t highResLatitude : 1; + uint16_t highResLongitude : 1; + uint16_t elipsoid : 1; + uint16_t meanSeaLevel : 1; + uint16_t geoidSeparation : 1; // Redundant but kept for backward-compatibility + uint16_t horizontalAccuracy : 1; + uint16_t verticalAccuracy : 1; + uint16_t elipsoidHp : 1; + uint16_t meanSeaLevelHp : 1; + uint16_t highResLatitudeHp : 1; + uint16_t highResLongitudeHp : 1; + } highResModuleQueried; + + uint16_t rtcmLen = 0; +}; + +#endif diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino new file mode 100644 index 0000000..05f5fb6 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino @@ -0,0 +1,154 @@ +/* + Get the high precision geodetic solution for latitude and longitude + By: Nathan Seidle + Modified by: Steven Rowland and Paul Clark + SparkFun Electronics + Date: April 17th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to inspect the accuracy of the high-precision + positional solution. Please see below for information about the units. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + + Wire.begin(); + + //myGPS.enableDebugging(Serial); + + if (myGPS.begin(Wire) == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(20); //Set output to 20 times a second + + byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate: "); + Serial.println(rate); + + //myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + // getHighResLatitude: returns the latitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLatitudeHp: returns the high resolution component of latitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getHighResLongitude: returns the longitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLongitudeHp: returns the high resolution component of longitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getElipsoid: returns the height above ellipsoid as an int32_t in mm + // getElipsoidHp: returns the high resolution component of the height above ellipsoid as an int8_t in mm * 10^-1 + // getMeanSeaLevel: returns the height above mean sea level as an int32_t in mm + // getMeanSeaLevelHp: returns the high resolution component of the height above mean sea level as an int8_t in mm * 10^-1 + // getHorizontalAccuracy: returns the horizontal accuracy estimate from HPPOSLLH as an uint32_t in mm * 10^-1 + + // If you want to use the high precision latitude and longitude with the full 9 decimal places + // you will need to use a 64-bit double - which is not supported on all platforms + + // To allow this example to run on standard platforms, we cheat by converting lat and lon to integer and fractional degrees + + // The high resolution altitudes can be converted into standard 32-bit float + + // First, let's collect the position data + int32_t latitude = myGPS.getHighResLatitude(); + int8_t latitudeHp = myGPS.getHighResLatitudeHp(); + int32_t longitude = myGPS.getHighResLongitude(); + int8_t longitudeHp = myGPS.getHighResLongitudeHp(); + int32_t ellipsoid = myGPS.getElipsoid(); + int8_t ellipsoidHp = myGPS.getElipsoidHp(); + int32_t msl = myGPS.getMeanSeaLevel(); + int8_t mslHp = myGPS.getMeanSeaLevelHp(); + uint32_t accuracy = myGPS.getHorizontalAccuracy(); + + // Defines storage for the lat and lon units integer and fractional parts + int32_t lat_int; // Integer part of the latitude in degrees + int32_t lat_frac; // Fractional part of the latitude + int32_t lon_int; // Integer part of the longitude in degrees + int32_t lon_frac; // Fractional part of the longitude + + // Calculate the latitude and longitude integer and fractional parts + lat_int = latitude / 10000000; // Convert latitude from degrees * 10^-7 to Degrees + lat_frac = latitude - (lat_int * 10000000); // Calculate the fractional part of the latitude + lat_frac = (lat_frac * 100) + latitudeHp; // Now add the high resolution component + if (lat_frac < 0) // If the fractional part is negative, remove the minus sign + { + lat_frac = 0 - lat_frac; + } + lon_int = longitude / 10000000; // Convert latitude from degrees * 10^-7 to Degrees + lon_frac = longitude - (lon_int * 10000000); // Calculate the fractional part of the longitude + lon_frac = (lon_frac * 100) + longitudeHp; // Now add the high resolution component + if (lon_frac < 0) // If the fractional part is negative, remove the minus sign + { + lon_frac = 0 - lon_frac; + } + + // Print the lat and lon + Serial.print("Lat (deg): "); + Serial.print(lat_int); // Print the integer part of the latitude + Serial.print("."); + Serial.print(lat_frac); // Print the fractional part of the latitude + Serial.print(", Lon (deg): "); + Serial.print(lon_int); // Print the integer part of the latitude + Serial.print("."); + Serial.println(lon_frac); // Print the fractional part of the latitude + + // Now define float storage for the heights and accuracy + float f_ellipsoid; + float f_msl; + float f_accuracy; + + // Calculate the height above ellipsoid in mm * 10^-1 + f_ellipsoid = (ellipsoid * 10) + ellipsoidHp; + // Now convert to m + f_ellipsoid = f_ellipsoid / 10000.0; // Convert from mm * 10^-1 to m + + // Calculate the height above mean sea level in mm * 10^-1 + f_msl = (msl * 10) + mslHp; + // Now convert to m + f_msl = f_msl / 10000.0; // Convert from mm * 10^-1 to m + + // Convert the horizontal accuracy (mm * 10^-1) to a float + f_accuracy = accuracy; + // Now convert to m + f_accuracy = f_accuracy / 10000.0; // Convert from mm * 10^-1 to m + + // Finally, do the printing + Serial.print("Ellipsoid (m): "); + Serial.print(f_ellipsoid, 4); // Print the ellipsoid with 4 decimal places + + Serial.print(", Mean Sea Level(m): "); + Serial.print(f_msl, 4); // Print the mean sea level with 4 decimal places + + Serial.print(", Accuracy (m): "); + Serial.println(f_accuracy, 4); // Print the accuracy with 4 decimal places + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino new file mode 100644 index 0000000..7da62f1 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino @@ -0,0 +1,146 @@ +/* + Get the high precision geodetic solution for latitude and longitude using double + By: Nathan Seidle + Modified by: Paul Clark (PaulZC) + SparkFun Electronics + Date: April 17th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to inspect the accuracy of the high-precision + positional solution. Please see below for information about the units. + + ** This example will only work correctly on platforms which support 64-bit double ** + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + + Hardware Connections: + Plug a Qwiic cable into the GPS and (e.g.) a Redboard Artemis https://www.sparkfun.com/products/15444 + or an Artemis Thing Plus https://www.sparkfun.com/products/15574 + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include // Needed for I2C to GPS + +#define myWire Wire // This will work on the Redboard Artemis and the Artemis Thing Plus using Qwiic +//#define myWire Wire1 // Uncomment this line if you are using the extra SCL1/SDA1 pins (D17 and D16) on the Thing Plus + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + + myWire.begin(); + + //myGPS.enableDebugging(Serial); // Uncomment this line to enable debug messages + + if (myGPS.begin(myWire) == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + // Check that this platform supports 64-bit (8 byte) double + if (sizeof(double) < 8) + { + Serial.println(F("Warning! Your platform does not support 64-bit double.")); + Serial.println(F("The latitude and longitude will be inaccurate.")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + //myGPS.setNavigationFrequency(20); //Set output to 20 times a second + + byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate: "); + Serial.println(rate); + + //myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. + //The module only responds when a new position is available. + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + // getHighResLatitude: returns the latitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLatitudeHp: returns the high resolution component of latitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getHighResLongitude: returns the longitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLongitudeHp: returns the high resolution component of longitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getElipsoid: returns the height above ellipsoid as an int32_t in mm + // getElipsoidHp: returns the high resolution component of the height above ellipsoid as an int8_t in mm * 10^-1 + // getMeanSeaLevel: returns the height above mean sea level as an int32_t in mm + // getMeanSeaLevelHp: returns the high resolution component of the height above mean sea level as an int8_t in mm * 10^-1 + // getHorizontalAccuracy: returns the horizontal accuracy estimate from HPPOSLLH as an uint32_t in mm * 10^-1 + + // First, let's collect the position data + int32_t latitude = myGPS.getHighResLatitude(); + int8_t latitudeHp = myGPS.getHighResLatitudeHp(); + int32_t longitude = myGPS.getHighResLongitude(); + int8_t longitudeHp = myGPS.getHighResLongitudeHp(); + int32_t ellipsoid = myGPS.getElipsoid(); + int8_t ellipsoidHp = myGPS.getElipsoidHp(); + int32_t msl = myGPS.getMeanSeaLevel(); + int8_t mslHp = myGPS.getMeanSeaLevelHp(); + uint32_t accuracy = myGPS.getHorizontalAccuracy(); + + // Defines storage for the lat and lon as double + double d_lat; // latitude + double d_lon; // longitude + + // Assemble the high precision latitude and longitude + d_lat = ((double)latitude) / 10000000.0; // Convert latitude from degrees * 10^-7 to degrees + d_lat += ((double)latitudeHp) / 1000000000.0; // Now add the high resolution component (degrees * 10^-9 ) + d_lon = ((double)longitude) / 10000000.0; // Convert longitude from degrees * 10^-7 to degrees + d_lon += ((double)longitudeHp) / 1000000000.0; // Now add the high resolution component (degrees * 10^-9 ) + + // Print the lat and lon + Serial.print("Lat (deg): "); + Serial.print(d_lat, 9); + Serial.print(", Lon (deg): "); + Serial.print(d_lon, 9); + + // Now define float storage for the heights and accuracy + float f_ellipsoid; + float f_msl; + float f_accuracy; + + // Calculate the height above ellipsoid in mm * 10^-1 + f_ellipsoid = (ellipsoid * 10) + ellipsoidHp; + // Now convert to m + f_ellipsoid = f_ellipsoid / 10000.0; // Convert from mm * 10^-1 to m + + // Calculate the height above mean sea level in mm * 10^-1 + f_msl = (msl * 10) + mslHp; + // Now convert to m + f_msl = f_msl / 10000.0; // Convert from mm * 10^-1 to m + + // Convert the horizontal accuracy (mm * 10^-1) to a float + f_accuracy = accuracy; + // Now convert to m + f_accuracy = f_accuracy / 10000.0; // Convert from mm * 10^-1 to m + + // Finally, do the printing + Serial.print(", Ellipsoid (m): "); + Serial.print(f_ellipsoid, 4); // Print the ellipsoid with 4 decimal places + + Serial.print(", Mean Sea Level (m): "); + Serial.print(f_msl, 4); // Print the mean sea level with 4 decimal places + + Serial.print(", Accuracy (m): "); + Serial.println(f_accuracy, 4); // Print the accuracy with 4 decimal places + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino new file mode 100644 index 0000000..1f47f95 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino @@ -0,0 +1,73 @@ +/* + Set the static position of the receiver. + By: SparkFun Electronics / Nathan Seidle + Date: September 26th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to set the static position of a receiver + using an Earth-Centered, Earth-Fixed (ECEF) location. This is the + output from a long (24 hour+) survey-in. Setting the static position + immediately causes the receiver to begin outputting RTCM data (if + enabled), perfect for setting up your own RTCM NTRIP caster or CORS. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... + + //Units are cm so 1234 = 12.34m + //myGPS.setStaticPosition(-128020831, -471680385, 408666581); + + //Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) + myGPS.setStaticPosition(-128020830, -80, -471680384, -70, 408666581, 10); //With high precision 0.1mm parts + + //We can also set via lat/long + //40.09029751,-105.18507900,1560.238 + //myGPS.setStaticPosition(400902975, -1051850790, 156024, true); //True at end enables lat/long input + //myGPS.setStaticPosition(400902975, 10, -1051850790, 0, 156023, 80, true); + + //Now let's use getVals to read back the data + //long ecefX = myGPS.getVal32(0x40030003); + //Serial.print("ecefX: "); + //Serial.println(ecefX); + + Serial.println(F("Done!")); +} + +void loop() +{ +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino new file mode 100644 index 0000000..016be5d --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino @@ -0,0 +1,119 @@ +/* + Configuring the GPS to automatically send HPPOSLLH position reports over I2C + By: Paul Clark + Date: October 27th 2020 + + Based on an earlier example: + By: Nathan Seidle and Thorsten von Eicken + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GPS the send navigation reports automatically + and retrieving the latest one via getHPPOSLLH. This eliminates the blocking in getHPPOSLLH while the GPS + produces a fresh navigation solution at the expense of returning a slighly old solution. + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GPS + simply outputs the UBX_NAV_HPPOSLLH packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable lots of helpful debug messages + //myGPS.enableDebugging(Serial, true); // Uncomment this line to enable the minimum of helpful debug messages + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one solution per second + + + // The acid test: all four of these combinations should work seamlessly :-) + + //myGPS.setAutoPVT(false); // Library will poll each reading + //myGPS.setAutoHPPOSLLH(false); // Library will poll each reading + + //myGPS.setAutoPVT(true); // Tell the GPS to "send" each solution automatically + //myGPS.setAutoHPPOSLLH(false); // Library will poll each reading + + //myGPS.setAutoPVT(false); // Library will poll each reading + //myGPS.setAutoHPPOSLLH(true); // Tell the GPS to "send" each hi res solution automatically + + myGPS.setAutoPVT(true); // Tell the GPS to "send" each solution automatically + myGPS.setAutoHPPOSLLH(true); // Tell the GPS to "send" each hi res solution automatically +} + +void loop() +{ + // Calling getHPPOSLLH returns true if there actually is a fresh navigation solution available. + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if ((myGPS.getHPPOSLLH()) || (myGPS.getPVT())) + { + Serial.println(); + + long highResLatitude = myGPS.getHighResLatitude(); + Serial.print(F("Hi Res Lat: ")); + Serial.print(highResLatitude); + + int highResLatitudeHp = myGPS.getHighResLatitudeHp(); + Serial.print(F(" ")); + Serial.print(highResLatitudeHp); + + long highResLongitude = myGPS.getHighResLongitude(); + Serial.print(F(" Hi Res Long: ")); + Serial.print(highResLongitude); + + int highResLongitudeHp = myGPS.getHighResLongitudeHp(); + Serial.print(F(" ")); + Serial.print(highResLongitudeHp); + + unsigned long horizAccuracy = myGPS.getHorizontalAccuracy(); + Serial.print(F(" Horiz accuracy: ")); + Serial.print(horizAccuracy); + + long latitude = myGPS.getLatitude(); + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.println(longitude); + } + else + { + Serial.print("."); + delay(50); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino new file mode 100644 index 0000000..b6f0be0 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino @@ -0,0 +1,274 @@ +/* + Use ESP32 WiFi to push RTCM data to RTK2Go (caster) as a Server + By: SparkFun Electronics / Nathan Seidle + Date: December 14th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to gather RTCM data over I2C and push it to a casting service over WiFi. + It's confusing, but the Arduino is acting as a 'server' to a 'caster'. In this case we will + use RTK2Go.com as our caster because it is free. A rover (car, surveyor stick, etc) can + then connect to RTK2Go as a 'client' and get the RTCM data it needs. + + You will need to register your mountpoint here: http://www.rtk2go.com/new-reservation/ + (They'll probably block the credentials we include in this example) + + To see if your mountpoint is active go here: http://rtk2go.com:2101/ + + This is a proof of concept. Serving RTCM to a caster over WiFi is useful when you need to + set up a high-precision base station. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/16481 + RTK Surveyor: https://www.sparkfun.com/products/17369 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a ESP32 Thing Plus + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include +#include "secrets.h" +WiFiClient client; + +#include //Needed for I2C to GPS +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +//Basic Connection settings to RTK2Go NTRIP Caster - See secrets for mount specific credentials +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +const uint16_t casterPort = 2101; +const char * casterHost = "rtk2go.com"; +const char * ntrip_server_name = "SparkFun_RTK_Surveyor"; + +long lastSentRTCM_ms = 0; //Time of last data pushed to socket +int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster + +uint32_t serverBytesSent = 0; //Just a running total +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +long lastReport_ms = 0; //Time of last report of bytes sent + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + Serial.print("Connecting to local WiFi"); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.print("\nWiFi connected with IP: "); + Serial.println(WiFi.localIP()); + + myGPS.setI2COutput(COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3); //UBX+RTCM3 is not a valid option so we enable all three. + + myGPS.setNavigationFrequency(1); //Set output in Hz. RTCM rarely benefits from >1Hz. + + //Disable all NMEA sentences + bool response = true; + response &= myGPS.disableNMEAMessage(UBX_NMEA_GGA, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GSA, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GSV, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_RMC, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GST, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GLL, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_VTG, COM_PORT_I2C); + + if (response == false) + { + Serial.println(F("Failed to disable NMEA. Freezing...")); + while (1); + } + else + Serial.println(F("NMEA disabled")); + + //Enable necessary RTCM sentences + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through UART2, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1074, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1084, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1094, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1124, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == false) + { + Serial.println(F("Failed to enable RTCM. Freezing...")); + while (1); + } + else + Serial.println(F("RTCM sentences enabled")); + + //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... + //Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) + //For more infomation see Example12_setStaticPosition + //Note: If you leave these coordinates in place and setup your antenna *not* at SparkFun, your receiver + //will be very confused and fail to generate correction data because, well, you aren't at SparkFun... + //See this tutorial on getting PPP coordinates: https://learn.sparkfun.com/tutorials/how-to-build-a-diy-gnss-reference-station/all + response &= myGPS.setStaticPosition(-128020830, -80, -471680384, -70, 408666581, 10); //With high precision 0.1mm parts + if (response == false) + { + Serial.println(F("Failed to enter static position. Freezing...")); + while (1); + } + else + Serial.println(F("Static position set")); + + //You could instead do a survey-in but it takes much longer to start generating RTCM data. See Example4_BaseWithLCD + //myGPS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m + + if (myGPS.saveConfiguration() == false) //Save the current settings to flash and BBR + Serial.println(F("Module failed to save.")); + + Serial.println(F("Module configuration complete")); +} + +void loop() +{ + if (Serial.available()) beginServing(); + + Serial.println(F("Press any key to start serving.")); + + delay(1000); +} + +void beginServing() +{ + Serial.println("Xmit to RTK2Go. Press any key to stop"); + delay(10); //Wait for any serial to arrive + while (Serial.available()) Serial.read(); //Flush + + while (Serial.available() == 0) + { + //Connect if we are not already + if (client.connected() == false) + { + Serial.printf("Opening socket to %s\n", casterHost); + + if (client.connect(casterHost, casterPort) == true) //Attempt connection + { + Serial.printf("Connected to %s:%d\n", casterHost, casterPort); + + const int SERVER_BUFFER_SIZE = 512; + char serverBuffer[SERVER_BUFFER_SIZE]; + + snprintf(serverBuffer, SERVER_BUFFER_SIZE, "SOURCE %s /%s\r\nSource-Agent: NTRIP %s/%s\r\n\r\n", + mntpnt_pw, mntpnt, ntrip_server_name, "App Version 1.0"); + + Serial.printf("Sending credentials:\n%s\n", serverBuffer); + client.write(serverBuffer, strlen(serverBuffer)); + + //Wait for response + unsigned long timeout = millis(); + while (client.available() == 0) + { + if (millis() - timeout > 5000) + { + Serial.println(">>> Client Timeout !"); + client.stop(); + return; + } + delay(10); + } + + //Check reply + bool connectionSuccess = false; + char response[512]; + int responseSpot = 0; + while (client.available()) + { + response[responseSpot++] = client.read(); + if (strstr(response, "200") > 0) //Look for 'ICY 200 OK' + connectionSuccess = true; + if (responseSpot == 512 - 1) break; + } + response[responseSpot] = '\0'; + + if (connectionSuccess == false) + { + Serial.printf("Failed to connect to RTK2Go: %s", response); + } + } //End attempt to connect + else + { + Serial.println("Connection to host failed"); + } + } //End connected == false + + if (client.connected() == true) + { + delay(10); + while (Serial.available()) Serial.read(); //Flush any endlines or carriage returns + + lastReport_ms = millis(); + lastSentRTCM_ms = millis(); + + //This is the main sending loop. We scan for new ublox data but processRTCM() is where the data actually gets sent out. + while (1) + { + if (Serial.available()) break; + + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + //Close socket if we don't have new data for 10s + //RTK2Go will ban your IP address if you abuse it. See http://www.rtk2go.com/how-to-get-your-ip-banned/ + //So let's not leave the socket open/hanging without data + if (millis() - lastSentRTCM_ms > maxTimeBeforeHangup_ms) + { + Serial.println("RTCM timeout. Disconnecting..."); + client.stop(); + return; + } + + delay(10); + + //Report some statistics every 250 + if (millis() - lastReport_ms > 250) + { + lastReport_ms += 250; + Serial.printf("Total sent: %d\n", serverBytesSent); + } + } + } + + delay(10); + } + + Serial.println("User pressed a key"); + Serial.println("Disconnecting..."); + client.stop(); + + delay(10); + while (Serial.available()) Serial.read(); //Flush any endlines or carriage returns +} + +//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_GPS::processRTCM(uint8_t incoming) +{ + if (client.connected() == true) + { + client.write(incoming); //Send this byte to socket + serverBytesSent++; + lastSentRTCM_ms = millis(); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/secrets.h b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/secrets.h new file mode 100644 index 0000000..5dcd50e --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example14_NTRIPServer/secrets.h @@ -0,0 +1,7 @@ +//Your WiFi credentials +const char* ssid = "TRex"; +const char* password = "hasBigTeeth"; + +//Your RTK2GO mount point credentials +const char* mntpnt_pw = "WR5wRo4H"; +const char* mntpnt = "bldr_dwntwn2"; diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino new file mode 100644 index 0000000..04a88ee --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino @@ -0,0 +1,77 @@ +/* + Get the high position accuracy of the RTK enhanced position + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to inspect the accuracy of the high-precision + positional solution. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + long accuracy = myGPS.getPositionAccuracy(); + Serial.print(F(" 3D Positional Accuracy: ")); + Serial.print(accuracy); + Serial.println(F("mm")); + } + +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino new file mode 100644 index 0000000..3cae992 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino @@ -0,0 +1,60 @@ +/* + Configuring Ublox Module using new VALGET / VALSET / VALDEL methods + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Ublox depricated many -CFG messages and replaced them with new + VALGET, VALSET, VALDEL methods. This shows the basics of how to use + these methods. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun Ublox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + byte response; + response = myGPS.getVal8(VAL_GROUP_I2C, VAL_ID_I2C_ADDRESS, VAL_GROUP_I2C_SIZE, VAL_LAYER_RAM); + Serial.print(F("I2C Address: 0x")); + Serial.println(response >> 1, HEX); //We have to shift by 1 to get the common '7-bit' I2C address format + + response = myGPS.getVal8(VAL_GROUP_I2COUTPROT, VAL_ID_I2COUTPROT_NMEA, VAL_GROUP_I2COUTPROT_SIZE, VAL_LAYER_RAM); + Serial.print(F("Output NMEA over I2C port: 0x")); + Serial.print(response, HEX); +} + +void loop() +{ +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino new file mode 100644 index 0000000..1e279b5 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino @@ -0,0 +1,176 @@ +/* + Send UBX binary commands to enable RTCM sentences on Ublox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a ZED-F9P as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable six RTCM messages + Begin outputting RTCM bytes + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +//#define USE_SERIAL1 // Uncomment this line to push the RTCM data to Serial1 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("Ublox Base station example"); + +#ifdef USE_SERIAL1 + // If our board supports it, we can output the RTCM data on Serial1 + Serial1.begin(115200); +#endif + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR + + while (Serial.available()) Serial.read(); //Clear any latent chars in serial buffer + Serial.println("Press any key to send commands to begin Survey-In"); + while (Serial.available() == 0) ; //Wait for user to press a key + + boolean response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1074, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1084, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1094, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1124, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + //Use COM_PORT_UART1 for the above six messages to direct RTCM messages out UART1 + //COM_PORT_UART2, COM_PORT_USB, COM_PORT_SPI are also available + //For example: response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_UART1, 10); + + if (response == true) + { + Serial.println("RTCM messages enabled"); + } + else + { + Serial.println("RTCM failed to enable. Are you sure you have an ZED-F9P?"); + while (1); //Freeze + } + + //Check if Survey is in Progress before initiating one + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + if (response == false) + { + Serial.println("Failed to get Survey In status"); + while (1); //Freeze + } + + if (myGPS.svin.active == true) + { + Serial.print("Survey already in progress."); + } + else + { + //Start survey + //The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info. + //response = myGPS.enableSurveyMode(300, 2.000); //Enable Survey in on NEO-M8P, 300 seconds, 2.0m + response = myGPS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m + if (response == false) + { + Serial.println("Survey start failed"); + while (1); + } + Serial.println("Survey started. This will run until 60s has passed and less than 5m accuracy is achieved."); + } + + while(Serial.available()) Serial.read(); //Clear buffer + + //Begin waiting for survey to complete + while (myGPS.svin.valid == false) + { + if(Serial.available()) + { + byte incoming = Serial.read(); + if(incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println("Survey stopped"); + break; + } + } + + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) + { + Serial.print("Press x to end survey - "); + Serial.print("Time elapsed: "); + Serial.print((String)myGPS.svin.observationTime); + + Serial.print(" Accuracy: "); + Serial.print((String)myGPS.svin.meanAccuracy); + Serial.println(); + } + else + { + Serial.println("SVIN request failed"); + } + + delay(1000); + } + Serial.println("Survey valid!"); + + Serial.println("Base survey complete! RTCM now broadcasting."); + + myGPS.setI2COutput(COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the I2C port to output UBX and RTCM sentences (not really an option, turns on NMEA as well) +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun Ublox 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_GPS::processRTCM(uint8_t incoming) +{ +#ifdef USE_SERIAL1 + //Push the RTCM data to Serial1 + Serial1.write(incoming); +#endif + + //Pretty-print the HEX values to Serial + if (myGPS.rtcmFrameCounter % 16 == 0) Serial.println(); + Serial.print(" "); + if (incoming < 0x10) Serial.print("0"); + Serial.print(incoming, HEX); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino new file mode 100644 index 0000000..67d7f36 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino @@ -0,0 +1,200 @@ +/* + Send UBX binary commands to enable RTCM sentences on Ublox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a ZED-F9P as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable six RTCM messages + Begin outputting RTCM bytes + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + Plug a SerLCD onto the Qwiic bus + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Watch the output on the LCD or open the serial monitor at 115200 baud to see the output +*/ + +#define STAT_LED 13 + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include //http://librarymanager/All#SparkFun_SerLCD +SerLCD lcd; // Initialize the library with default I2C address 0x72 + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("Ublox GPS I2C Test"); + + Wire.begin(); + + pinMode(STAT_LED, OUTPUT); + digitalWrite(STAT_LED, LOW); + + lcd.begin(Wire); //Set up the LCD for Serial communication at 9600bps + lcd.setBacklight(0x4B0082); //indigo, a kind of dark purplish blue + lcd.clear(); + lcd.print(F("LCD Ready")); + + myGPS.begin(Wire); + if (myGPS.isConnected() == false) + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + lcd.setCursor(0, 1); + lcd.print(F("No GPS detected")); + while (1) + ; + } + + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + lcd.setCursor(0, 1); + lcd.print("GPS Detected"); + + //myGPS.setI2COutput(COM_TYPE_RTCM3); //Set the I2C port to output RTCM3 sentences (turn off NMEA noise) + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX sentences (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + boolean response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1074, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1084, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1094, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1124, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an ZED-F9P? Freezing.")); + while (1) + ; //Freeze + } + + //Check if Survey is in Progress before initiating one + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + if (response == false) + { + Serial.println(F("Failed to get Survey In status. Freezing.")); + while (1) + ; //Freeze + } + + if (myGPS.svin.active == true) + { + Serial.print(F("Survey already in progress.")); + lcd.setCursor(0, 2); + lcd.print(F("Survey already going")); + } + else + { + //Start survey + response = myGPS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m + if (response == false) + { + Serial.println(F("Survey start failed")); + lcd.setCursor(0, 3); + lcd.print(F("Survey start failed. Freezing.")); + while (1) + ; + } + Serial.println(F("Survey started. This will run until 60s has passed and less than 5m accuracy is achieved.")); + } + + while (Serial.available()) + Serial.read(); //Clear buffer + + lcd.clear(); + lcd.print(F("Survey in progress")); + + //Begin waiting for survey to complete + while (myGPS.svin.valid == false) + { + if (Serial.available()) + { + byte incoming = Serial.read(); + if (incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println(F("Survey stopped")); + break; + } + } + + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) + { + Serial.print(F("Press x to end survey - ")); + Serial.print(F("Time elapsed: ")); + Serial.print((String)myGPS.svin.observationTime); + + lcd.setCursor(0, 1); + lcd.print(F("Elapsed: ")); + lcd.print((String)myGPS.svin.observationTime); + + Serial.print(F(" Accuracy: ")); + Serial.print((String)myGPS.svin.meanAccuracy); + Serial.println(); + + lcd.setCursor(0, 2); + lcd.print(F("Accuracy: ")); + lcd.print((String)myGPS.svin.meanAccuracy); + } + else + { + Serial.println(F("SVIN request failed")); + } + + delay(1000); + } + Serial.println(F("Survey valid!")); + + Serial.println(F("Base survey complete! RTCM now broadcasting.")); + lcd.clear(); + lcd.print(F("Transmitting RTCM")); + + myGPS.setI2COutput(COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the I2C port to output UBX and RTCM sentences (not really an option, turns on NMEA as well) + +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + //Do anything you want. Call checkUblox() every second. ZED-F9P has TX buffer of 4k bytes. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun Ublox 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_GPS::processRTCM(uint8_t incoming) +{ + //Let's just pretty-print the HEX values for now + if (myGPS.rtcmFrameCounter % 16 == 0) + Serial.println(); + Serial.print(" "); + if (incoming < 0x10) + Serial.print("0"); + Serial.print(incoming, HEX); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino new file mode 100644 index 0000000..391f874 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino @@ -0,0 +1,161 @@ +/* + Send UBX binary commands to enable RTCM sentences on Ublox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query the module for RELPOS information in the NED frame. + It assumes you already have RTCM correction data being fed to the receiver. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a RedBoard Qwiic or BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +//#define USE_SERIAL1 // Uncomment this line to push the RTCM data from Serial1 to the module via I2C + +size_t numBytes = 0; // Record the number os bytes received from Serial1 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("Ublox Base station example"); + +#ifdef USE_SERIAL1 + // If our board supports it, we can receive the RTCM data on Serial1 + Serial1.begin(115200); +#endif + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + +#ifdef USE_SERIAL1 + Serial.print(F("Enabling UBX and RTCM input on I2C. Result: ")); + Serial.print(myGPS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_RTCM3)); //Enable UBX and RTCM input on I2C + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR +#endif +} + +void loop() +{ + if (myGPS.getRELPOSNED() == true) + { + Serial.print("relPosN: "); + Serial.println(myGPS.relPosInfo.relPosN, 4); + Serial.print("relPosE: "); + Serial.println(myGPS.relPosInfo.relPosE, 4); + Serial.print("relPosD: "); + Serial.println(myGPS.relPosInfo.relPosD, 4); + + Serial.print("relPosLength: "); + Serial.println(myGPS.relPosInfo.relPosLength); + Serial.print("relPosHeading: "); + Serial.println(myGPS.relPosInfo.relPosHeading); + + Serial.print("relPosHPN: "); + Serial.println(myGPS.relPosInfo.relPosHPN); + Serial.print("relPosHPE: "); + Serial.println(myGPS.relPosInfo.relPosHPE); + Serial.print("relPosHPD: "); + Serial.println(myGPS.relPosInfo.relPosHPD); + Serial.print("relPosHPLength: "); + Serial.println(myGPS.relPosInfo.relPosHPLength); + + Serial.print("accN: "); + Serial.println(myGPS.relPosInfo.accN, 4); + Serial.print("accE: "); + Serial.println(myGPS.relPosInfo.accE, 4); + Serial.print("accD: "); + Serial.println(myGPS.relPosInfo.accD, 4); + + Serial.print("gnssFixOk: "); + if (myGPS.relPosInfo.gnssFixOk == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("diffSolution: "); + if (myGPS.relPosInfo.diffSoln == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("relPosValid: "); + if (myGPS.relPosInfo.relPosValid == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("carrier Solution Type: "); + if (myGPS.relPosInfo.carrSoln == 0) + Serial.println("None"); + else if (myGPS.relPosInfo.carrSoln == 1) + Serial.println("Float"); + else if (myGPS.relPosInfo.carrSoln == 2) + Serial.println("Fixed"); + + Serial.print("isMoving: "); + if (myGPS.relPosInfo.isMoving == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("refPosMiss: "); + if (myGPS.relPosInfo.refPosMiss == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("refObsMiss: "); + if (myGPS.relPosInfo.refObsMiss == true) + Serial.println("x"); + else + Serial.println(""); + } + else + Serial.println("RELPOS request failed"); + + for (int i = 0; i < 500; i++) + { +#ifdef USE_SERIAL1 + uint8_t store[256]; + while ((Serial1.available()) && (numBytes < 256)) // Check if data has been received + { + store[numBytes++] = Serial1.read(); // Read a byte from Serial1 and store it + } + if (numBytes > 0) // Check if data was received + { + //Serial.print("Pushing "); + //Serial.print(numBytes); + //Serial.println(" bytes via I2C"); + myGPS.pushRawData(((uint8_t *)&store), numBytes); // Push the RTCM data via I2C + numBytes = 0; // Reset numBytes + } +#endif + delay(10); + } +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino new file mode 100644 index 0000000..7e07e7a --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino @@ -0,0 +1,88 @@ +/* + Get a device's I2C address using advanced getVal method + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox changed how to configure their modules in 2019. As of version 23 of the UBX protocol the + UBX-CFG commands are deprecated; they still work, they just recommend using VALSET, VALGET, and VALDEL + commands instead. This example shows how to use this new command structure. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a RedBoard Qwiic or BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("u-blox getVal example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + uint8_t currentI2Caddress = myGPS.getVal8(UBLOX_CFG_I2C_ADDRESS); + Serial.print("Current I2C address (should be 0x42): 0x"); + Serial.println(currentI2Caddress >> 1, HEX); //Ublox module returns a shifted 8-bit address. Make it 7-bit unshifted. + + while (1) + ; +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } +} \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino new file mode 100644 index 0000000..cfec549 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino @@ -0,0 +1,76 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox changed how to configure their modules in 2019. As of version 23 of the UBX protocol the + UBX-CFG commands are deprecated; they still work, they just recommend using VALSET, VALGET, and VALDEL + commands instead. This example shows how to use this new command structure. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a RedBoard Qwiic or BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("u-blox getVal example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + bool setValueSuccess; + + //These key values are hard coded and defined in u-blox_config_keys.h. + //You can obtain them from the ZED-F9P interface description doc + //or from u-center's Messages->CFG->VALSET window. Keys must be 32-bit. + //setValueSuccess = myGPS.setVal(UBLOX_CFG_NMEA_HIGHPREC, 0); //Enable high precision NMEA + setValueSuccess = myGPS.setVal(UBLOX_CFG_RATE_MEAS, 100); //Set measurement rate to 100ms (10Hz update rate) + //setValueSuccess = myGPS.setVal(UBLOX_CFG_RATE_MEAS, 1000); //Set measurement rate to 1000ms (1Hz update rate) + + //Below is the original way we enabled the RTCM message on the I2C port. After that, we show how to do the same + //but with setVal(). + //Original: myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + //setValueSuccess = myGPS.setVal(0x209102bd, 1); //Set output rate of msg 1005 over the I2C port to once per second + + if (setValueSuccess == true) + { + Serial.println("Value was successfully set"); + } + else + Serial.println("Value set failed"); +} + +void loop() +{ +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino new file mode 100644 index 0000000..b2ca2ac --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino @@ -0,0 +1,98 @@ +/* + Configuring port settings using the newer getVal/setVal methods + By: Nathan Seidle + SparkFun Electronics + Date: October 23rd, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its UART1 settings and + then change them if the settings aren't what we want. + + Note: getVal/setVal/delVal are only support in u-blox protocol versions 27 and higher. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a RedBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + bool response = true; + + //Read the settings from RAM (what the module is running right now, not BBR, Flash, or default) + uint8_t currentUART1Setting_ubx = myGPS.getVal8(UBLOX_CFG_UART1INPROT_UBX); + uint8_t currentUART1Setting_nmea = myGPS.getVal8(UBLOX_CFG_UART1INPROT_NMEA); + uint8_t currentUART1Setting_rtcm3 = myGPS.getVal8(UBLOX_CFG_UART1INPROT_RTCM3X); + + Serial.print("currentUART1Setting_ubx: "); + Serial.println(currentUART1Setting_ubx); + Serial.print("currentUART1Setting_nmea: "); + Serial.println(currentUART1Setting_nmea); + Serial.print("currentUART1Setting_rtcm3: "); + Serial.println(currentUART1Setting_rtcm3); + + //Check if NMEA and RTCM are enabled for UART1 + if (currentUART1Setting_ubx == 0 || currentUART1Setting_nmea == 0) + { + Serial.println("Updating UART1 configuration"); + + //setVal sets the values for RAM, BBR, and Flash automatically so no .saveConfiguration() is needed + response &= myGPS.setVal8(UBLOX_CFG_UART1INPROT_UBX, 1); //Enable UBX on UART1 Input + response &= myGPS.setVal8(UBLOX_CFG_UART1INPROT_NMEA, 1); //Enable NMEA on UART1 Input + response &= myGPS.setVal8(UBLOX_CFG_UART1INPROT_RTCM3X, 0); //Disable RTCM on UART1 Input + + if (response == false) + Serial.println("SetVal failed"); + else + Serial.println("SetVal succeeded"); + } + else + Serial.println("No port change needed"); + + //Change speed of UART2 + uint32_t currentUART2Baud = myGPS.getVal32(UBLOX_CFG_UART2_BAUDRATE); + Serial.print("currentUART2Baud: "); + Serial.println(currentUART2Baud); + + if (currentUART2Baud != 57600) + { + response &= myGPS.setVal32(UBLOX_CFG_UART2_BAUDRATE, 57600); + if (response == false) + Serial.println("SetVal failed"); + else + Serial.println("SetVal succeeded"); + } + else + Serial.println("No baud change needed"); + + Serial.println("Done"); +} + +void loop() +{ +} \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino new file mode 100644 index 0000000..1310451 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino @@ -0,0 +1,90 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + Based on Example7 By: Nathan Seidle + SparkFun Electronics + Updated by Paul Clark to demonstrate setVal8/16/32, newCfgValset8/16/32, addCfgValset8/16/32 and sendCfgValset8/16/32 + Date: July 1st, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox changed how to configure their modules in 2019. As of version 23 of the UBX protocol the + UBX-CFG commands are deprecated; they still work, they just recommend using VALSET, VALGET, and VALDEL + commands instead. This example shows how to use this new command structure. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a RedBoard Qwiic or BlackBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GPS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("u-blox multi setVal example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + bool setValueSuccess = true; + + //These key values are hard coded. You can obtain them from the ZED-F9P interface description doc + //or from u-center's Messages->CFG->VALSET window. Keys must be 32-bit. + //Choose setVal8, setVal16 or setVal32 depending on the required value data width (1, 2 or 4 bytes) + //L, U1, I1, E1 and X1 values are 8-bit + //U2, I2, E2 and X2 values are 16-bit + //U4, I4, R4, E4, X4 values are 32-bit + + setValueSuccess &= myGPS.setVal8(UBLOX_CFG_NMEA_HIGHPREC, 0); //Enable high precision NMEA (value is 8-bit (L / U1)) + //setValueSuccess &= myGPS.setVal16(UBLOX_CFG_RATE_MEAS, 200); //Set measurement rate to 100ms (10Hz update rate) (value is 16-bit (U2)) + //setValueSuccess &= myGPS.setVal16(UBLOX_CFG_RATE_MEAS, 200, 1); //Set rate setting in RAM instead of BBR + setValueSuccess &= myGPS.setVal16(UBLOX_CFG_RATE_MEAS, 1000); //Set measurement rate to 1000ms (1Hz update rate) (value is 16-bit (U2)) + + //Below is the original way we enabled a single RTCM message on the I2C port. After that, we show how to do the same + //but with multiple messages all in one go using newCfgValset, addCfgValset and sendCfgValset. + //Original: myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + + //Begin with newCfgValset8/16/32 + setValueSuccess &= myGPS.newCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C, 1); //Set output rate of msg 1005 over the I2C port to once per measurement (value is 8-bit (U1)) + //setValueSuccess &= myGPS.newCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C, 1, VAL_LAYER_RAM); //Set this and the following settings in RAM only instead of Flash/RAM/BBR + //Add extra keyIDs and values using addCfgValset8/16/32 + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_I2C, 1); //Set output rate of msg 1077 over the I2C port to once per measurement (value is 8-bit (U1)) + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_I2C, 1); //Set output rate of msg 1087 over the I2C port to once per measurement (value is 8-bit (U1)) + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_I2C, 1); //Set output rate of msg 1127 over the I2C port to once per measurement (value is 8-bit (U1)) + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_I2C, 1); //Set output rate of msg 1097 over the I2C port to once per measurement (value is 8-bit (U1)) + // Add the final value and send the packet using sendCfgValset8/16/32 + setValueSuccess &= myGPS.sendCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_I2C, 10); //Set output rate of msg 1230 over the I2C port to once every 10 measurements (value is 8-bit (U1)) + + if (setValueSuccess == true) + { + Serial.println("Values were successfully set"); + } + else + Serial.println("Value set failed"); +} + +void loop() +{ +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt new file mode 100644 index 0000000..3dd6c0a --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(sparkfun_ublox_zephyr_library) + +zephyr_compile_options(-fdiagnostics-color=always) + +zephyr_include_directories(.) +target_sources(app PRIVATE src/SparkFun_Ublox_Zephyr_Library.cpp) +target_sources(app PRIVATE src/SparkFun_Ublox_Zephyr_Interface.cpp) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c) diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..3c95572 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay @@ -0,0 +1,4 @@ +&i2c0 { + status = "okay"; + compatible = "nordic,nrf-twim"; +}; \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf new file mode 100644 index 0000000..e239329 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf @@ -0,0 +1,7 @@ +#turn on c++ support +CONFIG_CPLUSPLUS=y + +# turn on peripherals +CONFIG_GPIO=y +CONFIG_I2C=y +CONFIG_I2C_0=y diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp new file mode 100644 index 0000000..65505be --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp @@ -0,0 +1,100 @@ +/* + This is an interface that connects the CPP Ublox library with the main C code. + Added to make it possible to run Ublox lib on Zephyr (NCS) + + This port was made by Vid Rajtmajer , www.irnas.eu +*/ +#include "SparkFun_Ublox_Zephyr_Interface.h" + +#include +#include +#include +#include + +#include "SparkFun_Ublox_Zephyr_Library.h" + + +SFE_UBLOX_GPS myGPS; // driver class instance +long lastTime = 0; // Simple local timer. Limits amount if I2C traffic to Ublox module. + +// init GPIO checksumFailurePin and load GPIO device pointer to the driver +uint8_t set_gpio_dev(struct device *gpio_dev, uint8_t enable_debug) +{ + if (myGPS.init_gpio_pins(*gpio_dev) == false) + { + return -EIO; + } + // turn on debugging if enable_debug is set + if (enable_debug) + { + myGPS.enableDebugging(); + } + return 0; +} + +// initialize I2C and check if GPS device respons +uint8_t gps_begin(struct device *i2c_dev) +{ + if (myGPS.begin(*i2c_dev) == false) + { + return -EIO; + } + return 0; +} + +// This will pipe all NMEA sentences to UART so we can see them +void pipe_nmea_sentences(void) +{ + myGPS.setNMEAOutputPort(); +} + +// Check for available bytes from the device +void check_ublox(void) +{ + myGPS.checkUblox(); +} + +// Get position information when requested, also display number of satellites used in the fix +int get_position(void) +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available, print it to console + if (k_uptime_get_32() - lastTime > 1000) + { + lastTime = k_uptime_get_32(); //Update the timer + + long latitude = myGPS.getLatitude(); + long longitude = myGPS.getLongitude(); + long altitude = myGPS.getAltitude(); + uint8_t SIV = myGPS.getSIV(); + + printk("Position: Lat: %ld, Lon: %ld, Alt: %ld, SIV: %d", latitude, longitude, altitude, SIV); + return 0; + } + return -EBUSY; +} + +// Get date and time information when requested, check if they are valid and print info to console, it returns UNIX time +void get_datetime(void) +{ + int year = myGPS.getYear(); + int month = myGPS.getMonth(); + int day = myGPS.getDay(); + int hour = myGPS.getHour(); + int minute = myGPS.getMinute(); + int second = myGPS.getSecond(); + + printk("DateTime: %d-%d-%d %d:%d:%d\n", year, month, day, hour, minute, second); + + printk("Time is "); + if (myGPS.getTimeValid() == false) + { + printk("not "); + } + printk("valid. Date is "); + if (myGPS.getDateValid() == false) + { + printk("not "); + } + printk("valid.\n"); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h new file mode 100644 index 0000000..0af3d3c --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h @@ -0,0 +1,29 @@ +/* + This is an interface that connects the CPP Ublox library with the main C code. + Added to make it possible to run Ublox lib on Zephyr (NCS) + + This port was made by Vid Rajtmajer , www.irnas.eu +*/ +#include +#include + +#ifndef _UBLOX_LIB_INTERFACE_H_ +#define _UBLOX_LIB_INTERFACE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + uint8_t set_gpio_dev(struct device *gpio_dev, uint8_t enable_debug); // init GPIO + uint8_t gps_begin(struct device *i2c_dev); // initialize I2C and check if GPS device respons + void pipe_nmea_sentences(void); // print NMEA sentences + + void check_ublox(void); // Check for available bytes from the device + int get_position(void); // Get position information + void get_datetime(void); // Get date and time information + +#ifdef __cplusplus +} +#endif + +#endif //UBLOX_LIB_INTERFACE_H_ \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp new file mode 100644 index 0000000..3179e3b --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp @@ -0,0 +1,3492 @@ +/* + This is a library written for the Ublox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a Ublox GPS module. Works with most modules from Ublox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + NCS v1.0.3 release - this port + + This port was made by Vid Rajtmajer , www.irnas.eu + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "SparkFun_Ublox_Zephyr_Library.h" +#include + +SFE_UBLOX_GPS::SFE_UBLOX_GPS(void) +{ + // Constructor + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + moduleQueried.versionNumber = false; +} + +// Get i2c device to the class and configure checksumFailurePin +bool SFE_UBLOX_GPS::init_gpio_pins(struct device &gpio_dev) +{ + _gpio_dev = &gpio_dev; + if (checksumFailurePin >= 0) + { + int err; + err = gpio_pin_configure(_gpio_dev, (uint8_t)checksumFailurePin, GPIO_OUTPUT); + err = gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + if (err) + { + return false; + } + } + return true; +} + +//Initialize the I2C port +bool SFE_UBLOX_GPS::begin(struct device &i2c_dev, uint8_t deviceAddress) +{ + commType = COMM_TYPE_I2C; + _i2cPort = &i2c_dev; //Grab which port the user wants us to use + + //We expect caller to begin their I2C port, with the speed of their choice external to the library + //But if they forget, we start the hardware here. + + //We're moving away from the practice of starting Wire hardware in a library. This is to avoid cross platform issues. + //ie, there are some platforms that don't handle multiple starts to the wire hardware. Also, every time you start the wire + //hardware the clock speed reverts back to 100kHz regardless of previous Wire.setClocks(). + //_i2cPort->begin(); + + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + + return (isConnected()); +} + +//Initialize the Serial port - function not ported +/* +bool SFE_UBLOX_GPS::begin(Stream &serialPort) +{ + commType = COMM_TYPE_SERIAL; + _serialPort = &serialPort; //Grab which port the user wants us to use + + return (isConnected()); +} +*/ + +//Enable or disable the printing of sent/response HEX values. +//Use this in conjunction with 'Transport Logging' from the Universal Reader Assistant to see what they're doing that we're not +void SFE_UBLOX_GPS::enableDebugging(bool printLimitedDebug) +{ + if (printLimitedDebug == false) + { + _printDebug = true; //Should we print the commands we send? Good for debugging + } + else + { + _printLimitedDebug = true; //Should we print limited debug messages? Good for debugging high navigation rates + } +} + +void SFE_UBLOX_GPS::disableDebugging(void) +{ + _printDebug = false; //Turn off extra print statements + _printLimitedDebug = false; +} + +//Safely print messages +void SFE_UBLOX_GPS::debugPrint(char *message) +{ + if (_printDebug == true) + { + printk("%s", message); + } +} +//Safely print messages +void SFE_UBLOX_GPS::debugPrintln(char *message) +{ + if (_printDebug == true) + { + printk("%s\n", message); + } +} + +const char *SFE_UBLOX_GPS::statusString(sfe_ublox_status_e stat) +{ + switch (stat) + { + case SFE_UBLOX_STATUS_SUCCESS: + return "Success"; + break; + case SFE_UBLOX_STATUS_FAIL: + return "General Failure"; + break; + case SFE_UBLOX_STATUS_CRC_FAIL: + return "CRC Fail"; + break; + case SFE_UBLOX_STATUS_TIMEOUT: + return "Timeout"; + break; + case SFE_UBLOX_STATUS_COMMAND_NACK: + return "Command not acknowledged (NACK)"; + break; + case SFE_UBLOX_STATUS_OUT_OF_RANGE: + return "Out of range"; + break; + case SFE_UBLOX_STATUS_INVALID_ARG: + return "Invalid Arg"; + break; + case SFE_UBLOX_STATUS_INVALID_OPERATION: + return "Invalid operation"; + break; + case SFE_UBLOX_STATUS_MEM_ERR: + return "Memory Error"; + break; + case SFE_UBLOX_STATUS_HW_ERR: + return "Hardware Error"; + break; + case SFE_UBLOX_STATUS_DATA_SENT: + return "Data Sent"; + break; + case SFE_UBLOX_STATUS_DATA_RECEIVED: + return "Data Received"; + break; + case SFE_UBLOX_STATUS_I2C_COMM_FAILURE: + return "I2C Comm Failure"; + break; + case SFE_UBLOX_STATUS_DATA_OVERWRITTEN: + return "Data Packet Overwritten"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + +void SFE_UBLOX_GPS::factoryReset() +{ + // Copy default settings to permanent + // Note: this does not load the permanent configuration into the current configuration. Calling factoryDefault() will do that. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 13; + packetCfg.startingSpot = 0; + for (uint8_t i = 0; i < 4; i++) + { + payloadCfg[0 + i] = 0xff; // clear mask: copy default config to permanent config + payloadCfg[4 + i] = 0x00; // save mask: don't save current to permanent + payloadCfg[8 + i] = 0x00; // load mask: don't copy permanent config to current + } + payloadCfg[12] = 0xff; // all forms of permanent memory + sendCommand(&packetCfg, 0); // don't expect ACK + hardReset(); // cause factory default config to actually be loaded and used cleanly +} + +void SFE_UBLOX_GPS::hardReset() +{ + // Issue hard reset + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RST; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + payloadCfg[0] = 0xff; // cold start + payloadCfg[1] = 0xff; // cold start + payloadCfg[2] = 0; // 0=HW reset + payloadCfg[3] = 0; // reserved + sendCommand(&packetCfg, 0); // don't expect ACK +} + +// Write data to I2C, Arguments: buffer - data to write, num_bytes - buffer length, +// stop - if true send STOP, if false send RESTART after message +int SFE_UBLOX_GPS::transferWriteI2C(u8_t *buffer, u32_t num_bytes, bool stop) +{ + struct i2c_msg msgs[1]; + // Data to be written and STOP or RESTART after this. + msgs[0].buf = buffer; + msgs[0].len = num_bytes; + if (stop) { + msgs[0].flags = I2C_MSG_WRITE | I2C_MSG_STOP; + } + else { + msgs[0].flags = I2C_MSG_WRITE | I2C_MSG_RESTART; + } + return i2c_transfer(_i2cPort, &msgs[0], 1, _gpsI2Caddress); +} + +// Read data from I2C, Arguments: buffer - store read data, num_bytes - buffer length +int SFE_UBLOX_GPS::transferReadI2C(u8_t *buffer, u32_t num_bytes) +{ + struct i2c_msg msgs[1]; + // Data to be read and STOP after this + msgs[0].buf = buffer; + msgs[0].len = num_bytes; + msgs[0].flags = I2C_MSG_READ | I2C_MSG_STOP; + + int err = i2c_transfer(_i2cPort, &msgs[0], 1, _gpsI2Caddress); + return err; +} + +//Changes the serial baud rate of the Ublox module, can't return success/fail 'cause ACK from modem +//is lost due to baud rate change +void SFE_UBLOX_GPS::setSerialRate(uint32_t baudrate, uint8_t uartPort, uint16_t maxWait) +{ + //Get the current config values for the UART port + getPortSettings(uartPort, maxWait); //This will load the payloadCfg array with current port settings + + if (_printDebug == true) + { + printk("Current baud rate: "); + printk("%d\n", ((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[8] = baudrate; + payloadCfg[9] = baudrate >> 8; + payloadCfg[10] = baudrate >> 16; + payloadCfg[11] = baudrate >> 24; + + if (_printDebug == true) + { + printk("New baud rate: "); + printk("%d\n", ((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + printk("setSerialRate: sendCommand returned: %s\n", statusString(retVal)); + } +} + +//Changes the I2C address that the Ublox module responds to +//0x42 is the default but can be changed with this command +bool SFE_UBLOX_GPS::setI2CAddress(uint8_t deviceAddress, uint16_t maxWait) +{ + //Get the current config values for the I2C port + getPortSettings(COM_PORT_I2C, maxWait); //This will load the payloadCfg array with current port settings + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[4] = deviceAddress << 1; //DDC mode LSB + + if (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT) // We are only expecting an ACK + { + //Success! Now change our internal global. + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + return (true); + } + return (false); +} + +//Want to see the NMEA messages on the Serial port? Here's how - ported to print to console, here only enable the flag +void SFE_UBLOX_GPS::setNMEAOutputPort() +{ + _nmeaOutputPort = true; +} + +//Called regularly to check for available bytes on the user' specified port +bool SFE_UBLOX_GPS::checkUblox(uint8_t requestedClass, uint8_t requestedID) +{ + return checkUbloxInternal(&packetCfg, requestedClass, requestedID); +} + +//Called regularly to check for available bytes on the user' specified port +bool SFE_UBLOX_GPS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (commType == COMM_TYPE_I2C) + return (checkUbloxI2C(incomingUBX, requestedClass, requestedID)); + else if (commType == COMM_TYPE_SERIAL) + return (checkUbloxSerial(incomingUBX, requestedClass, requestedID)); + return false; +} + +//Polls I2C for data, passing any new bytes to process() +//Returns true if new bytes are available +bool SFE_UBLOX_GPS::checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (k_uptime_get_32() - lastCheck >= i2cPollingWait) + { + uint16_t bytesAvailable = 0; + int err; + + //Get the number of bytes available from the module + //0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available + u8_t data_buffer[1]; + data_buffer[0] = 0xFD; + err = transferWriteI2C(data_buffer, 1, true); + if (err) { + return (false); + } + + bool data_read_ok = true; + u8_t read_buffer[2]; + err = transferReadI2C(data_buffer, 2); + if (err) { + data_read_ok = false; + } + + if (data_read_ok) + { + uint8_t msb = read_buffer[0]; + uint8_t lsb = read_buffer[1]; + if (lsb == 0xFF) + { + //I believe this is a Ublox bug. Device should never present an 0xFF. + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + printk("checkUbloxI2C: Ublox bug, length lsb is 0xFF\n"); + } + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + lastCheck = k_uptime_get_32(); //Put off checking to avoid I2C bus traffic + return (false); + } + bytesAvailable = (uint16_t)msb << 8 | lsb; + } + + if (bytesAvailable == 0) + { + if (_printDebug == true) + { + printk("checkUbloxI2C: OK, zero bytes available\n"); + } + lastCheck = k_uptime_get_32(); //Put off checking to avoid I2C bus traffic + return (false); + } + + //Check for undocumented bit error. We found this doing logic scans. + //This error is rare but if we incorrectly interpret the first bit of the two 'data available' bytes as 1 + //then we have far too many bytes to check. May be related to I2C setup time violations: https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40 + if (bytesAvailable & ((uint16_t)1 << 15)) + { + //Clear the MSbit + bytesAvailable &= ~((uint16_t)1 << 15); + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + printk("checkUbloxI2C: Bytes available error: %d\n", bytesAvailable); + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + } + } + + if (bytesAvailable > 100) + { + if (_printDebug == true) + { + printk("checkUbloxI2C: Large packet of %d bytes received\n", bytesAvailable); + } + } + else + { + if (_printDebug == true) + { + printk("checkUbloxI2C: Reading %d bytes\n", bytesAvailable); + } + } + + while (bytesAvailable) + { + u8_t data_buffer[1]; + data_buffer[0] = 0xFF; //0xFF is the register to read data from + err = transferWriteI2C(data_buffer, 1, false); //Send a restart command. Do not release bus. + if (err) { + return (false); + } + + //Limit to 32 bytes or whatever the buffer limit is for given platform + uint16_t bytesToRead = bytesAvailable; + if (bytesToRead > I2C_BUFFER_LENGTH) + bytesToRead = I2C_BUFFER_LENGTH; + + TRY_AGAIN: + bool data_read_ok = true; + u8_t read_buffer[(uint8_t)bytesToRead]; + err = transferReadI2C(read_buffer, (uint8_t)bytesToRead); + if (err) { + data_read_ok = false; + } + + if (data_read_ok) + { + for (uint16_t x = 0; x < bytesToRead; x++) + { + uint8_t incoming = read_buffer[x]; //Grab the actual character + + //Check to see if the first read is 0x7F. If it is, the module is not ready + //to respond. Stop, wait, and try again + if (x == 0) + { + if (incoming == 0x7F) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + printk("checkUbloxU2C: Ublox error, module not ready with data\n"); + } + k_msleep(5); //In logic analyzation, the module starting responding after 1.48ms + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + goto TRY_AGAIN; + } + } + process(incoming, incomingUBX, requestedClass, requestedID); //Process this valid character + } + } + else + return (false); //Sensor did not respond + + bytesAvailable -= bytesToRead; + } + } + + return (true); + +} //end checkUbloxI2C() + +//Checks Serial for data, passing any new bytes to process() +bool SFE_UBLOX_GPS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + printk("checkUbloxSerial function not ported...\n"); + /* + while (_serialPort->available()) + { + process(_serialPort->read(), incomingUBX, requestedClass, requestedID); + } + */ + return (true); + +} //end checkUbloxSerial() + +//Processes NMEA and UBX binary sentences one byte at a time +//Take a given byte and file it into the proper array +void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if ((currentSentence == NONE) || (currentSentence == NMEA)) + { + if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + { + //This is the start of a binary sentence. Reset flags. + //We still don't know the response class + ubxFrameCounter = 0; + currentSentence = UBX; + //Reset the packetBuf.counter even though we will need to reset it again when ubxFrameCounter == 2 + packetBuf.counter = 0; + ignoreThisPayload = false; //We should not ignore this payload - yet + //Store data in packetBuf until we know if we have a requested class and ID match + activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + } + else if (incoming == '$') + { + currentSentence = NMEA; + } + else if (incoming == 0xD3) //RTCM frames start with 0xD3 + { + rtcmFrameCounter = 0; + currentSentence = RTCM; + } + else + { + //This character is unknown or we missed the previous start of a sentence + } + } + + //Depending on the sentence, pass the character to the individual processor + if (currentSentence == UBX) + { + //Decide what type of response this is + if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + currentSentence = NONE; //Something went wrong. Reset. + else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + currentSentence = NONE; //Something went wrong. Reset. + // Note to future self: + // There may be some duplication / redundancy in the next few lines as processUBX will also + // load information into packetBuf, but we'll do it here too for clarity + else if (ubxFrameCounter == 2) //Class + { + // Record the class in packetBuf until we know what to do with it + packetBuf.cls = incoming; // (Duplication) + rollingChecksumA = 0; //Reset our rolling checksums here (not when we receive the 0xB5) + rollingChecksumB = 0; + packetBuf.counter = 0; //Reset the packetBuf.counter (again) + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // Reset the packet validity (redundant?) + packetBuf.startingSpot = incomingUBX->startingSpot; //Copy the startingSpot + } + else if (ubxFrameCounter == 3) //ID + { + // Record the ID in packetBuf until we know what to do with it + packetBuf.id = incoming; // (Duplication) + //We can now identify the type of response + //If the packet we are receiving is not an ACK then check for a class and ID match + if (packetBuf.cls != UBX_CLASS_ACK) + { + //This is not an ACK so check for a class and ID match + if ((packetBuf.cls == requestedClass) && (packetBuf.id == requestedID)) + { + //This is not an ACK and we have a class and ID match + //So start diverting data into incomingUBX (usually packetCfg) + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + else + { + //This is not an ACK and we do not have a class and ID match + //so we should keep diverting data into packetBuf and ignore the payload + ignoreThisPayload = true; + } + } + else + { + // This is an ACK so it is to early to do anything with it + // We need to wait until we have received the length and data bytes + // So we should keep diverting data into packetBuf + } + } + else if (ubxFrameCounter == 4) //Length LSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len = incoming; // (Duplication) + } + else if (ubxFrameCounter == 5) //Length MSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len |= incoming << 8; // (Duplication) + } + else if (ubxFrameCounter == 6) //This should be the first byte of the payload unless .len is zero + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + if (_printDebug == true) + { + printk("process: ZERO LENGTH packet received: Class: 0x%x ID: 0x%x\n", packetBuf.cls, packetBuf.id); + } + //If length is zero (!) this will be the first byte of the checksum so record it + packetBuf.checksumA = incoming; + } + else + { + //The length is not zero so record this byte in the payload + packetBuf.payload[0] = incoming; + } + } + else if (ubxFrameCounter == 7) //This should be the second byte of the payload unless .len is zero or one + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + //If length is zero (!) this will be the second byte of the checksum so record it + packetBuf.checksumB = incoming; + } + else if (packetBuf.len == 1) // Check if length is one + { + //The length is one so this is the first byte of the checksum + packetBuf.checksumA = incoming; + } + else // Length is >= 2 so this must be a payload byte + { + packetBuf.payload[1] = incoming; + } + // Now that we have received two payload bytes, we can check for a matching ACK/NACK + if ((activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) // If we are not already processing a data packet + && (packetBuf.cls == UBX_CLASS_ACK) // and if this is an ACK/NACK + && (packetBuf.payload[0] == requestedClass) // and if the class matches + && (packetBuf.payload[1] == requestedID)) // and if the ID matches + { + if (packetBuf.len == 2) // Check if .len is 2 + { + // Then this is a matching ACK so copy it into packetAck + activePacketBuffer = SFE_UBLOX_PACKET_PACKETACK; + packetAck.cls = packetBuf.cls; + packetAck.id = packetBuf.id; + packetAck.len = packetBuf.len; + packetAck.counter = packetBuf.counter; + packetAck.payload[0] = packetBuf.payload[0]; + packetAck.payload[1] = packetBuf.payload[1]; + } + else // Length is not 2 (hopefully this is impossible!) + { + if (_printDebug == true) + { + printk("process: ACK received with .len != 2: Class: 0x%x ID: 0x%x, len: %d\n", packetBuf.payload[0], packetBuf.payload[1], packetBuf.len); + } + } + } + } + + //Divert incoming into the correct buffer + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETACK) + processUBX(incoming, &packetAck, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + processUBX(incoming, incomingUBX, requestedClass, requestedID); + else // if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) + processUBX(incoming, &packetBuf, requestedClass, requestedID); + + //Finally, increment the frame counter + ubxFrameCounter++; + } + else if (currentSentence == NMEA) + { + processNMEA(incoming); //Process each NMEA character + } + else if (currentSentence == RTCM) + { + processRTCMframe(incoming); //Deal with RTCM bytes + } +} + +//This is the default or generic NMEA processor. We're only going to pipe the data to serial port so we can see it. +//User could overwrite this function to pipe characters to nmea.process(c) of tinyGPS or MicroNMEA +//Or user could pipe each character to a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //If user has assigned an output port then pipe the characters there + + if (_nmeaOutputPort) + printk("%c", incoming); //Echo this byte to the serial port +} + +//We need to be able to identify an RTCM packet and then the length +//so that we know when the RTCM message is completely received and we then start +//listening for other sentences (like NMEA or UBX) +//RTCM packet structure is very odd. I never found RTCM STANDARD 10403.2 but +//http://d1.amobbs.com/bbs_upload782111/files_39/ourdev_635123CK0HJT.pdf is good +//https://dspace.cvut.cz/bitstream/handle/10467/65205/F3-BP-2016-Shkalikava-Anastasiya-Prenos%20polohove%20informace%20prostrednictvim%20datove%20site.pdf?sequence=-1 +//Lead me to: https://forum.u-blox.com/index.php/4348/how-to-read-rtcm-messages-from-neo-m8p +//RTCM 3.2 bytes look like this: +//Byte 0: Always 0xD3 +//Byte 1: 6-bits of zero +//Byte 2: 10-bits of length of this packet including the first two-ish header bytes, + 6. +//byte 3 + 4 bits: Msg type 12 bits +//Example: D3 00 7C 43 F0 ... / 0x7C = 124+6 = 130 bytes in this packet, 0x43F = Msg type 1087 +void SFE_UBLOX_GPS::processRTCMframe(uint8_t incoming) +{ + if (rtcmFrameCounter == 1) + { + rtcmLen = (incoming & 0x03) << 8; //Get the last two bits of this byte. Bits 8&9 of 10-bit length + } + else if (rtcmFrameCounter == 2) + { + rtcmLen |= incoming; //Bits 0-7 of packet length + rtcmLen += 6; //There are 6 additional bytes of what we presume is header, msgType, CRC, and stuff + } + /*else if (rtcmFrameCounter == 3) + { + rtcmMsgType = incoming << 4; //Message Type, MS 4 bits + } + else if (rtcmFrameCounter == 4) + { + rtcmMsgType |= (incoming >> 4); //Message Type, bits 0-7 + }*/ + + rtcmFrameCounter++; + + processRTCM(incoming); //Here is where we expose this byte to the user + + if (rtcmFrameCounter == rtcmLen) + { + //We're done! + currentSentence = NONE; //Reset and start looking for next sentence type + } +} + +//This function is called for each byte of an RTCM frame +//Ths user can overwrite this function and process the RTCM frame as they please +//Bytes can be piped to Serial or other interface. The consumer could be a radio or the internet (Ntrip broadcaster) +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Radio.sendReliable((String)incoming); //An example of passing this byte to a radio + + //Debug printing + // printk(" "); + // if(incoming < 0x10) printk("0"); + // if(incoming < 0x10) printk("0"); + // printk("%d", incoming); + // if(rtcmFrameCounter % 16 == 0) printk("\n"); +} + +//Given a character, file it away into the uxb packet structure +//Set valid to VALID or NOT_VALID once sentence is completely received and passes or fails CRC +//The payload portion of the packet can be 100s of bytes but the max array +//size is MAX_PAYLOAD_SIZE bytes. startingSpot can be set so we only record +//a subset of bytes within a larger packet. +void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + //Add all incoming bytes to the rolling checksum + //Stop at len+4 as this is the checksum bytes to that should not be added to the rolling checksum + if (incomingUBX->counter < incomingUBX->len + 4) + addToChecksum(incoming); + + if (incomingUBX->counter == 0) + { + incomingUBX->cls = incoming; + } + else if (incomingUBX->counter == 1) + { + incomingUBX->id = incoming; + } + else if (incomingUBX->counter == 2) //Len LSB + { + incomingUBX->len = incoming; + } + else if (incomingUBX->counter == 3) //Len MSB + { + incomingUBX->len |= incoming << 8; + } + else if (incomingUBX->counter == incomingUBX->len + 4) //ChecksumA + { + incomingUBX->checksumA = incoming; + } + else if (incomingUBX->counter == incomingUBX->len + 5) //ChecksumB + { + incomingUBX->checksumB = incoming; + + currentSentence = NONE; //We're done! Reset the sentence to being looking for a new start char + + //Validate this sentence + if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + + // Let's check if the class and ID match the requestedClass and requestedID + // Remember - this could be a data packet or an ACK packet + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is a NACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_NACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_NOTACKNOWLEDGED; // If we have a match, set the classAndIDmatch flag to NOTACKNOWLEDGED + if (_printDebug == true) + { + printk("processUBX: NACK received: Requested Class: 0x%x, Requested ID: 0x%x\n", incomingUBX->payload[0], incomingUBX->payload[1]); + } + } + + if (_printDebug == true) + { + printk("Incoming: Size: %d Received: \n", incomingUBX->len); + printPacket(incomingUBX); + + if (incomingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetCfg now valid\n"); + } + if (packetAck.valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetAck now valid\n"); + } + if (incomingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetCfg classAndIDmatch\n"); + } + if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetAck classAndIDmatch\n"); + } + } + + //We've got a valid packet, now do something with it but only if ignoreThisPayload is false + if (ignoreThisPayload == false) + { + processUBXpacket(incomingUBX); + } + } + else // Checksum failure + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; + + // Let's check if the class and ID match the requestedClass and requestedID. + // This is potentially risky as we are saying that we saw the requested Class and ID + // but that the packet checksum failed. Potentially it could be the class or ID bytes + // that caused the checksum error! + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + //Drive an external pin to allow for easier logic analyzation + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + + printk("Checksum failed: checksumA: %d checksumB: %d\n", incomingUBX->checksumA, incomingUBX->checksumB); + printk("rollingChecksumA: %d, rollingChecksumB: %d\n", rollingChecksumA, rollingChecksumB); + printk("Failed: Size: %d Received: \n", incomingUBX->len); + printPacket(incomingUBX); + } + } + } + else //Load this byte into the payload array + { + //If a UBX_NAV_PVT packet comes in asynchronously, we need to fudge the startingSpot + uint16_t startingSpot = incomingUBX->startingSpot; + if (incomingUBX->cls == UBX_CLASS_NAV && incomingUBX->id == UBX_NAV_PVT) + startingSpot = 0; + //Begin recording if counter goes past startingSpot + if ((incomingUBX->counter - 4) >= startingSpot) + { + //Check to see if we have room for this byte + if (((incomingUBX->counter - 4) - startingSpot) < MAX_PAYLOAD_SIZE) //If counter = 208, starting spot = 200, we're good to record. + { + // Check if this is payload data which should be ignored + if (ignoreThisPayload == false) + { + incomingUBX->payload[incomingUBX->counter - 4 - startingSpot] = incoming; //Store this byte into payload array + } + } + } + } + + //Increment the counter + incomingUBX->counter++; + + if (incomingUBX->counter == MAX_PAYLOAD_SIZE) + { + //Something has gone very wrong + currentSentence = NONE; //Reset the sentence to being looking for a new start char + if (_printDebug == true) + { + printk("processUBX: counter hit MAX_PAYLOAD_SIZE\n"); + } + } +} + +//Once a packet has been received and validated, identify this packet's class/id and update internal flags +//Note: if the user requests a PVT or a HPPOSLLH message using a custom packet, the data extraction will +// not work as expected beacuse extractLong etc are hardwired to packetCfg payloadCfg. Ideally +// extractLong etc should be updated so they receive a pointer to the packet buffer. +void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) +{ + switch (msg->cls) + { + case UBX_CLASS_NAV: + if (msg->id == UBX_NAV_PVT && msg->len == 92) + { + //Parse various byte fields into global vars + constexpr int startingSpot = 0; //fixed value used in processUBX + + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + gpsYear = extractInt(4); + gpsMonth = extractByte(6); + gpsDay = extractByte(7); + gpsHour = extractByte(8); + gpsMinute = extractByte(9); + gpsSecond = extractByte(10); + gpsDateValid = extractByte(11) & 0x01; + gpsTimeValid = (extractByte(11) & 0x02) >> 1; + gpsNanosecond = extractLong(16); //Includes milliseconds + + fixType = extractByte(20 - startingSpot); + carrierSolution = extractByte(21 - startingSpot) >> 6; //Get 6th&7th bits of this byte + SIV = extractByte(23 - startingSpot); + longitude = extractLong(24 - startingSpot); + latitude = extractLong(28 - startingSpot); + altitude = extractLong(32 - startingSpot); + altitudeMSL = extractLong(36 - startingSpot); + groundSpeed = extractLong(60 - startingSpot); + headingOfMotion = extractLong(64 - startingSpot); + pDOP = extractInt(76 - startingSpot); + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = true; + moduleQueried.gpsYear = true; + moduleQueried.gpsMonth = true; + moduleQueried.gpsDay = true; + moduleQueried.gpsHour = true; + moduleQueried.gpsMinute = true; + moduleQueried.gpsSecond = true; + moduleQueried.gpsDateValid = true; + moduleQueried.gpsTimeValid = true; + moduleQueried.gpsNanosecond = true; + + moduleQueried.all = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + moduleQueried.SIV = true; + moduleQueried.fixType = true; + moduleQueried.carrierSolution = true; + moduleQueried.groundSpeed = true; + moduleQueried.headingOfMotion = true; + moduleQueried.pDOP = true; + } + else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == 36) + { + timeOfWeek = extractLong(4); + highResLongitude = extractLong(8); + highResLatitude = extractLong(12); + elipsoid = extractLong(16); + meanSeaLevel = extractLong(20); + highResLongitudeHp = extractSignedChar(24); + highResLatitudeHp = extractSignedChar(25); + elipsoidHp = extractSignedChar(26); + meanSeaLevelHp = extractSignedChar(27); + horizontalAccuracy = extractLong(28); + verticalAccuracy = extractLong(32); + + highResModuleQueried.all = true; + highResModuleQueried.highResLatitude = true; + highResModuleQueried.highResLatitudeHp = true; + highResModuleQueried.highResLongitude = true; + highResModuleQueried.highResLongitudeHp = true; + highResModuleQueried.elipsoid = true; + highResModuleQueried.elipsoidHp = true; + highResModuleQueried.meanSeaLevel = true; + highResModuleQueried.meanSeaLevelHp = true; + highResModuleQueried.geoidSeparation = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + moduleQueried.gpsiTOW = true; // this can arrive via HPPOS too. + + if (_printDebug == true) + { + printk("Sec: %f", ((float)extractLong(4)) / 1000.0f); + printk("\n"); + printk("LON: %f", ((float)(int32_t)extractLong(8)) / 10000000.0f); + printk("\n"); + printk("LAT: %f", ((float)(int32_t)extractLong(12)) / 10000000.0f); + printk("\n"); + printk("ELI M: %f", ((float)(int32_t)extractLong(16)) / 1000.0f); + printk("\n"); + printk("MSL M: %f", ((float)(int32_t)extractLong(20)) / 1000.0f); + printk("\n"); + printk("LON HP: %c", extractSignedChar(24)); + printk("\n"); + printk("LAT HP: %c", extractSignedChar(25)); + printk("\n"); + printk("ELI HP: %calcChecksum", extractSignedChar(26)); + printk("\n"); + printk("MSL HP: %c", extractSignedChar(27)); + printk("\n"); + printk("HA 2D M: %f", ((float)(int32_t)extractLong(28)) / 10000.0f); + printk("\n"); + printk("VERT M: %f", ((float)(int32_t)extractLong(32)) / 10000.0f); + } + } + break; + } +} + +//Given a packet and payload, send everything including CRC bytes via I2C port +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + sfe_ublox_status_e retVal = SFE_UBLOX_STATUS_SUCCESS; + + calcChecksum(outgoingUBX); //Sets checksum A and B bytes of the packet + + if (_printDebug == true) + { + printk("\nSending: "); + printPacket(outgoingUBX); + } + + if (commType == COMM_TYPE_I2C) + { + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + if (_printDebug == true) + { + printk("Send I2C Command failed\n"); + } + return retVal; + } + } + else if (commType == COMM_TYPE_SERIAL) + { + sendSerialCommand(outgoingUBX); + } + + if (maxWait > 0) + { + //Depending on what we just sent, either we need to look for an ACK or not + if (outgoingUBX->cls == UBX_CLASS_CFG) + { + if (_printDebug == true) + { + printk("sendCommand: Waiting for ACK response\n"); + } + retVal = waitForACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + else + { + if (_printDebug == true) + { + printk("sendCommand: Waiting for No ACK response\n"); + } + retVal = waitForNoACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + } + return retVal; +} + +//Returns false if sensor fails to respond to I2C traffic +sfe_ublox_status_e SFE_UBLOX_GPS::sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + int err; + //Point at 0xFF data register + u8_t small_buffer[1]; + small_buffer[0] = 0xFF; + err = transferWriteI2C(small_buffer, 1); //There is no register to write to, we just begin writing data bytes + if (err) { + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + } + + //Write header bytes + u8_t header_buffer[6]; //There is no register to write to, we just begin writing data bytes + header_buffer[0] = UBX_SYNCH_1; //μ - oh ublox, you're funny. I will call you micro-blox from now on. + header_buffer[1] = UBX_SYNCH_2; //b + header_buffer[2] = outgoingUBX->cls; + header_buffer[3] = outgoingUBX->id; + header_buffer[4] = outgoingUBX->len & 0xFF; //LSB + header_buffer[5] = outgoingUBX->len >> 8; //MSB + + err = transferWriteI2C(header_buffer, 6, false); + k_msleep(1); // added this millisecond sleep to not progress too fast + if (err) { + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + } + + //Write payload. Limit the sends into 32 byte chunks + //This code based on ublox: https://forum.u-blox.com/index.php/20528/how-to-use-i2c-to-get-the-nmea-frames + uint16_t bytesToSend = outgoingUBX->len; + + //"The number of data bytes must be at least 2 to properly distinguish + //from the write access to set the address counter in random read accesses." + uint16_t startSpot = 0; + while (bytesToSend > 1) + { + uint8_t len = bytesToSend; + if (len > I2C_BUFFER_LENGTH) + len = I2C_BUFFER_LENGTH; + + u8_t data_buffer[len]; + for (uint16_t x = 0; x < len; x++) { + data_buffer[x] = outgoingUBX->payload[startSpot + x]; + } + err = transferWriteI2C(data_buffer, len, false); //Write a portion of the payload to the bus + if (err) { + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + } + + //*outgoingUBX->payload += len; //Move the pointer forward + startSpot += len; //Move the pointer forward + bytesToSend -= len; + } + + //Write checksum + if (bytesToSend == 1) { + u8_t checksum_buffer[3]; + uint8_t payload = *outgoingUBX->payload; + checksum_buffer[0] = payload; + checksum_buffer[1] = outgoingUBX->checksumA; + checksum_buffer[2] = outgoingUBX->checksumB; + err = transferWriteI2C(checksum_buffer, 3, true); + } + else { + u8_t checksum_buffer[2]; + checksum_buffer[0] = outgoingUBX->checksumA; + checksum_buffer[1] = outgoingUBX->checksumB; + err = transferWriteI2C(checksum_buffer, 2, true); + } + if (err) { + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + } + + return (SFE_UBLOX_STATUS_SUCCESS); +} + +//Given a packet and payload, send everything including CRC bytesA via Serial port +void SFE_UBLOX_GPS::sendSerialCommand(ubxPacket *outgoingUBX) +{ + printk("sendSerialCommand function not ported...\n"); + /* + //Write header bytes + _serialPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _serialPort->write(UBX_SYNCH_2); //b + _serialPort->write(outgoingUBX->cls); + _serialPort->write(outgoingUBX->id); + _serialPort->write(outgoingUBX->len & 0xFF); //LSB + _serialPort->write(outgoingUBX->len >> 8); //MSB + + //Write payload. + for (int i = 0; i < outgoingUBX->len; i++) + { + _serialPort->write(outgoingUBX->payload[i]); + } + + //Write checksum + _serialPort->write(outgoingUBX->checksumA); + _serialPort->write(outgoingUBX->checksumB); + */ +} + +//Returns true if I2C device ack's +bool SFE_UBLOX_GPS::isConnected(uint16_t maxWait) +{ + if (commType == COMM_TYPE_I2C) + { + u8_t buff[1]; + buff[0] = 0x00; + int err = transferWriteI2C(buff, 1); + if (err) { + return false; //Sensor did not ack + } + } + + // Query navigation rate to see whether we get a meaningful response + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are polling the RATE so we expect data and an ACK + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // data send is returned, not data received - TODO library bug? +} + +//Given a message, calc and store the two byte "8-Bit Fletcher" checksum over the entirety of the message +//This is called before we send a command message +void SFE_UBLOX_GPS::calcChecksum(ubxPacket *msg) +{ + msg->checksumA = 0; + msg->checksumB = 0; + + msg->checksumA += msg->cls; + msg->checksumB += msg->checksumA; + + msg->checksumA += msg->id; + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len & 0xFF); + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len >> 8); + msg->checksumB += msg->checksumA; + + for (uint16_t i = 0; i < msg->len; i++) + { + msg->checksumA += msg->payload[i]; + msg->checksumB += msg->checksumA; + } +} + +//Given a message and a byte, add to rolling "8-Bit Fletcher" checksum +//This is used when receiving messages from module +void SFE_UBLOX_GPS::addToChecksum(uint8_t incoming) +{ + rollingChecksumA += incoming; + rollingChecksumB += rollingChecksumA; +} + +//Pretty prints the current ubxPacket +void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) +{ + if (_printDebug == true) + { + printk("CLS:"); + if (packet->cls == UBX_CLASS_NAV) //1 + printk("NAV"); + else if (packet->cls == UBX_CLASS_ACK) //5 + printk("ACK"); + else if (packet->cls == UBX_CLASS_CFG) //6 + printk("CFG"); + else if (packet->cls == UBX_CLASS_MON) //0x0A + printk("MON"); + else + { + printk("0x%x", packet->cls); + } + + printk(" ID:"); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + printk("PVT"); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_RATE) + printk("RATE"); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_CFG) + printk("SAVE"); + else + { + printk("0x%x", packet->id); + } + + printk(" Len: 0x%x", packet->len); + + // Only print the payload is ignoreThisPayload is false otherwise + // we could be printing gibberish from beyond the end of packetBuf + if (ignoreThisPayload == false) + { + printk(" Payload:"); + + for (int x = 0; x < packet->len; x++) + { + printk(" %x", packet->payload[x]); + } + } + else + { + printk(" Payload: IGNORED"); + } + printk("\n"); + } +} + +//=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//When messages from the class CFG are sent to the receiver, the receiver will send an "acknowledge"(UBX - ACK - ACK) or a +//"not acknowledge"(UBX-ACK-NAK) message back to the sender, depending on whether or not the message was processed correctly. +//Some messages from other classes also use the same acknowledgement mechanism. + +//When we poll or get a setting, we will receive _both_ a config packet and an ACK +//If the poll or get request is not valid, we will receive _only_ a NACK + +//If we are trying to get or poll a setting, then packetCfg.len will be 0 or 1 when the packetCfg is _sent_. +//If we poll the setting for a particular port using UBX-CFG-PRT then .len will be 1 initially +//For all other gets or polls, .len will be 0 initially +//(It would be possible for .len to be 2 _if_ we were using UBX-CFG-MSG to poll the settings for a particular message - but we don't use that (currently)) + +//If the get or poll _fails_, i.e. is NACK'd, then packetCfg.len could still be 0 or 1 after the NACK is received +//But if the get or poll is ACK'd, then packetCfg.len will have been updated by the incoming data and will always be at least 2 + +//If we are going to set the value for a setting, then packetCfg.len will be at least 3 when the packetCfg is _sent_. +//(UBX-CFG-MSG appears to have the shortest set length of 3 bytes) + +//We need to think carefully about how interleaved PVT packets affect things. +//It is entirely possible that our packetCfg and packetAck were received successfully +//but while we are still in the "if (checkUblox() == true)" loop a PVT packet is processed +//or _starts_ to arrive (remember that Serial data can arrive very slowly). + +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got an ACK and a valid packetCfg (module is responding with register content) +//Returns SFE_UBLOX_STATUS_DATA_SENT if we got an ACK and no packetCfg (no valid packetCfg needed, module absorbs new register data) +//Returns SFE_UBLOX_STATUS_FAIL if something very bad happens (e.g. a double checksum failure) +//Returns SFE_UBLOX_STATUS_COMMAND_NACK if the packet was not-acknowledged (NACK) +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we had a checksum failure +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an ACK and a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = k_uptime_get_32(); + while (k_uptime_get_32() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: valid data and valid ACK received after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and a correct ACK! + } + + // We can be confident that the data packet (if we are going to get one) will always arrive + // before the matching ACK. So if we sent a config packet which only produces an ACK + // then outgoingUBX->classAndIDmatch will be NOT_DEFINED and the packetAck.classAndIDmatch will VALID. + // We should not check outgoingUBX->valid, outgoingUBX->cls or outgoingUBX->id + // as these may have been changed by a PVT packet. + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: no data and valid ACK after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_SENT); //We got an ACK but no data... + } + + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + printk("waitForACKResponse: data being OVERWRITTEN after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If packetAck.classAndIDmatch is VALID but both outgoingUBX->valid and outgoingUBX->classAndIDmatch + // are NOT_VALID then we can be confident we have had a checksum failure on the data packet + else if ((packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: CRC failed after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //Checksum fail + } + + // If our packet was not-acknowledged (NACK) we do not receive a data packet - we only get the NACK. + // So you would expect outgoingUBX->valid and outgoingUBX->classAndIDmatch to still be NOT_DEFINED + // But if a full PVT packet arrives afterwards outgoingUBX->valid could be VALID (or just possibly NOT_VALID) + // but outgoingUBX->cls and outgoingUBX->id would not match... + // So I think this is telling us we need a special state for packetAck.classAndIDmatch to tell us + // the packet was definitely NACK'd otherwise we are possibly just guessing... + // Note: the addition of packetBuf changes the logic of this, but we'll leave the code as is for now. + else if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_NOTACKNOWLEDGED) + { + if (_printDebug == true) + { + printk("waitForACKResponse: data was NOTACKNOWLEDGED (NACK) after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_COMMAND_NACK); //We received a NACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID but the packetAck.classAndIDmatch is NOT_VALID + // then the ack probably had a checksum error. We will take a gamble and return DATA_RECEIVED. + // If we were playing safe, we should return FAIL instead + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: VALID data and INVALID ACK received after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID and the packetAck.classAndIDmatch is NOT_VALID + // then we return a FAIL. This must be a double checksum failure? + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: INVALID data and INVALID ACK received after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_FAIL); //We received invalid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID and the packetAck.classAndIDmatch is NOT_DEFINED + // then the ACK has not yet been received and we should keep waiting for it + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: valid data after %lu msec. Waiting for ACK\n", k_uptime_get_32() - startTime); + } + } + + } //checkUbloxInternal == true + + k_usleep(500); + } //while (k_uptime_get_32() - startTime < maxTime) + + // We have timed out... + // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED + // even though we did not get an ACK + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: TIMEOUT with valid data after %lu msec.\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data... But no ACK! + } + + if (_printDebug == true) + { + printk("waitForACKResponse: TIMEOUT after %lu msec.\n", k_uptime_get_32() - startTime); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//For non-CFG queries no ACK is sent so we use this function +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we got a corrupt config packet that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = k_uptime_get_32(); + while (k_uptime_get_32() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + + // If outgoingUBX->classAndIDmatch is VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: valid data with CLS/ID match after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data! + } + + // If the outgoingUBX->classAndIDmatch is VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: data being OVERWRITTEN after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If outgoingUBX->classAndIDmatch is NOT_DEFINED + // and outgoingUBX->valid is VALID then this must be (e.g.) a PVT packet + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: valid but UNWANTED data after %lu msec. Class: %d ID: %d\n", k_uptime_get_32() - startTime, outgoingUBX->cls, outgoingUBX->id); + } + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID then we return CRC failure + else if (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: CLS/ID match but failed CRC after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //We received invalid data + } + } + + k_usleep(500); + } + + if (_printDebug == true) + { + printk("waitForNoACKResponse: TIMEOUT after %lu msec. No packet received.\n", k_uptime_get_32() - startTime); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//Save current configuration to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +bool SFE_UBLOX_GPS::saveConfiguration(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = 0xFF; //Set any bit in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save the selected configuration sub-sections to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +bool SFE_UBLOX_GPS::saveConfigSelective(uint32_t configMask, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = configMask & 0xFF; //Set the appropriate bits in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = (configMask >> 8) & 0xFF; + packetCfg.payload[6] = (configMask >> 16) & 0xFF; + packetCfg.payload[7] = (configMask >> 24) & 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Reset module to factory defaults +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +bool SFE_UBLOX_GPS::factoryDefault(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[0] = 0xFF; //Set any bit in the clearMask field to clear saved config + packetCfg.payload[1] = 0xFF; + packetCfg.payload[8] = 0xFF; //Set any bit in the loadMask field to discard current config and rebuild from lower non-volatile memory layers + packetCfg.payload[9] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a group, ID and size, return the value of this config spot +//The 32-bit key is put together from group/ID/size. See other getVal to send key directly. +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + //Create key + uint32_t key = 0; + key |= (uint32_t)id; + key |= (uint32_t)group << 16; + key |= (uint32_t)size << 28; + + if (_printDebug == true) + { + printk("key: 0x%x\n", key); + } + + return getVal8(key, layer, maxWait); +} + +//Given a key, return its value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALGET; + packetCfg.len = 4 + 4 * 1; //While multiple keys are allowed, we will send only one key at a time + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //VALGET uses different memory layer definitions to VALSET + //because it can only return the value for one layer. + //So we need to fiddle the layer here. + //And just to complicate things further, the ZED-F9P only responds + //correctly to layer 0 (RAM) and layer 7 (Default)! + uint8_t getLayer = 7; // 7 is the "Default Layer" + if ((layer & VAL_LAYER_RAM) == VAL_LAYER_RAM) // Did the user request the RAM layer? + { + getLayer = 0; // Layer 0 is RAM + } + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = getLayer; //Layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + if (_printDebug == true) + { + printk("key: 0x%x\n", key); + } + + //Send VALGET command with this key + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + printk("getVal8: sendCommand returned: %s\n", statusString(retVal)); + } + if (retVal != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (0); //If command send fails then bail + + //Verify the response is the correct length as compared to what the user called (did the module respond with 8-bits but the user called getVal32?) + //Response is 8 bytes plus cfg data + //if(packet->len > 8+1) + + //Pull the requested value from the response + //Response starts at 4+1*N with the 32-bit key so the actual data we're looking for is at 8+1*N + return (extractByte(8)); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + return setVal16(key, value, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal16(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set an 8-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal8(uint32_t key, uint8_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set a 32-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal32(uint32_t key, uint32_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset32(uint32_t key, uint32_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset16(uint32_t key, uint16_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +//Default layer is BBR +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset8(uint32_t key, uint8_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset32(uint32_t key, uint32_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + payloadCfg[packetCfg.len + 6] = value >> 8 * 2; + payloadCfg[packetCfg.len + 7] = value >> 8 * 3; + + //Update packet length: 4 byte key ID, 4 bytes of value + packetCfg.len = packetCfg.len + 4 + 4; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset16(uint32_t key, uint16_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + + //Update packet length: 4 byte key ID, 2 bytes of value + packetCfg.len = packetCfg.len + 4 + 2; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset8(uint32_t key, uint8_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value; //Value + + //Update packet length: 4 byte key ID, 1 byte value + packetCfg.len = packetCfg.len + 4 + 1; + + //All done + return (true); +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset32(uint32_t key, uint32_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset32(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset16(uint32_t key, uint16_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset16(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset8(uint32_t key, uint8_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset8(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the current TimeMode3 settings - these contain survey in statuses +bool SFE_UBLOX_GPS::getSurveyMode(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Control Survey-In for NEO-M8P +bool SFE_UBLOX_GPS::setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + if (getSurveyMode(maxWait) == false) //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 40; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //payloadCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = mode; //Set mode. Survey-In and Disabled are most common. Use ECEF (not LAT/LON/ALT). + + //svinMinDur is U4 (uint32_t) but we'll only use a uint16_t (waiting more than 65535 seconds seems excessive!) + payloadCfg[24] = observationTime & 0xFF; //svinMinDur in seconds + payloadCfg[25] = observationTime >> 8; //svinMinDur in seconds + payloadCfg[26] = 0; //Truncate to 16 bits + payloadCfg[27] = 0; //Truncate to 16 bits + + //svinAccLimit is U4 (uint32_t) in 0.1mm. + uint32_t svinAccLimit = (uint32_t)(requiredAccuracy * 10000.0); //Convert m to 0.1mm + payloadCfg[28] = svinAccLimit & 0xFF; //svinAccLimit in 0.1mm increments + payloadCfg[29] = svinAccLimit >> 8; + payloadCfg[30] = svinAccLimit >> 16; + payloadCfg[31] = svinAccLimit >> 24; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Begin Survey-In for NEO-M8P +bool SFE_UBLOX_GPS::enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_ENABLE, observationTime, requiredAccuracy, maxWait)); +} + +//Stop Survey-In for NEO-M8P +bool SFE_UBLOX_GPS::disableSurveyMode(uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_DISABLE, 0, 0, maxWait)); +} + +//Reads survey in status and sets the global variables +//for status, position valid, observation time, and mean 3D StdDev +//Returns true if commands was successful +bool SFE_UBLOX_GPS::getSurveyStatus(uint16_t maxWait) +{ + //Reset variables + svin.active = false; + svin.valid = false; + svin.observationTime = 0; + svin.meanAccuracy = 0; + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SVIN; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if ((sendCommand(&packetCfg, maxWait)) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //We got a response, now parse the bits into the svin structure + + //dur (Passed survey-in observation time) is U4 (uint32_t) seconds. We truncate to 16 bits + //(waiting more than 65535 seconds (18.2 hours) seems excessive!) + uint32_t tmpObsTime = extractLong(8); + if (tmpObsTime <= 0xFFFF) + { + svin.observationTime = (uint16_t)tmpObsTime; + } + else + { + svin.observationTime = 0xFFFF; + } + + // meanAcc is U4 (uint32_t) in 0.1mm. We convert this to float. + uint32_t tempFloat = extractLong(28); + svin.meanAccuracy = ((float)tempFloat) / 10000.0; //Convert 0.1mm to m + + svin.valid = payloadCfg[36]; //1 if survey-in position is valid, 0 otherwise + svin.active = payloadCfg[37]; //1 if survey-in in progress, 0 otherwise + + return (true); +} + +//Loads the payloadCfg array with the current protocol bits located the UBX-CFG-PRT register for a given port +bool SFE_UBLOX_GPS::getPortSettings(uint8_t portID, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 1; + packetCfg.startingSpot = 0; + + payloadCfg[0] = portID; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +bool SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[14] = outStreamSettings; //OutProtocolMask LSB - Set outStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +bool SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + //This will load the payloadCfg array with current port settings + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[12] = inStreamSettings; //InProtocolMask LSB - Set inStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a port to output UBX, NMEA, RTCM3 or a combination thereof +bool SFE_UBLOX_GPS::setI2COutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_I2C, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setUART1Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART1, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setUART2Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART2, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setUSBOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_USB, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setSPIOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_SPI, comSettings, maxWait)); +} + +//Set the rate at which the module will give us an updated navigation solution +//Expects a number that is the updates per second. For example 1 = 1Hz, 2 = 2Hz, etc. +//Max is 40Hz(?!) +bool SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) +{ + //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values + + //Adjust the I2C polling timeout based on update rate + i2cPollingWait = 1000 / (navFreq * 4); //This is the number of ms to wait between checks for new I2C data + + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 1000 / navFreq; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[0] = measurementRate & 0xFF; //measRate LSB + payloadCfg[1] = measurementRate >> 8; //measRate MSB + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the rate at which the module is outputting nav solutions +uint8_t SFE_UBLOX_GPS::getNavigationFrequency(uint16_t maxWait) +{ + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 0; + + //payloadCfg is now loaded with current bytes. Get what we need + measurementRate = extractInt(0); //Pull from payloadCfg at measRate LSB + + measurementRate = 1000 / measurementRate; //This may return an int when it's a float, but I'd rather not return 4 bytes + return (measurementRate); +} + +//In case no config access to the GPS is possible and PVT is send cyclically already +//set config to suitable parameters +bool SFE_UBLOX_GPS::assumeAutoPVT(bool enabled, bool implicitUpdate) +{ + bool changes = autoPVT != enabled || autoPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoPVT = enabled; + autoPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +bool SFE_UBLOX_GPS::setAutoPVT(bool enable, uint16_t maxWait) +{ + return setAutoPVT(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +bool SFE_UBLOX_GPS::setAutoPVT(bool enable, bool implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + bool ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoPVT = enable; + autoPVTImplicitUpdate = implicitUpdate; + } + moduleQueried.all = false; + return ok; +} + +//Configure a given message type for a given port (UART1, I2C, SPI, etc) +bool SFE_UBLOX_GPS::configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + //Poll for the current settings for a given message + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + payloadCfg[0] = msgClass; + payloadCfg[1] = msgID; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //Now send it back with new mods + packetCfg.len = 8; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[2 + portID] = sendRate; //Send rate is relative to the event a message is registered on. For example, if the rate of a navigation message is set to 2, the message is sent every 2nd navigation solution. + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable a given message type, default of 1 per update rate (usually 1 per second) +bool SFE_UBLOX_GPS::enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, rate, maxWait)); +} +//Disable a given message type on a given port +bool SFE_UBLOX_GPS::disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, 0, maxWait)); +} + +bool SFE_UBLOX_GPS::enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(UBX_CLASS_NMEA, msgID, portID, rate, maxWait)); +} +bool SFE_UBLOX_GPS::disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (enableNMEAMessage(msgID, portID, 0, maxWait)); +} + +//Given a message number turns on a message ID for output over a given portID (UART, I2C, SPI, USB, etc) +//To disable a message, set secondsBetween messages to 0 +//Note: This function will return false if the message is already enabled +//For base station RTK output we need to enable various sentences + +//NEO-M8P has four: +//1005 = 0xF5 0x05 - Stationary RTK reference ARP +//1077 = 0xF5 0x4D - GPS MSM7 +//1087 = 0xF5 0x57 - GLONASS MSM7 +//1230 = 0xF5 0xE6 - GLONASS code-phase biases, set to once every 10 seconds + +//ZED-F9P has six: +//1005, 1074, 1084, 1094, 1124, 1230 + +//Much of this configuration is not documented and instead discerned from u-center binary console +bool SFE_UBLOX_GPS::enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + return (configureMessage(UBX_RTCM_MSB, messageNumber, portID, sendRate, maxWait)); +} + +//Disable a given message on a given port by setting secondsBetweenMessages to zero +bool SFE_UBLOX_GPS::disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait) +{ + return (enableRTCMmessage(messageNumber, portID, 0, maxWait)); +} + +//Add a new geofence using UBX-CFG-GEOFENCE +bool SFE_UBLOX_GPS::addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, uint8_t confidence, uint8_t pinPolarity, uint8_t pin, uint16_t maxWait) +{ + if (currentGeofenceParams.numFences >= 4) + return (false); // Quit if we already have four geofences defined + + // Store the new geofence parameters + currentGeofenceParams.lats[currentGeofenceParams.numFences] = latitude; + currentGeofenceParams.longs[currentGeofenceParams.numFences] = longitude; + currentGeofenceParams.rads[currentGeofenceParams.numFences] = radius; + currentGeofenceParams.numFences = currentGeofenceParams.numFences + 1; // Increment the number of fences + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = (currentGeofenceParams.numFences * 12) + 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = currentGeofenceParams.numFences; // numFences + payloadCfg[2] = confidence; // confLvl = Confidence level 0-4 (none, 68%, 95%, 99.7%, 99.99%) + payloadCfg[3] = 0; // reserved1 + if (pin > 0) + { + payloadCfg[4] = 1; // enable PIO combined fence state + } + else + { + payloadCfg[4] = 0; // disable PIO combined fence state + } + payloadCfg[5] = pinPolarity; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = pin; // PIO pin + payloadCfg[7] = 0; //reserved2 + payloadCfg[8] = currentGeofenceParams.lats[0] & 0xFF; + payloadCfg[9] = currentGeofenceParams.lats[0] >> 8; + payloadCfg[10] = currentGeofenceParams.lats[0] >> 16; + payloadCfg[11] = currentGeofenceParams.lats[0] >> 24; + payloadCfg[12] = currentGeofenceParams.longs[0] & 0xFF; + payloadCfg[13] = currentGeofenceParams.longs[0] >> 8; + payloadCfg[14] = currentGeofenceParams.longs[0] >> 16; + payloadCfg[15] = currentGeofenceParams.longs[0] >> 24; + payloadCfg[16] = currentGeofenceParams.rads[0] & 0xFF; + payloadCfg[17] = currentGeofenceParams.rads[0] >> 8; + payloadCfg[18] = currentGeofenceParams.rads[0] >> 16; + payloadCfg[19] = currentGeofenceParams.rads[0] >> 24; + if (currentGeofenceParams.numFences >= 2) + { + payloadCfg[20] = currentGeofenceParams.lats[1] & 0xFF; + payloadCfg[21] = currentGeofenceParams.lats[1] >> 8; + payloadCfg[22] = currentGeofenceParams.lats[1] >> 16; + payloadCfg[23] = currentGeofenceParams.lats[1] >> 24; + payloadCfg[24] = currentGeofenceParams.longs[1] & 0xFF; + payloadCfg[25] = currentGeofenceParams.longs[1] >> 8; + payloadCfg[26] = currentGeofenceParams.longs[1] >> 16; + payloadCfg[27] = currentGeofenceParams.longs[1] >> 24; + payloadCfg[28] = currentGeofenceParams.rads[1] & 0xFF; + payloadCfg[29] = currentGeofenceParams.rads[1] >> 8; + payloadCfg[30] = currentGeofenceParams.rads[1] >> 16; + payloadCfg[31] = currentGeofenceParams.rads[1] >> 24; + } + if (currentGeofenceParams.numFences >= 3) + { + payloadCfg[32] = currentGeofenceParams.lats[2] & 0xFF; + payloadCfg[33] = currentGeofenceParams.lats[2] >> 8; + payloadCfg[34] = currentGeofenceParams.lats[2] >> 16; + payloadCfg[35] = currentGeofenceParams.lats[2] >> 24; + payloadCfg[36] = currentGeofenceParams.longs[2] & 0xFF; + payloadCfg[37] = currentGeofenceParams.longs[2] >> 8; + payloadCfg[38] = currentGeofenceParams.longs[2] >> 16; + payloadCfg[39] = currentGeofenceParams.longs[2] >> 24; + payloadCfg[40] = currentGeofenceParams.rads[2] & 0xFF; + payloadCfg[41] = currentGeofenceParams.rads[2] >> 8; + payloadCfg[42] = currentGeofenceParams.rads[2] >> 16; + payloadCfg[43] = currentGeofenceParams.rads[2] >> 24; + } + if (currentGeofenceParams.numFences >= 4) + { + payloadCfg[44] = currentGeofenceParams.lats[3] & 0xFF; + payloadCfg[45] = currentGeofenceParams.lats[3] >> 8; + payloadCfg[46] = currentGeofenceParams.lats[3] >> 16; + payloadCfg[47] = currentGeofenceParams.lats[3] >> 24; + payloadCfg[48] = currentGeofenceParams.longs[3] & 0xFF; + payloadCfg[49] = currentGeofenceParams.longs[3] >> 8; + payloadCfg[50] = currentGeofenceParams.longs[3] >> 16; + payloadCfg[51] = currentGeofenceParams.longs[3] >> 24; + payloadCfg[52] = currentGeofenceParams.rads[3] & 0xFF; + payloadCfg[53] = currentGeofenceParams.rads[3] >> 8; + payloadCfg[54] = currentGeofenceParams.rads[3] >> 16; + payloadCfg[55] = currentGeofenceParams.rads[3] >> 24; + } + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear all geofences using UBX-CFG-GEOFENCE +bool SFE_UBLOX_GPS::clearGeofences(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = 0; // numFences + payloadCfg[2] = 0; // confLvl + payloadCfg[3] = 0; // reserved1 + payloadCfg[4] = 0; // disable PIO combined fence state + payloadCfg[5] = 0; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = 0; // PIO pin + payloadCfg[7] = 0; //reserved2 + + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear the antenna control settings using UBX-CFG-ANT +//This function is hopefully redundant but may be needed to release +//any PIO pins pre-allocated for antenna functions +bool SFE_UBLOX_GPS::clearAntPIO(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_ANT; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x10; // Antenna flag mask: set the recovery bit + payloadCfg[1] = 0; + payloadCfg[2] = 0xFF; // Antenna pin configuration: set pinSwitch and pinSCD to 31 + payloadCfg[3] = 0xFF; // Antenna pin configuration: set pinOCD to 31, set reconfig bit + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Returns the combined geofence state using UBX-NAV-GEOFENCE +bool SFE_UBLOX_GPS::getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_GEOFENCE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the geofence status. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + currentGeofenceState.status = payloadCfg[5]; // Extract the status + currentGeofenceState.numFences = payloadCfg[6]; // Extract the number of geofences + currentGeofenceState.combState = payloadCfg[7]; // Extract the combined state of all geofences + if (currentGeofenceState.numFences > 0) + currentGeofenceState.states[0] = payloadCfg[8]; // Extract geofence 1 state + if (currentGeofenceState.numFences > 1) + currentGeofenceState.states[1] = payloadCfg[10]; // Extract geofence 2 state + if (currentGeofenceState.numFences > 2) + currentGeofenceState.states[2] = payloadCfg[12]; // Extract geofence 3 state + if (currentGeofenceState.numFences > 3) + currentGeofenceState.states[3] = payloadCfg[14]; // Extract geofence 4 state + + return (true); +} + +//Power Save Mode +//Enables/Disables Low Power Mode using UBX-CFG-RXM +bool SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + printk"(Protocol version is %d\n", protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + printk("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version\n"); + } + return (false); + } + + // Now let's change the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + if (power_save) + { + payloadCfg[1] = 1; // Power Save Mode + } + else + { + payloadCfg[1] = 0; // Continuous Mode + } + + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// Get Power Save Mode +// Returns the current Low Power Mode using UBX-CFG-RXM +// Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getPowerSaveMode(uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + printk("Protocol version is %d\n", protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + printk("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version\n"); + } + return (255); + } + + // Now let's read the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[1]); // Return the low power mode +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +bool SFE_UBLOX_GPS::powerOff(uint32_t durationInMs, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + printk("Powering off for %d ms\n", durationInMs); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + // duration + // big endian to little endian, switch byte order + payloadCfg[0] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[1] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[2] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[3] = (durationInMs >> (8 * 3)) & 0xff; + + payloadCfg[4] = 0x02; //Flags : set the backup bit + payloadCfg[5] = 0x00; //Flags + payloadCfg[6] = 0x00; //Flags + payloadCfg[7] = 0x00; //Flags + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// While powered off it can be woken up by creating a falling or rising voltage edge on the specified pin. +// NOTE: The GPS seems to be sensitve to signals on the pins while powered off. Works best when Microcontroller is in deepsleep. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +bool SFE_UBLOX_GPS::powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources, bool forceWhileUsb, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + printk("Powering off for %d ms\n", durationInMs); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 16; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x00; // message version + + // bytes 1-3 are reserved - and must be set to zero + payloadCfg[1] = 0x00; + payloadCfg[2] = 0x00; + payloadCfg[3] = 0x00; + + // duration + // big endian to little endian, switch byte order + payloadCfg[4] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[5] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[6] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[7] = (durationInMs >> (8 * 3)) & 0xff; + + // flags + + // disables USB interface when powering off, defaults to true + if (forceWhileUsb) + { + payloadCfg[8] = 0x06; // force | backup + } + else + { + payloadCfg[8] = 0x02; // backup only (leave the force bit clear - module will stay on if USB is connected) + } + + payloadCfg[9] = 0x00; + payloadCfg[10] = 0x00; + payloadCfg[11] = 0x00; + + // wakeUpSources + + // wakeupPin mapping, defaults to VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + + // Possible values are: + // VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 + // VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS + + payloadCfg[12] = (wakeupSources >> (8 * 0)) & 0xff; + payloadCfg[13] = (wakeupSources >> (8 * 1)) & 0xff; + payloadCfg[14] = (wakeupSources >> (8 * 2)) & 0xff; + payloadCfg[15] = (wakeupSources >> (8 * 3)) & 0xff; + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +//Change the dynamic platform model using UBX-CFG-NAV5 +//Possible values are: +//PORTABLE,STATIONARY,PEDESTRIAN,AUTOMOTIVE,SEA, +//AIRBORNE1g,AIRBORNE2g,AIRBORNE4g,WRIST,BIKE +//WRIST is not supported in protocol versions less than 18 +//BIKE is supported in protocol versions 19.2 +bool SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) + payloadCfg[1] = 0x00; // mask + payloadCfg[2] = newDynamicModel; // dynModel + + packetCfg.len = 36; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the dynamic platform model using UBX-CFG-NAV5 +//Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getDynamicModel(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[2]); // Return the dynamic model +} + +//Given a spot in the payload array, extract four bytes and build a long +uint32_t SFE_UBLOX_GPS::extractLong(uint8_t spotToStart) +{ + uint32_t val = 0; + val |= (uint32_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint32_t)payloadCfg[spotToStart + 1] << 8 * 1; + val |= (uint32_t)payloadCfg[spotToStart + 2] << 8 * 2; + val |= (uint32_t)payloadCfg[spotToStart + 3] << 8 * 3; + return (val); +} + +//Given a spot in the payload array, extract two bytes and build an int +uint16_t SFE_UBLOX_GPS::extractInt(uint8_t spotToStart) +{ + uint16_t val = 0; + val |= (uint16_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint16_t)payloadCfg[spotToStart + 1] << 8 * 1; + return (val); +} + +//Given a spot, extract a byte from the payload +uint8_t SFE_UBLOX_GPS::extractByte(uint8_t spotToStart) +{ + return (payloadCfg[spotToStart]); +} + +//Given a spot, extract a signed 8-bit value from the payload +int8_t SFE_UBLOX_GPS::extractSignedChar(uint8_t spotToStart) +{ + return ((int8_t)payloadCfg[spotToStart]); +} + +//Get the current year +uint16_t SFE_UBLOX_GPS::getYear(uint16_t maxWait) +{ + if (moduleQueried.gpsYear == false) + getPVT(maxWait); + moduleQueried.gpsYear = false; //Since we are about to give this to user, mark this data as stale + return (gpsYear); +} + +//Get the current month +uint8_t SFE_UBLOX_GPS::getMonth(uint16_t maxWait) +{ + if (moduleQueried.gpsMonth == false) + getPVT(maxWait); + moduleQueried.gpsMonth = false; //Since we are about to give this to user, mark this data as stale + return (gpsMonth); +} + +//Get the current day +uint8_t SFE_UBLOX_GPS::getDay(uint16_t maxWait) +{ + if (moduleQueried.gpsDay == false) + getPVT(maxWait); + moduleQueried.gpsDay = false; //Since we are about to give this to user, mark this data as stale + return (gpsDay); +} + +//Get the current hour +uint8_t SFE_UBLOX_GPS::getHour(uint16_t maxWait) +{ + if (moduleQueried.gpsHour == false) + getPVT(maxWait); + moduleQueried.gpsHour = false; //Since we are about to give this to user, mark this data as stale + return (gpsHour); +} + +//Get the current minute +uint8_t SFE_UBLOX_GPS::getMinute(uint16_t maxWait) +{ + if (moduleQueried.gpsMinute == false) + getPVT(maxWait); + moduleQueried.gpsMinute = false; //Since we are about to give this to user, mark this data as stale + return (gpsMinute); +} + +//Get the current second +uint8_t SFE_UBLOX_GPS::getSecond(uint16_t maxWait) +{ + if (moduleQueried.gpsSecond == false) + getPVT(maxWait); + moduleQueried.gpsSecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsSecond); +} + +//Get the current date validity +bool SFE_UBLOX_GPS::getDateValid(uint16_t maxWait) +{ + if (moduleQueried.gpsDateValid == false) + getPVT(maxWait); + moduleQueried.gpsDateValid = false; //Since we are about to give this to user, mark this data as stale + return (gpsDateValid); +} + +//Get the current time validity +bool SFE_UBLOX_GPS::getTimeValid(uint16_t maxWait) +{ + if (moduleQueried.gpsTimeValid == false) + getPVT(maxWait); + moduleQueried.gpsTimeValid = false; //Since we are about to give this to user, mark this data as stale + return (gpsTimeValid); +} + +//Get the current millisecond +uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) +{ + if (moduleQueried.gpsiTOW == false) + getPVT(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (gpsMillisecond); +} + +//Get the current nanoseconds - includes milliseconds +int32_t SFE_UBLOX_GPS::getNanosecond(uint16_t maxWait) +{ + if (moduleQueried.gpsNanosecond == false) + getPVT(maxWait); + moduleQueried.gpsNanosecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsNanosecond); +} + +//Get the latest Position/Velocity/Time solution and fill all global variables +bool SFE_UBLOX_GPS::getPVT(uint16_t maxWait) +{ + if (autoPVT && autoPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + printk("getPVT: Autoreporting\n"); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_PVT); + return moduleQueried.all; + } + else if (autoPVT && !autoPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + printk("getPVT: Exit immediately\n"); + } + return (false); + } + else + { + if (_printDebug == true) + { + printk("getPVT: Polling\n"); + } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_PVT; + packetCfg.len = 0; + //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+MAX_PAYLOAD_SIZE = 84 bytes Note:now hard-coded in processUBX + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + printk("getPVT retVal: %s\n", statusString(retVal)); + } + return (false); + } +} + +uint32_t SFE_UBLOX_GPS::getTimeOfWeek(uint16_t maxWait /* = 250*/) +{ + if (moduleQueried.gpsiTOW == false) + getPVT(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (timeOfWeek); +} + +int32_t SFE_UBLOX_GPS::getHighResLatitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLatitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitudeHp); +} + +int32_t SFE_UBLOX_GPS::getHighResLongitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLongitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitudeHp); +} + +int32_t SFE_UBLOX_GPS::getElipsoid(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoid == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoid = false; //Since we are about to give this to user, mark this data as stale + return (elipsoid); +} + +int8_t SFE_UBLOX_GPS::getElipsoidHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoidHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoidHp = false; //Since we are about to give this to user, mark this data as stale + return (elipsoidHp); +} + +int32_t SFE_UBLOX_GPS::getMeanSeaLevel(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevel == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevel = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevel); +} + +int8_t SFE_UBLOX_GPS::getMeanSeaLevelHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevelHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevelHp = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevelHp); +} + +// getGeoidSeparation is currently redundant. The geoid separation seems to only be provided in NMEA GGA and GNS messages. +int32_t SFE_UBLOX_GPS::getGeoidSeparation(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.geoidSeparation == false) + getHPPOSLLH(maxWait); + highResModuleQueried.geoidSeparation = false; //Since we are about to give this to user, mark this data as stale + return (geoidSeparation); +} + +uint32_t SFE_UBLOX_GPS::getHorizontalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.horizontalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.horizontalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (horizontalAccuracy); +} + +uint32_t SFE_UBLOX_GPS::getVerticalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.verticalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.verticalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (verticalAccuracy); +} + +bool SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) +{ + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSLLH; + packetCfg.len = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are only expecting data (no ACK) +} + +//Get the current 3D high precision positional accuracy - a fun thing to watch +//Returns a long representing the 3D accuracy in millimeters +uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSECEF; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (0); //If command send fails then bail + + uint32_t tempAccuracy = extractLong(24); //We got a response, now extract a long beginning at a given position + + if ((tempAccuracy % 10) >= 5) + tempAccuracy += 5; //Round fraction of mm up to next mm if .5 or above + tempAccuracy /= 10; //Convert 0.1mm units to mm + + return (tempAccuracy); +} + +//Get the current latitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLatitude(uint16_t maxWait) +{ + if (moduleQueried.latitude == false) + getPVT(maxWait); + moduleQueried.latitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (latitude); +} + +//Get the current longitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLongitude(uint16_t maxWait) +{ + if (moduleQueried.longitude == false) + getPVT(maxWait); + moduleQueried.longitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (longitude); +} + +//Get the current altitude in mm according to ellipsoid model +int32_t SFE_UBLOX_GPS::getAltitude(uint16_t maxWait) +{ + if (moduleQueried.altitude == false) + getPVT(maxWait); + moduleQueried.altitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitude); +} + +//Get the current altitude in mm according to mean sea level +//Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html +//Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ +int32_t SFE_UBLOX_GPS::getAltitudeMSL(uint16_t maxWait) +{ + if (moduleQueried.altitudeMSL == false) + getPVT(maxWait); + moduleQueried.altitudeMSL = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitudeMSL); +} + +//Get the number of satellites used in fix +uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) +{ + if (moduleQueried.SIV == false) + getPVT(maxWait); + moduleQueried.SIV = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (SIV); +} + +//Get the current fix type +//0=no fix, 1=dead reckoning, 2=2D, 3=3D, 4=GNSS, 5=Time fix +uint8_t SFE_UBLOX_GPS::getFixType(uint16_t maxWait) +{ + if (moduleQueried.fixType == false) + { + getPVT(maxWait); + } + moduleQueried.fixType = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (fixType); +} + +//Get the carrier phase range solution status +//Useful when querying module to see if it has high-precision RTK fix +//0=No solution, 1=Float solution, 2=Fixed solution +uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) +{ + if (moduleQueried.carrierSolution == false) + getPVT(maxWait); + moduleQueried.carrierSolution = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (carrierSolution); +} + +//Get the ground speed in mm/s +int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) +{ + if (moduleQueried.groundSpeed == false) + getPVT(maxWait); + moduleQueried.groundSpeed = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (groundSpeed); +} + +//Get the heading of motion (as opposed to heading of car) in degrees * 10^-5 +int32_t SFE_UBLOX_GPS::getHeading(uint16_t maxWait) +{ + if (moduleQueried.headingOfMotion == false) + getPVT(maxWait); + moduleQueried.headingOfMotion = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headingOfMotion); +} + +//Get the positional dillution of precision * 10^-2 +uint16_t SFE_UBLOX_GPS::getPDOP(uint16_t maxWait) +{ + if (moduleQueried.pDOP == false) + getPVT(maxWait); + moduleQueried.pDOP = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (pDOP); +} + +//Get the current protocol version of the Ublox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionHigh(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionHigh); +} + +//Get the current protocol version of the Ublox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionLow(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionLow); +} + +//Get the current protocol version of the Ublox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +bool SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) +{ + //Send packet with only CLS and ID, length of zero. This will cause the module to respond with the contents of that CLS/ID. + packetCfg.cls = UBX_CLASS_MON; + packetCfg.id = UBX_MON_VER; + + packetCfg.len = 0; + packetCfg.startingSpot = 40; //Start at first "extended software information" string + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //Payload should now contain ~220 characters (depends on module type) + + // if (_printDebug == true) + // { + // printk("MON VER Payload:")); + // for (int location = 0; location < packetCfg.len; location++) + // { + // if (location % 30 == 0) + // printk("\n"); + // printk("%d", payloadCfg[location]); + // } + // printk("\n"); + // } + + //We will step through the payload looking at each extension field of 30 bytes + for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) + { + //Now we need to find "PROTVER=18.00" in the incoming byte stream + if (payloadCfg[(30 * extensionNumber) + 0] == 'P' && payloadCfg[(30 * extensionNumber) + 6] == 'R') + { + versionHigh = (payloadCfg[(30 * extensionNumber) + 8] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 9] - '0'); //Convert '18' to 18 + versionLow = (payloadCfg[(30 * extensionNumber) + 11] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 12] - '0'); //Convert '00' to 00 + moduleQueried.versionNumber = true; //Mark this data as new + + if (_printDebug == true) + { + printk("Protocol version: %d.%d\n", versionHigh, versionLow); + } + return (true); //Success! + } + } + + return (false); //We failed +} + +//Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushPVT() +{ + //Mark all datums as stale (read before) + moduleQueried.gpsiTOW = false; + moduleQueried.gpsYear = false; + moduleQueried.gpsMonth = false; + moduleQueried.gpsDay = false; + moduleQueried.gpsHour = false; + moduleQueried.gpsMinute = false; + moduleQueried.gpsSecond = false; + moduleQueried.gpsDateValid = false; + moduleQueried.gpsTimeValid = false; + moduleQueried.gpsNanosecond = false; + + moduleQueried.all = false; + moduleQueried.longitude = false; + moduleQueried.latitude = false; + moduleQueried.altitude = false; + moduleQueried.altitudeMSL = false; + moduleQueried.SIV = false; + moduleQueried.fixType = false; + moduleQueried.carrierSolution = false; + moduleQueried.groundSpeed = false; + moduleQueried.headingOfMotion = false; + moduleQueried.pDOP = false; +} + +//Relative Positioning Information in NED frame +//Returns true if commands was successful +bool SFE_UBLOX_GPS::getRELPOSNED(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RELPOSNED; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //We got a response, now parse the bits + + uint16_t refStationID = extractInt(2); + //printk("refStationID: %d\n", refStationID); + + int32_t tempRelPos; + + tempRelPos = extractLong(8); + relPosInfo.relPosN = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(12); + relPosInfo.relPosE = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(16); + relPosInfo.relPosD = tempRelPos / 100.0; //Convert cm to m + + relPosInfo.relPosLength = extractLong(20); + relPosInfo.relPosHeading = extractLong(24); + + relPosInfo.relPosHPN = payloadCfg[32]; + relPosInfo.relPosHPE = payloadCfg[33]; + relPosInfo.relPosHPD = payloadCfg[34]; + relPosInfo.relPosHPLength = payloadCfg[35]; + + uint32_t tempAcc; + + tempAcc = extractLong(36); + relPosInfo.accN = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(40); + relPosInfo.accE = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(44); + relPosInfo.accD = tempAcc / 10000.0; //Convert 0.1 mm to m + + uint8_t flags = payloadCfg[60]; + + relPosInfo.gnssFixOk = flags & (1 << 0); + relPosInfo.diffSoln = flags & (1 << 1); + relPosInfo.relPosValid = flags & (1 << 2); + relPosInfo.carrSoln = (flags & (0b11 << 3)) >> 3; + relPosInfo.isMoving = flags & (1 << 5); + relPosInfo.refPosMiss = flags & (1 << 6); + relPosInfo.refObsMiss = flags & (1 << 7); + + return (true); +} +bool SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) +{ + // Requesting Data from the receiver + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // payload should be loaded. + imuMeas.version = extractByte(4); + imuMeas.fusionMode = extractByte(12); + ubloxSen.numSens = extractByte(15); + + // Individual Status Sensor in different function + return (true); +} + +// +bool SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_INS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // Validity of each sensor value below + uint32_t validity = extractLong(0); + + imuMeas.xAngRateVald = (validity && 0x0080) >> 8; + imuMeas.yAngRateVald = (validity && 0x0100) >> 9; + imuMeas.zAngRateVald = (validity && 0x0200) >> 10; + imuMeas.xAccelVald = (validity && 0x0400) >> 11; + imuMeas.yAccelVald = (validity && 0x0800) >> 12; + imuMeas.zAccelVald = (validity && 0x1000) >> 13; + + imuMeas.xAngRate = extractLong(12); // deg/s + imuMeas.yAngRate = extractLong(16); // deg/s + imuMeas.zAngRate = extractLong(20); // deg/s + + imuMeas.xAccel = extractLong(24); // m/s + imuMeas.yAccel = extractLong(28); // m/s + imuMeas.zAccel = extractLong(32); // m/s + + return (true); +} + +// +bool SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_MEAS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t timeStamp = extractLong(0); + uint32_t flags = extractInt(4); + + uint8_t timeSent = (flags && 0x01) >> 1; + uint8_t timeEdge = (flags && 0x02) >> 2; + uint8_t tagValid = (flags && 0x04) >> 3; + uint8_t numMeas = (flags && 0x1000) >> 15; + + if (numMeas > DEF_NUM_SENS) + numMeas = DEF_NUM_SENS; + + uint8_t byteOffset = 4; + + for (uint8_t i = 0; i < numMeas; i++) + { + + uint32_t bitField = extractLong(4 + byteOffset * i); + imuMeas.dataType[i] = (bitField && 0xFF000000) >> 23; + imuMeas.data[i] = (bitField && 0xFFFFFF); + imuMeas.dataTStamp[i] = extractLong(8 + byteOffset * i); + } + + return (true); +} + +bool SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) +{ + + // Need to know the number of sensor to get the correct data + // Rate selected in UBX-CFG-MSG is not respected + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RAW; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t bitField = extractLong(4); + imuMeas.rawDataType = (bitField && 0xFF000000) >> 23; + imuMeas.rawData = (bitField && 0xFFFFFF); + imuMeas.rawTStamp = extractLong(8); + + return (true); +} + +sfe_ublox_status_e SFE_UBLOX_GPS::getSensState(uint8_t sensor, uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + ubloxSen.numSens = extractByte(15); + + if (sensor > ubloxSen.numSens) + return (SFE_UBLOX_STATUS_OUT_OF_RANGE); + + checkUblox(); + + uint8_t offset = 4; + + // Only the last sensor value checked will remain. + for (uint8_t i = 0; i < sensor; i++) + { + + uint8_t sensorFieldOne = extractByte(16 + offset * i); + uint8_t sensorFieldTwo = extractByte(17 + offset * i); + ubloxSen.freq = extractByte(18 + offset * i); + uint8_t sensorFieldThr = extractByte(19 + offset * i); + + ubloxSen.senType = (sensorFieldOne && 0x10) >> 5; + ubloxSen.isUsed = (sensorFieldOne && 0x20) >> 6; + ubloxSen.isReady = (sensorFieldOne && 0x30) >> 7; + + ubloxSen.calibStatus = sensorFieldTwo && 0x03; + ubloxSen.timeStatus = (sensorFieldTwo && 0xC) >> 2; + + ubloxSen.badMeas = (sensorFieldThr && 0x01); + ubloxSen.badTag = (sensorFieldThr && 0x02) >> 1; + ubloxSen.missMeas = (sensorFieldThr && 0x04) >> 2; + ubloxSen.noisyMeas = (sensorFieldThr && 0x08) >> 3; + } + + return (SFE_UBLOX_STATUS_SUCCESS); +} + +bool SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_ATT; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + checkUblox(); + + vehAtt.roll = extractLong(8); + vehAtt.pitch = extractLong(12); + vehAtt.heading = extractLong(16); + + vehAtt.accRoll = extractLong(20); + vehAtt.accPitch = extractLong(24); + vehAtt.accHeading = extractLong(28); + + return (true); +} diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h new file mode 100644 index 0000000..7bd3958 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h @@ -0,0 +1,901 @@ +/* + This is a library written for the Ublox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a Ublox GPS module. Works with most modules from Ublox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + NCS v1.0.3 release + + This port was made by Vid Rajtmajer , IRNAS www.irnas.eu + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SPARKFUN_UBLOX_ZEPHYR_LIBRARY_H +#define SPARKFUN_UBLOX_ZEPHYR_LIBRARY_H + +#include +#include + +//The catch-all default is 32 +#define I2C_BUFFER_LENGTH 32 + +//Define pin states +#define LOW 0 +#define HIGH 1 + +//Define a digital pin to aid checksum failure capture and analysis +//Leave set to -1 if not needed +const int checksumFailurePin = -1; + +// Global Status Returns +typedef enum +{ + SFE_UBLOX_STATUS_SUCCESS, + SFE_UBLOX_STATUS_FAIL, + SFE_UBLOX_STATUS_CRC_FAIL, + SFE_UBLOX_STATUS_TIMEOUT, + SFE_UBLOX_STATUS_COMMAND_NACK, // Indicates that the command was unrecognised, invalid or that the module is too busy to respond + SFE_UBLOX_STATUS_OUT_OF_RANGE, + SFE_UBLOX_STATUS_INVALID_ARG, + SFE_UBLOX_STATUS_INVALID_OPERATION, + SFE_UBLOX_STATUS_MEM_ERR, + SFE_UBLOX_STATUS_HW_ERR, + SFE_UBLOX_STATUS_DATA_SENT, // This indicates that a 'set' was successful + SFE_UBLOX_STATUS_DATA_RECEIVED, // This indicates that a 'get' (poll) was successful + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, + SFE_UBLOX_STATUS_DATA_OVERWRITTEN // This is an error - the data was valid but has been or _is being_ overwritten by another packet +} sfe_ublox_status_e; + +// ubxPacket validity +typedef enum +{ + SFE_UBLOX_PACKET_VALIDITY_NOT_VALID, + SFE_UBLOX_PACKET_VALIDITY_VALID, + SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, + SFE_UBLOX_PACKET_NOTACKNOWLEDGED // This indicates that we received a NACK +} sfe_ublox_packet_validity_e; + +// Identify which packet buffer is in use: +// packetCfg (or a custom packet), packetAck or packetBuf +typedef enum +{ + SFE_UBLOX_PACKET_PACKETCFG, + SFE_UBLOX_PACKET_PACKETACK, + SFE_UBLOX_PACKET_PACKETBUF +} sfe_ublox_packet_buffer_e; + +//Registers +const uint8_t UBX_SYNCH_1 = 0xB5; +const uint8_t UBX_SYNCH_2 = 0x62; + +//The following are UBX Class IDs. Descriptions taken from ZED-F9P Interface Description Document page 32, NEO-M8P Interface Description page 145 +const uint8_t UBX_CLASS_NAV = 0x01; //Navigation Results Messages: Position, Speed, Time, Acceleration, Heading, DOP, SVs used +const uint8_t UBX_CLASS_RXM = 0x02; //Receiver Manager Messages: Satellite Status, RTC Status +const uint8_t UBX_CLASS_INF = 0x04; //Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice +const uint8_t UBX_CLASS_ACK = 0x05; //Ack/Nak Messages: Acknowledge or Reject messages to UBX-CFG input messages +const uint8_t UBX_CLASS_CFG = 0x06; //Configuration Input Messages: Configure the receiver. +const uint8_t UBX_CLASS_UPD = 0x09; //Firmware Update Messages: Memory/Flash erase/write, Reboot, Flash identification, etc. +const uint8_t UBX_CLASS_MON = 0x0A; //Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status +const uint8_t UBX_CLASS_AID = 0x0B; //(NEO-M8P ONLY!!!) AssistNow Aiding Messages: Ephemeris, Almanac, other A-GPS data input +const uint8_t UBX_CLASS_TIM = 0x0D; //Timing Messages: Time Pulse Output, Time Mark Results +const uint8_t UBX_CLASS_ESF = 0x10; //(NEO-M8P ONLY!!!) External Sensor Fusion Messages: External Sensor Measurements and Status Information +const uint8_t UBX_CLASS_MGA = 0x13; //Multiple GNSS Assistance Messages: Assistance data for various GNSS +const uint8_t UBX_CLASS_LOG = 0x21; //Logging Messages: Log creation, deletion, info and retrieval +const uint8_t UBX_CLASS_SEC = 0x27; //Security Feature Messages +const uint8_t UBX_CLASS_HNR = 0x28; //(NEO-M8P ONLY!!!) High Rate Navigation Results Messages: High rate time, position speed, heading +const uint8_t UBX_CLASS_NMEA = 0xF0; //NMEA Strings: standard NMEA strings + +//The following are used for configuration. Descriptions are from the ZED-F9P Interface Description pg 33-34 and NEO-M9N Interface Description pg 47-48 +const uint8_t UBX_CFG_ANT = 0x13; //Antenna Control Settings. Used to configure the antenna control settings +const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. +const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration +const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum +const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence +const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration +const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration +const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration +const uint8_t UBX_CFG_MSG = 0x01; //Poll a message configuration, or Set Message Rate(s), or Set Message Rate +const uint8_t UBX_CFG_NAV5 = 0x24; //Navigation Engine Settings. Used to configure the navigation engine including the dynamic model. +const uint8_t UBX_CFG_NAVX5 = 0x23; //Navigation Engine Expert Settings +const uint8_t UBX_CFG_NMEA = 0x17; //Extended NMEA protocol configuration V1 +const uint8_t UBX_CFG_ODO = 0x1E; //Odometer, Low-speed COG Engine Settings +const uint8_t UBX_CFG_PM2 = 0x3B; //Extended power management configuration +const uint8_t UBX_CFG_PMS = 0x86; //Power mode setup +const uint8_t UBX_CFG_PRT = 0x00; //Used to configure port specifics. Polls the configuration for one I/O Port, or Port configuration for UART ports, or Port configuration for USB port, or Port configuration for SPI port, or Port configuration for DDC port +const uint8_t UBX_CFG_PWR = 0x57; //Put receiver in a defined power state +const uint8_t UBX_CFG_RATE = 0x08; //Navigation/Measurement Rate Settings. Used to set port baud rates. +const uint8_t UBX_CFG_RINV = 0x34; //Contents of Remote Inventory +const uint8_t UBX_CFG_RST = 0x04; //Reset Receiver / Clear Backup Data Structures. Used to reset device. +const uint8_t UBX_CFG_RXM = 0x11; //RXM configuration +const uint8_t UBX_CFG_SBAS = 0x16; //SBAS configuration +const uint8_t UBX_CFG_TMODE3 = 0x71; //Time Mode Settings 3. Used to enable Survey In Mode +const uint8_t UBX_CFG_TP5 = 0x31; //Time Pulse Parameters +const uint8_t UBX_CFG_USB = 0x1B; //USB Configuration +const uint8_t UBX_CFG_VALDEL = 0x8C; //Used for config of higher version Ublox modules (ie protocol v27 and above). Deletes values corresponding to provided keys/ provided keys with a transaction +const uint8_t UBX_CFG_VALGET = 0x8B; //Used for config of higher version Ublox modules (ie protocol v27 and above). Configuration Items +const uint8_t UBX_CFG_VALSET = 0x8A; //Used for config of higher version Ublox modules (ie protocol v27 and above). Sets values corresponding to provided key-value pairs/ provided key-value pairs within a transaction. + +//The following are used to enable NMEA messages. Descriptions come from the NMEA messages overview in the ZED-F9P Interface Description +const uint8_t UBX_NMEA_MSB = 0xF0; //All NMEA enable commands have 0xF0 as MSB +const uint8_t UBX_NMEA_DTM = 0x0A; //GxDTM (datum reference) +const uint8_t UBX_NMEA_GAQ = 0x45; //GxGAQ (poll a standard message (if the current talker ID is GA)) +const uint8_t UBX_NMEA_GBQ = 0x44; //GxGBQ (poll a standard message (if the current Talker ID is GB)) +const uint8_t UBX_NMEA_GBS = 0x09; //GxGBS (GNSS satellite fault detection) +const uint8_t UBX_NMEA_GGA = 0x00; //GxGGA (Global positioning system fix data) +const uint8_t UBX_NMEA_GLL = 0x01; //GxGLL (latitude and long, whith time of position fix and status) +const uint8_t UBX_NMEA_GLQ = 0x43; //GxGLQ (poll a standard message (if the current Talker ID is GL)) +const uint8_t UBX_NMEA_GNQ = 0x42; //GxGNQ (poll a standard message (if the current Talker ID is GN)) +const uint8_t UBX_NMEA_GNS = 0x0D; //GxGNS (GNSS fix data) +const uint8_t UBX_NMEA_GPQ = 0x040; //GxGPQ (poll a standard message (if the current Talker ID is GP)) +const uint8_t UBX_NMEA_GRS = 0x06; //GxGRS (GNSS range residuals) +const uint8_t UBX_NMEA_GSA = 0x02; //GxGSA (GNSS DOP and Active satellites) +const uint8_t UBX_NMEA_GST = 0x07; //GxGST (GNSS Pseudo Range Error Statistics) +const uint8_t UBX_NMEA_GSV = 0x03; //GxGSV (GNSS satellites in view) +const uint8_t UBX_NMEA_RMC = 0x04; //GxRMC (Recommended minimum data) +const uint8_t UBX_NMEA_TXT = 0x41; //GxTXT (text transmission) +const uint8_t UBX_NMEA_VLW = 0x0F; //GxVLW (dual ground/water distance) +const uint8_t UBX_NMEA_VTG = 0x05; //GxVTG (course over ground and Ground speed) +const uint8_t UBX_NMEA_ZDA = 0x08; //GxZDA (Time and Date) + +//The following are used to configure the NMEA protocol main talker ID and GSV talker ID +const uint8_t UBX_NMEA_MAINTALKERID_NOTOVERRIDDEN = 0x00; //main talker ID is system dependent +const uint8_t UBX_NMEA_MAINTALKERID_GP = 0x01; //main talker ID is GPS +const uint8_t UBX_NMEA_MAINTALKERID_GL = 0x02; //main talker ID is GLONASS +const uint8_t UBX_NMEA_MAINTALKERID_GN = 0x03; //main talker ID is combined receiver +const uint8_t UBX_NMEA_MAINTALKERID_GA = 0x04; //main talker ID is Galileo +const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou +const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) +const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID + +//The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class +const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents +const uint8_t UBX_INF_ERROR = 0x00; //ASCII output with error contents +const uint8_t UBX_INF_NOTICE = 0x02; //ASCII output with informational contents +const uint8_t UBX_INF_TEST = 0x03; //ASCII output with test contents +const uint8_t UBX_INF_WARNING = 0x01; //ASCII output with warning contents + +//The following are used to configure LOG UBX messages (loggings messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_LOG_CREATE = 0x07; //Create Log File +const uint8_t UBX_LOG_ERASE = 0x03; //Erase Logged Data +const uint8_t UBX_LOG_FINDTIME = 0x0E; //Find index of a log entry based on a given time, or response to FINDTIME requested +const uint8_t UBX_LOG_INFO = 0x08; //Poll for log information, or Log information +const uint8_t UBX_LOG_RETRIEVEPOSEXTRA = 0x0F; //Odometer log entry +const uint8_t UBX_LOG_RETRIEVEPOS = 0x0B; //Position fix log entry +const uint8_t UBX_LOG_RETRIEVESTRING = 0x0D; //Byte string log entry +const uint8_t UBX_LOG_RETRIEVE = 0x09; //Request log data +const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board flash + +//The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance +const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance +const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance +const uint8_t UBX_MGA_BDS_UTC = 0x03; //BDS UTC Assistance +const uint8_t UBX_MGA_BDS_IONO = 0x03; //BDS Ionospheric Assistance +const uint8_t UBX_MGA_DBD = 0x80; //Either: Poll the Navigation Database, or Navigation Database Dump Entry +const uint8_t UBX_MGA_GAL_EPH = 0x02; //Galileo Ephemeris Assistance +const uint8_t UBX_MGA_GAL_ALM = 0x02; //Galileo Almanac Assitance +const uint8_t UBX_MGA_GAL_TIMOFFSET = 0x02; //Galileo GPS time offset assistance +const uint8_t UBX_MGA_GAL_UTC = 0x02; //Galileo UTC Assistance +const uint8_t UBX_MGA_GLO_EPH = 0x06; //GLONASS Ephemeris Assistance +const uint8_t UBX_MGA_GLO_ALM = 0x06; //GLONASS Almanac Assistance +const uint8_t UBX_MGA_GLO_TIMEOFFSET = 0x06; //GLONASS Auxiliary Time Offset Assistance +const uint8_t UBX_MGA_GPS_EPH = 0x00; //GPS Ephemeris Assistance +const uint8_t UBX_MGA_GPS_ALM = 0x00; //GPS Almanac Assistance +const uint8_t UBX_MGA_GPS_HEALTH = 0x00; //GPS Health Assistance +const uint8_t UBX_MGA_GPS_UTC = 0x00; //GPS UTC Assistance +const uint8_t UBX_MGA_GPS_IONO = 0x00; //GPS Ionosphere Assistance +const uint8_t UBX_MGA_INI_POS_XYZ = 0x40; //Initial Position Assistance +const uint8_t UBX_MGA_INI_POS_LLH = 0x40; //Initial Position Assitance +const uint8_t UBX_MGA_INI_TIME_UTC = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_TIME_GNSS = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_CLKD = 0x40; //Initial Clock Drift Assitance +const uint8_t UBX_MGA_INI_FREQ = 0x40; //Initial Frequency Assistance +const uint8_t UBX_MGA_INI_EOP = 0x40; //Earth Orientation Parameters Assistance +const uint8_t UBX_MGA_QZSS_EPH = 0x05; //QZSS Ephemeris Assistance +const uint8_t UBX_MGA_QZSS_ALM = 0x05; //QZSS Almanac Assistance +const uint8_t UBX_MGA_QZAA_HEALTH = 0x05; //QZSS Health Assistance + +//The following are used to configure the MON UBX messages (monitoring messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35) +const uint8_t UBX_MON_COMMS = 0x36; //Comm port information +const uint8_t UBX_MON_GNSS = 0x28; //Information message major GNSS selection +const uint8_t UBX_MON_HW2 = 0x0B; //Extended Hardware Status +const uint8_t UBX_MON_HW3 = 0x37; //HW I/O pin information +const uint8_t UBX_MON_HW = 0x09; //Hardware Status +const uint8_t UBX_MON_IO = 0x02; //I/O Subsystem Status +const uint8_t UBX_MON_MSGPP = 0x06; //Message Parse and Process Status +const uint8_t UBX_MON_PATCH = 0x27; //Output information about installed patches +const uint8_t UBX_MON_RF = 0x38; //RF information +const uint8_t UBX_MON_RXBUF = 0x07; //Receiver Buffer Status +const uint8_t UBX_MON_RXR = 0x21; //Receiver Status Information +const uint8_t UBX_MON_TXBUF = 0x08; //Transmitter Buffer Status. Used for query tx buffer size/state. +const uint8_t UBX_MON_VER = 0x04; //Receiver/Software Version. Used for obtaining Protocol Version. + +//The following are used to configure the NAV UBX messages (navigation results messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35-36) +const uint8_t UBX_NAV_ATT = 0x05; //Vehicle "Attitude" Solution +const uint8_t UBX_NAV_CLOCK = 0x22; //Clock Solution +const uint8_t UBX_NAV_DOP = 0x04; //Dilution of precision +const uint8_t UBX_NAV_EOE = 0x61; //End of Epoch +const uint8_t UBX_NAV_GEOFENCE = 0x39; //Geofencing status. Used to poll the geofence status +const uint8_t UBX_NAV_HPPOSECEF = 0x13; //High Precision Position Solution in ECEF. Used to find our positional accuracy (high precision). +const uint8_t UBX_NAV_HPPOSLLH = 0x14; //High Precision Geodetic Position Solution. Used for obtaining lat/long/alt in high precision +const uint8_t UBX_NAV_ODO = 0x09; //Odometer Solution +const uint8_t UBX_NAV_ORB = 0x34; //GNSS Orbit Database Info +const uint8_t UBX_NAV_POSECEF = 0x01; //Position Solution in ECEF +const uint8_t UBX_NAV_POSLLH = 0x02; //Geodetic Position Solution +const uint8_t UBX_NAV_PVT = 0x07; //All the things! Position, velocity, time, PDOP, height, h/v accuracies, number of satellites. Navigation Position Velocity Time Solution. +const uint8_t UBX_NAV_RELPOSNED = 0x3C; //Relative Positioning Information in NED frame +const uint8_t UBX_NAV_RESETODO = 0x10; //Reset odometer +const uint8_t UBX_NAV_SAT = 0x35; //Satellite Information +const uint8_t UBX_NAV_SIG = 0x43; //Signal Information +const uint8_t UBX_NAV_STATUS = 0x03; //Receiver Navigation Status +const uint8_t UBX_NAV_SVIN = 0x3B; //Survey-in data. Used for checking Survey In status +const uint8_t UBX_NAV_TIMEBDS = 0x24; //BDS Time Solution +const uint8_t UBX_NAV_TIMEGAL = 0x25; //Galileo Time Solution +const uint8_t UBX_NAV_TIMEGLO = 0x23; //GLO Time Solution +const uint8_t UBX_NAV_TIMEGPS = 0x20; //GPS Time Solution +const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information +const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution +const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF +const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED + +//The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_RXM_MEASX = 0x14; //Satellite Measurements for RRLP +const uint8_t UBX_RXM_PMREQ = 0x41; //Requests a Power Management task (two differenent packet sizes) +const uint8_t UBX_RXM_RAWX = 0x15; //Multi-GNSS Raw Measurement Data +const uint8_t UBX_RXM_RLM = 0x59; //Galileo SAR Short-RLM report (two different packet sizes) +const uint8_t UBX_RXM_RTCM = 0x32; //RTCM input status +const uint8_t UBX_RXM_SFRBX = 0x13; //Boradcast Navigation Data Subframe + +//The following are used to configure the SEC UBX messages (security feature messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_SEC_UNIQID = 0x03; //Unique chip ID + +//The following are used to configure the TIM UBX messages (timing messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_TIM_TM2 = 0x03; //Time mark data +const uint8_t UBX_TIM_TP = 0x01; //Time Pulse Timedata +const uint8_t UBX_TIM_VRFY = 0x06; //Sourced Time Verification + +//The following are used to configure the UPD UBX messages (firmware update messages). Descriptions from UBX messages overview (ZED-F9P Interface Description Document page 36) +const uint8_t UBX_UPD_SOS = 0x14; //Poll Backup Fil Restore Status, Create Backup File in Flash, Clear Backup File in Flash, Backup File Creation Acknowledge, System Restored from Backup + +//The following are used to enable RTCM messages +const uint8_t UBX_RTCM_MSB = 0xF5; //All RTCM enable commands have 0xF5 as MSB +const uint8_t UBX_RTCM_1005 = 0x05; //Stationary RTK reference ARP +const uint8_t UBX_RTCM_1074 = 0x4A; //GPS MSM4 +const uint8_t UBX_RTCM_1077 = 0x4D; //GPS MSM7 +const uint8_t UBX_RTCM_1084 = 0x54; //GLONASS MSM4 +const uint8_t UBX_RTCM_1087 = 0x57; //GLONASS MSM7 +const uint8_t UBX_RTCM_1094 = 0x5E; //Galileo MSM4 +const uint8_t UBX_RTCM_1097 = 0x61; //Galileo MSM7 +const uint8_t UBX_RTCM_1124 = 0x7C; //BeiDou MSM4 +const uint8_t UBX_RTCM_1127 = 0x7F; //BeiDou MSM7 +const uint8_t UBX_RTCM_1230 = 0xE6; //GLONASS code-phase biases, set to once every 10 seconds +const uint8_t UBX_RTCM_4072_0 = 0xFE; //Reference station PVT (ublox proprietary RTCM message) +const uint8_t UBX_RTCM_4072_1 = 0xFD; //Additional reference station information (ublox proprietary RTCM message) + +const uint8_t UBX_ACK_NACK = 0x00; +const uint8_t UBX_ACK_ACK = 0x01; +const uint8_t UBX_ACK_NONE = 0x02; //Not a real value + +// The following constants are used to get External Sensor Measurements and Status +// Information. +const uint8_t UBX_ESF_MEAS = 0x02; +const uint8_t UBX_ESF_RAW = 0x03; +const uint8_t UBX_ESF_STATUS = 0x10; +const uint8_t UBX_ESF_INS = 0x15; //36 bytes + +const uint8_t SVIN_MODE_DISABLE = 0x00; +const uint8_t SVIN_MODE_ENABLE = 0x01; + +//The following consts are used to configure the various ports and streams for those ports. See -CFG-PRT. +const uint8_t COM_PORT_I2C = 0; +const uint8_t COM_PORT_UART1 = 1; +const uint8_t COM_PORT_UART2 = 2; +const uint8_t COM_PORT_USB = 3; +const uint8_t COM_PORT_SPI = 4; + +const uint8_t COM_TYPE_UBX = (1 << 0); +const uint8_t COM_TYPE_NMEA = (1 << 1); +const uint8_t COM_TYPE_RTCM3 = (1 << 5); + +//The following consts are used to generate KEY values for the advanced protocol functions of VELGET/SET/DEL +const uint8_t VAL_SIZE_1 = 0x01; //One bit +const uint8_t VAL_SIZE_8 = 0x02; //One byte +const uint8_t VAL_SIZE_16 = 0x03; //Two bytes +const uint8_t VAL_SIZE_32 = 0x04; //Four bytes +const uint8_t VAL_SIZE_64 = 0x05; //Eight bytes + +//These are the Bitfield layers definitions for the UBX-CFG-VALSET message (not to be confused with Bitfield deviceMask in UBX-CFG-CFG) +const uint8_t VAL_LAYER_RAM = (1 << 0); +const uint8_t VAL_LAYER_BBR = (1 << 1); +const uint8_t VAL_LAYER_FLASH = (1 << 2); + +//Below are various Groups, IDs, and sizes for various settings +//These can be used to call getVal/setVal/delVal +const uint8_t VAL_GROUP_I2COUTPROT = 0x72; +const uint8_t VAL_GROUP_I2COUTPROT_SIZE = VAL_SIZE_1; //All fields in I2C group are currently 1 bit + +const uint8_t VAL_ID_I2COUTPROT_UBX = 0x01; +const uint8_t VAL_ID_I2COUTPROT_NMEA = 0x02; +const uint8_t VAL_ID_I2COUTPROT_RTCM3 = 0x03; + +const uint8_t VAL_GROUP_I2C = 0x51; +const uint8_t VAL_GROUP_I2C_SIZE = VAL_SIZE_8; //All fields in I2C group are currently 1 byte + +const uint8_t VAL_ID_I2C_ADDRESS = 0x01; + +// Configuration Sub-Section mask definitions for saveConfigSelective (UBX-CFG-CFG) +const uint32_t VAL_CFG_SUBSEC_IOPORT = 0x00000001; // ioPort - communications port settings (causes IO system reset!) +const uint32_t VAL_CFG_SUBSEC_MSGCONF = 0x00000002; // msgConf - message configuration +const uint32_t VAL_CFG_SUBSEC_INFMSG = 0x00000004; // infMsg - INF message configuration +const uint32_t VAL_CFG_SUBSEC_NAVCONF = 0x00000008; // navConf - navigation configuration +const uint32_t VAL_CFG_SUBSEC_RXMCONF = 0x00000010; // rxmConf - receiver manager configuration +const uint32_t VAL_CFG_SUBSEC_SENCONF = 0x00000100; // senConf - sensor interface configuration (requires protocol 19+) +const uint32_t VAL_CFG_SUBSEC_RINVCONF = 0x00000200; // rinvConf - remove inventory configuration +const uint32_t VAL_CFG_SUBSEC_ANTCONF = 0x00000400; // antConf - antenna configuration +const uint32_t VAL_CFG_SUBSEC_LOGCONF = 0x00000800; // logConf - logging configuration +const uint32_t VAL_CFG_SUBSEC_FTSCONF = 0x00001000; // ftsConf - FTS configuration (FTS products only) + +// Bitfield wakeupSources for UBX_RXM_PMREQ +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX = 0x00000008; // uartrx +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = 0x00000020; // extint0 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = 0x00000040; // extint1 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS = 0x00000080; // spics + +enum dynModel // Possible values for the dynamic platform model, which provide more accuract position output for the situation. Description extracted from ZED-F9P Integration Manual +{ + DYN_MODEL_PORTABLE = 0, //Applications with low acceleration, e.g. portable devices. Suitable for most situations. + // 1 is not defined + DYN_MODEL_STATIONARY = 2, //Used in timing applications (antenna must be stationary) or other stationary applications. Velocity restricted to 0 m/s. Zero dynamics assumed. + DYN_MODEL_PEDESTRIAN, //Applications with low acceleration and speed, e.g. how a pedestrian would move. Low acceleration assumed. + DYN_MODEL_AUTOMOTIVE, //Used for applications with equivalent dynamics to those of a passenger car. Low vertical acceleration assumed + DYN_MODEL_SEA, //Recommended for applications at sea, with zero vertical velocity. Zero vertical velocity assumed. Sea level assumed. + DYN_MODEL_AIRBORNE1g, //Airborne <1g acceleration. Used for applications with a higher dynamic range and greater vertical acceleration than a passenger car. No 2D position fixes supported. + DYN_MODEL_AIRBORNE2g, //Airborne <2g acceleration. Recommended for typical airborne environments. No 2D position fixes supported. + DYN_MODEL_AIRBORNE4g, //Airborne <4g acceleration. Only recommended for extremely dynamic environments. No 2D position fixes supported. + DYN_MODEL_WRIST, // Not supported in protocol versions less than 18. Only recommended for wrist worn applications. Receiver will filter out arm motion. + DYN_MODEL_BIKE, // Supported in protocol versions 19.2 +}; + +#ifndef MAX_PAYLOAD_SIZE + +#define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules +//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values + +#endif + +//-=-=-=-=- UBX binary specific variables +typedef struct +{ + uint8_t cls; + uint8_t id; + uint16_t len; //Length of the payload. Does not include cls, id, or checksum bytes + uint16_t counter; //Keeps track of number of overall bytes received. Some responses are larger than 255 bytes. + uint16_t startingSpot; //The counter value needed to go past before we begin recording into payload array + uint8_t *payload; + uint8_t checksumA; //Given to us from module. Checked against the rolling calculated A/B checksums. + uint8_t checksumB; + sfe_ublox_packet_validity_e valid; //Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked + sfe_ublox_packet_validity_e classAndIDmatch; // Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID +} ubxPacket; + +// Struct to hold the results returned by getGeofenceState (returned by UBX-NAV-GEOFENCE) +typedef struct +{ + uint8_t status; // Geofencing status: 0 - Geofencing not available or not reliable; 1 - Geofencing active + uint8_t numFences; // Number of geofences + uint8_t combState; // Combined (logical OR) state of all geofences: 0 - Unknown; 1 - Inside; 2 - Outside + uint8_t states[4]; // Geofence states: 0 - Unknown; 1 - Inside; 2 - Outside +} geofenceState; + +// Struct to hold the current geofence parameters +typedef struct +{ + uint8_t numFences; // Number of active geofences + int32_t lats[4]; // Latitudes of geofences (in degrees * 10^-7) + int32_t longs[4]; // Longitudes of geofences (in degrees * 10^-7) + uint32_t rads[4]; // Radii of geofences (in m * 10^-2) +} geofenceParams; + +class SFE_UBLOX_GPS +{ +public: + SFE_UBLOX_GPS(void); + +// A default of 250ms for maxWait seems fine for I2C but is not enough for SerialUSB. +// If you know you are only going to be using I2C / Qwiic communication, you can +// safely reduce defaultMaxWait to 250. +#ifndef defaultMaxWait // Let's allow the user to define their own value if they want to +#define defaultMaxWait 250 // only I2C is used, so this has been reduced from 1100 +#endif + + // Set gpio device, used by checksumFailurePin + bool init_gpio_pins(struct device &gpio_dev); + + //By default use the default I2C address + bool begin(struct device &i2c_dev, uint8_t deviceAddress = 0x42); //Returns true if module is detected + //serialPort needs to be perviously initialized to correct baud rate + //bool begin(Stream &serialPort); //Returns true if module is detected - function not ported + + //Returns true if device answers on _gpsI2Caddress address or via Serial + //maxWait is only used for Serial + bool isConnected(uint16_t maxWait = 1100); + + //Changed in V1.8.1: provides backward compatibility for the examples that call checkUblox directly + //Will default to using packetCfg to look for explicit autoPVT packets so they get processed correctly by processUBX + bool checkUblox(uint8_t requestedClass = UBX_CLASS_NAV, uint8_t requestedID = UBX_NAV_PVT); //Checks module with user selected commType + + bool checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process() + bool checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process() + + void process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Processes NMEA and UBX binary sentences one byte at a time + void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure + void processRTCMframe(uint8_t incoming); //Monitor the incoming bytes for start and length bytes + void processRTCM(uint8_t incoming) __attribute__((weak)); //Given rtcm byte, do something with it. User can overwrite if desired to pipe bytes to radio, internet, etc. + + void processUBXpacket(ubxPacket *msg); //Once a packet has been received and validated, identify this packet's class/id and update internal flags + void processNMEA(char incoming) __attribute__((weak)); //Given a NMEA character, do something with it. User can overwrite if desired to use something like tinyGPS or MicroNMEA libraries + + void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages + sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait); //Given a packet and payload, send everything including CRC bytes, return true if we got a response + sfe_ublox_status_e sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait = 250); + void sendSerialCommand(ubxPacket *outgoingUBX); + + void printPacket(ubxPacket *packet); //Useful for debugging + + void factoryReset(); //Send factory reset sequence (i.e. load "default" configuration and perform hardReset) + void hardReset(); //Perform a reset leading to a cold start (zero info start-up) + + int transferWriteI2C(u8_t *buf, u32_t num_bytes, bool stop = true); // Port to Zephyr, i2c function to actualy WRITE data + int transferReadI2C(u8_t *buf, u32_t num_bytes); // Port to Zephyr, i2c function to actualy READ data + + bool setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = 250); //Changes the I2C address of the Ublox module + void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the Ublox module, uartPort should be COM_PORT_UART1/2 + void setNMEAOutputPort(); //Sets the internal variable for the port to direct NMEA characters to + + bool setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second + uint8_t getNavigationFrequency(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + bool saveConfiguration(uint16_t maxWait = defaultMaxWait); //Save current configuration to flash and BBR (battery backed RAM) + bool factoryDefault(uint16_t maxWait = defaultMaxWait); //Reset module to factory defaults + bool saveConfigSelective(uint32_t configMask, uint16_t maxWait = defaultMaxWait); //Save the selected configuration sub-sections to flash and BBR (battery backed RAM) + + sfe_ublox_status_e waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet and an ACK is received + sfe_ublox_status_e waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet is received + +// getPVT will only return data once in each navigation cycle. By default, that is once per second. +// Therefore we should set getPVTmaxWait to slightly longer than that. +// If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) +// then you should use a shorter maxWait for getPVT. 300msec would be about right: getPVT(300) +// The same is true for getHPPOSLLH. +#define getPVTmaxWait 1100 // Default maxWait for getPVT and all functions which call it +#define getHPPOSLLHmaxWait 1100 // Default maxWait for getHPPOSLLH and all functions which call it + + bool assumeAutoPVT(bool enabled, bool implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already + bool setAutoPVT(bool enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency + bool getPVT(uint16_t maxWait = getPVTmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Retruns true if new PVT is available. + bool setAutoPVT(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + bool getHPPOSLLH(uint16_t maxWait = getHPPOSLLHmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Retruns true if new PVT is available. + void flushPVT(); //Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure + + int32_t getLatitude(uint16_t maxWait = getPVTmaxWait); //Returns the current latitude in degrees * 10^-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getLongitude(uint16_t maxWait = getPVTmaxWait); //Returns the current longitude in degrees * 10-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getAltitude(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above ellipsoid + int32_t getAltitudeMSL(uint16_t maxWait = getPVTmaxWait); //Returns number of sats used in fix + uint8_t getSIV(uint16_t maxWait = getPVTmaxWait); //Returns number of sats used in fix + uint8_t getFixType(uint16_t maxWait = getPVTmaxWait); //Returns the type of fix: 0=no, 3=3D, 4=GNSS+Deadreckoning + uint8_t getCarrierSolutionType(uint16_t maxWait = getPVTmaxWait); //Returns RTK solution: 0=no, 1=float solution, 2=fixed solution + int32_t getGroundSpeed(uint16_t maxWait = getPVTmaxWait); //Returns speed in mm/s + int32_t getHeading(uint16_t maxWait = getPVTmaxWait); //Returns heading in degrees * 10^-7 + uint16_t getPDOP(uint16_t maxWait = getPVTmaxWait); //Returns positional dillution of precision * 10^-2 + uint16_t getYear(uint16_t maxWait = getPVTmaxWait); + uint8_t getMonth(uint16_t maxWait = getPVTmaxWait); + uint8_t getDay(uint16_t maxWait = getPVTmaxWait); + uint8_t getHour(uint16_t maxWait = getPVTmaxWait); + uint8_t getMinute(uint16_t maxWait = getPVTmaxWait); + uint8_t getSecond(uint16_t maxWait = getPVTmaxWait); + uint16_t getMillisecond(uint16_t maxWait = getPVTmaxWait); + int32_t getNanosecond(uint16_t maxWait = getPVTmaxWait); + uint32_t getTimeOfWeek(uint16_t maxWait = getPVTmaxWait); + bool getDateValid(uint16_t maxWait = getPVTmaxWait); + bool getTimeValid(uint16_t maxWait = getPVTmaxWait); + + int32_t getHighResLatitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLatitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getHighResLongitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLongitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getElipsoid(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getElipsoidHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getMeanSeaLevel(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getMeanSeaLevelHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getGeoidSeparation(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getHorizontalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getVerticalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + + //Port configurations + bool setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof + bool setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof + bool getPortSettings(uint8_t portID, uint16_t maxWait = defaultMaxWait); //Returns the current protocol bits in the UBX-CFG-PRT command for a given port + + bool setI2COutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof + bool setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + bool setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + bool setUSBOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof + bool setSPIOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof + + //Functions to turn on/off message types for a given port ID (see COM_PORT_I2C, etc above) + bool configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); + bool enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + bool disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + bool enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + bool disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + bool enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); //Given a message number turns on a message ID for output over given PortID + bool disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); //Turn off given RTCM message from a given port + + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) + //It is probably safe to assume that users ofovaj the ZED-F9P will be using I2C / Qwiic. + //If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 32-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); //Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); //Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); //Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); //Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + + //Functions used for RTK and base station setup + //It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. + bool getSurveyMode(uint16_t maxWait = 250); //Get the current TimeMode3 settings + bool setSurveyMode(uint8_t mode, uint16_t ovajobservationTime, float requiredAccuracy, uint16_t maxWait = 250); //Control survey in mode + bool enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Begin Survey-In for NEO-M8P + bool disableSurveyMode(uint16_t maxWait = 250); //Stop Survey-In mode + + bool getSurveyStatus(uint16_t maxWait); //Reads survey in status and sets the global variables + + uint32_t getPositionAccuracy(uint16_t maxWait = 1100); //Returns the 3D accuracy of the current high-precision fix, in mm. Supported on NEO-M8P, ZED-F9P, + + uint8_t getProtocolVersionHigh(uint16_t maxWait = 500); //Returns the PROTVER XX.00 from UBX-MON-VER register + uint8_t getProtocolVersionLow(uint16_t maxWait = 500); //Returns the PROTVER 00.XX from UBX-MON-VER register + bool getProtocolVersion(uint16_t maxWait = 500); //Queries module, loads low/high bytes + + bool getRELPOSNED(uint16_t maxWait = 1100); //Get Relative Positioning Information of the NED frame + + void enableDebugging(bool printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + void disableDebugging(void); //Turn off debug statements + void debugPrint(char *message); //Safely print debug statements + void debugPrintln(char *message); //Safely print debug statements + const char *statusString(sfe_ublox_status_e stat); //Pretty print the return value + + //Support for geofences + bool addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, uint8_t confidence = 0, uint8_t pinPolarity = 0, uint8_t pin = 0, uint16_t maxWait = 1100); // Add a new geofence + bool clearGeofences(uint16_t maxWait = 1100); //Clears all geofences + bool getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = 1100); //Returns the combined geofence state + bool clearAntPIO(uint16_t maxWait = 1100); //Clears the antenna control pin settings to release the PIOs + geofenceParams currentGeofenceParams; // Global to store the geofence parameters + + bool powerSaveMode(bool power_save = true, uint16_t maxWait = 1100); + uint8_t getPowerSaveMode(uint16_t maxWait = 1100); // Returns 255 if the sendCommand fails + bool powerOff(uint32_t durationInMs, uint16_t maxWait = 1100); + bool powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources = VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0, bool forceWhileUsb = true, uint16_t maxWait = 1100); + + //Change the dynamic platform model using UBX-CFG-NAV5 + bool setDynamicModel(dynModel newDynamicModel = DYN_MODEL_PORTABLE, uint16_t maxWait = 1100); + uint8_t getDynamicModel(uint16_t maxWait = 1100); // Get the dynamic model - returns 255 if the sendCommand fails + + bool getEsfInfo(uint16_t maxWait = 1100); + bool getEsfIns(uint16_t maxWait = 1100); + bool getEsfDataInfo(uint16_t maxWait = 1100); + bool getEsfRawDataInfo(uint16_t maxWait = 1100); + sfe_ublox_status_e getSensState(uint8_t sensor, uint16_t maxWait = 1100); + bool getVehAtt(uint16_t maxWait = 1100); + + //Survey-in specific controls + struct svinStructure + { + bool active; + bool valid; + uint16_t observationTime; + float meanAccuracy; + } svin; + + //Relative Positioning Info in NED frame specific controls + struct frelPosInfoStructure + { + uint16_t refStationID; + + float relPosN; + float relPosE; + float relPosD; + + long relPosLength; + long relPosHeading; + + int8_t relPosHPN; + int8_t relPosHPE; + int8_t relPosHPD; + int8_t relPosHPLength; + + float accN; + float accE; + float accD; + + bool gnssFixOk; + bool diffSoln; + bool relPosValid; + uint8_t carrSoln; + bool isMoving; + bool refPosMiss; + bool refObsMiss; + } relPosInfo; + + //The major datums we want to globally store + uint16_t gpsYear; + uint8_t gpsMonth; + uint8_t gpsDay; + uint8_t gpsHour; + uint8_t gpsMinute; + uint8_t gpsSecond; + uint16_t gpsMillisecond; + int32_t gpsNanosecond; + bool gpsDateValid; + bool gpsTimeValid; + + int32_t latitude; //Degrees * 10^-7 (more accurate than floats) + int32_t longitude; //Degrees * 10^-7 (more accurate than floats) + int32_t altitude; //Number of mm above ellipsoid + int32_t altitudeMSL; //Number of mm above Mean Sea Level + uint8_t SIV; //Number of satellites used in position solution + uint8_t fixType; //Tells us when we have a solution aka lock + uint8_t carrierSolution; //Tells us when we have an RTK float/fixed solution + int32_t groundSpeed; //mm/s + int32_t headingOfMotion; //degrees * 10^-5 + uint16_t pDOP; //Positional dilution of precision + uint8_t versionLow; //Loaded from getProtocolVersion(). + uint8_t versionHigh; + + uint32_t timeOfWeek; // ms + int32_t highResLatitude; // Degrees * 10^-7 + int32_t highResLongitude; // Degrees * 10^-7 + int32_t elipsoid; // Height above ellipsoid in mm (Typo! Should be eLLipsoid! **Uncorrected for backward-compatibility.**) + int32_t meanSeaLevel; // Height above mean sea level in mm + int32_t geoidSeparation; // This seems to only be provided in NMEA GGA and GNS messages + uint32_t horizontalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + uint32_t verticalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + int8_t elipsoidHp; // High precision component of the height above ellipsoid in mm * 10^-1 (Deliberate typo! Should be eLLipsoidHp!) + int8_t meanSeaLevelHp; // High precision component of Height above mean sea level in mm * 10^-1 + int8_t highResLatitudeHp; // High precision component of latitude: Degrees * 10^-9 + int8_t highResLongitudeHp; // High precision component of longitude: Degrees * 10^-9 + + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame + +#define DEF_NUM_SENS 7 + struct deadReckData + { + uint8_t version; + uint8_t fusionMode; + + uint8_t xAngRateVald; + uint8_t yAngRateVald; + uint8_t zAngRateVald; + uint8_t xAccelVald; + uint8_t yAccelVald; + uint8_t zAccelVald; + + int32_t xAngRate; + int32_t yAngRate; + int32_t zAngRate; + + int32_t xAccel; + int32_t yAccel; + int32_t zAccel; + + // The array size is based on testing directly on M8U and F9R + uint32_t rawData; + uint32_t rawDataType; + uint32_t rawTStamp; + + uint32_t data[DEF_NUM_SENS]; + uint32_t dataType[DEF_NUM_SENS]; + uint32_t dataTStamp[DEF_NUM_SENS]; + } imuMeas; + + struct indivImuData + { + + uint8_t numSens; + + uint8_t senType; + bool isUsed; + bool isReady; + uint8_t calibStatus; + uint8_t timeStatus; + + uint8_t freq; // Hz + + bool badMeas; + bool badTag; + bool missMeas; + bool noisyMeas; + } ubloxSen; + + struct vehicleAttitude + { + // All values in degrees + int32_t roll; + int32_t pitch; + int32_t heading; + uint32_t accRoll; + uint32_t accPitch; + uint32_t accHeading; + } vehAtt; + +private: + //Depending on the sentence type the processor will load characters into different arrays + enum SentenceTypes + { + NONE = 0, + NMEA, + UBX, + RTCM + } currentSentence = NONE; + + //Depending on the ubx binary response class, store binary responses into different places + enum classTypes + { + CLASS_NONE = 0, + CLASS_ACK, + CLASS_NOT_AN_ACK + } ubxFrameClass = CLASS_NONE; + + enum commTypes + { + COMM_TYPE_I2C = 0, + COMM_TYPE_SERIAL, + COMM_TYPE_SPI + } commType = COMM_TYPE_I2C; //Controls which port we look to for incoming bytes + + //Functions + bool checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType + uint32_t extractLong(uint8_t spotToStart); //Combine four bytes from payload into long + uint16_t extractInt(uint8_t spotToStart); //Combine two bytes from payload into int + uint8_t extractByte(uint8_t spotToStart); //Get byte from payload + int8_t extractSignedChar(uint8_t spotToStart); //Get signed 8-bit value from payload + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + + //Variables + struct device *_gpio_dev; // GPIO device + struct device *_i2cPort; //The generic connection to user's chosen I2C hardware + //Stream *_serialPort; //The generic connection to user's chosen Serial hardware + bool _nmeaOutputPort = false; //The user can assign an output port to print NMEA sentences if they wish - ported to print to console, converted to flag + //Stream *_debugSerial; //The stream to send debug messages to if enabled + + uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series + //This can be changed using the ublox configuration software + + bool _printDebug = false; //Flag to print the serial commands we are sending to the Serial port for debug + bool _printLimitedDebug = false; //Flag to print limited debug messages. Useful for I2C debugging or high navigation rates + + //The packet buffers + //These are pointed at from within the ubxPacket + uint8_t payloadAck[2]; // Holds the requested ACK/NACK + uint8_t payloadCfg[MAX_PAYLOAD_SIZE]; // Holds the requested data packet + uint8_t payloadBuf[2]; // Temporary buffer used to screen incoming packets or dump unrequested packets + + //Init the packet structures and init them with pointers to the payloadAck, payloadCfg and payloadBuf arrays + ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetCfg = {0, 0, 0, 0, 0, payloadCfg, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + //Flag if this packet is unrequested (and so should be ignored and not copied into packetCfg or packetAck) + bool ignoreThisPayload = false; + + //Identify which buffer is in use + //Data is stored in packetBuf until the requested class and ID can be validated + //If a match is seen, data is diverted into packetAck or packetCfg + sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + + //Limit checking of new data to every X ms + //If we are expecting an update every X Hz then we should check every half that amount of time + //Otherwise we may block ourselves from seeing new data + uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() + + unsigned long lastCheck = 0; + bool autoPVT = false; //Whether autoPVT is enabled or not + bool autoPVTImplicitUpdate = true; // Whether autoPVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] + + uint8_t rollingChecksumA; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + + //Create bit field for staleness of each datum in PVT we want to monitor + //moduleQueried.latitude goes true each time we call getPVT() + //This reduces the number of times we have to call getPVT as this can take up to ~1s per read + //depending on update rate + struct + { + uint32_t gpsiTOW : 1; + uint32_t gpsYear : 1; + uint32_t gpsMonth : 1; + uint32_t gpsDay : 1; + uint32_t gpsHour : 1; + uint32_t gpsMinute : 1; + uint32_t gpsSecond : 1; + uint32_t gpsDateValid : 1; + uint32_t gpsTimeValid : 1; + uint32_t gpsNanosecond : 1; + + uint32_t all : 1; + uint32_t longitude : 1; + uint32_t latitude : 1; + uint32_t altitude : 1; + uint32_t altitudeMSL : 1; + uint32_t SIV : 1; + uint32_t fixType : 1; + uint32_t carrierSolution : 1; + uint32_t groundSpeed : 1; + uint32_t headingOfMotion : 1; + uint32_t pDOP : 1; + uint32_t versionNumber : 1; + } moduleQueried; + + struct + { + uint16_t all : 1; + uint16_t timeOfWeek : 1; + uint16_t highResLatitude : 1; + uint16_t highResLongitude : 1; + uint16_t elipsoid : 1; + uint16_t meanSeaLevel : 1; + uint16_t geoidSeparation : 1; // Redundant but kept for backward-compatibility + uint16_t horizontalAccuracy : 1; + uint16_t verticalAccuracy : 1; + uint16_t elipsoidHp : 1; + uint16_t meanSeaLevelHp : 1; + uint16_t highResLatitudeHp : 1; + uint16_t highResLongitudeHp : 1; + } highResModuleQueried; + + uint16_t rtcmLen = 0; +}; + +#endif //SPARKFUN_UBLOX_ZEPHYR_LIBRARY_H diff --git a/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c new file mode 100644 index 0000000..32d2717 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c @@ -0,0 +1,102 @@ +/* + Reading lat, long and UTC time via UBX binary commands + By: Nathan Seidle + SparkFun Electronics + Date: August 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads the NMEA setences from the Ublox module over I2c and outputs + them to the serial port + + Open the serial monitor at 115200 baud to see the output + I2C clock speed: 100 kHz + + Ported to Zephyr by Vid Rajtmajer , www.irnas.eu + + Development environment specifics: NCS v1.0.3 release + + To build: west build -b -p Can also read NMEA sentences over I2C with check_ublox function + To flash: west flash --erase +*/ +#include +#include +#include +#include +#include + +#include "SparkFun_Ublox_Zephyr_Interface.h" + + +#define I2C_DEV "I2C_0" + +struct device *gpio_dev; +struct device *i2c_dev; +/* I2C pins used are defaults for I2C_0 on nrf52840 + SDA: 26 + SCL: 27 +*/ + +uint8_t init_gpio(void) { + const char* gpioName = "GPIO_0"; + gpio_dev = device_get_binding(gpioName); + if (gpio_dev == NULL) { + printk("Error: Could not get %s device\n", gpioName); + return -EIO; + } + int err = set_gpio_dev(gpio_dev, true); // set GPIO_0 device and enable debugging + if (err) { + return -EIO; + } + return 0; +} + +uint8_t init_i2c(void) { + i2c_dev = device_get_binding(I2C_DEV); + if (!i2c_dev) + { + printk("I2C_0 error\n"); + return -1; + } + else + { + printk("I2C_0 Init OK\n"); + return 0; + } +} + +uint8_t init_gps(void) { + if (gps_begin(i2c_dev) != 0) + { + printk("Ublox GPS init error!\n"); + return -1; + } + return 0; +} + + +void main(void) { + printk("Zephyr Ublox example\n"); + + int err; + err = init_gpio(); + if (err) { + return; + } + err = init_i2c(); + if (err) { + return; + } + + err = init_gps(); + if (err) { + return; + } + + while(1) { + //check_ublox(); // See if new data is available. Process bytes as they come in. + get_position(); + get_datetime(); + k_msleep(250); // Don't pound too hard on the I2C bus + } +} \ No newline at end of file diff --git a/lib/SparkFun u-blox Arduino Library/img/Contributing.JPG b/lib/SparkFun u-blox Arduino Library/img/Contributing.JPG new file mode 100644 index 0000000000000000000000000000000000000000..39ebf68b048682d490c560ab5cd4fc67656ae778 GIT binary patch literal 62131 zcmeFZ2{hFI|1UnG5R$TmsO*HuQr1*L6QZmWlVlxZl6{*gLPSCcA%wC`*0BteJ$v@d zU@Y0U8Os=Eap!Y?_x$et-gEx<|G)op{^$JfIp3Fgoim5m`}KZ3U$5u#cs&=#e~xDX zXYb!Lz6W4pVgfv6`~Z$|fIEN_OicfL{##={$^1_{#lpgTlJyiT>pz{1or9f??KB%J z>uIjjr#Uzojg|cjHy7ubf4=`S$v=<(^AzL5$;Qg|&xrrMcKj2-!^ZTOxr~|V3g83} z6EhFfaXUZ^0AM=FSnWRx{%?)x1T)LYQ;a2@=3oq{Im=i+GxG_?Vo$QLFh++l{tjT_ zIeG4~vi_;_=8mjaym?h#C+4z=->zukvlt>sTz~d5oc%PvfZzooNhxU=SvgfTb&VT0 zHSgRtxMyf&eE-p7ODk&|TRW%c&MvNQ?jAnAe*OW0LBSDkBBP?;#>6Hie@sbD`;?xM zm;be(u;|xP(np#v{eM4(odq-#2ukN1VkO~we^T~8(Z$1{>jdKrSXlp|i|IrFqciicoV=`j>YTngtE2b%D=M$q zcyA}>R~m)+OlZfzDEv9;I+k4mtV>f_{jJ(%1j zpIY)6x7=Xu@(K;0_^tTw1iptc!~2gq(}aYR=Qy|^meEh1cN#Q+f8{*CPU?<#lFoPf zmJtxfAAg8F1|*xAlp`BXWmK*m!NqdIHa4qtJMz$jBtzPFUv8YhEIexgdIJA&Bq_+H za#Xedwvrs>Ui0g|0RH46LbvdD*3qZ$xq_V^=gn_)T#%Ljk{2s?kJsD)@GsqZ;22Qt z+GWN|<|a{SS=xLB9y!j0!T0k-c}OkTC6Yb`y_%bfKZuLE`)8}`z_YO`>eqZ0MIqFO zXx0wHX^|2M*;U11Rc&;pF;y+-sfmr>T_mR+C`~t`x-*^!}=e! zDbJD?3+rN8g#Vn)_boPV`N;~%mi>w|2Q}%^cpknfk`&w;w8_h_$=Ot|=#C4D6&6ju z@Ld*~%afF30XoTReycTB=F5rygF1Ht2C_OkbPSj+!Bj@ltJPPAv4KTqBa6wj2X>{k zsY{nXKQ$thuA#$&n1`ENkk@IYde67n=#vO`NkYQAxh5n2d3lX7| z`!$vh718+cYetvC^$9VUr(#&{_G`Ug)-kn8apj>rnwdcgd-#*?cK#U2X5}i1d4Njy z`^v9!e|JAGOMploIhG;*RbF;m+y3J?&^Kd6IGoh5R#up$0MVh#Hx~_L%kTIp{Sly_ zRxVk+jF=M~)3!$o7 zZwtxwu5;eSIO=Vams=ODA2b2YlqG8A<-YQ-v~iq}>pbgpq1W2-7;y3=`TQJgGKbFA zO?3T9=NO(JqH4??16aWN?w)Z^b|SN;2W#ZU-)BOFib!~8fl#D96-233L-BP zTw5|eqk;nxHnsJ6l~*)KWuE;|Unk)DXM01mzof9LJ`@?)Fcjzr}VRAp=T?_mo zfnM-E^yU~`p#S7VxL*_3!>PcRHjX>ymJ*yvtwZNU5^u<@y?F8BE|>}7suZWoL!`a0 zAxbZ7F1K^&C<*|>#2*k+a-vJipFfvfn)VF(n*K_nxTN#-4}X!<>!d`|$oGp%vU-n6 zc0nPvlcC1bjXrh2KaUFcJ!JKy1A-ts0n^w)XGHojz!~``vn8hnwRxUmg=p;#Kj?4} zs3GTS^JfcZkZe?cEo+9QC#@OHKi@5obFx(7=XoP}BJEk22A%!f0h}2{UL^eR$SC|i zH~0p_5Y3^UcMlPSO6qiWti0r`+HEv zpA3I&pct#s1vd3q16T1NH%CN^VJKoY=QNe2c1RlnTZn%qCJa%};LrRLczXwA0&Y+9 zqCcB7y!x`;Lc?aN=RpNBwVcBzf>`u(n`sSEnMUQEm~}3Sq_a-L2jPO`RJDG2n-T8# zD`QKMjDEHX!+`BJFMXsF*jK5E1R zU~mjzquP08s@05rPMtAnLOLcm z?F)bMRF#QB`U;#owE~t42T8quNNRz2L_siTaN(*6D5F~UWH*yGn@SrNJewQ z!t=(QcFnQrp60R={1hr(fts|5TRwbk3xC3`aEeWYJkdEwmWXDs8rGHMGz#aZ-li4$ z?Qxv6sk^OgKd$m)-h892q$>oAmGT#>(V%|aCTSl7kY)nMfYnNDDZM>s zCs7Z_N^gS;9Ro&Gtc$6SXeo#}XZi?+wORZa01JCZ=hN0~2T$Uk?dsLQ@lf6iyP++M zL2!rdI!aYS+J*1BY>X2j6zRDk<8*cDQ*?$+imV7JbO?*sa4;iT|Jmp$$6sd%8(hQH z1G*Y9UU=OYLahXASQ^7mGj_{VO`zMvzlMGvxNrF37|`N(vW0QVc2pomHqJgM!!YSN z>r=#Lh(cqbhdZI5dC)!KwE=iM&%*0wlP$QU3U<}l$^5@1{TCn>z!ISlREKj zS2RmX{GC}d+B{5gyWny4_^~4&+oGN3U#m@r){w)=RaY-`nSCfFCk=mAm#3&Lht(!9 zVHUR0O|c|H77Am6+(WqI)Cyn!C7!=JR5Kb}8u94kl!3R(sq@tk|hlQt>%wMEM@}Sz;BET((0lgEstTqXZiIC^m!_vl7cAsUZrZD?bkyEY5|?>) zQ?Ro#!%Jh#!EBgVIRcR$^4yjzwqAZ?7neHF9gH<@3JA8h-wx4HC+NM=s}hJtIK#*a z$AD;GCDz_zEZJ^(?o6(aQ{EIF7y!)^+XVUrZayd-6uEi}A@4u-jB<teBS{}| z&D`7D(eFBUWp$?h<@xkG=Wg#Y4JSYTUb`LmIG##tgx4O};c8zyWTY56d)eN$+99dK zt!$8dFZ z5AwAI?#{M52$JtLEVt%NVo0^a7&m+6OU*yXF(qo~w2Y=571ij6A3TzRYIh@@1JSj& zkUL*G!n!G{43W7;RU+t-jLEMF5b&5+C~QtoY(2QP;HB5IK(8yP=BIZRE~gqM*|a9$ zQ#t!Io(lC6G&Yopy{+n2y26>HVxrpitGy*7$5(-RW(n<3w^t`oVGpZLY27+y?j@<6 z^>g%_yZ-}&1#GGyzd*t3pr&)WgS)fB}n~zEk8-rj}UXD5SvITCe zQk&>?auosDi5JvmE_(9}mD8pxoB=E(4AhSBr;-*P7(0b@?AXZoofAaYYB61!VBmum z(`F9(AcNTuvys*g@?@~1BQg-w;w{n4fk|Cjn}-T)+BH?$w-?UE{d#G4UF7qZhjjoZ zFf=rbe3F>cC3emfN!VQ&q{?reo9|aBR|O58#6l7!icP&PrTmRq_z-?duv^qx>*md6 z9GyL8mSNMMBF=}h)Q{~qPHS}FA&p~d-R)qnx=}Ah`1nDiCBLMjcTEc4?N_;BtGDyw zU{K!rq&3>o#*vP&aczc`>Cw*KZx6oJ0aJ?oUS+J=z;HgaNA&jm3V{Zp(mt*!I7 z?!`dR143#-m^Uro+x=OocAhe-`{fY)5l#K(ia}?_J1a*6UMrVe z1Kx*B|3~#8a65*VoY0vg1ci%{1MFa@+xQb`^(7E~((fs-1oURCphBu|pk-E)#g4Ja zE4h>G^=)|&{CT9FtBtnS7mv!ou6h6Z`FHD|REtoRBXdn34cW7jT)3c~>c!1so=!~q z%S+ERyE2<+{Qarh2fvA_M_NjN239+)_Z-5o+3`#X#j*H`dZDoy_-1vY*IPVnbnat^YX^B~;arB4@t)#li ztYe&HJh#oO80}M1`zl)^a5E~6_N5>ng4jO>xD9@e=l*ngCobR3=K~N&%D6^eMeBLb znu*vt*yF%Y+=izL!VTt`-K;}jAPT(V|J-;kAkXI9{j23G?N7b$F+dX0qE#?C?bbTs zAA+1fbl4u2=$NnZT1SM6(kfw8FyZ=7>`cXM&e`tyZ6VNMYOvI&tEe@(52aO4S{=4~ zVyEwLlgSwx-5e+%8JG>(oha8kpY|GBvo51l_UvOX?|7Q|$%e}83qrDA5+DEA6;HW% zX1M2mR>5nQsrfm$*DGID>+DcvT5di$a-lJS3m<6Kk$rY-#Kgn_dl9O@irciib0JBu zYEXSRzFGxXG0|t1mK+C*6{|g!N$Bd(l^W5TJ4dW-V&F&TIH5vRKmit~(-c`+BF^q3 zL9EWaYicWQmCSru(82?YS;tJf!p8&ts6jhmG|+YfDx^fT-XO^OJpvLY(1Oi60x_fF7HgEN6k7jHHoeq#QX2WP!R zoP@Imf=B@bIWnXL3nuBtqSCDAahunlJcFdq1m*aa_wNG5Yp=il3;>)cARszG+`@rc z78B4*O+YU7t)mg&*z|h!WMj?*QNi%Y`LM4ai~zGPb+9OWt!X0h0{Z&(Ku~AKsgwnv zdgE_D1-tPX88vkfqFVKIj8?x$w%xC`{&=4D2j-vo6m&!y6~aU@@wGdrYTs?q_wy-8 zO!@||bv8MGlsKgQs326cp|)nsTvr;aw)9aV_8N55P8e+#t!0*~yt94SRFBl5eW2eU z=g&t~&3&qwezZBXCU$18-+wkNLlE6NSuSlXdC2P|7ptD|(dm_3Jy@&o0K6!D45-EY z)NT7#YelsKf8UejTNWDJR48=A@cIpMHou!^7bn!j*`=aN-)RQ!`W|MKd|5ap*u<7QP?_Q0a_-eywT@mv8r8c{ywH_^{GgE-RJ8NljSv zUdh1(`tpL_84v%VNuXzboJEGUFU>9+7HR<&6aHQGFOZD-?ox=Uc_7rJ?0Y?1fvHbJiU z@)bx4T7jJp!! zqGklQL?5SGC21SJ{O|*x#!*8mD5=Dob-^-MQ2t?2W2jxK2Bo1Xas9FN2MB5hmS$2;V_%f$E4r$OCh}{7T(I@N>Z?rAvvsA>`$oMB_EN=@Ylzi8LJ`hKi4_D`(4x7ebV>4E6 zMap0i0a3Rnn#JKGsfaTU16yXCfhCU#9j%k-8S#quqpP(*Ke;Ypy>v~}H4Sm)DA^Mw zC(Kw_lKoNO2^i*!js_i5f@})VQHdp*y~Wf-MwX`&PY6~cMcqjbCq81=GUz$5qL1O; z7tyUZ_~-BKUP$u$+xpnC^6{qBV8SuLb5m??J4V~wRU;A&2`eIBS;OMng*plf8f{jM zwIB9XDF=<)np-@5a!2I-Yr)EsGEdCC#@EW5R40hw;4@|ZQzSY0F1R>+606r%P2^jq zUYWT*PSu;cN$)6L4|cApPW4CG+=<&mDqvf*LNmp|P@Q82Y6$_P{uG{Eo=#&FrNcDk6?|U)F~%GRsJu zJFvu?ty7WMHA;Bn;V}Tb>_zhbv#%-NRj%jRbcGhm5I+Jcj%x2Sk2%8t4(3j<0vt&G zozBqrU7`8FM2SUg7VP8I8yg8`pz8e z%$leOAtzqjGGJbTEA(E2L)OVW2b(`A4vCqtcJosTDLh}}#rdGcw=0A+T{kVilB+uH zyprd#INIUzu?MCj`yugnOhJ!pM--IOJE}*n?si;1#q+D(KqT_E$g9AjZh71QKIc$v zEo1sxFpLJJL$)96pO()eCoVK%m{$EqY{=kt2Ze@)oNkbg2#Eb5)I~*Ws-M#+!b{hk zN%AAWxLnMPhV(_AC;POe=?W7j-f5c`li&B4==*{MH?K60%*C3?xzs`4z5NpRb@Cl7 z19$`ay0wa(j^UsiW0IN2zaeGJunR_JXUXB?>uodgBOBV6okVlzxvaKk*h=@@X8P>E zeYk+02z&G0$SE7=X~7tHf1OTRKL#*UCkn;}9rMs74{6^9;^&$amnc7V z)hoyLHy^s&nZ`(D*rG?La5aPJA|=7l+d=M^50|a_o}ZNX20aGEi1GR9jpm<3p4?_<`C$(Q zl3ED`QPGOIBAWf1bwkxn4oncwO5SQ$s zKz2=nOS~R{RUc!@6Lyl|@0W{*o*1^`yc5y5z8d_i_PP!l z*KW;58ZqJM>=*goo|%&Wv`6LZA;URU1J9l1j@9EU+lR7tWM3v6MED6?Cv88dsHNz> zeujmX?r2B;eQ!+n&%XU3X3{b zf`Q zo7Y`bmyFjl z=QzP`#dVAdZZI^p!gYtnq0&9G(}8UcV>R+Ow~?yrHXHK|L*n7f8&&TjvzM;*i_9&^ zGksiAxpgVm(V#YIoo-DjD2wP3CY0 zDs|~9G&KGQvn})ertd)U8-t6!yVjX#?*RW7UM;`-9 zf>-M|My)w!8PS(E%z3{}l`caKlza&}SnnL}Nww}16VII>M;qlcl~HR2_pa&R6f9c7 zi|&Q_kV3#Q<{%GXuI^hb;q`)kz~8I)EE?~8#8>;`;|{j(HjOSSq#f2AcFw&3S ztK_c}vqQKc2ZOn7oP68Cga85;EFFs$cIA%#OEjrjLb;za_~u>#-F1m~Of}~DV9euI zXtXHmK~4X~jiMp<;0hRQuUU)lGFBI}WJT%#86qFyO=O!fA!VPieb$63V*W z%qD_z8kRSVwLoApqRX)GNVgAYZ@rH?Eb|`=Ff227u)x%0rx&_q{LMIsLR-&ex!ws; zrMF>X$3e7PGdE)gH$DoyqS9SRaEb1eMC33j_r5JHe-mZ)7$woUBq848-EktA6nJ1{KWO}w1 z@niPIQL6lW&dGJmQ=x*I?7FMHU=WN4omf-WzPh7z$=AgitgQP)AA!Xg)sB0qHYv5yWr_Sh8u~k9T%C&Uc#1tr#Y&{E*<`!t$2dj@-V4gsn>q2W zJ~lkH5dlK%0JraW;En;HW5ABmmTQT*gZmseHu(+_UN%Qlb;mB{6t6dAk>Vxer_9-_ zOB!$3x#!i%iYrY$w9Iqoi^yel@AuO?1DCQ1HLVzP{SmvxJFEt25_No0Tu@m1v2&x& zY3JM-_Li=PvN%u>c(JfK1Btd*T83pe1h3E+^77CltvS`z4jLKr%RoXvJWN|X@kzHk zvIFW#vdfL|Ukq2i*`PIP>*Vs<;=zeh%fw6}3hDPSS|cwoX`x+|hlq*xdOOK{-MA5UpF$(%*<>cOEG|2STJQl?yZPP*%cuIxIN zo}d>^y)$PY(gtNhkNAB=?V`W`uD+g+>sK`s-bf5ge&P1{n&5?^c_da!>13$U$es?c z6|R69QPVNKD9X*FYK57E23!qUNDxI}U-Z9id#et+Um33z69Bw_b*|D341bT}+lU(eJ z|2+4j9TwO|Y92vLhm;;V@DOXOipW-MMMTkjCkT{;;NE^~+AY_u3A~!v+ACgA?W0nn zB>dJQwU};9I|hs)YxacSS$MByf_+f?qx(Jbtep%`;T|j+!(C1_|8CEdZ;N}En#zXt zPXP(DxV@@=|M#2aLm)rkgi-lv3aKpfUS;(=bB6zezpY)fX102`nqA;Q>VYK;242J$1>`mA zc*X9pGjw*A5Uw@VI$;hei14%zj3b+ZxeIF08Cj_Xf6?o_jjq>Dx{Zn4udH@-F?4-A z(`&{E5TVYzn}=FIc#Rfc$WkBQw$~MC#x&Q|yvN@itF-i7?q~_PisJ=(Ro$|CEb0|- za>Z6%tw@qfAcLwlPE%+aL;8gle43j_$_8!h%^Zx}E8WVXqRaIsb(A(d(4AcA{zlYZkA?z06}k;4h0?nQ8dyXKr|Y z-$70fU2fw0OxLotbMAIt7;nBJlJjPdzQmFm#|y8Ad{GaP5^Qmg7N1?xfn9&iQ z_8r@w>DRU2*oJt3I)_XON&b`D$kYiFjY{{ztpvk7w5tKFMs<)U5JoW_Y?;x>5bn3K z=f0QFU1=M7Cxml?2gp^KnOH47>Fgsxe+)Vm@cFD3{!I>FS+fE;cx_8Z>(LOb8aL^? zX$TyBb9>jME`g;>I$0v|bH3q~kBKQscQ5Te0xPr+y-Tsd4h&izI#5<-x>xppzvwj2 zG=GPOiY=oIog0m7sx&j?`jj-3YJ;pac05>TE;kdLP0qRXIYLd z^E0d%asZaW8BX-HA@@`aSM#Mcx@3Z>PcM?v>g!Ha5O?^k?5rez5sG{r5wv z6HzqxRN7q5xvt#FCk}Q7^TG)g@tUL%U^0|}?5EUvexy8S?tjh9_9^_{6v07<_QkJ^ zl~j!$+Gj(XGlFAGf%0v4+BFOFy&6D&9K>lwDo+mIA3Wow+FlFOtp0I zb2gZ~ulzCqQe>jEtx=?SFs)#C6{=>2y|-y^c=rIR)M!saJz^k6Swg}l2vWZG&b@@J z(Mdm(x@(+cH=;necmzeATQXpO5)-c>uGjHd4 zZnnVl?LwCH!u1jy=)%4Y`kk?YgUno#`yKwzd0jh3TesLIZ~GP{IY~?Ni6#^W44d{$ zG=^4MS&yl1y1LuGzqo@1ZSE{K?j7W9*JRYL94W@`{6e_z2F=st^-%(D ziyCHAV{IL%_w^3mH2tm7+6NXDAjHj&m#4m@JX-T>d)qpe3-c&Ls2&5P4+8AUxK^zr z?~Q7L^4E}uVbix`Wy*b?{?{zBe=^Db?>qBgvn*A3OdGfmFW=G3N91+5K)ja*QOq8; zU&Uk9h2p=)l{?nGGUknjeDif%icdkgzO0f(vbI1&Kt$JYHHQ8qXLmgsLraE)1xWw# z^>K#0MDY)LRdflPiMOA<_RPT;sJ`|1tiZ5aVJ0vIN)M(ZkS~H8G3!Ui0L5R7t1=@{ z`y(oG78ds?FnrL2Y#&erEbT87UlV1CMVtiz<O6T!5VomRNs ztPS<&fJF&aqsINMu;Jv!pWf@k*KxOhuq!N5?kceb4l&(Rk&$6*QD_Y5z*j)i-lFXn zrtNTcVK>MqQ=@9~xjw-?;n^Rjnr?}}o_UrEO7}*1!ky3yxNk+WJ$4wqF2s6~zJbi< zA{k2aTl3BqBEP_J(og8mg&0d>G)?0?Q|&(`ZRzuHQtrWBJ!?+|dF&)iGH^K2exi;0 z&2AtL{N7%#<%kQNDs;{bR^))4Zi=2vz)8B}`rPtTeNX>r>s@>;C%;lG>p(rASWy`c zcN|_Z0^#hBS%B>v?rTfWCczeVZ>4y$?UI`y8&!~-xc++_2H_wjm+Y@Z^M;Tuo10&2s-Fq0JIoIE)A>Cg0% zlj{xK!F$NKOwpjUA|u660wb%3Va%Cec*GD$)dcNkTJ}Z_o-gzsdK4+NZc2_Xuv-N~L0Zn2gC zsMTBMLGi^hGfw)iqv-HG@Cin?6gkr@;xWOYBIx@i=!JpQOO3`(sRP*2C-R`XF;bVo zH`_yASavbvR*FeUjQ9_d3{yaDbuU^~Ea!;Oxz>&W=lup*J&pw4LbY{@$r=oMjS6YH zx_0(Sx`I@#a<^cH6fP!4>xt+!wgE^F{bs1pI9iXuNMpEP(=uYl^PV`1_k%W-4tFRQ+Ni+gV|8pL;CTQJ3>-42!DXp6MhvyJj0q#T%8Ip=@#vlw z0~}f)T6M8Ee@`;giDlUH9HCu$$`(O71Ca1WT4t!zd`@)!Eh2Y2hwwtauzbRqHGcR- zio$ft^w$yz4GmDa1Ta-f^-RUr(=A_r+V?!d$`th-)*r3VA&=?SBR`sZ5)6*8$AGY! z?NOpm2*@JMe;0pOeO40}ssx_9S=J4VYc``^Eql1;fcS$# zxVp@YU(5_`SweKx`lL|ET^CdW*GcxrfHxzp7y;G6&A}=}>xeV#`Ud0nR!REP;y%(R z3OkCYJ=%7vH7gx5Rxk64G5lKw>keyc$_&FH)VB|FgsE_{&-{h7PRto?;bL;eR&VDK zcBa3mdsgR`oy@aNZN+Wv0&AdvTO3Vlssa(Z`Z&(M= zTChv52vE5KjY@Hex+DD5D0B4GgZN)Ir!4NAX^vIe>eTCHm>)FCYlT&P})kxw!jh(?B@n>3|gL6Q@GV!XPkN$rJ?+-{*C;V2e@Y2=fbBJa`N21 z?uX7;9lFzRmFM@4Ao}5@J4&xHl|RS|A+wYB8fkgj;6;u%kwlV zbpZTeW`S=}bFh`NJ$Ye6oG|g00fmaE0-te-S;Y3vjTe-66-%P4s<~kbjc>1(BM>Ss zL85vY>d#yM(p(L0qI4u{F$veyEi>x>2Mk| zmfRb*Ka5w<>lU@A0O5nydSaJGJq#nK&mm*AD8q7wHc za_Sf$E5Y+CRGk)AgL&O7M)*wt$5m(P*$t^uvaYN#7>$dTZ#t?ViB8>8qg=JVR&92^ zr{%N>3UWi{Wuxw^yed0+C2$L}yIP~1Rz}9}T5DI+`>e1_$wbjE@YwR00$mcM4-Nf| zE35wDSphWkyXidD!5Oj0WX#MevTO?Z2|NZEj;WQg0&~oMD?&pR6`s{* zI2$CbZK>BMZ=U0exAugda9lim`OArx;Deiae>Fh_56onuV?kz!O_vWXAJX0Prmz^W>;JA zy%1%3us z8qAc!FASCkCJn4n=5}bQb9*$&vMb}IL%FGf7e#~a^Jb3^Ti`w^$NaS9-WmUmc_xqG zrm8cX$gp|f$5AEp9f7y1_6D94bO{qrVoiKqT*bK#ws`Z`@=dlsCqEcA3_V_!I?O)6 z46Ne2ryb}`)5AkA%W4lHMHy8!jkdmqOoYZc#j3e&;k6k#qC-k19-OR7G z>8|eX97by~=liuIvBvi485Z2XPLz){CKA&&n&z`$9dO;``^3f6WX4q_=6b7Nhz5a? zSE;0s3%a2o0RrVz%XLX4w&S%E@0ZkHdsQMp!pa5UYtW_BGnue&Bkt2Wh>PkIJzXEc z^u7lT-0hhPO6}Ic%EZN{rlzl?p=)XPc0BWIZVUHuNgw^_I(dej>GfgBYeFmk{m0JEHQ*_cYB1!SN;!ZT1ZH~B`cAU(SRcv_>II}z_s+A(A zBS7WeF--a=?{Z`w_Ab<{6ah0UoPk)`X$OaXz{=OA1Qgm&*|+%Yo~w+I=xtERmy6Q~ zJp|byTFiJMa1BD*+fdv2elbf7p)fZnfH&A7{3r~aXyyWaqwla5aMCv8_9E^kmhV=7 za3pWCyDdx8a^Jx*K!H1(8b)EU2^=54P*6`O|NecF9BnziInkcYpI|$pU3S}KM_;n) z!JEpIxYF-(>Q1XQ>G#Q1B&kNCVMM6jmpcf;@~z>$R}#29GOsPLU@9GJ*f{(ggkC-1 zEV#G3O#LM+QknB*rHK7Tf#60NEUA*-xLyaG@v@l469W+MXzI$^e7O)Io-s_#{zy4T z(>-7&z;hT!(HskZQ`?g>1HGK^39pPFyuW)4I7ROT6Jj8`JdN~eiXrt%1HG}_lfpk( zgK%#eIkeq9gO}gDF+D)b63`Bw<}II?hOJW%9EWED4%g{0lZ3Oh8me*-QCgpXkMb4t zBZq>5O4@WYi~sz&PdZZ7ZVS<#&9JKSS1VIzG)h-~hA*gsJ1r zYjG2Ks0GB}j$tg8-hGttYF~_NiY(Sqv68fM#6k8anV1>b8*IiG(<;iK^IU=TTYGY=~*^O zXOWYE7NzWPv)+hyHwFSQk)hrsibdBz#NH4PP#Xhx(cC#?^ar;I{e=`M>-uSL)c`i< zSSh#KE6NsZEr2j_gGZk;q5CutA`>xuw`b zEYzgP#(nVQ`wv*Zf+VcYh|g4*W0b_ zvc{uaMvyM`)#^zdB-Oh3VaYf5D&}V!4)5Fkl|=dH0Ghd{Lxl)Y1x5IE*LIDJ@`{jB zvp~=B5hWklln+;`0KB!VI)L)E*nrhVSZYSFTk{<30KZ(`+)&lFhoAK6z>2lffIou~ z?KY*MVzewD$Naad>Iv2(Ru2y4C9zX)Qg1}?FZ~ljB zyxPE72AxB&mcMiK@@*tF4l-2Z)txAYYLu@Qy~i6SYoI2Tb+k&>Lbi)G9$*&fMt`-n z2uKR^f?2G#Bk35RK|g1rW%h$2orv1NKZhh^;0uiitR_@&a&$RCoT#)RrV$Ijfg?9> zKOxk#R1&pC#ZirQgfE%xY!yc3R}s~A%kQ2tS>BEGkFQsV!tdGh=1X5Fe&>gp$yxdU zoOu^9xqSfIaSbQqNgAV}KmvWc?FFRyeEo>uMTqsdm*WIgnpPrY9OAp%Apx+z`+E?! zWb|FeDeLbB3#VBCMKUnjsCHrxd4_r`M2S1FXNIk2f6Vvw_qAlB+KS13ZcnR&NYdXtvin?S-h!s*Ks;z49%MA^w#li7SDz1sX8&6IlfXv8|1TVxX$&P^p zcT(V$%KDnq!>c=>N*(k!5tU@&E@8mW2o#I;g;MG%a^$?bZ@kwrV4gK0H?AC9B z%5$0rl{&^hQ+D#{!8hK#3c51TL{T{gKv%*CDKJJTDs~Cq8A9*M*|+wd3dgoaAww+Ff@ZoOV zWys2!disKp>E48f%iv>+L(-a4piDnzJMS&%= z>M?U1y-lpvXu=F8Xr6<8AH!|n+=41(A1XIdCq0 zg4R#5KZI2}>&VyC#VuFS^MpSH+MDt}O!Pjf%w=pb$AvTg?_WJ@%#crNhQ*Z`%&iR& zU%rHx%pi-xps2(G?M5!suO6+sd)9Jr@PzgEUG6uC+0WN(14%ky@EBUQSZ;F7&To1; z;Z4W;`tr6A=GM(`9$Y^1Pww-*=HwC3YBsfY^}tsp#A`E(BIrEebJSbI`(Y|Q_R7_( zUkH;mjqx{;$Da> zr0B3m*+xYkp0|cJIhUj$!~B|3ms81%B>jMxocXY0z`Ib6`F{B}q3p)OmVz~+N5gp! z(gBCpa1DA$?g_5iM>m8E&>dcku zBw6Nug~EK1Ar6;1Y>CX-8{&Zgd8p`tfs^6Qj$!k^NeBL+70odP0gy(i;iiphD}&MA z6;o>WdUKh=$fTCX7#?2qfqI>mQKQ604H%{R6hj;?)-^VTLtN+`gk_RJ=eM}-MfDr%Ihc*tCqBiwfvZ* z!Q=Bv{l!C(&$GcMe4lq8JT5H0>5&v$dB58usdBjUL#xG`C+CJux67utC;Euf=ShA< zhniyCGEtnS5S2*dqK z&z#4fqFx(TyXf&ZC(2HC#B&>ZiK6qTo}XaAw{ebDie3Dg7xOa9O{<<%t}S0H49=!Q z@&+)atTbQPT{6#D@@oMqWb1k5hGwOA6S5Ho2W{bZ47YXz2uMMAudWg~>wBOb;yih< zRWpt*>gKGOf{8FGuVAU@Z~9x&C=k8j;x?}O(KFG8^`~XnyLI@mBqq=C&gdS?^ zYg2u;d_qM5GMTw&kW={KhtO|5UFaQ(hNubD{K=osVV-xLgyQWqDvc$|xAu$``rYQf z4?|lI{F`tv1LqQ05x#MhQT@B1`M+0~TNY5z6>`ZH^6FOC)|NyZ{Qj+Pm^rZs$^nLa zLE{*}cT3<~u^u6zg+{?sIT=B)*nBz!P9?DHA^%Wn{^daMWA2!aZX zE*B4IGB9VjE#TbSGYpP?j;C6R{mWdpl*}I^{>RrDQpE`fI0JGsOEEO_x8EG2mSSYX zSvqZ-e#+z+(7~9EjVgv`_{e^CW5~s-|9<_yH~&AG@!zcXZ^8Sw+WEKA{r}qJ@R}Q) zh#eSHTGR+1KgCl!sA7Ej?-z;JuScX=kp=ps5abct^f3U>gCO#5$Y=6e=}m57R>T92 z0ilfA@OJZm8E|;#6O~cN7x59Z^zIli<#P=19y^4Ai*g9w8!x+#0jFVo^%)EFJApH2 z8M(9{5GKVkjL|RhQ_aXvvhDlE8R=Ur13#k!sBbj49)UkCec~>6t!(poxeEe;-J}=) z90T;Vbr!hy!7UrQVH;t{%H@Nb^|1cfkGk^r$CsK1NT~F~Xaj_qm}|o^;7dH7Mie*( z@EHEfimh|$d5nVRV*oXcQRt#8PnV+53-k`b$AG$=J{>-qc3Ad0JzN8E&<>^={7(}d zF{m(H&Fx=^G6ECvV&H@f{aELPJCf!uTH@ z5-fB^K`Gmh4sNPcgSw!TJUxqBo9H0wn2d0J_We#yW_nRg!%@}#02|Sa7BDx}%eN19 z+{l-~&6m;tp5Y%I_V0c8 z4_*H6efal2{ExQ%-~8~u!4KvJMt$kWfWJ<4j7WqVVo%n?BMNaSxTUv_6{DTD%c#C2 zgUn+12)=-85OD}+M$kK%{HU!~vu?u6OQA4(XLxIst2HkZA~^iV>-@A)hq(VCZ|<*~ zrI`1KT2um^mFmj#geo(x#trxrs0lmg5kN|NDEyZ5r9LT~qal}nWkcw4x=6#1R#~Z! z!qVwt3XFujUpc$?5L+4y6mz6Ei^Ck2;tyCD5G?XB17OAMmmto$-cyv|yE<3=>{}N{stMqriBPOby;lEDwU)X!^u%^0hUlbKZ zML?t}D3LDG6e$WwM5T#z=|YquB?3xE0uiKF>C&ZlB3){vD~L#!Py(TsKtc(D6wiA1 z{r27Ze*3uhbMHC#InRB*e<5qlHOKsoF-IR)s1pr<`lglrlLmhIC(V2!>|(en&A{LO zngD+eg!#V<>HMTYzJ=p^zvsNZ0Vs4%@D-LKIQlb~xCok*1@E330dFmU$SyTEOlDZ{ z!SVBGrnXYMR|OemtDa`f5qOIuly_`y+oRM?xkD?-!5hGxOdp*Vh_(-&7lZ zcuGuM&n9(7=b^r^o>J5&0pk}xZVDzpz&}f_SVLt5CFMW$_RZ;i>mc*}F@M|-HGEl7 z+6ND5@%S& zAFkX{@LZ_dH^%ZNqCMtE2vu8kj)@Z8C`(Pg2LX<`{^|xLiF%b8h)XMipmQw*)I~qD zv>ikT)8BrZg|{UUK!{hn+v*v$O0Scohjw^uWs zlnT>rt~$wk!P^>jGVCVmLT|8hqKj8W0rCzw;%;?vC)j>z?AzwH=&S}PsR(^{qk(%q z+%w$z8qrGE=B7<;LV*0G;EImZlkJu7QK3hRF1>z#8vHAqVt%ERDXY#2_-{DK{S5>%3@k#5O{4z%GWBiugn?E-6 zkpWFy=WCj!96ViQP0Xp4dyht?&1E%*P1P(pJUxZa9AApH3iGU_PmUdAwOz8+kyLwc zh5Yqvf8)Kfi8aH+uye zq*-$g`%3jg{iuNyR)s5P%#({8@s>Ge(`I{NLTK7ID&Pe)k(f-fC36$6W4^e7&clp| z(7D?#wCV!GwOK3^#^&7>vLaG?y%k3;2B`9;4Nhzdlr+jCe+9~&7Vs#M>$nnozsN1&ZRo55Etos0^$?^uPog!x$08eE#X@;zTVgJJg;!7a~N zpTep9{^m*JT*uf`h8-fu-n7wryr=t{Nc|mux_2)8r1={U_>Zdt^7SJiZ0&UKoCS(0 zOw+kALW`DVWlbjM-D$al3(MA}CopL^Iq7Cb)l#Pf+il$g!>E5W8+{z}+E1RSjR}cT z01kuRNP}Y4wv%6naJrlXu4>|-IblP8`K!~ZyPw!Dzf}=7ha^`3Y1uQa+cr15r@oDQ zSUB}hcu&Z#q>SqtgU(Q|*ul@k_}H7~`nIr_uG$yYtCLfBrf|Ku&oZA&rqm+kp3L*{ zornoWvr2&@S}*#Vb&R7?oG?$2UBiur38LmhY1mPGdCs4XF5o2gk@+Eq z-LKtC-=`a;te|U=Z)&%=C4wC>_zz<-Nv1Q-wCg%2^;GSKOfF%Msxn)@;D2^%TeiO~ z*VCFyqy@+cuYxhn``*B)29`-rvoB*)4GYhvqTpilJ1hkrAJ52n9<43PMIfPd?e`Wf#T?S?$6 z^i;6ao$zR6Yj#*HCShx7>LH0&JK>^}Rb={+sqpdK{J-iC4faFK;E=I@^l~k+dqs%} z+x>{BnVi(T+Y3AJWFHo-R1bJGm+L; zgO8a{lp;s-o3EOr{V!*&qm-gXsuOiSiF=+sd7_KeP&-QDWjH zb$dp8W>AU-RUbi{(tTJIIE#1I$Ej;#Px&dVpiVxE!S2t+tGQkVdt_x zMjhfI6IHHok2b*b1tTM4-7(X@b&_QOUQ;b43CQ-?=y_@jpwoQMFD?N%=~w0P`Upw}Q5x3q*^c1$*v%hm(HOn*v!9=4G_|&uX&{4J zFO4W%_sw~1CwIF$szg()3mZ80@{6MRE7fVQdu z#AGYHE5VYI;@+<)66&}(>>hFN;@rKwx~29R-9!Pk9bVWq{%dU)Id>66nJp?*meK~^ z=P`iKV#?1Abi7h+<6@8-LbbPlyY0Z`1GP)K^5l-xEF1Hk(=;nIT|oZw8z6CEsTw}T z@{`7e?O^98O+klQtKj2>`WVzB96R-r!R(4eIAb1b;h~O_a(n;@YeK3nuPMk zg#4s&K(ix*sGJbun{F9A7pbX~iz2K1ak#swte-t^?DMLl>vM*#)=ov+iQE9)zN;}G zk=BD+v#>B6i4aA6gF%PS@M}W46`67Und$Cc&Qh{X9Ipx@XpcHqPgi7$o_pHPAGFYv zB>adRPER8b2GTPz_2Fb5+`T#2%vrqa>S0JkGh_~XZrMyz@)Y;d(56njT$`jMpImxO z@*@AMClqF74WpO7>1I3X1>^l9V2f;oXxwx`3zw$Q_7qWTpO8Xq17RnGUWY_0u;tiO zFOJttUq!3n1uGc}O6PBjtBIlJb;7%E^W-(8q-@IcLA{-6wb>_lPDh$``dy-qfLL1D zso&72U?OwcY+fRBby41P6zxaV+{R>#GKkUDeb_d%V_HK6+OPAFueWZPH1N5(>M zY!+{rmFpGX1D@o(N0e8p|5%r*`f<2cU{A-PiFDqMXGq7J*1bSly{NC!`B|hID z#@QoV2Ntm9A){bH|~KuLFt((jt##T7>E*%ve#@5L}$Q0jVg z>P;4mA8Uz~=O&UrWG{rD#CRN!z57;VY2c*a#TTKJ#&C>6w%~-ZeF;K1d-_m6$QKkE@|tw;xlUdo1y0l z5-}7r^?DxT4`K}EUv+O-r&kN}KRI{jXw&6u!hJLGNz-B!cv1%E$<^F(z|Nb6L!|s( z!5An-R0-HqysAH$L$-6Vo;rdD`X|V{V#IjSTBCv^&&77_5al-Ep2M?sv2S`$_a@nM z6+zE8`o{IKt@{53iQZi3b za$&kS_$RzflxKlmU8-lhbMVV)DEi0P@MABJUc%ZY{p9}JZi`0&Y?4>MTAUBH6|VWx zqzY8d*1RA-|4GB;ToV&+gnEZp?G$5Dtt7tQS(S6lW|*5)KZjY{)VQ!=6+}jeYOJu6_7wju?LwsnBvnX8G z^XQjyUje}c7CJ%#F@>Z=Og-G`R=kO00&?qX)V3ttF5NIP>o;ph#l{{p({xHTPc!a5 zrdB{->}1PuzO(Gub&^bY=(ovo>H5Se;L@!3J3Z$4{Dq@)Mevn;tQ3->Ln z4n&rPkkdKH#!Lr5_3`J19Qbn}q2}NOyeFCZ5bj!cNYV;F^{Gfo!H2L6G>JDM=KiFy z1|l@zLv=_nsFI1C8Pg6TU2=1#hzBEGkd8i`qhK3&MAWfUeAPJiri;;qA}t$J*ny&x zu6t}mDCsQFGi8Mg$p6Z5m7g?2E6^RP=uaBIj-NE<+E|HPTXNj30U*|+c>@S+%x!J# z;lXp-*z9=*2_0e<#+-d0bAQ|6l|&d8cB^nr*Wr6-epKX;HhoLY+snP(C&LoXWvjK3 zKy!3&6z7hQ>! zA{S=Ffy-21AJ8z=;k(C7rL{ZRV~Xr#KM0{SXVk{m9{FqJLC>zsMDS~!>iL#(kbArB zQk2?&1f@(-2m-q~Oukl%0TJ)+-iGMQH1>pPHtR)kGM|YKar=x@UQ#Q{wC?>ZYjuIQ zxe$|uWkGoP=pg<51Pc>1ELTK}G7Yk$x+xQ=zL+w* z_e}9l)a&GjV+y(zkCidqgTvbR&9u4vq3C3&=(Wk|2~(+(Z~9*)Rr$ZIOnw?*y!3)r zE(UuX13$BN$QXnHg@q^$TjEuaISv+lA3uBuw=c&!j;V30*!c~xhCj9;e+apVxQVC` zNyBTmuQPFY?RRkVHl*SuV1oM+cCm7yc{jQi!S7DTy=b|ndfRpA9f${}MyYImnF|vg zu5g5D5ljhLh0_k-HNEOQWT>K5(FEh1#LGUn@-kHdq!Wiu)EStt)t7weamNfqLztza z_~uoRg$`SO3Op5Nt8&lYjeStHFP!Xiw&EYx`}|g2-ute7I_TY%DHDF0BYU@5kL(~e zI`CFN1ELW(<60mM-+}{A>zO=WTv!k$`l@~XT7I**q1j3qmdAw0!-Fz)^`wn{lD>rz z$;V5-2V5J{Q*PDhBAoq?9)Iy!harq(&hjmCp}q-7T4>AQT@KyGYZ^njPjb%y#k z%LgAxufoFwr^nXGsbmTFiM&HL4yO#{iL6y;m4UBF6p1j`KzR?h-3krZ>gmVrtBv}d z6Re!uVu+wO5x^(*_vil@;eD1WltS&rho*>`%7zBV-PQ3of;;@AiAgPte2Rwz?QMn9 z-D$)vM=D&h%JDibDyB^sdpCY4g+`TOnHyi&dh4jOmLBz%zod~qzIvOjDph}}F1ZDX zT}L$7m+!x_7>n|@;v(OE)l^iGL^WG|cG$9oQw>zSM%?er;-15K1Yv4$=7?~rxL1$r zh_h4%-9yr&BtojC=CNY}O?+AvM|Sd6pM7F#ra8d`C?L*E7l~b_G(F3!(I)M%*g)<<-GdTX*a39Q z-q*7wqR{4+mbn>RNby;l0u^QvclL#Ngv_6H|qmA2lXtFiuwA~ zGJZgCL1`q2Xw{*}Sx?-0?_juJZwe1B9I24LWFIwF z=}ZW<_(ofA696UNF%iVvjQ3!6B`AiG#E9A*rmcydh3Xi}Z7H?#_f;Qi@xchj6j~2_ z-b+=2Km&W%(VTz+Nv^n0ajq+gtzvh})*f&Qn+r_H7p~`;K2O&-#b$D4X2`wBtdMaw zM^!^ywfhUkGT@5f*(mU~oIRYN1UvGRCfi|jmjml-%ZjYo+W2gGI*;O%gw3fW59qKk%BrQltH3lxd(b?lJanM zXyHwOPdEC2H)a*y@s$YvM3uwGv>_u}Z{XP@Fr}eVnnr{$?(-uG@{}wGLTVsK9pR+{ zLWJj|iEen+`Gx!KZ$BS=UIkQwEOafnD=Q`oRLCukhrE7T4+WClu!y6o>o?R$iR{gp}x8K-5jGJxiy$MFWXG4}b#GIX=BBom=752RvTDI_3*Lc!M+|DA> zTu}%}2e0)4cXP{AvK5(DTTj_rj|$?@5u9tiDE;G02+LdzXp@Nj4mIYb!cny$J;t$@ z`|6C%$z7ny+|I+oNSIJAPmA=FfN|9;wwiT#0&tYARR=w~-$kX-KM(9yh}k!CcGs118_mgA^dLw>B$X+@nj zr7zv;LhsA%YZ(n1wWvsz`ofmNT)2Ir%rc`z9e{mm>f|n9H!fr8@p?VLXaU9ZF|4a{ zD%Eor_MFd8c|{mi`b)LVja7>k^t@;@(35US$?-h@wk!W@m%rsGV6wEyQ)SjbH4>k; z&T?&WthWk`NlmNouE(4$?~Z{Ns$Z`Q>RUDPV6QZLJjoNkDA{Rk#29z1o((9KWoSKa z4|HH@_Q7Pa$`qDCXL#_>D?#udVTs{rGvM<1`FL?+98K~_hxQGw!`zG=#QAEVSQX)eZTKe%H%3I99}Aju-> zC(Yr-y<8Jh>7g~Cr}zl}ZDcZAqnhG+g1GUUDcG#-kFjzZ@4qHL+(&FoGlrC|p)O&^ z-&9njPMVFCXT89!IObef3>1DA^e)kp)*E(?5&9)3Sr5PpkOFV6xfzdRQfoMq=M*rH zIfJjC{Zd#eQJD~<-L$(%3FIt~9IMyqa&>g&QkIf@@0NJ}X<`CSi8g@g@!->^Claj~ z%FC{snVG3n-aDhl%&UCfz~CY0rrLWo#rDi%YHy%uc)e(!smh)?Wz-vXqQBHun;3=BK1M!`Q*No< zBB%!JNnG^Ga9>}-v-N~LJtqbb1$*8O?!`)Q7k>XHWG5k?i|GXu?frcPcx8xfA>#7TlteH|Vgiovs3r1kkY z8mW(N3dTXfa>^kbO_!6@4HO}_4Ky6joDvdRaGpSS8!8^Bq*B@KhKR^6ZZ2Hdc&&(b zsOK1}AEWvHThy)UT5k=O!Y&a8+n1%a})YK48dpqO}; zTP!!36f~|U>KTd}3m-&_EH_Q-ca2-HJG1oQIeNw#BaxgoxIA_4Nh?xV0%cv}odH1#Zq)5}T=FA*xyd?^>ER8@-{snupSS8~r^9bWx%D`=1B;!fL< zk5>!K^WHyqGVcl+podZ=@RqPEgPV%H7Up)`mx*tRQ1@Kt2H4Hlm}K5H`t*t}j+oxG zZkW9MLZHot%cFIV))%d;d{HD~fAMPznw%W3Ep-6iw)gbxE-(<*?3Ly;T}5qbtNKj1 znojx@?;4WVD0!_{r@i#skX*^S#3(#V2l6agtSCZ)-mdP|=!Qe1oG3lRr%0CNFt$8< z@$~9okgxU(it^r140bo35P}a*nh~qQ`4vAE2SS`DpNznsdgo0JoQjr_6bLko>nTnV zkmUEDO6>k#9M!WX5r(e19nuXtt1T1?dpchVNVMr0WuKRGuD(ZCZ`Ala$USm0y{myO z($Aya#{*$}>}XCBr)OlLSUFtYS-dXZ2WezI^X^ga+DL|4u}fFocxKhmcx@as`B{V2 z&{ZP$abiX;?0&--wx)EnxdAu!9;H=OdL}jknt$ITl0&!Y^qNb<#l6qc9d__-A3wl= zspL}wteI)8mS^D`wOC&)v1XXO<(zn!x$BgDMG+Yn2VJ%Sw6y1SigJ{UU3s8+qv9zd zUD>MKR#;&Z{_*%L8%op|LMqj<6FjkB&&-R8U$}1-hq_#)629Ox^UWlz9q369?rdDh zavo9SB|`4uX-ydmB_igMd+Hm%6+gWe6!B2%RJm1jKemF+Pc6LYPG5?wh|$y|gL*&c zp5ZRq8EJ9I`M@(SZriUBjrbytPVQL8d+JsanSGc-YP0Ho{P2o+ScMk~<0Zxxt)&Wd z<;C8Log3r4RahygbXtq=&Se%J#-_+OXt-s$;l2^kz4VJvC|MR`E=01>-bitaIj@PS zv^f&ZJGPDPXLWh7E*$&X_iHh)IEK}za?of1FFQ`9JFrJ>PBtx##cC9iK|N-&W4+6r zpyM$8+|^XS2#yf4Yxw|PjLKv2N#O=}y>O&`fB6((UZtMD|Af5&1F31Y>F^Kpx_?O~ z1yiKJLy}mt5W+Ks!VFx!F%UNN%9ba5-mG7+z5V8%PkxZa$_@IHNAL65TWU)M-=|%? zbGQVw!OLH!8bRb059_CA5T)>OGv9tHeJ}bP`2yaf5^piw_KJ2!beuyu4I@*TpHkz@ zF#}2KJ3jQRQSC%QyL?2bXZ;u9j-Zpetp*M=F+D6^Q1x0s5ep|j#fVAFeM^~lv{fB( zUypLa%&eWyIjX@0&3Gu+&#F9~+;4C2uIG%+w{5QV-mB@g$T)~}AEF}WC(Tixb$4{j zBKv}`ZR7Gb<+o&_W{hK{gR0W{K0-L5T)s-WA8;W2X=?~VwEDfv*6ZNy{de$uc{qqOrLO9QRy#N(QtAV0Wt8#cT>5q0r3 zQ-aJsvQ{W!!wnMV$2bg7NMnl*Cf8cq8n>fgKPEV6m8PpLv@x~=xsPwYVg*e$I4AUO zkVeA_>J!8|6W~tOFyYFk^1Ug5%CGBTwm;RP`kWm79B!kEc{=j(%16Y__rF9o}qX z+(YnXW8Sx%pD_Ytikt6v6Opnzje<|t&kMOvkkJ0~ph`uAcNvk-Jb@VBVZlELuT>kG zRK=%L#o13SbXwQPQN3~#f2f&!^8H$Rg{v;;SeKi2Gy>y{+UkfU`j}-fRw95@B(&pxcU6Pu(tSdF-1?1DU5 z3jxNX%sO{acuC!_X8dOt$M!@ZoHIh}Dc@mIWWO=Xgo-;iL`vl-(uyDx%^>PPKRhSjt1jVN0x3z&1w$keEdc`ib2OS&lq zNE)unTzyXx9pspBX|sOD@{QZK4eTALqgbDlAkZf*hIP(pklk^R)F!*^G-b8Wu@ywU zf@E#GXrZSp42d0p^NKSUkHEs`N;UZ&H-)1i_!E`6>zr;`XC<86Z(^dy_7~IC9-Xz% zwyD=Y6Z=^J#hR6ZW};*{KgBV1NQ7`21=mh%X+DCfjMPwEwbD8{WS!4MFjlNW6vT z%`jOe(_A5F@(VIED=TH&JfxEz;jai9q4ID=DL3EF_9Au4C|<4#%9xKETUt%J6?kt; z<5I6#N&2~S_NuS;7&J8TLLD(bVmjj;4(sx$Vh~0L)BNd)CXb|{ZR7m(JnfGOYUXvS zHLpuK=h-I78boC%-jt+59Uj@U8FDMlYe`e{&(XMTXgpy7s$sd1m~@NnF|S&9C&OG1 z+t`@6Z1x7CJi+%|I3%KpR4Z+uS5Y#l(O}lHfS%1j5JAFbFrn5KyrsP>aUb(A^;sc~ z46WCVVVO~tN=?0~>k-c%j*C>A2soFt5G2S~V-<)W?{;P(rfJ~rXA0mABj%>TrbM3t z2UDl)64>2l+#@5-^v~U!?r}4y`%SSooM2&QmLUU4ZC^lcx@HeN9rr!Sw)MTbM%d#{ zNo^tmtIqA9v;}m zoz2mzQs;CuVJ$wQ{`j>j&zy7kE7*(zPJ%2V5vs_Kud6L4x*9c)iaNoRUlvdH73GIF zA6??`G*fRL`cbdTiRcgQZi}hfoP(yRTd7;ud)_S-eX7x|M(TJW51-U2Xf~QRw;USWcrGyJ*TrdHsn$E}t!rvF*=>3^Hej>t)8*5T*cl}E)5m(v z8)0TJ6t-$3_9d(t;o}Ep{7K_VLIdTJxM~a{ssJYu>JC3mM0OmGhh4^*kABO~pY%H1 z@8TSGxO`u4!Csaous?sJ>gog_s2$Ip?FmaYJX3DA$L0KsE3!B^Gwb$#zhl z{iHEOejKl8zQ+)&ks+NPb}8R2;r;R1d)u!ARr0sdPjwBKw|4RYo@Nu+7kA`H&d<3z zxCr>tb;)k%{(~72`Hy?_YK39-U6QAmKd3D7UEa|fRDMPdMmXO6CCK{k0oM6WSJI?% z3~9|YHGiZASudv+ciuMgY{dyBy2?vkhSXkQE?`!ZHu<2Sq7}C|BG*mw< z7{`VLvc)CGwi{pS*+y*%5p2vj1tBlB^NTPPL0Y@-=?lQHH#7Gg`Bw-b` zCl$W>W^g@2%Ya)VH|Mhv16RiiHNt%J)dko_SCGMQXPX138=zUK7w=B%l^Pn=JmnCU zw-U#)rg^B>JB3vfM05wsrQMIN%?P*e&NOrZszM-06-b+N%%U8!?bwnP?-4wI(&Ur^ z8wxBy{G{nF0(9BWe&2r5>>4mxa3ncwH;a7v5KK7+#C5j5 z=l;h^M*Ug11EvWyHN3o#dM*_>HqKlsh=&B|;)4b?faR+X9Mt?HIP?j4c^8Zmpg#Rc zb9OQUxnd9okev;Fz_JP^D(%7#PfP%HE7_{Qtb#}b&uxRMkrXyFZL)wgoGPvQpU{aq z_6a_)0-x0&cTjoiIpE(*Spa_oK%8x%Avo?00E1nI>Yp^oyD`5k1CfDuEP!p{hga*e zhz}fqh>%0fe?lWl`D*}%-+_wHh{9!zjHP|+%}!$b_8GXF_)w@jOQ5;%^}S?U1wn1Hy@cbNey z%hBUUl~R{*lYe_7|C`ae6qf1(!l zIUSiALZwB11!K5gdQwhTf{DBUaCvuKc>re;eo^)DNZu3KRJh{2lzig>l0F z5{$29)#c|If`N&JpXZ|ZqF^j#bB~@d?OrIw#SnOaDBU0tU_GmfsGl_X-^bUSvA_Wx zw;(d(IS4kyh&c)$#-N;9UO}|dA1*5o33gYD>fABaxM<&kIoXu*daCnCIxtzckHBjf zPxwx}&l+SkZ+`x$${ny%NImFVD__`4Dd@bN+zmaAY9xg3aQKrVs9&;z^_%e0JBI`T zk(!dbI*vZFC3+8*cb_V&0@EJ>PU#Hgp>8S+s<0*gn2>x%m@%!23{_$Bm_Gg~Iukv%=TB{{>C77K;uI| z%neKzQjgXHslK_t-5#9(+UdVtJpad^DP4e6&@JjW0N6;&8u;I!^BW+ej(&C16=e=o zK3#H+=KRqN;C}l9063JMssEXW{^AA!Hqalj>&9-K@Cg1Vj4FROiPQbpr}SL;jdTA+ zm49fh|DwwOcN|o#%=(C(Ro4QJ=OZNG#ygx@#e)=*JsaomI4Sl)oi^VVbt_C%E_Q0o z-mRv4;!<))@b&GZ;Y7MwG?`t$E9(MPglq;lCCOKT#CTKK?N*ZD8&9}YT+BsrCr@>2 zlcy>)cLK4t#|9o}rVv2%fNpyih3;z9ipBWyJs1|RjX2_dym9S8a@@mf*16OsWN1^SaFnK2Q_HSH-TU*#X)+>b2+uJcjAmn-|%b^dt-zP#24C=pzM5~2T-Mh^!b zkZ6H#15U|*d-;ife>p(L|93Cf1wI{~3Hj^O?@pYqBPB@ z07^XA5JEipm#W4GTnAaD5@cGyvQ<4y_NRLLt^*o56VMh{3@`|v%TkX3J5B}#g8xPX zz|#2pPW-mKIb+Ba6ZkA2pjs(JsZ#yQkT~YQ?Bg{Hx`V}i1ob``1N~S}_5Xk-9*0mL z`?}|G2wXQSMMC&!N8&z4EA(#wV?-Pb8+oz z%x};A&Y(u4S^fEc{)x`WeB{)LZ!;p2a<1N{GiKf*%vEc^zo0g+9~NCrpm z#Pk*nBU-1A2*4Vc!_`v0b#nuX!XdL7J*%$rzZK=duz&|jfzl+3oXyIrW*CSRs3%#J z7tN^VtzP41ol>hW~JX#q^qs z*0`qIBQ;}k(QKTTNcTw^qiG1vHQ`GcnH?t+`(iU`pyJ-n6`?-+r;VCFF5*9M-})r| z*VUqg8=ed4gdFM1@ru`|Z^hqR<2DbA(M>RacW$iDrqNWaT|PN>Q&yuUC*f^euARk$ z3avunop)tgJoA*O3lsb`&5qN%PK4pgX}=CQ&d8C4&_F4NiQ$GMg2!H&_qXKtQwj=Y z1md-s?3_S-u1Smsr-^*Gp2r(gfl~8;G<&(EG>X43kbHbtn^8_|2p3ro+dLzCy>;=$ zw-81)-KxT%s~ljDW@o}pd`K|dT6@w;om&WR5$hvRQ0eaMCyLz^a8xvL%zx7--g}Jq z^SrCzJ$G7s)4)#}OKrw=_<7`Pj*Q|td_i0s89cfvAyjQ0-$!~jLZem@)s*r2oZNxe zhO+D})cCdH@SZDouf^&4+StN_+;$rFmIL6k$OzmpCO88V0X||Uo7#6@)TSYPF3!a9 zLgLJHf!le#PfuO$xY5x*$(lmGL9^B%qd}+0((_^r3%;N(Q}eoVYio1F!T{%I|Bz=# zUh`F+Kym`-ZOIh)>O@E}bcT;8OV@!FQ6G69z?ESOiL-a+wY_4aTpM>c5S$#<~!~rOYZkIT8LzL^|?}!z4Fvf~j{pdOxbX`twrL*IiAcWJ$`a-SxErml7FZQ8uw3TLe3xpN_wV)~LH+^>tumNn?Y6rQw{{LYyz% zlKr7ms?@Hfai3J7TUEUY)9$ekShGuW#cR-l&b(5!?Mua+d#+UOJq?m2`6o?Z6ms4T zP7L=b|JDMy&L0bciMs{@Qi);DLr7LSfO^d3Or{3|{hTH_L&(HQ_^b&~Ty-@jwAnG& z>+br)H-@7%{o;;J8^-p7)6@K7a2JF0dnYZ=#IdP5M#KkNMIJLv*r;clU?Ow_o^?eT zG?5)x3|uG8bbzk|lvp4d&}k?U@Yn2%vwMhhtEk)ilM)J`;wX?&%$GQDl{uG}6Rf!k zaQ!!Y465g`Il!j}9A{!Dd*KTGYbjP5qtcKnv>hisF_S7${gA4X9=SHWs1w{4)!ijP zBsGWYy$shfHHR!ZK@VNl{ZJdyU#F9OU|Y~vXl--G!awY=Fi^qd>HNT{)mKaLU{p=Z zvmp`EErO)?_g?>b$FgH*N{W>lda`z+<6#{U} ztoqxoQ%e{;zSMRPA73mUi96ypmg#V?e#$cEZp!qB4}2ci->N!fNLW_v3;gLF5t`#= z?h$D}93{k@3WjY-(o-+KR*Y3`s4Fzk+)|m~l^K3hJF?50hS6p=A>Qk#Kdj4@o!F&_ zqcoa|J#HN6s^m|9cn|2Bvqyc83*YQk7#eim-n?0?zTM*j$41?w!W@w}AlD1e3V-eZ z+L?C*PzInPv;xwGyhIkk({-|QTEv!6DoBsMJCe1b91{gG{lkTpmD)*nPGC;{PdGv{)QDR2RO^_Z4gE)evUq3OQO>h8#R`1WxwzlM)I)@ z67rq%5Fh&(`1BnM^7V_CNMJS^waM?*i9DdX?mw95pH_YFS^P8nH_p)gl@0|Z$((yp z1(lA4ecn*^Y@ds9lamU)f*;igVHa3#J!u+#c`Rx@GbiG5(;pEO&hV;x-Sp$IRda2; zZgu#rO)*288%h_0I-R~`^p;t@73QO}vdS*8I7tk74}181QXjX}kV|}{X|h-gZIL0! zf26IvrEVp0L8Rr2`{d^!HxlZ;J z(AfLJ3jT9osqlspZ;kN8@jr@*MJCBjTMu||F@z0$Z z3ke^o-ItHOOz!{I|8CjiW%OPVqq6+vCf?sq{C7U>CdT5y_lU}KKWd%NqT#lyI}lU* zVu^VEhL4ROqfxZ&BA`?gtH<-lJk)8<$*xq@QXxQXJ#OSr0@Fd*_`Zw{vK1CPkC$!=x zkLL(;%eYFynuY{Q#A}XXwym|R9^Y;@x|(8hWQP?mqx0dQGCknfgTs7^JuvCq{QKV2 z#Gj~_8g6*E>CbYo8HoX7<8fPaiuD)~24@pw5Shn188zM28V2{a&CA`GxVV3<9oHi- z8vD^d{ekXiyCYB0cj>8J#BZYDcM(-=P+!ECy`VQ}&GE<8-*jg3R^viKocA16oU2z4 z&1;f9^)_s`bD}hwo+=5QKJ|(A@N`If=C$Ew=UIv@WLR9eJGOM9tH|^*t6k>@wSfDu zYiAk^>CPOv^eI;pRt^`LLHlO3fSxopngi*uh<$U&s3#Dd{)6Nx1MAKki4qi2+#5bQQCI2-=F zK548G9lEi5P;uGPz~+9E^0T`Jub*6dkm*&}hY+jfu0!ropu~)=-|nB^Z(W01%{F;v z>x~}Cf~5ro`K4`Q{Xw~p-)9o8)OlCkPnlZFwDw6+qF)kx`O(m`3lyn%j(BM<28-kb zl2kI!4mK~=ll*b%r!@I_NE zbUTN|>68Ff%t`kM&!OqanL9@pzzNzJBxK7z)uP!D9jf4*L)6wCDwo!TXbd~;ts^S! z-)0jkDxRmal!+Vk@?!!N6=Eg*{ROGArWe?m%8zO{T6=#Rh1`7dPg zUt;!;FiQ;=4cPdjM7UB#|F4huOPO@|7bX6oR{!s!#Nq-(D(Tb*_w5SV`q|TwRCj4s z;5u>{Ta*wRGXA#k)=3|0;D#VlN093a@RfJPtrzAW9BN+!?eDFkjL7gc$lf-1_o2b< zUI|@g*5JP^UOzi{TW>2}vMG@Tcen|bHr(pO+i&1IhpH-To39={O3!{~mPYC3?3*`` z1VFNNVWJ>&QV?dhBCFI1ME4t41zGT_Kq$jbu>n1cRFSNApv@h=Gc?p#xP$gYatEk( zxy{_{Q>$2jx>Spfi0Fs6Le(jZdUihojtLXmj{AS+H(IW%1ag0fb->&~<+Zjk zo5ji?4+kA$^S^Rmt#7IEvJSk4%06P|P0z}skaIdd-Ni5`Z>nIo5@XbR)$(QYw%@#_ z>0x1xp&{0_U&YB<$iTDI95ih9Qq_ zmYw7}tKBYUc85|duE;YpP^d90OxEzuw)fD?i-i_=v-oCSz&3ZvpYOj|oOtzarDdnE z={6W;I|twBAjy#3akg(@zH^|^?_Px%_o0F;_Dq88wT|V-VvPsiHp-AUUR|FYPY-{0 zm&K^?d$X4E#w{y#Qxth@`oiYS2}%R`2nG=X=*4(QNNryO!tru_YlwdG02D1`ZzA}R z_RNFePoIC%w5EYe&>uF-fwVNEjmc~vwq%vsrf?sm8-B`<7#95_ZDla$CgCZBnJf($ z{13dG2}5a0ql!GMMs+wZo2a0TI7R1L<3}p@^`?#*>P#}nBo$R|B{02PLOyhBkwo7g z7TYMB4MzbIr$z4;trKOESZ*9@tTQ`Q?yv@V=?d*Z{av+5aFvyGa1GT>A)V;L!0G8s@O4@y|@ zdBxfE=n3GRiC+Il*BHc|?NE2f0h1|wycvDSY9OFmf=2*$?D(k=>TNdlM|C`#FgDT) zvdWO36!kl|)F8EQKB5#Im`eMCXt||?(`u?{v&S@KGAQ%MLFr8_RRZLFZ#vpCRBhYv zK{@y2nNUzeN;#tStUWh9-Ui)40*6|#w&4A8izZA_eWe`)qZ3YZ3V?K_o2sZ=n;`gwO#$<(^h#_*RLQ`1w52i9VI*B54w(>Yu%0j9Fk zF_cp~Aq?(lrpeWw`y+0!|27c%cPEoj=*p(bHMM^;5h;6-T%*yUdi| zL;awHM;4`?!)EdaWX;>2I+1Rg6Iz>E8d@2VS=fY)9n$9IQCq^FdId?G=t7nC< z()edfkWjhgNa-l1*}T+(MH`Hbx)*IdUa%2AMi!z9d@aeoOG`8js zbRRIy=vMi_`(mDzJC@7$uA6~HI4+2LEVPXC1J0C}-stjq8^?;~fy=o12*oSZ?{H97 zJ0XkJ-I;d9FzXCi9;0qExa%khJ#d%*;v745d%9-uq{&mR`R%F)s+E|dN&?Prz}&5u z{l-$A+XonhqWN)*xy4?R&a7S`hTqGcLv%+4!&jfjip{qKFP(fMv7{kx=|S5bz9!3l zXw(j(g|%#d7a^>lrVA28Y_gPn*qUfC#tZbxnE`9Nr_w4eXGtSNYLSh;cE|=_0$otjdeX z7Pf*6Xq!*@7r-%btgc1OyWQGr+Y1 zViqMsFluBY-#PHbHM!#z?~~3>IMoH*RH(JX9C!v`8=5MgA3v`g1%C==%tAS6hv`WN zBEnz$Ub6KT23wzyeX>r`9GL35Xpfb~Xq;Is0j^t;{M=Z)I}Ap8fmAyjGdJ@`4;J)BV(8WV&LanA}mM1P zuR%!{Co5t<%LO39g}^)Z{&!G@@j>X&AjR_+Um7mVG$uJ+^TtNntjD-|5(eOHkwxcs7;$ z{1EkwLo0onZoHwUk#2pnFI?ZhqWOMoNn$#~oxbH$?=)XkKFrr_oRQ0Udy4=+K|X~; zbZnh9c1Tr8BxqnIfYI$;Wk~x}Z4oX12;~zOKFg~Mc^@C}kRCU(@$PnUba#dOs`tZJ zA5dR8L@P+&8ZLN0(<`F2mJZ0{-DGF+uNc3hWR5q$RVc!A8a1sdKOvgwl*^W5Hsx@x zsZ!Pm95qm4eD@gAKu&q z181k+_Sz@hz-Pr+jVKru!*r_ai1x)^^V)S|qR&OMv|B1OnBPZXd0umw9Pfl_wyKc> zU}G~ci%DW$S@U`53%fOV#dy5rR@^4wC#+%5*}BC0 zN6xo?{}jvQ}?CO%zE#pgW%*MYJEF|I+cf=I9T1OC$pLnLK-M3U}#KGCSTQu zfVEBdDdL-l!D|PZw?$N-5tMU0zC{n-zk8l7%BgznHBE1TiMyY)7YT#(SAfn!f?g>$ z;8nX*Q*hYvQ1<~AFVntu57bP~`pb*LoXREVpIg@Ii)=D?h<;@M^zmRuJHJ^2=h>-nr>MB5IW zy&uo;2KmBLL6B=95n)s{QWI*!Wr3n!aWY(!x6J0)SeJ;Sefj#eivJ_0?30Sg$c!nJ zg`tZc{Oj&3{{BM#y=Vd?AKfq5+$vDq4!cV1!CQSg4C&z7ieT=~wx?c>F~$+fZQPHC zC5e1A-*lT+!I-HF1Pi71qHL0GF4_eSy}#2e=He$w%*RJ-2es1UJ<7|8`*(5RP{u+8 z^i^R%4TG%SxPmNH3SSrhoZ!hmE+$rDs4C)A_3{sFvx4 zjoQb>CW>Z!0@m1tv^~_9$@{qrTVgg9r8z|1a-lFqcA~RRgc*cI)i>XCO8mnI(PIWo z=ju`S9gY^JWRUKBn)V-RuExMOq_fGIlpHC*U93M~Lrb_}+9#7tN#*V_xsK;U_cV)* zWLwq9Y&UM!#r3kAe_~Y(n}xrgVJ6zoYie|Io(O^Ft-Y#Cd7ArJx?1iYL7w?aO%TsO zB4@Wy0z-JinIqpudmu)7x`fuDOPh5RDYDqYL)Jt^b$3I!N2?FE{BYCDwqxsul@igH zq-H@@34jVaNr;OVMm(T?Gx#u+suY;??e?N$g{#=QrX0gQL(${SPghHKAR8)3TzA&# zW(oYcDQ_@CEB{(D3LcfC8(L#-P%9rQx|gLKeTT2M;eWLE-BC^b*}kYKDjag-jN<45T*B~fPjGX-lW&iJ0iUkihv-21Oy@p@txoN?%X@hT{ExE zT5ru;{&7~|99B-)+2>pKr)-%E0y*sbF4s<|jQm0{1C%iO3nXV7HW>!qdh@~4xAu^}_Gp2zI|JH|#-4{`!`>ca zC#9*fYNz2fG1qa^%Uo@8!IBIS<$f%N`JI`oEP7fMmDTlAcOqSSomgt>n}h9OqXx5i zmj22c9LA{;x*BN3LfFV^Mq6^TXbG?yFjNPxAba*GVAghx#=&W{jbF5EnI2+{jMx*$ z6z)978*cWiuG)Oy$lC7@Zp~;p4f4Cb2p7ZX1>O35D8Tm^=X$0FGjXC3D#~(CdvxXQ z4-Hzqql4U~-p6FxJXAH}7kG2*7l;EUXBu%lT%F^Y!Y~t?#BJ3zWJB~mI5*kx{JaUj z|Chxd+Z^JYP>DYAB!OW!Wt(R@thGtvc5_83->daFlXmZmv+w0jwg>_?0D@izc8-fw z8GfP!ZCpzM+<6LqW_i|6a9ap>?!kESp%eK$?ta-}>bJKE&8JH4Hn*UBSIcMyF3OuZ z z#KAtgOpUV+ZiT1Ek@tEBzwz~Ah$wY;V1BI5DgXgv)2ni~lRQG@Rm=7S%Cgfjd=?>d ztO|^aRSh%g#hr5Jn)#MTK*Ot1f~wJjkPPK&%z?&@F?)LYa=vzw7|+B^CL1AzV5tf< zk~8!X5s@`b2+tOx-IElNJvx`&W8&o$zu%oR}_1J^6269an)gz42Z zdr)eZ|F~76!>8O0uMMZFq$&mj8gj9H=eP~f3!DljTrM!JB#>6z&6$}$ zqhvk4plXoQeD^B$$G++?U)2RFwkjd1(6WJWW(ScIpv4MO@L(a8eH@MGMxCHPekP1! zre8^`t(4<9JxbYGC8yly@q3YBPWhZIW~N7m4qYa)ZufSo{pSp%x9+kRjIG_L_Tt-* zaljSgAHd~LqiZt48|{g_6RkW5GZ+o3JB2;vG&;@B*#dU#YAAAN%GsckqxPW|9M7nr zmR!-6U*C-fES!Q_Jg9SQl?S`L7wbaz_#wsav?{sf#oNvhJuQhX#p1yjUzm!-Z z(6OT5^iPU4!R|S zT0(1U#_zzPUL_0f-j<)pWE|CC{byJTV?RDDUFTf&;JkSnE_-&7a1JNVFWKFaRu@Ll zEH=TtfrL*%CHd@98zTK@*>5_5Ven#i36c`>R~JZHtq+Xdj2l}m4n~1ydKNvBcCLI< zUUh&->2w}4l6*8Ca9T|igoWaIoGue&uw>mjSjT|OG)w$rjBMNo^&@Lb8?B{{lQoE@ z!+KI(Ec;c}B+4*miVwoBa_5OM1m)8rSvCTQ8rKRX{n{#k16!=Mn2sxtm#im3w(bZZ zJZY1j4u7Qis2V8*v0*k~-YoaP)!^A+RBMQcBnXy1xp2^LN>RNDPv3;dmoGNFU`bzB z0a3r9S8PokOmVX=UswTkfx4nugnWa<+Zk z(IgGcA3m`$8jsX_drD;mNydlk~*+Tq$=1Z4l1+i*rT_E2}Bwsh-g=bKM)#j-~#} zFIDfBS4B!s;^)ENzNVFx`W{;#2KzOp78`5w9dr(xj5V=j5q^TO%^u_hY4;VU8~G8f zr#_ z%J-rz5zq``B`L(s*szC+Y7xxZQn)oYl`pzfk25C3G`Z8Y1J6mwBhe$uHwH;)Of+SKxofPPIMnz8ABf{k%9v+%Nnh*ziGK& z(4%&!fjyEVl%gg*Jp^}Mklzdt$;*0_SgExTG#fUxGsUwzN~4XH1@mGoIMOYO;cPfHl&!jwyc#lw-nmm< zPP1eqP&}vfyi)yqTV;LN#w)dEFFlgXg|0i--|RAh+BL{wRy`g}#MG?0t&_y0EUGI8 zsivcfcKKoSN_DEwLK75kuaU>nk~nv}jyArvkV#lXqWb|h^&5#MhkVFw+oh*(&6;YP zd<*L8j-MqQPY(ml?UB`jy?YU@LKD&&%X^e?)rQJ(f`xk)WD6iCcd$#d$!1s;%DhE8 zvrmId$OCA#!3|>PlT1U|m1G?$7g3Q&t4{n2*t9Y(X0^CfQ5CE7)?;eARuVjy12k2= zcc~A|;ezejIDh`g>p8tjl5j=|)sFKrDK5i#^((*VAj^}3r^bf6Q@ve{X?bzJuqJ0$ z*;KlYS#=)nNE20qyh7F5C^lGz0yb?wj8Q?kguKg68iq;1#U1{AuAK z7o=4jE_PYS&&FS3PvenQoiFDzK6Iq=D_-=Afhy##k2scV2u1*WlVVnC?B*I^q(=l1 zr=Akn#tDy2BUaxhCZ!05ri_h_yuNi-)7^gCbY{$m+!BAO-avf7QPBzVGO8a~;Q+Q$ zr&#h{xvQd7!9*5ZLIKj>{l|9!hDYPQf!dvt#fG#?qCA3*Z=UHUN353;Ud1(YXep3` zzazsC=7J%u0Hgo2#vSd6F>Gl(;=Vh!=@7x&)thy4e+V_4OLH5dUlN zUD0M0{B?pX;7C(kP7i)jZeTkXS)oy=k_StDHf}GPeCv$no_*;qTWzyLctOu94|z{` zR0AzHcdLQY2*iX~7u90_HVKSM25h^G9kZ+IzU-BKO9rH(9t*{a-F|%AM9I?+(&w-! z<0@Q|>+&LV8*c{XK#MJo>elk(u?2r5xnD3_WrwpEaeaJn2}eE@KfKT+}th-g;%v@pxw8xwqyL8Lqy z05b>Uq&Vt%+cq?IHe#&s^F$-}#a_dfH9cIM6W#?E-n+6F45uo$)kBbpK1^vE!j7*aUI+$Xg*9()W!XkB+se)LDzV<4K(4TerxH+d zcs4X>mS~lRN29Dp((tIm3>jlwIsOKYc}=`a0|yp$$NFZ3UP)z*;n)aT6R?s86!`u8 zg|x+H?w9-MHr_@pXS_cMUuvGTjm}Bx`Z9MInegT?DKVnqW1+2M%2pkb?1>?v@W(bD zBuN|=mt@ZX<{EQ5#1*l!IavU_?mBr4nv3gZS!GrpMW>nTCudWXU!#gkmiVC`$#jT3 zV}0}&o%Q=@O7juxJ3Jt|dXLtx$K(t) zDWqA(tN?<`f@E9{(*{qy>7Hwry;-B?ViAGYxub+vCOAgrbI-H&$r?IviSxJ)`t$=j z@+VNwZp$WPqaIY#Q{SYe6ogFqmz@(D6> zYN#%$zwArN=JxnW^5nge151yBgep4h;l0M`F%3K&Tr_D#tb92cE+^CTdQ~d0X^L=a zcm!GWduVYJWg^nEv3HC5x&~ix)I)m;1cw`l){ooKf25`-|wZZuanpI{`x8H0k3!t*F?vT&@&s*S?VHG%uv)8J~? zg#C|*X1+0=SARvq{>uOSSH7|`0X-f8fTBDMD4W@hSC`{{lKdAo2VB!UPImOT-gl0?sr=_Brq7v_NA=a;j;s-|r+(A|05snF`?2Ev$KF4`gY*9;IF)-GD|@Dwy?^Mg zLoghnpafe@r5OY2xp|JXMy2a>aZyFQWeGn?KqpD3@R-wUbS6cT|HbQhM$w`~tMzAp zqj2v3d@T|}^z{OaYLm}^8dz&`JDZFU=?OhVQxSs%L`bKPSNorm+L$NQW(n%bAkJ*t zf~sw~h{gbUoH-9rrn(uqu1aZ~9KQp89@W_<$==+SJNO1nYOeuKdC%8_L(q@IkAMIG z5CN>Ydx!sDU(cn_=hwU2?M@On@G?54<BvC3#F!{*ZG3&LEKj1^s;dn)^oJ+94FWa&l{a}KJtE)pNGy+*;DS3lV>>xB zeXU-w40q|q)^rVCO|-er?#yE z^=_=>?4C|*_@{GhXBC{{5^KnwfKd zI2+!F1$9x9wI5S=7u$I&C-E1z(CqI^Fiz(^Fcs#_OT+qfc(;7ltxe~`98M1H(r~|+ z$n=bhc~Hd6j=wB=F6+;`{WXjBf09H(Ydt$PUik^teunF|h zK;k+@Ep;DbwwLz(D1EjBev(@d7D_-B*0G0yo>CGS0475Th@ivtdO}kZATayd-N2~6 zQ4|h|e)xP~TUnm;r98{_BNFa%QQQdL1TJ@)K~S^H#$_8c)l51Gw9c~RrpNgds(;z# zX1AyfR|q%IKReT}9i7k%2E=5-xcg4mq*FG-*rLu@dSWbVi$1usr#?Qjr!&a)*{JWh zkvA!rpaL|^xiVurXEEfN;#lawvYy*z^(CE=R#M`KR~}^8q$Tv1Z=^mWDx(?{gtS86 z1`KIEt8z{N9~bj+5xkUvpx~hzHiMTOmXH(I8gPz{-Ljq%baaCG&a{+av${t{P{G+b z6%EuLdB=pi$=l@*@?E77s}EN*3rdx~6z;}WCj0|3p{LR~`Uf>oj|a=V*87@BWdu=L z-D8AlcZ@9+`nXLV`uNbNsgQn1n10EEH30nJ4KRBTacYGyw6tEH9qZN6FZ<;+>J+tA zl*N=y0jIH#ls|7nV%`dIolEgBZIrnT5TPmHR+wot8D?4?iwG1IL?f+)FRjlm{?W=za|{Vo zt+arxNgUw2U|Kw>0uZNSPQk=h6=GO;%GtyJe1QIVi2ma!X#YvV#xi~CqX;A-Sg$A& zw10OIFZ1<)t&(2hnNGL^ZU}Y3QToRI zR)z!Mj=l{5XGPa^a$XAu^vBZ z7%P=!*m`N}YNtoDiqgCQl0=(neOR3}uU%@Z7;d{^mY#Z4g){cWzBzKiI=;@aCoZM; zEm{xZomsOT2c0c{n3Bvp)2iIR-s2)UJuEA68tcc7LoGu)x0E9&lYo9&o`nVk<#C9v zfwua%J3gpkV$DD?D0OGa$oXardte7VXDr+ii$uJMf5iNCEdI@#*05s0nbN@E1Uf4A z<%HI%YUT5sjdT8nEZ#_sdXjNQ?6tWlUAEPQs5y(<3I zK#oGwO4mD*W%*qAwF$SMBy)DF8DUAvwb7kjtXD>>)p@ao0tYfp+YbCBR^Gg)S?3N& z@;w>bd*8t&PScxxmrvPNh;esFd+Rg5IaxH}6(r>6T56^NaK z)t@9JxcN>kSkK@ZYlhsbW?tfr3S(O*GP&R>PO$-uf!G@gy**ZkZ0o2YO?X(Ml51^i zb184fU5^MPK?+}1QwN^gbJl*F>u2GFvpD{iJ0J@b0Ahe#EC*&t7oF|wRVo}tGJuZ4IV zYte=BUhK$tbbz8J4vVF;VmCX=amv-udn%aCFhTb;!`1DbMI*2Ht!Yhxp^pnv>e6|7 zVV;Se=bUco#Yi=trV?~OJ%ZE*wrs2Js1RxSHEwAQx}^~DVC2Wo#q<5w7JQ3DCp1@t zc&Vx1-0e!G8q##+6;6pBm_O}5J$CShlukU_z5+(z5Ci>iLd}W7w^i9tX>psv`EQ1_ zwR3se>S>1;$L5H}B?DpHefEK7`@s^r5)vgodoe^BOh{m}Od()@oqie6`V!b*s&J$Z zmMb2Cr_FV5PuWN#Wn`U$FN&Y% zQKAy;Z{j)fuF$yC`vxdzvimvm$e^V}v6krc7W@_QsaqwaF4`0rBAVlmUpj zv-CAeoyQKe>7Gk|qEh*ru;La^ljx0j4->?Dd*a4owYTWjVZwBm z2GvZvC;Qw+421_n*=8u?n??6Js-zfchb&xGJH7(T* z4dd+kPPf7>yaZU{%%8={58PEzCRt8+?oAC9f^8n%nS$$;FTgDCx^um*Q9nC&1o^hL z?1QT*=HuZn9m;fWRVcNSdzWqP!-^E5+W};ufdqM=)EvLfDp(l-%o$;a%xPwNwb2&u zAZl#raDlIn>XIB|RT9|Y?{!>yjXen7e(`?bBtKJcu1TO(;UReVz`Dd+iB|LJnLo;D z|AzMOTV>{qM4{GEJ{BDL7?0h^IR36Eo9cN^F*8$3I*cHO7hn<@-NR$U$;84|eb zeqWH9z*45PN>1%`yTB)OC7q7O;)%BX?n@yThV%~^^sQ7jP{!(0nt9#$eUZV0$+;;c*su9ZPKB%KhcFaQjw;b5TlL9d^k*y@aAp_}327O-)vYhM z@~jeXx}mu4E7d)G6#^_d7eoy-cKcsGBfC#(*__>elyck=GyiS*IIL#xRinnv=?YqX z4=(tVgqE0@k5Vc(F;?igSf-ScH%amO?OQIFFSTTYM{;OwJfC$&4bopEiotM_0RHfk zg5j{9Ixe6Mbr-&4EC7>0;@0&`SE8&aH&esRsjXujI*Yh99G?mMLqCu@Oet62niW zdE1TCL1vf&I}Lu(w>C0egU-}$FE%gKODr(@DGj9C0Z@G~+a%&^3mkyp+G_z{%0AI7 zyORb=*6EdDg-u<|VbCNxuu*4yxY=dF0L=U7$VJ$H^j;=+w;J!G`1~6*yl5@VM*Im` zNC`iCOY=nfz><_lbF+nAL{>O$|1BMzJ2QiHPlcb5Z0Xqvk0c;$(7Kze(`cU+ryxJ^ z=8O&`g($Q6ggv2-yH#d_?Imhe^gddV}4qpAGFV44h3pM*>W7fekYSqp#D?xoMl!VC|Oof&mC#O#NK9_cO`X1op~L zY@VB(ckUsN=9 z@t`u}vRD<5`KFE0dzA3!XuU#)HWWQ9pchI;xL*P*R{-b)R$bmvf!fBSuR)7FSxhx25Un~&MQ48uhdBrvb`eBa? zn8@77`CaY_t5PXj0APV9#%$%8+yy_@*{_TUgm)LNhouf+GV z;BY?S4q%x~C4-$x7TD~fG(&W%i+AY?$LO1uw!f{R&k{YJcloMOYug@5lCbh|I|u%@b6-p1NsFk)uK z%>v-Pk+WT3esEs394O%bw@l74gpS|Jo@u`1SnEeNnq|x=Em5i87NDlV=a@)pEBi@0 z%d89G`bEmmM55bHNAQY`;0Ks@M+bO*SZ$|ttztyeF{6=mbWIRx18Q@${Q38*ITJh>I8PU-;q??nudeEX0`YpPfCZ|KMKmk z+wHFTPOap)Z?5;TZ1#X=GrE^JaTw%PX-e#c>GqWB6H*h_mhj`w;^IzcQoUDmV-@9uYFsdmuV>26t2>$sq-B+jN(l-Wzb>JkP`7cQA#52}0lQbV-J`Q8Lt z&J^@RcrQ@yh{GCnqDbi-n}z;to2jslgIX0>mZWfL@Rdp@8ges!2F>AE z0Yb0#Ri)cvsQsJfeJAJEP&=qX*nCf-i3CO&$q)qh$>9oz%LL3mLao!3F%bl6{Qa-3mz$H&l2b_^V1D3PX?fn_(4k=1YP3uV zeqT#>jfBuOHSnlM*HaRO<8lfwmwtg~f^lw~?5;gcTw+d>mFk~qPO5+y4?9q-$3TU- z;|@?B0Hu>s>Pg+`cbtR3&ETo2S< zz#yIcP1E+oB(12Mtdm7_4LoR`W@c7|@_+FBr7a2#!i$T?Y}SqMZH|k8{3Ti5r+jM3 z{EEFUD6QtsDELLaQb(!aq4F(=ThxB+*{v&1jL=By%4M*{^snTc<32nMIq8%UQ@!br^E(?5(I^SW|(9FqEc!b}7b@ z*_3v42co+jP4Dh7x$gTbd?zE(%hVC1gt_##6Im+8+yz9|B`@_bpEw$p6w}DbuJ1Of$wi+v2KZ@}~5a|NU>p574*N1*QkiO`) z{>{7Zj(SKsUI865FObpY%Z6PWEFG#R-Wpk>3P>dKVk{EsLfI#)>uc0+Pi0#*ve{6ypaxOZbB;>+vNDvEVt9j zS*YW-N1C}wpT|3!tYDBUhe;jK;B*#D*}G$5n^wkgf$CJ1`sd@)Pajs^av?ci`=OtN zV)HemMjWE=9rVx3F!7Au;oLd@ZK?jN8nAdWiPs1jx+^=ve;1Lh*fbQ{>SyB|DZH9; z7Qg|&nAHG#VH@Ok?xI7@+VNdPb^Pio%zS?hHfa`^zr?G?UP;{hrdLP;;QxO;&Zu;O zu*(^T2V}(PM=d%v5Y7pHLbWqE7p`)Pb&C@7NshhCHqeqRO1 zZ+~^>{2h(IO`YG-_#KVxf8b&JJ2(D!a-%5vZy{0Eza5Q}Ka@l`7lawGYE(yly!8~} z4l>m^_@EpG6!~BLeIe-YXBTCvAJ=!0-{wH4mkq5iiK=V5^}S87VO5^LVKX^`^G?Y> zt@J~z_n@$ifUTO8?xqPKhb_AXB6#!0r#$Ma{U-)TqO?0f|I7gCFRl*%%xUe}#J|8~ zNL`6u{`aT&(-rS`EdI+O@OLbJ$Kp@7z+b)ce&@yi2YGR&$?M{xfWQ5X3|?P!%=2ef z2ZaTIN7i6s9gmk|e2*u4fJ=sL@h@y-b<7Bp-k8@bpkGY+lV8#E!d=ZjHfn!T6aJSe zN0Q})kW#sCOHQYkdB6DGhby+=td{XC3LSg~mJL(iTSOCA4Bo1^`cgIrJ`bp8kJNi_ zPT?<6)}#XvrVsN^eTM%4){QjK)XOZ;Rq@XHlImM|t@&2PHjO!&s0EqkYrcv5%)!cW zY;j31BVQ?qUAm#uyBc-Wh|HY?Y%}iLqgygi4wO!zPhm}Nt06Zlc?qszZjnahuR0Z z>v7M+=GcupIUm&DCyRZs;>QiA$JTW_ARb^vHxe>u?Idp-Z=v(HmZT=DagG93`_N&A zsoK#;ANgZnnF=)uzu%>}aIe`JF9K`Dx6fGyWhz;iWkvCBT&iwgD>a?bhmP>|9dmAT zH=oLDyhC{2kAlf94YXwYAprz2JzdYkV}b5{Gjh3n5Guu2)>}G^9q)*>mORRH4~>cm z4zzljU>et4$*wuf_<%upPfgn?k=fZHoBv|b0r7JyO&xXs=3Ybora*LThLPb(qaAly zvZp|ElBrx(?_E+;&RAIk<%J5eR~4s;q7YwbTlloM>X^tW-{Tsrdi_$bW`;WR&JdLQ z{KKK{h~zhULwkub50sFN#J48JyzY@~QAN zXLZ-9s+}%4*|lKd5#FFr-(f9Y;cPLD$MTteI=KLXXATbEUTbo5%*m_~0GiJaF|dm~ z@7->HWzI%=wT%(14xs&hvZ~wql-#ty|qFyel z?xsjS-(1039!WfdL#{Ua-JWz0#C(b=-LBX&^es(GARK#)LS|9ErAuma7n5^LKhZtB zd1v&1rK_yDV(t`iC1!Kpy=li{n9sP>4S!Oecsz^VaErp55oBUM(b%3u(ilETJg(Hp zgm&L~2enE(R{eaTHjL0c&)?4G=utW6?AZgVk$Xirw;F+F)0kVgQ<21b* z+u$7OC^13@iUj5ZxC^=fHwd>lHp62@55r7jH#1qhi-KBID{juWxDZ0^$lm z9EYuY3Kja=GSDyatM^e>N=c$_E92#p%!v|5ku3r0B=7s+(9r?O{gN}f?Q`nday4?V zmobw^rS63&o5V(k#g>FITS+B!)DVr4kiNLQh}xZ5@)uCc4n^1f_pS&4hOvxNS&h!e zUg|!~Es^CJ`ySzQmNS`EDuOv|^K>i5ti*og`XcjQ&(8DL?~TQeo=-XP!+(5lqGU55 z&W<{+*QfR})rC-5odqRR-oqHx>PhBfv^=Cs>$RgbLr zPc_?(y1A@Xx$}!1b(iQSz`XnzX_}&nK1${wH%BjBl7M!S=~y`UG`$8zw*(_@_pGTM zz!iNmx0&2rhi~&Z$g1XAZ@ggkdNJgq$PK^nX*?1H9m{P@1=rbqbwKYKa@^}C?Xk{& z0}?JyP#C!Kvin)wC$dL|F0IEvA5h4N`&T9u^c!@aUsH*WF>I^pDX`UmIJOKLs>K6`X*m$|cE{c`**1J~qNdhYlGMF0ow^ByO* zYye#3E*H4lY(XGF6A$_L#pE5_QpR@l1UXJ+# literal 0 HcmV?d00001 diff --git a/lib/SparkFun u-blox Arduino Library/keywords.txt b/lib/SparkFun u-blox Arduino Library/keywords.txt new file mode 100644 index 0000000..99345c1 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/keywords.txt @@ -0,0 +1,272 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SFE_UBLOX_GPS KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +isConnected KEYWORD2 +checkUblox KEYWORD2 +checkUbloxI2C KEYWORD2 +checkUbloxSerial KEYWORD2 + +process KEYWORD2 +processUBX KEYWORD2 +processRTCMframe KEYWORD2 +processRTCM KEYWORD2 +processUBXpacket KEYWORD2 +processNMEA KEYWORD2 + +calcChecksum KEYWORD2 +sendCommand KEYWORD2 +printPacket KEYWORD2 +setI2CAddress KEYWORD2 +setSerialRate KEYWORD2 +setNMEAOutputPort KEYWORD2 + +setNavigationFrequency KEYWORD2 +getNavigationFrequency KEYWORD2 + +saveConfiguration KEYWORD2 +factoryDefault KEYWORD2 +saveConfigSelective KEYWORD2 + +waitForResponse KEYWORD2 + +getPVT KEYWORD2 +getLatitude KEYWORD2 +getLongitude KEYWORD2 +getAltitude KEYWORD2 +getAltitudeMSL KEYWORD2 +getSIV KEYWORD2 +getFixType KEYWORD2 +getCarrierSolutionType KEYWORD2 +getGroundSpeed KEYWORD2 +getHeading KEYWORD2 +getPDOP KEYWORD2 +getTimeOfWeek KEYWORD2 +getHorizontalAccEst KEYWORD2 +getVerticalAccEst KEYWORD2 +getNedNorthVel KEYWORD2 +getNedEastVel KEYWORD2 +getNedDownVel KEYWORD2 +getSpeedAccEst KEYWORD2 +getHeadingAccEst KEYWORD2 +getInvalidLlh KEYWORD2 +getHeadVeh KEYWORD2 +getMagDec KEYWORD2 +getMagAcc KEYWORD2 +getHeadVehValid KEYWORD2 + +setPortOutput KEYWORD2 +setPortInput KEYWORD2 +getPortSettings KEYWORD2 + +setI2COutput KEYWORD2 +setUART1Output KEYWORD2 +setUART2Output KEYWORD2 +setUSBOutput KEYWORD2 +setSPIOutput KEYWORD2 + +getVal KEYWORD2 +getVal8 KEYWORD2 +getVal16 KEYWORD2 +getVal32 KEYWORD2 +setVal KEYWORD2 +setVal8 KEYWORD2 +setVal16 KEYWORD2 +setVal32 KEYWORD2 +newCfgValset8 KEYWORD2 +newCfgValset16 KEYWORD2 +newCfgValset32 KEYWORD2 +addCfgValset8 KEYWORD2 +addCfgValset16 KEYWORD2 +addCfgValset32 KEYWORD2 +sendCfgValset8 KEYWORD2 +sendCfgValset16 KEYWORD2 +sendCfgValset32 KEYWORD2 + +getSurveyMode KEYWORD2 +setSurveyMode KEYWORD2 +enableSurveyMode KEYWORD2 +disableSurveyMode KEYWORD2 +getSurveyStatus KEYWORD2 + +enableRTCMmessage KEYWORD2 +disableRTCMmessage KEYWORD2 + +getPositionAccuracy KEYWORD2 + +getProtocolVersionHigh KEYWORD2 +getProtocolVersionLow KEYWORD2 +getProtocolVersion KEYWORD2 + +getRELPOSNED KEYWORD2 + +enableDebugging KEYWORD2 +disableDebugging KEYWORD2 +debugPrint KEYWORD2 +debugPrintln KEYWORD2 + +factoryReset KEYWORD2 +setAutoPVT KEYWORD2 +assumeAutoPVT KEYWORD2 +flushPVT KEYWORD2 + +getYear KEYWORD2 +getMonth KEYWORD2 +getDay KEYWORD2 +getHour KEYWORD2 +getMinute KEYWORD2 +getSecond KEYWORD2 +getMillisecond KEYWORD2 +getNanosecond KEYWORD2 +getDateValid KEYWORD2 +getTimeValid KEYWORD2 + +getHPPOSLLH KEYWORD2 +assumeAutoHPPOSLLH KEYWORD2 +setAutoHPPOSLLH KEYWORD2 +flushHPPOSLLH KEYWORD2 + +getTimeOfWeek KEYWORD2 +getHighResLatitude KEYWORD2 +getHighResLatitudeHp KEYWORD2 +getHighResLongitude KEYWORD2 +getHighResLongitudeHp KEYWORD2 +getElipsoid KEYWORD2 +getElipsoidHp KEYWORD2 +getMeanSeaLevel KEYWORD2 +getMeanSeaLevelHp KEYWORD2 +getGeoidSeparation KEYWORD2 +getHorizontalAccuracy KEYWORD2 +getVerticalAccuracy KEYWORD2 + +addGeofence KEYWORD2 +clearGeofences KEYWORD2 +getGeofenceState KEYWORD2 + +setDynamicModel KEYWORD2 +getDynamicModel KEYWORD2 +powerSaveMode KEYWORD2 +getPowerSaveMode KEYWORD2 +powerOff KEYWORD2 +powerOffWithInterrupt KEYWORD2 + +configureMessage KEYWORD2 +enableMessage KEYWORD2 +disableMessage KEYWORD2 +enableNMEAMessage KEYWORD2 +disableNMEAMessage KEYWORD2 + +getEsfInfo KEYWORD2 +getEsfIns KEYWORD2 +getEsfDataInfo KEYWORD2 +getEsfRawDataInfo KEYWORD2 + +getSensState KEYWORD2 +getVehAtt KEYWORD2 + +setI2CTransactionSize KEYWORD2 +getI2CTransactionSize KEYWORD2 + +setStaticPosition KEYWORD2 + +pushRawData KEYWORD2 + +setHNRNavigationRate KEYWORD2 +getHNRNavigationRate KEYWORD2 +assumeAutoHNRAtt KEYWORD2 +setAutoHNRAtt KEYWORD2 +getHNRAtt KEYWORD2 +assumeAutoHNRDyn KEYWORD2 +setAutoHNRDyn KEYWORD2 +getHNRDyn KEYWORD2 +assumeAutoHNRPVT KEYWORD2 +setAutoHNRPVT KEYWORD2 +getHNRPVT KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +COM_TYPE_UBX LITERAL1 +COM_TYPE_NMEA LITERAL1 +COM_TYPE_RTCM3 LITERAL1 + +COM_PORT_I2C LITERAL1 +COM_PORT_UART1 LITERAL1 +COM_PORT_UART2 LITERAL1 +COM_PORT_USB LITERAL1 +COM_PORT_SPI LITERAL1 + +UBX_CLASS_NAV LITERAL1 +UBX_CLASS_RXM LITERAL1 +UBX_CLASS_INF LITERAL1 +UBX_CLASS_ACK LITERAL1 +UBX_CLASS_CFG LITERAL1 +UBX_CLASS_UPD LITERAL1 +UBX_CLASS_MON LITERAL1 +UBX_CLASS_AID LITERAL1 +UBX_CLASS_TIM LITERAL1 +UBX_CLASS_ESF LITERAL1 +UBX_CLASS_MGA LITERAL1 +UBX_CLASS_LOG LITERAL1 +UBX_CLASS_SEC LITERAL1 +UBX_CLASS_HNR LITERAL1 +UBX_CLASS_NMEA LITERAL1 + +UBX_NMEA_GGA LITERAL1 +UBX_NMEA_GLL LITERAL1 +UBX_NMEA_GNS LITERAL1 +UBX_NMEA_GRS LITERAL1 +UBX_NMEA_GSA LITERAL1 +UBX_NMEA_GST LITERAL1 +UBX_NMEA_GSV LITERAL1 +UBX_NMEA_RMC LITERAL1 +UBX_NMEA_VTG LITERAL1 +UBX_NMEA_ZDA LITERAL1 + +UBX_NAV_PVT LITERAL1 +UBX_NAV_HPPOSECEF LITERAL1 +UBX_NAV_HPPOSLLH LITERAL1 +UBX_NAV_SVIN LITERAL1 +UBX_NAV_RELPOSNED LITERAL1 + +UBX_RTCM_MSB LITERAL1 +UBX_RTCM_1005 LITERAL1 +UBX_RTCM_1074 LITERAL1 +UBX_RTCM_1077 LITERAL1 +UBX_RTCM_1084 LITERAL1 +UBX_RTCM_1087 LITERAL1 +UBX_RTCM_1094 LITERAL1 +UBX_RTCM_1097 LITERAL1 +UBX_RTCM_1124 LITERAL1 +UBX_RTCM_1127 LITERAL1 +UBX_RTCM_1230 LITERAL1 +UBX_RTCM_4072_0 LITERAL1 +UBX_RTCM_4072_1 LITERAL1 + +DYN_MODEL_PORTABLE LITERAL1 +DYN_MODEL_STATIONARY LITERAL1 +DYN_MODEL_PEDESTRIAN LITERAL1 +DYN_MODEL_AUTOMOTIVE LITERAL1 +DYN_MODEL_SEA LITERAL1 +DYN_MODEL_AIRBORNE1g LITERAL1 +DYN_MODEL_AIRBORNE2g LITERAL1 +DYN_MODEL_AIRBORNE4g LITERAL1 +DYN_MODEL_WRIST LITERAL1 +DYN_MODEL_BIKE LITERAL1 + +UBX_ESF_STATUS LITERAL1 +UBX_ESF_RAW LITERAL1 +UBX_ESF_MEAS LITERAL1 +UBX_ESF_INS LITERAL1 diff --git a/lib/SparkFun u-blox Arduino Library/library.properties b/lib/SparkFun u-blox Arduino Library/library.properties new file mode 100644 index 0000000..3eb99e9 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/library.properties @@ -0,0 +1,9 @@ +name=SparkFun u-blox Arduino Library +version=1.8.11 +author=SparkFun Electronics +maintainer=SparkFun Electronics +sentence=DEPRECATED Library for I2C and Serial Communication with u-blox modules

+paragraph=Please note: this library is deprecated. Please migrate to v2.0. You can find the migration instructions here.

An Arduino Library to enable both I2C and Serial communication for both NMEA reception and binary UBX sending to u-blox modules. Useful for interfacing to the SparkFun GPS-RTK2 ZED-F9P, SparkFun GPS-RTK NEO-M8P-2, the SparkFun SAM-M8Q, and the SparkFun ZOE-M8Q. Library also works with other u-blox based boards.

The ZED-F9P and NEO-M8P-2 modules are top-of-the-line modules for high accuracy GNSS and GPS location solutions including RTK. The ZED-F9P is unique in that it is capable of both rover and base station operations allowing the module to become a base station and produce RTCM 3.x correction data.
+category=Sensors +url=https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library +architectures=* diff --git a/lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.cpp b/lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.cpp new file mode 100644 index 0000000..7fc43de --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.cpp @@ -0,0 +1,4875 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" + +SFE_UBLOX_GPS::SFE_UBLOX_GPS(void) +{ + // Constructor + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + moduleQueried.versionNumber = false; + + if (checksumFailurePin >= 0) + { + pinMode((uint8_t)checksumFailurePin, OUTPUT); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + + //Define the size of the I2C buffer based on the platform the user has + //In general we found that most platforms use 32 bytes as the I2C buffer size. We could + //implement platform gaurds here but as you can see, none currently benefit from >32 + //so we'll leave it up to the user to set it using setI2CTransactionSize if they will benefit from it + // //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + + // i2cTransactionSize = 32; + + // #elif defined(__SAMD21G18A__) + + // i2cTransactionSize = 32; + + //#elif __MK20DX256__ + //Teensy + + // #elif defined(ARDUINO_ARCH_ESP32) + + // i2cTransactionSize = 32; //The ESP32 has an I2C buffer length of 128. We reduce it to 32 bytes to increase stability with the module + + // #endif +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(TwoWire &wirePort, uint8_t deviceAddress) +{ + commType = COMM_TYPE_I2C; + _i2cPort = &wirePort; //Grab which port the user wants us to use + + //We expect caller to begin their I2C port, with the speed of their choice external to the library + //But if they forget, we start the hardware here. + + //We're moving away from the practice of starting Wire hardware in a library. This is to avoid cross platform issues. + //ie, there are some platforms that don't handle multiple starts to the wire hardware. Also, every time you start the wire + //hardware the clock speed reverts back to 100kHz regardless of previous Wire.setClocks(). + //_i2cPort->begin(); + + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + + // Attempt isConnected up to 3 times if required + boolean success = isConnected(); + + if (!success) + success = isConnected(); + + if (!success) + success = isConnected(); + + return (success); +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(Stream &serialPort) +{ + commType = COMM_TYPE_SERIAL; + _serialPort = &serialPort; //Grab which port the user wants us to use + + // Attempt isConnected up to 3 times if required + boolean success = isConnected(); + + if (!success) + success = isConnected(); + + if (!success) + success = isConnected(); + + return (success); +} + +//Sets the global size for I2C transactions +//Most platforms use 32 bytes (the default) but this allows users to increase the transaction +//size if the platform supports it +//Note: If the transaction size is set larger than the platforms buffer size, bad things will happen. +void SFE_UBLOX_GPS::setI2CTransactionSize(uint8_t transactionSize) +{ + i2cTransactionSize = transactionSize; +} +uint8_t SFE_UBLOX_GPS::getI2CTransactionSize(void) +{ + return (i2cTransactionSize); +} + +//Enable or disable the printing of sent/response HEX values. +//Use this in conjunction with 'Transport Logging' from the Universal Reader Assistant to see what they're doing that we're not +void SFE_UBLOX_GPS::enableDebugging(Stream &debugPort, boolean printLimitedDebug) +{ + _debugSerial = &debugPort; //Grab which port the user wants us to use for debugging + if (printLimitedDebug == false) + { + _printDebug = true; //Should we print the commands we send? Good for debugging + } + else + { + _printLimitedDebug = true; //Should we print limited debug messages? Good for debugging high navigation rates + } +} +void SFE_UBLOX_GPS::disableDebugging(void) +{ + _printDebug = false; //Turn off extra print statements + _printLimitedDebug = false; +} + +//Safely print messages +void SFE_UBLOX_GPS::debugPrint(char *message) +{ + if (_printDebug == true) + { + _debugSerial->print(message); + } +} +//Safely print messages +void SFE_UBLOX_GPS::debugPrintln(char *message) +{ + if (_printDebug == true) + { + _debugSerial->println(message); + } +} + +const char *SFE_UBLOX_GPS::statusString(sfe_ublox_status_e stat) +{ + switch (stat) + { + case SFE_UBLOX_STATUS_SUCCESS: + return "Success"; + break; + case SFE_UBLOX_STATUS_FAIL: + return "General Failure"; + break; + case SFE_UBLOX_STATUS_CRC_FAIL: + return "CRC Fail"; + break; + case SFE_UBLOX_STATUS_TIMEOUT: + return "Timeout"; + break; + case SFE_UBLOX_STATUS_COMMAND_NACK: + return "Command not acknowledged (NACK)"; + break; + case SFE_UBLOX_STATUS_OUT_OF_RANGE: + return "Out of range"; + break; + case SFE_UBLOX_STATUS_INVALID_ARG: + return "Invalid Arg"; + break; + case SFE_UBLOX_STATUS_INVALID_OPERATION: + return "Invalid operation"; + break; + case SFE_UBLOX_STATUS_MEM_ERR: + return "Memory Error"; + break; + case SFE_UBLOX_STATUS_HW_ERR: + return "Hardware Error"; + break; + case SFE_UBLOX_STATUS_DATA_SENT: + return "Data Sent"; + break; + case SFE_UBLOX_STATUS_DATA_RECEIVED: + return "Data Received"; + break; + case SFE_UBLOX_STATUS_I2C_COMM_FAILURE: + return "I2C Comm Failure"; + break; + case SFE_UBLOX_STATUS_DATA_OVERWRITTEN: + return "Data Packet Overwritten"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + +void SFE_UBLOX_GPS::factoryReset() +{ + // Copy default settings to permanent + // Note: this does not load the permanent configuration into the current configuration. Calling factoryDefault() will do that. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 13; + packetCfg.startingSpot = 0; + for (uint8_t i = 0; i < 4; i++) + { + payloadCfg[0 + i] = 0xff; // clear mask: copy default config to permanent config + payloadCfg[4 + i] = 0x00; // save mask: don't save current to permanent + payloadCfg[8 + i] = 0x00; // load mask: don't copy permanent config to current + } + payloadCfg[12] = 0xff; // all forms of permanent memory + sendCommand(&packetCfg, 0); // don't expect ACK + hardReset(); // cause factory default config to actually be loaded and used cleanly +} + +void SFE_UBLOX_GPS::hardReset() +{ + // Issue hard reset + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RST; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + payloadCfg[0] = 0xff; // cold start + payloadCfg[1] = 0xff; // cold start + payloadCfg[2] = 0; // 0=HW reset + payloadCfg[3] = 0; // reserved + sendCommand(&packetCfg, 0); // don't expect ACK +} + +//Changes the serial baud rate of the u-blox module, can't return success/fail 'cause ACK from modem +//is lost due to baud rate change +void SFE_UBLOX_GPS::setSerialRate(uint32_t baudrate, uint8_t uartPort, uint16_t maxWait) +{ + //Get the current config values for the UART port + getPortSettings(uartPort, maxWait); //This will load the payloadCfg array with current port settings + + if (_printDebug == true) + { + _debugSerial->print(F("Current baud rate: ")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[8] = baudrate; + payloadCfg[9] = baudrate >> 8; + payloadCfg[10] = baudrate >> 16; + payloadCfg[11] = baudrate >> 24; + + if (_printDebug == true) + { + _debugSerial->print(F("New baud rate:")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("setSerialRate: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } +} + +//Changes the I2C address that the u-blox module responds to +//0x42 is the default but can be changed with this command +boolean SFE_UBLOX_GPS::setI2CAddress(uint8_t deviceAddress, uint16_t maxWait) +{ + //Get the current config values for the I2C port + getPortSettings(COM_PORT_I2C, maxWait); //This will load the payloadCfg array with current port settings + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[4] = deviceAddress << 1; //DDC mode LSB + + if (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT) // We are only expecting an ACK + { + //Success! Now change our internal global. + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + return (true); + } + return (false); +} + +//Want to see the NMEA messages on the Serial port? Here's how +void SFE_UBLOX_GPS::setNMEAOutputPort(Stream &nmeaOutputPort) +{ + _nmeaOutputPort = &nmeaOutputPort; //Store the port from user +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUblox(uint8_t requestedClass, uint8_t requestedID) +{ + return checkUbloxInternal(&packetCfg, requestedClass, requestedID); +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (commType == COMM_TYPE_I2C) + return (checkUbloxI2C(incomingUBX, requestedClass, requestedID)); + else if (commType == COMM_TYPE_SERIAL) + return (checkUbloxSerial(incomingUBX, requestedClass, requestedID)); + return false; +} + +//Polls I2C for data, passing any new bytes to process() +//Returns true if new bytes are available +boolean SFE_UBLOX_GPS::checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (millis() - lastCheck >= i2cPollingWait) + { + //Get the number of bytes available from the module + uint16_t bytesAvailable = 0; + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFD); //0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)2); + if (_i2cPort->available()) + { + uint8_t msb = _i2cPort->read(); + uint8_t lsb = _i2cPort->read(); + if (lsb == 0xFF) + { + //I believe this is a u-blox bug. Device should never present an 0xFF. + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxI2C: u-blox bug, length lsb is 0xFF")); + } + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + bytesAvailable = (uint16_t)msb << 8 | lsb; + } + + if (bytesAvailable == 0) + { + if (_printDebug == true) + { + _debugSerial->println(F("checkUbloxI2C: OK, zero bytes available")); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + + //Check for undocumented bit error. We found this doing logic scans. + //This error is rare but if we incorrectly interpret the first bit of the two 'data available' bytes as 1 + //then we have far too many bytes to check. May be related to I2C setup time violations: https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40 + if (bytesAvailable & ((uint16_t)1 << 15)) + { + //Clear the MSbit + bytesAvailable &= ~((uint16_t)1 << 15); + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->print(F("checkUbloxI2C: Bytes available error:")); + _debugSerial->println(bytesAvailable); + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + } + } + + if (bytesAvailable > 100) + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Large packet of ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes received")); + } + } + else + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Reading ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes")); + } + } + + while (bytesAvailable) + { + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFF); //0xFF is the register to read data from + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + //Limit to 32 bytes or whatever the buffer limit is for given platform + uint16_t bytesToRead = bytesAvailable; + if (bytesToRead > i2cTransactionSize) + bytesToRead = i2cTransactionSize; + + TRY_AGAIN: + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)bytesToRead); + if (_i2cPort->available()) + { + for (uint16_t x = 0; x < bytesToRead; x++) + { + uint8_t incoming = _i2cPort->read(); //Grab the actual character + + //Check to see if the first read is 0x7F. If it is, the module is not ready + //to respond. Stop, wait, and try again + if (x == 0) + { + if (incoming == 0x7F) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxU2C: u-blox error, module not ready with data")); + } + delay(5); //In logic analyzation, the module starting responding after 1.48ms + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + goto TRY_AGAIN; + } + } + + process(incoming, incomingUBX, requestedClass, requestedID); //Process this valid character + } + } + else + return (false); //Sensor did not respond + + bytesAvailable -= bytesToRead; + } + } + + return (true); + +} //end checkUbloxI2C() + +//Checks Serial for data, passing any new bytes to process() +boolean SFE_UBLOX_GPS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + while (_serialPort->available()) + { + process(_serialPort->read(), incomingUBX, requestedClass, requestedID); + } + return (true); + +} //end checkUbloxSerial() + +//Processes NMEA and UBX binary sentences one byte at a time +//Take a given byte and file it into the proper array +void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if ((currentSentence == NONE) || (currentSentence == NMEA)) + { + if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + { + //This is the start of a binary sentence. Reset flags. + //We still don't know the response class + ubxFrameCounter = 0; + currentSentence = UBX; + //Reset the packetBuf.counter even though we will need to reset it again when ubxFrameCounter == 2 + packetBuf.counter = 0; + ignoreThisPayload = false; //We should not ignore this payload - yet + //Store data in packetBuf until we know if we have a requested class and ID match + activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + } + else if (incoming == '$') + { + currentSentence = NMEA; + } + else if (incoming == 0xD3) //RTCM frames start with 0xD3 + { + rtcmFrameCounter = 0; + currentSentence = RTCM; + } + else + { + //This character is unknown or we missed the previous start of a sentence + } + } + + //Depending on the sentence, pass the character to the individual processor + if (currentSentence == UBX) + { + //Decide what type of response this is + if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + currentSentence = NONE; //Something went wrong. Reset. + else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + currentSentence = NONE; //Something went wrong. Reset. + // Note to future self: + // There may be some duplication / redundancy in the next few lines as processUBX will also + // load information into packetBuf, but we'll do it here too for clarity + else if (ubxFrameCounter == 2) //Class + { + // Record the class in packetBuf until we know what to do with it + packetBuf.cls = incoming; // (Duplication) + rollingChecksumA = 0; //Reset our rolling checksums here (not when we receive the 0xB5) + rollingChecksumB = 0; + packetBuf.counter = 0; //Reset the packetBuf.counter (again) + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // Reset the packet validity (redundant?) + packetBuf.startingSpot = incomingUBX->startingSpot; //Copy the startingSpot + } + else if (ubxFrameCounter == 3) //ID + { + // Record the ID in packetBuf until we know what to do with it + packetBuf.id = incoming; // (Duplication) + //We can now identify the type of response + //If the packet we are receiving is not an ACK then check for a class and ID match + if (packetBuf.cls != UBX_CLASS_ACK) + { + //This is not an ACK so check for a class and ID match + if ((packetBuf.cls == requestedClass) && (packetBuf.id == requestedID)) + { + //This is not an ACK and we have a class and ID match + //So start diverting data into incomingUBX (usually packetCfg) + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + //This is not an ACK and we do not have a complete class and ID match + //So let's check for an HPPOSLLH message arriving when we were expecting PVT and vice versa + else if ((packetBuf.cls == requestedClass) && + (((packetBuf.id == UBX_NAV_PVT) && (requestedID == UBX_NAV_HPPOSLLH || requestedID == UBX_NAV_DOP)) || + ((packetBuf.id == UBX_NAV_HPPOSLLH) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_DOP)) || + ((packetBuf.id == UBX_NAV_DOP) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_HPPOSLLH)))) + { + //This is not the message we were expecting but we start diverting data into incomingUBX (usually packetCfg) and process it anyway + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + if (_printDebug == true) + { + _debugSerial->print(F("process: auto NAV PVT/HPPOSLLH/DOP collision: Requested ID: 0x")); + _debugSerial->print(requestedID, HEX); + _debugSerial->print(F(" Message ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + } + else if ((packetBuf.cls == requestedClass) && + (((packetBuf.id == UBX_HNR_ATT) && (requestedID == UBX_HNR_INS || requestedID == UBX_HNR_PVT)) || + ((packetBuf.id == UBX_HNR_INS) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_PVT)) || + ((packetBuf.id == UBX_HNR_PVT) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_INS)))) + { + //This is not the message we were expecting but we start diverting data into incomingUBX (usually packetCfg) and process it anyway + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + if (_printDebug == true) + { + _debugSerial->print(F("process: auto HNR ATT/INS/PVT collision: Requested ID: 0x")); + _debugSerial->print(requestedID, HEX); + _debugSerial->print(F(" Message ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + } + else + { + //This is not an ACK and we do not have a class and ID match + //so we should keep diverting data into packetBuf and ignore the payload + ignoreThisPayload = true; + } + } + else + { + // This is an ACK so it is to early to do anything with it + // We need to wait until we have received the length and data bytes + // So we should keep diverting data into packetBuf + } + } + else if (ubxFrameCounter == 4) //Length LSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len = incoming; // (Duplication) + } + else if (ubxFrameCounter == 5) //Length MSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len |= incoming << 8; // (Duplication) + } + else if (ubxFrameCounter == 6) //This should be the first byte of the payload unless .len is zero + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + if (_printDebug == true) + { + _debugSerial->print(F("process: ZERO LENGTH packet received: Class: 0x")); + _debugSerial->print(packetBuf.cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + //If length is zero (!) this will be the first byte of the checksum so record it + packetBuf.checksumA = incoming; + } + else + { + //The length is not zero so record this byte in the payload + packetBuf.payload[0] = incoming; + } + } + else if (ubxFrameCounter == 7) //This should be the second byte of the payload unless .len is zero or one + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + //If length is zero (!) this will be the second byte of the checksum so record it + packetBuf.checksumB = incoming; + } + else if (packetBuf.len == 1) // Check if length is one + { + //The length is one so this is the first byte of the checksum + packetBuf.checksumA = incoming; + } + else // Length is >= 2 so this must be a payload byte + { + packetBuf.payload[1] = incoming; + } + // Now that we have received two payload bytes, we can check for a matching ACK/NACK + if ((activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) // If we are not already processing a data packet + && (packetBuf.cls == UBX_CLASS_ACK) // and if this is an ACK/NACK + && (packetBuf.payload[0] == requestedClass) // and if the class matches + && (packetBuf.payload[1] == requestedID)) // and if the ID matches + { + if (packetBuf.len == 2) // Check if .len is 2 + { + // Then this is a matching ACK so copy it into packetAck + activePacketBuffer = SFE_UBLOX_PACKET_PACKETACK; + packetAck.cls = packetBuf.cls; + packetAck.id = packetBuf.id; + packetAck.len = packetBuf.len; + packetAck.counter = packetBuf.counter; + packetAck.payload[0] = packetBuf.payload[0]; + packetAck.payload[1] = packetBuf.payload[1]; + } + else // Length is not 2 (hopefully this is impossible!) + { + if (_printDebug == true) + { + _debugSerial->print(F("process: ACK received with .len != 2: Class: 0x")); + _debugSerial->print(packetBuf.payload[0], HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->print(packetBuf.payload[1], HEX); + _debugSerial->print(F(" len: ")); + _debugSerial->println(packetBuf.len); + } + } + } + } + + //Divert incoming into the correct buffer + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETACK) + processUBX(incoming, &packetAck, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + processUBX(incoming, incomingUBX, requestedClass, requestedID); + else // if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) + processUBX(incoming, &packetBuf, requestedClass, requestedID); + + //Finally, increment the frame counter + ubxFrameCounter++; + } + else if (currentSentence == NMEA) + { + processNMEA(incoming); //Process each NMEA character + } + else if (currentSentence == RTCM) + { + processRTCMframe(incoming); //Deal with RTCM bytes + } +} + +//This is the default or generic NMEA processor. We're only going to pipe the data to serial port so we can see it. +//User could overwrite this function to pipe characters to nmea.process(c) of tinyGPS or MicroNMEA +//Or user could pipe each character to a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //If user has assigned an output port then pipe the characters there + if (_nmeaOutputPort != NULL) + _nmeaOutputPort->write(incoming); //Echo this byte to the serial port +} + +//We need to be able to identify an RTCM packet and then the length +//so that we know when the RTCM message is completely received and we then start +//listening for other sentences (like NMEA or UBX) +//RTCM packet structure is very odd. I never found RTCM STANDARD 10403.2 but +//http://d1.amobbs.com/bbs_upload782111/files_39/ourdev_635123CK0HJT.pdf is good +//https://dspace.cvut.cz/bitstream/handle/10467/65205/F3-BP-2016-Shkalikava-Anastasiya-Prenos%20polohove%20informace%20prostrednictvim%20datove%20site.pdf?sequence=-1 +//Lead me to: https://forum.u-blox.com/index.php/4348/how-to-read-rtcm-messages-from-neo-m8p +//RTCM 3.2 bytes look like this: +//Byte 0: Always 0xD3 +//Byte 1: 6-bits of zero +//Byte 2: 10-bits of length of this packet including the first two-ish header bytes, + 6. +//byte 3 + 4 bits: Msg type 12 bits +//Example: D3 00 7C 43 F0 ... / 0x7C = 124+6 = 130 bytes in this packet, 0x43F = Msg type 1087 +void SFE_UBLOX_GPS::processRTCMframe(uint8_t incoming) +{ + if (rtcmFrameCounter == 1) + { + rtcmLen = (incoming & 0x03) << 8; //Get the last two bits of this byte. Bits 8&9 of 10-bit length + } + else if (rtcmFrameCounter == 2) + { + rtcmLen |= incoming; //Bits 0-7 of packet length + rtcmLen += 6; //There are 6 additional bytes of what we presume is header, msgType, CRC, and stuff + } + /*else if (rtcmFrameCounter == 3) + { + rtcmMsgType = incoming << 4; //Message Type, MS 4 bits + } + else if (rtcmFrameCounter == 4) + { + rtcmMsgType |= (incoming >> 4); //Message Type, bits 0-7 + }*/ + + rtcmFrameCounter++; + + processRTCM(incoming); //Here is where we expose this byte to the user + + if (rtcmFrameCounter == rtcmLen) + { + //We're done! + currentSentence = NONE; //Reset and start looking for next sentence type + } +} + +//This function is called for each byte of an RTCM frame +//Ths user can overwrite this function and process the RTCM frame as they please +//Bytes can be piped to Serial or other interface. The consumer could be a radio or the internet (Ntrip broadcaster) +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Radio.sendReliable((String)incoming); //An example of passing this byte to a radio + + //_debugSerial->write(incoming); //An example of passing this byte out the serial port + + //Debug printing + // _debugSerial->print(F(" ")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // _debugSerial->print(incoming, HEX); + // if(rtcmFrameCounter % 16 == 0) _debugSerial->println(); +} + +//Given a character, file it away into the uxb packet structure +//Set valid to VALID or NOT_VALID once sentence is completely received and passes or fails CRC +//The payload portion of the packet can be 100s of bytes but the max array +//size is MAX_PAYLOAD_SIZE bytes. startingSpot can be set so we only record +//a subset of bytes within a larger packet. +void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + size_t max_payload_size = (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) ? MAX_PAYLOAD_SIZE : 2; + bool overrun = false; + + //Add all incoming bytes to the rolling checksum + //Stop at len+4 as this is the checksum bytes to that should not be added to the rolling checksum + if (incomingUBX->counter < incomingUBX->len + 4) + addToChecksum(incoming); + + if (incomingUBX->counter == 0) + { + incomingUBX->cls = incoming; + } + else if (incomingUBX->counter == 1) + { + incomingUBX->id = incoming; + } + else if (incomingUBX->counter == 2) //Len LSB + { + incomingUBX->len = incoming; + } + else if (incomingUBX->counter == 3) //Len MSB + { + incomingUBX->len |= incoming << 8; + } + else if (incomingUBX->counter == incomingUBX->len + 4) //ChecksumA + { + incomingUBX->checksumA = incoming; + } + else if (incomingUBX->counter == incomingUBX->len + 5) //ChecksumB + { + incomingUBX->checksumB = incoming; + + currentSentence = NONE; //We're done! Reset the sentence to being looking for a new start char + + //Validate this sentence + if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + + // Let's check if the class and ID match the requestedClass and requestedID + // Remember - this could be a data packet or an ACK packet + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is a NACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_NACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_NOTACKNOWLEDGED; // If we have a match, set the classAndIDmatch flag to NOTACKNOWLEDGED + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: NACK received: Requested Class: 0x")); + _debugSerial->print(incomingUBX->payload[0], HEX); + _debugSerial->print(F(" Requested ID: 0x")); + _debugSerial->println(incomingUBX->payload[1], HEX); + } + } + + //This is not an ACK and we do not have a complete class and ID match + //So let's check for an HPPOSLLH message arriving when we were expecting PVT and vice versa + else if ((incomingUBX->cls == requestedClass) && + (((incomingUBX->id == UBX_NAV_PVT) && (requestedID == UBX_NAV_HPPOSLLH || requestedID == UBX_NAV_DOP)) || + ((incomingUBX->id == UBX_NAV_HPPOSLLH) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_DOP)) || + ((incomingUBX->id == UBX_NAV_DOP) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_HPPOSLLH)))) + { + // This isn't the message we are looking for... + // Let's say so and leave incomingUBX->classAndIDmatch _unchanged_ + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: auto NAV PVT/HPPOSLLH/DOP collision: Requested ID: 0x")); + _debugSerial->print(requestedID, HEX); + _debugSerial->print(F(" Message ID: 0x")); + _debugSerial->println(incomingUBX->id, HEX); + } + } + // Let's do the same for the HNR messages + else if ((incomingUBX->cls == requestedClass) && + (((incomingUBX->id == UBX_HNR_ATT) && (requestedID == UBX_HNR_INS || requestedID == UBX_HNR_PVT)) || + ((incomingUBX->id == UBX_HNR_INS) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_PVT)) || + ((incomingUBX->id == UBX_HNR_PVT) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_INS)))) + { + // This isn't the message we are looking for... + // Let's say so and leave incomingUBX->classAndIDmatch _unchanged_ + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: auto HNR ATT/INS/PVT collision: Requested ID: 0x")); + _debugSerial->print(requestedID, HEX); + _debugSerial->print(F(" Message ID: 0x")); + _debugSerial->println(incomingUBX->id, HEX); + } + } + + if (_printDebug == true) + { + _debugSerial->print(F("Incoming: Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); + + if (incomingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg now valid")); + } + if (packetAck.valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck now valid")); + } + if (incomingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg classAndIDmatch")); + } + if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck classAndIDmatch")); + } + } + + //We've got a valid packet, now do something with it but only if ignoreThisPayload is false + if (ignoreThisPayload == false) + { + processUBXpacket(incomingUBX); + } + } + else // Checksum failure + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; + + // Let's check if the class and ID match the requestedClass and requestedID. + // This is potentially risky as we are saying that we saw the requested Class and ID + // but that the packet checksum failed. Potentially it could be the class or ID bytes + // that caused the checksum error! + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + //Drive an external pin to allow for easier logic analyzation + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + + _debugSerial->print(F("Checksum failed:")); + _debugSerial->print(F(" checksumA: ")); + _debugSerial->print(incomingUBX->checksumA); + _debugSerial->print(F(" checksumB: ")); + _debugSerial->print(incomingUBX->checksumB); + + _debugSerial->print(F(" rollingChecksumA: ")); + _debugSerial->print(rollingChecksumA); + _debugSerial->print(F(" rollingChecksumB: ")); + _debugSerial->print(rollingChecksumB); + _debugSerial->println(); + + _debugSerial->print(F("Failed : ")); + _debugSerial->print(F("Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); + } + } + } + else //Load this byte into the payload array + { + //If a UBX_NAV_PVT packet comes in asynchronously, we need to fudge the startingSpot + uint16_t startingSpot = incomingUBX->startingSpot; + if (incomingUBX->cls == UBX_CLASS_NAV && incomingUBX->id == UBX_NAV_PVT) + startingSpot = 0; + // Check if this is payload data which should be ignored + if (ignoreThisPayload == false) + { + //Begin recording if counter goes past startingSpot + if ((incomingUBX->counter - 4) >= startingSpot) + { + //Check to see if we have room for this byte + if (((incomingUBX->counter - 4) - startingSpot) < max_payload_size) //If counter = 208, starting spot = 200, we're good to record. + { + incomingUBX->payload[incomingUBX->counter - 4 - startingSpot] = incoming; //Store this byte into payload array + } + else + { + overrun = true; + } + } + } + } + + //Increment the counter + incomingUBX->counter++; + + if (overrun || (incomingUBX->counter == MAX_PAYLOAD_SIZE)) + { + //Something has gone very wrong + currentSentence = NONE; //Reset the sentence to being looking for a new start char + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + if (overrun) + _debugSerial->println(F("processUBX: buffer overrun detected")); + else + _debugSerial->println(F("processUBX: counter hit MAX_PAYLOAD_SIZE")); + } + } +} + +//Once a packet has been received and validated, identify this packet's class/id and update internal flags +//Note: if the user requests a PVT or a HPPOSLLH message using a custom packet, the data extraction will +// not work as expected beacuse extractLong etc are hardwired to packetCfg payloadCfg. Ideally +// extractLong etc should be updated so they receive a pointer to the packet buffer. +void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) +{ + switch (msg->cls) + { + case UBX_CLASS_NAV: + if (msg->id == UBX_NAV_PVT && msg->len == 92) + { + //Parse various byte fields into global vars + constexpr int startingSpot = 0; //fixed value used in processUBX + + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + gpsYear = extractInt(4); + gpsMonth = extractByte(6); + gpsDay = extractByte(7); + gpsHour = extractByte(8); + gpsMinute = extractByte(9); + gpsSecond = extractByte(10); + gpsDateValid = extractByte(11) & 0x01; + gpsTimeValid = (extractByte(11) & 0x02) >> 1; + gpsNanosecond = extractSignedLong(16); //Includes milliseconds + + fixType = extractByte(20 - startingSpot); + gnssFixOk = extractByte(21 - startingSpot) & 0x1; //Get the 1st bit + diffSoln = (extractByte(21 - startingSpot) >> 1) & 0x1; //Get the 2nd bit + carrierSolution = extractByte(21 - startingSpot) >> 6; //Get 6th&7th bits of this byte + headVehValid = (extractByte(21 - startingSpot) >> 5) & 0x1; // Get the 5th bit + SIV = extractByte(23 - startingSpot); + longitude = extractSignedLong(24 - startingSpot); + latitude = extractSignedLong(28 - startingSpot); + altitude = extractSignedLong(32 - startingSpot); + altitudeMSL = extractSignedLong(36 - startingSpot); + horizontalAccEst = extractLong(40 - startingSpot); + verticalAccEst = extractLong(44 - startingSpot); + nedNorthVel = extractSignedLong(48 - startingSpot); + nedEastVel = extractSignedLong(52 - startingSpot); + nedDownVel = extractSignedLong(56 - startingSpot); + groundSpeed = extractSignedLong(60 - startingSpot); + headingOfMotion = extractSignedLong(64 - startingSpot); + speedAccEst = extractLong(68 - startingSpot); + headingAccEst = extractLong(72 - startingSpot); + pDOP = extractInt(76 - startingSpot); + invalidLlh = extractByte(78 - startingSpot) & 0x1; + headVeh = extractSignedLong(84 - startingSpot); + magDec = extractSignedInt(88 - startingSpot); + magAcc = extractInt(90 - startingSpot); + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = true; + moduleQueried.gpsYear = true; + moduleQueried.gpsMonth = true; + moduleQueried.gpsDay = true; + moduleQueried.gpsHour = true; + moduleQueried.gpsMinute = true; + moduleQueried.gpsSecond = true; + moduleQueried.gpsDateValid = true; + moduleQueried.gpsTimeValid = true; + moduleQueried.gpsNanosecond = true; + + moduleQueried.all = true; + moduleQueried.gnssFixOk = true; + moduleQueried.diffSoln = true; + moduleQueried.headVehValid = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + moduleQueried.horizontalAccEst = true; + moduleQueried.verticalAccEst = true; + moduleQueried.nedNorthVel = true; + moduleQueried.nedEastVel = true; + moduleQueried.nedDownVel = true; + moduleQueried.SIV = true; + moduleQueried.fixType = true; + moduleQueried.carrierSolution = true; + moduleQueried.groundSpeed = true; + moduleQueried.headingOfMotion = true; + moduleQueried.speedAccEst = true; + moduleQueried.headingAccEst = true; + moduleQueried.pDOP = true; + moduleQueried.invalidLlh = true; + moduleQueried.headVeh = true; + moduleQueried.magDec = true; + moduleQueried.magAcc = true; + } + else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == 36) + { + timeOfWeek = extractLong(4); + highResLongitude = extractSignedLong(8); + highResLatitude = extractSignedLong(12); + elipsoid = extractSignedLong(16); + meanSeaLevel = extractSignedLong(20); + highResLongitudeHp = extractSignedChar(24); + highResLatitudeHp = extractSignedChar(25); + elipsoidHp = extractSignedChar(26); + meanSeaLevelHp = extractSignedChar(27); + horizontalAccuracy = extractLong(28); + verticalAccuracy = extractLong(32); + + highResModuleQueried.all = true; + highResModuleQueried.highResLatitude = true; + highResModuleQueried.highResLatitudeHp = true; + highResModuleQueried.highResLongitude = true; + highResModuleQueried.highResLongitudeHp = true; + highResModuleQueried.elipsoid = true; + highResModuleQueried.elipsoidHp = true; + highResModuleQueried.meanSeaLevel = true; + highResModuleQueried.meanSeaLevelHp = true; + highResModuleQueried.geoidSeparation = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + moduleQueried.gpsiTOW = true; // this can arrive via HPPOS too. + +/* + if (_printDebug == true) + { + _debugSerial->print(F("Sec: ")); + _debugSerial->print(((float)extractLong(4)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LON: ")); + _debugSerial->print(((float)(int32_t)extractLong(8)) / 10000000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LAT: ")); + _debugSerial->print(((float)(int32_t)extractLong(12)) / 10000000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("ELI M: ")); + _debugSerial->print(((float)(int32_t)extractLong(16)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("MSL M: ")); + _debugSerial->print(((float)(int32_t)extractLong(20)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LON HP: ")); + _debugSerial->print(extractSignedChar(24)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LAT HP: ")); + _debugSerial->print(extractSignedChar(25)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("ELI HP: ")); + _debugSerial->print(extractSignedChar(26)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("MSL HP: ")); + _debugSerial->print(extractSignedChar(27)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("HA 2D M: ")); + _debugSerial->print(((float)(int32_t)extractLong(28)) / 10000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("VERT M: ")); + _debugSerial->println(((float)(int32_t)extractLong(32)) / 10000.0f); + } +*/ + } + else if (msg->id == UBX_NAV_DOP && msg->len == 18) + { + geometricDOP = extractInt(4); + positionDOP = extractInt(6); + timeDOP = extractInt(8); + verticalDOP = extractInt(10); + horizontalDOP = extractInt(12); + northingDOP = extractInt(14); + eastingDOP = extractInt(16); + dopModuleQueried.all = true; + dopModuleQueried.geometricDOP = true; + dopModuleQueried.positionDOP = true; + dopModuleQueried.timeDOP = true; + dopModuleQueried.verticalDOP = true; + dopModuleQueried.horizontalDOP = true; + dopModuleQueried.northingDOP = true; + dopModuleQueried.eastingDOP = true; + } + break; + case UBX_CLASS_HNR: + if (msg->id == UBX_HNR_ATT && msg->len == 32) + { + //Parse various byte fields into global vars + hnrAtt.iTOW = extractLong(0); + hnrAtt.roll = extractSignedLong(8); + hnrAtt.pitch = extractSignedLong(12); + hnrAtt.heading = extractSignedLong(16); + hnrAtt.accRoll = extractLong(20); + hnrAtt.accPitch = extractLong(24); + hnrAtt.accHeading = extractLong(28); + + hnrAttQueried = true; + } + else if (msg->id == UBX_HNR_INS && msg->len == 36) + { + //Parse various byte fields into global vars + hnrVehDyn.iTOW = extractLong(8); + hnrVehDyn.xAngRate = extractSignedLong(12); + hnrVehDyn.yAngRate = extractSignedLong(16); + hnrVehDyn.zAngRate = extractSignedLong(20); + hnrVehDyn.xAccel = extractSignedLong(24); + hnrVehDyn.yAccel = extractSignedLong(28); + hnrVehDyn.zAccel = extractSignedLong(32); + + uint32_t bitfield0 = extractLong(0); + hnrVehDyn.xAngRateValid = (bitfield0 & 0x00000100) > 0; + hnrVehDyn.yAngRateValid = (bitfield0 & 0x00000200) > 0; + hnrVehDyn.zAngRateValid = (bitfield0 & 0x00000400) > 0; + hnrVehDyn.xAccelValid = (bitfield0 & 0x00000800) > 0; + hnrVehDyn.yAccelValid = (bitfield0 & 0x00001000) > 0; + hnrVehDyn.zAccelValid = (bitfield0 & 0x00002000) > 0; + + hnrDynQueried = true; + } + else if (msg->id == UBX_HNR_PVT && msg->len == 72) + { + //Parse various byte fields into global vars + hnrPVT.iTOW = extractLong(0); + hnrPVT.year = extractInt(4); + hnrPVT.month = extractByte(6); + hnrPVT.day = extractByte(7); + hnrPVT.hour = extractByte(8); + hnrPVT.min = extractByte(9); + hnrPVT.sec = extractByte(10); + hnrPVT.nano = extractSignedLong(12); + hnrPVT.gpsFix = extractByte(16); + hnrPVT.lon = extractSignedLong(20); + hnrPVT.lat = extractSignedLong(24); + hnrPVT.height = extractSignedLong(28); + hnrPVT.hMSL = extractSignedLong(32); + hnrPVT.gSpeed = extractSignedLong(36); + hnrPVT.speed = extractSignedLong(40); + hnrPVT.headMot = extractSignedLong(44); + hnrPVT.headVeh = extractSignedLong(48); + hnrPVT.hAcc = extractLong(52); + hnrPVT.vAcc = extractLong(56); + hnrPVT.sAcc = extractLong(60); + hnrPVT.headAcc = extractLong(64); + + uint8_t valid = extractByte(11); + hnrPVT.validDate = (valid & 0x01) > 0; + hnrPVT.validTime = (valid & 0x02) > 0; + hnrPVT.fullyResolved = (valid & 0x04) > 0; + + uint8_t flags = extractByte(17); + hnrPVT.gpsFixOK = (flags & 0x01) > 0; + hnrPVT.diffSoln = (flags & 0x02) > 0; + hnrPVT.WKNSET = (flags & 0x04) > 0; + hnrPVT.TOWSET = (flags & 0x08) > 0; + hnrPVT.headVehValid = (flags & 0x10) > 0; + + hnrPVTQueried = true; + } + } +} + +//Given a packet and payload, send everything including CRC bytes via I2C port +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + sfe_ublox_status_e retVal = SFE_UBLOX_STATUS_SUCCESS; + + calcChecksum(outgoingUBX); //Sets checksum A and B bytes of the packet + + if (_printDebug == true) + { + _debugSerial->print(F("\nSending: ")); + printPacket(outgoingUBX); + } + + if (commType == COMM_TYPE_I2C) + { + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + if (_printDebug == true) + { + _debugSerial->println(F("Send I2C Command failed")); + } + return retVal; + } + } + else if (commType == COMM_TYPE_SERIAL) + { + sendSerialCommand(outgoingUBX); + } + + if (maxWait > 0) + { + //Depending on what we just sent, either we need to look for an ACK or not + if (outgoingUBX->cls == UBX_CLASS_CFG) + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for ACK response")); + } + retVal = waitForACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for No ACK response")); + } + retVal = waitForNoACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + } + return retVal; +} + +//Returns false if sensor fails to respond to I2C traffic +sfe_ublox_status_e SFE_UBLOX_GPS::sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + //Point at 0xFF data register + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); //There is no register to write to, we just begin writing data bytes + _i2cPort->write(0xFF); + if (_i2cPort->endTransmission(false) != 0) //Don't release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //Write header bytes + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); //There is no register to write to, we just begin writing data bytes + _i2cPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _i2cPort->write(UBX_SYNCH_2); //b + _i2cPort->write(outgoingUBX->cls); + _i2cPort->write(outgoingUBX->id); + _i2cPort->write(outgoingUBX->len & 0xFF); //LSB + _i2cPort->write(outgoingUBX->len >> 8); //MSB + if (_i2cPort->endTransmission(false) != 0) //Do not release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //Write payload. Limit the sends into 32 byte chunks + //This code based on ublox: https://forum.u-blox.com/index.php/20528/how-to-use-i2c-to-get-the-nmea-frames + uint16_t bytesToSend = outgoingUBX->len; + + //"The number of data bytes must be at least 2 to properly distinguish + //from the write access to set the address counter in random read accesses." + uint16_t startSpot = 0; + while (bytesToSend > 1) + { + uint8_t len = bytesToSend; + if (len > i2cTransactionSize) + len = i2cTransactionSize; + + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + //_i2cPort->write(outgoingUBX->payload, len); //Write a portion of the payload to the bus + + for (uint16_t x = 0; x < len; x++) + _i2cPort->write(outgoingUBX->payload[startSpot + x]); //Write a portion of the payload to the bus + + if (_i2cPort->endTransmission(false) != 0) //Don't release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //*outgoingUBX->payload += len; //Move the pointer forward + startSpot += len; //Move the pointer forward + bytesToSend -= len; + } + + //Write checksum + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (bytesToSend == 1) + _i2cPort->write(outgoingUBX->payload, 1); + _i2cPort->write(outgoingUBX->checksumA); + _i2cPort->write(outgoingUBX->checksumB); + + //All done transmitting bytes. Release bus. + if (_i2cPort->endTransmission() != 0) + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + return (SFE_UBLOX_STATUS_SUCCESS); +} + +//Given a packet and payload, send everything including CRC bytesA via Serial port +void SFE_UBLOX_GPS::sendSerialCommand(ubxPacket *outgoingUBX) +{ + //Write header bytes + _serialPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _serialPort->write(UBX_SYNCH_2); //b + _serialPort->write(outgoingUBX->cls); + _serialPort->write(outgoingUBX->id); + _serialPort->write(outgoingUBX->len & 0xFF); //LSB + _serialPort->write(outgoingUBX->len >> 8); //MSB + + //Write payload. + for (int i = 0; i < outgoingUBX->len; i++) + { + _serialPort->write(outgoingUBX->payload[i]); + } + + //Write checksum + _serialPort->write(outgoingUBX->checksumA); + _serialPort->write(outgoingUBX->checksumB); +} + +//Returns true if I2C device ack's +boolean SFE_UBLOX_GPS::isConnected(uint16_t maxWait) +{ + if (commType == COMM_TYPE_I2C) + { + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (_i2cPort->endTransmission() != 0) + return false; //Sensor did not ack + } + + // Query navigation rate to see whether we get a meaningful response + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + sfe_ublox_status_e result = sendCommand(&packetCfg, maxWait); // Poll the navigation rate + + // In this case, we don't acutally care what the navigation rate is, we're just polling it to indicate a connection. + // So we return true if result is DATA_RECEIVED or DATA_OVERWRITTEN (just in case the RATE was overwritten by an auto packet). + if ((result == SFE_UBLOX_STATUS_DATA_RECEIVED) || (result == SFE_UBLOX_STATUS_DATA_OVERWRITTEN)) + return (true); + else + return (false); +} + +//Given a message, calc and store the two byte "8-Bit Fletcher" checksum over the entirety of the message +//This is called before we send a command message +void SFE_UBLOX_GPS::calcChecksum(ubxPacket *msg) +{ + msg->checksumA = 0; + msg->checksumB = 0; + + msg->checksumA += msg->cls; + msg->checksumB += msg->checksumA; + + msg->checksumA += msg->id; + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len & 0xFF); + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len >> 8); + msg->checksumB += msg->checksumA; + + for (uint16_t i = 0; i < msg->len; i++) + { + msg->checksumA += msg->payload[i]; + msg->checksumB += msg->checksumA; + } +} + +//Given a message and a byte, add to rolling "8-Bit Fletcher" checksum +//This is used when receiving messages from module +void SFE_UBLOX_GPS::addToChecksum(uint8_t incoming) +{ + rollingChecksumA += incoming; + rollingChecksumB += rollingChecksumA; +} + +//Pretty prints the current ubxPacket +void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) +{ + if (_printDebug == true) + { + _debugSerial->print(F("CLS:")); + if (packet->cls == UBX_CLASS_NAV) //1 + _debugSerial->print(F("NAV")); + else if (packet->cls == UBX_CLASS_ACK) //5 + _debugSerial->print(F("ACK")); + else if (packet->cls == UBX_CLASS_CFG) //6 + _debugSerial->print(F("CFG")); + else if (packet->cls == UBX_CLASS_MON) //0x0A + _debugSerial->print(F("MON")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->cls, HEX); + } + + _debugSerial->print(F(" ID:")); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + _debugSerial->print(F("PVT")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_RATE) + _debugSerial->print(F("RATE")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_CFG) + _debugSerial->print(F("SAVE")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->id, HEX); + } + + _debugSerial->print(F(" Len: 0x")); + _debugSerial->print(packet->len, HEX); + + // Only print the payload is ignoreThisPayload is false otherwise + // we could be printing gibberish from beyond the end of packetBuf + if (ignoreThisPayload == false) + { + _debugSerial->print(F(" Payload:")); + + for (int x = 0; x < packet->len; x++) + { + _debugSerial->print(F(" ")); + _debugSerial->print(packet->payload[x], HEX); + } + } + else + { + _debugSerial->print(F(" Payload: IGNORED")); + } + _debugSerial->println(); + } +} + +//=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//When messages from the class CFG are sent to the receiver, the receiver will send an "acknowledge"(UBX - ACK - ACK) or a +//"not acknowledge"(UBX-ACK-NAK) message back to the sender, depending on whether or not the message was processed correctly. +//Some messages from other classes also use the same acknowledgement mechanism. + +//When we poll or get a setting, we will receive _both_ a config packet and an ACK +//If the poll or get request is not valid, we will receive _only_ a NACK + +//If we are trying to get or poll a setting, then packetCfg.len will be 0 or 1 when the packetCfg is _sent_. +//If we poll the setting for a particular port using UBX-CFG-PRT then .len will be 1 initially +//For all other gets or polls, .len will be 0 initially +//(It would be possible for .len to be 2 _if_ we were using UBX-CFG-MSG to poll the settings for a particular message - but we don't use that (currently)) + +//If the get or poll _fails_, i.e. is NACK'd, then packetCfg.len could still be 0 or 1 after the NACK is received +//But if the get or poll is ACK'd, then packetCfg.len will have been updated by the incoming data and will always be at least 2 + +//If we are going to set the value for a setting, then packetCfg.len will be at least 3 when the packetCfg is _sent_. +//(UBX-CFG-MSG appears to have the shortest set length of 3 bytes) + +//We need to think carefully about how interleaved PVT packets affect things. +//It is entirely possible that our packetCfg and packetAck were received successfully +//but while we are still in the "if (checkUblox() == true)" loop a PVT packet is processed +//or _starts_ to arrive (remember that Serial data can arrive very slowly). + +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got an ACK and a valid packetCfg (module is responding with register content) +//Returns SFE_UBLOX_STATUS_DATA_SENT if we got an ACK and no packetCfg (no valid packetCfg needed, module absorbs new register data) +//Returns SFE_UBLOX_STATUS_FAIL if something very bad happens (e.g. a double checksum failure) +//Returns SFE_UBLOX_STATUS_COMMAND_NACK if the packet was not-acknowledged (NACK) +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we had a checksum failure +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an ACK and a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data and valid ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and a correct ACK! + } + + // We can be confident that the data packet (if we are going to get one) will always arrive + // before the matching ACK. So if we sent a config packet which only produces an ACK + // then outgoingUBX->classAndIDmatch will be NOT_DEFINED and the packetAck.classAndIDmatch will VALID. + // We should not check outgoingUBX->valid, outgoingUBX->cls or outgoingUBX->id + // as these may have been changed by a PVT packet. + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: no data and valid ACK after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_SENT); //We got an ACK but no data... + } + + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && ((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If packetAck.classAndIDmatch is VALID but both outgoingUBX->valid and outgoingUBX->classAndIDmatch + // are NOT_VALID then we can be confident we have had a checksum failure on the data packet + else if ((packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: CRC failed after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //Checksum fail + } + + // If our packet was not-acknowledged (NACK) we do not receive a data packet - we only get the NACK. + // So you would expect outgoingUBX->valid and outgoingUBX->classAndIDmatch to still be NOT_DEFINED + // But if a full PVT packet arrives afterwards outgoingUBX->valid could be VALID (or just possibly NOT_VALID) + // but outgoingUBX->cls and outgoingUBX->id would not match... + // So I think this is telling us we need a special state for packetAck.classAndIDmatch to tell us + // the packet was definitely NACK'd otherwise we are possibly just guessing... + // Note: the addition of packetBuf changes the logic of this, but we'll leave the code as is for now. + else if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_NOTACKNOWLEDGED) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data was NOTACKNOWLEDGED (NACK) after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_COMMAND_NACK); //We received a NACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID but the packetAck.classAndIDmatch is NOT_VALID + // then the ack probably had a checksum error. We will take a gamble and return DATA_RECEIVED. + // If we were playing safe, we should return FAIL instead + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: VALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID and the packetAck.classAndIDmatch is NOT_VALID + // then we return a FAIL. This must be a double checksum failure? + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: INVALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_FAIL); //We received invalid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID and the packetAck.classAndIDmatch is NOT_DEFINED + // then the ACK has not yet been received and we should keep waiting for it + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. Waiting for ACK.")); + } + } + + } //checkUbloxInternal == true + + delayMicroseconds(500); + } //while (millis() - startTime < maxTime) + + // We have timed out... + // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED + // even though we did not get an ACK + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT with valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. ")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data... But no ACK! + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//For non-CFG queries no ACK is sent so we use this function +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we got a corrupt config packet that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + + // If outgoingUBX->classAndIDmatch is VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: valid data with CLS/ID match after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data! + } + + // If the outgoingUBX->classAndIDmatch is VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && ((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If outgoingUBX->classAndIDmatch is NOT_DEFINED + // and outgoingUBX->valid is VALID then this must be (e.g.) a PVT packet + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + // if (_printDebug == true) + // { + // _debugSerial->print(F("waitForNoACKResponse: valid but UNWANTED data after ")); + // _debugSerial->print(millis() - startTime); + // _debugSerial->print(F(" msec. Class: ")); + // _debugSerial->print(outgoingUBX->cls); + // _debugSerial->print(F(" ID: ")); + // _debugSerial->print(outgoingUBX->id); + // } + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID then we return CRC failure + else if (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: CLS/ID match but failed CRC after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //We received invalid data + } + } + + delayMicroseconds(500); + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. No packet received.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//Save current configuration to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfiguration(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = 0xFF; //Set any bit in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save the selected configuration sub-sections to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfigSelective(uint32_t configMask, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = configMask & 0xFF; //Set the appropriate bits in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = (configMask >> 8) & 0xFF; + packetCfg.payload[6] = (configMask >> 16) & 0xFF; + packetCfg.payload[7] = (configMask >> 24) & 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Reset module to factory defaults +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::factoryDefault(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[0] = 0xFF; //Set any bit in the clearMask field to clear saved config + packetCfg.payload[1] = 0xFF; + packetCfg.payload[8] = 0xFF; //Set any bit in the loadMask field to discard current config and rebuild from lower non-volatile memory layers + packetCfg.payload[9] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, load the payload with data that can then be extracted to 8, 16, or 32 bits +//This function takes a full 32-bit key +//Default layer is RAM +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +sfe_ublox_status_e SFE_UBLOX_GPS::getVal(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALGET; + packetCfg.len = 4 + 4 * 1; //While multiple keys are allowed, we will send only one key at a time + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //VALGET uses different memory layer definitions to VALSET + //because it can only return the value for one layer. + //So we need to fiddle the layer here. + //And just to complicate things further, the ZED-F9P only responds + //correctly to layer 0 (RAM) and layer 7 (Default)! + uint8_t getLayer = 7; // 7 is the "Default Layer" + if ((layer & VAL_LAYER_RAM) == VAL_LAYER_RAM) // Did the user request the RAM layer? + { + getLayer = 0; // Layer 0 is RAM + } + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = getLayer; //Layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + if (_printDebug == true) + { + _debugSerial->print(F("key: 0x")); + _debugSerial->print(key, HEX); + _debugSerial->println(); + } + + //Send VALGET command with this key + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("getVal: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } + + //Verify the response is the correct length as compared to what the user called (did the module respond with 8-bits but the user called getVal32?) + //Response is 8 bytes plus cfg data + //if(packet->len > 8+1) + + //The response is now sitting in payload, ready for extraction + return (retVal); +} + +//Given a key, return its value +//This function takes a full 32-bit key +//Default layer is RAM +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + if (getVal(key, layer, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + return (extractByte(8)); +} +uint16_t SFE_UBLOX_GPS::getVal16(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + if (getVal(key, layer, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + return (extractInt(8)); +} +uint32_t SFE_UBLOX_GPS::getVal32(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + if (getVal(key, layer, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + return (extractLong(8)); +} + +//Form 32-bit key from group/id/size +uint32_t SFE_UBLOX_GPS::createKey(uint16_t group, uint16_t id, uint8_t size) +{ + uint32_t key = 0; + key |= (uint32_t)id; + key |= (uint32_t)group << 16; + key |= (uint32_t)size << 28; + return (key); +} + +//Given a group, ID and size, return the value of this config spot +//The 32-bit key is put together from group/ID/size. See other getVal to send key directly. +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + uint32_t key = createKey(group, id, size); + return getVal8(key, layer, maxWait); +} +uint16_t SFE_UBLOX_GPS::getVal16(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + uint32_t key = createKey(group, id, size); + return getVal16(key, layer, maxWait); +} +uint32_t SFE_UBLOX_GPS::getVal32(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + uint32_t key = createKey(group, id, size); + return getVal32(key, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + return setVal16(key, value, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal16(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set an 8-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal8(uint32_t key, uint8_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set a 32-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern Ublox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal32(uint32_t key, uint32_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset32(uint32_t key, uint32_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset16(uint32_t key, uint16_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset8(uint32_t key, uint8_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset32(uint32_t key, uint32_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + payloadCfg[packetCfg.len + 6] = value >> 8 * 2; + payloadCfg[packetCfg.len + 7] = value >> 8 * 3; + + //Update packet length: 4 byte key ID, 4 bytes of value + packetCfg.len = packetCfg.len + 4 + 4; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset16(uint32_t key, uint16_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + + //Update packet length: 4 byte key ID, 2 bytes of value + packetCfg.len = packetCfg.len + 4 + 2; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset8(uint32_t key, uint8_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value; //Value + + //Update packet length: 4 byte key ID, 1 byte value + packetCfg.len = packetCfg.len + 4 + 1; + + //All done + return (true); +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset32(uint32_t key, uint32_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset32(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset16(uint32_t key, uint16_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset16(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset8(uint32_t key, uint8_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset8(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the current TimeMode3 settings - these contain survey in statuses +boolean SFE_UBLOX_GPS::getSurveyMode(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Control Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + if (getSurveyMode(maxWait) == false) //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 40; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //payloadCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = mode; //Set mode. Survey-In and Disabled are most common. Use ECEF (not LAT/LON/ALT). + + //svinMinDur is U4 (uint32_t) but we'll only use a uint16_t (waiting more than 65535 seconds seems excessive!) + payloadCfg[24] = observationTime & 0xFF; //svinMinDur in seconds + payloadCfg[25] = observationTime >> 8; //svinMinDur in seconds + payloadCfg[26] = 0; //Truncate to 16 bits + payloadCfg[27] = 0; //Truncate to 16 bits + + //svinAccLimit is U4 (uint32_t) in 0.1mm. + uint32_t svinAccLimit = (uint32_t)(requiredAccuracy * 10000.0); //Convert m to 0.1mm + payloadCfg[28] = svinAccLimit & 0xFF; //svinAccLimit in 0.1mm increments + payloadCfg[29] = svinAccLimit >> 8; + payloadCfg[30] = svinAccLimit >> 16; + payloadCfg[31] = svinAccLimit >> 24; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Begin Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_ENABLE, observationTime, requiredAccuracy, maxWait)); +} + +//Stop Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::disableSurveyMode(uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_DISABLE, 0, 0, maxWait)); +} + +//Reads survey in status and sets the global variables +//for status, position valid, observation time, and mean 3D StdDev +//Returns true if commands was successful +boolean SFE_UBLOX_GPS::getSurveyStatus(uint16_t maxWait) +{ + //Reset variables + svin.active = false; + svin.valid = false; + svin.observationTime = 0; + svin.meanAccuracy = 0; + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SVIN; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if ((sendCommand(&packetCfg, maxWait)) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //We got a response, now parse the bits into the svin structure + + //dur (Passed survey-in observation time) is U4 (uint32_t) seconds. We truncate to 16 bits + //(waiting more than 65535 seconds (18.2 hours) seems excessive!) + uint32_t tmpObsTime = extractLong(8); + if (tmpObsTime <= 0xFFFF) + { + svin.observationTime = (uint16_t)tmpObsTime; + } + else + { + svin.observationTime = 0xFFFF; + } + + // meanAcc is U4 (uint32_t) in 0.1mm. We convert this to float. + uint32_t tempFloat = extractLong(28); + svin.meanAccuracy = ((float)tempFloat) / 10000.0; //Convert 0.1mm to m + + svin.valid = payloadCfg[36]; //1 if survey-in position is valid, 0 otherwise + svin.active = payloadCfg[37]; //1 if survey-in in progress, 0 otherwise + + return (true); +} + +//Loads the payloadCfg array with the current protocol bits located the UBX-CFG-PRT register for a given port +boolean SFE_UBLOX_GPS::getPortSettings(uint8_t portID, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 1; + packetCfg.startingSpot = 0; + + payloadCfg[0] = portID; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[14] = outStreamSettings; //OutProtocolMask LSB - Set outStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + //This will load the payloadCfg array with current port settings + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[12] = inStreamSettings; //InProtocolMask LSB - Set inStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a port to output UBX, NMEA, RTCM3 or a combination thereof +boolean SFE_UBLOX_GPS::setI2COutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_I2C, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART1Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART1, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART2Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART2, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUSBOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_USB, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setSPIOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_SPI, comSettings, maxWait)); +} + +//Set the rate at which the module will give us an updated navigation solution +//Expects a number that is the updates per second. For example 1 = 1Hz, 2 = 2Hz, etc. +//Max is 40Hz(?!) +boolean SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) +{ + //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values + + //Adjust the I2C polling timeout based on update rate + i2cPollingWait = 1000 / (((int)navFreq) * 4); //This is the number of ms to wait between checks for new I2C data + + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 1000 / navFreq; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[0] = measurementRate & 0xFF; //measRate LSB + payloadCfg[1] = measurementRate >> 8; //measRate MSB + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the rate at which the module is outputting nav solutions +uint8_t SFE_UBLOX_GPS::getNavigationFrequency(uint16_t maxWait) +{ + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //payloadCfg is now loaded with current bytes. Get what we need + uint16_t measurementRate = extractInt(0); //Pull from payloadCfg at measRate LSB + + measurementRate = 1000 / measurementRate; //This may return an int when it's a float, but I'd rather not return 4 bytes + return (measurementRate); +} + +//In case no config access to the GPS is possible and PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoPVT(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoPVT != enabled || autoPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoPVT = enabled; + autoPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, uint16_t maxWait) +{ + return setAutoPVT(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoPVT = enable; + autoPVTImplicitUpdate = implicitUpdate; + } + moduleQueried.all = false; + return ok; +} + +//In case no config access to the GPS is possible and HPPOSLLH is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHPPOSLLH(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHPPOSLLH != enabled || autoHPPOSLLHImplicitUpdate != implicitUpdate; + if (changes) + { + autoHPPOSLLH = enabled; + autoHPPOSLLHImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getHPPOSLLH +//works. +boolean SFE_UBLOX_GPS::setAutoHPPOSLLH(boolean enable, uint16_t maxWait) +{ + return setAutoHPPOSLLH(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getHPPOSLLH +//works. +boolean SFE_UBLOX_GPS::setAutoHPPOSLLH(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_HPPOSLLH; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHPPOSLLH = enable; + autoHPPOSLLHImplicitUpdate = implicitUpdate; + } + highResModuleQueried.all = false; + return ok; +} + + +//In case no config access to the GPS is possible and DOP is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoDOP(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoDOP != enabled || autoDOPImplicitUpdate != implicitUpdate; + if (changes) + { + autoDOP = enabled; + autoDOPImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getDOP +//works. +boolean SFE_UBLOX_GPS::setAutoDOP(boolean enable, uint16_t maxWait) +{ + return setAutoDOP(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getDOP +//works. +boolean SFE_UBLOX_GPS::setAutoDOP(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_DOP; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoDOP = enable; + autoDOPImplicitUpdate = implicitUpdate; + } + dopModuleQueried.all = false; + return ok; +} + +//Configure a given message type for a given port (UART1, I2C, SPI, etc) +boolean SFE_UBLOX_GPS::configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + //Poll for the current settings for a given message + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + payloadCfg[0] = msgClass; + payloadCfg[1] = msgID; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //Now send it back with new mods + packetCfg.len = 8; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[2 + portID] = sendRate; //Send rate is relative to the event a message is registered on. For example, if the rate of a navigation message is set to 2, the message is sent every 2nd navigation solution. + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable a given message type, default of 1 per update rate (usually 1 per second) +boolean SFE_UBLOX_GPS::enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, rate, maxWait)); +} +//Disable a given message type on a given port +boolean SFE_UBLOX_GPS::disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, 0, maxWait)); +} + +boolean SFE_UBLOX_GPS::enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(UBX_CLASS_NMEA, msgID, portID, rate, maxWait)); +} +boolean SFE_UBLOX_GPS::disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (enableNMEAMessage(msgID, portID, 0, maxWait)); +} + +//Given a message number turns on a message ID for output over a given portID (UART, I2C, SPI, USB, etc) +//To disable a message, set secondsBetween messages to 0 +//Note: This function will return false if the message is already enabled +//For base station RTK output we need to enable various sentences + +//NEO-M8P has four: +//1005 = 0xF5 0x05 - Stationary RTK reference ARP +//1077 = 0xF5 0x4D - GPS MSM7 +//1087 = 0xF5 0x57 - GLONASS MSM7 +//1230 = 0xF5 0xE6 - GLONASS code-phase biases, set to once every 10 seconds + +//ZED-F9P has six: +//1005, 1074, 1084, 1094, 1124, 1230 + +//Much of this configuration is not documented and instead discerned from u-center binary console +boolean SFE_UBLOX_GPS::enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + return (configureMessage(UBX_RTCM_MSB, messageNumber, portID, sendRate, maxWait)); +} + +//Disable a given message on a given port by setting secondsBetweenMessages to zero +boolean SFE_UBLOX_GPS::disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait) +{ + return (enableRTCMmessage(messageNumber, portID, 0, maxWait)); +} + +//Add a new geofence using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence, byte pinPolarity, byte pin, uint16_t maxWait) +{ + if (currentGeofenceParams.numFences >= 4) + return (false); // Quit if we already have four geofences defined + + // Store the new geofence parameters + currentGeofenceParams.lats[currentGeofenceParams.numFences] = latitude; + currentGeofenceParams.longs[currentGeofenceParams.numFences] = longitude; + currentGeofenceParams.rads[currentGeofenceParams.numFences] = radius; + currentGeofenceParams.numFences = currentGeofenceParams.numFences + 1; // Increment the number of fences + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = (currentGeofenceParams.numFences * 12) + 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = currentGeofenceParams.numFences; // numFences + payloadCfg[2] = confidence; // confLvl = Confidence level 0-4 (none, 68%, 95%, 99.7%, 99.99%) + payloadCfg[3] = 0; // reserved1 + if (pin > 0) + { + payloadCfg[4] = 1; // enable PIO combined fence state + } + else + { + payloadCfg[4] = 0; // disable PIO combined fence state + } + payloadCfg[5] = pinPolarity; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = pin; // PIO pin + payloadCfg[7] = 0; //reserved2 + payloadCfg[8] = currentGeofenceParams.lats[0] & 0xFF; + payloadCfg[9] = currentGeofenceParams.lats[0] >> 8; + payloadCfg[10] = currentGeofenceParams.lats[0] >> 16; + payloadCfg[11] = currentGeofenceParams.lats[0] >> 24; + payloadCfg[12] = currentGeofenceParams.longs[0] & 0xFF; + payloadCfg[13] = currentGeofenceParams.longs[0] >> 8; + payloadCfg[14] = currentGeofenceParams.longs[0] >> 16; + payloadCfg[15] = currentGeofenceParams.longs[0] >> 24; + payloadCfg[16] = currentGeofenceParams.rads[0] & 0xFF; + payloadCfg[17] = currentGeofenceParams.rads[0] >> 8; + payloadCfg[18] = currentGeofenceParams.rads[0] >> 16; + payloadCfg[19] = currentGeofenceParams.rads[0] >> 24; + if (currentGeofenceParams.numFences >= 2) + { + payloadCfg[20] = currentGeofenceParams.lats[1] & 0xFF; + payloadCfg[21] = currentGeofenceParams.lats[1] >> 8; + payloadCfg[22] = currentGeofenceParams.lats[1] >> 16; + payloadCfg[23] = currentGeofenceParams.lats[1] >> 24; + payloadCfg[24] = currentGeofenceParams.longs[1] & 0xFF; + payloadCfg[25] = currentGeofenceParams.longs[1] >> 8; + payloadCfg[26] = currentGeofenceParams.longs[1] >> 16; + payloadCfg[27] = currentGeofenceParams.longs[1] >> 24; + payloadCfg[28] = currentGeofenceParams.rads[1] & 0xFF; + payloadCfg[29] = currentGeofenceParams.rads[1] >> 8; + payloadCfg[30] = currentGeofenceParams.rads[1] >> 16; + payloadCfg[31] = currentGeofenceParams.rads[1] >> 24; + } + if (currentGeofenceParams.numFences >= 3) + { + payloadCfg[32] = currentGeofenceParams.lats[2] & 0xFF; + payloadCfg[33] = currentGeofenceParams.lats[2] >> 8; + payloadCfg[34] = currentGeofenceParams.lats[2] >> 16; + payloadCfg[35] = currentGeofenceParams.lats[2] >> 24; + payloadCfg[36] = currentGeofenceParams.longs[2] & 0xFF; + payloadCfg[37] = currentGeofenceParams.longs[2] >> 8; + payloadCfg[38] = currentGeofenceParams.longs[2] >> 16; + payloadCfg[39] = currentGeofenceParams.longs[2] >> 24; + payloadCfg[40] = currentGeofenceParams.rads[2] & 0xFF; + payloadCfg[41] = currentGeofenceParams.rads[2] >> 8; + payloadCfg[42] = currentGeofenceParams.rads[2] >> 16; + payloadCfg[43] = currentGeofenceParams.rads[2] >> 24; + } + if (currentGeofenceParams.numFences >= 4) + { + payloadCfg[44] = currentGeofenceParams.lats[3] & 0xFF; + payloadCfg[45] = currentGeofenceParams.lats[3] >> 8; + payloadCfg[46] = currentGeofenceParams.lats[3] >> 16; + payloadCfg[47] = currentGeofenceParams.lats[3] >> 24; + payloadCfg[48] = currentGeofenceParams.longs[3] & 0xFF; + payloadCfg[49] = currentGeofenceParams.longs[3] >> 8; + payloadCfg[50] = currentGeofenceParams.longs[3] >> 16; + payloadCfg[51] = currentGeofenceParams.longs[3] >> 24; + payloadCfg[52] = currentGeofenceParams.rads[3] & 0xFF; + payloadCfg[53] = currentGeofenceParams.rads[3] >> 8; + payloadCfg[54] = currentGeofenceParams.rads[3] >> 16; + payloadCfg[55] = currentGeofenceParams.rads[3] >> 24; + } + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear all geofences using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::clearGeofences(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = 0; // numFences + payloadCfg[2] = 0; // confLvl + payloadCfg[3] = 0; // reserved1 + payloadCfg[4] = 0; // disable PIO combined fence state + payloadCfg[5] = 0; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = 0; // PIO pin + payloadCfg[7] = 0; //reserved2 + + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear the antenna control settings using UBX-CFG-ANT +//This function is hopefully redundant but may be needed to release +//any PIO pins pre-allocated for antenna functions +boolean SFE_UBLOX_GPS::clearAntPIO(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_ANT; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x10; // Antenna flag mask: set the recovery bit + payloadCfg[1] = 0; + payloadCfg[2] = 0xFF; // Antenna pin configuration: set pinSwitch and pinSCD to 31 + payloadCfg[3] = 0xFF; // Antenna pin configuration: set pinOCD to 31, set reconfig bit + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Returns the combined geofence state using UBX-NAV-GEOFENCE +boolean SFE_UBLOX_GPS::getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_GEOFENCE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the geofence status. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + currentGeofenceState.status = payloadCfg[5]; // Extract the status + currentGeofenceState.numFences = payloadCfg[6]; // Extract the number of geofences + currentGeofenceState.combState = payloadCfg[7]; // Extract the combined state of all geofences + if (currentGeofenceState.numFences > 0) + currentGeofenceState.states[0] = payloadCfg[8]; // Extract geofence 1 state + if (currentGeofenceState.numFences > 1) + currentGeofenceState.states[1] = payloadCfg[10]; // Extract geofence 2 state + if (currentGeofenceState.numFences > 2) + currentGeofenceState.states[2] = payloadCfg[12]; // Extract geofence 3 state + if (currentGeofenceState.numFences > 3) + currentGeofenceState.states[3] = payloadCfg[14]; // Extract geofence 4 state + + return (true); +} + +//Power Save Mode +//Enables/Disables Low Power Mode using UBX-CFG-RXM +boolean SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (false); + } + + // Now let's change the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + if (power_save) + { + payloadCfg[1] = 1; // Power Save Mode + } + else + { + payloadCfg[1] = 0; // Continuous Mode + } + + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// Get Power Save Mode +// Returns the current Low Power Mode using UBX-CFG-RXM +// Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getPowerSaveMode(uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (255); + } + + // Now let's read the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[1]); // Return the low power mode +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +boolean SFE_UBLOX_GPS::powerOff(uint32_t durationInMs, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + _debugSerial->print(F("Powering off for ")); + _debugSerial->print(durationInMs); + _debugSerial->println(" ms"); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + // duration + // big endian to little endian, switch byte order + payloadCfg[0] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[1] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[2] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[3] = (durationInMs >> (8 * 3)) & 0xff; + + payloadCfg[4] = 0x02; //Flags : set the backup bit + payloadCfg[5] = 0x00; //Flags + payloadCfg[6] = 0x00; //Flags + payloadCfg[7] = 0x00; //Flags + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// While powered off it can be woken up by creating a falling or rising voltage edge on the specified pin. +// NOTE: The GPS seems to be sensitve to signals on the pins while powered off. Works best when Microcontroller is in deepsleep. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +boolean SFE_UBLOX_GPS::powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources, boolean forceWhileUsb, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + _debugSerial->print(F("Powering off for ")); + _debugSerial->print(durationInMs); + _debugSerial->println(" ms"); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 16; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x00; // message version + + // bytes 1-3 are reserved - and must be set to zero + payloadCfg[1] = 0x00; + payloadCfg[2] = 0x00; + payloadCfg[3] = 0x00; + + // duration + // big endian to little endian, switch byte order + payloadCfg[4] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[5] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[6] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[7] = (durationInMs >> (8 * 3)) & 0xff; + + // flags + + // disables USB interface when powering off, defaults to true + if (forceWhileUsb) + { + payloadCfg[8] = 0x06; // force | backup + } + else + { + payloadCfg[8] = 0x02; // backup only (leave the force bit clear - module will stay on if USB is connected) + } + + payloadCfg[9] = 0x00; + payloadCfg[10] = 0x00; + payloadCfg[11] = 0x00; + + // wakeUpSources + + // wakeupPin mapping, defaults to VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + + // Possible values are: + // VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 + // VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS + + payloadCfg[12] = (wakeupSources >> (8 * 0)) & 0xff; + payloadCfg[13] = (wakeupSources >> (8 * 1)) & 0xff; + payloadCfg[14] = (wakeupSources >> (8 * 2)) & 0xff; + payloadCfg[15] = (wakeupSources >> (8 * 3)) & 0xff; + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +//Change the dynamic platform model using UBX-CFG-NAV5 +//Possible values are: +//PORTABLE,STATIONARY,PEDESTRIAN,AUTOMOTIVE,SEA, +//AIRBORNE1g,AIRBORNE2g,AIRBORNE4g,WRIST,BIKE +//WRIST is not supported in protocol versions less than 18 +//BIKE is supported in protocol versions 19.2 +boolean SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) + payloadCfg[1] = 0x00; // mask + payloadCfg[2] = newDynamicModel; // dynModel + + packetCfg.len = 36; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the dynamic platform model using UBX-CFG-NAV5 +//Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getDynamicModel(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[2]); // Return the dynamic model +} + +//Given a spot in the payload array, extract four bytes and build a long +uint32_t SFE_UBLOX_GPS::extractLong(uint8_t spotToStart) +{ + uint32_t val = 0; + val |= (uint32_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint32_t)payloadCfg[spotToStart + 1] << 8 * 1; + val |= (uint32_t)payloadCfg[spotToStart + 2] << 8 * 2; + val |= (uint32_t)payloadCfg[spotToStart + 3] << 8 * 3; + return (val); +} + +//Just so there is no ambiguity about whether a uint32_t will cast to a int32_t correctly... +int32_t SFE_UBLOX_GPS::extractSignedLong(uint8_t spotToStart) +{ + union // Use a union to convert from uint32_t to int32_t + { + uint32_t unsignedLong; + int32_t signedLong; + } unsignedSigned; + + unsignedSigned.unsignedLong = extractLong(spotToStart); + return (unsignedSigned.signedLong); +} + +//Given a spot in the payload array, extract two bytes and build an int +uint16_t SFE_UBLOX_GPS::extractInt(uint8_t spotToStart) +{ + uint16_t val = 0; + val |= (uint16_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint16_t)payloadCfg[spotToStart + 1] << 8 * 1; + return (val); +} + +//Just so there is no ambiguity about whether a uint16_t will cast to a int16_t correctly... +int16_t SFE_UBLOX_GPS::extractSignedInt(int8_t spotToStart) +{ + union // Use a union to convert from uint16_t to int16_t + { + uint16_t unsignedInt; + int16_t signedInt; + } stSignedInt; + + stSignedInt.unsignedInt = extractInt(spotToStart); + return (stSignedInt.signedInt); +} + +//Given a spot, extract a byte from the payload +uint8_t SFE_UBLOX_GPS::extractByte(uint8_t spotToStart) +{ + return (payloadCfg[spotToStart]); +} + +//Given a spot, extract a signed 8-bit value from the payload +int8_t SFE_UBLOX_GPS::extractSignedChar(uint8_t spotToStart) +{ + return ((int8_t)payloadCfg[spotToStart]); +} + +//Get the current year +uint16_t SFE_UBLOX_GPS::getYear(uint16_t maxWait) +{ + if (moduleQueried.gpsYear == false) + getPVT(maxWait); + moduleQueried.gpsYear = false; //Since we are about to give this to user, mark this data as stale + return (gpsYear); +} + +//Get the current month +uint8_t SFE_UBLOX_GPS::getMonth(uint16_t maxWait) +{ + if (moduleQueried.gpsMonth == false) + getPVT(maxWait); + moduleQueried.gpsMonth = false; //Since we are about to give this to user, mark this data as stale + return (gpsMonth); +} + +//Get the current day +uint8_t SFE_UBLOX_GPS::getDay(uint16_t maxWait) +{ + if (moduleQueried.gpsDay == false) + getPVT(maxWait); + moduleQueried.gpsDay = false; //Since we are about to give this to user, mark this data as stale + return (gpsDay); +} + +//Get the current hour +uint8_t SFE_UBLOX_GPS::getHour(uint16_t maxWait) +{ + if (moduleQueried.gpsHour == false) + getPVT(maxWait); + moduleQueried.gpsHour = false; //Since we are about to give this to user, mark this data as stale + return (gpsHour); +} + +//Get the current minute +uint8_t SFE_UBLOX_GPS::getMinute(uint16_t maxWait) +{ + if (moduleQueried.gpsMinute == false) + getPVT(maxWait); + moduleQueried.gpsMinute = false; //Since we are about to give this to user, mark this data as stale + return (gpsMinute); +} + +//Get the current second +uint8_t SFE_UBLOX_GPS::getSecond(uint16_t maxWait) +{ + if (moduleQueried.gpsSecond == false) + getPVT(maxWait); + moduleQueried.gpsSecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsSecond); +} + +//Get the current date validity +bool SFE_UBLOX_GPS::getDateValid(uint16_t maxWait) +{ + if (moduleQueried.gpsDateValid == false) + getPVT(maxWait); + moduleQueried.gpsDateValid = false; //Since we are about to give this to user, mark this data as stale + return (gpsDateValid); +} + +//Get the current time validity +bool SFE_UBLOX_GPS::getTimeValid(uint16_t maxWait) +{ + if (moduleQueried.gpsTimeValid == false) + getPVT(maxWait); + moduleQueried.gpsTimeValid = false; //Since we are about to give this to user, mark this data as stale + return (gpsTimeValid); +} + +uint32_t SFE_UBLOX_GPS::getSpeedAccEst(uint16_t maxWait) +{ + if (moduleQueried.speedAccEst == false) + getPVT(maxWait); + moduleQueried.speedAccEst = false; //Since we are about to give this to user, mark this data as stale + return (speedAccEst); +} + +uint32_t SFE_UBLOX_GPS::getHeadingAccEst(uint16_t maxWait) +{ + if (moduleQueried.headingAccEst == false) + getPVT(maxWait); + moduleQueried.headingAccEst = false; //Since we are about to give this to user, mark this data as stale + return (headingAccEst); +} + +bool SFE_UBLOX_GPS::getInvalidLlh(uint16_t maxWait) +{ + if (moduleQueried.invalidLlh == false) + getPVT(maxWait); + moduleQueried.invalidLlh = false; //Since we are about to give this to user, mark this data as stale + return (invalidLlh); +} + +int32_t SFE_UBLOX_GPS::getHeadVeh(uint16_t maxWait) +{ + if (moduleQueried.headVeh == false) + getPVT(maxWait); + moduleQueried.headVeh = false; //Since we are about to give this to user, mark this data as stale + return (headVeh); +} + +int16_t SFE_UBLOX_GPS::getMagDec(uint16_t maxWait) +{ + if (moduleQueried.magDec == false) + getPVT(maxWait); + moduleQueried.magDec = false; //Since we are about to give this to user, mark this data as stale + return (magDec); +} + +uint16_t SFE_UBLOX_GPS::getMagAcc(uint16_t maxWait) +{ + if (moduleQueried.magAcc == false) + getPVT(maxWait); + moduleQueried.magAcc = false; //Since we are about to give this to user, mark this data as stale + return (magAcc); +} + +//Get the current millisecond +uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) +{ + if (moduleQueried.gpsiTOW == false) + getPVT(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (gpsMillisecond); +} + +//Get the current nanoseconds - includes milliseconds +int32_t SFE_UBLOX_GPS::getNanosecond(uint16_t maxWait) +{ + if (moduleQueried.gpsNanosecond == false) + getPVT(maxWait); + moduleQueried.gpsNanosecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsNanosecond); +} + +//Get the latest Position/Velocity/Time solution and fill all global variables +boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) +{ + if (autoPVT && autoPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_PVT); + return moduleQueried.all; + } + else if (autoPVT && !autoPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Polling")); + } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_PVT; + packetCfg.len = 0; + //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+MAX_PAYLOAD_SIZE = 84 bytes Note:now hard-coded in processUBX + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: data was OVERWRITTEN by another NAV message (but that's OK)")); + } + return (true); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: data was OVERWRITTEN by a HNR message (and that's not OK)")); + } + return (false); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getPVT retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } +} + +uint32_t SFE_UBLOX_GPS::getTimeOfWeek(uint16_t maxWait /* = 250*/) +{ + if (moduleQueried.gpsiTOW == false) + getPVT(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (timeOfWeek); +} + +int32_t SFE_UBLOX_GPS::getHighResLatitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitude = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (highResLatitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLatitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitudeHp = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (highResLatitudeHp); +} + +int32_t SFE_UBLOX_GPS::getHighResLongitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitude = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (highResLongitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLongitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitudeHp = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (highResLongitudeHp); +} + +int32_t SFE_UBLOX_GPS::getElipsoid(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoid == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoid = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (elipsoid); +} + +int8_t SFE_UBLOX_GPS::getElipsoidHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoidHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoidHp = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (elipsoidHp); +} + +int32_t SFE_UBLOX_GPS::getMeanSeaLevel(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevel == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevel = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (meanSeaLevel); +} + +int8_t SFE_UBLOX_GPS::getMeanSeaLevelHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevelHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevelHp = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (meanSeaLevelHp); +} + +// getGeoidSeparation is currently redundant. The geoid separation seems to only be provided in NMEA GGA and GNS messages. +int32_t SFE_UBLOX_GPS::getGeoidSeparation(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.geoidSeparation == false) + getHPPOSLLH(maxWait); + highResModuleQueried.geoidSeparation = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (geoidSeparation); +} + +uint32_t SFE_UBLOX_GPS::getHorizontalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.horizontalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.horizontalAccuracy = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (horizontalAccuracy); +} + +uint32_t SFE_UBLOX_GPS::getVerticalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.verticalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.verticalAccuracy = false; //Since we are about to give this to user, mark this data as stale + highResModuleQueried.all = false; + + return (verticalAccuracy); +} + +boolean SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) +{ + if (autoHPPOSLLH && autoHPPOSLLHImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHPPOSLLH: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_HPPOSLLH); + return highResModuleQueried.all; + } + else if (autoHPPOSLLH && !autoHPPOSLLHImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHPPOSLLH: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHPPOSLLH: Polling")); + } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSLLH; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by another NAV message (but that's OK)")); + } + return (true); + } + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by a HNR message (and that's not OK)")); + } + return (false); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHPPOSLLH retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } +} + +uint16_t SFE_UBLOX_GPS::getGeometricDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.geometricDOP == false) + getDOP(maxWait); + dopModuleQueried.geometricDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (geometricDOP); +} + +uint16_t SFE_UBLOX_GPS::getPositionDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.positionDOP == false) + getDOP(maxWait); + dopModuleQueried.positionDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (positionDOP); +} + +uint16_t SFE_UBLOX_GPS::getTimeDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.timeDOP == false) + getDOP(maxWait); + dopModuleQueried.timeDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (timeDOP); +} + +uint16_t SFE_UBLOX_GPS::getVerticalDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.verticalDOP == false) + getDOP(maxWait); + dopModuleQueried.verticalDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (verticalDOP); +} + +uint16_t SFE_UBLOX_GPS::getHorizontalDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.horizontalDOP == false) + getDOP(maxWait); + dopModuleQueried.horizontalDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (horizontalDOP); +} + +uint16_t SFE_UBLOX_GPS::getNorthingDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.northingDOP == false) + getDOP(maxWait); + dopModuleQueried.northingDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (northingDOP); +} + +uint16_t SFE_UBLOX_GPS::getEastingDOP(uint16_t maxWait /* = 250*/) +{ + if (dopModuleQueried.eastingDOP == false) + getDOP(maxWait); + dopModuleQueried.eastingDOP = false; //Since we are about to give this to user, mark this data as stale + dopModuleQueried.all = false; + + return (eastingDOP); +} + +boolean SFE_UBLOX_GPS::getDOP(uint16_t maxWait) +{ + if (autoDOP && autoDOPImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getDOP: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_DOP); + return dopModuleQueried.all; + } + else if (autoDOP && !autoDOPImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getDOP: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getDOP: Polling")); + } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_DOP; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getDOP: data was OVERWRITTEN by another NAV message (but that's OK)")); + } + return (true); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getDOP: data was OVERWRITTEN by a HNR message (and that's not OK)")); + } + return (false); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getDOP retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } +} + +//Get the current 3D high precision positional accuracy - a fun thing to watch +//Returns a long representing the 3D accuracy in millimeters +uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSECEF; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (0); //If command send fails then bail + + uint32_t tempAccuracy = extractLong(24); //We got a response, now extract a long beginning at a given position + + if ((tempAccuracy % 10) >= 5) + tempAccuracy += 5; //Round fraction of mm up to next mm if .5 or above + tempAccuracy /= 10; //Convert 0.1mm units to mm + + return (tempAccuracy); +} + +//Get the current latitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLatitude(uint16_t maxWait) +{ + if (moduleQueried.latitude == false) + getPVT(maxWait); + moduleQueried.latitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (latitude); +} + +//Get the current longitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLongitude(uint16_t maxWait) +{ + if (moduleQueried.longitude == false) + getPVT(maxWait); + moduleQueried.longitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (longitude); +} + +//Get the current altitude in mm according to ellipsoid model +int32_t SFE_UBLOX_GPS::getAltitude(uint16_t maxWait) +{ + if (moduleQueried.altitude == false) + getPVT(maxWait); + moduleQueried.altitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitude); +} + +//Get the current altitude in mm according to mean sea level +//Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html +//Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ +int32_t SFE_UBLOX_GPS::getAltitudeMSL(uint16_t maxWait) +{ + if (moduleQueried.altitudeMSL == false) + getPVT(maxWait); + moduleQueried.altitudeMSL = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitudeMSL); +} + +int32_t SFE_UBLOX_GPS::getHorizontalAccEst(uint16_t maxWait) +{ + if (moduleQueried.horizontalAccEst == false) + getPVT(maxWait); + moduleQueried.horizontalAccEst = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (horizontalAccEst); +} + +int32_t SFE_UBLOX_GPS::getVerticalAccEst(uint16_t maxWait) +{ + if (moduleQueried.verticalAccEst == false) + getPVT(maxWait); + moduleQueried.verticalAccEst = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (verticalAccEst); +} + +int32_t SFE_UBLOX_GPS::getNedNorthVel(uint16_t maxWait) +{ + if (moduleQueried.nedNorthVel == false) + getPVT(maxWait); + moduleQueried.nedNorthVel = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (nedNorthVel); +} + +int32_t SFE_UBLOX_GPS::getNedEastVel(uint16_t maxWait) +{ + if (moduleQueried.nedEastVel == false) + getPVT(maxWait); + moduleQueried.nedEastVel = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (nedEastVel); +} + +int32_t SFE_UBLOX_GPS::getNedDownVel(uint16_t maxWait) +{ + if (moduleQueried.nedDownVel == false) + getPVT(maxWait); + moduleQueried.nedDownVel = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (nedDownVel); +} + +//Get the number of satellites used in fix +uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) +{ + if (moduleQueried.SIV == false) + getPVT(maxWait); + moduleQueried.SIV = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (SIV); +} + +//Get the current fix type +//0=no fix, 1=dead reckoning, 2=2D, 3=3D, 4=GNSS, 5=Time fix +uint8_t SFE_UBLOX_GPS::getFixType(uint16_t maxWait) +{ + if (moduleQueried.fixType == false) + { + getPVT(maxWait); + } + moduleQueried.fixType = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (fixType); +} + +//Get whether we have a valid fix (i.e within DOP & accuracy masks) +bool SFE_UBLOX_GPS::getGnssFixOk(uint16_t maxWait) +{ + if (moduleQueried.gnssFixOk == false) + getPVT(maxWait); + moduleQueried.gnssFixOk = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (gnssFixOk); +} + +//Get whether differential corrections were applied +bool SFE_UBLOX_GPS::getDiffSoln(uint16_t maxWait) +{ + if (moduleQueried.diffSoln == false) + getPVT(maxWait); + moduleQueried.diffSoln = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (diffSoln); +} + +//Get the carrier phase range solution status +//Useful when querying module to see if it has high-precision RTK fix +//0=No solution, 1=Float solution, 2=Fixed solution +uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) +{ + if (moduleQueried.carrierSolution == false) + getPVT(maxWait); + moduleQueried.carrierSolution = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (carrierSolution); +} + +//Get whether head vehicle valid or not +bool SFE_UBLOX_GPS::getHeadVehValid(uint16_t maxWait) +{ + if (moduleQueried.headVehValid == false) + getPVT(maxWait); + moduleQueried.headVehValid = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headVehValid); +} + + +//Get the ground speed in mm/s +int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) +{ + if (moduleQueried.groundSpeed == false) + getPVT(maxWait); + moduleQueried.groundSpeed = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (groundSpeed); +} + +//Get the heading of motion (as opposed to heading of car) in degrees * 10^-5 +int32_t SFE_UBLOX_GPS::getHeading(uint16_t maxWait) +{ + if (moduleQueried.headingOfMotion == false) + getPVT(maxWait); + moduleQueried.headingOfMotion = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headingOfMotion); +} + +//Get the positional dillution of precision * 10^-2 (dimensionless) +uint16_t SFE_UBLOX_GPS::getPDOP(uint16_t maxWait) +{ + if (moduleQueried.pDOP == false) + getPVT(maxWait); + moduleQueried.pDOP = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (pDOP); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionHigh(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionHigh); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionLow(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionLow); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +boolean SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) +{ + //Send packet with only CLS and ID, length of zero. This will cause the module to respond with the contents of that CLS/ID. + packetCfg.cls = UBX_CLASS_MON; + packetCfg.id = UBX_MON_VER; + + packetCfg.len = 0; + packetCfg.startingSpot = 40; //Start at first "extended software information" string + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //Payload should now contain ~220 characters (depends on module type) + + // if (_printDebug == true) + // { + // _debugSerial->print(F("MON VER Payload:")); + // for (int location = 0; location < packetCfg.len; location++) + // { + // if (location % 30 == 0) + // _debugSerial->println(); + // _debugSerial->write(payloadCfg[location]); + // } + // _debugSerial->println(); + // } + + //We will step through the payload looking at each extension field of 30 bytes + for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) + { + //Now we need to find "PROTVER=18.00" in the incoming byte stream + if ((payloadCfg[(30 * extensionNumber) + 0] == 'P') && (payloadCfg[(30 * extensionNumber) + 6] == 'R')) + { + versionHigh = (payloadCfg[(30 * extensionNumber) + 8] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 9] - '0'); //Convert '18' to 18 + versionLow = (payloadCfg[(30 * extensionNumber) + 11] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 12] - '0'); //Convert '00' to 00 + moduleQueried.versionNumber = true; //Mark this data as new + + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version: ")); + _debugSerial->print(versionHigh); + _debugSerial->print(F(".")); + _debugSerial->println(versionLow); + } + return (true); //Success! + } + } + + return (false); //We failed +} + +//Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushPVT() +{ + //Mark all datums as stale (read before) + moduleQueried.gpsiTOW = false; + moduleQueried.gpsYear = false; + moduleQueried.gpsMonth = false; + moduleQueried.gpsDay = false; + moduleQueried.gpsHour = false; + moduleQueried.gpsMinute = false; + moduleQueried.gpsSecond = false; + moduleQueried.gpsDateValid = false; + moduleQueried.gpsTimeValid = false; + moduleQueried.gpsNanosecond = false; + + moduleQueried.all = false; + moduleQueried.gnssFixOk = false; + moduleQueried.diffSoln = false; + moduleQueried.headVehValid = false; + moduleQueried.longitude = false; + moduleQueried.latitude = false; + moduleQueried.altitude = false; + moduleQueried.altitudeMSL = false; + moduleQueried.SIV = false; + moduleQueried.fixType = false; + moduleQueried.carrierSolution = false; + moduleQueried.groundSpeed = false; + moduleQueried.headingOfMotion = false; + moduleQueried.speedAccEst = false; + moduleQueried.headingAccEst = false; + moduleQueried.pDOP = false; + moduleQueried.invalidLlh = false; + moduleQueried.headVeh = false; + moduleQueried.magDec = false; + moduleQueried.magAcc = false; +} + +//Mark all the HPPOSLLH data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushHPPOSLLH() +{ + //Mark all datums as stale (read before) + highResModuleQueried.all = false; + highResModuleQueried.highResLatitude = false; + highResModuleQueried.highResLatitudeHp = false; + highResModuleQueried.highResLongitude = false; + highResModuleQueried.highResLongitudeHp = false; + highResModuleQueried.elipsoid = false; + highResModuleQueried.elipsoidHp = false; + highResModuleQueried.meanSeaLevel = false; + highResModuleQueried.meanSeaLevelHp = false; + highResModuleQueried.geoidSeparation = false; + highResModuleQueried.horizontalAccuracy = false; + highResModuleQueried.verticalAccuracy = false; + //moduleQueried.gpsiTOW = false; // this can arrive via HPPOS too. +} + +//Mark all the DOP data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushDOP() +{ + //Mark all DOPs as stale (read before) + dopModuleQueried.all = false; + dopModuleQueried.geometricDOP = false; + dopModuleQueried.positionDOP = false; + dopModuleQueried.timeDOP = false; + dopModuleQueried.verticalDOP = false; + dopModuleQueried.horizontalDOP = false; + dopModuleQueried.northingDOP = false; + dopModuleQueried.eastingDOP = false; +} + +//Relative Positioning Information in NED frame +//Returns true if commands was successful +//Note: +// RELPOSNED on the M8 is only 40 bytes long +// RELPOSNED on the F9 is 64 bytes long and contains much more information +boolean SFE_UBLOX_GPS::getRELPOSNED(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RELPOSNED; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //We got a response, now parse the bits + + uint16_t refStationID = extractInt(2); + //_debugSerial->print(F("refStationID: ")); + //_debugSerial->println(refStationID)); + + int32_t tempRelPos; + + tempRelPos = extractSignedLong(8); + relPosInfo.relPosN = ((float)tempRelPos) / 100.0; //Convert cm to m + + tempRelPos = extractSignedLong(12); + relPosInfo.relPosE = ((float)tempRelPos) / 100.0; //Convert cm to m + + tempRelPos = extractSignedLong(16); + relPosInfo.relPosD = ((float)tempRelPos) / 100.0; //Convert cm to m + + if (packetCfg.len == 40) + { + // The M8 version does not contain relPosLength or relPosHeading + relPosInfo.relPosLength = 0; + relPosInfo.relPosHeading = 0; + } + else + { + relPosInfo.relPosLength = extractSignedLong(20); + relPosInfo.relPosHeading = extractSignedLong(24); + } + + if (packetCfg.len == 40) + { + relPosInfo.relPosHPN = payloadCfg[20]; + relPosInfo.relPosHPE = payloadCfg[21]; + relPosInfo.relPosHPD = payloadCfg[22]; + relPosInfo.relPosHPLength = 0; // The M8 version does not contain relPosHPLength + } + else + { + relPosInfo.relPosHPN = payloadCfg[32]; + relPosInfo.relPosHPE = payloadCfg[33]; + relPosInfo.relPosHPD = payloadCfg[34]; + relPosInfo.relPosHPLength = payloadCfg[35]; + } + + uint32_t tempAcc; + + if (packetCfg.len == 40) + { + tempAcc = extractLong(24); + relPosInfo.accN = ((float)tempAcc) / 10000.0; //Convert 0.1 mm to m + tempAcc = extractLong(28); + relPosInfo.accE = ((float)tempAcc) / 10000.0; //Convert 0.1 mm to m + tempAcc = extractLong(32); + relPosInfo.accD = ((float)tempAcc) / 10000.0; //Convert 0.1 mm to m + } + else + { + tempAcc = extractLong(36); + relPosInfo.accN = ((float)tempAcc) / 10000.0; //Convert 0.1 mm to m + tempAcc = extractLong(40); + relPosInfo.accE = ((float)tempAcc) / 10000.0; //Convert 0.1 mm to m + tempAcc = extractLong(44); + relPosInfo.accD = ((float)tempAcc) / 10000.0; //Convert 0.1 mm to m + } + + uint8_t flags; + + if (packetCfg.len == 40) + { + flags = payloadCfg[36]; + } + else + { + flags = payloadCfg[60]; + } + + relPosInfo.gnssFixOk = flags & (1 << 0); + relPosInfo.diffSoln = flags & (1 << 1); + relPosInfo.relPosValid = flags & (1 << 2); + relPosInfo.carrSoln = (flags & (0b11 << 3)) >> 3; + relPosInfo.isMoving = flags & (1 << 5); + relPosInfo.refPosMiss = flags & (1 << 6); + relPosInfo.refObsMiss = flags & (1 << 7); + + return (true); +} + +boolean SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) +{ + // Requesting Data from the receiver + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + //checkUblox(); + + // payload should be loaded. + imuMeas.version = extractByte(4); + imuMeas.fusionMode = extractByte(12); + ubloxSen.numSens = extractByte(15); + + // Individual Status Sensor in different function + return (true); +} + +// +boolean SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_INS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + //checkUblox(); + + // Validity of each sensor value below + uint32_t validity = extractLong(0); + + imuMeas.xAngRateVald = (validity & 0x0100) >> 8; + imuMeas.yAngRateVald = (validity & 0x0200) >> 9; + imuMeas.zAngRateVald = (validity & 0x0400) >> 10; + imuMeas.xAccelVald = (validity & 0x0800) >> 11; + imuMeas.yAccelVald = (validity & 0x1000) >> 12; + imuMeas.zAccelVald = (validity & 0x2000) >> 13; + + imuMeas.xAngRate = extractSignedLong(12); // 0.001 deg/s + imuMeas.yAngRate = extractSignedLong(16); // 0.001 deg/s + imuMeas.zAngRate = extractSignedLong(20); // 0.001 deg/s + + imuMeas.xAccel = extractSignedLong(24); // 0.01 m/s^2 + imuMeas.yAccel = extractSignedLong(28); // 0.01 m/s^2 + imuMeas.zAccel = extractSignedLong(32); // 0.01 m/s^2 + + return (true); +} + +// +boolean SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_MEAS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + //checkUblox(); + + uint32_t timeStamp = extractLong(0); + uint32_t flags = extractInt(4); + + uint8_t timeSent = flags & 0x03; // timeSent is 2-bit: 0 = none, 1 = on Ext0, 2 = on Ext1 + uint8_t timeEdge = (flags & 0x04) >> 2; + uint8_t tagValid = (flags & 0x08) >> 3; + uint8_t numMeas = (flags & 0xF800) >> 11; + + if (numMeas > DEF_NUM_SENS) // Truncate numMeas if required + numMeas = DEF_NUM_SENS; + + uint8_t byteOffset = 4; + + for (uint8_t i = 0; i < numMeas; i++) + { + uint32_t bitField = extractLong(8 + (byteOffset * i)); + imuMeas.dataType[i] = (bitField & 0x3F000000) >> 24; + imuMeas.data[i] = (bitField & 0xFFFFFF); + } + + numMeas = (flags & 0xF800) >> 11; // Restore numMeas + + if (packetCfg.len > (8 + (4 * numMeas))) // The calibTtag is optional - only extract it if it is present + { + uint8_t startOfTtag = 8 + (4 * numMeas); // Calculate where the Ttag data starts + if (numMeas > DEF_NUM_SENS) // Truncate numMeas again if required + numMeas = DEF_NUM_SENS; + for (uint8_t i = 0; i < numMeas; i++) + { + imuMeas.dataTStamp[i] = extractLong(startOfTtag); // calibTtag is only appended once + } + } + + return (true); +} + +boolean SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) +{ + + // Need to know the number of sensor to get the correct data + // Rate selected in UBX-CFG-MSG is not respected + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RAW; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + //checkUblox(); + + uint32_t bitField = extractLong(4); + imuMeas.rawDataType = (bitField & 0xFF000000) >> 24; + imuMeas.rawData = (bitField & 0xFFFFFF); + + imuMeas.rawTStamp = extractLong(8); + + return (true); +} + +// Note: senor numbering starts at 1 (not 0) +sfe_ublox_status_e SFE_UBLOX_GPS::getSensState(uint8_t sensor, uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + ubloxSen.numSens = extractByte(15); + + if (sensor > ubloxSen.numSens) + return (SFE_UBLOX_STATUS_OUT_OF_RANGE); + + //checkUblox(); + + uint8_t offset = 4; + + // Only the last sensor value checked will remain. + for (uint8_t i = 0; i < sensor; i++) + { + + uint8_t sensorFieldOne = extractByte(16 + (offset * i)); + uint8_t sensorFieldTwo = extractByte(17 + (offset * i)); + ubloxSen.freq = extractByte(18 + (offset * i)); + uint8_t sensorFieldThr = extractByte(19 + offset * i); + + ubloxSen.senType = (sensorFieldOne & 0x3F); + ubloxSen.isUsed = (sensorFieldOne & 0x40) >> 6; + ubloxSen.isReady = (sensorFieldOne & 0x80) >> 7; + + ubloxSen.calibStatus = sensorFieldTwo & 0x03; + ubloxSen.timeStatus = (sensorFieldTwo & 0xC) >> 2; + + ubloxSen.badMeas = (sensorFieldThr & 0x01); + ubloxSen.badTag = (sensorFieldThr & 0x02) >> 1; + ubloxSen.missMeas = (sensorFieldThr & 0x04) >> 2; + ubloxSen.noisyMeas = (sensorFieldThr & 0x08) >> 3; + } + + return (SFE_UBLOX_STATUS_SUCCESS); +} + +boolean SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_ATT; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + //checkUblox(); + + vehAtt.roll = extractSignedLong(8); // 0.00001 deg + vehAtt.pitch = extractSignedLong(12); // 0.00001 deg + vehAtt.heading = extractSignedLong(16); // 0.00001 deg + + vehAtt.accRoll = extractLong(20); // 0.00001 deg + vehAtt.accPitch = extractLong(24); // 0.00001 deg + vehAtt.accHeading = extractLong(28); // 0.00001 deg + + return (true); +} + +//Set the ECEF or Lat/Long coordinates of a receiver +//This imediately puts the receiver in TIME mode (fixed) and will begin outputting RTCM sentences if enabled +//This is helpful once an antenna's position has been established. See this tutorial: https://learn.sparkfun.com/tutorials/how-to-build-a-diy-gnss-reference-station#gather-raw-gnss-data +// For ECEF the units are: cm, 0.1mm, cm, 0.1mm, cm, 0.1mm +// For Lat/Lon/Alt the units are: degrees^-7, degrees^-9, degrees^-7, degrees^-9, cm, 0.1mm +bool SFE_UBLOX_GPS::setStaticPosition(int32_t ecefXOrLat, int8_t ecefXOrLatHP, int32_t ecefYOrLon, int8_t ecefYOrLonHP, int32_t ecefZOrAlt, int8_t ecefZOrAltHP, bool latLong, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); + + packetCfg.len = 40; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + payloadCfg[x] = 0; + + //customCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = 2; //Set mode to fixed. Use ECEF (not LAT/LON/ALT). + + if (latLong == true) + payloadCfg[3] = (uint8_t)(1 << 0); //Set mode to fixed. Use LAT/LON/ALT. + + //Set ECEF X or Lat + payloadCfg[4] = (ecefXOrLat >> 8 * 0) & 0xFF; //LSB + payloadCfg[5] = (ecefXOrLat >> 8 * 1) & 0xFF; + payloadCfg[6] = (ecefXOrLat >> 8 * 2) & 0xFF; + payloadCfg[7] = (ecefXOrLat >> 8 * 3) & 0xFF; //MSB + + //Set ECEF Y or Long + payloadCfg[8] = (ecefYOrLon >> 8 * 0) & 0xFF; //LSB + payloadCfg[9] = (ecefYOrLon >> 8 * 1) & 0xFF; + payloadCfg[10] = (ecefYOrLon >> 8 * 2) & 0xFF; + payloadCfg[11] = (ecefYOrLon >> 8 * 3) & 0xFF; //MSB + + //Set ECEF Z or Altitude + payloadCfg[12] = (ecefZOrAlt >> 8 * 0) & 0xFF; //LSB + payloadCfg[13] = (ecefZOrAlt >> 8 * 1) & 0xFF; + payloadCfg[14] = (ecefZOrAlt >> 8 * 2) & 0xFF; + payloadCfg[15] = (ecefZOrAlt >> 8 * 3) & 0xFF; //MSB + + //Set high precision parts + payloadCfg[16] = ecefXOrLatHP; + payloadCfg[17] = ecefYOrLonHP; + payloadCfg[18] = ecefZOrAltHP; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +bool SFE_UBLOX_GPS::setStaticPosition(int32_t ecefXOrLat, int32_t ecefYOrLon, int32_t ecefZOrAlt, bool latlong, uint16_t maxWait) +{ + return (setStaticPosition(ecefXOrLat, 0, ecefYOrLon, 0, ecefZOrAlt, 0, latlong, maxWait)); +} + +// Push (e.g.) RTCM data directly to the module +// Returns true if all numDataBytes were pushed successfully +// Warning: this function does not check that the data is valid. It is the user's responsibility to ensure the data is valid before pushing. +boolean SFE_UBLOX_GPS::pushRawData(uint8_t *dataBytes, size_t numDataBytes) +{ + if (commType == COMM_TYPE_SERIAL) + { + // Serial: write all the bytes in one go + size_t bytesWritten = _serialPort->write(dataBytes, numDataBytes); + return (bytesWritten == numDataBytes); + } + else + { + // I2C: split the data up into packets of i2cTransactionSize + size_t bytesLeftToWrite = numDataBytes; + size_t bytesWrittenTotal = 0; + + while (bytesLeftToWrite > 0) + { + size_t bytesToWrite; // Limit bytesToWrite to i2cTransactionSize + if (bytesLeftToWrite > i2cTransactionSize) + bytesToWrite = i2cTransactionSize; + else + bytesToWrite = bytesLeftToWrite; + + _i2cPort->beginTransmission(_gpsI2Caddress); + size_t bytesWritten = _i2cPort->write(dataBytes, bytesToWrite); // Write the bytes + + bytesWrittenTotal += bytesWritten; // Update the totals + bytesLeftToWrite -= bytesToWrite; + dataBytes += bytesToWrite; // Point to fresh data + + if (bytesLeftToWrite > 0) + { + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + } + else + { + if (_i2cPort->endTransmission() != 0) //We're done. Release bus. + return (false); //Sensor did not ACK + } + } + + return (bytesWrittenTotal == numDataBytes); + } +} + +// Set the High Navigation Rate +// Returns true if the setHNRNavigationRate is successful +boolean SFE_UBLOX_GPS::setHNRNavigationRate(uint8_t rate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_HNR; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current HNR settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); + + //Load the new navigation rate into payloadCfg + payloadCfg[0] = rate; + + //Update the navigation rate + sfe_ublox_status_e result = sendCommand(&packetCfg, maxWait); // We are only expecting an ACK + + //Adjust the I2C polling timeout based on update rate + if (result == SFE_UBLOX_STATUS_DATA_SENT) + i2cPollingWait = 1000 / (((int)rate) * 4); //This is the number of ms to wait between checks for new I2C data + + return (result == SFE_UBLOX_STATUS_DATA_SENT); +} + +// Get the High Navigation Rate +// Returns 0 if the getHNRNavigationRate fails +uint8_t SFE_UBLOX_GPS::getHNRNavigationRate(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_HNR; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current HNR settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + //Return the navigation rate + return (payloadCfg[0]); +} + +//In case no config access to the GPS is possible and HNR attitude is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRAtt(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHNRAtt != enabled || autoHNRAttImplicitUpdate != implicitUpdate; + if (changes) + { + autoHNRAtt = enabled; + autoHNRAttImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic HNR attitude message generation by the GPS. This changes the way getHNRAtt +//works. +boolean SFE_UBLOX_GPS::setAutoHNRAtt(boolean enable, uint16_t maxWait) +{ + return setAutoHNRAtt(enable, true, maxWait); +} + +//Enable or disable automatic HNR attitude message generation by the GPS. This changes the way getHNRAtt +//works. +boolean SFE_UBLOX_GPS::setAutoHNRAtt(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_ATT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHNRAtt = enable; + autoHNRAttImplicitUpdate = implicitUpdate; + } + hnrAttQueried = false; // Mark data as stale + return ok; +} + +//Get the HNR Attitude data +// Returns true if the get HNR attitude is successful. Data is returned in hnrAtt +// Note: if hnrAttQueried is true, it gets set to false by this function since we assume +// that the user will read hnrAtt immediately after this. I.e. this function will +// only return true _once_ after each auto HNR Att is processed +boolean SFE_UBLOX_GPS::getHNRAtt(uint16_t maxWait) +{ + if (autoHNRAtt && autoHNRAttImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_ATT); + if (hnrAttQueried) + { + hnrAttQueried = false; // Mark data as stale as we assume the user will read it after this + return true; + } + return false; + } + else if (autoHNRAtt && !autoHNRAttImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: Polling")); + } + + //The GPS is not automatically reporting HNR attitude so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_ATT; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: data was OVERWRITTEN by a NAV message (and that's not OK)")); + } + return (false); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: data was OVERWRITTEN by another HNR message (but that's OK)")); + } + return (true); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHNRAtt retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//In case no config access to the GPS is possible and HNR vehicle dynamics is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRDyn(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHNRDyn != enabled || autoHNRDynImplicitUpdate != implicitUpdate; + if (changes) + { + autoHNRDyn = enabled; + autoHNRDynImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic HNR vehicle dynamics message generation by the GPS. This changes the way getHNRDyn +//works. +boolean SFE_UBLOX_GPS::setAutoHNRDyn(boolean enable, uint16_t maxWait) +{ + return setAutoHNRDyn(enable, true, maxWait); +} + +//Enable or disable automatic HNR vehicle dynamics message generation by the GPS. This changes the way getHNRDyn +//works. +boolean SFE_UBLOX_GPS::setAutoHNRDyn(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_INS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHNRDyn = enable; + autoHNRDynImplicitUpdate = implicitUpdate; + } + hnrDynQueried = false; // Mark data as stale + return ok; +} + +//Get the HNR vehicle dynamics data +// Returns true if the get HNR vehicle dynamics is successful. Data is returned in hnrVehDyn +// Note: if hnrDynQueried is true, it gets set to false by this function since we assume +// that the user will read hnrVehDyn immediately after this. I.e. this function will +// only return true _once_ after each auto HNR Dyn is processed +boolean SFE_UBLOX_GPS::getHNRDyn(uint16_t maxWait) +{ + if (autoHNRDyn && autoHNRDynImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_INS); + if (hnrDynQueried) + { + hnrDynQueried = false; // Mark data as stale as we assume the user will read it after this + return true; + } + return false; + } + else if (autoHNRDyn && !autoHNRDynImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: Polling")); + } + + //The GPS is not automatically reporting HNR vehicle dynamics so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_INS; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: data was OVERWRITTEN by a NAV message (and that's not OK)")); + } + return (false); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: data was OVERWRITTEN by another HNR message (but that's OK)")); + } + return (true); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHNRDyn retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//In case no config access to the GPS is possible and HNR PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRPVT(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHNRPVT != enabled || autoHNRPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoHNRPVT = enabled; + autoHNRPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic HNR PVT message generation by the GPS. This changes the way getHNRPVT +//works. +boolean SFE_UBLOX_GPS::setAutoHNRPVT(boolean enable, uint16_t maxWait) +{ + return setAutoHNRPVT(enable, true, maxWait); +} + +//Enable or disable automatic HNR PVT message generation by the GPS. This changes the way getHNRPVT +//works. +boolean SFE_UBLOX_GPS::setAutoHNRPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHNRPVT = enable; + autoHNRPVTImplicitUpdate = implicitUpdate; + } + hnrPVTQueried = false; // Mark data as stale + return ok; +} + +//Get the HNR PVT data +// Returns true if the get HNR PVT is successful. Data is returned in hnrPVT +// Note: if hnrPVTQueried is true, it gets set to false by this function since we assume +// that the user will read hnrPVT immediately after this. I.e. this function will +// only return true _once_ after each auto HNR PVT is processed +boolean SFE_UBLOX_GPS::getHNRPVT(uint16_t maxWait) +{ + if (autoHNRPVT && autoHNRPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_PVT); + if (hnrPVTQueried) + { + hnrPVTQueried = false; // Mark data as stale as we assume the user will read it after this + return true; + } + return false; + } + else if (autoHNRPVT && !autoHNRPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: Polling")); + } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_PVT; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: data was OVERWRITTEN by a NAV message (and that's not OK)")); + } + return (false); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: data was OVERWRITTEN by another HNR message (but that's OK)")); + } + return (true); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHNRPVT retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } + + return (false); // Trap. We should never get here... +} diff --git a/lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.h b/lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.h new file mode 100644 index 0000000..9a3e250 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/src/SparkFun_Ublox_Arduino_Library.h @@ -0,0 +1,1098 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SPARKFUN_UBLOX_ARDUINO_LIBRARY_H +#define SPARKFUN_UBLOX_ARDUINO_LIBRARY_H + +#if (ARDUINO >= 100) +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include + +#include "u-blox_config_keys.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Define a digital pin to aid checksum failure capture and analysis +//Leave set to -1 if not needed +const int checksumFailurePin = -1; + +// Global Status Returns +typedef enum +{ + SFE_UBLOX_STATUS_SUCCESS, + SFE_UBLOX_STATUS_FAIL, + SFE_UBLOX_STATUS_CRC_FAIL, + SFE_UBLOX_STATUS_TIMEOUT, + SFE_UBLOX_STATUS_COMMAND_NACK, // Indicates that the command was unrecognised, invalid or that the module is too busy to respond + SFE_UBLOX_STATUS_OUT_OF_RANGE, + SFE_UBLOX_STATUS_INVALID_ARG, + SFE_UBLOX_STATUS_INVALID_OPERATION, + SFE_UBLOX_STATUS_MEM_ERR, + SFE_UBLOX_STATUS_HW_ERR, + SFE_UBLOX_STATUS_DATA_SENT, // This indicates that a 'set' was successful + SFE_UBLOX_STATUS_DATA_RECEIVED, // This indicates that a 'get' (poll) was successful + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, + SFE_UBLOX_STATUS_DATA_OVERWRITTEN // This is an error - the data was valid but has been or _is being_ overwritten by another packet +} sfe_ublox_status_e; + +// ubxPacket validity +typedef enum +{ + SFE_UBLOX_PACKET_VALIDITY_NOT_VALID, + SFE_UBLOX_PACKET_VALIDITY_VALID, + SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, + SFE_UBLOX_PACKET_NOTACKNOWLEDGED // This indicates that we received a NACK +} sfe_ublox_packet_validity_e; + +// Identify which packet buffer is in use: +// packetCfg (or a custom packet), packetAck or packetBuf +typedef enum +{ + SFE_UBLOX_PACKET_PACKETCFG, + SFE_UBLOX_PACKET_PACKETACK, + SFE_UBLOX_PACKET_PACKETBUF +} sfe_ublox_packet_buffer_e; + +//Registers +const uint8_t UBX_SYNCH_1 = 0xB5; +const uint8_t UBX_SYNCH_2 = 0x62; + +//The following are UBX Class IDs. Descriptions taken from ZED-F9P Interface Description Document page 32, NEO-M8P Interface Description page 145 +const uint8_t UBX_CLASS_NAV = 0x01; //Navigation Results Messages: Position, Speed, Time, Acceleration, Heading, DOP, SVs used +const uint8_t UBX_CLASS_RXM = 0x02; //Receiver Manager Messages: Satellite Status, RTC Status +const uint8_t UBX_CLASS_INF = 0x04; //Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice +const uint8_t UBX_CLASS_ACK = 0x05; //Ack/Nak Messages: Acknowledge or Reject messages to UBX-CFG input messages +const uint8_t UBX_CLASS_CFG = 0x06; //Configuration Input Messages: Configure the receiver. +const uint8_t UBX_CLASS_UPD = 0x09; //Firmware Update Messages: Memory/Flash erase/write, Reboot, Flash identification, etc. +const uint8_t UBX_CLASS_MON = 0x0A; //Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status +const uint8_t UBX_CLASS_AID = 0x0B; //(NEO-M8P ONLY!!!) AssistNow Aiding Messages: Ephemeris, Almanac, other A-GPS data input +const uint8_t UBX_CLASS_TIM = 0x0D; //Timing Messages: Time Pulse Output, Time Mark Results +const uint8_t UBX_CLASS_ESF = 0x10; //(NEO-M8P ONLY!!!) External Sensor Fusion Messages: External Sensor Measurements and Status Information +const uint8_t UBX_CLASS_MGA = 0x13; //Multiple GNSS Assistance Messages: Assistance data for various GNSS +const uint8_t UBX_CLASS_LOG = 0x21; //Logging Messages: Log creation, deletion, info and retrieval +const uint8_t UBX_CLASS_SEC = 0x27; //Security Feature Messages +const uint8_t UBX_CLASS_HNR = 0x28; //(NEO-M8P ONLY!!!) High Rate Navigation Results Messages: High rate time, position speed, heading +const uint8_t UBX_CLASS_NMEA = 0xF0; //NMEA Strings: standard NMEA strings + +//The following are used for configuration. Descriptions are from the ZED-F9P Interface Description pg 33-34 and NEO-M9N Interface Description pg 47-48 +const uint8_t UBX_CFG_ANT = 0x13; //Antenna Control Settings. Used to configure the antenna control settings +const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. +const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration +const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum +const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_ESFALG = 0x56; //ESF alignment +const uint8_t UBX_CFG_ESFA = 0x4C; //ESF accelerometer +const uint8_t UBX_CFG_ESFG = 0x4D; //ESF gyro +const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence +const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_HNR = 0x5C; //High Navigation Rate +const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration +const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration +const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration +const uint8_t UBX_CFG_MSG = 0x01; //Poll a message configuration, or Set Message Rate(s), or Set Message Rate +const uint8_t UBX_CFG_NAV5 = 0x24; //Navigation Engine Settings. Used to configure the navigation engine including the dynamic model. +const uint8_t UBX_CFG_NAVX5 = 0x23; //Navigation Engine Expert Settings +const uint8_t UBX_CFG_NMEA = 0x17; //Extended NMEA protocol configuration V1 +const uint8_t UBX_CFG_ODO = 0x1E; //Odometer, Low-speed COG Engine Settings +const uint8_t UBX_CFG_PM2 = 0x3B; //Extended power management configuration +const uint8_t UBX_CFG_PMS = 0x86; //Power mode setup +const uint8_t UBX_CFG_PRT = 0x00; //Used to configure port specifics. Polls the configuration for one I/O Port, or Port configuration for UART ports, or Port configuration for USB port, or Port configuration for SPI port, or Port configuration for DDC port +const uint8_t UBX_CFG_PWR = 0x57; //Put receiver in a defined power state +const uint8_t UBX_CFG_RATE = 0x08; //Navigation/Measurement Rate Settings. Used to set port baud rates. +const uint8_t UBX_CFG_RINV = 0x34; //Contents of Remote Inventory +const uint8_t UBX_CFG_RST = 0x04; //Reset Receiver / Clear Backup Data Structures. Used to reset device. +const uint8_t UBX_CFG_RXM = 0x11; //RXM configuration +const uint8_t UBX_CFG_SBAS = 0x16; //SBAS configuration +const uint8_t UBX_CFG_TMODE3 = 0x71; //Time Mode Settings 3. Used to enable Survey In Mode +const uint8_t UBX_CFG_TP5 = 0x31; //Time Pulse Parameters +const uint8_t UBX_CFG_USB = 0x1B; //USB Configuration +const uint8_t UBX_CFG_VALDEL = 0x8C; //Used for config of higher version u-blox modules (ie protocol v27 and above). Deletes values corresponding to provided keys/ provided keys with a transaction +const uint8_t UBX_CFG_VALGET = 0x8B; //Used for config of higher version u-blox modules (ie protocol v27 and above). Configuration Items +const uint8_t UBX_CFG_VALSET = 0x8A; //Used for config of higher version u-blox modules (ie protocol v27 and above). Sets values corresponding to provided key-value pairs/ provided key-value pairs within a transaction. + +//The following are used to enable NMEA messages. Descriptions come from the NMEA messages overview in the ZED-F9P Interface Description +const uint8_t UBX_NMEA_MSB = 0xF0; //All NMEA enable commands have 0xF0 as MSB +const uint8_t UBX_NMEA_DTM = 0x0A; //GxDTM (datum reference) +const uint8_t UBX_NMEA_GAQ = 0x45; //GxGAQ (poll a standard message (if the current talker ID is GA)) +const uint8_t UBX_NMEA_GBQ = 0x44; //GxGBQ (poll a standard message (if the current Talker ID is GB)) +const uint8_t UBX_NMEA_GBS = 0x09; //GxGBS (GNSS satellite fault detection) +const uint8_t UBX_NMEA_GGA = 0x00; //GxGGA (Global positioning system fix data) +const uint8_t UBX_NMEA_GLL = 0x01; //GxGLL (latitude and long, whith time of position fix and status) +const uint8_t UBX_NMEA_GLQ = 0x43; //GxGLQ (poll a standard message (if the current Talker ID is GL)) +const uint8_t UBX_NMEA_GNQ = 0x42; //GxGNQ (poll a standard message (if the current Talker ID is GN)) +const uint8_t UBX_NMEA_GNS = 0x0D; //GxGNS (GNSS fix data) +const uint8_t UBX_NMEA_GPQ = 0x040; //GxGPQ (poll a standard message (if the current Talker ID is GP)) +const uint8_t UBX_NMEA_GRS = 0x06; //GxGRS (GNSS range residuals) +const uint8_t UBX_NMEA_GSA = 0x02; //GxGSA (GNSS DOP and Active satellites) +const uint8_t UBX_NMEA_GST = 0x07; //GxGST (GNSS Pseudo Range Error Statistics) +const uint8_t UBX_NMEA_GSV = 0x03; //GxGSV (GNSS satellites in view) +const uint8_t UBX_NMEA_RMC = 0x04; //GxRMC (Recommended minimum data) +const uint8_t UBX_NMEA_TXT = 0x41; //GxTXT (text transmission) +const uint8_t UBX_NMEA_VLW = 0x0F; //GxVLW (dual ground/water distance) +const uint8_t UBX_NMEA_VTG = 0x05; //GxVTG (course over ground and Ground speed) +const uint8_t UBX_NMEA_ZDA = 0x08; //GxZDA (Time and Date) + +//The following are used to configure the NMEA protocol main talker ID and GSV talker ID +const uint8_t UBX_NMEA_MAINTALKERID_NOTOVERRIDDEN = 0x00; //main talker ID is system dependent +const uint8_t UBX_NMEA_MAINTALKERID_GP = 0x01; //main talker ID is GPS +const uint8_t UBX_NMEA_MAINTALKERID_GL = 0x02; //main talker ID is GLONASS +const uint8_t UBX_NMEA_MAINTALKERID_GN = 0x03; //main talker ID is combined receiver +const uint8_t UBX_NMEA_MAINTALKERID_GA = 0x04; //main talker ID is Galileo +const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou +const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) +const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID + +//The following are used to configure the HNR message rates +const uint8_t UBX_HNR_ATT = 0x01; //HNR Attitude +const uint8_t UBX_HNR_INS = 0x02; //HNR Vehicle Dynamics +const uint8_t UBX_HNR_PVT = 0x00; //HNR PVT + +//The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class +const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents +const uint8_t UBX_INF_ERROR = 0x00; //ASCII output with error contents +const uint8_t UBX_INF_NOTICE = 0x02; //ASCII output with informational contents +const uint8_t UBX_INF_TEST = 0x03; //ASCII output with test contents +const uint8_t UBX_INF_WARNING = 0x01; //ASCII output with warning contents + +//The following are used to configure LOG UBX messages (loggings messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_LOG_CREATE = 0x07; //Create Log File +const uint8_t UBX_LOG_ERASE = 0x03; //Erase Logged Data +const uint8_t UBX_LOG_FINDTIME = 0x0E; //Find index of a log entry based on a given time, or response to FINDTIME requested +const uint8_t UBX_LOG_INFO = 0x08; //Poll for log information, or Log information +const uint8_t UBX_LOG_RETRIEVEPOSEXTRA = 0x0F; //Odometer log entry +const uint8_t UBX_LOG_RETRIEVEPOS = 0x0B; //Position fix log entry +const uint8_t UBX_LOG_RETRIEVESTRING = 0x0D; //Byte string log entry +const uint8_t UBX_LOG_RETRIEVE = 0x09; //Request log data +const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board flash + +//The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance +const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance +const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance +const uint8_t UBX_MGA_BDS_UTC = 0x03; //BDS UTC Assistance +const uint8_t UBX_MGA_BDS_IONO = 0x03; //BDS Ionospheric Assistance +const uint8_t UBX_MGA_DBD = 0x80; //Either: Poll the Navigation Database, or Navigation Database Dump Entry +const uint8_t UBX_MGA_GAL_EPH = 0x02; //Galileo Ephemeris Assistance +const uint8_t UBX_MGA_GAL_ALM = 0x02; //Galileo Almanac Assitance +const uint8_t UBX_MGA_GAL_TIMOFFSET = 0x02; //Galileo GPS time offset assistance +const uint8_t UBX_MGA_GAL_UTC = 0x02; //Galileo UTC Assistance +const uint8_t UBX_MGA_GLO_EPH = 0x06; //GLONASS Ephemeris Assistance +const uint8_t UBX_MGA_GLO_ALM = 0x06; //GLONASS Almanac Assistance +const uint8_t UBX_MGA_GLO_TIMEOFFSET = 0x06; //GLONASS Auxiliary Time Offset Assistance +const uint8_t UBX_MGA_GPS_EPH = 0x00; //GPS Ephemeris Assistance +const uint8_t UBX_MGA_GPS_ALM = 0x00; //GPS Almanac Assistance +const uint8_t UBX_MGA_GPS_HEALTH = 0x00; //GPS Health Assistance +const uint8_t UBX_MGA_GPS_UTC = 0x00; //GPS UTC Assistance +const uint8_t UBX_MGA_GPS_IONO = 0x00; //GPS Ionosphere Assistance +const uint8_t UBX_MGA_INI_POS_XYZ = 0x40; //Initial Position Assistance +const uint8_t UBX_MGA_INI_POS_LLH = 0x40; //Initial Position Assitance +const uint8_t UBX_MGA_INI_TIME_UTC = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_TIME_GNSS = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_CLKD = 0x40; //Initial Clock Drift Assitance +const uint8_t UBX_MGA_INI_FREQ = 0x40; //Initial Frequency Assistance +const uint8_t UBX_MGA_INI_EOP = 0x40; //Earth Orientation Parameters Assistance +const uint8_t UBX_MGA_QZSS_EPH = 0x05; //QZSS Ephemeris Assistance +const uint8_t UBX_MGA_QZSS_ALM = 0x05; //QZSS Almanac Assistance +const uint8_t UBX_MGA_QZAA_HEALTH = 0x05; //QZSS Health Assistance + +//The following are used to configure the MON UBX messages (monitoring messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35) +const uint8_t UBX_MON_COMMS = 0x36; //Comm port information +const uint8_t UBX_MON_GNSS = 0x28; //Information message major GNSS selection +const uint8_t UBX_MON_HW2 = 0x0B; //Extended Hardware Status +const uint8_t UBX_MON_HW3 = 0x37; //HW I/O pin information +const uint8_t UBX_MON_HW = 0x09; //Hardware Status +const uint8_t UBX_MON_IO = 0x02; //I/O Subsystem Status +const uint8_t UBX_MON_MSGPP = 0x06; //Message Parse and Process Status +const uint8_t UBX_MON_PATCH = 0x27; //Output information about installed patches +const uint8_t UBX_MON_RF = 0x38; //RF information +const uint8_t UBX_MON_RXBUF = 0x07; //Receiver Buffer Status +const uint8_t UBX_MON_RXR = 0x21; //Receiver Status Information +const uint8_t UBX_MON_TXBUF = 0x08; //Transmitter Buffer Status. Used for query tx buffer size/state. +const uint8_t UBX_MON_VER = 0x04; //Receiver/Software Version. Used for obtaining Protocol Version. + +//The following are used to configure the NAV UBX messages (navigation results messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35-36) +const uint8_t UBX_NAV_ATT = 0x05; //Vehicle "Attitude" Solution +const uint8_t UBX_NAV_CLOCK = 0x22; //Clock Solution +const uint8_t UBX_NAV_DOP = 0x04; //Dilution of precision +const uint8_t UBX_NAV_EOE = 0x61; //End of Epoch +const uint8_t UBX_NAV_GEOFENCE = 0x39; //Geofencing status. Used to poll the geofence status +const uint8_t UBX_NAV_HPPOSECEF = 0x13; //High Precision Position Solution in ECEF. Used to find our positional accuracy (high precision). +const uint8_t UBX_NAV_HPPOSLLH = 0x14; //High Precision Geodetic Position Solution. Used for obtaining lat/long/alt in high precision +const uint8_t UBX_NAV_ODO = 0x09; //Odometer Solution +const uint8_t UBX_NAV_ORB = 0x34; //GNSS Orbit Database Info +const uint8_t UBX_NAV_POSECEF = 0x01; //Position Solution in ECEF +const uint8_t UBX_NAV_POSLLH = 0x02; //Geodetic Position Solution +const uint8_t UBX_NAV_PVT = 0x07; //All the things! Position, velocity, time, PDOP, height, h/v accuracies, number of satellites. Navigation Position Velocity Time Solution. +const uint8_t UBX_NAV_RELPOSNED = 0x3C; //Relative Positioning Information in NED frame +const uint8_t UBX_NAV_RESETODO = 0x10; //Reset odometer +const uint8_t UBX_NAV_SAT = 0x35; //Satellite Information +const uint8_t UBX_NAV_SIG = 0x43; //Signal Information +const uint8_t UBX_NAV_STATUS = 0x03; //Receiver Navigation Status +const uint8_t UBX_NAV_SVIN = 0x3B; //Survey-in data. Used for checking Survey In status +const uint8_t UBX_NAV_TIMEBDS = 0x24; //BDS Time Solution +const uint8_t UBX_NAV_TIMEGAL = 0x25; //Galileo Time Solution +const uint8_t UBX_NAV_TIMEGLO = 0x23; //GLO Time Solution +const uint8_t UBX_NAV_TIMEGPS = 0x20; //GPS Time Solution +const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information +const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution +const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF +const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED + +//The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_RXM_MEASX = 0x14; //Satellite Measurements for RRLP +const uint8_t UBX_RXM_PMREQ = 0x41; //Requests a Power Management task (two differenent packet sizes) +const uint8_t UBX_RXM_RAWX = 0x15; //Multi-GNSS Raw Measurement Data +const uint8_t UBX_RXM_RLM = 0x59; //Galileo SAR Short-RLM report (two different packet sizes) +const uint8_t UBX_RXM_RTCM = 0x32; //RTCM input status +const uint8_t UBX_RXM_SFRBX = 0x13; //Boradcast Navigation Data Subframe + +//The following are used to configure the SEC UBX messages (security feature messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_SEC_UNIQID = 0x03; //Unique chip ID + +//The following are used to configure the TIM UBX messages (timing messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_TIM_TM2 = 0x03; //Time mark data +const uint8_t UBX_TIM_TP = 0x01; //Time Pulse Timedata +const uint8_t UBX_TIM_VRFY = 0x06; //Sourced Time Verification + +//The following are used to configure the UPD UBX messages (firmware update messages). Descriptions from UBX messages overview (ZED-F9P Interface Description Document page 36) +const uint8_t UBX_UPD_SOS = 0x14; //Poll Backup Fil Restore Status, Create Backup File in Flash, Clear Backup File in Flash, Backup File Creation Acknowledge, System Restored from Backup + +//The following are used to enable RTCM messages +const uint8_t UBX_RTCM_MSB = 0xF5; //All RTCM enable commands have 0xF5 as MSB +const uint8_t UBX_RTCM_1005 = 0x05; //Stationary RTK reference ARP +const uint8_t UBX_RTCM_1074 = 0x4A; //GPS MSM4 +const uint8_t UBX_RTCM_1077 = 0x4D; //GPS MSM7 +const uint8_t UBX_RTCM_1084 = 0x54; //GLONASS MSM4 +const uint8_t UBX_RTCM_1087 = 0x57; //GLONASS MSM7 +const uint8_t UBX_RTCM_1094 = 0x5E; //Galileo MSM4 +const uint8_t UBX_RTCM_1097 = 0x61; //Galileo MSM7 +const uint8_t UBX_RTCM_1124 = 0x7C; //BeiDou MSM4 +const uint8_t UBX_RTCM_1127 = 0x7F; //BeiDou MSM7 +const uint8_t UBX_RTCM_1230 = 0xE6; //GLONASS code-phase biases, set to once every 10 seconds +const uint8_t UBX_RTCM_4072_0 = 0xFE; //Reference station PVT (ublox proprietary RTCM message) +const uint8_t UBX_RTCM_4072_1 = 0xFD; //Additional reference station information (ublox proprietary RTCM message) + +const uint8_t UBX_ACK_NACK = 0x00; +const uint8_t UBX_ACK_ACK = 0x01; +const uint8_t UBX_ACK_NONE = 0x02; //Not a real value + +// The following constants are used to get External Sensor Measurements and Status +// Information. +const uint8_t UBX_ESF_MEAS = 0x02; +const uint8_t UBX_ESF_RAW = 0x03; +const uint8_t UBX_ESF_STATUS = 0x10; +const uint8_t UBX_ESF_INS = 0x15; //36 bytes + +const uint8_t SVIN_MODE_DISABLE = 0x00; +const uint8_t SVIN_MODE_ENABLE = 0x01; + +//The following consts are used to configure the various ports and streams for those ports. See -CFG-PRT. +const uint8_t COM_PORT_I2C = 0; +const uint8_t COM_PORT_UART1 = 1; +const uint8_t COM_PORT_UART2 = 2; +const uint8_t COM_PORT_USB = 3; +const uint8_t COM_PORT_SPI = 4; + +const uint8_t COM_TYPE_UBX = (1 << 0); +const uint8_t COM_TYPE_NMEA = (1 << 1); +const uint8_t COM_TYPE_RTCM3 = (1 << 5); + +// Configuration Sub-Section mask definitions for saveConfigSelective (UBX-CFG-CFG) +const uint32_t VAL_CFG_SUBSEC_IOPORT = 0x00000001; // ioPort - communications port settings (causes IO system reset!) +const uint32_t VAL_CFG_SUBSEC_MSGCONF = 0x00000002; // msgConf - message configuration +const uint32_t VAL_CFG_SUBSEC_INFMSG = 0x00000004; // infMsg - INF message configuration +const uint32_t VAL_CFG_SUBSEC_NAVCONF = 0x00000008; // navConf - navigation configuration +const uint32_t VAL_CFG_SUBSEC_RXMCONF = 0x00000010; // rxmConf - receiver manager configuration +const uint32_t VAL_CFG_SUBSEC_SENCONF = 0x00000100; // senConf - sensor interface configuration (requires protocol 19+) +const uint32_t VAL_CFG_SUBSEC_RINVCONF = 0x00000200; // rinvConf - remove inventory configuration +const uint32_t VAL_CFG_SUBSEC_ANTCONF = 0x00000400; // antConf - antenna configuration +const uint32_t VAL_CFG_SUBSEC_LOGCONF = 0x00000800; // logConf - logging configuration +const uint32_t VAL_CFG_SUBSEC_FTSCONF = 0x00001000; // ftsConf - FTS configuration (FTS products only) + +// Bitfield wakeupSources for UBX_RXM_PMREQ +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX = 0x00000008; // uartrx +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = 0x00000020; // extint0 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = 0x00000040; // extint1 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS = 0x00000080; // spics + +enum dynModel // Possible values for the dynamic platform model, which provide more accuract position output for the situation. Description extracted from ZED-F9P Integration Manual +{ + DYN_MODEL_PORTABLE = 0, //Applications with low acceleration, e.g. portable devices. Suitable for most situations. + // 1 is not defined + DYN_MODEL_STATIONARY = 2, //Used in timing applications (antenna must be stationary) or other stationary applications. Velocity restricted to 0 m/s. Zero dynamics assumed. + DYN_MODEL_PEDESTRIAN, //Applications with low acceleration and speed, e.g. how a pedestrian would move. Low acceleration assumed. + DYN_MODEL_AUTOMOTIVE, //Used for applications with equivalent dynamics to those of a passenger car. Low vertical acceleration assumed + DYN_MODEL_SEA, //Recommended for applications at sea, with zero vertical velocity. Zero vertical velocity assumed. Sea level assumed. + DYN_MODEL_AIRBORNE1g, //Airborne <1g acceleration. Used for applications with a higher dynamic range and greater vertical acceleration than a passenger car. No 2D position fixes supported. + DYN_MODEL_AIRBORNE2g, //Airborne <2g acceleration. Recommended for typical airborne environments. No 2D position fixes supported. + DYN_MODEL_AIRBORNE4g, //Airborne <4g acceleration. Only recommended for extremely dynamic environments. No 2D position fixes supported. + DYN_MODEL_WRIST, // Not supported in protocol versions less than 18. Only recommended for wrist worn applications. Receiver will filter out arm motion. + DYN_MODEL_BIKE, // Supported in protocol versions 19.2 +}; + +#ifndef MAX_PAYLOAD_SIZE + +#define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules +//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values + +#endif + +//-=-=-=-=- UBX binary specific variables +typedef struct +{ + uint8_t cls; + uint8_t id; + uint16_t len; //Length of the payload. Does not include cls, id, or checksum bytes + uint16_t counter; //Keeps track of number of overall bytes received. Some responses are larger than 255 bytes. + uint16_t startingSpot; //The counter value needed to go past before we begin recording into payload array + uint8_t *payload; + uint8_t checksumA; //Given to us from module. Checked against the rolling calculated A/B checksums. + uint8_t checksumB; + sfe_ublox_packet_validity_e valid; //Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked + sfe_ublox_packet_validity_e classAndIDmatch; // Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID +} ubxPacket; + +// Struct to hold the results returned by getGeofenceState (returned by UBX-NAV-GEOFENCE) +typedef struct +{ + uint8_t status; // Geofencing status: 0 - Geofencing not available or not reliable; 1 - Geofencing active + uint8_t numFences; // Number of geofences + uint8_t combState; // Combined (logical OR) state of all geofences: 0 - Unknown; 1 - Inside; 2 - Outside + uint8_t states[4]; // Geofence states: 0 - Unknown; 1 - Inside; 2 - Outside +} geofenceState; + +// Struct to hold the current geofence parameters +typedef struct +{ + uint8_t numFences; // Number of active geofences + int32_t lats[4]; // Latitudes of geofences (in degrees * 10^-7) + int32_t longs[4]; // Longitudes of geofences (in degrees * 10^-7) + uint32_t rads[4]; // Radii of geofences (in m * 10^-2) +} geofenceParams; + +class SFE_UBLOX_GPS +{ +public: + SFE_UBLOX_GPS(void); + +// A default of 250ms for maxWait seems fine for I2C but is not enough for SerialUSB. +// If you know you are only going to be using I2C / Qwiic communication, you can +// safely reduce defaultMaxWait to 250. +#ifndef defaultMaxWait // Let's allow the user to define their own value if they want to +#define defaultMaxWait 1100 +#endif + + //By default use the default I2C address, and use Wire port + boolean begin(TwoWire &wirePort = Wire, uint8_t deviceAddress = 0x42); //Returns true if module is detected + //serialPort needs to be perviously initialized to correct baud rate + boolean begin(Stream &serialPort); //Returns true if module is detected + + //Control the size of the internal I2C transaction amount + void setI2CTransactionSize(uint8_t bufferSize); + uint8_t getI2CTransactionSize(void); + + //Set the max number of bytes set in a given I2C transaction + uint8_t i2cTransactionSize = 32; //Default to ATmega328 limit + + //Returns true if device answers on _gpsI2Caddress address or via Serial + //maxWait is only used for Serial + boolean isConnected(uint16_t maxWait = 1100); + + //Changed in V1.8.1: provides backward compatibility for the examples that call checkUblox directly + //Will default to using packetCfg to look for explicit autoPVT packets so they get processed correctly by processUBX + boolean checkUblox(uint8_t requestedClass = UBX_CLASS_NAV, uint8_t requestedID = UBX_NAV_PVT); //Checks module with user selected commType + + boolean checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process() + boolean checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process() + + void process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Processes NMEA and UBX binary sentences one byte at a time + void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure + void processRTCMframe(uint8_t incoming); //Monitor the incoming bytes for start and length bytes + void processRTCM(uint8_t incoming) __attribute__((weak)); //Given rtcm byte, do something with it. User can overwrite if desired to pipe bytes to radio, internet, etc. + + void processUBXpacket(ubxPacket *msg); //Once a packet has been received and validated, identify this packet's class/id and update internal flags + void processNMEA(char incoming) __attribute__((weak)); //Given a NMEA character, do something with it. User can overwrite if desired to use something like tinyGPS or MicroNMEA libraries + + void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages + sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait); //Given a packet and payload, send everything including CRC bytes, return true if we got a response + sfe_ublox_status_e sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait = 250); + void sendSerialCommand(ubxPacket *outgoingUBX); + + void printPacket(ubxPacket *packet); //Useful for debugging + + void factoryReset(); //Send factory reset sequence (i.e. load "default" configuration and perform hardReset) + void hardReset(); //Perform a reset leading to a cold start (zero info start-up) + + boolean setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = 250); //Changes the I2C address of the u-blox module + void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the u-blox module, uartPort should be COM_PORT_UART1/2 + void setNMEAOutputPort(Stream &nmeaOutputPort); //Sets the internal variable for the port to direct NMEA characters to + + boolean setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second + uint8_t getNavigationFrequency(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + boolean saveConfiguration(uint16_t maxWait = defaultMaxWait); //Save current configuration to flash and BBR (battery backed RAM) + boolean factoryDefault(uint16_t maxWait = defaultMaxWait); //Reset module to factory defaults + boolean saveConfigSelective(uint32_t configMask, uint16_t maxWait = defaultMaxWait); //Save the selected configuration sub-sections to flash and BBR (battery backed RAM) + + sfe_ublox_status_e waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet and an ACK is received + sfe_ublox_status_e waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet is received + +// getPVT will only return data once in each navigation cycle. By default, that is once per second. +// Therefore we should set getPVTmaxWait to slightly longer than that. +// If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) +// then you should use a shorter maxWait for getPVT. 300msec would be about right: getPVT(300) +// The same is true for getHPPOSLLH. +#define getPVTmaxWait 1100 // Default maxWait for getPVT and all functions which call it +#define getHPPOSLLHmaxWait 1100 // Default maxWait for getHPPOSLLH and all functions which call it +#define getDOPmaxWait 1100 // Default maxWait for getDOP and all functions which all it + + boolean assumeAutoPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already + boolean setAutoPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency + boolean setAutoPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getPVT(uint16_t maxWait = getPVTmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. + boolean assumeAutoHPPOSLLH(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HPPOSLLH is send cyclically already + boolean setAutoHPPOSLLH(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSLLH reports at the navigation frequency + boolean setAutoHPPOSLLH(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSLLH reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHPPOSLLH(uint16_t maxWait = getHPPOSLLHmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new HPPOSLLH is available. + boolean assumeAutoDOP(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and DOP is send cyclically already + boolean setAutoDOP(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic DOP reports at the navigation frequency + boolean setAutoDOP(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic DOP reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getDOP(uint16_t maxWait = getDOPmaxWait); //Query module for latest dilution of precision values and load global vars:. If autoDOP is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new DOP is available. + void flushPVT(); //Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure + void flushHPPOSLLH(); //Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure + void flushDOP(); //Mark all the DOP data as read/stale. This is handy to get data alignment after CRC failure + + bool getGnssFixOk(uint16_t maxWait = getPVTmaxWait); //Get whether we have a valid fix (i.e within DOP & accuracy masks) + bool getDiffSoln(uint16_t maxWait = getPVTmaxWait); //Get whether differential corrections were applied + bool getHeadVehValid(uint16_t maxWait = getPVTmaxWait); + int32_t getLatitude(uint16_t maxWait = getPVTmaxWait); //Returns the current latitude in degrees * 10^-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getLongitude(uint16_t maxWait = getPVTmaxWait); //Returns the current longitude in degrees * 10-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getAltitude(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above ellipsoid + int32_t getAltitudeMSL(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above mean sea level + int32_t getHorizontalAccEst(uint16_t maxWait = getPVTmaxWait); + int32_t getVerticalAccEst(uint16_t maxWait = getPVTmaxWait); + int32_t getNedNorthVel(uint16_t maxWait = getPVTmaxWait); + int32_t getNedEastVel(uint16_t maxWait = getPVTmaxWait); + int32_t getNedDownVel(uint16_t maxWait = getPVTmaxWait); + uint8_t getSIV(uint16_t maxWait = getPVTmaxWait); //Returns number of sats used in fix + uint8_t getFixType(uint16_t maxWait = getPVTmaxWait); //Returns the type of fix: 0=no, 3=3D, 4=GNSS+Deadreckoning + uint8_t getCarrierSolutionType(uint16_t maxWait = getPVTmaxWait); //Returns RTK solution: 0=no, 1=float solution, 2=fixed solution + int32_t getGroundSpeed(uint16_t maxWait = getPVTmaxWait); //Returns speed in mm/s + int32_t getHeading(uint16_t maxWait = getPVTmaxWait); //Returns heading in degrees * 10^-5 + uint16_t getPDOP(uint16_t maxWait = getPVTmaxWait); //Returns positional dillution of precision * 10^-2 (dimensionless) + uint16_t getYear(uint16_t maxWait = getPVTmaxWait); + uint8_t getMonth(uint16_t maxWait = getPVTmaxWait); + uint8_t getDay(uint16_t maxWait = getPVTmaxWait); + uint8_t getHour(uint16_t maxWait = getPVTmaxWait); + uint8_t getMinute(uint16_t maxWait = getPVTmaxWait); + uint8_t getSecond(uint16_t maxWait = getPVTmaxWait); + uint16_t getMillisecond(uint16_t maxWait = getPVTmaxWait); + int32_t getNanosecond(uint16_t maxWait = getPVTmaxWait); + uint32_t getTimeOfWeek(uint16_t maxWait = getPVTmaxWait); + bool getDateValid(uint16_t maxWait = getPVTmaxWait); + bool getTimeValid(uint16_t maxWait = getPVTmaxWait); + uint32_t getSpeedAccEst(uint16_t maxWait = getPVTmaxWait); + uint32_t getHeadingAccEst(uint16_t maxWait = getPVTmaxWait); + bool getInvalidLlh(uint16_t maxWait = getPVTmaxWait); + int32_t getHeadVeh(uint16_t maxWait = getPVTmaxWait); + int16_t getMagDec(uint16_t maxWait = getPVTmaxWait); + uint16_t getMagAcc(uint16_t maxWait = getPVTmaxWait); + + int32_t getHighResLatitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLatitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getHighResLongitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLongitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getElipsoid(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getElipsoidHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getMeanSeaLevel(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getMeanSeaLevelHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getGeoidSeparation(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getHorizontalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getVerticalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + + uint16_t getGeometricDOP(uint16_t maxWait = getDOPmaxWait); + uint16_t getPositionDOP(uint16_t maxWait = getDOPmaxWait); + uint16_t getTimeDOP(uint16_t maxWait = getDOPmaxWait); + uint16_t getVerticalDOP(uint16_t maxWait = getDOPmaxWait); + uint16_t getHorizontalDOP(uint16_t maxWait = getDOPmaxWait); + uint16_t getNorthingDOP(uint16_t maxWait = getDOPmaxWait); + uint16_t getEastingDOP(uint16_t maxWait = getDOPmaxWait); + + //Port configurations + boolean setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof + boolean getPortSettings(uint8_t portID, uint16_t maxWait = defaultMaxWait); //Returns the current protocol bits in the UBX-CFG-PRT command for a given port + + boolean setI2COutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUSBOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setSPIOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof + + //Functions to turn on/off message types for a given port ID (see COM_PORT_I2C, etc above) + boolean configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); + boolean enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); //Given a message number turns on a message ID for output over given PortID + boolean disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); //Turn off given RTCM message from a given port + + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) + //It is probably safe to assume that users of the ZED-F9P will be using I2C / Qwiic. + //If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. + uint32_t createKey(uint16_t group, uint16_t id, uint8_t size); //Form 32-bit key from group/id/size + + sfe_ublox_status_e getVal(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Load payload with response + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given key location + uint16_t getVal16(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given key location + uint32_t getVal32(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given key location + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint16_t getVal16(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint32_t getVal32(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 32-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL); //Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL); //Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL); //Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); //Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); //Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); //Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); //Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + + //Functions used for RTK and base station setup + //It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. + boolean getSurveyMode(uint16_t maxWait = 250); //Get the current TimeMode3 settings + boolean setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Control survey in mode + boolean enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Begin Survey-In for NEO-M8P + boolean disableSurveyMode(uint16_t maxWait = 250); //Stop Survey-In mode + + boolean getSurveyStatus(uint16_t maxWait); //Reads survey in status and sets the global variables + + uint32_t getPositionAccuracy(uint16_t maxWait = 1100); //Returns the 3D accuracy of the current high-precision fix, in mm. Supported on NEO-M8P, ZED-F9P, + + uint8_t getProtocolVersionHigh(uint16_t maxWait = 500); //Returns the PROTVER XX.00 from UBX-MON-VER register + uint8_t getProtocolVersionLow(uint16_t maxWait = 500); //Returns the PROTVER 00.XX from UBX-MON-VER register + boolean getProtocolVersion(uint16_t maxWait = 500); //Queries module, loads low/high bytes + + boolean getRELPOSNED(uint16_t maxWait = 1100); //Get Relative Positioning Information of the NED frame + + // Enable debug messages using the chosen Serial port (Stream) + // Boards like the RedBoard Turbo use SerialUSB (not Serial). + // But other boards like the SAMD51 Thing Plus use Serial (not SerialUSB). + // These lines let the code compile cleanly on as many SAMD boards as possible. + #if defined(ARDUINO_ARCH_SAMD) // Is this a SAMD board? + #if defined(USB_VID) // Is the USB Vendor ID defined? + #if (USB_VID == 0x1B4F) // Is this a SparkFun board? + #if !defined(ARDUINO_SAMD51_THING_PLUS) & !defined(ARDUINO_SAMD51_MICROMOD) // If it is not a SAMD51 Thing Plus or SAMD51 MicroMod + void enableDebugging(Stream &debugPort = SerialUSB, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + + void disableDebugging(void); //Turn off debug statements + void debugPrint(char *message); //Safely print debug statements + void debugPrintln(char *message); //Safely print debug statements + const char *statusString(sfe_ublox_status_e stat); //Pretty print the return value + + //Support for geofences + boolean addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence = 0, byte pinPolarity = 0, byte pin = 0, uint16_t maxWait = 1100); // Add a new geofence + boolean clearGeofences(uint16_t maxWait = 1100); //Clears all geofences + boolean getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = 1100); //Returns the combined geofence state + boolean clearAntPIO(uint16_t maxWait = 1100); //Clears the antenna control pin settings to release the PIOs + geofenceParams currentGeofenceParams; // Global to store the geofence parameters + + boolean powerSaveMode(bool power_save = true, uint16_t maxWait = 1100); + uint8_t getPowerSaveMode(uint16_t maxWait = 1100); // Returns 255 if the sendCommand fails + boolean powerOff(uint32_t durationInMs, uint16_t maxWait = 1100); + boolean powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources = VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0, boolean forceWhileUsb = true, uint16_t maxWait = 1100); + + //Change the dynamic platform model using UBX-CFG-NAV5 + boolean setDynamicModel(dynModel newDynamicModel = DYN_MODEL_PORTABLE, uint16_t maxWait = 1100); + uint8_t getDynamicModel(uint16_t maxWait = 1100); // Get the dynamic model - returns 255 if the sendCommand fails + + boolean getEsfInfo(uint16_t maxWait = 1100); + boolean getEsfIns(uint16_t maxWait = 1100); + boolean getEsfDataInfo(uint16_t maxWait = 1100); + boolean getEsfRawDataInfo(uint16_t maxWait = 1100); + sfe_ublox_status_e getSensState(uint8_t sensor, uint16_t maxWait = 1100); + boolean getVehAtt(uint16_t maxWait = 1100); + + // Given coordinates, put receiver into static position. Set latlong to true to pass in lat/long values instead of ecef. + // For ECEF the units are: cm, 0.1mm, cm, 0.1mm, cm, 0.1mm + // For Lat/Lon/Alt the units are: degrees^-7, degrees^-9, degrees^-7, degrees^-9, cm, 0.1mm + bool setStaticPosition(int32_t ecefXOrLat, int8_t ecefXOrLatHP, int32_t ecefYOrLon, int8_t ecefYOrLonHP, int32_t ecefZOrAlt, int8_t ecefZOrAltHP, bool latLong = false, uint16_t maxWait = 250); + bool setStaticPosition(int32_t ecefXOrLat, int32_t ecefYOrLon, int32_t ecefZOrAlt, bool latLong = false, uint16_t maxWait = 250); + + // Push (e.g.) RTCM data directly to the module + // Warning: this function does not check that the data is valid. It is the user's responsibility to ensure the data is valid before pushing. + boolean pushRawData(uint8_t *dataBytes, size_t numDataBytes); + + //Survey-in specific controls + struct svinStructure + { + boolean active; + boolean valid; + uint16_t observationTime; + float meanAccuracy; + } svin; + + //Relative Positioning Info in NED frame specific controls + struct frelPosInfoStructure + { + uint16_t refStationID; + + float relPosN; + float relPosE; + float relPosD; + + long relPosLength; + long relPosHeading; + + int8_t relPosHPN; + int8_t relPosHPE; + int8_t relPosHPD; + int8_t relPosHPLength; + + float accN; + float accE; + float accD; + + bool gnssFixOk; + bool diffSoln; + bool relPosValid; + uint8_t carrSoln; + bool isMoving; + bool refPosMiss; + bool refObsMiss; + } relPosInfo; + + //The major datums we want to globally store + uint16_t gpsYear; + uint8_t gpsMonth; + uint8_t gpsDay; + uint8_t gpsHour; + uint8_t gpsMinute; + uint8_t gpsSecond; + uint16_t gpsMillisecond; + int32_t gpsNanosecond; + bool gpsDateValid; + bool gpsTimeValid; + + bool gnssFixOk; //valid fix (i.e within DOP & accuracy masks) + bool diffSoln; //Differential corrections were applied + bool headVehValid; + int32_t latitude; //Degrees * 10^-7 (more accurate than floats) + int32_t longitude; //Degrees * 10^-7 (more accurate than floats) + int32_t altitude; //Number of mm above ellipsoid + int32_t altitudeMSL; //Number of mm above Mean Sea Level + uint32_t horizontalAccEst; + uint32_t verticalAccEst; + int32_t nedNorthVel; + int32_t nedEastVel; + int32_t nedDownVel; + uint8_t SIV; //Number of satellites used in position solution + uint8_t fixType; //Tells us when we have a solution aka lock + uint8_t carrierSolution; //Tells us when we have an RTK float/fixed solution + int32_t groundSpeed; //mm/s + int32_t headingOfMotion; //degrees * 10^-5 + uint32_t speedAccEst; + uint32_t headingAccEst; + uint16_t pDOP; //Positional dilution of precision * 10^-2 (dimensionless) + bool invalidLlh; + int32_t headVeh; + int16_t magDec; + uint16_t magAcc; + uint8_t versionLow; //Loaded from getProtocolVersion(). + uint8_t versionHigh; + + uint32_t timeOfWeek; // ms + int32_t highResLatitude; // Degrees * 10^-7 + int32_t highResLongitude; // Degrees * 10^-7 + int32_t elipsoid; // Height above ellipsoid in mm (Typo! Should be eLLipsoid! **Uncorrected for backward-compatibility.**) + int32_t meanSeaLevel; // Height above mean sea level in mm + int32_t geoidSeparation; // This seems to only be provided in NMEA GGA and GNS messages + uint32_t horizontalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + uint32_t verticalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + int8_t elipsoidHp; // High precision component of the height above ellipsoid in mm * 10^-1 (Deliberate typo! Should be eLLipsoidHp!) + int8_t meanSeaLevelHp; // High precision component of Height above mean sea level in mm * 10^-1 + int8_t highResLatitudeHp; // High precision component of latitude: Degrees * 10^-9 + int8_t highResLongitudeHp; // High precision component of longitude: Degrees * 10^-9 + + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame + + uint16_t geometricDOP; // Geometric dilution of precision * 10^-2 + uint16_t positionDOP; // Posoition dilution of precision * 10^-2 + uint16_t timeDOP; // Time dilution of precision * 10^-2 + uint16_t verticalDOP; // Vertical dilution of precision * 10^-2 + uint16_t horizontalDOP; // Horizontal dilution of precision * 10^-2 + uint16_t northingDOP; // Northing dilution of precision * 10^-2 + uint16_t eastingDOP; // Easting dilution of precision * 10^-2 + +#define DEF_NUM_SENS 7 + struct deadReckData + { + uint8_t version; + uint8_t fusionMode; + + uint8_t xAngRateVald; + uint8_t yAngRateVald; + uint8_t zAngRateVald; + uint8_t xAccelVald; + uint8_t yAccelVald; + uint8_t zAccelVald; + + int32_t xAngRate; + int32_t yAngRate; + int32_t zAngRate; + + int32_t xAccel; + int32_t yAccel; + int32_t zAccel; + + // The array size is based on testing directly on M8U and F9R + uint32_t rawData; + uint32_t rawDataType; + uint32_t rawTStamp; + + uint32_t data[DEF_NUM_SENS]; + uint32_t dataType[DEF_NUM_SENS]; + uint32_t dataTStamp[DEF_NUM_SENS]; + } imuMeas; + + struct indivImuData + { + + uint8_t numSens; + + uint8_t senType; + boolean isUsed; + boolean isReady; + uint8_t calibStatus; + uint8_t timeStatus; + + uint8_t freq; // Hz + + boolean badMeas; + boolean badTag; + boolean missMeas; + boolean noisyMeas; + } ubloxSen; + + struct vehicleAttitude + { + // All values in degrees + int32_t roll; + int32_t pitch; + int32_t heading; + uint32_t accRoll; + uint32_t accPitch; + uint32_t accHeading; + } vehAtt; + + //HNR-specific structs + struct hnrAttitudeSolution + { + uint32_t iTOW; + int32_t roll; // Degrees * 1e-5 + int32_t pitch; // Degrees * 1e-5 + int32_t heading; // Degrees * 1e-5 + uint32_t accRoll; // Degrees * 1e-5 + uint32_t accPitch; // Degrees * 1e-5 + uint32_t accHeading; // Degrees * 1e-5 + } hnrAtt; + + struct hnrVehicleDynamics + { + boolean xAngRateValid; + boolean yAngRateValid; + boolean zAngRateValid; + boolean xAccelValid; + boolean yAccelValid; + boolean zAccelValid; + uint32_t iTOW; + int32_t xAngRate; // Degrees/s * 1e-3 + int32_t yAngRate; // Degrees/s * 1e-3 + int32_t zAngRate; // Degrees/s * 1e-3 + int32_t xAccel; // m/s^2 * 1e-2 + int32_t yAccel; // m/s^2 * 1e-2 + int32_t zAccel; // m/s^2 * 1e-2 + } hnrVehDyn; + + struct hnrPosVelTime + { + uint32_t iTOW; + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + boolean validDate; + boolean validTime; + boolean fullyResolved; + int32_t nano; + uint8_t gpsFix; + boolean gpsFixOK; + boolean diffSoln; + boolean WKNSET; + boolean TOWSET; + boolean headVehValid; + int32_t lon; // Degrees * 1e-7 + int32_t lat; // Degrees * 1e-7 + int32_t height; // mm above ellipsoid + int32_t hMSL; // mm above MSL + int32_t gSpeed; // mm/s 2D + int32_t speed; // mm/s 3D + int32_t headMot; // Degrees * 1e-5 + int32_t headVeh; // Degrees * 1e-5 + uint32_t hAcc; // mm + uint32_t vAcc; // mm + uint32_t sAcc; // mm + uint32_t headAcc; // Degrees * 1e-5 + } hnrPVT; + + //HNR functions + boolean setHNRNavigationRate(uint8_t rate, uint16_t maxWait = 1100); // Returns true if the setHNRNavigationRate is successful + uint8_t getHNRNavigationRate(uint16_t maxWait = 1100); // Returns 0 if the getHNRNavigationRate fails + boolean assumeAutoHNRAtt(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR Attitude is send cyclically already + boolean setAutoHNRAtt(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR Attitude reports at the HNR rate + boolean setAutoHNRAtt(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR Attitude reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHNRAtt(uint16_t maxWait = 1100); // Returns true if the get HNR attitude is successful. Data is returned in hnrAtt + boolean assumeAutoHNRDyn(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR dynamics is send cyclically already + boolean setAutoHNRDyn(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR dynamics reports at the HNR rate + boolean setAutoHNRDyn(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR dynamics reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHNRDyn(uint16_t maxWait = 1100); // Returns true if the get HNR dynamics is successful. Data is returned in hnrVehDyn + boolean assumeAutoHNRPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR PVT is send cyclically already + boolean setAutoHNRPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR PVT reports at the HNR rate + boolean setAutoHNRPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR PVT reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHNRPVT(uint16_t maxWait = 1100); // Returns true if the get HNR PVT is successful. Data is returned in hnrPVT + +private: + //Depending on the sentence type the processor will load characters into different arrays + enum SentenceTypes + { + NONE = 0, + NMEA, + UBX, + RTCM + } currentSentence = NONE; + + //Depending on the ubx binary response class, store binary responses into different places + enum classTypes + { + CLASS_NONE = 0, + CLASS_ACK, + CLASS_NOT_AN_ACK + } ubxFrameClass = CLASS_NONE; + + enum commTypes + { + COMM_TYPE_I2C = 0, + COMM_TYPE_SERIAL, + COMM_TYPE_SPI + } commType = COMM_TYPE_I2C; //Controls which port we look to for incoming bytes + + //Functions + boolean checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType + uint32_t extractLong(uint8_t spotToStart); //Combine four bytes from payload into long + int32_t extractSignedLong(uint8_t spotToStart); //Combine four bytes from payload into signed long (avoiding any ambiguity caused by casting) + uint16_t extractInt(uint8_t spotToStart); //Combine two bytes from payload into int + int16_t extractSignedInt(int8_t spotToStart); + uint8_t extractByte(uint8_t spotToStart); //Get byte from payload + int8_t extractSignedChar(uint8_t spotToStart); //Get signed 8-bit value from payload + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + + //Variables + TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + Stream *_serialPort; //The generic connection to user's chosen Serial hardware + Stream *_nmeaOutputPort = NULL; //The user can assign an output port to print NMEA sentences if they wish + Stream *_debugSerial; //The stream to send debug messages to if enabled + + uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series + //This can be changed using the ublox configuration software + + boolean _printDebug = false; //Flag to print the serial commands we are sending to the Serial port for debug + boolean _printLimitedDebug = false; //Flag to print limited debug messages. Useful for I2C debugging or high navigation rates + + //The packet buffers + //These are pointed at from within the ubxPacket + uint8_t payloadAck[2]; // Holds the requested ACK/NACK + uint8_t payloadCfg[MAX_PAYLOAD_SIZE]; // Holds the requested data packet + uint8_t payloadBuf[2]; // Temporary buffer used to screen incoming packets or dump unrequested packets + + //Init the packet structures and init them with pointers to the payloadAck, payloadCfg and payloadBuf arrays + ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetCfg = {0, 0, 0, 0, 0, payloadCfg, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + //Flag if this packet is unrequested (and so should be ignored and not copied into packetCfg or packetAck) + boolean ignoreThisPayload = false; + + //Identify which buffer is in use + //Data is stored in packetBuf until the requested class and ID can be validated + //If a match is seen, data is diverted into packetAck or packetCfg + sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + + //Limit checking of new data to every X ms + //If we are expecting an update every X Hz then we should check every half that amount of time + //Otherwise we may block ourselves from seeing new data + uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() + + unsigned long lastCheck = 0; + boolean autoPVT = false; //Whether autoPVT is enabled or not + boolean autoPVTImplicitUpdate = true; // Whether autoPVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHPPOSLLH = false; //Whether autoHPPOSLLH is enabled or not + boolean autoHPPOSLLHImplicitUpdate = true; // Whether autoHPPOSLLH is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoDOP = false; //Whether autoDOP is enabled or not + boolean autoDOPImplicitUpdate = true; // Whether autoDOP is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHNRAtt = false; //Whether auto HNR attitude is enabled or not + boolean autoHNRAttImplicitUpdate = true; // Whether auto HNR attitude is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHNRDyn = false; //Whether auto HNR dynamics is enabled or not + boolean autoHNRDynImplicitUpdate = true; // Whether auto HNR dynamics is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHNRPVT = false; //Whether auto HNR PVT is enabled or not + boolean autoHNRPVTImplicitUpdate = true; // Whether auto HNR PVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + + uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] + + uint8_t rollingChecksumA; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + + //Create bit field for staleness of each datum in PVT we want to monitor + //moduleQueried.latitude goes true each time we call getPVT() + //This reduces the number of times we have to call getPVT as this can take up to ~1s per read + //depending on update rate + struct + { + uint32_t gpsiTOW : 1; + uint32_t gpsYear : 1; + uint32_t gpsMonth : 1; + uint32_t gpsDay : 1; + uint32_t gpsHour : 1; + uint32_t gpsMinute : 1; + uint32_t gpsSecond : 1; + uint32_t gpsDateValid : 1; + uint32_t gpsTimeValid : 1; + uint32_t gpsNanosecond : 1; + + uint32_t all : 1; + uint32_t gnssFixOk : 1; + uint32_t diffSoln : 1; + uint32_t headVehValid : 1; + uint32_t longitude : 1; + uint32_t latitude : 1; + uint32_t altitude : 1; + uint32_t altitudeMSL : 1; + uint32_t horizontalAccEst : 1; + uint32_t verticalAccEst : 1; + uint32_t nedNorthVel : 1; + uint32_t nedEastVel : 1; + uint32_t nedDownVel : 1; + uint32_t SIV : 1; + uint32_t fixType : 1; + uint32_t carrierSolution : 1; + uint32_t groundSpeed : 1; + uint32_t headingOfMotion : 1; + uint32_t speedAccEst : 1; + uint32_t headingAccEst : 1; + uint32_t pDOP : 1; + uint32_t invalidLlh : 1; + uint32_t headVeh : 1; + uint32_t magDec : 1; + uint32_t magAcc : 1; + uint32_t versionNumber : 1; + } moduleQueried; + + struct + { + uint16_t all : 1; + uint16_t timeOfWeek : 1; + uint16_t highResLatitude : 1; + uint16_t highResLongitude : 1; + uint16_t elipsoid : 1; + uint16_t meanSeaLevel : 1; + uint16_t geoidSeparation : 1; // Redundant but kept for backward-compatibility + uint16_t horizontalAccuracy : 1; + uint16_t verticalAccuracy : 1; + uint16_t elipsoidHp : 1; + uint16_t meanSeaLevelHp : 1; + uint16_t highResLatitudeHp : 1; + uint16_t highResLongitudeHp : 1; + } highResModuleQueried; + + struct + { + uint16_t all : 1; + uint16_t geometricDOP : 1; + uint16_t positionDOP : 1; + uint16_t timeDOP : 1; + uint16_t verticalDOP : 1; + uint16_t horizontalDOP : 1; + uint16_t northingDOP : 1; + uint16_t eastingDOP : 1; + } dopModuleQueried; + + boolean hnrAttQueried; + boolean hnrDynQueried; + boolean hnrPVTQueried; + + uint16_t rtcmLen = 0; +}; + +#endif diff --git a/lib/SparkFun u-blox Arduino Library/src/u-blox_config_keys.h b/lib/SparkFun u-blox Arduino Library/src/u-blox_config_keys.h new file mode 100644 index 0000000..9e5b463 --- /dev/null +++ b/lib/SparkFun u-blox Arduino Library/src/u-blox_config_keys.h @@ -0,0 +1,838 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __u_blox_config_keys_h__ +#define __u_blox_config_keys_h__ + +//The following consts are used to generate KEY values for the advanced protocol functions of VELGET/SET/DEL +const uint8_t VAL_SIZE_1 = 0x01; //One bit +const uint8_t VAL_SIZE_8 = 0x02; //One byte +const uint8_t VAL_SIZE_16 = 0x03; //Two bytes +const uint8_t VAL_SIZE_32 = 0x04; //Four bytes +const uint8_t VAL_SIZE_64 = 0x05; //Eight bytes + +//These are the Bitfield layers definitions for the UBX-CFG-VALSET message (not to be confused with Bitfield deviceMask in UBX-CFG-CFG) +const uint8_t VAL_LAYER_RAM = (1 << 0); +const uint8_t VAL_LAYER_BBR = (1 << 1); +const uint8_t VAL_LAYER_FLASH = (1 << 2); +const uint8_t VAL_LAYER_ALL = VAL_LAYER_RAM | VAL_LAYER_BBR | VAL_LAYER_FLASH; //Not valid with getVal() + +//Below are various Groups, IDs, and sizes for various settings +//These can be used to call getVal/setVal/delVal +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint8_t VAL_ID_PROT_UBX = 0x01; +const uint8_t VAL_ID_PROT_NMEA = 0x02; +const uint8_t VAL_ID_PROT_RTCM3 = 0x04; + +const uint8_t VAL_GROUP_I2C = 0x51; +const uint8_t VAL_GROUP_I2COUTPROT = 0x72; +const uint8_t VAL_GROUP_UART1INPROT = 0x73; +const uint8_t VAL_GROUP_UART1OUTPROT = 0x74; +const uint8_t VAL_GROUP_UART2INPROT = 0x75; +const uint8_t VAL_GROUP_UART2OUTPROT = 0x76; +const uint8_t VAL_GROUP_USBINPROT = 0x77; +const uint8_t VAL_GROUP_USBOUTPROT = 0x78; + +const uint8_t VAL_GROUP_UART_SIZE = VAL_SIZE_1; //All fields in UART group are currently 1 bit +const uint8_t VAL_GROUP_I2C_SIZE = VAL_SIZE_8; //All fields in I2C group are currently 1 byte + +const uint8_t VAL_ID_I2C_ADDRESS = 0x01; + +//Below are the key values for a given configuration setting + +//CFG-BDS: BeiDou system configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_BDS_USE_PRN_1_TO_5 = 0x10340014; // Use BeiDou geostationary satellites (PRN 1-5) + +//CFG-GEOFENCE: Geofencing configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_GEOFENCE_CONFLVL = 0x20240011; // Required confidence level for state evaluation +const uint32_t UBLOX_CFG_GEOFENCE_USE_PIO = 0x10240012; // Use PIO combined fence state output +const uint32_t UBLOX_CFG_GEOFENCE_PINPOL = 0x20240013; // PIO pin polarity +const uint32_t UBLOX_CFG_GEOFENCE_PIN = 0x20240014; // PIO pin number +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE1 = 0x10240020; // Use frst geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE1_LAT = 0x40240021; // Latitude of the first geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE1_LON = 0x40240022; // Longitude of the first geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE1_RAD = 0x40240023; // Radius of the first geofence circle +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE2 = 0x10240030; // Use second geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE2_LAT = 0x40240031; // Latitude of the second geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE2_LON = 0x40240032; // Longitude of the second geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE2_RAD = 0x40240033; // Radius of the second geofence circle +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE3 = 0x10240040; // Use third geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE3_LAT = 0x40240041; // Latitude of the third geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE3_LON = 0x40240042; // Longitude of the third geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE3_RAD = 0x40240043; // Radius of the third geofence circle +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE4 = 0x10240050; // Use fourth geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE4_LAT = 0x40240051; // Latitude of the fourth geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE4_LON = 0x40240052; // Longitude of the fourth geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE4_RAD = 0x40240053; // Radius of the fourth geofence circle + +//CFG-HW: Hardware configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_HW_ANT_CFG_VOLTCTRL = 0x10a3002e; // Active antenna voltage control flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_SHORTDET = 0x10a3002f; // Short antenna detection flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_SHORTDET_POL = 0x10a30030; // Short antenna detection polarity +const uint32_t UBLOX_CFG_HW_ANT_CFG_OPENDET = 0x10a30031; // Open antenna detection flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_OPENDET_POL = 0x10a30032; // Open antenna detection polarity +const uint32_t UBLOX_CFG_HW_ANT_CFG_PWRDOWN = 0x10a30033; // Power down antenna flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_PWRDOWN_POL = 0x10a30034; // Power down antenna logic polarity +const uint32_t UBLOX_CFG_HW_ANT_CFG_RECOVER = 0x10a30035; // Automatic recovery from short state flag +const uint32_t UBLOX_CFG_HW_ANT_SUP_SWITCH_PIN = 0x20a30036; // ANT1 PIO number +const uint32_t UBLOX_CFG_HW_ANT_SUP_SHORT_PIN = 0x20a30037; // ANT0 PIO number +const uint32_t UBLOX_CFG_HW_ANT_SUP_OPEN_PIN = 0x20a30038; // ANT2 PIO number +const uint32_t UBLOX_CFG_HW_ANT_SUP_ENGINE = 0x20a30054; // Antenna supervisor engine selection +const uint32_t UBLOX_CFG_HW_ANT_SUP_SHORT_THR = 0x20a30055; // Antenna supervisor MADC engine short detection threshold +const uint32_t UBLOX_CFG_HW_ANT_SUP_OPEN_THR = 0x20a30056; // Antenna supervisor MADC engine open detection threshold + +//CFG-I2C: Configuration of the I2C interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_I2C_ADDRESS = 0x20510001; // I2C slave address of the receiver (7 bits) +const uint32_t UBLOX_CFG_I2C_EXTENDEDTIMEOUT = 0x10510002; // Flag to disable timeouting the interface after 1.5 s +const uint32_t UBLOX_CFG_I2C_ENABLED = 0x10510003; // Flag to indicate if the I2C interface should be enabled + +//CFG-I2CINPROT: Input protocol configuration of the I2C interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_I2CINPROT_UBX = 0x10710001; // Flag to indicate if UBX should be an input protocol on I2C +const uint32_t UBLOX_CFG_I2CINPROT_NMEA = 0x10710002; // Flag to indicate if NMEA should be an input protocol on I2C +const uint32_t UBLOX_CFG_I2CINPROT_RTCM3X = 0x10710004; // Flag to indicate if RTCM3X should be an input protocol on I2C + +//CFG-I2COUTPROT: Output protocol configuration of the I2C interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_I2COUTPROT_UBX = 0x10720001; // Flag to indicate if UBX should be an output protocol on I2C +const uint32_t UBLOX_CFG_I2COUTPROT_NMEA = 0x10720002; // Flag to indicate if NMEA should be an output protocol on I2C +const uint32_t UBLOX_CFG_I2COUTPROT_RTCM3X = 0x10720004; // Flag to indicate if RTCM3X should be an output protocol on I2C + +//CFG-INFMSG: Information message configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_INFMSG_UBX_I2C = 0x20920001; // Information message enable flags for the UBX protocol on the I2C interface +const uint32_t UBLOX_CFG_INFMSG_UBX_UART1 = 0x20920002; // Information message enable flags for the UBX protocol on the UART1 interface +const uint32_t UBLOX_CFG_INFMSG_UBX_UART2 = 0x20920003; // Information message enable flags for the UBX protocol on the UART2 interface +const uint32_t UBLOX_CFG_INFMSG_UBX_USB = 0x20920004; // Information message enable flags for the UBX protocol on the USB interface +const uint32_t UBLOX_CFG_INFMSG_UBX_SPI = 0x20920005; // Information message enable flags for the UBX protocol on the SPI interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_I2C = 0x20920006; // Information message enable flags for the NMEA protocol on the I2C interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_UART1 = 0x20920007; // Information message enable flags for the NMEA protocol on the UART1 interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_UART2 = 0x20920008; // Information message enable flags for the NMEA protocol on the UART2 interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_USB = 0x20920009; // Information message enable flags for the NMEA protocol on the USB interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_SPI = 0x2092000a; // Information message enable flags for the NMEA protocol on the SPI interface + +//CFG-ITFM: Jamming and interference monitor configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_ITFM_BBTHRESHOLD = 0x20410001; // Broadband jamming detection threshold +const uint32_t UBLOX_CFG_ITFM_CWTHRESHOLD = 0x20410002; // CW jamming detection threshold +const uint32_t UBLOX_CFG_ITFM_ENABLE = 0x1041000d; // Enable interference detection +const uint32_t UBLOX_CFG_ITFM_ANTSETTING = 0x20410010; // Antenna setting +const uint32_t UBLOX_CFG_ITFM_ENABLE_AUX = 0x10410013; // Scan auxiliary bands + +//CFG-LOGFILTER: Data logger configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_LOGFILTER_RECORD_ENA = 0x10de0002; // Recording enabled +const uint32_t UBLOX_CFG_LOGFILTER_ONCE_PER_WAKE_UP_ENA = 0x10de0003; // Once per wake up +const uint32_t UBLOX_CFG_LOGFILTER_APPLY_ALL_FILTERS = 0x10de0004; // Apply all filter settings +const uint32_t UBLOX_CFG_LOGFILTER_MIN_INTERVAL = 0x30de0005; // Minimum time interval between loggedpositions +const uint32_t UBLOX_CFG_LOGFILTER_TIME_THRS = 0x30de0006; // Time threshold +const uint32_t UBLOX_CFG_LOGFILTER_SPEED_THRS = 0x30de0007; // Speed threshold +const uint32_t UBLOX_CFG_LOGFILTER_POSITION_THRS = 0x40de0008; // Position threshold + +//CFG-MOT: Motion detector configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_MOT_GNSSSPEED_THRS = 0x20250038; // GNSS speed threshold below which platform is considered as stationary (a.k.a. static hold threshold) +const uint32_t UBLOX_CFG_MOT_GNSSDIST_THRS = 0x3025003b; // Distance above which GNSS-based stationary motion is exit (a.k.a. static hold distance threshold) + +// CFG-MSGOUT: Message output configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +// For each message and port a separate output rate (per second, per epoch) can be configured. +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_I2C = 0x209100a6; //Output rate of the NMEA-GX-DTM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_SPI = 0x209100aa; //Output rate of the NMEA-GX-DTM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_UART1 = 0x209100a7; //Output rate of the NMEA-GX-DTM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_UART2 = 0x209100a8; //Output rate of the NMEA-GX-DTM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_USB = 0x209100a9; //Output rate of the NMEA-GX-DTM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_I2C = 0x209100dd; //Output rate of the NMEA-GX-GBS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_SPI = 0x209100e1; //Output rate of the NMEA-GX-GBS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_UART1 = 0x209100de; //Output rate of the NMEA-GX-GBS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_UART2 = 0x209100df; //Output rate of the NMEA-GX-GBS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_USB = 0x209100e0; //Output rate of the NMEA-GX-GBS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C = 0x209100ba; //Output rate of the NMEA-GX-GGA message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_SPI = 0x209100be; //Output rate of the NMEA-GX-GGA message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART1 = 0x209100bb; //Output rate of the NMEA-GX-GGA message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART2 = 0x209100bc; //Output rate of the NMEA-GX-GGA message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_USB = 0x209100bd; //Output rate of the NMEA-GX-GGA message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C = 0x209100c9; //Output rate of the NMEA-GX-GLL message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_SPI = 0x209100cd; //Output rate of the NMEA-GX-GLL message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART1 = 0x209100ca; //Output rate of the NMEA-GX-GLL message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART2 = 0x209100cb; //Output rate of the NMEA-GX-GLL message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_USB = 0x209100cc; //Output rate of the NMEA-GX-GLL message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_I2C = 0x209100b5; //Output rate of the NMEA-GX-GNS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_SPI = 0x209100b9; //Output rate of the NMEA-GX-GNS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_UART1 = 0x209100b6; //Output rate of the NMEA-GX-GNS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_UART2 = 0x209100b7; //Output rate of the NMEA-GX-GNS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_USB = 0x209100b8; //Output rate of the NMEA-GX-GNS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_I2C = 0x209100ce; //Output rate of the NMEA-GX-GRS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_SPI = 0x209100d2; //Output rate of the NMEA-GX-GRS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_UART1 = 0x209100cf; //Output rate of the NMEA-GX-GRS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_UART2 = 0x209100d0; //Output rate of the NMEA-GX-GRS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_USB = 0x209100d1; //Output rate of the NMEA-GX-GRS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C = 0x209100bf; //Output rate of the NMEA-GX-GSA message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_SPI = 0x209100c3; //Output rate of the NMEA-GX-GSA message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART1 = 0x209100c0; //Output rate of the NMEA-GX-GSA message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART2 = 0x209100c1; //Output rate of the NMEA-GX-GSA message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_USB = 0x209100c2; //Output rate of the NMEA-GX-GSA message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_I2C = 0x209100d3; //Output rate of the NMEA-GX-GST message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_SPI = 0x209100d7; //Output rate of the NMEA-GX-GST message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART1 = 0x209100d4; //Output rate of the NMEA-GX-GST message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART2 = 0x209100d5; //Output rate of the NMEA-GX-GST message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_USB = 0x209100d6; //Output rate of the NMEA-GX-GST message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C = 0x209100c4; //Output rate of the NMEA-GX-GSV message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_SPI = 0x209100c8; //Output rate of the NMEA-GX-GSV message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART1 = 0x209100c5; //Output rate of the NMEA-GX-GSV message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART2 = 0x209100c6; //Output rate of the NMEA-GX-GSV message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_USB = 0x209100c7; //Output rate of the NMEA-GX-GSV message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_I2C = 0x20910400; //Output rate of the NMEA-GX-RLM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_SPI = 0x20910404; //Output rate of the NMEA-GX-RLM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_UART1 = 0x20910401; //Output rate of the NMEA-GX-RLM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_UART2 = 0x20910402; //Output rate of the NMEA-GX-RLM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_USB = 0x20910403; //Output rate of the NMEA-GX-RLM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_I2C = 0x209100ab; //Output rate of the NMEA-GX-RMC message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_SPI = 0x209100af; //Output rate of the NMEA-GX-RMC message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART1 = 0x209100ac; //Output rate of the NMEA-GX-RMC message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART2 = 0x209100ad; //Output rate of the NMEA-GX-RMC message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_USB = 0x209100ae; //Output rate of the NMEA-GX-RMC message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_I2C = 0x209100e7; //Output rate of the NMEA-GX-VLW message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_SPI = 0x209100eb; //Output rate of the NMEA-GX-VLW message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_UART1 = 0x209100e8; //Output rate of the NMEA-GX-VLW message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_UART2 = 0x209100e9; //Output rate of the NMEA-GX-VLW message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_USB = 0x209100ea; //Output rate of the NMEA-GX-VLW message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C = 0x209100b0; //Output rate of the NMEA-GX-VTG message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_SPI = 0x209100b4; //Output rate of the NMEA-GX-VTG message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART1 = 0x209100b1; //Output rate of the NMEA-GX-VTG message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART2 = 0x209100b2; //Output rate of the NMEA-GX-VTG message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_USB = 0x209100b3; //Output rate of the NMEA-GX-VTG message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_I2C = 0x209100d8; //Output rate of the NMEA-GX-ZDA message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_SPI = 0x209100dc; //Output rate of the NMEA-GX-ZDA message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_UART1 = 0x209100d9; //Output rate of the NMEA-GX-ZDA message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_UART2 = 0x209100da; //Output rate of the NMEA-GX-ZDA message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_USB = 0x209100db; //Output rate of the NMEA-GX-ZDA message on port USB +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_I2C = 0x209100ec; //Output rate of the NMEA-GX-PUBX00 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_SPI = 0x209100f0; //Output rate of the NMEA-GX-PUBX00 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_UART1 = 0x209100ed; //Output rate of the NMEA-GX-PUBX00 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_UART2 = 0x209100ee; //Output rate of the NMEA-GX-PUBX00 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_USB = 0x209100ef; //Output rate of the NMEA-GX-PUBX00 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_I2C = 0x209100f1; //Output rate of the NMEA-GX-PUBX03 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_SPI = 0x209100f5; //Output rate of the NMEA-GX-PUBX03 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_UART1 = 0x209100f2; //Output rate of the NMEA-GX-PUBX03 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_UART2 = 0x209100f3; //Output rate of the NMEA-GX-PUBX03 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_USB = 0x209100f4; //Output rate of the NMEA-GX-PUBX03 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_I2C = 0x209100f6; //Output rate of the NMEA-GX-PUBX04 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_SPI = 0x209100fa; //Output rate of the NMEA-GX-PUBX04 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_UART1 = 0x209100f7; //Output rate of the NMEA-GX-PUBX04 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_UART2 = 0x209100f8; //Output rate of the NMEA-GX-PUBX04 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_USB = 0x209100f9; //Output rate of the NMEA-GX-PUBX04 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C = 0x209102bd; //Output rate of the RTCM-3X-TYPE1005 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_SPI = 0x209102c1; //Output rate of the RTCM-3X-TYPE1005 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_UART1 = 0x209102be;//Output rate of the RTCM-3X-TYPE1005 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_UART2 = 0x209102bf;//Output rate of the RTCM-3X-TYPE1005 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_USB = 0x209102c0; //Output rate of the RTCM-3X-TYPE1005 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_I2C = 0x2091035e; //Output rate of the RTCM-3X-TYPE1074 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_SPI = 0x20910362; //Output rate of the RTCM-3X-TYPE1074 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_UART1 = 0x2091035f;//Output rate of the RTCM-3X-TYPE1074 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_UART2 = 0x20910360;//Output rate of the RTCM-3X-TYPE1074 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_USB = 0x20910361; //Output rate of the RTCM-3X-TYPE1074 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_I2C = 0x209102cc; //Output rate of the RTCM-3X-TYPE1077 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_SPI = 0x209102d0; //Output rate of the RTCM-3X-TYPE1077 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_UART1 = 0x209102cd;//Output rate of the RTCM-3X-TYPE1077 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_UART2 = 0x209102ce;//Output rate of the RTCM-3X-TYPE1077 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_USB = 0x209102cf; //Output rate of the RTCM-3X-TYPE1077 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_I2C = 0x20910363; //Output rate of the RTCM-3X-TYPE1084 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_SPI = 0x20910367; //Output rate of the RTCM-3X-TYPE1084 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_UART1 = 0x20910364;//Output rate of the RTCM-3X-TYPE1084 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_UART2 = 0x20910365;//Output rate of the RTCM-3X-TYPE1084 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_USB = 0x20910366; //Output rate of the RTCM-3X-TYPE1084 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_I2C = 0x209102d1; //Output rate of the RTCM-3X-TYPE1087 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_SPI = 0x209102d5; //Output rate of the RTCM-3X-TYPE1087 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_UART1 = 0x209102d2;//Output rate of the RTCM-3X-TYPE1087 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_UART2 = 0x209102d3;//Output rate of the RTCM-3X-TYPE1087 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_USB = 0x209102d4; //Output rate of the RTCM-3X-TYPE1087 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_I2C = 0x20910368; //Output rate of the RTCM-3X-TYPE1094 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_SPI = 0x2091036c; //Output rate of the RTCM-3X-TYPE1094 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_UART1 = 0x20910369;//Output rate of the RTCM-3X-TYPE1094 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_UART2 = 0x2091036a;//Output rate of the RTCM-3X-TYPE1094 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_USB = 0x2091036b; //Output rate of the RTCM-3X-TYPE1094 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_I2C = 0x20910318; //Output rate of the RTCM-3X-TYPE1097 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_SPI = 0x2091031c; //Output rate of the RTCM-3X-TYPE1097 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_UART1 = 0x20910319;//Output rate of the RTCM-3X-TYPE1097 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_UART2 = 0x2091031a;//Output rate of the RTCM-3X-TYPE1097 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_USB = 0x2091031b; //Output rate of the RTCM-3X-TYPE1097 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_I2C = 0x2091036d; //Output rate of the RTCM-3X-TYPE1124 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_SPI = 0x20910371; //Output rate of the RTCM-3X-TYPE1124 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_UART1 = 0x2091036e;//Output rate of the RTCM-3X-TYPE1124 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_UART2 = 0x2091036f;//Output rate of the RTCM-3X-TYPE1124 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_USB = 0x20910370; //Output rate of the RTCM-3X-TYPE1124 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_I2C = 0x209102d6; //Output rate of the RTCM-3X-TYPE1127 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_SPI = 0x209102da; //Output rate of the RTCM-3X-TYPE1127 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_UART1 = 0x209102d7;//Output rate of the RTCM-3X-TYPE1127 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_UART2 = 0x209102d8;//Output rate of the RTCM-3X-TYPE1127 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_USB = 0x209102d9; //Output rate of the RTCM-3X-TYPE1127 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_I2C = 0x20910303; //Output rate of the RTCM-3X-TYPE1230 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_SPI = 0x20910307; //Output rate of the RTCM-3X-TYPE1230 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART1 = 0x20910304;//Output rate of the RTCM-3X-TYPE1230 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART2 = 0x20910305;//Output rate of the RTCM-3X-TYPE1230 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_USB = 0x20910306; //Output rate of the RTCM-3X-TYPE1230 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_I2C = 0x209102fe;//Output rate of the RTCM-3X-TYPE4072_0 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_SPI = 0x20910302;//Output rate of the RTCM-3X-TYPE4072_0 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_UART1 = 0x209102ff; //Output rate of the RTCM-3X-TYPE4072_0 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_UART2 = 0x20910300; //Output rate of the RTCM-3X-TYPE4072_0 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_USB = 0x20910301;//Output rate of the RTCM-3X-TYPE4072_0 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_I2C = 0x20910381;//Output rate of the RTCM-3X-TYPE4072_1 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_SPI = 0x20910385;//Output rate of the RTCM-3X-TYPE4072_1 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_UART1 = 0x20910382; //Output rate of the RTCM-3X-TYPE4072_1 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_UART2 = 0x20910383; //Output rate of the RTCM-3X-TYPE4072_1 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_USB = 0x20910384;//Output rate of the RTCM-3X-TYPE4072_1 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_I2C = 0x20910259; //Output rate of the UBX-LOG-INFO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_SPI = 0x2091025d; //Output rate of the UBX-LOG-INFO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_UART1 = 0x2091025a; //Output rate of the UBX-LOG-INFO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_UART2 = 0x2091025b; //Output rate of the UBX-LOG-INFO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_USB = 0x2091025c; //Output rate of the UBX-LOG-INFO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_I2C = 0x2091034f; //Output rate of the UBX-MON-COMMS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_SPI = 0x20910353; //Output rate of the UBX-MON-COMMS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_UART1 = 0x20910350; //Output rate of the UBX-MON-COMMS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_UART2 = 0x20910351; //Output rate of the UBX-MON-COMMS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_USB = 0x20910352; //Output rate of the UBX-MON-COMMS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_I2C = 0x209101b9; //Output rate of the UBX-MON-HW2 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_SPI = 0x209101bd; //Output rate of the UBX-MON-HW2 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_UART1 = 0x209101ba; //Output rate of the UBX-MON-HW2 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_UART2 = 0x209101bb; //Output rate of the UBX-MON-HW2 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_USB = 0x209101bc; //Output rate of the UBX-MON-HW2 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_I2C = 0x20910354; //Output rate of the UBX-MON-HW3 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_SPI = 0x20910358; //Output rate of the UBX-MON-HW3 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_UART1 = 0x20910355; //Output rate of the UBX-MON-HW3 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_UART2 = 0x20910356; //Output rate of the UBX-MON-HW3 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_USB = 0x20910357; //Output rate of the UBX-MON-HW3 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_I2C = 0x209101b4; //Output rate of the UBX-MON-HW message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_SPI = 0x209101b8; //Output rate of the UBX-MON-HW message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_UART1 = 0x209101b5; //Output rate of the UBX-MON-HW message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_UART2 = 0x209101b6; //Output rate of the UBX-MON-HW message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_USB = 0x209101b7; //Output rate of the UBX-MON-HW message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_I2C = 0x209101a5; //Output rate of the UBX-MON-IO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_SPI = 0x209101a9; //Output rate of the UBX-MON-IO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_UART1 = 0x209101a6; //Output rate of the UBX-MON-IO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_UART2 = 0x209101a7; //Output rate of the UBX-MON-IO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_USB = 0x209101a8; //Output rate of the UBX-MON-IO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_I2C = 0x20910196; //Output rate of the UBX-MON-MSGPP message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_SPI = 0x2091019a; //Output rate of the UBX-MON-MSGPP message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_UART1 = 0x20910197; //Output rate of the UBX-MON-MSGPP message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_UART2 = 0x20910198; //Output rate of the UBX-MON-MSGPP message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_USB = 0x20910199; //Output rate of the UBX-MON-MSGPP message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_I2C = 0x20910359; //Output rate of the UBX-MON-RF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_SPI = 0x2091035d; //Output rate of the UBX-MON-RF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_UART1 = 0x2091035a; //Output rate of the UBX-MON-RF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_UART2 = 0x2091035b; //Output rate of the UBX-MON-RF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_USB = 0x2091035c; // Output rate of the UBX-MON-RF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_I2C = 0x209101a0; // Output rate of the UBX-MON-RXBUF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_SPI = 0x209101a4; // Output rate of the UBX-MON-RXBUF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_UART1 = 0x209101a1; // Output rate of the UBX-MON-RXBUF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_UART2 = 0x209101a2; // Output rate of the UBX-MON-RXBUF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_USB = 0x209101a3; // Output rate of the UBX-MON-RXBUF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_I2C = 0x20910187; // Output rate of the UBX-MON-RXR message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_SPI = 0x2091018b; // Output rate of the UBX-MON-RXR message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_UART1 = 0x20910188; // Output rate of the UBX-MON-RXR message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_UART2 = 0x20910189; // Output rate of the UBX-MON-RXR message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_USB = 0x2091018a; // Output rate of the UBX-MON-RXR message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_I2C = 0x2091038b; // Output rate of the UBX-MON-SPAN message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_SPI = 0x2091038f; // Output rate of the UBX-MON-SPAN message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_UART1 = 0x2091038c; // Output rate of the UBX-MON-SPAN message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_UART2 = 0x2091038d; // Output rate of the UBX-MON-SPAN message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_USB = 0x2091038e; // Output rate of the UBX-MON-SPAN message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_I2C = 0x2091019b; // Output rate of the UBX-MON-TXBUF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_SPI = 0x2091019f; // Output rate of the UBX-MON-TXBUF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_UART1 = 0x2091019c; // Output rate of the UBX-MON-TXBUF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_UART2 = 0x2091019d; // Output rate of the UBX-MON-TXBUF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_USB = 0x2091019e; // Output rate of the UBX-MON-TXBUF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_I2C = 0x2091001f; // Output rate of the UBX_NAV_ATT message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_SPI = 0x20910023; // Output rate of the UBX_NAV_ATT message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_UART1 = 0x20910020; // Output rate of the UBX_NAV_ATT message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_UART2 = 0x20910021; // Output rate of the UBX_NAV_ATT message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_USB = 0x20910022; // Output rate of the UBX_NAV_ATT message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_I2C = 0x20910065; // Output rate of the UBX-NAV-CLOCK message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_SPI = 0x20910069; // Output rate of the UBX-NAV-CLOCK message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_UART1 = 0x20910066; // Output rate of the UBX-NAV-CLOCK message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_UART2 = 0x20910067; // Output rate of the UBX-NAV-CLOCK message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_USB = 0x20910068; // Output rate of the UBX-NAV-CLOCK message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_I2C = 0x20910038; // Output rate of the UBX-NAV-DOP message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_SPI = 0x2091003c; // Output rate of the UBX-NAV-DOP message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_UART1 = 0x20910039; // Output rate of the UBX-NAV-DOP message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_UART2 = 0x2091003a; // Output rate of the UBX-NAV-DOP message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_USB = 0x2091003b; // Output rate of the UBX-NAV-DOP message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_I2C = 0x2091015f; // Output rate of the UBX-NAV-EOE message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_SPI = 0x20910163; // Output rate of the UBX-NAV-EOE message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_UART1 = 0x20910160; // Output rate of the UBX-NAV-EOE message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_UART2 = 0x20910161; // Output rate of the UBX-NAV-EOE message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_USB = 0x20910162; // Output rate of the UBX-NAV-EOE message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_I2C = 0x209100a1; // Output rate of the UBX-NAV-GEOFENCE message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_SPI = 0x209100a5; // Output rate of the UBX-NAV-GEOFENCE message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_UART1 = 0x209100a2;// Output rate of the UBX-NAV-GEOFENCE message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_UART2 = 0x209100a3;// Output rate of the UBX-NAV-GEOFENCE message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_USB = 0x209100a4; // Output rate of the UBX-NAV-GEOFENCE message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_I2C = 0x2091002e;// Output rate of the UBX-NAV-HPPOSECEF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_SPI = 0x20910032;// Output rate of the UBX-NAV-HPPOSECEF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_UART1 = 0x2091002f;// Output rate of the UBX-NAV-HPPOSECEF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_UART2 = 0x20910030;// Output rate of the UBX-NAV-HPPOSECEF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_USB = 0x20910031;// Output rate of the UBX-NAV-HPPOSECEF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_I2C = 0x20910033; // Output rate of the UBX-NAV-HPPOSLLH message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_SPI = 0x20910037; // Output rate of the UBX-NAV-HPPOSLLH message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_UART1 = 0x20910034;// Output rate of the UBX-NAV-HPPOSLLH message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_UART2 = 0x20910035;// Output rate of the UBX-NAV-HPPOSLLH message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_USB = 0x20910036; // Output rate of the UBX-NAV-HPPOSLLH message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_I2C = 0x2091007e; // Output rate of the UBX-NAV-ODO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_SPI = 0x20910082; // Output rate of the UBX-NAV-ODO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_UART1 = 0x2091007f; // Output rate of the UBX-NAV-ODO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_UART2 = 0x20910080; // Output rate of the UBX-NAV-ODO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_USB = 0x20910081; // Output rate of the UBX-NAV-ODO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_I2C = 0x20910010; // Output rate of the UBX-NAV-ORB message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_SPI = 0x20910014; // Output rate of the UBX-NAV-ORB message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_UART1 = 0x20910011; // Output rate of the UBX-NAV-ORB message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_UART2 = 0x20910012; // Output rate of the UBX-NAV-ORB message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_USB = 0x20910013; // Output rate of the UBX-NAV-ORB message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_I2C = 0x20910024; // Output rate of the UBX-NAV-POSECEF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_SPI = 0x20910028; // Output rate of the UBX-NAV-POSECEF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_UART1 = 0x20910025;// Output rate of the UBX-NAV-POSECEF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_UART2 = 0x20910026;// Output rate of the UBX-NAV-POSECEF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_USB = 0x20910027; // Output rate of the UBX-NAV-POSECEF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_I2C = 0x20910029; // Output rate of the UBX-NAV-POSLLH message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_SPI = 0x2091002d; // Output rate of the UBX-NAV-POSLLH message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_UART1 = 0x2091002a; // Output rate of the UBX-NAV-POSLLH message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_UART2 = 0x2091002b; // Output rate of the UBX-NAV-POSLLH message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_USB = 0x2091002c; // Output rate of the UBX-NAV-POSLLH message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_I2C = 0x20910006; // Output rate of the UBX-NAV-PVT message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_SPI = 0x2091000a; // Output rate of the UBX-NAV-PVT message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_UART1 = 0x20910007; // Output rate of the UBX-NAV-PVT message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_UART2 = 0x20910008; // Output rate of the UBX-NAV-PVT message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_USB = 0x20910009; // Output rate of the UBX-NAV-PVT message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_I2C = 0x2091008d; // Output rate of the UBX-NAV-RELPOSNED message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_SPI = 0x20910091;// Output rate of the UBX-NAV-RELPOSNED message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_UART1 = 0x2091008e;// Output rate of the UBX-NAV-RELPOSNED message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_UART2 = 0x2091008f;// Output rate of the UBX-NAV-RELPOSNED message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_USB = 0x20910090;// Output rate of the UBX-NAV-RELPOSNED message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_I2C = 0x20910015; // Output rate of the UBX-NAV-SAT message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_SPI = 0x20910019; // Output rate of the UBX-NAV-SAT message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_UART1 = 0x20910016; // Output rate of the UBX-NAV-SAT message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_UART2 = 0x20910017; // Output rate of the UBX-NAV-SAT message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_USB = 0x20910018; // Output rate of the UBX-NAV-SAT message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_I2C = 0x2091006a; // Output rate of the UBX-NAV-SBAS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_SPI = 0x2091006e; // Output rate of the UBX-NAV-SBAS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_UART1 = 0x2091006b; // Output rate of the UBX-NAV-SBAS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_UART2 = 0x2091006c; // Output rate of the UBX-NAV-SBAS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_USB = 0x2091006d; // Output rate of the UBX-NAV-SBAS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_I2C = 0x20910345; // Output rate of the UBX-NAV-SIG message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_SPI = 0x20910349; // Output rate of the UBX-NAV-SIG message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_UART1 = 0x20910346; // Output rate of the UBX-NAV-SIG message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_UART2 = 0x20910347; // Output rate of the UBX-NAV-SIG message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_USB = 0x20910348; // Output rate of the UBX-NAV-SIG message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_I2C = 0x20910336; // Output rate of the UBX-NAV-SLAS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_SPI = 0x2091033a; // Output rate of the UBX-NAV-SLAS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_UART1 = 0x20910337; // Output rate of the UBX-NAV-SLAS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_UART2 = 0x20910338; // Output rate of the UBX-NAV-SLAS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_USB = 0x20910339; // Output rate of the UBX-NAV-SLAS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_I2C = 0x2091001a; // Output rate of the UBX-NAV-STATUS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_SPI = 0x2091001e; // Output rate of the UBX-NAV-STATUS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_UART1 = 0x2091001b; // Output rate of the UBX-NAV-STATUS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_UART2 = 0x2091001c; // Output rate of the UBX-NAV-STATUS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_USB = 0x2091001d; // Output rate of the UBX-NAV-STATUS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_I2C = 0x20910088; // Output rate of the UBX-NAV-SVIN message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_SPI = 0x2091008c; // Output rate of the UBX-NAV-SVIN message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_UART1 = 0x20910089; // Output rate of the UBX-NAV-SVIN message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_UART2 = 0x2091008a; // Output rate of the UBX-NAV-SVIN message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_USB = 0x2091008b; // Output rate of the UBX-NAV-SVIN message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_I2C = 0x20910051; // Output rate of the UBX-NAV-TIMEBDS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_SPI = 0x20910055; // Output rate of the UBX-NAV-TIMEBDS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_UART1 = 0x20910052;// Output rate of the UBX-NAV-TIMEBDS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_UART2 = 0x20910053;// Output rate of the UBX-NAV-TIMEBDS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_USB = 0x20910054; // Output rate of the UBX-NAV-TIMEBDS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_I2C = 0x20910056; // Output rate of the UBX-NAV-TIMEGAL message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_SPI = 0x2091005a; // Output rate of the UBX-NAV-TIMEGAL message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_UART1 = 0x20910057;// Output rate of the UBX-NAV-TIMEGAL message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_UART2 = 0x20910058;// Output rate of the UBX-NAV-TIMEGAL message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_USB = 0x20910059; // Output rate of the UBX-NAV-TIMEGAL message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_I2C = 0x2091004c; // Output rate of the UBX-NAV-TIMEGLO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_SPI = 0x20910050; // Output rate of the UBX-NAV-TIMEGLO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_UART1 = 0x2091004d;// Output rate of the UBX-NAV-TIMEGLO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_UART2 = 0x2091004e;// Output rate of the UBX-NAV-TIMEGLO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_USB = 0x2091004f; // Output rate of the UBX-NAV-TIMEGLO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_I2C = 0x20910047; // Output rate of the UBX-NAV-TIMEGPS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_SPI = 0x2091004b; // Output rate of the UBX-NAV-TIMEGPS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_UART1 = 0x20910048;// Output rate of the UBX-NAV-TIMEGPS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_UART2 = 0x20910049;// Output rate of the UBX-NAV-TIMEGPS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_USB = 0x2091004a; // Output rate of the UBX-NAV-TIMEGPS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_I2C = 0x20910060; // Output rate of the UBX-NAV-TIMELS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_SPI = 0x20910064; // Output rate of the UBX-NAV-TIMELS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_UART1 = 0x20910061; // Output rate of the UBX-NAV-TIMELS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_UART2 = 0x20910062; // Output rate of the UBX-NAV-TIMELS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_USB = 0x20910063; // Output rate of the UBX-NAV-TIMELS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_I2C = 0x20910386; // Output rate of the UBX-NAV-TIMEQZSSmessage on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_SPI = 0x2091038a; // Output rate of the UBX-NAV-TIMEQZSSmessage on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_UART1 = 0x20910387;// Output rate of the UBX-NAV-TIMEQZSSmessage on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_UART2 = 0x20910388;// Output rate of the UBX-NAV-TIMEQZSSmessage on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_USB = 0x20910389; // Output rate of the UBX-NAV-TIMEQZSSmessage on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_I2C = 0x2091005b; // Output rate of the UBX-NAV-TIMEUTC message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_SPI = 0x2091005f; // Output rate of the UBX-NAV-TIMEUTC message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_UART1 = 0x2091005c;// Output rate of the UBX-NAV-TIMEUTC message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_UART2 = 0x2091005d;// Output rate of the UBX-NAV-TIMEUTC message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_USB = 0x2091005e; // Output rate of the UBX-NAV-TIMEUTC message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_I2C = 0x2091003d; // Output rate of the UBX-NAV-VELECEF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_SPI = 0x20910041; // Output rate of the UBX-NAV-VELECEF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_UART1 = 0x2091003e;// Output rate of the UBX-NAV-VELECEF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_UART2 = 0x2091003f;// Output rate of the UBX-NAV-VELECEF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_USB = 0x20910040; // Output rate of the UBX-NAV-VELECEF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_I2C = 0x20910042; // Output rate of the UBX-NAV-VELNED message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_SPI = 0x20910046; // Output rate of the UBX-NAV-VELNED message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_UART1 = 0x20910043; // Output rate of the UBX-NAV-VELNED message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_UART2 = 0x20910044; // Output rate of the UBX-NAV-VELNED message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_USB = 0x20910045; // Output rate of the UBX-NAV-VELNED message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_I2C = 0x20910204; // Output rate of the UBX-RXM-MEASX message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_SPI = 0x20910208; // Output rate of the UBX-RXM-MEASX message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_UART1 = 0x20910205; // Output rate of the UBX-RXM-MEASX message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_UART2 = 0x20910206; // Output rate of the UBX-RXM-MEASX message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_USB = 0x20910207; // Output rate of the UBX-RXM-MEASX message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_I2C = 0x209102a4; // Output rate of the UBX-RXM-RAWX message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_SPI = 0x209102a8; // Output rate of the UBX-RXM-RAWX message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_UART1 = 0x209102a5; // Output rate of the UBX-RXM-RAWX message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_UART2 = 0x209102a6; // Output rate of the UBX-RXM-RAWX message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_USB = 0x209102a7; // Output rate of the UBX-RXM-RAWX message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_I2C = 0x2091025e; // Output rate of the UBX-RXM-RLM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_SPI = 0x20910262; // Output rate of the UBX-RXM-RLM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_UART1 = 0x2091025f; // Output rate of the UBX-RXM-RLM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_UART2 = 0x20910260; // Output rate of the UBX-RXM-RLM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_USB = 0x20910261; // Output rate of the UBX-RXM-RLM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_I2C = 0x20910268; // Output rate of the UBX-RXM-RTCM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_SPI = 0x2091026c; // Output rate of the UBX-RXM-RTCM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_UART1 = 0x20910269; // Output rate of the UBX-RXM-RTCM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_UART2 = 0x2091026a; // Output rate of the UBX-RXM-RTCM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_USB = 0x2091026b; // Output rate of the UBX-RXM-RTCM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_I2C = 0x20910231; // Output rate of the UBX-RXM-SFRBX message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_SPI = 0x20910235; // Output rate of the UBX-RXM-SFRBX message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_UART1 = 0x20910232; // Output rate of the UBX-RXM-SFRBX message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_UART2 = 0x20910233; // Output rate of the UBX-RXM-SFRBX message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_USB = 0x20910234; // Output rate of the UBX-RXM-SFRBX message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_I2C = 0x20910178; // Output rate of the UBX-TIM-TM2 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_SPI = 0x2091017c; // Output rate of the UBX-TIM-TM2 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_UART1 = 0x20910179; // Output rate of the UBX-TIM-TM2 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_UART2 = 0x2091017a; // Output rate of the UBX-TIM-TM2 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_USB = 0x2091017b; // Output rate of the UBX-TIM-TM2 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_I2C = 0x2091017d; // Output rate of the UBX-TIM-TP message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_SPI = 0x20910181; // Output rate of the UBX-TIM-TP message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART1 = 0x2091017e; // Output rate of the UBX-TIM-TP message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART2 = 0x2091017f; // Output rate of the UBX-TIM-TP message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_USB = 0x20910180; // Output rate of the UBX-TIM-TP message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_I2C = 0x20910092; // Output rate of the UBX-TIM-VRFY message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_SPI = 0x20910096; // Output rate of the UBX-TIM-VRFY message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_UART1 = 0x20910093; // Output rate of the UBX-TIM-VRFY message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_UART2 = 0x20910094; // Output rate of the UBX-TIM-VRFY message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_USB = 0x20910095; // Output rate of the UBX-TIM-VRFY message on port USB + +//CFG-NAVHPG: High precision navigation configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_NAVHPG_DGNSSMODE = 0x20140011; // Differential corrections mode + +//CFG-NAVSPG: Standard precision navigation configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_NAVSPG_FIXMODE = 0x20110011; // Position fix mode +const uint32_t UBLOX_CFG_NAVSPG_INIFIX3D = 0x10110013; // Initial fix must be a 3D fix +const uint32_t UBLOX_CFG_NAVSPG_WKNROLLOVER = 0x30110017; // GPS week rollover number +const uint32_t UBLOX_CFG_NAVSPG_UTCSTANDARD = 0x2011001c; // UTC standard to be used +const uint32_t UBLOX_CFG_NAVSPG_DYNMODEL = 0x20110021; // Dynamic platform model +const uint32_t UBLOX_CFG_NAVSPG_ACKAIDING = 0x10110025; // Acknowledge assistance input messages +const uint32_t UBLOX_CFG_NAVSPG_USE_USRDAT = 0x10110061; // Use user geodetic datum parameters +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_MAJA = 0x50110062; // Geodetic datum semi-major axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_FLAT = 0x50110063; // Geodetic datum 1.0 flattening +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_DX = 0x40110064; // Geodetic datum X axis shift at the origin +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_DY = 0x40110065; // Geodetic datum Y axis shift at the origin +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_DZ = 0x40110066; // Geodetic datum Z axis shift at the origin +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_ROTX = 0x40110067; // arcsec Geodetic datum rotation about the X axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_ROTY = 0x40110068; // arcsec Geodetic datum rotation about the Y axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_ROTZ = 0x40110069; // arcsec Geodetic datum rotation about the Z axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_SCALE = 0x4011006a; // ppm Geodetic datum scale factor +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MINSVS = 0x201100a1; // Minimum number of satellites for navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MAXSVS = 0x201100a2; // Maximum number of satellites for navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MINCNO = 0x201100a3; // Minimum satellite signal level for navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MINELEV = 0x201100a4; // Minimum elevation for a GNSS satellite to be used in navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_NCNOTHRS = 0x201100aa; // Number of satellites required to have C/N0 above const uint32_t UBLOX_CFG_NAVSPG-INFIL_CNOTHRS for a fix to be attempted +const uint32_t UBLOX_CFG_NAVSPG_INFIL_CNOTHRS = 0x201100ab; // C/N0 threshold for deciding whether to attempt a fix +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_PDOP = 0x301100b1; // Output filter position DOP mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_TDOP = 0x301100b2; // Output filter time DOP mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_PACC = 0x301100b3; // Output filter position accuracy mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_TACC = 0x301100b4; // Output filter time accuracy mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_FACC = 0x301100b5; // Output filter frequency accuracy mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_CONSTR_ALT = 0x401100c1; // Fixed altitude (mean sea level) for 2D fix mode +const uint32_t UBLOX_CFG_NAVSPG_CONSTR_ALTVAR = 0x401100c2; // Fixed altitude variance for 2D mode +const uint32_t UBLOX_CFG_NAVSPG_CONSTR_DGNSSTO = 0x201100c4; // DGNSS timeout + +//CFG-NMEA: NMEA protocol configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_NMEA_PROTVER = 0x20930001; // NMEA protocol version +const uint32_t UBLOX_CFG_NMEA_MAXSVS = 0x20930002; // Maximum number of SVs to report per Talker ID +const uint32_t UBLOX_CFG_NMEA_COMPAT = 0x10930003; // Enable compatibility mode +const uint32_t UBLOX_CFG_NMEA_CONSIDER = 0x10930004; // Enable considering mode +const uint32_t UBLOX_CFG_NMEA_LIMIT82 = 0x10930005; // Enable strict limit to 82 characters maximum NMEA message length +const uint32_t UBLOX_CFG_NMEA_HIGHPREC = 0x10930006; // Enable high precision mode +const uint32_t UBLOX_CFG_NMEA_SVNUMBERING = 0x20930007; // Display configuration for SVs that do not have value defined in NMEA +const uint32_t UBLOX_CFG_NMEA_FILT_GPS = 0x10930011; // Disable reporting of GPS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_SBAS = 0x10930012; // Disable reporting of SBAS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_GAL = 0x10930013; // Disable reporting of Galileo satellites +const uint32_t UBLOX_CFG_NMEA_FILT_QZSS = 0x10930015; // Disable reporting of QZSS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_GLO = 0x10930016; // Disable reporting of GLONASS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_BDS = 0x10930017; // Disable reporting of BeiDou satellites +const uint32_t UBLOX_CFG_NMEA_OUT_INVFIX = 0x10930021; // Enable position output for failed or invalid fixes +const uint32_t UBLOX_CFG_NMEA_OUT_MSKFIX = 0x10930022; // Enable position output for invalid fixes +const uint32_t UBLOX_CFG_NMEA_OUT_INVTIME = 0x10930023; // Enable time output for invalid times +const uint32_t UBLOX_CFG_NMEA_OUT_INVDATE = 0x10930024; // Enable date output for invalid dates +const uint32_t UBLOX_CFG_NMEA_OUT_ONLYGPS = 0x10930025; // Restrict output to GPS satellites only +const uint32_t UBLOX_CFG_NMEA_OUT_FROZENCOG = 0x10930026; // Enable course over ground output even if it is frozen +const uint32_t UBLOX_CFG_NMEA_MAINTALKERID = 0x20930031; // Main Talker ID +const uint32_t UBLOX_CFG_NMEA_GSVTALKERID = 0x20930032; // Talker ID for GSV NMEA messages +const uint32_t UBLOX_CFG_NMEA_BDSTALKERID = 0x30930033; // BeiDou Talker ID + +//CFG-ODO: Odometer and low-speed course over ground filter +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_ODO_USE_ODO = 0x10220001; // Use odometer +const uint32_t UBLOX_CFG_ODO_USE_COG = 0x10220002; // Use low-speed course over ground filter +const uint32_t UBLOX_CFG_ODO_OUTLPVEL = 0x10220003; // Output low-pass filtered velocity +const uint32_t UBLOX_CFG_ODO_OUTLPCOG = 0x10220004; // Output low-pass filtered course over ground (heading) +const uint32_t UBLOX_CFG_ODO_PROFILE = 0x20220005; // Odometer profile configuration +const uint32_t UBLOX_CFG_ODO_COGMAXSPEED = 0x20220021; // Upper speed limit for low-speed course over ground filter +const uint32_t UBLOX_CFG_ODO_COGMAXPOSACC = 0x20220022; // Maximum acceptable position accuracy for computing low-speed filtered course over ground +const uint32_t UBLOX_CFG_ODO_VELLPGAIN = 0x20220031; // Velocity low-pass filter level +const uint32_t UBLOX_CFG_ODO_COGLPGAIN = 0x20220032; // Course over ground low-pass filter level (at speed < 8 m/s) + +//CFG-QZSS: QZSS system configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_QZSS_USE_SLAS_DGNSS = 0x10370005; // Apply QZSS SLAS DGNSS corrections +const uint32_t UBLOX_CFG_QZSS_USE_SLAS_TESTMODE = 0x10370006; // Use QZSS SLAS data when it is in test mode (SLAS msg 0) +const uint32_t UBLOX_CFG_QZSS_USE_SLAS_RAIM_UNCORR = 0x10370007; // Raim out measurements that are not corrected by QZSS SLAS, if at least 5 measurements are corrected + +//CFG-RATE: Navigation and measurement rate configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_RATE_MEAS = 0x30210001; // Nominal time between GNSS measurements +const uint32_t UBLOX_CFG_RATE_NAV = 0x30210002; // Ratio of number of measurements to number of navigation solutions +const uint32_t UBLOX_CFG_RATE_TIMEREF = 0x20210003; // Time system to which measurements are aligned + +//CFG-RINV: Remote inventory +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_RINV_DUMP = 0x10c70001; // Dump data at startup +const uint32_t UBLOX_CFG_RINV_BINARY = 0x10c70002; // Data is binary +const uint32_t UBLOX_CFG_RINV_DATA_SIZE = 0x20c70003; // Size of data +const uint32_t UBLOX_CFG_RINV_CHUNK0 = 0x50c70004; // Data bytes 1-8 (LSB) +const uint32_t UBLOX_CFG_RINV_CHUNK1 = 0x50c70005; // Data bytes 9-16 +const uint32_t UBLOX_CFG_RINV_CHUNK2 = 0x50c70006; // Data bytes 17-240x44434241. +const uint32_t UBLOX_CFG_RINV_CHUNK3 = 0x50c70007; // Data bytes 25-30 (MSB) + +//CFG-RTCM: RTCM protocol configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_RTCM_DF003_OUT = 0x30090001; // RTCM DF003 (Reference station ID) output value +const uint32_t UBLOX_CFG_RTCM_DF003_IN = 0x30090008; // RTCM DF003 (Reference station ID) input value +const uint32_t UBLOX_CFG_RTCM_DF003_IN_FILTER = 0x20090009; // RTCM input filter configuration based on RTCM DF003 (Reference station ID) value + +//CFG-SBAS: SBAS configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SBAS_USE_TESTMODE = 0x10360002; // Use SBAS data when it is in test mode (SBAS msg 0) +const uint32_t UBLOX_CFG_SBAS_USE_RANGING = 0x10360003; // Use SBAS GEOs as a ranging source (for navigation) +const uint32_t UBLOX_CFG_SBAS_USE_DIFFCORR = 0x10360004; // Use SBAS differential corrections +const uint32_t UBLOX_CFG_SBAS_USE_INTEGRITY = 0x10360005; // Use SBAS integrity information +const uint32_t UBLOX_CFG_SBAS_PRNSCANMASK = 0x50360006; // SBAS PRN search configuration + +//CFG-SIGNAL: Satellite systems (GNSS) signal configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SIGNAL_GPS_ENA = 0x1031001f; // GPS enable +const uint32_t UBLOX_CFG_SIGNAL_GPS_L1CA_ENA = 0x10310001; // GPS L1C/A +const uint32_t UBLOX_CFG_SIGNAL_GPS_L2C_ENA = 0x10310003; // GPS L2C (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_SBAS_ENA = 0x10310020; // SBAS enable +const uint32_t UBLOX_CFG_SIGNAL_SBAS_L1CA_ENA = 0x10310005; // SBAS L1C/A +const uint32_t UBLOX_CFG_SIGNAL_GAL_ENA = 0x10310021; // Galileo enable +const uint32_t UBLOX_CFG_SIGNAL_GAL_E1_ENA = 0x10310007; // Galileo E1 +const uint32_t UBLOX_CFG_SIGNAL_GAL_E5B_ENA = 0x1031000a; // Galileo E5b (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_BDS_ENA = 0x10310022; // BeiDou Enable +const uint32_t UBLOX_CFG_SIGNAL_BDS_B1_ENA = 0x1031000d; // BeiDou B1I +const uint32_t UBLOX_CFG_SIGNAL_BDS_B2_ENA = 0x1031000e; // BeiDou B2I (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_QZSS_ENA = 0x10310024; // QZSS enable +const uint32_t UBLOX_CFG_SIGNAL_QZSS_L1CA_ENA = 0x10310012; // QZSS L1C/A +const uint32_t UBLOX_CFG_SIGNAL_QZSS_L1S_ENA = 0x10310014; // QZSS L1S +const uint32_t UBLOX_CFG_SIGNAL_QZSS_L2C_ENA = 0x10310015; // QZSS L2C (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_GLO_ENA = 0x10310025; // GLONASS enable +const uint32_t UBLOX_CFG_SIGNAL_GLO_L1_ENA = 0x10310018; // GLONASS L1 +const uint32_t UBLOX_CFG_SIGNAL_GLO_L2_ENA = 0x1031001a; // GLONASS L2 (only on u-blox F9 platform products) + +//CFG-SPI: Configuration of the SPI interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SPI_MAXFF = 0x20640001; // Number of bytes containing 0xFF to receive before switching off reception. Range: 0 (mechanism off) - 63 +const uint32_t UBLOX_CFG_SPI_CPOLARITY = 0x10640002; // Clock polarity select: 0: Active Hight Clock, SCLK idles low, 1: Active Low Clock, SCLK idles high +const uint32_t UBLOX_CFG_SPI_CPHASE = 0x10640003; // Clock phase select: 0: Data captured on first edge of SCLK, 1: Data captured on second edge of SCLK +const uint32_t UBLOX_CFG_SPI_EXTENDEDTIMEOUT = 0x10640005; // Flag to disable timeouting the interface after 1.5s +const uint32_t UBLOX_CFG_SPI_ENABLED = 0x10640006; // Flag to indicate if the SPI interface should be enabled + +//CFG-SPIINPROT: Input protocol configuration of the SPI interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SPIINPROT_UBX = 0x10790001; // Flag to indicate if UBX should be an input protocol on SPI +const uint32_t UBLOX_CFG_SPIINPROT_NMEA = 0x10790002; // Flag to indicate if NMEA should be an input protocol on SPI +const uint32_t UBLOX_CFG_SPIINPROT_RTCM3X = 0x10790004; // Flag to indicate if RTCM3X should be an input protocol on SPI + +//CFG-SPIOUTPROT: Output protocol configuration of the SPI interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SPIOUTPROT_UBX = 0x107a0001; // Flag to indicate if UBX should be an output protocol on SPI +const uint32_t UBLOX_CFG_SPIOUTPROT_NMEA = 0x107a0002; // Flag to indicate if NMEA should be an output protocol on SPI +const uint32_t UBLOX_CFG_SPIOUTPROT_RTCM3X = 0x107a0004; // Flag to indicate if RTCM3X should be an output protocol on SPI + +//CFG-TMODE: Time mode configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_TMODE_MODE = 0x20030001; // Receiver mode +const uint32_t UBLOX_CFG_TMODE_POS_TYPE = 0x20030002; // Determines whether the ARP position is given in ECEF or LAT/LON/HEIGHT? +const uint32_t UBLOX_CFG_TMODE_ECEF_X = 0x40030003; // ECEF X coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Y = 0x40030004; // ECEF Y coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Z = 0x40030005; // ECEF Z coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_X_HP = 0x20030006; // High-precision ECEF X coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Y_HP = 0x20030007; // High-precision ECEF Y coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Z_HP = 0x20030008; // High-precision ECEF Z coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_LAT = 0x40030009; // Latitude of the ARP position. +const uint32_t UBLOX_CFG_TMODE_LON = 0x4003000a; // Longitude of the ARP position. +const uint32_t UBLOX_CFG_TMODE_HEIGHT = 0x4003000b; // Height of the ARP position. +const uint32_t UBLOX_CFG_TMODE_LAT_HP = 0x2003000c; // High-precision latitude of the ARP position +const uint32_t UBLOX_CFG_TMODE_LON_HP = 0x2003000d; // High-precision longitude of the ARP position. +const uint32_t UBLOX_CFG_TMODE_HEIGHT_HP = 0x2003000e; // High-precision height of the ARP position. +const uint32_t UBLOX_CFG_TMODE_FIXED_POS_ACC = 0x4003000f; // Fixed position 3D accuracy +const uint32_t UBLOX_CFG_TMODE_SVIN_MIN_DUR = 0x40030010; // Survey-in minimum duration +const uint32_t UBLOX_CFG_TMODE_SVIN_ACC_LIMIT = 0x40030011; // Survey-in position accuracy limit + +//CFG-TP: Timepulse configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_TP_PULSE_DEF = 0x20050023; // Determines whether the time pulse is interpreted as frequency or period +const uint32_t UBLOX_CFG_TP_PULSE_LENGTH_DEF = 0x20050030; // Determines whether the time pulse length is interpreted as length[us] or pulse ratio[%] +const uint32_t UBLOX_CFG_TP_FREQ_TP1 = 0x40050024; // Time pulse frequency (TP1) +const uint32_t UBLOX_CFG_TP_FREQ_LOCK_TP1 = 0x40050025; // Time pulse frequency when locked to GNSS time (TP1) +const uint32_t UBLOX_CFG_TP_LEN_TP1 = 0x40050004; // Time pulse length (TP1) +const uint32_t UBLOX_CFG_TP_LEN_LOCK_TP1 = 0x40050005; // Time pulse length when locked to GNSS time (TP1) +const uint32_t UBLOX_CFG_TP_DUTY_TP1 = 0x5005002a; // Time pulse duty cycle (TP1) +const uint32_t UBLOX_CFG_TP_DUTY_LOCK_TP1 = 0x5005002b; // Time pulse duty cycle when locked to GNSS time (TP1) +const uint32_t UBLOX_CFG_TP_USER_DELAY_TP1 = 0x40050006; // User-configurable time pulse delay (TP1) +const uint32_t UBLOX_CFG_TP_TP1_ENA = 0x10050007; // Enable the first timepulse +const uint32_t UBLOX_CFG_TP_SYNC_GNSS_TP1 = 0x10050008; // Sync time pulse to GNSS time or local clock (TP1) +const uint32_t UBLOX_CFG_TP_USE_LOCKED_TP1 = 0x10050009; // Use locked parameters when possible (TP1) +const uint32_t UBLOX_CFG_TP_ALIGN_TO_TOW_TP1 = 0x1005000a; // Align time pulse to top of second (TP1) +const uint32_t UBLOX_CFG_TP_POL_TP1 = 0x1005000b; // Set time pulse polarity (TP1) +const uint32_t UBLOX_CFG_TP_TIMEGRID_TP1 = 0x2005000c; // Time grid to use (TP1) + +//CFG-TXREADY: TX ready configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_TXREADY_ENABLED = 0x10a20001; // Flag to indicate if TX ready pin mechanism should be enabled +const uint32_t UBLOX_CFG_TXREADY_POLARITY = 0x10a20002; // The polarity of the TX ready pin: false:high- active, true:low-active +const uint32_t UBLOX_CFG_TXREADY_PIN = 0x20a20003; // Pin number to use for the TX ready functionality +const uint32_t UBLOX_CFG_TXREADY_THRESHOLD = 0x30a20004; // Amount of data that should be ready on the interface before triggering the TX ready pin +const uint32_t UBLOX_CFG_TXREADY_INTERFACE = 0x20a20005; // Interface where the TX ready feature should be linked to + +//CFG-UART1: Configuration of the UART1 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART1_BAUDRATE = 0x40520001; // The baud rate that should be configured on the UART1 +const uint32_t UBLOX_CFG_UART1_STOPBITS = 0x20520002; // Number of stopbits that should be used on UART1 +const uint32_t UBLOX_CFG_UART1_DATABITS = 0x20520003; // Number of databits that should be used on UART1 +const uint32_t UBLOX_CFG_UART1_PARITY = 0x20520004; // Parity mode that should be used on UART1 +const uint32_t UBLOX_CFG_UART1_ENABLED = 0x10520005; // Flag to indicate if the UART1 should be enabled + +//CFG-UART1INPROT: Input protocol configuration of the UART1 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART1INPROT_UBX = 0x10730001; // Flag to indicate if UBX should be an input protocol on UART1 +const uint32_t UBLOX_CFG_UART1INPROT_NMEA = 0x10730002; // Flag to indicate if NMEA should be an input protocol on UART1 +const uint32_t UBLOX_CFG_UART1INPROT_RTCM3X = 0x10730004; // Flag to indicate if RTCM3X should be an input protocol on UART1 + +//CFG-UART1OUTPROT: Output protocol configuration of the UART1 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART1OUTPROT_UBX = 0x10740001; // Flag to indicate if UBX should be an output protocol on UART1 +const uint32_t UBLOX_CFG_UART1OUTPROT_NMEA = 0x10740002; // Flag to indicate if NMEA should be an output protocol on UART1 +const uint32_t UBLOX_CFG_UART1OUTPROT_RTCM3X = 0x10740004; // Flag to indicate if RTCM3X should be an output protocol on UART1 + +//CFG-UART2: Configuration of the UART2 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART2_BAUDRATE = 0x40530001; // The baud rate that should be configured on the UART2 +const uint32_t UBLOX_CFG_UART2_STOPBITS = 0x20530002; // Number of stopbits that should be used on UART2 +const uint32_t UBLOX_CFG_UART2_DATABITS = 0x20530003; // Number of databits that should be used on UART2 +const uint32_t UBLOX_CFG_UART2_PARITY = 0x20530004; // Parity mode that should be used on UART2 +const uint32_t UBLOX_CFG_UART2_ENABLED = 0x10530005; // Flag to indicate if the UART2 should be enabled +const uint32_t UBLOX_CFG_UART2_REMAP = 0x10530006; // UART2 Remapping + +//CFG-UART2INPROT: Input protocol configuration of the UART2 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART2INPROT_UBX = 0x10750001; // Flag to indicate if UBX should be an input protocol on UART2 +const uint32_t UBLOX_CFG_UART2INPROT_NMEA = 0x10750002; // Flag to indicate if NMEA should be an input protocol on UART2 +const uint32_t UBLOX_CFG_UART2INPROT_RTCM3X = 0x10750004; // Flag to indicate if RTCM3X should be an input protocol on UART2 + +//CFG-UART2OUTPROT: Output protocol configuration of the UART2 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART2OUTPROT_UBX = 0x10760001; // Flag to indicate if UBX should be an output protocol on UART2 +const uint32_t UBLOX_CFG_UART2OUTPROT_NMEA = 0x10760002; // Flag to indicate if NMEA should be an output protocol on UART2 +const uint32_t UBLOX_CFG_UART2OUTPROT_RTCM3X = 0x10760004; // Flag to indicate if RTCM3X should be an output protocol on UART2 + +//CFG-USB: Configuration of the USB interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_USB_ENABLED = 0x10650001; // Flag to indicate if the USB interface should be enabled +const uint32_t UBLOX_CFG_USB_SELFPOW = 0x10650002; // Self-powered device +const uint32_t UBLOX_CFG_USB_VENDOR_ID = 0x3065000a; // Vendor ID +const uint32_t UBLOX_CFG_USB_PRODUCT_ID = 0x3065000b; // Vendor ID +const uint32_t UBLOX_CFG_USB_POWER = 0x3065000c; // Power consumption +const uint32_t UBLOX_CFG_USB_VENDOR_STR0 = 0x5065000d; // Vendor string characters 0-7 +const uint32_t UBLOX_CFG_USB_VENDOR_STR1 = 0x5065000e; // Vendor string characters 8-15 +const uint32_t UBLOX_CFG_USB_VENDOR_STR2 = 0x5065000f; // Vendor string characters 16-23 +const uint32_t UBLOX_CFG_USB_VENDOR_STR3 = 0x50650010; // Vendor string characters 24-31 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR0 = 0x50650011; // Product string characters 0-7 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR1 = 0x50650012; // Product string characters 8-15 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR2 = 0x50650013; // Product string characters 16-23 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR3 = 0x50650014; // Product string characters 24-31 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR0 = 0x50650015; // Serial number string characters 0-7 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR1 = 0x50650016; // Serial number string characters 8-15 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR2 = 0x50650017; // Serial number string characters 16-23 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR3 = 0x50650018; // Serial number string characters 24-31 + +//CFG-USBINPROT: Input protocol configuration of the USB interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_USBINPROT_UBX = 0x10770001; // Flag to indicate if UBX should be an input protocol on USB +const uint32_t UBLOX_CFG_USBINPROT_NMEA = 0x10770002; // Flag to indicate if NMEA should be an input protocol on USB +const uint32_t UBLOX_CFG_USBINPROT_RTCM3X = 0x10770004; // Flag to indicate if RTCM3X should be an input protocol on USB + +//CFG-USBOUTPROT: Output protocol configuration of the USB interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_USBOUTPROT_UBX = 0x10780001; // Flag to indicate if UBX should be an output protocol on USB +const uint32_t UBLOX_CFG_USBOUTPROT_NMEA = 0x10780002; // Flag to indicate if NMEA should be an output protocol on USB +const uint32_t UBLOX_CFG_USBOUTPROT_RTCM3X = 0x10780004; // Flag to indicate if RTCM3X should be an output protocol on USB + +#endif diff --git a/lib/TinyGSM/.piopm b/lib/TinyGSM/.piopm new file mode 100644 index 0000000..4e95d81 --- /dev/null +++ b/lib/TinyGSM/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "TinyGSM", "version": "0.11.7", "spec": {"owner": "vshymanskyy", "id": 1287, "name": "TinyGSM", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/lib/TinyGSM/LICENSE b/lib/TinyGSM/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/lib/TinyGSM/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/lib/TinyGSM/README.md b/lib/TinyGSM/README.md new file mode 100644 index 0000000..890a6d9 --- /dev/null +++ b/lib/TinyGSM/README.md @@ -0,0 +1,417 @@ +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://vshymanskyy.github.io/StandWithUkraine) + +![TinyGSM logo](https://cdn.rawgit.com/vshymanskyy/TinyGSM/d18e93dc51fe988a0b175aac647185457ef640b5/extras/logo.svg) + +A small Arduino library for GSM modules, that just works. + +[![GitHub version](https://img.shields.io/github/release/vshymanskyy/TinyGSM.svg)](https://github.com/vshymanskyy/TinyGSM/releases/latest) +[![Build status](https://img.shields.io/travis/vshymanskyy/TinyGSM.svg)](https://travis-ci.org/vshymanskyy/TinyGSM) +[![GitHub issues](https://img.shields.io/github/issues/vshymanskyy/TinyGSM.svg)](https://github.com/vshymanskyy/TinyGSM/issues) +[![GitHub wiki](https://img.shields.io/badge/Wiki-available-brightgreen.svg)](https://github.com/vshymanskyy/TinyGSM/wiki) +[![GitHub stars](https://img.shields.io/github/stars/vshymanskyy/TinyGSM.svg)](https://github.com/vshymanskyy/TinyGSM/stargazers) +[![License](https://img.shields.io/badge/license-LGPL3-blue.svg)](https://github.com/vshymanskyy/TinyGSM/blob/master/LICENSE) + +If you like **TinyGSM** - give it a star, or fork it and contribute! +[![GitHub stars](https://img.shields.io/github/stars/vshymanskyy/TinyGSM.svg?style=social&label=Star)](https://github.com/vshymanskyy/TinyGSM/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/vshymanskyy/TinyGSM.svg?style=social&label=Fork)](https://github.com/vshymanskyy/TinyGSM/network) + +You can also join our chat: +[![Gitter](https://img.shields.io/gitter/room/vshymanskyy/TinyGSM.svg)](https://gitter.im/tinygsm) + +- [Supported modems](#supported-modems) + - [Supported boards/modules](#supported-boardsmodules) +- [Features](#features) +- [Getting Started](#getting-started) + - [First Steps](#first-steps) + - [Writing your own code](#writing-your-own-code) + - [If you have any issues](#if-you-have-any-issues) +- [How does it work?](#how-does-it-work) +- [API Reference](#api-reference) +- [Troubleshooting](#troubleshooting) + - [Ensure stable data & power connection](#ensure-stable-data--power-connection) + - [Baud rates](#baud-rates) + - [Broken initial configuration](#broken-initial-configuration) + - [Failed connection or no data received](#failed-connection-or-no-data-received) + - [Diagnostics sketch](#diagnostics-sketch) + - [Web request formatting problems - "but it works with PostMan"](#web-request-formatting-problems---but-it-works-with-postman) + - [SoftwareSerial problems](#softwareserial-problems) + - [ESP32 Notes](#esp32-notes) + - [HardwareSerial](#hardwareserial) + - [HttpClient](#httpclient) + - [SAMD21](#samd21) + - [Goouuu Tech IOT-GA6 vs AI-Thinker A6 confusion](#goouuu-tech-iot-ga6-vs-ai-thinker-a6-confusion) + - [SIM800 and SSL](#sim800-and-ssl) + - [Which version of the SIM7000 code to use](#which-version-of-the-sim7000-code-to-use) +- [License](#license) + +### Arduino Client interface support +This library is easy to integrate with lots of sketches which use Ethernet or WiFi. +**PubSubClient ([MQTT](http://mqtt.org/))**, **[Blynk](http://blynk.cc)**, **HTTP Client** and **File Download** examples are provided. + +![examples](/extras/examples.png) + +### TinyGSM is tiny +The complete WebClient example for Arduino Uno (via Software Serial) takes little resources: +``` +Sketch uses 15022 bytes (46%) of program storage space. Maximum is 32256 bytes. +Global variables use 574 bytes (28%) of dynamic memory, leaving 1474 bytes for local variables. Maximum is 2048 bytes. +``` +Arduino GSM library uses 15868 bytes (49%) of Flash and 1113 bytes (54%) of RAM in a similar scenario. +TinyGSM also pulls data gently from the modem (whenever possible), so it can operate on very little RAM. +**Now, you have more space for your experiments.** + + +## Supported modems + +- SIMCom SIM800 series (SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868) +- SIMCom SIM900 series (SIM900A, SIM900D, SIM908, SIM968) +- SIMCom WCDMA/HSPA/HSPA+ Modules (SIM5360, SIM5320, SIM5300E, SIM5300E/A) +- SIMCom LTE Modules (SIM7100E, SIM7500E, SIM7500A, SIM7600C, SIM7600E) +- SIMCom SIM7000E/A/G CAT-M1/NB-IoT Module +- SIMCom SIM7070/SIM7080/SIM7090 CAT-M1/NB-IoT Module +- AI-Thinker A6, A6C, A7, A20 +- ESP8266/ESP32 (AT commands interface, similar to GSM modems) +- Digi XBee WiFi and Cellular (using XBee command mode) +- Neoway M590 +- u-blox 2G, 3G, 4G, and LTE Cat1 Cellular Modems (many modules including LEON-G100, LISA-U2xx, SARA-G3xx, SARA-U2xx, TOBY-L2xx, LARA-R2xx, MPCI-L2xx) +- u-blox LTE-M/NB-IoT Modems (SARA-R4xx, SARA-N4xx, _but NOT SARA-N2xx_) +- Sequans Monarch LTE Cat M1/NB1 (VZM20Q) +- Quectel BG96 +- Quectel M95 +- Quectel MC60 ***(alpha)*** + +### Supported boards/modules +- Arduino MKR GSM 1400 +- GPRSbee +- Microduino GSM +- Adafruit FONA (Mini Cellular GSM Breakout) +- Adafruit FONA 800/808 Shield +- Industruino GSM +- RAK WisLTE ***(alpha)*** +- ... other modules, based on supported modems. Some boards require [**special configuration**](https://github.com/vshymanskyy/TinyGSM/wiki/Board-configuration). + +More modems may be supported later: +- [ ] Quectel M10, UG95 +- [ ] SIMCom SIM7020 +- [ ] Telit GL865 +- [ ] ZTE MG2639 +- [ ] Hi-Link HLK-RM04 + +Watch this repo for new updates! And of course, contributions are welcome ;) + +## Features + +**Data connections** +- TCP (HTTP, MQTT, Blynk, ...) + - ALL modules support TCP connections + - Most modules support multiple simultaneous connections: + - A6/A7 - 8 + - ESP8266 - 5 + - Neoway M590 - 2 + - Quectel BG96 - 12 + - Quectel M95 - 6 + - Quectel MC60/MC60E - 6 + - Sequans Monarch - 6 + - SIM 800/900 - 5 + - SIM 5360/5320/5300/7100 - 10 + - SIM7000 - 8 possible without SSL, only 2 with + - SIM 7070/7080/7090 - 12 + - SIM 7500/7600/7800 - 10 + - u-blox 2G/3G - 7 + - u-blox SARA R4/N4 - 7 + - Digi XBee - _only 1 connection supported!_ +- UDP + - Not yet supported on any module, though it may be some day +- SSL/TLS (HTTPS) + - Supported on: + - SIM800, SIM7000, u-Blox, XBee _cellular_, ESP8266, and Sequans Monarch + - Note: **only some device models or firmware revisions have this feature** (SIM8xx R14.18, A7, etc.) + - Not yet supported on: + - Quectel modems, SIM 5360/5320/7100, SIM 7500/7600/7800 + - Not possible on: + - SIM900, A6/A7, Neoway M590, XBee _WiFi_ + - Like TCP, most modules support simultaneous connections + - TCP and SSL connections can usually be mixed up to the total number of possible connections + +**USSD** +- Sending USSD requests and decoding 7,8,16-bit responses + - Supported on: + - All SIMCom modems, Quectel modems, most u-blox + - Not possible on: + - XBee, u-blox SARA R4/N4, ESP8266 (obviously) + +**SMS** +- Only _sending_ SMS is supported, not receiving + - Supported on all cellular modules + +**Voice Calls** +- Supported on: + - SIM800/SIM900, SIM7600, A6/A7, Quectel modems, u-blox +- Not yet supported on: + - SIM7000, SIM5360/5320/7100, SIM7500/7800, VZM20Q (Monarch) +- Not possible on: + - XBee (any type), u-blox SARA R4/N4, Neoway M590, ESP8266 (obviously) +- Functions: + - Dial, hangup + - DTMF sending + +**Location** +- GPS/GNSS + - SIM808, SIM7000, SIM7500/7600/7800, BG96, u-blox + - NOTE: u-blox chips do _NOT_ have embedded GPS - this functionality only works if a secondary GPS is connected to primary cellular chip over I2C +- GSM location service + - SIM800, SIM7000, Quectel, u-blox + +**Credits** +- Primary Authors/Contributors: + - [vshymanskyy](https://github.com/vshymanskyy) + - [SRGDamia1](https://github.com/SRGDamia1/) +- SIM7000: + - [captFuture](https://github.com/captFuture/) + - [FStefanni](https://github.com/FStefanni/) +- Sequans Monarch: + - [nootropicdesign](https://github.com/nootropicdesign/) +- Quectel M9C60 + - [V1pr](https://github.com/V1pr) +- Quectel M95 + - [replicadeltd](https://github.com/replicadeltd) +- Other Contributors: + - https://github.com/vshymanskyy/TinyGSM/graphs/contributors + +## Getting Started + +#### First Steps + + 1. Using your phone: + - Disable PIN code on the SIM card + - Check your balance + - Check that APN, User, Pass are correct and you have internet + 2. Ensure the SIM card is correctly inserted into the module + 3. Ensure that GSM antenna is firmly attached + 4. Ensure that you have a stable power supply to the module of at least **2A**. + 5. Check if serial connection is working (Hardware Serial is recommended) + Send an ```AT``` command using [this sketch](tools/AT_Debug/AT_Debug.ino) + 6. Try out the [WebClient](https://github.com/vshymanskyy/TinyGSM/blob/master/examples/WebClient/WebClient.ino) example + +#### Writing your own code + +The general flow of your code should be: +- Define the module that you are using (choose one and only one) + - ie, ```#define TINY_GSM_MODEM_SIM800``` +- Included TinyGSM + - ```#include ``` +- Create a TinyGSM modem instance + - ```TinyGsm modem(SerialAT);``` +- Create one or more TinyGSM client instances + - For a single connection, use + - ```TinyGsmClient client(modem);``` + or + ```TinyGsmClientSecure client(modem);``` (on supported modules) + - For multiple connections (on supported modules) use: + - ```TinyGsmClient clientX(modem, 0);```, ```TinyGsmClient clientY(modem, 1);```, etc + or + - ```TinyGsmClientSecure clientX(modem, 0);```, ```TinyGsmClientSecure clientY(modem, 1);```, etc + - Secure and insecure clients can usually be mixed when using multiple connections. + - The total number of connections possible varies by module +- Begin your serial communication and set all your pins as required to power your module and bring it to full functionality. + - The examples attempt to guess the module's baud rate. In working code, you should use a set baud. +- Wait for the module to be ready (could be as much as 6s, depending on the module) +- Initialize the modem + - ```modem.init()``` or ```modem.restart()``` + - restart generally takes longer than init but ensures the module doesn't have lingering connections +- Unlock your SIM, if necessary: + - ```modem.simUnlock(GSM_PIN)``` +- If using **WiFi**, specify your SSID information: + - ```modem.networkConnect(wifiSSID, wifiPass)``` + - Network registration should be automatic on cellular modules +- Wait for network registration to be successful + - ```modem.waitForNetwork(600000L)``` +- If using cellular, establish the GPRS or EPS data connection _after_ your are successfully registered on the network + - ```modem.gprsConnect(apn, gprsUser, gprsPass)``` (or simply ```modem.gprsConnect(apn)```) + - The same command is used for both GPRS or EPS connection + - If using a **Digi** brand cellular XBee, you must specify your GPRS/EPS connection information _before_ waiting for the network. This is true ONLY for _Digi cellular XBees_! _For all other cellular modules, use the GPRS connect function after network registration._ +- Connect the TCP or SSL client + ```client.connect(server, port)``` +- Send out your data. + + +#### If you have any issues + + 1. Read the whole README (you're looking at it!), particularly the troubleshooting section below. + 2. Some boards require [**special configuration**](https://github.com/vshymanskyy/TinyGSM/wiki/Board-configuration). + 3. Try running the Diagnostics sketch + 4. Check for [**highlighted topics here**](https://github.com/vshymanskyy/TinyGSM/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22for+reference%22+) + 5. If you have a question, please post it in our [Gitter chat](https://gitter.im/tinygsm) + +## How does it work? + +Many GSM modems, WiFi and radio modules can be controlled by sending AT commands over Serial. +TinyGSM knows which commands to send, and how to handle AT responses, and wraps that into standard Arduino Client interface. + +This library is "blocking" in all of its communication. +Depending on the function, your code may be blocked for a long time waiting for the module responses. +Apart from the obvious (ie, `waitForNetwork()`) several other functions may block your code for up to several *minutes*. +The `gprsConnect()` and `client.connect()` functions commonly block the longest, especially in poorer service regions. +The module shutdown and restart may also be quite slow. + +This libary *does not* support any sort of "hardware" or pin level controls for the modules. +If you need to turn your module on or reset it using some sort of High/Low/High pin sequence, you must write those functions yourself. + +## API Reference + +For GPRS data streams, this library provides the standard [Arduino Client](https://www.arduino.cc/en/Reference/ClientConstructor) interface. +For additional functions, please refer to [this example sketch](examples/AllFunctions/AllFunctions.ino) + +## Troubleshooting + +### Ensure stable data & power connection + +Most modules require _**as much as 2A**_ to properly connect to the network. +This is 4x what a "standard" USB will supply! +Improving the power supply actually solves stability problems in **many** cases! +- Read about [**powering your module**](https://github.com/vshymanskyy/TinyGSM/wiki/Powering-GSM-module). +- Keep your wires as short as possible +- Consider soldering them for a stable connection +- Do not put your wires next to noisy signal sources (buck converters, antennas, oscillators etc.) +- If everything else seems to be working but you are unable to connect to the network, check your power supply! + +### Baud rates + +Most modules support some sort of "auto-bauding" feature where the module will attempt to adjust it's baud rate to match what it is receiving. +TinyGSM also implements its own auto bauding function (`TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX);`). +While very useful when initially connecting to a module and doing tests, these should **NOT** be used in any sort of production code. +Once you've established communication with the module, set the baud rate using the `setBaud(#)` function and stick with that rate. + +### Broken initial configuration + +Sometimes (especially if you played with AT commands), your module configuration may become invalid. +This may result in problems such as: + + * Can't connect to the GPRS network + * Can't connect to the server + * Sent/received data contains invalid bytes + * etc. + +To return module to **Factory Defaults**, use this sketch: + File -> Examples -> TinyGSM -> tools -> [FactoryReset](https://github.com/vshymanskyy/TinyGSM/blob/master/tools/FactoryReset/FactoryReset.ino) + +In some cases, you may need to set an initial APN to connect to the cellular network. +Try using the ```gprsConnect(APN)``` function to set an initial APN if you are unable to register on the network. +You may need set the APN again after registering. +(In most cases, you should set the APN after registration.) + +### Failed connection or no data received + +The first connection with a new SIM card, a new module, or at a new location/tower may take a *LONG* time - up to 15 minutes or even more, especially if the signal quality isn't excellent. +If it is your first connection, you may need to adjust your wait times and possibly go to lunch while you're waiting. + +If you are able to open a TCP connection but have the connection close before receiving data, try adding a keep-alive header to your request. +Some modules (ie, the SIM7000 in SSL mode) will immediately throw away any un-read data when the remote server closes the connection - sometimes without even giving a notification that data arrived in the first place. +When using MQTT, to keep a continuous connection you may need to reduce your keep-alive interval (PINGREQ/PINGRESP). + +### Diagnostics sketch + +Use this sketch to help diagnose SIM card and GPRS connection issues: + File -> Examples -> TinyGSM -> tools -> [Diagnostics](https://github.com/vshymanskyy/TinyGSM/blob/master/tools/Diagnostics/Diagnostics.ino) + +If the diagnostics fail, uncomment this line to output some debugging comments from the library: +```cpp +#define TINY_GSM_DEBUG SerialMon +``` +In any custom code, ```TINY_GSM_DEBUG``` must be defined before including the TinyGSM library. + +If you are unable to see any obvious errors in the library debugging, use [StreamDebugger](https://github.com/vshymanskyy/StreamDebugger) to copy the entire AT command sequence to the main serial port. +In the diagnostics example, simply uncomment the line: +```cpp +#define DUMP_AT_COMMANDS +``` +In custom code, you can add this snippit: +```cpp +#ifdef DUMP_AT_COMMANDS + #include + StreamDebugger debugger(SerialAT, SerialMon); + TinyGsm modem(debugger); +#else + TinyGsm modem(SerialAT); +#endif +``` + +### Web request formatting problems - "but it works with PostMan" + +This library opens a TCP (or SSL) connection to a server. +In the [OSI model](https://en.wikipedia.org/wiki/OSI_model), that's [layer 4](http://www.tcpipguide.com/free/t_TransportLayerLayer4.htm) (or 5 for SSL). +HTTP (GET/POST), MQTT, and most of the other functions you probably want to use live up at [layer 7](http://www.tcpipguide.com/free/t_ApplicationLayerLayer7.htm). +This means that you need to either manually code the top layer or use another library (like [HTTPClient](https://github.com/arduino-libraries/ArduinoHttpClient) or [PubSubClient](https://pubsubclient.knolleary.net/)) to do it for you. +Tools like [PostMan](https://www.postman.com/) also show layer 7, not layer 4/5 like TinyGSM. +If you are successfully connecting to a server, but getting responses of "bad request" (or no response), the issue is probably your formatting. +Here are some tips for writing layer 7 (particularly HTTP request) manually: +- Look at the "WebClient" example +- Make sure you are including all required headers. + - If you are testing with PostMan, make sure you un-hide and look at the "auto-generated" headers; you'll probably be surprised by how many of them there are. +- Use ```client.print("...")```, or ```client.write(buf, #)```, or even ```client.write(String("..."))```, not ```client.write("...")``` to help prevent text being sent out one character at a time (typewriter style) +- Enclose the entirety of each header or line within a single string or print statement + - use + ```cpp + client.print(String("GET ") + resource + " HTTP/1.1\r\n"); + ``` + instead of + ```cpp + client.print("GET "); + client.print(resource); + client.println(" HTTP/1.1") + ``` +- Make sure there is one entirely blank line between the last header and the content of any POST request. + - Add two lines to the last header ```client.print("....\r\n\r\n")``` or put in an extra ```client.println()``` + - This is an HTTP requirement and is really easy to miss. + +### SoftwareSerial problems + +When using ```SoftwareSerial``` (on Uno, Nano, etc), the speed **115200** may not work. +Try selecting **57600**, **38400**, or even lower - the one that works best for you. +In some cases **9600** is unstable, but using **38400** helps, etc. +Be sure to set correct TX/RX pins in the sketch. Please note that not every Arduino pin can serve as TX or RX pin. +**Read more about SoftSerial options and configuration [here](https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html) and [here](https://www.arduino.cc/en/Reference/SoftwareSerial).** + +### ESP32 Notes + +#### HardwareSerial + +When using ESP32 `HardwareSerial`, you may need to specify additional parameters to the `.begin()` call. +Please [refer to this comment](https://github.com/vshymanskyy/TinyGSM/issues/91#issuecomment-356024747). + +#### HttpClient +You will not be able to compile the HttpClient or HttpsClient examples with ESP32 core 1.0.2. Upgrade to 1.0.3, downgrade to version 1.0.1 or use the WebClient example. + +### SAMD21 + +When using SAMD21-based boards, you may need to use a sercom uart port instead of `Serial1`. +Please [refer to this comment](https://github.com/vshymanskyy/TinyGSM/issues/102#issuecomment-345548941). + +### Goouuu Tech IOT-GA6 vs AI-Thinker A6 confusion + +It turns out that **Goouuu Tech IOT-GA6** is not the same as **AI-Thinker A6**. Unfortunately IOT-GA6 is not supported out of the box yet. There are some hints that IOT-GA6 firmware may be updated to match A6... See [this topic](https://github.com/vshymanskyy/TinyGSM/issues/164). + +### SIM800 and SSL + +Some, but not all, versions of the SIM800 support SSL. +Having SSL support depends on the firmware version and the individual module. +Users have had varying levels of success in using SSL on the SIM800 even with apparently identical firmware. +If you need SSL and it does not appear to be working on your SIM800, try a different module or try using a secondary SSL library. + +### Which version of the SIM7000 code to use + +There are two versions of the SIM7000 code, one using `TINY_GSM_MODEM_SIM7000` and another with `TINY_GSM_MODEM_SIM7000SSL`. +The `TINY_GSM_MODEM_SIM7000` version *does not support SSL* but supports up to 8 simultaneous connections. +The `TINY_GSM_MODEM_SIM7000SSL` version supports both SSL *and unsecured connections* with up to 2 simultaneous connections. +So why are there two versions? +The "SSL" version uses the SIM7000's "application" commands while the other uses the "TCP-IP toolkit". +Depending on your region/firmware, one or the other may not work for you. +Try both and use whichever is more stable. +If you do not need SSL, I recommend starting with `TINY_GSM_MODEM_SIM7000`. + +__________ + +## License +This project is released under +The GNU Lesser General Public License (LGPL-3.0) diff --git a/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino b/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino new file mode 100644 index 0000000..b3e6ff6 --- /dev/null +++ b/lib/TinyGSM/examples/AllFunctions/AllFunctions.ino @@ -0,0 +1,488 @@ +/************************************************************** + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + * NOTE: + * Some of the functions may be unavailable for your modem. + * Just comment them out. + * + **************************************************************/ + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM7000 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_SIM5360 +// #define TINY_GSM_MODEM_SIM7600 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_M95 +// #define TINY_GSM_MODEM_BG96 +// #define TINY_GSM_MODEM_A6 +// #define TINY_GSM_MODEM_A7 +// #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Set serial for AT commands (to the module) +// Use Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + +// See all AT commands, if wanted +// #define DUMP_AT_COMMANDS + +// Define the serial console for debug prints, if needed +#define TINY_GSM_DEBUG SerialMon + +// Range to attempt to autobaud +// NOTE: DO NOT AUTOBAUD in production code. Once you've established +// communication, set a fixed baud rate using modem.setBaud(#). +#define GSM_AUTOBAUD_MIN 9600 +#define GSM_AUTOBAUD_MAX 57600 + +// Add a reception delay, if needed. +// This may be needed for a fast processor at a slow baud rate. +// #define TINY_GSM_YIELD() { delay(2); } + +/* + * Tests enabled + */ +#define TINY_GSM_TEST_GPRS true +#define TINY_GSM_TEST_WIFI false +#define TINY_GSM_TEST_TCP true +#define TINY_GSM_TEST_SSL true +#define TINY_GSM_TEST_CALL false +#define TINY_GSM_TEST_SMS false +#define TINY_GSM_TEST_USSD false +#define TINY_GSM_TEST_BATTERY true +#define TINY_GSM_TEST_TEMPERATURE true +#define TINY_GSM_TEST_GSM_LOCATION false +#define TINY_GSM_TEST_NTP false +#define TINY_GSM_TEST_TIME false +#define TINY_GSM_TEST_GPS false +// disconnect and power down modem after tests +#define TINY_GSM_POWERDOWN false + +// set GSM PIN, if any +#define GSM_PIN "" + +// Set phone numbers, if you want to test SMS and Calls +// #define SMS_TARGET "+380xxxxxxxxx" +// #define CALL_TARGET "+380xxxxxxxxx" + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +// const char apn[] = "ibasis.iot"; +const char gprsUser[] = ""; +const char gprsPass[] = ""; + +// Your WiFi connection credentials, if applicable +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "YourWiFiPass"; + +// Server details to test TCP/SSL +const char server[] = "vsh.pp.ua"; +const char resource[] = "/TinyGSM/logo.txt"; + +#include + +#if TINY_GSM_TEST_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS +#undef TINY_GSM_TEST_GPRS +#undef TINY_GSM_TEST_WIFI +#define TINY_GSM_TEST_GPRS false +#define TINY_GSM_TEST_WIFI true +#endif +#if TINY_GSM_TEST_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false +#endif + +#ifdef DUMP_AT_COMMANDS +#include +StreamDebugger debugger(SerialAT, SerialMon); +TinyGsm modem(debugger); +#else +TinyGsm modem(SerialAT); +#endif + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // !!!!!!!!!!! + // Set your reset, enable, power pins here + // !!!!!!!!!!! + + DBG("Wait..."); + delay(6000); + + // Set GSM module baud rate + TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); + // SerialAT.begin(9600); +} + +void loop() { + // Restart takes quite some time + // To skip it, call init() instead of restart() + DBG("Initializing modem..."); + if (!modem.restart()) { + // if (!modem.init()) { + DBG("Failed to restart modem, delaying 10s and retrying"); + // restart autobaud in case GSM just rebooted + // TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); + return; + } + + String name = modem.getModemName(); + DBG("Modem Name:", name); + + String modemInfo = modem.getModemInfo(); + DBG("Modem Info:", modemInfo); + +#if TINY_GSM_TEST_GPRS + // Unlock your SIM card with a PIN if needed + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } +#endif + +#if TINY_GSM_TEST_WIFI + DBG("Setting SSID/password..."); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + DBG(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); +#endif + +#if TINY_GSM_TEST_GPRS && defined TINY_GSM_MODEM_XBEE + // The XBee must run the gprsConnect function BEFORE waiting for network! + modem.gprsConnect(apn, gprsUser, gprsPass); +#endif + + DBG("Waiting for network..."); + if (!modem.waitForNetwork(600000L, true)) { + delay(10000); + return; + } + + if (modem.isNetworkConnected()) { DBG("Network connected"); } + +#if TINY_GSM_TEST_GPRS + DBG("Connecting to", apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + delay(10000); + return; + } + + bool res = modem.isGprsConnected(); + DBG("GPRS status:", res ? "connected" : "not connected"); + + String ccid = modem.getSimCCID(); + DBG("CCID:", ccid); + + String imei = modem.getIMEI(); + DBG("IMEI:", imei); + + String imsi = modem.getIMSI(); + DBG("IMSI:", imsi); + + String cop = modem.getOperator(); + DBG("Operator:", cop); + + IPAddress local = modem.localIP(); + DBG("Local IP:", local); + + int csq = modem.getSignalQuality(); + DBG("Signal quality:", csq); +#endif + +#if TINY_GSM_TEST_USSD && defined TINY_GSM_MODEM_HAS_SMS + String ussd_balance = modem.sendUSSD("*111#"); + DBG("Balance (USSD):", ussd_balance); + + String ussd_phone_num = modem.sendUSSD("*161#"); + DBG("Phone number (USSD):", ussd_phone_num); +#endif + +#if TINY_GSM_TEST_TCP && defined TINY_GSM_MODEM_HAS_TCP + TinyGsmClient client(modem, 0); + const int port = 80; + DBG("Connecting to", server); + if (!client.connect(server, port)) { + DBG("... failed"); + } else { + // Make a HTTP GET request: + client.print(String("GET ") + resource + " HTTP/1.0\r\n"); + client.print(String("Host: ") + server + "\r\n"); + client.print("Connection: close\r\n\r\n"); + + // Wait for data to arrive + uint32_t start = millis(); + while (client.connected() && !client.available() && + millis() - start < 30000L) { + delay(100); + }; + + // Read data + start = millis(); + char logo[640] = { + '\0', + }; + int read_chars = 0; + while (client.connected() && millis() - start < 10000L) { + while (client.available()) { + logo[read_chars] = client.read(); + logo[read_chars + 1] = '\0'; + read_chars++; + start = millis(); + } + } + SerialMon.println(logo); + DBG("##### RECEIVED:", strlen(logo), "CHARACTERS"); + client.stop(); + } +#endif + +#if TINY_GSM_TEST_SSL && defined TINY_GSM_MODEM_HAS_SSL + TinyGsmClientSecure secureClient(modem, 1); + const int securePort = 443; + DBG("Connecting securely to", server); + if (!secureClient.connect(server, securePort)) { + DBG("... failed"); + } else { + // Make a HTTP GET request: + secureClient.print(String("GET ") + resource + " HTTP/1.0\r\n"); + secureClient.print(String("Host: ") + server + "\r\n"); + secureClient.print("Connection: close\r\n\r\n"); + + // Wait for data to arrive + uint32_t startS = millis(); + while (secureClient.connected() && !secureClient.available() && + millis() - startS < 30000L) { + delay(100); + }; + + // Read data + startS = millis(); + char logoS[640] = { + '\0', + }; + int read_charsS = 0; + while (secureClient.connected() && millis() - startS < 10000L) { + while (secureClient.available()) { + logoS[read_charsS] = secureClient.read(); + logoS[read_charsS + 1] = '\0'; + read_charsS++; + startS = millis(); + } + } + SerialMon.println(logoS); + DBG("##### RECEIVED:", strlen(logoS), "CHARACTERS"); + secureClient.stop(); + } +#endif + +#if TINY_GSM_TEST_CALL && defined TINY_GSM_MODEM_HAS_CALLING && \ + defined CALL_TARGET + DBG("Calling:", CALL_TARGET); + + // This is NOT supported on M590 + res = modem.callNumber(CALL_TARGET); + DBG("Call:", res ? "OK" : "fail"); + + if (res) { + delay(1000L); + + // Play DTMF A, duration 1000ms + modem.dtmfSend('A', 1000); + + // Play DTMF 0..4, default duration (100ms) + for (char tone = '0'; tone <= '4'; tone++) { modem.dtmfSend(tone); } + + delay(5000); + + res = modem.callHangup(); + DBG("Hang up:", res ? "OK" : "fail"); + } +#endif + +#if TINY_GSM_TEST_SMS && defined TINY_GSM_MODEM_HAS_SMS && defined SMS_TARGET + res = modem.sendSMS(SMS_TARGET, String("Hello from ") + imei); + DBG("SMS:", res ? "OK" : "fail"); + + // This is only supported on SIMxxx series + res = modem.sendSMS_UTF8_begin(SMS_TARGET); + if (res) { + auto stream = modem.sendSMS_UTF8_stream(); + stream.print(F("Привіііт! Print number: ")); + stream.print(595); + res = modem.sendSMS_UTF8_end(); + } + DBG("UTF8 SMS:", res ? "OK" : "fail"); + +#endif + +#if TINY_GSM_TEST_GSM_LOCATION && defined TINY_GSM_MODEM_HAS_GSM_LOCATION + float lat = 0; + float lon = 0; + float accuracy = 0; + int year = 0; + int month = 0; + int day = 0; + int hour = 0; + int min = 0; + int sec = 0; + for (int8_t i = 15; i; i--) { + DBG("Requesting current GSM location"); + if (modem.getGsmLocation(&lat, &lon, &accuracy, &year, &month, &day, &hour, + &min, &sec)) { + DBG("Latitude:", String(lat, 8), "\tLongitude:", String(lon, 8)); + DBG("Accuracy:", accuracy); + DBG("Year:", year, "\tMonth:", month, "\tDay:", day); + DBG("Hour:", hour, "\tMinute:", min, "\tSecond:", sec); + break; + } else { + DBG("Couldn't get GSM location, retrying in 15s."); + delay(15000L); + } + } + DBG("Retrieving GSM location again as a string"); + String location = modem.getGsmLocation(); + DBG("GSM Based Location String:", location); +#endif + +#if TINY_GSM_TEST_GPS && defined TINY_GSM_MODEM_HAS_GPS + DBG("Enabling GPS/GNSS/GLONASS and waiting 15s for warm-up"); + modem.enableGPS(); + delay(15000L); + float lat2 = 0; + float lon2 = 0; + float speed2 = 0; + float alt2 = 0; + int vsat2 = 0; + int usat2 = 0; + float accuracy2 = 0; + int year2 = 0; + int month2 = 0; + int day2 = 0; + int hour2 = 0; + int min2 = 0; + int sec2 = 0; + for (int8_t i = 15; i; i--) { + DBG("Requesting current GPS/GNSS/GLONASS location"); + if (modem.getGPS(&lat2, &lon2, &speed2, &alt2, &vsat2, &usat2, &accuracy2, + &year2, &month2, &day2, &hour2, &min2, &sec2)) { + DBG("Latitude:", String(lat2, 8), "\tLongitude:", String(lon2, 8)); + DBG("Speed:", speed2, "\tAltitude:", alt2); + DBG("Visible Satellites:", vsat2, "\tUsed Satellites:", usat2); + DBG("Accuracy:", accuracy2); + DBG("Year:", year2, "\tMonth:", month2, "\tDay:", day2); + DBG("Hour:", hour2, "\tMinute:", min2, "\tSecond:", sec2); + break; + } else { + DBG("Couldn't get GPS/GNSS/GLONASS location, retrying in 15s."); + delay(15000L); + } + } + DBG("Retrieving GPS/GNSS/GLONASS location again as a string"); + String gps_raw = modem.getGPSraw(); + DBG("GPS/GNSS Based Location String:", gps_raw); + DBG("Disabling GPS"); + modem.disableGPS(); +#endif + +#if TINY_GSM_TEST_NTP && defined TINY_GSM_MODEM_HAS_NTP + DBG("Asking modem to sync with NTP"); + modem.NTPServerSync("132.163.96.5", 20); +#endif + +#if TINY_GSM_TEST_TIME && defined TINY_GSM_MODEM_HAS_TIME + int year3 = 0; + int month3 = 0; + int day3 = 0; + int hour3 = 0; + int min3 = 0; + int sec3 = 0; + float timezone = 0; + for (int8_t i = 5; i; i--) { + DBG("Requesting current network time"); + if (modem.getNetworkTime(&year3, &month3, &day3, &hour3, &min3, &sec3, + &timezone)) { + DBG("Year:", year3, "\tMonth:", month3, "\tDay:", day3); + DBG("Hour:", hour3, "\tMinute:", min3, "\tSecond:", sec3); + DBG("Timezone:", timezone); + break; + } else { + DBG("Couldn't get network time, retrying in 15s."); + delay(15000L); + } + } + DBG("Retrieving time again as a string"); + String time = modem.getGSMDateTime(DATE_FULL); + DBG("Current Network Time:", time); +#endif + +#if TINY_GSM_TEST_BATTERY && defined TINY_GSM_MODEM_HAS_BATTERY + uint8_t chargeState = -99; + int8_t percent = -99; + uint16_t milliVolts = -9999; + modem.getBattStats(chargeState, percent, milliVolts); + DBG("Battery charge state:", chargeState); + DBG("Battery charge 'percent':", percent); + DBG("Battery voltage:", milliVolts / 1000.0F); +#endif + +#if TINY_GSM_TEST_TEMPERATURE && defined TINY_GSM_MODEM_HAS_TEMPERATURE + float temp = modem.getTemperature(); + DBG("Chip temperature:", temp); +#endif + +#if TINY_GSM_POWERDOWN + +#if TINY_GSM_TEST_GPRS + modem.gprsDisconnect(); + delay(5000L); + if (!modem.isGprsConnected()) { + DBG("GPRS disconnected"); + } else { + DBG("GPRS disconnect: Failed."); + } +#endif + +#if TINY_GSM_TEST_WIFI + modem.networkDisconnect(); + DBG("WiFi disconnected"); +#endif + + // Try to power-off (modem may decide to restart automatically) + // To turn off modem completely, please use Reset/Enable pins + modem.poweroff(); + DBG("Poweroff."); +#endif + + DBG("End of tests."); + + // Do nothing forevermore + while (true) { modem.maintain(); } +} diff --git a/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino b/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino new file mode 100644 index 0000000..bd4885d --- /dev/null +++ b/lib/TinyGSM/examples/BlynkClient/BlynkClient.ino @@ -0,0 +1,110 @@ +/************************************************************** + * + * For this example, you need to install Blynk library: + * https://github.com/blynkkk/blynk-library/releases/latest + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + ************************************************************** + * + * Blynk is a platform with iOS and Android apps to control + * Arduino, Raspberry Pi and the likes over the Internet. + * You can easily build graphic interfaces for all your + * projects by simply dragging and dropping widgets. + * + * Blynk supports many development boards with WiFi, Ethernet, + * GSM, Bluetooth, BLE, USB/Serial connection methods. + * See more in Blynk library examples and community forum. + * + * http://www.blynk.io/ + * + * Change GPRS apm, user, pass, and Blynk auth token to run :) + **************************************************************/ + +#define BLYNK_PRINT Serial // Comment this out to disable prints and save space + +// Default heartbeat interval for GSM is 60 +// If you want override this value, uncomment and set this option: +// #define BLYNK_HEARTBEAT 30 + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM7000 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_SIM5360 +// #define TINY_GSM_MODEM_SIM7600 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_M95 +// #define TINY_GSM_MODEM_BG96 +// #define TINY_GSM_MODEM_A6 +// #define TINY_GSM_MODEM_A7 +// #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +#include +#include + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +const char user[] = ""; +const char pass[] = ""; + +// You should get Auth Token in the Blynk App. +// Go to the Project Settings (nut icon). +const char auth[] = "YourAuthToken"; + +TinyGsm modem(SerialAT); + +void setup() +{ + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // Set GSM module baud rate + SerialAT.begin(115200); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println("Initializing modem..."); + modem.restart(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print("Modem Info: "); + SerialMon.println(modemInfo); + + // Unlock your SIM card with a PIN + //modem.simUnlock("1234"); + + Blynk.begin(auth, modem, apn, user, pass); +} + +void loop() +{ + Blynk.run(); +} diff --git a/lib/TinyGSM/examples/FileDownload/FileDownload.ino b/lib/TinyGSM/examples/FileDownload/FileDownload.ino new file mode 100644 index 0000000..d66e4b7 --- /dev/null +++ b/lib/TinyGSM/examples/FileDownload/FileDownload.ino @@ -0,0 +1,356 @@ +/************************************************************** + * + * For this example, you need to install CRC32 library: + * https://github.com/bakercp/CRC32 + * or from http://librarymanager/all#CRC32+checksum + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + * ATTENTION! Downloading big files requires of knowledge of + * the TinyGSM internals and some modem specifics, + * so this is for more experienced developers. + * + **************************************************************/ + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM7000 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_SIM5360 +// #define TINY_GSM_MODEM_SIM7600 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_M95 +// #define TINY_GSM_MODEM_BG96 +// #define TINY_GSM_MODEM_A6 +// #define TINY_GSM_MODEM_A7 +// #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Set serial for AT commands (to the module) +// Use Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + +// Increase RX buffer to capture the entire response +// Chips without internal buffering (A6/A7, ESP8266, M590) +// need enough space in the buffer for the entire response +// else data will be lost (and the http library will fail). +#if !defined(TINY_GSM_RX_BUFFER) +#define TINY_GSM_RX_BUFFER 1024 +#endif + +// See all AT commands, if wanted +// #define DUMP_AT_COMMANDS + +// Define the serial console for debug prints, if needed +#define TINY_GSM_DEBUG SerialMon +// #define LOGGING // <- Logging is for the HTTP library + +// Add a reception delay, if needed. +// This may be needed for a fast processor at a slow baud rate. +// #define TINY_GSM_YIELD() { delay(2); } + +// Define how you're planning to connect to the internet. +// This is only needed for this example, not in other code. +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false + +// set GSM PIN, if any +#define GSM_PIN "" + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +const char gprsUser[] = ""; +const char gprsPass[] = ""; + +// Your WiFi connection credentials, if applicable +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "YourWiFiPass"; + +// Server details +const char server[] = "vsh.pp.ua"; +const int port = 80; + +#include +#include + +// Just in case someone defined the wrong thing.. +#if TINY_GSM_USE_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS false +#define TINY_GSM_USE_WIFI true +#endif +#if TINY_GSM_USE_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false +#endif + +const char resource[] = "/TinyGSM/test_1k.bin"; +uint32_t knownCRC32 = 0x6f50d767; +uint32_t knownFileSize = 1024; // In case server does not send it + +#ifdef DUMP_AT_COMMANDS +#include +StreamDebugger debugger(SerialAT, SerialMon); +TinyGsm modem(debugger); +#else +TinyGsm modem(SerialAT); +#endif + +TinyGsmClient client(modem); + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // !!!!!!!!!!! + // Set your reset, enable, power pins here + // !!!!!!!!!!! + + SerialMon.println("Wait..."); + + // Set GSM module baud rate + SerialAT.begin(115200); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println("Initializing modem..."); + modem.restart(); + // modem.init(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print("Modem Info: "); + SerialMon.println(modemInfo); + +#if TINY_GSM_USE_GPRS + // Unlock your SIM card with a PIN if needed + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } +#endif +} + +void printPercent(uint32_t readLength, uint32_t contentLength) { + // If we know the total length + if (contentLength != (uint32_t)-1) { + SerialMon.print("\r "); + SerialMon.print((100.0 * readLength) / contentLength); + SerialMon.print('%'); + } else { + SerialMon.println(readLength); + } +} + +void loop() { +#if TINY_GSM_USE_WIFI + // Wifi connection parameters must be set before waiting for the network + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); +#endif + +#if TINY_GSM_USE_GPRS && defined TINY_GSM_MODEM_XBEE + // The XBee must run the gprsConnect function BEFORE waiting for network! + modem.gprsConnect(apn, gprsUser, gprsPass); +#endif + + SerialMon.print("Waiting for network..."); + if (!modem.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isNetworkConnected()) { SerialMon.println("Network connected"); } + +#if TINY_GSM_USE_GPRS + // GPRS connection parameters are usually set after network registration + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isGprsConnected()) { SerialMon.println("GPRS connected"); } +#endif + + SerialMon.print(F("Connecting to ")); + SerialMon.print(server); + if (!client.connect(server, port)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + // Make a HTTP GET request: + client.print(String("GET ") + resource + " HTTP/1.0\r\n"); + client.print(String("Host: ") + server + "\r\n"); + client.print("Connection: close\r\n\r\n"); + + // Let's see what the entire elapsed time is, from after we send the request. + uint32_t timeElapsed = millis(); + + SerialMon.println(F("Waiting for response header")); + + // While we are still looking for the end of the header (i.e. empty line + // FOLLOWED by a newline), continue to read data into the buffer, parsing each + // line (data FOLLOWED by a newline). If it takes too long to get data from + // the client, we need to exit. + + const uint32_t clientReadTimeout = 5000; + uint32_t clientReadStartTime = millis(); + String headerBuffer; + bool finishedHeader = false; + uint32_t contentLength = 0; + + while (!finishedHeader) { + int nlPos; + + if (client.available()) { + clientReadStartTime = millis(); + while (client.available()) { + char c = client.read(); + headerBuffer += c; + + // Uncomment the lines below to see the data coming into the buffer + // if (c < 16) + // SerialMon.print('0'); + // SerialMon.print(c, HEX); + // SerialMon.print(' '); + // if (isprint(c)) + // SerialMon.print(reinterpret_cast c); + // else + // SerialMon.print('*'); + // SerialMon.print(' '); + + // Let's exit and process if we find a new line + if (headerBuffer.indexOf(F("\r\n")) >= 0) break; + } + } else { + if (millis() - clientReadStartTime > clientReadTimeout) { + // Time-out waiting for data from client + SerialMon.println(F(">>> Client Timeout !")); + break; + } + } + + // See if we have a new line. + nlPos = headerBuffer.indexOf(F("\r\n")); + + if (nlPos > 0) { + headerBuffer.toLowerCase(); + // Check if line contains content-length + if (headerBuffer.startsWith(F("content-length:"))) { + contentLength = + headerBuffer.substring(headerBuffer.indexOf(':') + 1).toInt(); + // SerialMon.print(F("Got Content Length: ")); // uncomment for + // SerialMon.println(contentLength); // confirmation + } + + headerBuffer.remove(0, nlPos + 2); // remove the line + } else if (nlPos == 0) { + // if the new line is empty (i.e. "\r\n" is at the beginning of the line), + // we are done with the header. + finishedHeader = true; + } + } + + // The two cases which are not managed properly are as follows: + // 1. The client doesn't provide data quickly enough to keep up with this + // loop. + // 2. If the client data is segmented in the middle of the 'Content-Length: ' + // header, + // then that header may be missed/damaged. + // + + uint32_t readLength = 0; + CRC32 crc; + + if (finishedHeader && contentLength == knownFileSize) { + SerialMon.println(F("Reading response data")); + clientReadStartTime = millis(); + + printPercent(readLength, contentLength); + while (readLength < contentLength && client.connected() && + millis() - clientReadStartTime < clientReadTimeout) { + while (client.available()) { + uint8_t c = client.read(); + // SerialMon.print(reinterpret_castc); // Uncomment this to show + // data + crc.update(c); + readLength++; + if (readLength % (contentLength / 13) == 0) { + printPercent(readLength, contentLength); + } + clientReadStartTime = millis(); + } + } + printPercent(readLength, contentLength); + } + + timeElapsed = millis() - timeElapsed; + SerialMon.println(); + + // Shutdown + + client.stop(); + SerialMon.println(F("Server disconnected")); + +#if TINY_GSM_USE_WIFI + modem.networkDisconnect(); + SerialMon.println(F("WiFi disconnected")); +#endif +#if TINY_GSM_USE_GPRS + modem.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); +#endif + + float duration = float(timeElapsed) / 1000; + + SerialMon.println(); + SerialMon.print("Content-Length: "); + SerialMon.println(contentLength); + SerialMon.print("Actually read: "); + SerialMon.println(readLength); + SerialMon.print("Calc. CRC32: 0x"); + SerialMon.println(crc.finalize(), HEX); + SerialMon.print("Known CRC32: 0x"); + SerialMon.println(knownCRC32, HEX); + SerialMon.print("Duration: "); + SerialMon.print(duration); + SerialMon.println("s"); + + // Do nothing forevermore + while (true) { delay(1000); } +} diff --git a/lib/TinyGSM/examples/HttpClient/HttpClient.ino b/lib/TinyGSM/examples/HttpClient/HttpClient.ino new file mode 100644 index 0000000..3e4659b --- /dev/null +++ b/lib/TinyGSM/examples/HttpClient/HttpClient.ino @@ -0,0 +1,261 @@ +/************************************************************** + * + * This sketch connects to a website and downloads a page. + * It can be used to perform HTTP/RESTful API calls. + * + * For this example, you need to install ArduinoHttpClient library: + * https://github.com/arduino-libraries/ArduinoHttpClient + * or from http://librarymanager/all#ArduinoHttpClient + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + * For more HTTP API examples, see ArduinoHttpClient library + * + * NOTE: This example may NOT work with the XBee because the + * HttpClient library does not empty to serial buffer fast enough + * and the buffer overflow causes the HttpClient library to stall. + * Boards with faster processors may work, 8MHz boards will not. + **************************************************************/ + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM7000 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_SIM5360 +// #define TINY_GSM_MODEM_SIM7600 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_M95 +// #define TINY_GSM_MODEM_BG96 +// #define TINY_GSM_MODEM_A6 +// #define TINY_GSM_MODEM_A7 +// #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Set serial for AT commands (to the module) +// Use Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + +// Increase RX buffer to capture the entire response +// Chips without internal buffering (A6/A7, ESP8266, M590) +// need enough space in the buffer for the entire response +// else data will be lost (and the http library will fail). +#if !defined(TINY_GSM_RX_BUFFER) +#define TINY_GSM_RX_BUFFER 650 +#endif + +// See all AT commands, if wanted +// #define DUMP_AT_COMMANDS + +// Define the serial console for debug prints, if needed +#define TINY_GSM_DEBUG SerialMon +// #define LOGGING // <- Logging is for the HTTP library + +// Range to attempt to autobaud +// NOTE: DO NOT AUTOBAUD in production code. Once you've established +// communication, set a fixed baud rate using modem.setBaud(#). +#define GSM_AUTOBAUD_MIN 9600 +#define GSM_AUTOBAUD_MAX 115200 + +// Add a reception delay, if needed. +// This may be needed for a fast processor at a slow baud rate. +// #define TINY_GSM_YIELD() { delay(2); } + +// Define how you're planning to connect to the internet +// These defines are only for this example; they are not needed in other code. +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false + +// set GSM PIN, if any +#define GSM_PIN "" + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +const char gprsUser[] = ""; +const char gprsPass[] = ""; + +// Your WiFi connection credentials, if applicable +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "YourWiFiPass"; + +// Server details +const char server[] = "vsh.pp.ua"; +const char resource[] = "/TinyGSM/logo.txt"; +const int port = 80; + +#include +#include + +// Just in case someone defined the wrong thing.. +#if TINY_GSM_USE_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS false +#define TINY_GSM_USE_WIFI true +#endif +#if TINY_GSM_USE_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false +#endif + +#ifdef DUMP_AT_COMMANDS +#include +StreamDebugger debugger(SerialAT, SerialMon); +TinyGsm modem(debugger); +#else +TinyGsm modem(SerialAT); +#endif + +TinyGsmClient client(modem); +HttpClient http(client, server, port); + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // !!!!!!!!!!! + // Set your reset, enable, power pins here + // !!!!!!!!!!! + + SerialMon.println("Wait..."); + + // Set GSM module baud rate + TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); + // SerialAT.begin(9600); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println("Initializing modem..."); + modem.restart(); + // modem.init(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print("Modem Info: "); + SerialMon.println(modemInfo); + +#if TINY_GSM_USE_GPRS + // Unlock your SIM card with a PIN if needed + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } +#endif +} + +void loop() { +#if TINY_GSM_USE_WIFI + // Wifi connection parameters must be set before waiting for the network + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); +#endif + +#if TINY_GSM_USE_GPRS && defined TINY_GSM_MODEM_XBEE + // The XBee must run the gprsConnect function BEFORE waiting for network! + modem.gprsConnect(apn, gprsUser, gprsPass); +#endif + + SerialMon.print("Waiting for network..."); + if (!modem.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isNetworkConnected()) { SerialMon.println("Network connected"); } + +#if TINY_GSM_USE_GPRS + // GPRS connection parameters are usually set after network registration + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isGprsConnected()) { SerialMon.println("GPRS connected"); } +#endif + + SerialMon.print(F("Performing HTTP GET request... ")); + int err = http.get(resource); + if (err != 0) { + SerialMon.println(F("failed to connect")); + delay(10000); + return; + } + + int status = http.responseStatusCode(); + SerialMon.print(F("Response status code: ")); + SerialMon.println(status); + if (!status) { + delay(10000); + return; + } + + SerialMon.println(F("Response Headers:")); + while (http.headerAvailable()) { + String headerName = http.readHeaderName(); + String headerValue = http.readHeaderValue(); + SerialMon.println(" " + headerName + " : " + headerValue); + } + + int length = http.contentLength(); + if (length >= 0) { + SerialMon.print(F("Content length is: ")); + SerialMon.println(length); + } + if (http.isResponseChunked()) { + SerialMon.println(F("The response is chunked")); + } + + String body = http.responseBody(); + SerialMon.println(F("Response:")); + SerialMon.println(body); + + SerialMon.print(F("Body length is: ")); + SerialMon.println(body.length()); + + // Shutdown + + http.stop(); + SerialMon.println(F("Server disconnected")); + +#if TINY_GSM_USE_WIFI + modem.networkDisconnect(); + SerialMon.println(F("WiFi disconnected")); +#endif +#if TINY_GSM_USE_GPRS + modem.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); +#endif + + // Do nothing forevermore + while (true) { delay(1000); } +} diff --git a/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino b/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino new file mode 100644 index 0000000..33092f2 --- /dev/null +++ b/lib/TinyGSM/examples/HttpsClient/HttpsClient.ino @@ -0,0 +1,257 @@ +/************************************************************** + * + * This sketch connects to a website and downloads a page. + * It can be used to perform HTTP/RESTful API calls. + * + * For this example, you need to install ArduinoHttpClient library: + * https://github.com/arduino-libraries/ArduinoHttpClient + * or from http://librarymanager/all#ArduinoHttpClient + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + * SSL/TLS is not yet supported on the Quectel modems + * The A6/A7/A20 and M590 are not capable of SSL/TLS + * + * For more HTTP API examples, see ArduinoHttpClient library + * + * NOTE: This example may NOT work with the XBee because the + * HttpClient library does not empty to serial buffer fast enough + * and the buffer overflow causes the HttpClient library to stall. + * Boards with faster processors may work, 8MHz boards will not. + **************************************************************/ + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Set serial for AT commands (to the module) +// Use Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + +// Increase RX buffer to capture the entire response +// Chips without internal buffering (A6/A7, ESP8266, M590) +// need enough space in the buffer for the entire response +// else data will be lost (and the http library will fail). +#if !defined(TINY_GSM_RX_BUFFER) +#define TINY_GSM_RX_BUFFER 650 +#endif + +// See all AT commands, if wanted +// #define DUMP_AT_COMMANDS + +// Define the serial console for debug prints, if needed +#define TINY_GSM_DEBUG SerialMon +// #define LOGGING // <- Logging is for the HTTP library + +// Range to attempt to autobaud +// NOTE: DO NOT AUTOBAUD in production code. Once you've established +// communication, set a fixed baud rate using modem.setBaud(#). +#define GSM_AUTOBAUD_MIN 9600 +#define GSM_AUTOBAUD_MAX 115200 + +// Add a reception delay, if needed. +// This may be needed for a fast processor at a slow baud rate. +// #define TINY_GSM_YIELD() { delay(2); } + +// Define how you're planning to connect to the internet. +// This is only needed for this example, not in other code. +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false + +// set GSM PIN, if any +#define GSM_PIN "" + +// flag to force SSL client authentication, if needed +// #define TINY_GSM_SSL_CLIENT_AUTHENTICATION + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +const char gprsUser[] = ""; +const char gprsPass[] = ""; + +// Your WiFi connection credentials, if applicable +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "YourWiFiPass"; + +// Server details +const char server[] = "vsh.pp.ua"; +const char resource[] = "/TinyGSM/logo.txt"; +const int port = 443; + +#include +#include + +// Just in case someone defined the wrong thing.. +#if TINY_GSM_USE_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS false +#define TINY_GSM_USE_WIFI true +#endif +#if TINY_GSM_USE_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false +#endif + +#ifdef DUMP_AT_COMMANDS +#include +StreamDebugger debugger(SerialAT, SerialMon); +TinyGsm modem(debugger); +#else +TinyGsm modem(SerialAT); +#endif + +TinyGsmClientSecure client(modem); +HttpClient http(client, server, port); + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // !!!!!!!!!!! + // Set your reset, enable, power pins here + // !!!!!!!!!!! + + SerialMon.println("Wait..."); + + // Set GSM module baud rate + TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); + // SerialAT.begin(9600); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println("Initializing modem..."); + modem.restart(); + // modem.init(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print("Modem Info: "); + SerialMon.println(modemInfo); + +#if TINY_GSM_USE_GPRS + // Unlock your SIM card with a PIN if needed + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } +#endif +} + +void loop() { +#if TINY_GSM_USE_WIFI + // Wifi connection parameters must be set before waiting for the network + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); +#endif + +#if TINY_GSM_USE_GPRS && defined TINY_GSM_MODEM_XBEE + // The XBee must run the gprsConnect function BEFORE waiting for network! + modem.gprsConnect(apn, gprsUser, gprsPass); +#endif + + SerialMon.print("Waiting for network..."); + if (!modem.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isNetworkConnected()) { SerialMon.println("Network connected"); } + +#if TINY_GSM_USE_GPRS + // GPRS connection parameters are usually set after network registration + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isGprsConnected()) { SerialMon.println("GPRS connected"); } +#endif + + SerialMon.print(F("Performing HTTPS GET request... ")); + http.connectionKeepAlive(); // Currently, this is needed for HTTPS + int err = http.get(resource); + if (err != 0) { + SerialMon.println(F("failed to connect")); + delay(10000); + return; + } + + int status = http.responseStatusCode(); + SerialMon.print(F("Response status code: ")); + SerialMon.println(status); + if (!status) { + delay(10000); + return; + } + + SerialMon.println(F("Response Headers:")); + while (http.headerAvailable()) { + String headerName = http.readHeaderName(); + String headerValue = http.readHeaderValue(); + SerialMon.println(" " + headerName + " : " + headerValue); + } + + int length = http.contentLength(); + if (length >= 0) { + SerialMon.print(F("Content length is: ")); + SerialMon.println(length); + } + if (http.isResponseChunked()) { + SerialMon.println(F("The response is chunked")); + } + + String body = http.responseBody(); + SerialMon.println(F("Response:")); + SerialMon.println(body); + + SerialMon.print(F("Body length is: ")); + SerialMon.println(body.length()); + + // Shutdown + + http.stop(); + SerialMon.println(F("Server disconnected")); + +#if TINY_GSM_USE_WIFI + modem.networkDisconnect(); + SerialMon.println(F("WiFi disconnected")); +#endif +#if TINY_GSM_USE_GPRS + modem.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); +#endif + + // Do nothing forevermore + while (true) { delay(1000); } +} diff --git a/lib/TinyGSM/examples/MqttClient/MqttClient.ino b/lib/TinyGSM/examples/MqttClient/MqttClient.ino new file mode 100644 index 0000000..77a8500 --- /dev/null +++ b/lib/TinyGSM/examples/MqttClient/MqttClient.ino @@ -0,0 +1,290 @@ +/************************************************************** + * + * For this example, you need to install PubSubClient library: + * https://github.com/knolleary/pubsubclient + * or from http://librarymanager/all#PubSubClient + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + * For more MQTT examples, see PubSubClient library + * + ************************************************************** + * This example connects to HiveMQ's showcase broker. + * + * You can quickly test sending and receiving messages from the HiveMQ webclient + * available at http://www.hivemq.com/demos/websocket-client/. + * + * Subscribe to the topic GsmClientTest/ledStatus + * Publish "toggle" to the topic GsmClientTest/led and the LED on your board + * should toggle and you should see a new message published to + * GsmClientTest/ledStatus with the newest LED status. + * + **************************************************************/ + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM7000 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_SIM5360 +// #define TINY_GSM_MODEM_SIM7600 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_M95 +// #define TINY_GSM_MODEM_BG96 +// #define TINY_GSM_MODEM_A6 +// #define TINY_GSM_MODEM_A7 +// #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Set serial for AT commands (to the module) +// Use Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + +// See all AT commands, if wanted +// #define DUMP_AT_COMMANDS + +// Define the serial console for debug prints, if needed +#define TINY_GSM_DEBUG SerialMon + +// Range to attempt to autobaud +// NOTE: DO NOT AUTOBAUD in production code. Once you've established +// communication, set a fixed baud rate using modem.setBaud(#). +#define GSM_AUTOBAUD_MIN 9600 +#define GSM_AUTOBAUD_MAX 115200 + +// Add a reception delay, if needed. +// This may be needed for a fast processor at a slow baud rate. +// #define TINY_GSM_YIELD() { delay(2); } + +// Define how you're planning to connect to the internet. +// This is only needed for this example, not in other code. +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false + +// set GSM PIN, if any +#define GSM_PIN "" + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +const char gprsUser[] = ""; +const char gprsPass[] = ""; + +// Your WiFi connection credentials, if applicable +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "YourWiFiPass"; + +// MQTT details +const char* broker = "broker.hivemq.com"; + +const char* topicLed = "GsmClientTest/led"; +const char* topicInit = "GsmClientTest/init"; +const char* topicLedStatus = "GsmClientTest/ledStatus"; + +#include +#include + +// Just in case someone defined the wrong thing.. +#if TINY_GSM_USE_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS false +#define TINY_GSM_USE_WIFI true +#endif +#if TINY_GSM_USE_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false +#endif + +#ifdef DUMP_AT_COMMANDS +#include +StreamDebugger debugger(SerialAT, SerialMon); +TinyGsm modem(debugger); +#else +TinyGsm modem(SerialAT); +#endif +TinyGsmClient client(modem); +PubSubClient mqtt(client); + +#define LED_PIN 13 +int ledStatus = LOW; + +uint32_t lastReconnectAttempt = 0; + +void mqttCallback(char* topic, byte* payload, unsigned int len) { + SerialMon.print("Message arrived ["); + SerialMon.print(topic); + SerialMon.print("]: "); + SerialMon.write(payload, len); + SerialMon.println(); + + // Only proceed if incoming message's topic matches + if (String(topic) == topicLed) { + ledStatus = !ledStatus; + digitalWrite(LED_PIN, ledStatus); + mqtt.publish(topicLedStatus, ledStatus ? "1" : "0"); + } +} + +boolean mqttConnect() { + SerialMon.print("Connecting to "); + SerialMon.print(broker); + + // Connect to MQTT Broker + boolean status = mqtt.connect("GsmClientTest"); + + // Or, if you want to authenticate MQTT: + // boolean status = mqtt.connect("GsmClientName", "mqtt_user", "mqtt_pass"); + + if (status == false) { + SerialMon.println(" fail"); + return false; + } + SerialMon.println(" success"); + mqtt.publish(topicInit, "GsmClientTest started"); + mqtt.subscribe(topicLed); + return mqtt.connected(); +} + + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + pinMode(LED_PIN, OUTPUT); + + // !!!!!!!!!!! + // Set your reset, enable, power pins here + // !!!!!!!!!!! + + SerialMon.println("Wait..."); + + // Set GSM module baud rate + TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); + // SerialAT.begin(9600); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println("Initializing modem..."); + modem.restart(); + // modem.init(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print("Modem Info: "); + SerialMon.println(modemInfo); + +#if TINY_GSM_USE_GPRS + // Unlock your SIM card with a PIN if needed + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } +#endif + +#if TINY_GSM_USE_WIFI + // Wifi connection parameters must be set before waiting for the network + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); +#endif + +#if TINY_GSM_USE_GPRS && defined TINY_GSM_MODEM_XBEE + // The XBee must run the gprsConnect function BEFORE waiting for network! + modem.gprsConnect(apn, gprsUser, gprsPass); +#endif + + SerialMon.print("Waiting for network..."); + if (!modem.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isNetworkConnected()) { SerialMon.println("Network connected"); } + +#if TINY_GSM_USE_GPRS + // GPRS connection parameters are usually set after network registration + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isGprsConnected()) { SerialMon.println("GPRS connected"); } +#endif + + // MQTT Broker setup + mqtt.setServer(broker, 1883); + mqtt.setCallback(mqttCallback); +} + +void loop() { + // Make sure we're still registered on the network + if (!modem.isNetworkConnected()) { + SerialMon.println("Network disconnected"); + if (!modem.waitForNetwork(180000L, true)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + if (modem.isNetworkConnected()) { + SerialMon.println("Network re-connected"); + } + +#if TINY_GSM_USE_GPRS + // and make sure GPRS/EPS is still connected + if (!modem.isGprsConnected()) { + SerialMon.println("GPRS disconnected!"); + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + if (modem.isGprsConnected()) { SerialMon.println("GPRS reconnected"); } + } +#endif + } + + if (!mqtt.connected()) { + SerialMon.println("=== MQTT NOT CONNECTED ==="); + // Reconnect every 10 seconds + uint32_t t = millis(); + if (t - lastReconnectAttempt > 10000L) { + lastReconnectAttempt = t; + if (mqttConnect()) { lastReconnectAttempt = 0; } + } + delay(100); + return; + } + + mqtt.loop(); +} diff --git a/lib/TinyGSM/examples/WebClient/WebClient.ino b/lib/TinyGSM/examples/WebClient/WebClient.ino new file mode 100644 index 0000000..74ffa0a --- /dev/null +++ b/lib/TinyGSM/examples/WebClient/WebClient.ino @@ -0,0 +1,244 @@ +/************************************************************** + * + * This sketch connects to a website and downloads a page. + * It can be used to perform HTTP/RESTful API calls. + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + **************************************************************/ + +// Select your modem: +#define TINY_GSM_MODEM_SIM800 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM7000 +// #define TINY_GSM_MODEM_SIM7000SSL +// #define TINY_GSM_MODEM_SIM7080 +// #define TINY_GSM_MODEM_SIM5360 +// #define TINY_GSM_MODEM_SIM7600 +// #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_SARAR4 +// #define TINY_GSM_MODEM_M95 +// #define TINY_GSM_MODEM_BG96 +// #define TINY_GSM_MODEM_A6 +// #define TINY_GSM_MODEM_A7 +// #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE +// #define TINY_GSM_MODEM_SEQUANS_MONARCH + +// Set serial for debug console (to the Serial Monitor, default speed 115200) +#define SerialMon Serial + +// Set serial for AT commands (to the module) +// Use Hardware Serial on Mega, Leonardo, Micro +#ifndef __AVR_ATmega328P__ +#define SerialAT Serial1 + +// or Software Serial on Uno, Nano +#else +#include +SoftwareSerial SerialAT(2, 3); // RX, TX +#endif + +// Increase RX buffer to capture the entire response +// Chips without internal buffering (A6/A7, ESP8266, M590) +// need enough space in the buffer for the entire response +// else data will be lost (and the http library will fail). +#if !defined(TINY_GSM_RX_BUFFER) +#define TINY_GSM_RX_BUFFER 650 +#endif + +// See all AT commands, if wanted +// #define DUMP_AT_COMMANDS + +// Define the serial console for debug prints, if needed +#define TINY_GSM_DEBUG SerialMon + +// Range to attempt to autobaud +// NOTE: DO NOT AUTOBAUD in production code. Once you've established +// communication, set a fixed baud rate using modem.setBaud(#). +#define GSM_AUTOBAUD_MIN 9600 +#define GSM_AUTOBAUD_MAX 115200 + +// Add a reception delay, if needed. +// This may be needed for a fast processor at a slow baud rate. +// #define TINY_GSM_YIELD() { delay(2); } + +// Uncomment this if you want to use SSL +// #define USE_SSL + +// Define how you're planning to connect to the internet. +// This is only needed for this example, not in other code. +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false + +// set GSM PIN, if any +#define GSM_PIN "" + +// Your GPRS credentials, if any +const char apn[] = "YourAPN"; +const char gprsUser[] = ""; +const char gprsPass[] = ""; + +// Your WiFi connection credentials, if applicable +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "YourWiFiPass"; + +// Server details +const char server[] = "vsh.pp.ua"; +const char resource[] = "/TinyGSM/logo.txt"; + +#include + +// Just in case someone defined the wrong thing.. +#if TINY_GSM_USE_GPRS && not defined TINY_GSM_MODEM_HAS_GPRS +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS false +#define TINY_GSM_USE_WIFI true +#endif +#if TINY_GSM_USE_WIFI && not defined TINY_GSM_MODEM_HAS_WIFI +#undef TINY_GSM_USE_GPRS +#undef TINY_GSM_USE_WIFI +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_WIFI false +#endif + +#ifdef DUMP_AT_COMMANDS +#include +StreamDebugger debugger(SerialAT, SerialMon); +TinyGsm modem(debugger); +#else +TinyGsm modem(SerialAT); +#endif + +#ifdef USE_SSL +TinyGsmClientSecure client(modem); +const int port = 443; +#else +TinyGsmClient client(modem); +const int port = 80; +#endif + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // !!!!!!!!!!! + // Set your reset, enable, power pins here + // !!!!!!!!!!! + + SerialMon.println("Wait..."); + + // Set GSM module baud rate + TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); + // SerialAT.begin(9600); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println("Initializing modem..."); + modem.restart(); + // modem.init(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print("Modem Info: "); + SerialMon.println(modemInfo); + +#if TINY_GSM_USE_GPRS + // Unlock your SIM card with a PIN if needed + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } +#endif +} + +void loop() { +#if TINY_GSM_USE_WIFI + // Wifi connection parameters must be set before waiting for the network + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); +#endif + +#if TINY_GSM_USE_GPRS && defined TINY_GSM_MODEM_XBEE + // The XBee must run the gprsConnect function BEFORE waiting for network! + modem.gprsConnect(apn, gprsUser, gprsPass); +#endif + + SerialMon.print("Waiting for network..."); + if (!modem.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isNetworkConnected()) { SerialMon.println("Network connected"); } + +#if TINY_GSM_USE_GPRS + // GPRS connection parameters are usually set after network registration + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + if (modem.isGprsConnected()) { SerialMon.println("GPRS connected"); } +#endif + + SerialMon.print("Connecting to "); + SerialMon.println(server); + if (!client.connect(server, port)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + // Make a HTTP GET request: + SerialMon.println("Performing HTTP GET request..."); + client.print(String("GET ") + resource + " HTTP/1.1\r\n"); + client.print(String("Host: ") + server + "\r\n"); + client.print("Connection: close\r\n\r\n"); + client.println(); + + uint32_t timeout = millis(); + while (client.connected() && millis() - timeout < 10000L) { + // Print available data + while (client.available()) { + char c = client.read(); + SerialMon.print(c); + timeout = millis(); + } + } + SerialMon.println(); + + // Shutdown + + client.stop(); + SerialMon.println(F("Server disconnected")); + +#if TINY_GSM_USE_WIFI + modem.networkDisconnect(); + SerialMon.println(F("WiFi disconnected")); +#endif +#if TINY_GSM_USE_GPRS + modem.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); +#endif + + // Do nothing forevermore + while (true) { delay(1000); } +} diff --git a/lib/TinyGSM/examples/example_dependencies.json b/lib/TinyGSM/examples/example_dependencies.json new file mode 100644 index 0000000..76d7297 --- /dev/null +++ b/lib/TinyGSM/examples/example_dependencies.json @@ -0,0 +1,41 @@ +[ + { + "name": "PubSubClient", + "owner": "knolleary", + "library id": "89", + "url": "https://github.com/knolleary/pubsubclient.git", + "version": "~2.8", + "note": "A client library for MQTT messaging.", + "authors": ["Nick O'Leary"] + }, + { + "name": "Blynk", + "owner": "blynkkk", + "library id": "415", + "url": "https://github.com/blynkkk/blynk-library.git", + "version": "~0.6.7", + "authors": ["Volodymyr Shymanskyy"], + "frameworks": "*", + "platforms": "*" + }, + { + "name": "AceCRC", + "owner": "bxparks", + "library id": "1202", + "url": "https://github.com/bxparks/AceCRC.git", + "version": "~1.0.1", + "authors": ["Brian T. Park"], + "frameworks": "*", + "platforms": "*" + }, + { + "name": "StreamDebugger", + "owner": "vshymanskyy", + "library id": "1286", + "url": "https://github.com/vshymanskyy/StreamDebugger.git", + "version": "~1.0.1", + "authors": ["Volodymyr Shymanskyy"], + "frameworks": "*", + "platforms": "*" + } +] diff --git a/lib/TinyGSM/examples/more/Hologram_Dash/Hologram_Dash.ino b/lib/TinyGSM/examples/more/Hologram_Dash/Hologram_Dash.ino new file mode 100644 index 0000000..5578fc4 --- /dev/null +++ b/lib/TinyGSM/examples/more/Hologram_Dash/Hologram_Dash.ino @@ -0,0 +1,136 @@ +/************************************************************** + * + * This sketch connects to a website and downloads a page. + * It can be used to perform HTTP/RESTful API calls. + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + **************************************************************/ + +// Hologram Dash uses UBLOX U2 modems +#define TINY_GSM_MODEM_UBLOX + +// Increase RX buffer if needed +#if !defined(TINY_GSM_RX_BUFFER) +#define TINY_GSM_RX_BUFFER 512 +#endif + +#include + +// Uncomment this if you want to see all AT commands +// #define DUMP_AT_COMMANDS + +// Uncomment this if you want to use SSL +// #define USE_SSL + +// Set serial for debug console (to the Serial Monitor, speed 115200) +#define SerialMon Serial + +// We'll be using SerialSystem in Passthrough mode +#define SerialAT SerialSystem + +// Your GPRS credentials +// Leave empty, if missing user or pass +const char apn[] = "YourAPN"; +const char user[] = ""; +const char pass[] = ""; + +// Server details +const char server[] = "vsh.pp.ua"; +const char resource[] = "/TinyGSM/logo.txt"; + +#ifdef DUMP_AT_COMMANDS + #include + StreamDebugger debugger(SerialAT, SerialMon); + TinyGsm mdm(debugger); +#else + TinyGsm mdm(SerialAT); +#endif + +#ifdef USE_SSL + TinyGsmClientSecure client(mdm); + const int port = 443; +#else + TinyGsmClient client(mdm); + const int port = 80; +#endif + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // Set up Passthrough + HologramCloud.enterPassthrough(); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println(F("Initializing modem...")); + mdm.restart(); + + String modemInfo = mdm.getModemInfo(); + SerialMon.print(F("Modem: ")); + SerialMon.println(modemInfo); + + // Unlock your SIM card with a PIN + //mdm.simUnlock("1234"); +} + +void loop() { + SerialMon.print(F("Waiting for network...")); + if (!mdm.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!mdm.gprsConnect(apn, user, pass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + SerialMon.print(F("Connecting to ")); + SerialMon.print(server); + if (!client.connect(server, port)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + // Make a HTTP GET request: + client.print(String("GET ") + resource + " HTTP/1.0\r\n"); + client.print(String("Host: ") + server + "\r\n"); + client.print("Connection: close\r\n\r\n"); + + uint32_t timeout = millis(); + while (client.connected() && millis() - timeout < 10000L) { + // Print available data + while (client.available()) { + char c = client.read(); + SerialMon.print(c); + timeout = millis(); + } + } + SerialMon.println(); + + // Shutdown + + client.stop(); + SerialMon.println(F("Server disconnected")); + + mdm.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); + + // Do nothing forevermore + while (true) { + delay(1000); + } +} diff --git a/lib/TinyGSM/examples/more/Industruino/Industruino.ino b/lib/TinyGSM/examples/more/Industruino/Industruino.ino new file mode 100644 index 0000000..cd2b747 --- /dev/null +++ b/lib/TinyGSM/examples/more/Industruino/Industruino.ino @@ -0,0 +1,161 @@ +/************************************************************** + * + * This sketch connects to a website and downloads a page. + * It can be used to perform HTTP/RESTful API calls. + * + * For this example, you need to install ArduinoHttpClient library: + * https://github.com/arduino-libraries/ArduinoHttpClient + * or from http://librarymanager/all#ArduinoHttpClient + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + * For more HTTP API examples, see ArduinoHttpClient library + * + **************************************************************/ + +// Industruino uses SIM800H +#define TINY_GSM_MODEM_SIM800 + +// Increase RX buffer if needed +#if !defined(TINY_GSM_RX_BUFFER) +#define TINY_GSM_RX_BUFFER 512 +#endif + +#include +#include + +// Uncomment this if you want to see all AT commands +// #define DUMP_AT_COMMANDS + +// Uncomment this if you want to use SSL +// #define USE_SSL + +// Set serial for debug console (to the Serial Monitor, speed 115200) +#define SerialMon SerialUSB + +// Select Serial1 or Serial depending on your module configuration +#define SerialAT Serial1 + +// Your GPRS credentials +// Leave empty, if missing user or pass +const char apn[] = "YourAPN"; +const char user[] = ""; +const char pass[] = ""; + +// Server details +const char server[] = "vsh.pp.ua"; +const char resource[] = "/TinyGSM/logo.txt"; + +#ifdef DUMP_AT_COMMANDS + #include + StreamDebugger debugger(SerialAT, SerialMon); + TinyGsm modem(debugger); +#else + TinyGsm modem(SerialAT); +#endif + +#ifdef USE_SSL + TinyGsmClientSecure client(modem); + HttpClient http(client, server, 443); +#else + TinyGsmClient client(modem); + HttpClient http(client, server, 80); +#endif + +void setup() { + // Turn on modem with 1 second pulse on D6 + pinMode(6, OUTPUT); + digitalWrite(6, HIGH); + delay(1000); + digitalWrite(6, LOW); + + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // Set GSM module baud rate + SerialAT.begin(115200); + delay(6000); + + // Restart takes quite some time + // To skip it, call init() instead of restart() + SerialMon.println(F("Initializing modem...")); + modem.restart(); + + String modemInfo = modem.getModemInfo(); + SerialMon.print(F("Modem: ")); + SerialMon.println(modemInfo); + + // Unlock your SIM card with a PIN + //modem.simUnlock("1234"); +} + +void loop() { + SerialMon.print(F("Waiting for network...")); + if (!modem.waitForNetwork()) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, user, pass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" success"); + + SerialMon.print(F("Performing HTTP GET request... ")); + int err = http.get(resource); + if (err != 0) { + SerialMon.println(F("failed to connect")); + delay(10000); + return; + } + + int status = http.responseStatusCode(); + SerialMon.println(status); + if (!status) { + delay(10000); + return; + } + + while (http.headerAvailable()) { + String headerName = http.readHeaderName(); + String headerValue = http.readHeaderValue(); + //SerialMon.println(headerName + " : " + headerValue); + } + + int length = http.contentLength(); + if (length >= 0) { + SerialMon.print(F("Content length is: ")); + SerialMon.println(length); + } + if (http.isResponseChunked()) { + SerialMon.println(F("The response is chunked")); + } + + String body = http.responseBody(); + SerialMon.println(F("Response:")); + SerialMon.println(body); + + SerialMon.print(F("Body length is: ")); + SerialMon.println(body.length()); + + // Shutdown + + http.stop(); + SerialMon.println(F("Server disconnected")); + + modem.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); + + // Do nothing forevermore + while (true) { + delay(1000); + } +} diff --git a/lib/TinyGSM/examples/more/SIM800_SslSetCert/COMODORSACertificationAuthority.h b/lib/TinyGSM/examples/more/SIM800_SslSetCert/COMODORSACertificationAuthority.h new file mode 100644 index 0000000..514db99 --- /dev/null +++ b/lib/TinyGSM/examples/more/SIM800_SslSetCert/COMODORSACertificationAuthority.h @@ -0,0 +1,36 @@ +const char cert[] PROGMEM = +"-----BEGIN CERTIFICATE-----\n" +"MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\n" +"hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" +"A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\n" +"BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\n" +"MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\n" +"EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\n" +"Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\n" +"dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n" +"6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\n" +"pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n" +"9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n" +"/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\n" +"Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n" +"+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\n" +"qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\n" +"SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\n" +"u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\n" +"Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\n" +"crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\n" +"FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n" +"/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\n" +"wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n" +"4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n" +"2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\n" +"FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\n" +"CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\n" +"boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\n" +"jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\n" +"S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\n" +"QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n" +"0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\n" +"NVOFBkpdn627G190\n" +"-----END CERTIFICATE-----\n"; + diff --git a/lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.der.h b/lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.der.h new file mode 100644 index 0000000..15063a3 --- /dev/null +++ b/lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.der.h @@ -0,0 +1,74 @@ +const char cert[] PROGMEM = +{ + 0x30, 0x82, 0x03, 0x4a, 0x30, 0x82, 0x02, 0x32, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x10, 0x44, 0xaf, 0xb0, 0x80, 0xd6, 0xa3, 0x27, 0xba, 0x89, + 0x30, 0x39, 0x86, 0x2e, 0xf8, 0x40, 0x6b, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x3f, + 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1b, 0x44, + 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, + 0x6f, 0x2e, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x0e, 0x44, 0x53, 0x54, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x58, 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x39, 0x33, + 0x30, 0x32, 0x31, 0x31, 0x32, 0x31, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x31, + 0x30, 0x39, 0x33, 0x30, 0x31, 0x34, 0x30, 0x31, 0x31, 0x35, 0x5a, 0x30, + 0x3f, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1b, + 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x43, 0x6f, 0x2e, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x0e, 0x44, 0x53, 0x54, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, + 0x41, 0x20, 0x58, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, + 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xdf, 0xaf, 0xe9, 0x97, 0x50, 0x08, 0x83, 0x57, 0xb4, 0xcc, 0x62, + 0x65, 0xf6, 0x90, 0x82, 0xec, 0xc7, 0xd3, 0x2c, 0x6b, 0x30, 0xca, 0x5b, + 0xec, 0xd9, 0xc3, 0x7d, 0xc7, 0x40, 0xc1, 0x18, 0x14, 0x8b, 0xe0, 0xe8, + 0x33, 0x76, 0x49, 0x2a, 0xe3, 0x3f, 0x21, 0x49, 0x93, 0xac, 0x4e, 0x0e, + 0xaf, 0x3e, 0x48, 0xcb, 0x65, 0xee, 0xfc, 0xd3, 0x21, 0x0f, 0x65, 0xd2, + 0x2a, 0xd9, 0x32, 0x8f, 0x8c, 0xe5, 0xf7, 0x77, 0xb0, 0x12, 0x7b, 0xb5, + 0x95, 0xc0, 0x89, 0xa3, 0xa9, 0xba, 0xed, 0x73, 0x2e, 0x7a, 0x0c, 0x06, + 0x32, 0x83, 0xa2, 0x7e, 0x8a, 0x14, 0x30, 0xcd, 0x11, 0xa0, 0xe1, 0x2a, + 0x38, 0xb9, 0x79, 0x0a, 0x31, 0xfd, 0x50, 0xbd, 0x80, 0x65, 0xdf, 0xb7, + 0x51, 0x63, 0x83, 0xc8, 0xe2, 0x88, 0x61, 0xea, 0x4b, 0x61, 0x81, 0xec, + 0x52, 0x6b, 0xb9, 0xa2, 0xe2, 0x4b, 0x1a, 0x28, 0x9f, 0x48, 0xa3, 0x9e, + 0x0c, 0xda, 0x09, 0x8e, 0x3e, 0x17, 0x2e, 0x1e, 0xdd, 0x20, 0xdf, 0x5b, + 0xc6, 0x2a, 0x8a, 0xab, 0x2e, 0xbd, 0x70, 0xad, 0xc5, 0x0b, 0x1a, 0x25, + 0x90, 0x74, 0x72, 0xc5, 0x7b, 0x6a, 0xab, 0x34, 0xd6, 0x30, 0x89, 0xff, + 0xe5, 0x68, 0x13, 0x7b, 0x54, 0x0b, 0xc8, 0xd6, 0xae, 0xec, 0x5a, 0x9c, + 0x92, 0x1e, 0x3d, 0x64, 0xb3, 0x8c, 0xc6, 0xdf, 0xbf, 0xc9, 0x41, 0x70, + 0xec, 0x16, 0x72, 0xd5, 0x26, 0xec, 0x38, 0x55, 0x39, 0x43, 0xd0, 0xfc, + 0xfd, 0x18, 0x5c, 0x40, 0xf1, 0x97, 0xeb, 0xd5, 0x9a, 0x9b, 0x8d, 0x1d, + 0xba, 0xda, 0x25, 0xb9, 0xc6, 0xd8, 0xdf, 0xc1, 0x15, 0x02, 0x3a, 0xab, + 0xda, 0x6e, 0xf1, 0x3e, 0x2e, 0xf5, 0x5c, 0x08, 0x9c, 0x3c, 0xd6, 0x83, + 0x69, 0xe4, 0x10, 0x9b, 0x19, 0x2a, 0xb6, 0x29, 0x57, 0xe3, 0xe5, 0x3d, + 0x9b, 0x9f, 0xf0, 0x02, 0x5d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x42, + 0x30, 0x40, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, + 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, + 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, + 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xc4, 0xa7, + 0xb1, 0xa4, 0x7b, 0x2c, 0x71, 0xfa, 0xdb, 0xe1, 0x4b, 0x90, 0x75, 0xff, + 0xc4, 0x15, 0x60, 0x85, 0x89, 0x10, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, + 0x01, 0x00, 0xa3, 0x1a, 0x2c, 0x9b, 0x17, 0x00, 0x5c, 0xa9, 0x1e, 0xee, + 0x28, 0x66, 0x37, 0x3a, 0xbf, 0x83, 0xc7, 0x3f, 0x4b, 0xc3, 0x09, 0xa0, + 0x95, 0x20, 0x5d, 0xe3, 0xd9, 0x59, 0x44, 0xd2, 0x3e, 0x0d, 0x3e, 0xbd, + 0x8a, 0x4b, 0xa0, 0x74, 0x1f, 0xce, 0x10, 0x82, 0x9c, 0x74, 0x1a, 0x1d, + 0x7e, 0x98, 0x1a, 0xdd, 0xcb, 0x13, 0x4b, 0xb3, 0x20, 0x44, 0xe4, 0x91, + 0xe9, 0xcc, 0xfc, 0x7d, 0xa5, 0xdb, 0x6a, 0xe5, 0xfe, 0xe6, 0xfd, 0xe0, + 0x4e, 0xdd, 0xb7, 0x00, 0x3a, 0xb5, 0x70, 0x49, 0xaf, 0xf2, 0xe5, 0xeb, + 0x02, 0xf1, 0xd1, 0x02, 0x8b, 0x19, 0xcb, 0x94, 0x3a, 0x5e, 0x48, 0xc4, + 0x18, 0x1e, 0x58, 0x19, 0x5f, 0x1e, 0x02, 0x5a, 0xf0, 0x0c, 0xf1, 0xb1, + 0xad, 0xa9, 0xdc, 0x59, 0x86, 0x8b, 0x6e, 0xe9, 0x91, 0xf5, 0x86, 0xca, + 0xfa, 0xb9, 0x66, 0x33, 0xaa, 0x59, 0x5b, 0xce, 0xe2, 0xa7, 0x16, 0x73, + 0x47, 0xcb, 0x2b, 0xcc, 0x99, 0xb0, 0x37, 0x48, 0xcf, 0xe3, 0x56, 0x4b, + 0xf5, 0xcf, 0x0f, 0x0c, 0x72, 0x32, 0x87, 0xc6, 0xf0, 0x44, 0xbb, 0x53, + 0x72, 0x6d, 0x43, 0xf5, 0x26, 0x48, 0x9a, 0x52, 0x67, 0xb7, 0x58, 0xab, + 0xfe, 0x67, 0x76, 0x71, 0x78, 0xdb, 0x0d, 0xa2, 0x56, 0x14, 0x13, 0x39, + 0x24, 0x31, 0x85, 0xa2, 0xa8, 0x02, 0x5a, 0x30, 0x47, 0xe1, 0xdd, 0x50, + 0x07, 0xbc, 0x02, 0x09, 0x90, 0x00, 0xeb, 0x64, 0x63, 0x60, 0x9b, 0x16, + 0xbc, 0x88, 0xc9, 0x12, 0xe6, 0xd2, 0x7d, 0x91, 0x8b, 0xf9, 0x3d, 0x32, + 0x8d, 0x65, 0xb4, 0xe9, 0x7c, 0xb1, 0x57, 0x76, 0xea, 0xc5, 0xb6, 0x28, + 0x39, 0xbf, 0x15, 0x65, 0x1c, 0xc8, 0xf6, 0x77, 0x96, 0x6a, 0x0a, 0x8d, + 0x77, 0x0b, 0xd8, 0x91, 0x0b, 0x04, 0x8e, 0x07, 0xdb, 0x29, 0xb6, 0x0a, + 0xee, 0x9d, 0x82, 0x35, 0x35, 0x10 +}; diff --git a/lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.h b/lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.h new file mode 100644 index 0000000..55e79d9 --- /dev/null +++ b/lib/TinyGSM/examples/more/SIM800_SslSetCert/DSTRootCAX3.h @@ -0,0 +1,22 @@ +const char cert[] PROGMEM = +"-----BEGIN CERTIFICATE-----\n" +"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" +"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" +"DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" +"PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" +"Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" +"AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" +"rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" +"OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" +"xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" +"7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" +"aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" +"HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" +"SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" +"ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" +"AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" +"R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" +"JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" +"Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" +"-----END CERTIFICATE-----\n"; + diff --git a/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino b/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino new file mode 100644 index 0000000..eef4207 --- /dev/null +++ b/lib/TinyGSM/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino @@ -0,0 +1,96 @@ +/************************************************************** + * + * This sketch uploads SSL certificates to the SIM8xx + * + * TinyGSM Getting Started guide: + * https://tiny.cc/tinygsm-readme + * + **************************************************************/ + +// This example is specific to SIM8xx +#define TINY_GSM_MODEM_SIM800 + +// Select your certificate: +#include "DSTRootCAX3.h" +//#include "DSTRootCAX3.der.h" +//#include "COMODORSACertificationAuthority.h" + +// Select the file you want to write into +// (the file is stored on the modem) +#define CERT_FILE "C:\\USER\\CERT.CRT" + +#include + +// Set serial for debug console (to the Serial Monitor, speed 115200) +#define SerialMon Serial + +// Use Hardware Serial for AT commands +#define SerialAT Serial1 + +// Uncomment this if you want to see all AT commands +// #define DUMP_AT_COMMANDS + + +#ifdef DUMP_AT_COMMANDS + #include + StreamDebugger debugger(SerialAT, SerialMon); + TinyGsm modem(debugger); +#else + TinyGsm modem(SerialAT); +#endif + +void setup() { + // Set console baud rate + SerialMon.begin(115200); + delay(10); + + // Set GSM module baud rate + SerialAT.begin(115200); + delay(6000); + + SerialMon.println(F("Initializing modem...")); + modem.init(); + + modem.sendAT(GF("+FSCREATE=" CERT_FILE)); + if (modem.waitResponse() != 1) return; + + const int cert_size = sizeof(cert); + + modem.sendAT(GF("+FSWRITE=" CERT_FILE ",0,"), cert_size, GF(",10")); + if (modem.waitResponse(GF(">")) != 1) { + return; + } + + for (int i = 0; i < cert_size; i++) { + char c = pgm_read_byte(&cert[i]); + modem.stream.write(c); + } + + modem.stream.write(GSM_NL); + modem.stream.flush(); + + if (modem.waitResponse(2000) != 1) return; + + modem.sendAT(GF("+SSLSETCERT=\"" CERT_FILE "\"")); + if (modem.waitResponse() != 1) return; + if (modem.waitResponse(5000L, GF(GSM_NL "+SSLSETCERT:")) != 1) return; + const int retCode = modem.stream.readStringUntil('\n').toInt(); + + + SerialMon.println(); + SerialMon.println(); + SerialMon.println(F("****************************")); + SerialMon.print(F("Setting Certificate: ")); + SerialMon.println((0 == retCode) ? "OK" : "FAILED"); + SerialMon.println(F("****************************")); +} + +void loop() { + if (SerialAT.available()) { + SerialMon.write(SerialAT.read()); + } + if (SerialMon.available()) { + SerialAT.write(SerialMon.read()); + } + delay(0); +} diff --git a/lib/TinyGSM/keywords.txt b/lib/TinyGSM/keywords.txt new file mode 100644 index 0000000..b6b4b10 --- /dev/null +++ b/lib/TinyGSM/keywords.txt @@ -0,0 +1,29 @@ +####################################### +# Data types (KEYWORD1) +####################################### +TinyGsm KEYWORD1 +TinyGsmClient KEYWORD1 +TinyGsmClientSecure KEYWORD1 + +SerialAT KEYWORD1 +SerialMon KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +begin KEYWORD2 +restart KEYWORD2 +waitForNetwork KEYWORD2 +getSimStatus KEYWORD2 +gprsConnect KEYWORD2 +gprsDisconnect KEYWORD2 +isGprsConnected KEYWORD2 +isNetworkConnected KEYWORD2 +factoryReset KEYWORD2 + +####################################### +# Literals (LITERAL1) +####################################### +DATE_FULL LITERAL1 +DATE_TIME LITERAL1 +DATE_DATE LITERAL1 diff --git a/lib/TinyGSM/library.json b/lib/TinyGSM/library.json new file mode 100644 index 0000000..a4d1827 --- /dev/null +++ b/lib/TinyGSM/library.json @@ -0,0 +1,32 @@ +{ + "name": "TinyGSM", + "version": "0.11.7", + "description": "A small Arduino library for GPRS modules, that just works. Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces.", + "keywords": "GSM, AT commands, AT, SIM800, SIM900, A6, A7, M590, ESP8266, SIM7000, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968, M95, MC60, MC60E, BG96, ublox, Quectel, SIMCOM, AI Thinker, LTE, LTE-M", + "authors": [ + { + "name": "Volodymyr Shymanskyy", + "url": "https://github.com/vshymanskyy", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/vshymanskyy/TinyGSM.git" + }, + "homepage": "https://github.com/vshymanskyy/TinyGSM", + "export": { + "include": [ + "LICENSE", + "library.json", + "library.properties", + "README.md", + "keywords.txt", + "src/*", + "examples/*" + ] + }, + "frameworks": ["arduino", "energia", "wiringpi"], + "platforms": "*", + "headers": "TinyGsmClient.h" +} diff --git a/lib/TinyGSM/library.properties b/lib/TinyGSM/library.properties new file mode 100644 index 0000000..5df8580 --- /dev/null +++ b/lib/TinyGSM/library.properties @@ -0,0 +1,10 @@ +name=TinyGSM +version=0.11.7 +author=Volodymyr Shymanskyy +maintainer=Volodymyr Shymanskyy +sentence=A small Arduino library for GPRS modules, that just works. +paragraph=Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces. +category=Communication +url=https://github.com/vshymanskyy/TinyGSM +architectures=* +includes=TinyGsmClient.h diff --git a/lib/TinyGSM/src/ArduinoCompat/Client.h b/lib/TinyGSM/src/ArduinoCompat/Client.h new file mode 100644 index 0000000..0421b30 --- /dev/null +++ b/lib/TinyGSM/src/ArduinoCompat/Client.h @@ -0,0 +1,47 @@ +/* + Client.h - Base class that provides Client + 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 client_h +#define client_h +#include "Print.h" +#include "Stream.h" +#include "ArduinoCompat/IPAddress.h" + +class Client : public Stream { + public: + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char* host, uint16_t port) = 0; + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t* buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t* buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; + + protected: + uint8_t* rawIPAddress(IPAddress& addr) { + return addr.raw_address(); + }; +}; + +#endif diff --git a/lib/TinyGSM/src/ArduinoCompat/IPAddress.h b/lib/TinyGSM/src/ArduinoCompat/IPAddress.h new file mode 100644 index 0000000..991b2cb --- /dev/null +++ b/lib/TinyGSM/src/ArduinoCompat/IPAddress.h @@ -0,0 +1,146 @@ +/* + IPAddress.h - Base class that provides IPAddress + 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 IPAddress_h +#define IPAddress_h + +#include +#include "Printable.h" +#include "WString.h" + +// A class to make it easier to handle and pass around IP addresses + +class IPAddress : public Printable { +private: + union { + uint8_t bytes[4]; // IPv4 address + uint32_t dword; + } _address; + + // Access the raw byte array containing the address. Because this returns a pointer + // to the internal structure rather than a copy of the address this function should only + // be used when you know that the usage of the returned uint8_t* will be transient and not + // stored. + uint8_t* raw_address() { return _address.bytes; }; + +public: + // Constructors + IPAddress() { + _address.dword = 0; + } + IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) { + _address.bytes[0] = first_octet; + _address.bytes[1] = second_octet; + _address.bytes[2] = third_octet; + _address.bytes[3] = fourth_octet; + } + + IPAddress(uint32_t address) { + _address.dword = address; + } + IPAddress(const uint8_t *address) { + memcpy(_address.bytes, address, sizeof(_address.bytes)); + } + + bool fromString(const char *address) { + uint16_t acc = 0; // Accumulator + uint8_t dots = 0; + + while (*address) + { + char c = *address++; + if (c >= '0' && c <= '9') + { + acc = acc * 10 + (c - '0'); + if (acc > 255) { + // Value out of [0..255] range + return false; + } + } + else if (c == '.') + { + if (dots == 3) { + // Too much dots (there must be 3 dots) + return false; + } + _address.bytes[dots++] = acc; + acc = 0; + } + else + { + // Invalid char + return false; + } + } + + if (dots != 3) { + // Too few dots (there must be 3 dots) + return false; + } + _address.bytes[3] = acc; + return true; + } + + bool fromString(const String &address) { return fromString(address.c_str()); } + + // Overloaded cast operator to allow IPAddress objects to be used where a pointer + // to a four-byte uint8_t array is expected + operator uint32_t() const { return _address.dword; }; + bool operator==(const IPAddress& addr) const { return _address.dword == addr._address.dword; }; + bool operator==(const uint8_t* addr) const { + return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0; + } + + // Overloaded index operator to allow getting and setting individual octets of the address + uint8_t operator[](int index) const { return _address.bytes[index]; }; + uint8_t& operator[](int index) { return _address.bytes[index]; }; + + // Overloaded copy operators to allow initialisation of IPAddress objects from other types + IPAddress& operator=(const uint8_t *address) { + memcpy(_address.bytes, address, sizeof(_address.bytes)); + return *this; + } + IPAddress& operator=(uint32_t address) { + _address.dword = address; + return *this; + } + + virtual size_t printTo(Print& p) const { + size_t n = 0; + for (int i =0; i < 3; i++) + { + n += p.print(_address.bytes[i], DEC); + n += p.print('.'); + } + n += p.print(_address.bytes[3], DEC); + return n; + } + + + friend class EthernetClass; + friend class UDP; + friend class Client; + friend class Server; + friend class DhcpClass; + friend class DNSClient; +}; + +const IPAddress INADDR_NONE(0,0,0,0); + +#endif diff --git a/lib/TinyGSM/src/TinyGSM.h b/lib/TinyGSM/src/TinyGSM.h new file mode 100644 index 0000000..850e346 --- /dev/null +++ b/lib/TinyGSM/src/TinyGSM.h @@ -0,0 +1,6 @@ +#ifndef TINYGSM_H +#define TINYGSM_H + +#include "TinyGsmClient.h" + +#endif diff --git a/lib/TinyGSM/src/TinyGsmBattery.tpp b/lib/TinyGSM/src/TinyGsmBattery.tpp new file mode 100644 index 0000000..a662682 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmBattery.tpp @@ -0,0 +1,98 @@ +/** + * @file TinyGsmBattery.tpp + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMBATTERY_H_ +#define SRC_TINYGSMBATTERY_H_ + +#include "TinyGsmCommon.h" + +#define TINY_GSM_MODEM_HAS_BATTERY + +template +class TinyGsmBattery { + public: + /* + * Battery functions + */ + uint16_t getBattVoltage() { + return thisModem().getBattVoltageImpl(); + } + int8_t getBattPercent() { + return thisModem().getBattPercentImpl(); + } + uint8_t getBattChargeState() { + return thisModem().getBattChargeStateImpl(); + } + bool getBattStats(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + return thisModem().getBattStatsImpl(chargeState, percent, milliVolts); + } + + /* + * CRTP Helper + */ + protected: + inline const modemType& thisModem() const { + return static_cast(*this); + } + inline modemType& thisModem() { + return static_cast(*this); + } + + /* + * Battery functions + */ + protected: + // Use: float vBatt = modem.getBattVoltage() / 1000.0; + uint16_t getBattVoltageImpl() { + thisModem().sendAT(GF("+CBC")); + if (thisModem().waitResponse(GF("+CBC:")) != 1) { return 0; } + thisModem().streamSkipUntil(','); // Skip battery charge status + thisModem().streamSkipUntil(','); // Skip battery charge level + // return voltage in mV + uint16_t res = thisModem().streamGetIntBefore('\n'); + // Wait for final OK + thisModem().waitResponse(); + return res; + } + + int8_t getBattPercentImpl() { + thisModem().sendAT(GF("+CBC")); + if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; } + thisModem().streamSkipUntil(','); // Skip battery charge status + // Read battery charge level + int8_t res = thisModem().streamGetIntBefore(','); + // Wait for final OK + thisModem().waitResponse(); + return res; + } + + uint8_t getBattChargeStateImpl() { + thisModem().sendAT(GF("+CBC")); + if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; } + // Read battery charge status + int8_t res = thisModem().streamGetIntBefore(','); + // Wait for final OK + thisModem().waitResponse(); + return res; + } + + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + thisModem().sendAT(GF("+CBC")); + if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; } + chargeState = thisModem().streamGetIntBefore(','); + percent = thisModem().streamGetIntBefore(','); + milliVolts = thisModem().streamGetIntBefore('\n'); + // Wait for final OK + thisModem().waitResponse(); + return true; + } +}; + +#endif // SRC_TINYGSMBATTERY_H_ diff --git a/lib/TinyGSM/src/TinyGsmBluetooth.tpp b/lib/TinyGSM/src/TinyGsmBluetooth.tpp new file mode 100644 index 0000000..ca1cb4e --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmBluetooth.tpp @@ -0,0 +1,57 @@ +/** + * @file TinyGsmGPS.tpp + * @author Adrian Cervera Andes + * @license LGPL-3.0 + * @copyright Copyright (c) 2021 Adrian Cervera Andes + * @date Jan 2021 + */ + +#ifndef SRC_TINYGSMBLUETOOTH_H_ +#define SRC_TINYGSMBLUETOOTH_H_ + +#include "TinyGsmCommon.h" + +#define TINY_GSM_MODEM_HAS_BLUETOOTH + +template +class TinyGsmBluetooth { + public: + /* + * Bluetooth functions + */ + bool enableBluetooth() { + return thisModem().enableBluetoothImpl(); + } + bool disableBluetooth() { + return thisModem().disableBluetoothImpl(); + } + bool setBluetoothVisibility(bool visible) { + return thisModem().setBluetoothVisibilityImpl(visible); + } + bool setBluetoothHostName(const char* name) { + return thisModem().setBluetoothHostNameImpl(name); + } + + /* + * CRTP Helper + */ + protected: + inline const modemType& thisModem() const { + return static_cast(*this); + } + inline modemType& thisModem() { + return static_cast(*this); + } + + /* + * Bluetooth functions + */ + + bool enableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool disableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool setBluetoothVisibilityImpl(bool visible) TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool setBluetoothHostNameImpl(const char* name) TINY_GSM_ATTR_NOT_IMPLEMENTED; +}; + + +#endif // SRC_TINYGSMBLUETOOTH_H_ diff --git a/lib/TinyGSM/src/TinyGsmCalling.tpp b/lib/TinyGSM/src/TinyGsmCalling.tpp new file mode 100644 index 0000000..413515a --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmCalling.tpp @@ -0,0 +1,90 @@ +/** + * @file TinyGsmCalling.tpp + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCALLING_H_ +#define SRC_TINYGSMCALLING_H_ + +#include "TinyGsmCommon.h" + +#define TINY_GSM_MODEM_HAS_CALLING + +template +class TinyGsmCalling { + public: + /* + * Phone Call functions + */ + bool callAnswer() { + return thisModem().callAnswerImpl(); + } + bool callNumber(const String& number) { + return thisModem().callNumberImpl(number); + } + bool callHangup() { + return thisModem().callHangupImpl(); + } + bool dtmfSend(char cmd, int duration_ms = 100) { + return thisModem().dtmfSendImpl(cmd, duration_ms); + } + + /* + * CRTP Helper + */ + protected: + inline const modemType& thisModem() const { + return static_cast(*this); + } + inline modemType& thisModem() { + return static_cast(*this); + } + + /* + * Phone Call functions + */ + protected: + bool callAnswerImpl() { + thisModem().sendAT(GF("A")); + return thisModem().waitResponse() == 1; + } + + // Returns true on pick-up, false on error/busy + bool callNumberImpl(const String& number) { + if (number == GF("last")) { + thisModem().sendAT(GF("DL")); + } else { + thisModem().sendAT(GF("D"), number, ";"); + } + int8_t status = thisModem().waitResponse(60000L, GF("OK"), GF("BUSY"), + GF("NO ANSWER"), GF("NO CARRIER")); + switch (status) { + case 1: return true; + case 2: + case 3: return false; + default: return false; + } + } + + bool callHangupImpl() { + thisModem().sendAT(GF("H")); + return thisModem().waitResponse() == 1; + } + + // 0-9,*,#,A,B,C,D + bool dtmfSendImpl(char cmd, int duration_ms = 100) { + duration_ms = constrain(duration_ms, 100, 1000); + + thisModem().sendAT(GF("+VTD="), + duration_ms / 100); // VTD accepts in 1/10 of a second + thisModem().waitResponse(); + + thisModem().sendAT(GF("+VTS="), cmd); + return thisModem().waitResponse(10000L) == 1; + } +}; + +#endif // SRC_TINYGSMCALLING_H_ diff --git a/lib/TinyGSM/src/TinyGsmClient.h b/lib/TinyGSM/src/TinyGsmClient.h new file mode 100644 index 0000000..a8ae763 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClient.h @@ -0,0 +1,121 @@ +/** + * @file TinyGsmClient.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENT_H_ +#define SRC_TINYGSMCLIENT_H_ + +#if defined(TINY_GSM_MODEM_SIM800) +#include "TinyGsmClientSIM800.h" +typedef TinyGsmSim800 TinyGsm; +typedef TinyGsmSim800::GsmClientSim800 TinyGsmClient; +typedef TinyGsmSim800::GsmClientSecureSim800 TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SIM808) || defined(TINY_GSM_MODEM_SIM868) +#include "TinyGsmClientSIM808.h" +typedef TinyGsmSim808 TinyGsm; +typedef TinyGsmSim808::GsmClientSim800 TinyGsmClient; +typedef TinyGsmSim808::GsmClientSecureSim800 TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SIM900) +#include "TinyGsmClientSIM800.h" +typedef TinyGsmSim800 TinyGsm; +typedef TinyGsmSim800::GsmClientSim800 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_SIM7000) +#include "TinyGsmClientSIM7000.h" +typedef TinyGsmSim7000 TinyGsm; +typedef TinyGsmSim7000::GsmClientSim7000 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_SIM7000SSL) +#include "TinyGsmClientSIM7000SSL.h" +typedef TinyGsmSim7000SSL TinyGsm; +typedef TinyGsmSim7000SSL::GsmClientSim7000SSL TinyGsmClient; +typedef TinyGsmSim7000SSL::GsmClientSecureSIM7000SSL TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SIM7070) || defined(TINY_GSM_MODEM_SIM7080) || \ + defined(TINY_GSM_MODEM_SIM7090) +#include "TinyGsmClientSIM7080.h" +typedef TinyGsmSim7080 TinyGsm; +typedef TinyGsmSim7080::GsmClientSim7080 TinyGsmClient; +typedef TinyGsmSim7080::GsmClientSecureSIM7080 TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SIM5320) || defined(TINY_GSM_MODEM_SIM5360) || \ + defined(TINY_GSM_MODEM_SIM5300) || defined(TINY_GSM_MODEM_SIM7100) +#include "TinyGsmClientSIM5360.h" +typedef TinyGsmSim5360 TinyGsm; +typedef TinyGsmSim5360::GsmClientSim5360 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_SIM7600) || defined(TINY_GSM_MODEM_SIM7800) || \ + defined(TINY_GSM_MODEM_SIM7500) +#include "TinyGsmClientSIM7600.h" +typedef TinyGsmSim7600 TinyGsm; +typedef TinyGsmSim7600::GsmClientSim7600 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_UBLOX) +#include "TinyGsmClientUBLOX.h" +typedef TinyGsmUBLOX TinyGsm; +typedef TinyGsmUBLOX::GsmClientUBLOX TinyGsmClient; +typedef TinyGsmUBLOX::GsmClientSecureUBLOX TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SARAR4) +#include "TinyGsmClientSaraR4.h" +typedef TinyGsmSaraR4 TinyGsm; +typedef TinyGsmSaraR4::GsmClientSaraR4 TinyGsmClient; +typedef TinyGsmSaraR4::GsmClientSecureR4 TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_M95) +#include "TinyGsmClientM95.h" +typedef TinyGsmM95 TinyGsm; +typedef TinyGsmM95::GsmClientM95 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_BG96) +#include "TinyGsmClientBG96.h" +typedef TinyGsmBG96 TinyGsm; +typedef TinyGsmBG96::GsmClientBG96 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_A6) || defined(TINY_GSM_MODEM_A7) +#include "TinyGsmClientA6.h" +typedef TinyGsmA6 TinyGsm; +typedef TinyGsmA6::GsmClientA6 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_M590) +#include "TinyGsmClientM590.h" +typedef TinyGsmM590 TinyGsm; +typedef TinyGsmM590::GsmClientM590 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_MC60) || defined(TINY_GSM_MODEM_MC60E) +#include "TinyGsmClientMC60.h" +typedef TinyGsmMC60 TinyGsm; +typedef TinyGsmMC60::GsmClientMC60 TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_ESP8266) +#define TINY_GSM_MODEM_HAS_WIFI +#include "TinyGsmClientESP8266.h" +typedef TinyGsmESP8266 TinyGsm; +typedef TinyGsmESP8266::GsmClientESP8266 TinyGsmClient; +typedef TinyGsmESP8266::GsmClientSecureESP8266 TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_XBEE) +#define TINY_GSM_MODEM_HAS_WIFI +#include "TinyGsmClientXBee.h" +typedef TinyGsmXBee TinyGsm; +typedef TinyGsmXBee::GsmClientXBee TinyGsmClient; +typedef TinyGsmXBee::GsmClientSecureXBee TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SEQUANS_MONARCH) +#include "TinyGsmClientSequansMonarch.h" +typedef TinyGsmSequansMonarch TinyGsm; +typedef TinyGsmSequansMonarch::GsmClientSequansMonarch TinyGsmClient; +typedef TinyGsmSequansMonarch::GsmClientSecureSequansMonarch + TinyGsmClientSecure; + +#else +#error "Please define GSM modem model" +#endif + +#endif // SRC_TINYGSMCLIENT_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientA6.h b/lib/TinyGSM/src/TinyGsmClientA6.h new file mode 100644 index 0000000..9226960 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientA6.h @@ -0,0 +1,580 @@ +/** + * @file TinyGsmClientA6.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTA6_H_ +#define SRC_TINYGSMCLIENTA6_H_ +// #pragma message("TinyGSM: TinyGsmClientA6") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 8 +#define TINY_GSM_NO_MODEM_BUFFER + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTime.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmA6 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmTime, + public TinyGsmBattery { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmTime; + friend class TinyGsmBattery; + + /* + * Inner Client + */ + public: + class GsmClientA6 : public GsmClient { + friend class TinyGsmA6; + + public: + GsmClientA6() {} + + explicit GsmClientA6(TinyGsmA6& modem, uint8_t = 0) { + init(&modem, -1); + } + + bool init(TinyGsmA6* modem, uint8_t = 0) { + this->at = modem; + this->mux = -1; + sock_connected = false; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + uint8_t newMux = -1; + sock_connected = at->modemConnect(host, port, &newMux, timeout_s); + if (sock_connected) { + mux = newMux; + at->sockets[mux] = this; + } + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + TINY_GSM_YIELD(); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(maxWaitMs); + rx.clear(); + } + void stop() override { + stop(1000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + // Doesn't support SSL + + /* + * Constructor + */ + public: + explicit TinyGsmA6(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientA6")); + + if (!testAT()) { return false; } + + // sendAT(GF("&FZ")); // Factory + Reset + // waitResponse(); + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + sendAT( + GF("+CMER=3,0,0,2")); // Set unsolicited result code output destination + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + bool factoryDefaultImpl() { + sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + waitResponse(); + sendAT(GF("&W")); // Write configuration + return waitResponse() == 1; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+RST=1")); + delay(3000); + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPOF")); + // +CPOF: MS OFF OK + return waitResponse() == 1; + } + + bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE; + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) + TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + String getLocalIPImpl() { + sendAT(GF("+CIFSR")); + String res; + if (waitResponse(10000L, res) != 1) { return ""; } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // TODO(?): wait AT+CGATT? + + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + if (!user) user = ""; + if (!pwd) pwd = ""; + sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + sendAT(GF("+CIPMUX=1")); + if (waitResponse() != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + // Shut the TCP/IP connection + sendAT(GF("+CIPSHUT")); + if (waitResponse(60000L) != 1) { return false; } + + for (int i = 0; i < 3; i++) { + sendAT(GF("+CGATT=0")); + if (waitResponse(5000L) == 1) { return true; } + } + + return false; + } + + String getOperatorImpl() { + sendAT(GF("+COPS=3,0")); // Set format + waitResponse(); + + sendAT(GF("+COPS?")); + if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { return ""; } + streamSkipUntil('"'); // Skip mode and format + String res = stream.readStringUntil('"'); + waitResponse(); + return res; + } + + /* + * SIM card functions + */ + protected: + String getSimCCIDImpl() { + sendAT(GF("+CCID")); + if (waitResponse(GF(GSM_NL "+SCID: SIM Card ID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + // Returns true on pick-up, false on error/busy + bool callNumberImpl(const String& number) { + if (number == GF("last")) { + sendAT(GF("DLST")); + } else { + sendAT(GF("D\""), number, "\";"); + } + + if (waitResponse(5000L) != 1) { return false; } + + if (waitResponse(60000L, GF(GSM_NL "+CIEV: \"CALL\",1"), + GF(GSM_NL "+CIEV: \"CALL\",0"), GFP(GSM_ERROR)) != 1) { + return false; + } + + int8_t rsp = waitResponse(60000L, GF(GSM_NL "+CIEV: \"SOUNDER\",0"), + GF(GSM_NL "+CIEV: \"CALL\",0")); + + int8_t rsp2 = waitResponse(300L, GF(GSM_NL "BUSY" GSM_NL), + GF(GSM_NL "NO ANSWER" GSM_NL)); + + return rsp == 1 && rsp2 == 0; + } + + // 0-9,*,#,A,B,C,D + bool dtmfSendImpl(char cmd, uint8_t duration_ms = 100) { + duration_ms = constrain(duration_ms, 100, 1000); + + // The duration parameter is not working, so we simulate it using delay.. + // TODO(?): Maybe there's another way... + + // sendAT(GF("+VTD="), duration_ms / 100); + // waitResponse(); + + sendAT(GF("+VTS="), cmd); + if (waitResponse(10000L) == 1) { + delay(duration_ms); + return true; + } + return false; + } + + /* + * Audio functions + */ + public: + bool audioSetHeadphones() { + sendAT(GF("+SNFS=0")); + return waitResponse() == 1; + } + + bool audioSetSpeaker() { + sendAT(GF("+SNFS=1")); + return waitResponse() == 1; + } + + bool audioMuteMic(bool mute) { + sendAT(GF("+CMUT="), mute); + return waitResponse() == 1; + } + + /* + * Messaging functions + */ + protected: + String sendUSSDImpl(const String& code) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CUSD=1,\""), code, GF("\",15")); + if (waitResponse(10000L) != 1) { return ""; } + if (waitResponse(GF(GSM_NL "+CUSD:")) != 1) { return ""; } + streamSkipUntil('"'); + String hex = stream.readStringUntil('"'); + streamSkipUntil(','); + int8_t dcs = streamGetIntBefore('\n'); + + if (dcs == 15) { + return TinyGsmDecodeHex7bit(hex); + } else if (dcs == 72) { + return TinyGsmDecodeHex16bit(hex); + } else { + return hex; + } + } + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + // Note - the clock probably has to be set manaually first + + /* + * Battery functions + */ + protected: + uint16_t getBattVoltageImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + // Needs a '?' after CBC, unlike most + int8_t getBattPercentImpl() { + sendAT(GF("+CBC?")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; } + streamSkipUntil(','); // Skip battery charge status + // Read battery charge level + int8_t res = streamGetIntBefore('\n'); + // Wait for final OK + waitResponse(); + return res; + } + + // Needs a '?' after CBC, unlike most + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + sendAT(GF("+CBC?")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; } + chargeState = streamGetIntBefore(','); + percent = streamGetIntBefore('\n'); + milliVolts = 0; + // Wait for final OK + waitResponse(); + return true; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t* mux, + int timeout_s = 75) { + uint32_t startMillis = millis(); + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + sendAT(GF("+CIPSTART="), GF("\"TCP"), GF("\",\""), host, GF("\","), port); + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPNUM:")) != 1) { return false; } + int8_t newMux = streamGetIntBefore('\n'); + + int8_t rsp = waitResponse( + (timeout_ms - (millis() - startMillis)), GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), GF("ALREADY CONNECT" GSM_NL)); + if (waitResponse() != 1) { return false; } + *mux = newMux; + + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(2000L, GF(GSM_NL ">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(10000L, GFP(GSM_OK), GF(GSM_NL "FAIL")) != 1) { return 0; } + return len; + } + + bool modemGetConnected(uint8_t) { + sendAT(GF("+CIPSTATUS")); // TODO(?) mux? + int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), + GF(",\"CLOSING\""), GF(",\"INITIAL\"")); + waitResponse(); + return 1 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+CIPRCV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore(','); + int16_t len_orig = len; + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (len > sockets[mux]->rx.free()) { + DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); + } else { + DBG("### Got: ", len, "->", sockets[mux]->rx.free()); + } + while (len--) { moveCharFromStreamToFifo(mux); } + // TODO(?) Deal with missing characters + if (len_orig > sockets[mux]->available()) { + DBG("### Fewer characters received than expected: ", + sockets[mux]->available(), " vs ", len_orig); + } + } + data = ""; + } else if (data.endsWith(GF("+TCPCLOSED:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientA6* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTA6_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientBG96.h b/lib/TinyGSM/src/TinyGsmClientBG96.h new file mode 100644 index 0000000..e696404 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientBG96.h @@ -0,0 +1,727 @@ +/** + * @file TinyGsmClientBG96.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Apr 2018 + */ + +#ifndef SRC_TINYGSMCLIENTBG96_H_ +#define SRC_TINYGSMCLIENTBG96_H_ +// #pragma message("TinyGSM: TinyGsmClientBG96") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 12 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmBG96 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmGPS, + public TinyGsmBattery, + public TinyGsmTemperature { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmGPS; + friend class TinyGsmBattery; + friend class TinyGsmTemperature; + + /* + * Inner Client + */ + public: + class GsmClientBG96 : public GsmClient { + friend class TinyGsmBG96; + + public: + GsmClientBG96() {} + + explicit GsmClientBG96(TinyGsmBG96& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmBG96* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + uint32_t startMillis = millis(); + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+QICLOSE="), mux); + sock_connected = false; + at->waitResponse((maxWaitMs - (millis() - startMillis))); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + /* + class GsmClientSecureBG96 : public GsmClientBG96 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmBG96& modem, uint8_t mux = 0) + : public GsmClient(modem, mux) + {} + + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmBG96(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientBG96")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Disable time and time zone URC's + sendAT(GF("+CTZR=0")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable automatic time zone update + sendAT(GF("+CTZU=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + if (!setPhoneFunctionality(1, true)) { return false; } + waitResponse(10000L, GF("APP RDY")); + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+QPOWD=1")); + waitResponse(300); // returns OK first + return waitResponse(300, GF("POWERED DOWN")) == 1; + } + + // When entering into sleep mode is enabled, DTR is pulled up, and WAKEUP_IN + // is pulled up, the module can directly enter into sleep mode.If entering + // into sleep mode is enabled, DTR is pulled down, and WAKEUP_IN is pulled + // down, there is a need to pull the DTR pin and the WAKEUP_IN pin up first, + // and then the module can enter into sleep mode. + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+QSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L, GF("OK")) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + // Check first for EPS registration + RegStatus epsStatus = (RegStatus)getRegistrationStatusXREG("CEREG"); + + // If we're connected on EPS, great! + if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) { + return epsStatus; + } else { + // Otherwise, check generic network status + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Configure the TCPIP Context + sendAT(GF("+QICSGP=1,1,\""), apn, GF("\",\""), user, GF("\",\""), pwd, + GF("\"")); + if (waitResponse() != 1) { return false; } + + // Activate GPRS/CSD Context + sendAT(GF("+QIACT=1")); + if (waitResponse(150000L) != 1) { return false; } + + // Attach to Packet Domain service - is this necessary? + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + sendAT(GF("+QIDEACT=1")); // Deactivate the bearer context + if (waitResponse(40000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + String getSimCCIDImpl() { + sendAT(GF("+QCCID")); + if (waitResponse(GF(GSM_NL "+QCCID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + // Can follow all of the phone call functions from the template + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ + protected: + // NOTE: As of application firmware version 01.016.01.016 triangulated + // locations can be obtained via the QuecLocator service and accompanying AT + // commands. As this is a separate paid service which I do not have access + // to, I am not implementing it here. + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl() { + sendAT(GF("+QGPS=1")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool disableGPSImpl() { + sendAT(GF("+QGPSEND")); + if (waitResponse() != 1) { return false; } + return true; + } + + // get the RAW GPS output + String getGPSrawImpl() { + sendAT(GF("+QGPSLOC=2")); + if (waitResponse(10000L, GF(GSM_NL "+QGPSLOC:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + sendAT(GF("+QGPSLOC=2")); + if (waitResponse(10000L, GF(GSM_NL "+QGPSLOC:")) != 1) { + // NOTE: Will return an error if the position isn't fixed + return false; + } + + // init variables + float ilat = 0; + float ilon = 0; + float ispeed = 0; + float ialt = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + + // UTC date & Time + ihour = streamGetIntLength(2); // Two digit hour + imin = streamGetIntLength(2); // Two digit minute + secondWithSS = streamGetFloatBefore(','); // 6 digit second with subseconds + + ilat = streamGetFloatBefore(','); // Latitude + ilon = streamGetFloatBefore(','); // Longitude + iaccuracy = streamGetFloatBefore(','); // Horizontal precision + ialt = streamGetFloatBefore(','); // Altitude from sea level + streamSkipUntil(','); // GNSS positioning mode + streamSkipUntil(','); // Course Over Ground based on true north + streamSkipUntil(','); // Speed Over Ground in Km/h + ispeed = streamGetFloatBefore(','); // Speed Over Ground in knots + + iday = streamGetIntLength(2); // Two digit day + imonth = streamGetIntLength(2); // Two digit month + iyear = streamGetIntBefore(','); // Two digit year + + iusat = streamGetIntBefore(','); // Number of satellites, + streamSkipUntil('\n'); // The error code of the operation. If it is not + // 0, it is the type of error. + + // Set pointers + if (lat != NULL) *lat = ilat; + if (lon != NULL) *lon = ilon; + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = 0; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + waitResponse(); // Final OK + return true; + } + + /* + * Time functions + */ + protected: + String getGSMDateTimeImpl(TinyGSMDateTimeFormat format) { + sendAT(GF("+QLTS=2")); + if (waitResponse(2000L, GF("+QLTS: \"")) != 1) { return ""; } + + String res; + + switch (format) { + case DATE_FULL: res = stream.readStringUntil('"'); break; + case DATE_TIME: + streamSkipUntil(','); + res = stream.readStringUntil('"'); + break; + case DATE_DATE: res = stream.readStringUntil(','); break; + } + waitResponse(); // Ends with OK + return res; + } + + // The BG96 returns UTC time instead of local time as other modules do in + // response to CCLK, so we're using QLTS where we can specifically request + // local time. + bool getNetworkTimeImpl(int* year, int* month, int* day, int* hour, + int* minute, int* second, float* timezone) { + sendAT(GF("+QLTS=2")); + if (waitResponse(2000L, GF("+QLTS: \"")) != 1) { return false; } + + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + int isec = 0; + int itimezone = 0; + + // Date & Time + iyear = streamGetIntBefore('/'); + imonth = streamGetIntBefore('/'); + iday = streamGetIntBefore(','); + ihour = streamGetIntBefore(':'); + imin = streamGetIntBefore(':'); + isec = streamGetIntLength(2); + char tzSign = stream.read(); + itimezone = streamGetIntBefore(','); + if (tzSign == '-') { itimezone = itimezone * -1; } + streamSkipUntil('\n'); // DST flag + + // Set pointers + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = isec; + if (timezone != NULL) *timezone = static_cast(itimezone) / 4.0; + + // Final OK + waitResponse(); // Ends with OK + return true; + } + + /* + * NTP server functions + */ + + byte NTPServerSyncImpl(String server = "pool.ntp.org", byte = -5) { + // Request network synchronization + // AT+QNTP=,[,][,] + sendAT(GF("+QNTP=1,\""), server, '"'); + if (waitResponse(10000L, GF("+QNTP:"))) { + String result = stream.readStringUntil(','); + streamSkipUntil('\n'); + result.trim(); + if (TinyGsmIsValidNumber(result)) { return result.toInt(); } + } else { + return -1; + } + return -1; + } + + String ShowNTPErrorImpl(byte error) TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * Battery functions + */ + protected: + // Can follow CBC as in the template + + /* + * Temperature functions + */ + protected: + // get temperature in degree celsius + uint16_t getTemperatureImpl() { + sendAT(GF("+QTEMP")); + if (waitResponse(GF(GSM_NL "+QTEMP:")) != 1) { return 0; } + // return temperature in C + uint16_t res = + streamGetIntBefore(','); // read PMIC (primary ic) temperature + streamSkipUntil(','); // skip XO temperature ?? + streamSkipUntil('\n'); // skip PA temperature ?? + // Wait for final OK + waitResponse(); + return res; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 150) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + // (1-16), (0-11), + // "TCP/UDP/TCP LISTENER/UDPSERVICE", "/", + // ,,(0-2; 0=buffer) + sendAT(GF("+QIOPEN=1,"), mux, GF(",\""), GF("TCP"), GF("\",\""), host, + GF("\","), port, GF(",0,0")); + waitResponse(); + + if (waitResponse(timeout_ms, GF(GSM_NL "+QIOPEN:")) != 1) { return false; } + + if (streamGetIntBefore(',') != mux) { return false; } + // Read status + return (0 == streamGetIntBefore('\n')); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+QISEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; } + // TODO(?): Wait for ACK? AT+QISEND=id,0 + return len; + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+QIRD="), mux, ',', (uint16_t)size); + if (waitResponse(GF("+QIRD:")) != 1) { return 0; } + int16_t len = streamGetIntBefore('\n'); + + for (int i = 0; i < len; i++) { moveCharFromStreamToFifo(mux); } + waitResponse(); + // DBG("### READ:", len, "from", mux); + sockets[mux]->sock_available = modemGetAvailable(mux); + return len; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+QIRD="), mux, GF(",0")); + size_t result = 0; + if (waitResponse(GF("+QIRD:")) == 1) { + streamSkipUntil(','); // Skip total received + streamSkipUntil(','); // Skip have read + result = streamGetIntBefore('\n'); + if (result) { DBG("### DATA AVAILABLE:", result, "on", mux); } + waitResponse(); + } + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+QISTATE=1,"), mux); + // +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" + + if (waitResponse(GF("+QISTATE:")) != 1) { return false; } + + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip socket type + streamSkipUntil(','); // Skip remote ip + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + int8_t res = streamGetIntBefore(','); // socket state + + waitResponse(); + + // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing + return 2 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+QIURC:"))) { + streamSkipUntil('\"'); + String urc = stream.readStringUntil('\"'); + streamSkipUntil(','); + if (urc == "recv") { + int8_t mux = streamGetIntBefore('\n'); + DBG("### URC RECV:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + } else if (urc == "closed") { + int8_t mux = streamGetIntBefore('\n'); + DBG("### URC CLOSE:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + } else { + streamSkipUntil('\n'); + } + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientBG96* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTBG96_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientESP8266.h b/lib/TinyGSM/src/TinyGsmClientESP8266.h new file mode 100644 index 0000000..92763a0 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientESP8266.h @@ -0,0 +1,485 @@ +/** + * @file TinyGsmClientESP8266.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTESP8266_H_ +#define SRC_TINYGSMCLIENTESP8266_H_ +// #pragma message("TinyGSM: TinyGsmClientESP8266") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 5 +#define TINY_GSM_NO_MODEM_BUFFER + +#include "TinyGsmModem.tpp" +#include "TinyGsmSSL.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmWifi.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +static uint8_t TINY_GSM_TCP_KEEP_ALIVE = 120; + +// status of ESP8266 station interface +// 2 : ESP8266 station connected to an AP and has obtained IP +// 3 : ESP8266 station created a TCP or UDP transmission +// 4 : the TCP or UDP transmission of ESP8266 station disconnected +// 5 : ESP8266 station did NOT connect to an AP +enum RegStatus { + REG_OK_IP = 2, + REG_OK_TCP = 3, + REG_OK_NO_TCP = 4, + REG_DENIED = 5, + REG_UNKNOWN = 6, +}; + +class TinyGsmESP8266 : public TinyGsmModem, + public TinyGsmWifi, + public TinyGsmTCP, + public TinyGsmSSL { + friend class TinyGsmModem; + friend class TinyGsmWifi; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + + /* + * Inner Client + */ + public: + class GsmClientESP8266 : public GsmClient { + friend class TinyGsmESP8266; + + public: + GsmClientESP8266() {} + + explicit GsmClientESP8266(TinyGsmESP8266& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmESP8266* modem, uint8_t mux = 0) { + this->at = modem; + sock_connected = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + TINY_GSM_YIELD(); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(maxWaitMs); + rx.clear(); + } + void stop() override { + stop(5000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + public: + class GsmClientSecureESP8266 : public GsmClientESP8266 { + public: + GsmClientSecureESP8266() {} + + explicit GsmClientSecureESP8266(TinyGsmESP8266& modem, uint8_t mux = 0) + : GsmClientESP8266(modem, mux) {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmESP8266(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientESP8266")); + + if (!testAT()) { return false; } + if (pin && strlen(pin) > 0) { + DBG("ESP8266 modules do not use an unlock pin!"); + } + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + sendAT(GF("+CIPMUX=1")); // Enable Multiple Connections + if (waitResponse() != 1) { return false; } + sendAT(GF("+CWMODE=1")); // Put into "station" mode + if (waitResponse() != 1) { + sendAT(GF("+CWMODE_CUR=1")); // Attempt "current" station mode command + // for some firmware variants if needed + if (waitResponse() != 1) { return false; } + } + DBG(GF("### Modem:"), getModemName()); + return true; + } + + String getModemNameImpl() { + return "ESP8266"; + } + + void setBaudImpl(uint32_t baud) { + sendAT(GF("+UART_CUR="), baud, "8,1,0,0"); + if (waitResponse() != 1) { + sendAT(GF("+UART="), baud, + "8,1,0,0"); // Really old firmwares might need this + // if (waitResponse() != 1) { + // sendAT(GF("+IPR="), baud); // First release firmwares might need + // this + waitResponse(); + // } + } + } + + bool factoryDefaultImpl() { + sendAT(GF("+RESTORE")); + return waitResponse() == 1; + } + + String getModemInfoImpl() { + sendAT(GF("+GMR")); + String res; + if (waitResponse(1000L, res) != 1) { return ""; } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, " "); + res.trim(); + return res; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+RST")); + if (waitResponse(10000L) != 1) { return false; } + if (waitResponse(10000L, GF(GSM_NL "ready" GSM_NL)) != 1) { return false; } + delay(500); + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+GSLP=0")); // Power down indefinitely - until manually reset! + return waitResponse() == 1; + } + + bool radioOffImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED; + + bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE; + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) + TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + sendAT(GF("+CIPSTATUS")); + if (waitResponse(3000, GF("STATUS:")) != 1) return REG_UNKNOWN; + int8_t status = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), + GF("5")); + waitResponse(); // Returns an OK after the status + return (RegStatus)status; + } + + protected: + int8_t getSignalQualityImpl() { + sendAT(GF("+CWJAP?")); + int8_t res1 = waitResponse(GF("No AP"), GF("+CWJAP:")); + if (res1 != 2) { + waitResponse(); + sendAT(GF("+CWJAP_CUR?")); // attempt "current" as used by some firmware + // versions + int8_t res1 = waitResponse(GF("No AP"), GF("+CWJAP_CUR:")); + if (res1 != 2) { + waitResponse(); + return 0; + } + } + streamSkipUntil(','); // Skip SSID + streamSkipUntil(','); // Skip BSSID/MAC address + streamSkipUntil(','); // Skip Chanel number + int8_t res2 = stream.parseInt(); // Read RSSI + waitResponse(); // Returns an OK after the value + return res2; + } + + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + if (s == REG_OK_IP || s == REG_OK_TCP) { + // with these, we're definitely connected + return true; + } else if (s == REG_OK_NO_TCP) { + // with this, we may or may not be connected + if (getLocalIP() == "") { + return false; + } else { + return true; + } + } else { + return false; + } + } + + String getLocalIPImpl() { + // attempt with and without 'current' flag + sendAT(GF("+CIPSTA?")); + int8_t res1 = waitResponse(GF("ERROR"), GF("+CIPSTA:")); + if (res1 != 2) { + sendAT(GF("+CIPSTA_CUR?")); + res1 = waitResponse(GF("ERROR"), GF("+CIPSTA_CUR:")); + if (res1 != 2) { return ""; } + } + String res2 = stream.readStringUntil('\n'); + res2.replace("ip:", ""); // newer firmwares have this + res2.replace("\"", ""); + res2.trim(); + waitResponse(); + return res2; + } + + /* + * WiFi functions + */ + protected: + bool networkConnectImpl(const char* ssid, const char* pwd) { + // attempt first without than with the 'current' flag used in some firmware + // versions + sendAT(GF("+CWJAP=\""), ssid, GF("\",\""), pwd, GF("\"")); + if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) { + sendAT(GF("+CWJAP_CUR=\""), ssid, GF("\",\""), pwd, GF("\"")); + if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) { + return false; + } + } + + return true; + } + + bool networkDisconnectImpl() { + sendAT(GF("+CWQAP")); + bool retVal = waitResponse(10000L) == 1; + waitResponse(GF("WIFI DISCONNECT")); + return retVal; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + if (ssl) { + sendAT(GF("+CIPSSLSIZE=4096")); + waitResponse(); + } + sendAT(GF("+CIPSTART="), mux, ',', ssl ? GF("\"SSL") : GF("\"TCP"), + GF("\",\""), host, GF("\","), port, GF(","), + TINY_GSM_TCP_KEEP_ALIVE); + // TODO(?): Check mux + int8_t rsp = waitResponse(timeout_ms, GFP(GSM_OK), GFP(GSM_ERROR), + GF("ALREADY CONNECT")); + // if (rsp == 3) waitResponse(); + // May return "ERROR" after the "ALREADY CONNECT" + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(10000L, GF(GSM_NL "SEND OK" GSM_NL)) != 1) { return 0; } + return len; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+CIPSTATUS")); + if (waitResponse(3000, GF("STATUS:")) != 1) { return false; } + int8_t status = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), + GF("5")); + if (status != 3) { + // if the status is anything but 3, there are no connections open + waitResponse(); // Returns an OK after the status + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + if (sockets[muxNo]) { sockets[muxNo]->sock_connected = false; } + } + return false; + } + bool verified_connections[TINY_GSM_MUX_COUNT] = {0, 0, 0, 0, 0}; + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + uint8_t has_status = waitResponse(GF("+CIPSTATUS:"), GFP(GSM_OK), + GFP(GSM_ERROR)); + if (has_status == 1) { + int8_t returned_mux = streamGetIntBefore(','); + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip type + streamSkipUntil(','); // Skip remote IP + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + streamSkipUntil('\n'); // Skip client/server type + verified_connections[returned_mux] = 1; + } + if (has_status == 2) break; // once we get to the ok, stop + } + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + if (sockets[muxNo]) { + sockets[muxNo]->sock_connected = verified_connections[muxNo]; + } + } + return verified_connections[mux]; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), GsmConstStr r3 = NULL, + GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+IPD,"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore(':'); + int16_t len_orig = len; + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (len > sockets[mux]->rx.free()) { + DBG("### Buffer overflow: ", len, "received vs", + sockets[mux]->rx.free(), "available"); + } else { + // DBG("### Got Data: ", len, "on", mux); + } + while (len--) { moveCharFromStreamToFifo(mux); } + // TODO(SRGDamia1): deal with buffer overflow/missed characters + if (len_orig > sockets[mux]->available()) { + DBG("### Fewer characters received than expected: ", + sockets[mux]->available(), " vs ", len_orig); + } + } + data = ""; + } else if (data.endsWith(GF("CLOSED"))) { + int8_t muxStart = + TinyGsmMax(0, data.lastIndexOf(GSM_NL, data.length() - 8)); + int8_t coma = data.indexOf(',', muxStart); + int8_t mux = data.substring(muxStart, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), GsmConstStr r3 = NULL, + GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), GsmConstStr r3 = NULL, + GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientESP8266* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTESP8266_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientM590.h b/lib/TinyGSM/src/TinyGsmClientM590.h new file mode 100644 index 0000000..e921447 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientM590.h @@ -0,0 +1,473 @@ +/** + * @file TinyGsmClientM590.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTM590_H_ +#define SRC_TINYGSMCLIENTM590_H_ +// #pragma message("TinyGSM: TinyGsmClientM590") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 2 +#define TINY_GSM_NO_MODEM_BUFFER + +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTime.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 3, + REG_DENIED = 2, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmM590 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSMS, + public TinyGsmTime { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSMS; + friend class TinyGsmTime; + + /* + * Inner Client + */ + public: + class GsmClientM590 : public GsmClient { + friend class TinyGsmM590; + + public: + GsmClientM590() {} + + explicit GsmClientM590(TinyGsmM590& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmM590* modem, uint8_t mux = 0) { + this->at = modem; + sock_connected = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + TINY_GSM_YIELD(); + at->sendAT(GF("+TCPCLOSE="), mux); + sock_connected = false; + at->waitResponse(maxWaitMs); + rx.clear(); + } + void stop() override { + stop(1000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Constructor + */ + public: + explicit TinyGsmM590(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientM590")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + // Doesn't support CGMI + String getModemNameImpl() { + return "Neoway M590"; + } + + // Extra stuff here - pwr save, internal stack + bool factoryDefaultImpl() { + sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + waitResponse(); + sendAT(GF("+ICF=3,1")); // 8 data 0 parity 1 stop + waitResponse(); + sendAT(GF("+ENPWRSAVE=0")); // Disable PWR save + waitResponse(); + sendAT(GF("+XISP=0")); // Use internal stack + waitResponse(); + sendAT(GF("&W")); // Write configuration + return waitResponse() == 1; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + if (!setPhoneFunctionality(15)) { return false; } + // MODEM:STARTUP + waitResponse(60000L, GF(GSM_NL "+PBREADY" GSM_NL)); + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPWROFF")); + return waitResponse(3000L) == 1; + } + + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+ENPWRSAVE="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + String getLocalIPImpl() { + sendAT(GF("+XIIC?")); + if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { return ""; } + streamSkipUntil(','); + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + sendAT(GF("+XISP=0")); + waitResponse(); + + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + if (!user) user = ""; + if (!pwd) pwd = ""; + sendAT(GF("+XGAUTH=1,1,\""), user, GF("\",\""), pwd, GF("\"")); + waitResponse(); + + sendAT(GF("+XIIC=1")); + waitResponse(); + + const uint32_t timeout_ms = 60000L; + for (uint32_t start = millis(); millis() - start < timeout_ms;) { + if (isGprsConnected()) { + // goto set_dns; // TODO + return true; + } + delay(500); + } + return false; + + // set_dns: // TODO + // sendAT(GF("+DNSSERVER=1,8.8.8.8")); + // waitResponse(); + // + // sendAT(GF("+DNSSERVER=2,8.8.4.4")); + // waitResponse(); + + return true; + } + + bool gprsDisconnectImpl() { + // TODO(?): There is no command in AT command set + // XIIC=0 does not work + return true; + } + + bool isGprsConnectedImpl() { + sendAT(GF("+XIIC?")); + if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { return false; } + int8_t res = streamGetIntBefore(','); + waitResponse(); + return res == 1; + } + + /* + * SIM card functions + */ + protected: + // Able to follow all SIM card functions as inherited from the template + + /* + * Messaging functions + */ + protected: + bool sendSMS_UTF16Impl(const String& number, const void* text, + size_t len) TINY_GSM_ATTR_NOT_AVAILABLE; + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool, + int timeout_s = 75) { + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + for (int i = 0; i < 3; i++) { // TODO(?): no need for loop? + String ip = dnsIpQuery(host); + + sendAT(GF("+TCPSETUP="), mux, GF(","), ip, GF(","), port); + int8_t rsp = waitResponse(timeout_ms, GF(",OK" GSM_NL), + GF(",FAIL" GSM_NL), + GF("+TCPSETUP:Error" GSM_NL)); + if (1 == rsp) { + return true; + } else if (3 == rsp) { + sendAT(GF("+TCPCLOSE="), mux); + waitResponse(); + } + delay(1000); + } + return false; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+TCPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.write(static_cast(0x0D)); + stream.flush(); + if (waitResponse(30000L, GF(GSM_NL "+TCPSEND:")) != 1) { return 0; } + streamSkipUntil('\n'); + return len; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+CIPSTATUS="), mux); + int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), + GF(",\"CLOSING\""), GF(",\"INITIAL\"")); + waitResponse(); + return 1 == res; + } + + String dnsIpQuery(const char* host) { + sendAT(GF("+DNS=\""), host, GF("\"")); + if (waitResponse(10000L, GF(GSM_NL "+DNS:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(GF("+DNS:OK" GSM_NL)); + res.trim(); + return res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+TCPRECV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore(','); + int16_t len_orig = len; + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (len > sockets[mux]->rx.free()) { + DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free()); + } else { + DBG("### Got: ", len, "->", sockets[mux]->rx.free()); + } + while (len--) { moveCharFromStreamToFifo(mux); } + // TODO(?): Handle lost characters + if (len_orig > sockets[mux]->available()) { + DBG("### Fewer characters received than expected: ", + sockets[mux]->available(), " vs ", len_orig); + } + } + data = ""; + } else if (data.endsWith(GF("+TCPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientM590* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTM590_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientM95.h b/lib/TinyGSM/src/TinyGsmClientM95.h new file mode 100644 index 0000000..538969a --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientM95.h @@ -0,0 +1,636 @@ +/** + * @file TinyGsmClientM95.h + * @author Volodymyr Shymanskyy, Pacman Pereira, and Replicade Ltd. + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy, (c)2017 Replicade Ltd. + * + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTM95_H_ +#define SRC_TINYGSMCLIENTM95_H_ +// #pragma message("TinyGSM: TinyGsmClientM95") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 6 +#define TINY_GSM_BUFFER_READ_NO_CHECK + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmM95 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmTime, + public TinyGsmBattery, + public TinyGsmTemperature { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmTime; + friend class TinyGsmBattery; + friend class TinyGsmTemperature; + + /* + * Inner Client + */ + public: + class GsmClientM95 : public GsmClient { + friend class TinyGsmM95; + + public: + GsmClientM95() {} + + explicit GsmClientM95(TinyGsmM95& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmM95* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + sock_connected = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + uint32_t startMillis = millis(); + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+QICLOSE="), mux); + sock_connected = false; + at->waitResponse((maxWaitMs - (millis() - startMillis)), GF("CLOSED"), + GF("CLOSE OK"), GF("ERROR")); + } + void stop() override { + stop(75000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + /* + class GsmClientSecureM95 : public GsmClientM95 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmm95& modem, uint8_t mux = 0) + : GsmClient(modem, mux) + {} + + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmM95(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientM95")); + + if (!testAT()) { return false; } + + // sendAT(GF("&FZ")); // Factory + Reset + // waitResponse(); + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable network time synchronization + sendAT(GF("+QNITZ=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L, GF("NORMAL POWER DOWN"), GF("OK"), GF("FAIL")) == + 3) { + return false; + } + sendAT(GF("+CFUN=1")); + if (waitResponse(10000L, GF("Call Ready"), GF("OK"), GF("FAIL")) == 3) { + return false; + } + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+QPOWD=1")); + return waitResponse(300, GF("NORMAL POWER DOWN")) == 1; + } + + // When entering into sleep mode is enabled, DTR is pulled up, and WAKEUP_IN + // is pulled up, the module can directly enter into sleep mode.If entering + // into sleep mode is enabled, DTR is pulled down, and WAKEUP_IN is pulled + // down, there is a need to pull the DTR pin and the WAKEUP_IN pin up first, + // and then the module can enter into sleep mode. + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+QSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) + TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + void setHostFormat(bool useDottedQuad) { + if (useDottedQuad) { + sendAT(GF("+QIDNSIP=0")); + } else { + sendAT(GF("+QIDNSIP=1")); + } + waitResponse(); + } + + String getLocalIPImpl() { + sendAT(GF("+QILOCIP")); + streamSkipUntil('\n'); + String res = stream.readStringUntil('\n'); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // select foreground context 0 = VIRTUAL_UART_1 + sendAT(GF("+QIFGCNT=0")); + if (waitResponse() != 1) { return false; } + + // Select GPRS (=1) as the Bearer + sendAT(GF("+QICSGP=1,\""), apn, GF("\",\""), user, GF("\",\""), pwd, + GF("\"")); + if (waitResponse() != 1) { return false; } + + // Select TCP/IP transfer mode - NOT transparent mode + sendAT(GF("+QIMODE=0")); + if (waitResponse() != 1) { return false; } + + // Enable multiple TCP/IP connections + sendAT(GF("+QIMUX=1")); + if (waitResponse() != 1) { return false; } + + // Start TCPIP Task and Set APN, User Name and Password + sendAT("+QIREGAPP=\"", apn, "\",\"", user, "\",\"", pwd, "\""); + if (waitResponse() != 1) { return false; } + + // Activate GPRS/CSD Context + sendAT(GF("+QIACT")); + if (waitResponse(60000L) != 1) { return false; } + + // Check that we have a local IP address + if (localIP() == IPAddress(0, 0, 0, 0)) { return false; } + + // Set Method to Handle Received TCP/IP Data + // Mode = 1 - Output a notification when data is received + // +QIRDI: ,, + sendAT(GF("+QINDI=1")); + if (waitResponse() != 1) { return false; } + + // // Request an IP header for received data + // // "IPD(data length):" + // sendAT(GF("+QIHEAD=1")); + // if (waitResponse() != 1) { + // return false; + // } + // + // // Do NOT show the IP address of the sender when receiving data + // // The format to show the address is: RECV FROM: : + // sendAT(GF("+QISHOWRA=0")); + // if (waitResponse() != 1) { + // return false; + // } + // + // // Do NOT show the protocol type at the end of the header for received + // data + // // IPD(data length)(TCP/UDP): + // sendAT(GF("+QISHOWPT=0")); + // if (waitResponse() != 1) { + // return false; + // } + // + // // Do NOT show the destination address before receiving data + // // The format to show the address is: TO: + // sendAT(GF("+QISHOWLA=0")); + // if (waitResponse() != 1) { + // return false; + // } + + return true; + } + + bool gprsDisconnectImpl() { + sendAT(GF("+QIDEACT")); // Deactivate the bearer context + return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1; + } + + /* + * SIM card functions + */ + protected: + String getSimCCIDImpl() { + sendAT(GF("+QCCID")); + if (waitResponse(GF(GSM_NL "+QCCID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + // Can follow all of the phone call functions from the template + + /* + * Messaging functions + */ + protected: + // Can follow all template functions + + public: + /** Delete all SMS */ + bool deleteAllSMS() { + sendAT(GF("+QMGDA=6")); + if (waitResponse(waitResponse(60000L, GF("OK"), GF("ERROR")) == 1)) { + return true; + } + return false; + } + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * Battery functions + */ + // Can follow the battery functions in the template + + /* + * Temperature functions + */ + protected: + float getTemperatureImpl() { + sendAT(GF("+QTEMP?")); + if (waitResponse(GF(GSM_NL "+QTEMP:")) != 1) { + return static_cast(-9999); + } + streamSkipUntil(','); // Skip mode + // Read charge of thermistor + // milliVolts = streamGetIntBefore(','); + streamSkipUntil(','); // Skip thermistor charge + float temp = streamGetFloatBefore('\n'); + // Wait for final OK + waitResponse(); + return temp; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + sendAT(GF("+QIOPEN="), mux, GF(",\""), GF("TCP"), GF("\",\""), host, + GF("\","), port); + int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL)); + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+QISEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; } + + // bool allAcknowledged = false; + // // bool failed = false; + // while ( !allAcknowledged ) { + // sendAT( GF("+QISACK")); + // if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) { + // return -1; + // } else { + // streamSkipUntil(','); // Skip total length sent on connection + // streamSkipUntil(','); // Skip length already acknowledged by remote + // // Make sure the total length un-acknowledged is 0 + // if ( streamGetIntBefore('\n') == 0 ) { + // allAcknowledged = true; + // } + // } + // } + // waitResponse(5000L); + + return len; // TODO(?): get len/ack properly + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; + // TODO(?): Does this work???? + // AT+QIRD=,,, + // id = GPRS context number = 0, set in GPRS connect + // sc = role in connection = 1, client of connection + // sid = index of connection = mux + // len = maximum length of data to retrieve + sendAT(GF("+QIRD=0,1,"), mux, ',', (uint16_t)size); + // If it replies only OK for the write command, it means there is no + // received data in the buffer of the connection. + int8_t res = waitResponse(GF("+QIRD:"), GFP(GSM_OK), GFP(GSM_ERROR)); + if (res == 1) { + streamSkipUntil(':'); // skip IP address + streamSkipUntil(','); // skip port + streamSkipUntil(','); // skip connection type (TCP/UDP) + // read the real length of the retrieved data + uint16_t len = streamGetIntBefore('\n'); + // We have no way of knowing in advance how much data will be in the + // buffer so when data is received we always assume the buffer is + // completely full. Chances are, this is not true and there's really not + // that much there. In that case, make sure we make sure we re-set the + // amount of data available. + if (len < size) { sockets[mux]->sock_available = len; } + for (uint16_t i = 0; i < len; i++) { + moveCharFromStreamToFifo(mux); + sockets[mux]->sock_available--; + // ^^ One less character available after moving from modem's FIFO to our + // FIFO + } + waitResponse(); // ends with an OK + // DBG("### READ:", len, "from", mux); + return len; + } else { + sockets[mux]->sock_available = 0; + return 0; + } + } + + // Not possible to check the number of characters remaining in buffer + size_t modemGetAvailable(uint8_t) { + return 0; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+QISTATE=1,"), mux); + // +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" + + if (waitResponse(GF("+QISTATE:")) != 1) { return false; } + + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip socket type + streamSkipUntil(','); // Skip remote ip + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + int8_t res = streamGetIntBefore(','); // socket state + + waitResponse(); + + // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing + return 2 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+QIRDI:"))) { + streamSkipUntil(','); // Skip the context + streamSkipUntil(','); // Skip the role + int8_t mux = streamGetIntBefore('\n'); + // DBG("### Got Data:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + // We have no way of knowing how much data actually came in, so + // we set the value to 1500, the maximum possible size. + sockets[mux]->sock_available = 1500; + } + data = ""; + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+QNITZ:"))) { + streamSkipUntil('\n'); // URC for time sync + data = ""; + DBG("### Network time updated."); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientM95* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTM95_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientMC60.h b/lib/TinyGSM/src/TinyGsmClientMC60.h new file mode 100644 index 0000000..abc5488 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientMC60.h @@ -0,0 +1,622 @@ +/** + * @file TinyGsmClientMC60.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + * + * @MC60 support added by Tamas Dajka 2017.10.15 - with fixes by Sara Damiano + * + */ + +#ifndef SRC_TINYGSMCLIENTMC60_H_ +#define SRC_TINYGSMCLIENTMC60_H_ +// #pragma message("TinyGSM: TinyGsmClientMC60") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 6 +#define TINY_GSM_BUFFER_READ_NO_CHECK + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTime.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmMC60 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmTime, + public TinyGsmBattery { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmTime; + friend class TinyGsmBattery; + + /* + * Inner Client + */ + public: + class GsmClientMC60 : public GsmClient { + friend class TinyGsmMC60; + + public: + GsmClientMC60() {} + + explicit GsmClientMC60(TinyGsmMC60& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmMC60* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + sock_connected = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + uint32_t startMillis = millis(); + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+QICLOSE="), mux); + sock_connected = false; + at->waitResponse((maxWaitMs - (millis() - startMillis)), GF("CLOSED"), + GF("CLOSE OK"), GF("ERROR")); + } + void stop() override { + stop(75000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + /* + class GsmClientSecureMC60 : public GsmClientMC60 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmMC60& modem, uint8_t mux = 0) + : GsmClient(modem, mux) + {} + + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmMC60(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientMC60")); + + if (!testAT()) { return false; } + + // sendAT(GF("&FZ")); // Factory + Reset + // waitResponse(); + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable network time synchronization + sendAT(GF("+QNITZ=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + if (!setPhoneFunctionality(0)) { return false; } + if (!setPhoneFunctionality(1, true)) { return false; } + delay(3000); + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+QPOWD=1")); + return waitResponse(GF("NORMAL POWER DOWN")) == 1; + } + + // When entering into sleep mode is enabled, DTR is pulled up, and WAKEUP_IN + // is pulled up, the module can directly enter into sleep mode.If entering + // into sleep mode is enabled, DTR is pulled down, and WAKEUP_IN is pulled + // down, there is a need to pull the DTR pin and the WAKEUP_IN pin up first, + // and then the module can enter into sleep mode. + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+QSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + String getLocalIPImpl() { + sendAT(GF("+QILOCIP")); + streamSkipUntil('\n'); + String res = stream.readStringUntil('\n'); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // select foreground context 0 = VIRTUAL_UART_1 + sendAT(GF("+QIFGCNT=0")); + if (waitResponse() != 1) { return false; } + + // Select GPRS (=1) as the Bearer + sendAT(GF("+QICSGP=1,\""), apn, GF("\",\""), user, GF("\",\""), pwd, + GF("\"")); + if (waitResponse() != 1) { return false; } + + // Define PDP context - is this necessary? + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Activate PDP context - is this necessary? + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + // Select TCP/IP transfer mode - NOT transparent mode + sendAT(GF("+QIMODE=0")); + if (waitResponse() != 1) { return false; } + + // Enable multiple TCP/IP connections + sendAT(GF("+QIMUX=1")); + if (waitResponse() != 1) { return false; } + + // Modem is used as a client + sendAT(GF("+QISRVC=1")); + if (waitResponse() != 1) { return false; } + + // Start TCPIP Task and Set APN, User Name and Password + sendAT("+QIREGAPP=\"", apn, "\",\"", user, "\",\"", pwd, "\""); + if (waitResponse() != 1) { return false; } + + // Activate GPRS/CSD Context + sendAT(GF("+QIACT")); + if (waitResponse(60000L) != 1) { return false; } + + // Check that we have a local IP address + if (localIP() == IPAddress(0, 0, 0, 0)) { return false; } + + // Set Method to Handle Received TCP/IP Data + // Mode=2 - Output a notification statement: + // +QIRDI: ,,,,,< tlen> + sendAT(GF("+QINDI=2")); + if (waitResponse() != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + sendAT(GF("+QIDEACT")); // Deactivate the bearer context + return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1; + } + + /* + * SIM card functions + */ + protected: + SimStatus getSimStatusImpl(uint32_t timeout_ms = 10000L) { + for (uint32_t start = millis(); millis() - start < timeout_ms;) { + sendAT(GF("+CPIN?")); + if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) { + delay(1000); + continue; + } + int8_t status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"), + GF("NOT INSERTED"), GF("PH_SIM PIN"), + GF("PH_SIM PUK")); + waitResponse(); + switch (status) { + case 2: + case 3: return SIM_LOCKED; + case 5: + case 6: return SIM_ANTITHEFT_LOCKED; + case 1: return SIM_READY; + default: return SIM_ERROR; + } + } + return SIM_ERROR; + } + + /* + * Phone Call functions + */ + protected: + // Can follow all of the phone call functions from the template + + /* + * Messaging functions + */ + protected: + // Can follow all template functions + + public: + /** Delete all SMS */ + bool deleteAllSMS() { + sendAT(GF("+QMGDA=6")); + if (waitResponse(waitResponse(60000L, GF("OK"), GF("ERROR")) == 1)) { + return true; + } + return false; + } + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * Battery functions + */ + // Can follow battery functions as in the template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + + // By default, MC60 expects IP address as 'host' parameter. + // If it is a domain name, "AT+QIDNSIP=1" should be executed. + // "AT+QIDNSIP=0" is for dotted decimal IP address. + IPAddress addr; + sendAT(GF("+QIDNSIP="), + (TinyGsmIpFromString(host) == IPAddress(0, 0, 0, 0) ? 0 : 1)); + if (waitResponse() != 1) { return false; } + + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + sendAT(GF("+QIOPEN="), mux, GF(",\""), GF("TCP"), GF("\",\""), host, + GF("\","), port); + int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL)); + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+QISEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; } + + bool allAcknowledged = false; + // bool failed = false; + while (!allAcknowledged) { + sendAT(GF("+QISACK="), mux); // If 'mux' is not specified, MC60 returns + // 'ERRROR' (for QIMUX == 1) + if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) { + return -1; + } else { + streamSkipUntil(','); /** Skip total */ + streamSkipUntil(','); /** Skip acknowledged data size */ + if (streamGetIntBefore('\n') == 0) { allAcknowledged = true; } + } + } + waitResponse(5000L); + + // streamSkipUntil(','); // Skip mux + // return streamGetIntBefore('\n'); + + return len; // TODO(?): verify len/ack + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; + // TODO(?): Does this even work???? + // AT+QIRD=,,, + // id = GPRS context number = 0, set in GPRS connect + // sc = role in connection = 1, client of connection + // sid = index of connection = mux + // len = maximum length of data to retrieve + sendAT(GF("+QIRD=0,1,"), mux, ',', (uint16_t)size); + // If it replies only OK for the write command, it means there is no + // received data in the buffer of the connection. + int8_t res = waitResponse(GF("+QIRD:"), GFP(GSM_OK), GFP(GSM_ERROR)); + if (res == 1) { + streamSkipUntil(':'); // skip IP address + streamSkipUntil(','); // skip port + streamSkipUntil(','); // skip connection type (TCP/UDP) + // read the real length of the retrieved data + uint16_t len = streamGetIntBefore('\n'); + // It's possible that the real length available is less than expected + // This is quite likely if the buffer is broken into packets - which may + // be different sizes. + // If so, make sure we make sure we re-set the amount of data available. + if (len < size) { sockets[mux]->sock_available = len; } + for (uint16_t i = 0; i < len; i++) { + moveCharFromStreamToFifo(mux); + sockets[mux]->sock_available--; + // ^^ One less character available after moving from modem's FIFO to our + // FIFO + } + waitResponse(); // ends with an OK + // DBG("### READ:", len, "from", mux); + return len; + } else { + sockets[mux]->sock_available = 0; + return 0; + } + } + + // Not possible to check the number of characters remaining in buffer + size_t modemGetAvailable(uint8_t) { + return 0; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+QISTATE=1,"), mux); + // +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" + + if (waitResponse(GF("+QISTATE:")) != 1) { return false; } + + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip socket type + streamSkipUntil(','); // Skip remote ip + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + int8_t res = streamGetIntBefore(','); // socket state + + waitResponse(); + + // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing + return 2 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + String r6s(r6); r6s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s, ",", r6s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (r6 && data.endsWith(r6)) { + index = 6; + goto finish; + } else if (data.endsWith( + GF(GSM_NL "+QIRDI:"))) { // TODO(?): QIRD? or QIRDI? + // +QIRDI: ,,,,,< tlen> + streamSkipUntil(','); // Skip the context + streamSkipUntil(','); // Skip the role + // read the connection id + int8_t mux = streamGetIntBefore(','); + // read the number of packets in the buffer + int8_t num_packets = streamGetIntBefore(','); + // read the length of the current packet + streamSkipUntil( + ','); // Skip the length of the current package in the buffer + int16_t len_total = + streamGetIntBefore('\n'); // Total length of all packages + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux] && + num_packets >= 0 && len_total >= 0) { + sockets[mux]->sock_available = len_total; + } + data = ""; + // DBG("### Got Data:", len_total, "on", mux); + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+QNITZ:"))) { + streamSkipUntil('\n'); // URC for time sync + DBG("### Network time updated."); + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5, r6); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5, r6); + } + + public: + Stream& stream; + + protected: + GsmClientMC60* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTMC60_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM5360.h b/lib/TinyGSM/src/TinyGsmClientSIM5360.h new file mode 100644 index 0000000..f181036 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM5360.h @@ -0,0 +1,741 @@ +/** + * @file TinyGsmClientSIM5360.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM5360_H_ +#define SRC_TINYGSMCLIENTSIM5360_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 10 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmSim5360 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSMS, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmGSMLocation, + public TinyGsmBattery, + public TinyGsmTemperature { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSMS; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmGSMLocation; + friend class TinyGsmBattery; + friend class TinyGsmTemperature; + + /* + * Inner Client + */ + public: + class GsmClientSim5360 : public GsmClient { + friend class TinyGsmSim5360; + + public: + GsmClientSim5360() {} + + explicit GsmClientSim5360(TinyGsmSim5360& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim5360* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + // TODO(?): Add SSL support + /* + class GsmClientSecureSim5360 : public GsmClientSim5360 { + public: + GsmClientSecureSim5360() {} + + explicit GsmClientSecureSim5360(TinyGsmSim5360& modem, uint8_t mux = 0) + : GsmClientSim5360(modem, mux) {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmSim5360(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM5360")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Disable time and time zone URC's + sendAT(GF("+CTZR=0")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable automatic time zome update + sendAT(GF("+CTZU=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemNameImpl() { + String name = "SIMCom SIM5360"; + + sendAT(GF("+CGMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { return name; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + DBG("### Modem:", name); + return name; + } + + bool factoryDefaultImpl() { // these commands aren't supported + return false; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+REBOOT")); + // Should return an 'OK' after reboot command is sent + if (waitResponse(10000L) != 1) { return false; } + // After booting, modem sends out messages as each of its + // internal modules loads. The final message is "PB DONE". + if (waitResponse(40000L, GF(GSM_NL "PB DONE")) != 1) { return false; } + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPOF")); + return waitResponse() == 1; + } + + bool radioOffImpl() { + if (!setPhoneFunctionality(4)) { return false; } + delay(3000); + return true; + } + + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CGREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + String getNetworkModes() { + sendAT(GF("+CNMP=?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + return res; + } + + int16_t getNetworkMode() { + sendAT(GF("+CNMP?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } + int16_t mode = streamGetIntBefore('\n'); + waitResponse(); + return mode; + } + + bool setNetworkMode(uint8_t mode) { + sendAT(GF("+CNMP="), mode); + return waitResponse() == 1; + } + + String getLocalIPImpl() { + sendAT(GF("+IPADDR")); // Inquire Socket PDP address + // sendAT(GF("+CGPADDR=1")); // Show PDP address + String res; + if (waitResponse(10000L, res) != 1) { return ""; } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); // Make sure we're not connected first + + // Define the PDP context + + // The CGDCONT commands set up the "external" PDP context + + // Set the external authentication + if (user && strlen(user) > 0) { + sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); + waitResponse(); + } + + // Define external PDP context 1 + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0"); + waitResponse(); + + // The CGSOCKCONT commands define the "embedded" PDP context for TCP/IP + + // Define the socket PDP context + sendAT(GF("+CGSOCKCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Set the embedded authentication + if (user && strlen(user) > 0) { + sendAT(GF("+CSOCKAUTH=1,1,\""), user, "\",\"", pwd, '"'); + waitResponse(); + } + + // Set active PDP context's profile number + // This ties the embedded TCP/IP application to the external PDP context + sendAT(GF("+CSOCKSETPN=1")); + waitResponse(); + + // Configure TCP parameters + + // Select TCP/IP application mode (command mode) + sendAT(GF("+CIPMODE=0")); + waitResponse(); + + // Set Sending Mode - send without waiting for peer TCP ACK + sendAT(GF("+CIPSENDMODE=0")); + waitResponse(); + + // Configure socket parameters + // AT+CIPCCFG= , , , , , + // , + // NmRetry = number of retransmission to be made for an IP packet + // = 10 (default) + // DelayTm = number of milliseconds to delay before outputting received data + // = 0 (default) + // Ack = sets whether reporting a string "Send ok" = 0 (don't report) + // errMode = mode of reporting error result code = 0 (numberic values) + // HeaderType = which data header of receiving data in multi-client mode + // = 1 (+RECEIVE,,) + // AsyncMode = sets mode of executing commands + // = 0 (synchronous command executing) + // TimeoutVal = minimum retransmission timeout in milliseconds = 75000 + sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000")); + if (waitResponse() != 1) { return false; } + + // Configure timeouts for opening and closing sockets + // AT+CIPTIMEOUT=, , + sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000); + waitResponse(); + + // Start the socket service + + // This activates and attaches to the external PDP context that is tied + // to the embedded context for TCP/IP (ie AT+CGACT=1,1 and AT+CGATT=1) + // Response may be an immediate "OK" followed later by "+NETOPEN: 0". + // We to ignore any immediate response and wait for the + // URC to show it's really connected. + sendAT(GF("+NETOPEN")); + if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + // Close any open sockets + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClientSim5360* sock = sockets[mux]; + if (sock) { sock->stop(); } + } + + // Stop the socket service + // Note: all sockets should be closed first - on 3G/4G models the sockets + // must be closed manually + sendAT(GF("+NETCLOSE")); + if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; } + + return true; + } + + bool isGprsConnectedImpl() { + sendAT(GF("+NETOPEN?")); + // May return +NETOPEN: 1, 0. We just confirm that the first number is 1 + if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; } + waitResponse(); + + sendAT(GF("+IPADDR")); // Inquire Socket PDP address + // sendAT(GF("+CGPADDR=1")); // Show PDP address + if (waitResponse() != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Gets the CCID of a sim card via AT+CCID + String getSimCCIDImpl() { + sendAT(GF("+CICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ + protected: + // SIM5360 and SIM7100 can return a GSM-based location from CLBS as per the + // template; SIM5320 doesn't not appear to be able to + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // SRGD Note: Returns voltage in VOLTS instead of millivolts + uint16_t getBattVoltageImpl() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; } + streamSkipUntil(','); // Skip battery charge status + streamSkipUntil(','); // Skip battery charge level + // get voltage in VOLTS + float voltage = streamGetFloatBefore('\n'); + // Wait for final OK + waitResponse(); + // Return millivolts + uint16_t res = voltage * 1000; + return res; + } + + // SRGD Note: Returns voltage in VOLTS instead of millivolts + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; } + chargeState = streamGetIntBefore(','); + percent = streamGetIntBefore(','); + // get voltage in VOLTS + float voltage = streamGetFloatBefore('\n'); + milliVolts = voltage * 1000; + // Wait for final OK + waitResponse(); + return true; + } + + /* + * Temperature functions + */ + protected: + // get temperature in degree celsius + float getTemperatureImpl() { + // Enable Temparature Reading + sendAT(GF("+CMTE=1")); + if (waitResponse() != 1) { return 0; } + // Get Temparature Value + sendAT(GF("+CMTE?")); + if (waitResponse(GF(GSM_NL "+CMTE:")) != 1) { return false; } + float res = streamGetFloatBefore('\n'); + // Wait for final OK + waitResponse(); + return res; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 15) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + // Make sure we'll be getting data manually on this connection + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Establish a connection in multi-socket mode + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), + port); + // The reply is +CIPOPEN: ## of socket created + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; } + return true; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip requested bytes to send + // TODO(?): make sure requested and confirmed bytes match + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux/cid (connecion id) + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ The data length which not read in the buffer + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + // Read the status of all sockets at once + sendAT(GF("+CIPCLOSE?")); + if (waitResponse(GF("+CIPCLOSE:")) != 1) { return false; } + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // +CIPCLOSE:,,..., + bool muxState = stream.parseInt(); + if (sockets[muxNo]) { sockets[muxNo]->sock_connected = muxState; } + } + waitResponse(); // Should be an OK at the end + if (!sockets[mux]) return false; + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+IPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); // Skip the reason code + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+CIPEVENT:"))) { + // Need to close all open sockets and release the network library. + // User will then need to reconnect. + DBG("### Network error!"); + if (!isGprsConnected()) { gprsDisconnect(); } + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientSim5360* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTSIM5360_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7000.h b/lib/TinyGSM/src/TinyGsmClientSIM7000.h new file mode 100644 index 0000000..e708845 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM7000.h @@ -0,0 +1,534 @@ +/** + * @file TinyGsmClientSIM7000.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM7000_H_ +#define SRC_TINYGSMCLIENTSIM7000_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 8 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmClientSIM70xx.h" +#include "TinyGsmTCP.tpp" + + +class TinyGsmSim7000 : public TinyGsmSim70xx, + public TinyGsmTCP { + friend class TinyGsmSim70xx; + friend class TinyGsmTCP; + + /* + * Inner Client + */ + public: + class GsmClientSim7000 : public GsmClient { + friend class TinyGsmSim7000; + + public: + GsmClientSim7000() {} + + explicit GsmClientSim7000(TinyGsmSim7000& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim7000* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(3000); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + // NOTE: Use modem TINYGSMSIM7000SSL for a secure client! + + /* + * Constructor + */ + public: + explicit TinyGsmSim7000(Stream& stream) + : TinyGsmSim70xx(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + if (waitResponse() != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + /* + * Power functions + */ + protected: + // Follows the SIM70xx template + + /* + * Generic network functions + */ + protected: + String getLocalIPImpl() { + sendAT(GF("+CIFSR;E0")); + String res; + if (waitResponse(10000L, res) != 1) { return ""; } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); + waitResponse(); + + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); + waitResponse(); + + // Set the user name + if (user && strlen(user) > 0) { + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); + waitResponse(); + } + // Set the password + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); + waitResponse(); + } + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // Activate the PDP context + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + // Open the definied GPRS bearer context + sendAT(GF("+SAPBR=1,1")); + waitResponse(85000L); + // Query the GPRS bearer context status + sendAT(GF("+SAPBR=2,1")); + if (waitResponse(30000L) != 1) { return false; } + + // Set the TCP application toolkit to multi-IP + sendAT(GF("+CIPMUX=1")); + if (waitResponse() != 1) { return false; } + + // Put the TCP application toolkit in "quick send" mode + // (thus no extra "Send OK") + sendAT(GF("+CIPQSEND=1")); + if (waitResponse() != 1) { return false; } + + // Set the TCP application toolkit to get data manually + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Start the TCP application toolkit task and set APN, USER NAME, PASSWORD + sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse(60000L) != 1) { return false; } + + // Bring up the TCP application toolkit wireless connection with GPRS or CSD + sendAT(GF("+CIICR")); + if (waitResponse(60000L) != 1) { return false; } + + // Get local IP address for the TCP application toolkit + // only assigned after connection + sendAT(GF("+CIFSR;E0")); + if (waitResponse(10000L) != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + // Shut the TCP application toolkit connection + // CIPSHUT will close *all* open TCP application toolkit connections + sendAT(GF("+CIPSHUT")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // Deactivate the bearer context + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Follows the SIM70xx template + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // Follows the SIM70xx template + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + if (ssl) { DBG("SSL only supported using application on SIM7000!"); } + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + // when not using SSL, the TCP application toolkit is more stable + sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, + GF("\","), port); + return (1 == + waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL), + GF("CLOSE OK" GSM_NL))); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + + stream.write(reinterpret_cast(buff), len); + stream.flush(); + + if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; + +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ Confirmed number of data bytes to be read, which may be less than + // requested. 0 indicates that no data can be read. + // SRGD NOTE: Contrary to above (which is copied from AT command manual) + // this is actually be the number of bytes that will be remaining in the + // buffer after the read. + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+CIPSTATUS="), mux); + waitResponse(GF("+CIPSTATUS")); + int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), + GF(",\"CLOSING\""), GF(",\"REMOTE CLOSING\""), + GF(",\"INITIAL\"")); + waitResponse(); + return 1 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + protected: + GsmClientSim7000* sockets[TINY_GSM_MUX_COUNT]; +}; + +#endif // SRC_TINYGSMCLIENTSIM7000_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h b/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h new file mode 100644 index 0000000..2f88e89 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h @@ -0,0 +1,730 @@ +/** + * @file TinyGsmClientSim7000SSL.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM7000SSL_H_ +#define SRC_TINYGSMCLIENTSIM7000SSL_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 2 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmClientSIM70xx.h" +#include "TinyGsmTCP.tpp" +#include "TinyGsmSSL.tpp" + +class TinyGsmSim7000SSL + : public TinyGsmSim70xx, + public TinyGsmTCP, + public TinyGsmSSL { + friend class TinyGsmSim70xx; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + + /* + * Inner Client + */ + public: + class GsmClientSim7000SSL : public GsmClient { + friend class TinyGsmSim7000SSL; + + public: + GsmClientSim7000SSL() {} + + explicit GsmClientSim7000SSL(TinyGsmSim7000SSL& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim7000SSL* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CACLOSE="), mux); + sock_connected = false; + at->waitResponse(3000); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + class GsmClientSecureSIM7000SSL : public GsmClientSim7000SSL { + public: + GsmClientSecureSIM7000SSL() {} + + explicit GsmClientSecureSIM7000SSL(TinyGsmSim7000SSL& modem, + uint8_t mux = 0) + : GsmClientSim7000SSL(modem, mux) {} + + public: + bool setCertificate(const String& certificateName) { + return at->setCertificate(certificateName, mux); + } + + virtual int connect(const char* host, uint16_t port, + int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSim7000SSL(Stream& stream) + : TinyGsmSim70xx(stream), + certificates() { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000SSL")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + if (waitResponse() != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + void maintainImpl() { + // Keep listening for modem URC's and proactively iterate through + // sockets asking if any data is avaiable + bool check_socks = false; + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClientSim7000SSL* sock = sockets[mux]; + if (sock && sock->got_data) { + sock->got_data = false; + check_socks = true; + } + } + // modemGetAvailable checks all socks, so we only want to do it once + // modemGetAvailable calls modemGetConnected(), which also checks allf + if (check_socks) { modemGetAvailable(0); } + while (stream.available()) { waitResponse(15, NULL, NULL); } + } + + /* + * Power functions + */ + protected: + // Follows the SIM70xx template + + /* + * Generic network functions + */ + protected: + String getLocalIPImpl() { + sendAT(GF("+CNACT?")); + if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } + streamSkipUntil('\"'); + String res = stream.readStringUntil('\"'); + waitResponse(); + return res; + } + + /* + * Secure socket layer functions + */ + protected: + bool setCertificate(const String& certificateName, const uint8_t mux = 0) { + if (mux >= TINY_GSM_MUX_COUNT) return false; + certificates[mux] = certificateName; + return true; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // NOTE: **DO NOT** activate the PDP context + // For who only knows what reason, doing so screws up the rest of the + // process + + // Bearer settings for applications based on IP + // Set the user name and password + // AT+CNCFG=[,[,,[,]]] + // 0: Dual PDN Stack + // 1: Internet Protocol Version 4 + // 2: Internet Protocol Version 6 + // 0: NONE + // 1: PAP + // 2: CHAP + // 3: PAP or CHAP + if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) { + sendAT(GF("+CNCFG=1,\""), apn, "\",\"", "\",\"", user, pwd, '"'); + waitResponse(); + } else if (user && strlen(user) > 0) { + // Set the user name only + sendAT(GF("+CNCFG=1,\""), apn, "\",\"", user, '"'); + waitResponse(); + } else { + // Set the APN only + sendAT(GF("+CNCFG=1,\""), apn, '"'); + waitResponse(); + } + + // Activate application network connection + // This is for most other supported applications outside of the + // TCP application toolkit (ie, SSL) + // AT+CNACT=, + // 0: Deactive + // 1: Active + // 2: Auto Active + bool res = false; + int ntries = 0; + while (!res && ntries < 5) { + sendAT(GF("+CNACT=1,\""), apn, GF("\"")); + res = waitResponse(60000L, GF(GSM_NL "+APP PDP: ACTIVE"), + GF(GSM_NL "+APP PDP: DEACTIVE")) == 1; + waitResponse(); + ntries++; + } + + return res; + } + + bool gprsDisconnectImpl() { + // Shut down the general application TCP/IP connection + // CNACT will close *all* open application connections + sendAT(GF("+CNACT=0")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // Deactivate the bearer context + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Follows the SIM70xx template + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // Follows the SIM70xx template + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + // set the connection (mux) identifier to use + sendAT(GF("+CACID="), mux); + if (waitResponse(timeout_ms) != 1) return false; + + + if (ssl) { + // set the ssl version + // AT+CSSLCFG="SSLVERSION",, + // PDP context identifier + // 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN + // 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0 + // 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1 + // 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2 + // 4: QAPI_NET_SSL_PROTOCOL_DTLS_1_0 + // 5: QAPI_NET_SSL_PROTOCOL_DTLS_1_2 + // NOTE: despite docs using caps, "sslversion" must be in lower case + sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2 + if (waitResponse(5000L) != 1) return false; + } + + // enable or disable ssl + // AT+CASSLCFG=,"SSL", + // Application connection ID (set with AT+CACID above) + // 0: Not support SSL + // 1: Support SSL + sendAT(GF("+CASSLCFG="), mux, ',', GF("ssl,"), ssl); + waitResponse(); + + if (ssl) { + // set the PDP context to apply SSL to + // AT+CSSLCFG="CTXINDEX", + // PDP context identifier + // NOTE: despite docs using caps, "ctxindex" must be in lower case + sendAT(GF("+CSSLCFG=\"ctxindex\",0")); + if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false; + streamSkipUntil('\n'); // read out the certificate information + waitResponse(); + + if (certificates[mux] != "") { + // apply the correct certificate to the connection + // AT+CASSLCFG=,"CACERT", + // Application connection ID (set with AT+CACID above) + // certificate name + sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(), + "\""); + if (waitResponse(5000L) != 1) return false; + } + + // set the protocol + // 0: TCP; 1: UDP + sendAT(GF("+CASSLCFG="), mux, ',', GF("protocol,0")); + waitResponse(); + + // set the SSL SNI (server name indication) + // NOTE: despite docs using caps, "sni" must be in lower case + sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\"")); + waitResponse(); + } + + // actually open the connection + // AT+CAOPEN=[,],, + // TCP/UDP identifier + // "TCP" or "UDP" + // NOTE: the "TCP" can't be included + sendAT(GF("+CAOPEN="), mux, GF(",\""), host, GF("\","), port); + if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; } + // returns OK/r/n/r/n+CAOPEN: , + // 0: Success + // 1: Socket error + // 2: No memory + // 3: Connection limit + // 4: Parameter invalid + // 6: Invalid IP address + // 7: Not support the function + // 12: Can’t bind the port + // 13: Can’t listen the port + // 20: Can’t resolve the host + // 21: Network not active + // 23: Remote refuse + // 24: Certificate’s time expired + // 25: Certificate’s common name does not match + // 26: Certificate’s common name does not match and time expired + // 27: Connect failed + streamSkipUntil(','); // Skip mux + + // make sure the connection really opened + int8_t res = streamGetIntBefore('\n'); + waitResponse(); + + return 0 == res; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + // send data on prompt + sendAT(GF("+CASEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + + stream.write(reinterpret_cast(buff), len); + stream.flush(); + + // after posting data, module responds with: + //+CASEND: ,, + if (waitResponse(GF(GSM_NL "+CASEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + if (streamGetIntBefore(',') != 0) { return 0; } // If result != success + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) { return 0; } + + sendAT(GF("+CARECV="), mux, ',', (uint16_t)size); + + if (waitResponse(GF("+CARECV:")) != 1) { return 0; } + + // uint8_t ret_mux = stream.parseInt(); + // streamSkipUntil(','); + // const int16_t len_confirmed = streamGetIntBefore('\n'); + // DBG("### READING:", len_confirmed, "from", ret_mux); + + // if (ret_mux != mux) { + // DBG("### Data from wrong mux! Got", ret_mux, "expected", mux); + // waitResponse(); + // sockets[mux]->sock_available = modemGetAvailable(mux); + // return 0; + // } + + // NOTE: manual says the mux number is returned before the number of + // characters available, but in tests only the number is returned + + int16_t len_confirmed = stream.parseInt(); + streamSkipUntil(','); // skip the comma + if (len_confirmed <= 0) { + waitResponse(); + sockets[mux]->sock_available = modemGetAvailable(mux); + return 0; + } + + for (int i = 0; i < len_confirmed; i++) { + uint32_t startMillis = millis(); + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); + sockets[mux]->rx.put(c); + } + waitResponse(); + // DBG("### READ:", len_confirmed, "from", mux); + // make sure the sock available number is accurate again + // the module is **EXTREMELY** testy about being asked to read more from + // the buffer than exits; it will freeze until a hard reset or power cycle! + sockets[mux]->sock_available = modemGetAvailable(mux); + return len_confirmed; + } + + size_t modemGetAvailable(uint8_t mux) { + // If the socket doesn't exist, just return + if (!sockets[mux]) { return 0; } + // We need to check if there are any connections open *before* checking for + // available characters. The SIM7000 *will crash* if you ask about data + // when there are no open connections. + if (!modemGetConnected(mux)) { return 0; } + // NOTE: This gets how many characters are available on all connections that + // have data. It does not return all the connections, just those with data. + sendAT(GF("+CARECV?")); + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // after the last connection, there's an ok, so we catch it right away + int res = waitResponse(3000, GF("+CARECV:"), GFP(GSM_OK), GFP(GSM_ERROR)); + // if we get the +CARECV: response, read the mux number and the number of + // characters available + if (res == 1) { + int ret_mux = streamGetIntBefore(','); + size_t result = streamGetIntBefore('\n'); + GsmClientSim7000SSL* sock = sockets[ret_mux]; + if (sock) { sock->sock_available = result; } + // if the first returned mux isn't 0 (or is higher than expected) + // we need to fill in the missing muxes + if (ret_mux > muxNo) { + for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) { + GsmClientSim7000SSL* isock = sockets[extra_mux]; + if (isock) { isock->sock_available = 0; } + } + muxNo = ret_mux; + } + } else if (res == 2) { + // if we get an OK, we've reached the last socket with available data + // so we set any we haven't gotten to yet to 0 + for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT; + extra_mux++) { + GsmClientSim7000SSL* isock = sockets[extra_mux]; + if (isock) { isock->sock_available = 0; } + } + break; + } else { + // if we got an error, give up + break; + } + // Should be a final OK at the end. + // If every connection was returned, catch the OK here. + // If only a portion were returned, catch it above. + if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); } + } + modemGetConnected(mux); // check the state of all connections + if (!sockets[mux]) { return 0; } + return sockets[mux]->sock_available; + } + + bool modemGetConnected(uint8_t mux) { + // NOTE: This gets the state of all connections that have been opened + // since the last connection + sendAT(GF("+CASTATE?")); + + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // after the last connection, there's an ok, so we catch it right away + int res = waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK), + GFP(GSM_ERROR)); + // if we get the +CASTATE: response, read the mux number and the status + if (res == 1) { + int ret_mux = streamGetIntBefore(','); + size_t status = streamGetIntBefore('\n'); + // 0: Closed by remote server or internal error + // 1: Connected to remote server + // 2: Listening (server mode) + GsmClientSim7000SSL* sock = sockets[ret_mux]; + if (sock) { sock->sock_connected = (status == 1); } + // if the first returned mux isn't 0 (or is higher than expected) + // we need to fill in the missing muxes + if (ret_mux > muxNo) { + for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) { + GsmClientSim7000SSL* isock = sockets[extra_mux]; + if (isock) { isock->sock_connected = false; } + } + muxNo = ret_mux; + } + } else if (res == 2) { + // if we get an OK, we've reached the last socket with available data + // so we set any we haven't gotten to yet to 0 + for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT; + extra_mux++) { + GsmClientSim7000SSL* isock = sockets[extra_mux]; + if (isock) { isock->sock_connected = false; } + } + break; + } else { + // if we got an error, give up + break; + } + // Should be a final OK at the end. + // If every connection was returned, catch the OK here. + // If only a portion were returned, catch it above. + if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); } + } + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+CARECV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+CADATAIND:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF("+CASTATE:"))) { + int8_t mux = streamGetIntBefore(','); + int8_t state = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (state != 1) { + sockets[mux]->sock_connected = false; + DBG("### Closed: ", mux); + } + } + data = ""; + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + protected: + GsmClientSim7000SSL* sockets[TINY_GSM_MUX_COUNT]; + String certificates[TINY_GSM_MUX_COUNT]; +}; + +#endif // SRC_TINYGSMCLIENTSIM7000SSL_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7080.h b/lib/TinyGSM/src/TinyGsmClientSIM7080.h new file mode 100644 index 0000000..f5584a9 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM7080.h @@ -0,0 +1,729 @@ +/** + * @file TinyGsmClientSim7080.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM7080_H_ +#define SRC_TINYGSMCLIENTSIM7080_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 12 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmClientSIM70xx.h" +#include "TinyGsmTCP.tpp" +#include "TinyGsmSSL.tpp" + +class TinyGsmSim7080 : public TinyGsmSim70xx, + public TinyGsmTCP, + public TinyGsmSSL { + friend class TinyGsmSim70xx; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + + /* + * Inner Client + */ + public: + class GsmClientSim7080 : public GsmClient { + friend class TinyGsmSim7080; + + public: + GsmClientSim7080() {} + + explicit GsmClientSim7080(TinyGsmSim7080& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim7080* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CACLOSE="), mux); + sock_connected = false; + at->waitResponse(3000); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + class GsmClientSecureSIM7080 : public GsmClientSim7080 { + public: + GsmClientSecureSIM7080() {} + + explicit GsmClientSecureSIM7080(TinyGsmSim7080& modem, uint8_t mux = 0) + : GsmClientSim7080(modem, mux) {} + + public: + bool setCertificate(const String& certificateName) { + return at->setCertificate(certificateName, mux); + } + + virtual int connect(const char* host, uint16_t port, + int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSim7080(Stream& stream) + : TinyGsmSim70xx(stream), + certificates() { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7080")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + if (waitResponse() != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + void maintainImpl() { + // Keep listening for modem URC's and proactively iterate through + // sockets asking if any data is avaiable + bool check_socks = false; + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClientSim7080* sock = sockets[mux]; + if (sock && sock->got_data) { + sock->got_data = false; + check_socks = true; + } + } + // modemGetAvailable checks all socks, so we only want to do it once + // modemGetAvailable calls modemGetConnected(), which also checks allf + if (check_socks) { modemGetAvailable(0); } + while (stream.available()) { waitResponse(15, NULL, NULL); } + } + + /* + * Power functions + */ + protected: + // Follows the SIM70xx template + + /* + * Generic network functions + */ + protected: + String getLocalIPImpl() { + sendAT(GF("+CNACT?")); + if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } + streamSkipUntil('\"'); + String res = stream.readStringUntil('\"'); + waitResponse(); + return res; + } + + /* + * Secure socket layer functions + */ + protected: + bool setCertificate(const String& certificateName, const uint8_t mux = 0) { + if (mux >= TINY_GSM_MUX_COUNT) return false; + certificates[mux] = certificateName; + return true; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // NOTE: **DO NOT** activate the PDP context + // For who only knows what reason, doing so screws up the rest of the + // process + + // Check the APN returned by the server + // not sure why, but the connection is more consistent with this + sendAT(GF("+CGNAPN")); + waitResponse(); + + // Bearer settings for applications based on IP + // Set the user name and password + // AT+CNCFG=,,[,[,,[]]] + // PDP Context Identifier - for reasons not understood by me, + // use PDP context identifier of 0 for what we defined as 1 above + // 0: Dual PDN Stack + // 1: Internet Protocol Version 4 + // 2: Internet Protocol Version 6 + // 0: NONE + // 1: PAP + // 2: CHAP + // 3: PAP or CHAP + if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) { + sendAT(GF("+CNCFG=0,1,\""), apn, "\",\"", user, "\",\"", pwd, '"'); + waitResponse(); + } else if (user && strlen(user) > 0) { + // Set the user name only + sendAT(GF("+CNCFG=0,1,\""), apn, "\",\"", user, '"'); + waitResponse(); + } else { + // Set the APN only + sendAT(GF("+CNCFG=0,1,\""), apn, '"'); + waitResponse(); + } + + // Activate application network connection + // AT+CNACT=, + // PDP Context Identifier - for reasons not understood by me, + // use PDP context identifier of 0 for what we defined as 1 above + // 0: Deactive + // 1: Active + // 2: Auto Active + bool res = false; + int ntries = 0; + while (!res && ntries < 5) { + sendAT(GF("+CNACT=0,1")); + res = waitResponse(60000L, GF(GSM_NL "+APP PDP: 0,ACTIVE"), + GF(GSM_NL "+APP PDP: 0,DEACTIVE")); + waitResponse(); + ntries++; + } + + return res; + } + + bool gprsDisconnectImpl() { + // Shut down the general application TCP/IP connection + // CNACT will close *all* open application connections + sendAT(GF("+CNACT=0,0")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // Deactivate the bearer context + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Follows the SIM70xx template + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // Follows the SIM70xx template + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + // set the connection (mux) identifier to use + sendAT(GF("+CACID="), mux); + if (waitResponse(timeout_ms) != 1) return false; + + + if (ssl) { + // set the ssl version + // AT+CSSLCFG="SSLVERSION",, + // PDP context identifier + // 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN + // 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0 + // 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1 + // 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2 + // 4: QAPI_NET_SSL_PROTOCOL_DTLS_1_0 + // 5: QAPI_NET_SSL_PROTOCOL_DTLS_1_2 + // NOTE: despite docs using caps, "sslversion" must be in lower case + sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2 + if (waitResponse(5000L) != 1) return false; + } + + // enable or disable ssl + // AT+CASSLCFG=,"SSL", + // Application connection ID (set with AT+CACID above) + // 0: Not support SSL + // 1: Support SSL + sendAT(GF("+CASSLCFG="), mux, ',', GF("SSL,"), ssl); + waitResponse(); + + if (ssl) { + // set the PDP context to apply SSL to + // AT+CSSLCFG="CTXINDEX", + // PDP context identifier + // NOTE: despite docs using "CRINDEX" in all caps, the module only + // accepts the command "ctxindex" and it must be in lower case + sendAT(GF("+CSSLCFG=\"ctxindex\",0")); + if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false; + streamSkipUntil('\n'); // read out the certificate information + waitResponse(); + + if (certificates[mux] != "") { + // apply the correct certificate to the connection + // AT+CASSLCFG=,"CACERT", + // Application connection ID (set with AT+CACID above) + // certificate name + sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(), + "\""); + if (waitResponse(5000L) != 1) return false; + } + + // set the SSL SNI (server name indication) + // NOTE: despite docs using caps, "sni" must be in lower case + sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\"")); + waitResponse(); + } + + // actually open the connection + // AT+CAOPEN=,,,,[,] + // TCP/UDP identifier + // Index of PDP connection; we set up PCP context 1 above + // "TCP" or "UDP" + // 0: The received data can only be read manually using + // AT+CARECV= + // 1: After receiving the data, it will automatically report + // URC: + // +CAURC: + // "recv",,,, + // NOTE: including the fails + sendAT(GF("+CAOPEN="), mux, GF(",0,\"TCP\",\""), host, GF("\","), port); + if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; } + // returns OK/r/n/r/n+CAOPEN: , + // 0: Success + // 1: Socket error + // 2: No memory + // 3: Connection limit + // 4: Parameter invalid + // 6: Invalid IP address + // 7: Not support the function + // 12: Can’t bind the port + // 13: Can’t listen the port + // 20: Can’t resolve the host + // 21: Network not active + // 23: Remote refuse + // 24: Certificate’s time expired + // 25: Certificate’s common name does not match + // 26: Certificate’s common name does not match and time expired + // 27: Connect failed + streamSkipUntil(','); // Skip mux + + // make sure the connection really opened + int8_t res = streamGetIntBefore('\n'); + waitResponse(); + + return 0 == res; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + // send data on prompt + sendAT(GF("+CASEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + + stream.write(reinterpret_cast(buff), len); + stream.flush(); + + // OK after posting data + if (waitResponse() != 1) { return 0; } + + return len; + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) { return 0; } + + sendAT(GF("+CARECV="), mux, ',', (uint16_t)size); + + if (waitResponse(GF("+CARECV:")) != 1) { return 0; } + + // uint8_t ret_mux = stream.parseInt(); + // streamSkipUntil(','); + // const int16_t len_confirmed = streamGetIntBefore('\n'); + // DBG("### READING:", len_confirmed, "from", ret_mux); + + // if (ret_mux != mux) { + // DBG("### Data from wrong mux! Got", ret_mux, "expected", mux); + // waitResponse(); + // sockets[mux]->sock_available = modemGetAvailable(mux); + // return 0; + // } + + // NOTE: manual says the mux number is returned before the number of + // characters available, but in tests only the number is returned + + int16_t len_confirmed = stream.parseInt(); + streamSkipUntil(','); // skip the comma + if (len_confirmed <= 0) { + waitResponse(); + sockets[mux]->sock_available = modemGetAvailable(mux); + return 0; + } + + for (int i = 0; i < len_confirmed; i++) { + uint32_t startMillis = millis(); + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); + sockets[mux]->rx.put(c); + } + waitResponse(); + // make sure the sock available number is accurate again + sockets[mux]->sock_available = modemGetAvailable(mux); + return len_confirmed; + } + + size_t modemGetAvailable(uint8_t mux) { + // If the socket doesn't exist, just return + if (!sockets[mux]) { return 0; } + // NOTE: This gets how many characters are available on all connections that + // have data. It does not return all the connections, just those with data. + sendAT(GF("+CARECV?")); + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // after the last connection, there's an ok, so we catch it right away + int res = waitResponse(3000, GF("+CARECV:"), GFP(GSM_OK), GFP(GSM_ERROR)); + // if we get the +CARECV: response, read the mux number and the number of + // characters available + if (res == 1) { + int ret_mux = streamGetIntBefore(','); + size_t result = streamGetIntBefore('\n'); + GsmClientSim7080* sock = sockets[ret_mux]; + if (sock) { sock->sock_available = result; } + // if the first returned mux isn't 0 (or is higher than expected) + // we need to fill in the missing muxes + if (ret_mux > muxNo) { + for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) { + GsmClientSim7080* isock = sockets[extra_mux]; + if (isock) { isock->sock_available = 0; } + } + muxNo = ret_mux; + } + } else if (res == 2) { + // if we get an OK, we've reached the last socket with available data + // so we set any we haven't gotten to yet to 0 + for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT; + extra_mux++) { + GsmClientSim7080* isock = sockets[extra_mux]; + if (isock) { isock->sock_available = 0; } + } + break; + } else { + // if we got an error, give up + break; + } + // Should be a final OK at the end. + // If every connection was returned, catch the OK here. + // If only a portion were returned, catch it above. + if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); } + } + modemGetConnected(mux); // check the state of all connections + if (!sockets[mux]) { return 0; } + return sockets[mux]->sock_available; + } + + bool modemGetConnected(uint8_t mux) { + // NOTE: This gets the state of all connections that have been opened + // since the last connection + sendAT(GF("+CASTATE?")); + + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // after the last connection, there's an ok, so we catch it right away + int res = waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK), + GFP(GSM_ERROR)); + // if we get the +CASTATE: response, read the mux number and the status + if (res == 1) { + int ret_mux = streamGetIntBefore(','); + size_t status = streamGetIntBefore('\n'); + // 0: Closed by remote server or internal error + // 1: Connected to remote server + // 2: Listening (server mode) + GsmClientSim7080* sock = sockets[ret_mux]; + if (sock) { sock->sock_connected = (status == 1); } + // if the first returned mux isn't 0 (or is higher than expected) + // we need to fill in the missing muxes + if (ret_mux > muxNo) { + for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) { + GsmClientSim7080* isock = sockets[extra_mux]; + if (isock) { isock->sock_connected = false; } + } + muxNo = ret_mux; + } + } else if (res == 2) { + // if we get an OK, we've reached the last socket with available data + // so we set any we haven't gotten to yet to 0 + for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT; + extra_mux++) { + GsmClientSim7080* isock = sockets[extra_mux]; + if (isock) { isock->sock_connected = false; } + } + break; + } else { + // if we got an error, give up + break; + } + // Should be a final OK at the end. + // If every connection was returned, catch the OK here. + // If only a portion were returned, catch it above. + if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); } + } + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF("+CARECV:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+CADATAIND:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF("+CASTATE:"))) { + int8_t mux = streamGetIntBefore(','); + int8_t state = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (state != 1) { + sockets[mux]->sock_connected = false; + DBG("### Closed: ", mux); + } + } + data = ""; + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + protected: + GsmClientSim7080* sockets[TINY_GSM_MUX_COUNT]; + String certificates[TINY_GSM_MUX_COUNT]; +}; + +#endif // SRC_TINYGSMCLIENTSIM7080_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM70xx.h b/lib/TinyGSM/src/TinyGsmClientSIM70xx.h new file mode 100644 index 0000000..45d1f5e --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM70xx.h @@ -0,0 +1,459 @@ +/** + * @file TinyGsmClientSIM70xx.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM70XX_H_ +#define SRC_TINYGSMCLIENTSIM70XX_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#include "TinyGsmBattery.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" +#include "TinyGsmGSMLocation.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +template +class TinyGsmSim70xx : public TinyGsmModem>, + public TinyGsmGPRS>, + public TinyGsmSMS>, + public TinyGsmGPS>, + public TinyGsmTime>, + public TinyGsmNTP>, + public TinyGsmBattery>, + public TinyGsmGSMLocation> { + friend class TinyGsmModem>; + friend class TinyGsmGPRS>; + friend class TinyGsmSMS>; + friend class TinyGsmGPS>; + friend class TinyGsmTime>; + friend class TinyGsmNTP>; + friend class TinyGsmBattery>; + friend class TinyGsmGSMLocation>; + + /* + * CRTP Helper + */ + protected: + inline const modemType& thisModem() const { + return static_cast(*this); + } + inline modemType& thisModem() { + return static_cast(*this); + } + + /* + * Constructor + */ + public: + explicit TinyGsmSim70xx(Stream& stream) : stream(stream) {} + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + return thisModem().initImpl(pin); + } + + String getModemNameImpl() { + String name = "SIMCom SIM7000"; + + thisModem().sendAT(GF("+GMM")); + String res2; + if (thisModem().waitResponse(5000L, res2) != 1) { return name; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + return name; + } + + bool factoryDefaultImpl() { // these commands aren't supported + thisModem().sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + thisModem().waitResponse(); + thisModem().sendAT(GF("+IPR=0")); // Auto-baud + thisModem().waitResponse(); + thisModem().sendAT(GF("+IFC=0,0")); // No Flow Control + thisModem().waitResponse(); + thisModem().sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop + thisModem().waitResponse(); + thisModem().sendAT(GF("+CSCLK=0")); // Disable Slow Clock + thisModem().waitResponse(); + thisModem().sendAT(GF("&W")); // Write configuration + return thisModem().waitResponse() == 1; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + thisModem().sendAT(GF("E0")); // Echo Off + thisModem().waitResponse(); + if (!thisModem().setPhoneFunctionality(0)) { return false; } + if (!thisModem().setPhoneFunctionality(1, true)) { return false; } + thisModem().waitResponse(30000L, GF("SMS Ready")); + return thisModem().initImpl(pin); + } + + bool powerOffImpl() { + thisModem().sendAT(GF("+CPOWD=1")); + return thisModem().waitResponse(GF("NORMAL POWER DOWN")) == 1; + } + + // During sleep, the SIM70xx module has its serial communication disabled. + // In order to reestablish communication pull the DRT-pin of the SIM70xx + // module LOW for at least 50ms. Then use this function to disable sleep + // mode. The DTR-pin can then be released again. + bool sleepEnableImpl(bool enable = true) { + thisModem().sendAT(GF("+CSCLK="), enable); + return thisModem().waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + thisModem().sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return thisModem().waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + RegStatus epsStatus = + (RegStatus)thisModem().getRegistrationStatusXREG("CEREG"); + // If we're connected on EPS, great! + if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) { + return epsStatus; + } else { + // Otherwise, check GPRS network status + // We could be using GPRS fall-back or the board could be being moody + return (RegStatus)thisModem().getRegistrationStatusXREG("CGREG"); + } + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + String getNetworkModes() { + // Get the help string, not the setting value + thisModem().sendAT(GF("+CNMP=?")); + if (thisModem().waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + return res; + } + + int16_t getNetworkMode() { + thisModem().sendAT(GF("+CNMP?")); + if (thisModem().waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } + int16_t mode = thisModem().streamGetIntBefore('\n'); + thisModem().waitResponse(); + return mode; + } + + bool setNetworkMode(uint8_t mode) { + // 2 Automatic + // 13 GSM only + // 38 LTE only + // 51 GSM and LTE only + thisModem().sendAT(GF("+CNMP="), mode); + return thisModem().waitResponse() == 1; + } + + String getPreferredModes() { + // Get the help string, not the setting value + thisModem().sendAT(GF("+CMNB=?")); + if (thisModem().waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + return res; + } + + int16_t getPreferredMode() { + thisModem().sendAT(GF("+CMNB?")); + if (thisModem().waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return false; } + int16_t mode = thisModem().streamGetIntBefore('\n'); + thisModem().waitResponse(); + return mode; + } + + bool setPreferredMode(uint8_t mode) { + // 1 CAT-M + // 2 NB-IoT + // 3 CAT-M and NB-IoT + thisModem().sendAT(GF("+CMNB="), mode); + return thisModem().waitResponse() == 1; + } + + bool getNetworkSystemMode(bool& n, int16_t& stat) { + // n: whether to automatically report the system mode info + // stat: the current service. 0 if it not connected + thisModem().sendAT(GF("+CNSMOD?")); + if (thisModem().waitResponse(GF(GSM_NL "+CNSMOD:")) != 1) { return false; } + n = thisModem().streamGetIntBefore(',') != 0; + stat = thisModem().streamGetIntBefore('\n'); + thisModem().waitResponse(); + return true; + } + + bool setNetworkSystemMode(bool n) { + // n: whether to automatically report the system mode info + thisModem().sendAT(GF("+CNSMOD="), int8_t(n)); + return thisModem().waitResponse() == 1; + } + + String getLocalIPImpl() { + return thisModem().getLocalIPImpl(); + } + + /* + * GPRS functions + */ + protected: + // should implement in sub-classes + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + return thisModem().gprsConnectImpl(apn, user, pwd); + } + + bool gprsDisconnectImpl() { + return thisModem().gprsDisconnectImpl(); + } + + /* + * SIM card functions + */ + protected: + // Doesn't return the "+CCID" before the number + String getSimCCIDImpl() { + thisModem().sendAT(GF("+CCID")); + if (thisModem().waitResponse(GF(GSM_NL)) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + res.trim(); + return res; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl() { + thisModem().sendAT(GF("+CGNSPWR=1")); + if (thisModem().waitResponse() != 1) { return false; } + return true; + } + + bool disableGPSImpl() { + thisModem().sendAT(GF("+CGNSPWR=0")); + if (thisModem().waitResponse() != 1) { return false; } + return true; + } + + // get the RAW GPS output + String getGPSrawImpl() { + thisModem().sendAT(GF("+CGNSINF")); + if (thisModem().waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + thisModem().sendAT(GF("+CGNSINF")); + if (thisModem().waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { + return false; + } + + thisModem().streamSkipUntil(','); // GNSS run status + if (thisModem().streamGetIntBefore(',') == 1) { // fix status + // init variables + float ilat = 0; + float ilon = 0; + float ispeed = 0; + float ialt = 0; + int ivsat = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + + // UTC date & Time + iyear = thisModem().streamGetIntLength(4); // Four digit year + imonth = thisModem().streamGetIntLength(2); // Two digit month + iday = thisModem().streamGetIntLength(2); // Two digit day + ihour = thisModem().streamGetIntLength(2); // Two digit hour + imin = thisModem().streamGetIntLength(2); // Two digit minute + secondWithSS = thisModem().streamGetFloatBefore( + ','); // 6 digit second with subseconds + + ilat = thisModem().streamGetFloatBefore(','); // Latitude + ilon = thisModem().streamGetFloatBefore(','); // Longitude + ialt = thisModem().streamGetFloatBefore( + ','); // MSL Altitude. Unit is meters + ispeed = thisModem().streamGetFloatBefore( + ','); // Speed Over Ground. Unit is knots. + thisModem().streamSkipUntil(','); // Course Over Ground. Degrees. + thisModem().streamSkipUntil(','); // Fix Mode + thisModem().streamSkipUntil(','); // Reserved1 + iaccuracy = thisModem().streamGetFloatBefore( + ','); // Horizontal Dilution Of Precision + thisModem().streamSkipUntil(','); // Position Dilution Of Precision + thisModem().streamSkipUntil(','); // Vertical Dilution Of Precision + thisModem().streamSkipUntil(','); // Reserved2 + ivsat = thisModem().streamGetIntBefore(','); // GNSS Satellites in View + iusat = thisModem().streamGetIntBefore(','); // GNSS Satellites Used + thisModem().streamSkipUntil(','); // GLONASS Satellites Used + thisModem().streamSkipUntil(','); // Reserved3 + thisModem().streamSkipUntil(','); // C/N0 max + thisModem().streamSkipUntil(','); // HPA + thisModem().streamSkipUntil('\n'); // VPA + + // Set pointers + if (lat != NULL) *lat = ilat; + if (lon != NULL) *lon = ilon; + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = ivsat; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + thisModem().waitResponse(); + return true; + } + + thisModem().streamSkipUntil('\n'); // toss the row of commas + thisModem().waitResponse(); + return false; + } + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + // should implement in sub-classes + + /* + * Utilities + */ + public: + // should implement in sub-classes + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return thisModem().waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return thisModem().waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return thisModem().waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTSIM70XX_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM7600.h b/lib/TinyGSM/src/TinyGsmClientSIM7600.h new file mode 100644 index 0000000..9f47c5d --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM7600.h @@ -0,0 +1,866 @@ +/** + * @file TinyGsmClientSIM7600.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM7600_H_ +#define SRC_TINYGSMCLIENTSIM7600_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 10 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmSim7600 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSMS, + public TinyGsmGSMLocation, + public TinyGsmGPS, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmBattery, + public TinyGsmTemperature, + public TinyGsmCalling { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSMS; + friend class TinyGsmGPS; + friend class TinyGsmGSMLocation; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmBattery; + friend class TinyGsmTemperature; + friend class TinyGsmCalling; + + /* + * Inner Client + */ + public: + class GsmClientSim7600 : public GsmClient { + friend class TinyGsmSim7600; + + public: + GsmClientSim7600() {} + + explicit GsmClientSim7600(TinyGsmSim7600& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim7600* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + /*TODO(?)) + class GsmClientSecureSIM7600 : public GsmClientSim7600 + { + public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmSim7600& modem, uint8_t mux = 0) + : public GsmClient(modem, mux) + {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + */ + + /* + * Constructor + */ + public: + explicit TinyGsmSim7600(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7600")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Disable time and time zone URC's + sendAT(GF("+CTZR=0")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable automatic time zome update + sendAT(GF("+CTZU=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemNameImpl() { + String name = "SIMCom SIM7600"; + + sendAT(GF("+CGMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { return name; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + DBG("### Modem:", name); + return name; + } + + bool factoryDefaultImpl() { // these commands aren't supported + return false; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("+CRESET")); + if (waitResponse(10000L) != 1) { return false; } + delay(5000L); // TODO(?): Test this delay! + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPOF")); + return waitResponse() == 1; + } + + bool radioOffImpl() { + if (!setPhoneFunctionality(4)) { return false; } + delay(3000); + return true; + } + + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CGREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + String getNetworkModes() { + sendAT(GF("+CNMP=?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + return res; + } + + int16_t getNetworkMode() { + sendAT(GF("+CNMP?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } + int16_t mode = streamGetIntBefore('\n'); + waitResponse(); + return mode; + } + + bool setNetworkMode(uint8_t mode) { + sendAT(GF("+CNMP="), mode); + return waitResponse() == 1; + } + + String getLocalIPImpl() { + sendAT(GF("+IPADDR")); // Inquire Socket PDP address + // sendAT(GF("+CGPADDR=1")); // Show PDP address + String res; + if (waitResponse(10000L, res) != 1) { return ""; } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); // Make sure we're not connected first + + // Define the PDP context + + // The CGDCONT commands set up the "external" PDP context + + // Set the external authentication + if (user && strlen(user) > 0) { + sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); + waitResponse(); + } + + // Define external PDP context 1 + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0"); + waitResponse(); + + // Configure TCP parameters + + // Select TCP/IP application mode (command mode) + sendAT(GF("+CIPMODE=0")); + waitResponse(); + + // Set Sending Mode - send without waiting for peer TCP ACK + sendAT(GF("+CIPSENDMODE=0")); + waitResponse(); + + // Configure socket parameters + // AT+CIPCCFG= , , , , , + // , + // NmRetry = number of retransmission to be made for an IP packet + // = 10 (default) + // DelayTm = number of milliseconds to delay before outputting received data + // = 0 (default) + // Ack = sets whether reporting a string "Send ok" = 0 (don't report) + // errMode = mode of reporting error result code = 0 (numberic values) + // HeaderType = which data header of receiving data in multi-client mode + // = 1 (+RECEIVE,,) + // AsyncMode = sets mode of executing commands + // = 0 (synchronous command executing) + // TimeoutVal = minimum retransmission timeout in milliseconds = 75000 + sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000")); + if (waitResponse() != 1) { return false; } + + // Configure timeouts for opening and closing sockets + // AT+CIPTIMEOUT= , + sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000); + waitResponse(); + + // Start the socket service + + // This activates and attaches to the external PDP context that is tied + // to the embedded context for TCP/IP (ie AT+CGACT=1,1 and AT+CGATT=1) + // Response may be an immediate "OK" followed later by "+NETOPEN: 0". + // We to ignore any immediate response and wait for the + // URC to show it's really connected. + sendAT(GF("+NETOPEN")); + if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + // Close all sockets and stop the socket service + // Note: On the LTE models, this single command closes all sockets and the + // service + sendAT(GF("+NETCLOSE")); + if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; } + + return true; + } + + bool isGprsConnectedImpl() { + sendAT(GF("+NETOPEN?")); + // May return +NETOPEN: 1, 0. We just confirm that the first number is 1 + if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; } + waitResponse(); + + sendAT(GF("+IPADDR")); // Inquire Socket PDP address + // sendAT(GF("+CGPADDR=1")); // Show PDP address + if (waitResponse() != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Gets the CCID of a sim card via AT+CCID + String getSimCCIDImpl() { + sendAT(GF("+CICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + bool callHangupImpl() { + sendAT(GF("+CHUP")); + return waitResponse() == 1; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ + protected: + // Can return a GSM-based location from CLBS as per the template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl() { + sendAT(GF("+CGPS=1")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool disableGPSImpl() { + sendAT(GF("+CGPS=0")); + if (waitResponse() != 1) { return false; } + return true; + } + + // get the RAW GPS output + String getGPSrawImpl() { + sendAT(GF("+CGNSSINFO")); + if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + sendAT(GF("+CGNSSINFO")); + if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return false; } + + uint8_t fixMode = streamGetIntBefore(','); // mode 2=2D Fix or 3=3DFix + // TODO(?) Can 1 be returned + if (fixMode == 1 || fixMode == 2 || fixMode == 3) { + // init variables + float ilat = 0; + char north; + float ilon = 0; + char east; + float ispeed = 0; + float ialt = 0; + int ivsat = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + + streamSkipUntil(','); // GPS satellite valid numbers + streamSkipUntil(','); // GLONASS satellite valid numbers + streamSkipUntil(','); // BEIDOU satellite valid numbers + ilat = streamGetFloatBefore(','); // Latitude in ddmm.mmmmmm + north = stream.read(); // N/S Indicator, N=north or S=south + streamSkipUntil(','); + ilon = streamGetFloatBefore(','); // Longitude in ddmm.mmmmmm + east = stream.read(); // E/W Indicator, E=east or W=west + streamSkipUntil(','); + + // Date. Output format is ddmmyy + iday = streamGetIntLength(2); // Two digit day + imonth = streamGetIntLength(2); // Two digit month + iyear = streamGetIntBefore(','); // Two digit year + + // UTC Time. Output format is hhmmss.s + ihour = streamGetIntLength(2); // Two digit hour + imin = streamGetIntLength(2); // Two digit minute + secondWithSS = + streamGetFloatBefore(','); // 4 digit second with subseconds + + ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters + ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots. + streamSkipUntil(','); // Course Over Ground. Degrees. + streamSkipUntil(','); // After set, will report GPS every x seconds + iaccuracy = streamGetFloatBefore(','); // Position Dilution Of Precision + streamSkipUntil(','); // Horizontal Dilution Of Precision + streamSkipUntil(','); // Vertical Dilution Of Precision + streamSkipUntil('\n'); // TODO(?) is one more field reported?? + + // Set pointers + if (lat != NULL) + *lat = (floor(ilat / 100) + fmod(ilat, 100.) / 60) * + (north == 'N' ? 1 : -1); + if (lon != NULL) + *lon = (floor(ilon / 100) + fmod(ilon, 100.) / 60) * + (east == 'E' ? 1 : -1); + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = ivsat; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + waitResponse(); + return true; + } + + waitResponse(); + return false; + } + + + /** + * CGNSSMODE: , + * This command is used to configure GPS, GLONASS, BEIDOU and QZSS support + * mode. 0 : GLONASS 1 : BEIDOU 2 : GALILEO 3 : QZSS dpo_mode: 1 enable , 0 + * disable + */ + String setGNSSModeImpl(uint8_t mode, bool dpo) { + String res; + sendAT(GF("+CGNSSMODE="), mode, ",", dpo); + if (waitResponse(10000L, res) != 1) { return ""; } + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + uint8_t getGNSSModeImpl() { + sendAT(GF("+CGNSSMODE?")); + if (waitResponse(GF(GSM_NL "+CGNSSMODE:")) != 1) { return 0; } + return stream.readStringUntil(',').toInt(); + } + + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // returns volts, multiply by 1000 to get mV + uint16_t getBattVoltageImpl() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; } + + // get voltage in VOLTS + float voltage = streamGetFloatBefore('\n'); + // Wait for final OK + waitResponse(); + // Return millivolts + uint16_t res = voltage * 1000; + return res; + } + + int8_t getBattPercentImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + uint8_t getBattChargeStateImpl() TINY_GSM_ATTR_NOT_AVAILABLE; + + bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent, + uint16_t& milliVolts) { + chargeState = 0; + percent = 0; + milliVolts = getBattVoltage(); + return true; + } + + /* + * Temperature functions + */ + protected: + // get temperature in degree celsius + uint16_t getTemperatureImpl() { + sendAT(GF("+CPMUTEMP")); + if (waitResponse(GF(GSM_NL "+CPMUTEMP:")) != 1) { return 0; } + // return temperature in C + uint16_t res = streamGetIntBefore('\n'); + // Wait for final OK + waitResponse(); + return res; + } + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 15) { + if (ssl) { DBG("SSL not yet supported on this module!"); } + // Make sure we'll be getting data manually on this connection + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Establish a connection in multi-socket mode + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), + port); + // The reply is OK followed by +CIPOPEN: , where + // is the mux number and should be 0 if there's no error + if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; } + uint8_t opened_mux = streamGetIntBefore(','); + uint8_t opened_result = streamGetIntBefore('\n'); + if (opened_mux != mux || opened_result != 0) return false; + return true; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip requested bytes to send + // TODO(?): make sure requested and confirmed bytes match + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux/cid (connecion id) + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ The data length which not read in the buffer + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + // Read the status of all sockets at once + sendAT(GF("+CIPCLOSE?")); + if (waitResponse(GF("+CIPCLOSE:")) != 1) { + // return false; // TODO: Why does this not read correctly? + } + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + // +CIPCLOSE:,,..., + bool muxState = stream.parseInt(); + if (sockets[muxNo]) { sockets[muxNo]->sock_connected = muxState; } + } + waitResponse(); // Should be an OK at the end + if (!sockets[mux]) return false; + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("+IPCLOSE:"))) { + int8_t mux = streamGetIntBefore(','); + streamSkipUntil('\n'); // Skip the reason code + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("+CIPEVENT:"))) { + // Need to close all open sockets and release the network library. + // User will then need to reconnect. + DBG("### Network error!"); + if (!isGprsConnected()) { gprsDisconnect(); } + data = ""; + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientSim7600* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTSIM7600_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM800.h b/lib/TinyGSM/src/TinyGsmClientSIM800.h new file mode 100644 index 0000000..ebefdb9 --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM800.h @@ -0,0 +1,771 @@ +/** + * @file TinyGsmClientSIM800.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM800_H_ +#define SRC_TINYGSMCLIENTSIM800_H_ +// #pragma message("TinyGSM: TinyGsmClientSIM800") + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 5 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmSSL.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; +class TinyGsmSim800 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSSL, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmGSMLocation, + public TinyGsmTime, + public TinyGsmNTP, + public TinyGsmBattery { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmGSMLocation; + friend class TinyGsmTime; + friend class TinyGsmNTP; + friend class TinyGsmBattery; + + /* + * Inner Client + */ + public: + class GsmClientSim800 : public GsmClient { + friend class TinyGsmSim800; + + public: + GsmClientSim800() {} + + explicit GsmClientSim800(TinyGsmSim800& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim800* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CIPCLOSE="), mux, GF(",1")); // Quick close + sock_connected = false; + at->waitResponse(); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + public: + class GsmClientSecureSim800 : public GsmClientSim800 { + public: + GsmClientSecureSim800() {} + + explicit GsmClientSecureSim800(TinyGsmSim800& modem, uint8_t mux = 0) + : GsmClientSim800(modem, mux) {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSim800(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM800")); + + if (!testAT()) { return false; } + + // sendAT(GF("&FZ")); // Factory + Reset + // waitResponse(); + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + waitResponse(); + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemNameImpl() { + String name = ""; +#if defined(TINY_GSM_MODEM_SIM800) + name = "SIMCom SIM800"; +#elif defined(TINY_GSM_MODEM_SIM808) + name = "SIMCom SIM808"; +#elif defined(TINY_GSM_MODEM_SIM868) + name = "SIMCom SIM868"; +#elif defined(TINY_GSM_MODEM_SIM900) + name = "SIMCom SIM900"; +#endif + + sendAT(GF("+GMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { return name; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + DBG("### Modem:", name); + return name; + } + + bool factoryDefaultImpl() { + sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + waitResponse(); + sendAT(GF("+IPR=0")); // Auto-baud + waitResponse(); + sendAT(GF("+IFC=0,0")); // No Flow Control + waitResponse(); + sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop + waitResponse(); + sendAT(GF("+CSCLK=0")); // Disable Slow Clock + waitResponse(); + sendAT(GF("&W")); // Write configuration + return waitResponse() == 1; + } + + /* + bool thisHasSSL() { + #if defined(TINY_GSM_MODEM_SIM900) + return false; + #else + sendAT(GF("+CIPSSL=?")); + if (waitResponse(GF(GSM_NL "+CIPSSL:")) != 1) { return false; } + return waitResponse() == 1; + #endif + } + */ + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + sendAT(GF("&W")); + waitResponse(); + if (!setPhoneFunctionality(0)) { return false; } + if (!setPhoneFunctionality(1, true)) { return false; } + delay(3000); + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPOWD=1")); + return waitResponse(10000L, GF("NORMAL POWER DOWN")) == 1; + } + + // During sleep, the SIM800 module has its serial communication disabled. In + // order to reestablish communication pull the DRT-pin of the SIM800 module + // LOW for at least 50ms. Then use this function to disable sleep mode. The + // DTR-pin can then be released again. + bool sleepEnableImpl(bool enable = true) { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + // 0 Minimum functionality + // 1 Full functionality (Default) + // 4 Disable phone both transmit and receive RF circuits. + // Reset the MT before setting it to power level. + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + String getLocalIPImpl() { + sendAT(GF("+CIFSR;E0")); + String res; + if (waitResponse(10000L, res) != 1) { return ""; } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); + waitResponse(); + + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); + waitResponse(); + + // Set the user name + if (user && strlen(user) > 0) { + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); + waitResponse(); + } + // Set the password + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); + waitResponse(); + } + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Activate the PDP context + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + // Open the definied GPRS bearer context + sendAT(GF("+SAPBR=1,1")); + waitResponse(85000L); + // Query the GPRS bearer context status + sendAT(GF("+SAPBR=2,1")); + if (waitResponse(30000L) != 1) { return false; } + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // Set to multi-IP + sendAT(GF("+CIPMUX=1")); + if (waitResponse() != 1) { return false; } + + // Put in "quick send" mode (thus no extra "Send OK") + sendAT(GF("+CIPQSEND=1")); + if (waitResponse() != 1) { return false; } + + // Set to get data manually + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Start Task and Set APN, USER NAME, PASSWORD + sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse(60000L) != 1) { return false; } + + // Bring Up Wireless Connection with GPRS or CSD + sendAT(GF("+CIICR")); + if (waitResponse(60000L) != 1) { return false; } + + // Get Local IP Address, only assigned after connection + sendAT(GF("+CIFSR;E0")); + if (waitResponse(10000L) != 1) { return false; } + + // Configure Domain Name Server (DNS) + sendAT(GF("+CDNSCFG=\"8.8.8.8\",\"8.8.4.4\"")); + if (waitResponse() != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + // Shut the TCP/IP connection + // CIPSHUT will close *all* open connections + sendAT(GF("+CIPSHUT")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // Detach from GPRS + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // May not return the "+CCID" before the number + String getSimCCIDImpl() { + sendAT(GF("+CCID")); + if (waitResponse(GF(GSM_NL)) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + // Trim out the CCID header in case it is there + res.replace("CCID:", ""); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + public: + bool setGsmBusy(bool busy = true) { + sendAT(GF("+GSMBUSY="), busy ? 1 : 0); + return waitResponse() == 1; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GSM Location functions + */ + protected: + // Depending on the exacty model and firmware revision, should return a + // GSM-based location from CLBS as per the template + // TODO(?): Check number of digits in year (2 or 4) + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // No functions of this type supported + + /* + * Audio functions + */ + public: + bool setVolume(uint8_t volume = 50) { + // Set speaker volume + sendAT(GF("+CLVL="), volume); + return waitResponse() == 1; + } + + uint8_t getVolume() { + // Get speaker volume + sendAT(GF("+CLVL?")); + if (waitResponse(GF(GSM_NL)) != 1) { return 0; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.replace("+CLVL:", ""); + res.trim(); + return res.toInt(); + } + + bool setMicVolume(uint8_t channel, uint8_t level) { + if (channel > 4) { return 0; } + sendAT(GF("+CMIC="), level); + return waitResponse() == 1; + } + + bool setAudioChannel(uint8_t channel) { + sendAT(GF("+CHFA="), channel); + return waitResponse() == 1; + } + + bool playToolkitTone(uint8_t tone, uint32_t duration) { + sendAT(GF("STTONE="), 1, tone); + delay(duration); + sendAT(GF("STTONE="), 0); + return waitResponse(); + } + + /* + * Time functions + */ + protected: + // Can follow the standard CCLK function in the template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + int8_t rsp; + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; +#if !defined(TINY_GSM_MODEM_SIM900) + sendAT(GF("+CIPSSL="), ssl); + rsp = waitResponse(); + if (ssl && rsp != 1) { return false; } +#ifdef TINY_GSM_SSL_CLIENT_AUTHENTICATION + // set SSL options + // +SSLOPT=, + // + // 0 (default) ignore invalid certificate + // 1 client authentication + // + // 0 (default) close + // 1 open + sendAT(GF("+CIPSSL=1,1")); + if (waitResponse() != 1) return false; +#endif +#endif + sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, + GF("\","), port); + rsp = waitResponse( + timeout_ms, GF("CONNECT OK" GSM_NL), GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL), + GF("CLOSE OK" GSM_NL)); // Happens when HTTPS handshake fails + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + stream.write(reinterpret_cast(buff), len); + stream.flush(); + if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ Confirmed number of data bytes to be read, which may be less than + // requested. 0 indicates that no data can be read. + // SRGD NOTE: Contrary to above (which is copied from AT command manual) + // this is actually be the number of bytes that will be remaining in the + // buffer after the read. + for (int i = 0; i < len_requested; i++) { + uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); +#endif + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + sockets[mux]->sock_available = len_confirmed; + waitResponse(); + return len_requested; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); + } + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+CIPSTATUS="), mux); + waitResponse(GF("+CIPSTATUS")); + int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), + GF(",\"CLOSING\""), GF(",\"REMOTE CLOSING\""), + GF(",\"INITIAL\"")); + waitResponse(); + return 1 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(a); + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + int8_t mode = streamGetIntBefore(','); + if (mode == 1) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + // DBG("### Got Data:", mux); + } else { + data += mode; + } + } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { + int8_t mux = streamGetIntBefore(','); + int16_t len = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; } + } + data = ""; + // DBG("### Got Data:", len, "on", mux); + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t mux = data.substring(nl + 2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST:"))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + GsmClientSim800* sockets[TINY_GSM_MUX_COUNT]; + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTSIM800_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSIM808.h b/lib/TinyGSM/src/TinyGsmClientSIM808.h new file mode 100644 index 0000000..7398bfa --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSIM808.h @@ -0,0 +1,168 @@ +/** + * @file TinyGsmClientSIM808.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM808_H_ +#define SRC_TINYGSMCLIENTSIM808_H_ +// #pragma message("TinyGSM: TinyGsmClientSIM808") + +#include "TinyGsmClientSIM800.h" +#include "TinyGsmGPS.tpp" +#include "TinyGsmBluetooth.tpp" + +class TinyGsmSim808 : public TinyGsmSim800, public TinyGsmGPS, public TinyGsmBluetooth { + friend class TinyGsmGPS; + friend class TinyGsmBluetooth; + + public: + explicit TinyGsmSim808(Stream& stream) : TinyGsmSim800(stream) {} + + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl() { + sendAT(GF("+CGNSPWR=1")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool disableGPSImpl() { + sendAT(GF("+CGNSPWR=0")); + if (waitResponse() != 1) { return false; } + return true; + } + + // get the RAW GPS output + // works only with ans SIM808 V2 + String getGPSrawImpl() { + sendAT(GF("+CGNSINF")); + if (waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + // works only with ans SIM808 V2 + bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + sendAT(GF("+CGNSINF")); + if (waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { return false; } + + streamSkipUntil(','); // GNSS run status + if (streamGetIntBefore(',') == 1) { // fix status + // init variables + float ilat = 0; + float ilon = 0; + float ispeed = 0; + float ialt = 0; + int ivsat = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + + // UTC date & Time + iyear = streamGetIntLength(4); // Four digit year + imonth = streamGetIntLength(2); // Two digit month + iday = streamGetIntLength(2); // Two digit day + ihour = streamGetIntLength(2); // Two digit hour + imin = streamGetIntLength(2); // Two digit minute + secondWithSS = + streamGetFloatBefore(','); // 6 digit second with subseconds + + ilat = streamGetFloatBefore(','); // Latitude + ilon = streamGetFloatBefore(','); // Longitude + ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters + ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots. + streamSkipUntil(','); // Course Over Ground. Degrees. + streamSkipUntil(','); // Fix Mode + streamSkipUntil(','); // Reserved1 + iaccuracy = + streamGetFloatBefore(','); // Horizontal Dilution Of Precision + streamSkipUntil(','); // Position Dilution Of Precision + streamSkipUntil(','); // Vertical Dilution Of Precision + streamSkipUntil(','); // Reserved2 + ivsat = streamGetIntBefore(','); // GNSS Satellites in View + iusat = streamGetIntBefore(','); // GNSS Satellites Used + streamSkipUntil(','); // GLONASS Satellites Used + streamSkipUntil(','); // Reserved3 + streamSkipUntil(','); // C/N0 max + streamSkipUntil(','); // HPA + streamSkipUntil('\n'); // VPA + + // Set pointers + if (lat != NULL) *lat = ilat; + if (lon != NULL) *lon = ilon; + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = ivsat; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + waitResponse(); + return true; + } + + streamSkipUntil('\n'); // toss the row of commas + waitResponse(); + return false; + } + + /* + * Bluetooth functions + */ + + bool enableBluetoothImpl() { + sendAT(GF("+BTPOWER=1")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool disableBluetoothImpl() { + sendAT(GF("+BTPOWER=0")); + if (waitResponse() != 1) { return false; } + return true; + } + + bool setBluetoothVisibilityImpl(bool visible) { + sendAT(GF("+BTVIS="), visible); + if (waitResponse() != 1) { + return false; + } + + return true; + } + + bool setBluetoothHostNameImpl(const char* name) { + sendAT(GF("+BTHOST="), name); + if (waitResponse() != 1) { + return false; + } + + return true; + } +}; + +#endif // SRC_TINYGSMCLIENTSIM808_H_ diff --git a/lib/TinyGSM/src/TinyGsmClientSaraR4.h b/lib/TinyGSM/src/TinyGsmClientSaraR4.h new file mode 100644 index 0000000..8cc0b4d --- /dev/null +++ b/lib/TinyGSM/src/TinyGsmClientSaraR4.h @@ -0,0 +1,900 @@ +/** + * @file TinyGsmClientSaraR4.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSARAR4_H_ +#define SRC_TINYGSMCLIENTSARAR4_H_ +// #pragma message("TinyGSM: TinyGsmClientSaraR4") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 7 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmSSL.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTemperature.tpp" +#include "TinyGsmTime.tpp" + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +class TinyGsmSaraR4 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSSL, + public TinyGsmBattery, + public TinyGsmGSMLocation, + public TinyGsmGPS, + public TinyGsmSMS, + public TinyGsmTemperature, + public TinyGsmTime { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + friend class TinyGsmBattery; + friend class TinyGsmGSMLocation; + friend class TinyGsmGPS; + friend class TinyGsmSMS; + friend class TinyGsmTemperature; + friend class TinyGsmTime; + + /* + * Inner Client + */ + public: + class GsmClientSaraR4 : public GsmClient { + friend class TinyGsmSaraR4; + + public: + GsmClientSaraR4() {} + + explicit GsmClientSaraR4(TinyGsmSaraR4& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSaraR4* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + // stop(); // DON'T stop! + TINY_GSM_YIELD(); + rx.clear(); + + uint8_t oldMux = mux; + sock_connected = at->modemConnect(host, port, &mux, false, timeout_s); + if (mux != oldMux) { + DBG("WARNING: Mux number changed from", oldMux, "to", mux); + at->sockets[oldMux] = NULL; + } + at->sockets[mux] = this; + at->maintain(); + + return sock_connected; + } + virtual int connect(IPAddress ip, uint16_t port, int timeout_s) { + return connect(TinyGsmStringFromIp(ip).c_str(), port, timeout_s); + } + int connect(const char* host, uint16_t port) override { + return connect(host, port, 120); + } + int connect(IPAddress ip, uint16_t port) override { + return connect(ip, port, 120); + } + + void stop(uint32_t maxWaitMs) { + uint32_t startMillis = millis(); + dumpModemBuffer(maxWaitMs); + // We want to use an async socket close because the syncrhonous close of + // an open socket is INCREDIBLY SLOW and the modem can freeze up. But we + // only attempt the async close if we already KNOW the socket is open + // because calling the async close on a closed socket and then attempting + // opening a new socket causes the board to lock up for 2-3 minutes and + // then finally return with a "new" socket that is immediately closed. + // Attempting to close a socket that is already closed with a synchronous + // close quickly returns an error. + if (at->supportsAsyncSockets && sock_connected) { + DBG("### Closing socket asynchronously! Socket might remain open " + "until arrival of +UUSOCL:", + mux); + // faster asynchronous close + // NOT supported on SARA-R404M / SARA-R410M-01B + at->sendAT(GF("+USOCL="), mux, GF(",1")); + // NOTE: can take up to 120s to get a response + at->waitResponse((maxWaitMs - (millis() - startMillis))); + // We set the sock as disconnected right away because it can no longer + // be used + sock_connected = false; + } else { + // synchronous close + at->sendAT(GF("+USOCL="), mux); + // NOTE: can take up to 120s to get a response + at->waitResponse((maxWaitMs - (millis() - startMillis))); + sock_connected = false; + } + } + void stop() override { + stop(135000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + public: + class GsmClientSecureR4 : public GsmClientSaraR4 { + public: + GsmClientSecureR4() {} + + explicit GsmClientSecureR4(TinyGsmSaraR4& modem, uint8_t mux = 0) + : GsmClientSaraR4(modem, mux) {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + // stop(); // DON'T stop! + TINY_GSM_YIELD(); + rx.clear(); + uint8_t oldMux = mux; + sock_connected = at->modemConnect(host, port, &mux, true, timeout_s); + if (mux != oldMux) { + DBG("WARNING: Mux number changed from", oldMux, "to", mux); + at->sockets[oldMux] = NULL; + } + at->sockets[mux] = this; + at->maintain(); + return sock_connected; + } + int connect(IPAddress ip, uint16_t port, int timeout_s) override { + return connect(TinyGsmStringFromIp(ip).c_str(), port, timeout_s); + } + int connect(const char* host, uint16_t port) override { + return connect(host, port, 120); + } + int connect(IPAddress ip, uint16_t port) override { + return connect(ip, port, 120); + } + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSaraR4(Stream& stream) + : stream(stream), + has2GFallback(false), + supportsAsyncSockets(false) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSaraR4")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + String modemName = getModemName(); + DBG(GF("### Modem:"), modemName); + if (modemName.startsWith("u-blox SARA-R412")) { + has2GFallback = true; + } else { + has2GFallback = false; + } + if (modemName.startsWith("u-blox SARA-R404M") || + modemName.startsWith("u-blox SARA-R410M-01B")) { + supportsAsyncSockets = false; + } else { + supportsAsyncSockets = true; + } + + // Enable automatic time zome update + sendAT(GF("+CTZU=1")); + if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + // only difference in implementation is the warning on the wrong type + String getModemNameImpl() { + sendAT(GF("+CGMI")); + String res1; + if (waitResponse(1000L, res1) != 1) { return "u-blox Cellular Modem"; } + res1.replace(GSM_NL "OK" GSM_NL, ""); + res1.trim(); + + sendAT(GF("+GMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { return "u-blox Cellular Modem"; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.trim(); + + String name = res1 + String(' ') + res2; + DBG("### Modem:", name); + if (!name.startsWith("u-blox SARA-R4") && + !name.startsWith("u-blox SARA-N4")) { + DBG("### WARNING: You are using the wrong TinyGSM modem!"); + } + + return name; + } + + bool factoryDefaultImpl() { + sendAT(GF("&F")); // Resets the current profile, other NVM not affected + return waitResponse() == 1; + } + + /* + * Power functions + */ + protected: + // using +CFUN=15 instead of the more common CFUN=1,1 + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + if (!setPhoneFunctionality(15)) { return false; } + delay(3000); // TODO(?): Verify delay timing here + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPWROFF")); + return waitResponse(40000L) == 1; + } + + bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE; + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + // Check first for EPS registration + RegStatus epsStatus = (RegStatus)getRegistrationStatusXREG("CEREG"); + + // If we're connected on EPS, great! + if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) { + return epsStatus; + } else { + // Otherwise, check generic network status + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + bool setURAT(uint8_t urat) { + // AT+URAT=[,[,<2ndPreferredAct>]] + + sendAT(GF("+COPS=2")); // Deregister from network + if (waitResponse() != 1) { return false; } + sendAT(GF("+URAT="), urat); // Radio Access Technology (RAT) selection + if (waitResponse() != 1) { return false; } + sendAT(GF("+COPS=0")); // Auto-register to the network + if (waitResponse() != 1) { return false; } + return restart(); + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + // gprsDisconnect(); + + sendAT(GF("+CGATT=1")); // attach to GPRS + if (waitResponse(360000L) != 1) { return false; } + + // Using CGDCONT sets up an "external" PCP context, i.e. a data connection + // using the external IP stack (e.g. Windows dial up) and PPP link over the + // serial interface. This is the only command set supported by the LTE-M + // and LTE NB-IoT modules (SARA-R4xx, SARA-N4xx) + + // Set the authentication + if (user && strlen(user) > 0) { + sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); + waitResponse(); + } + + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); // Define PDP context 1 + waitResponse(); + + sendAT(GF("+CGACT=1,1")); // activate PDP profile/context 1 + if (waitResponse(150000L) != 1) { return false; } + + return true; + } + + bool gprsDisconnectImpl() { + // Mark all the sockets as closed + // This ensures that asynchronously closed sockets are marked closed + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClientSaraR4* sock = sockets[mux]; + if (sock && sock->sock_connected) { sock->sock_connected = false; } + } + + // sendAT(GF("+CGACT=0,1")); // Deactivate PDP context 1 + sendAT(GF("+CGACT=0")); // Deactivate all contexts + if (waitResponse(40000L) != 1) { + // return false; + } + + sendAT(GF("+CGATT=0")); // detach from GPRS + if (waitResponse(360000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // This uses "CGSN" instead of "GSN" + String getIMEIImpl() { + sendAT(GF("+CGSN")); + if (waitResponse(GF(GSM_NL)) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Messaging functions + */ + protected: + String sendUSSDImpl(const String& code) TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool sendSMS_UTF16Impl(const String& number, const void* text, + size_t len) TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * GSM/GPS/GNSS/GLONASS Location functions + * NOTE: u-blox modules use the same function to get location data from both + * GSM tower triangulation and from dedicated GPS/GNSS/GLONASS receivers. The + * only difference in which sensor the data is requested from. If a GNSS + * location is requested from a modem without a GNSS receiver installed on the + * I2C port, the GSM-based "Cell Locate" location will be returned instead. + */ + protected: + bool enableGPSImpl() { + // AT+UGPS=[,[,]] + // - 0: GNSS receiver powered off, 1: on + // - 0: no aiding (default) + // - 3: GPS + SBAS (default) + sendAT(GF("+UGPS=1,0,3")); + if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; } + return waitResponse(10000L) == 1; + } + bool disableGPSImpl() { + sendAT(GF("+UGPS=0")); + if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; } + return waitResponse(10000L) == 1; + } + String inline getUbloxLocationRaw(int8_t sensor) { + // AT+ULOC=,,,, + // - 2: single shot position + // - 0: use the last fix in the internal database and stop the GNSS + // receiver + // - 1: use the GNSS receiver for localization + // - 2: use cellular CellLocate location information + // - 3: ?? use the combined GNSS receiver and CellLocate service + // information ?? - Docs show using sensor 3 and it's + // documented for the +UTIME command but not for +ULOC + // - 0: standard (single-hypothesis) response + // - Timeout period in seconds + // - Target accuracy in meters (1 - 999999) + sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1")); + // wait for first "OK" + if (waitResponse(10000L) != 1) { return ""; } + // wait for the final result - wait full timeout time + if (waitResponse(120000L, GF(GSM_NL "+UULOC:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + String getGsmLocationRawImpl() { + return getUbloxLocationRaw(2); + } + String getGPSrawImpl() { + return getUbloxLocationRaw(1); + } + + inline bool getUbloxLocation(int8_t sensor, float* lat, float* lon, + float* speed = 0, float* alt = 0, int* vsat = 0, + int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, + int* hour = 0, int* minute = 0, + int* second = 0) { + // AT+ULOC=,,,, + // - 2: single shot position + // - 2: use cellular CellLocate location information + // - 0: use the last fix in the internal database and stop the GNSS + // receiver + // - 1: use the GNSS receiver for localization + // - 3: ?? use the combined GNSS receiver and CellLocate service + // information ?? - Docs show using sensor 3 and it's documented + // for the +UTIME command but not for +ULOC + // - 0: standard (single-hypothesis) response + // - Timeout period in seconds + // - Target accuracy in meters (1 - 999999) + sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1")); + // wait for first "OK" + if (waitResponse(10000L) != 1) { return false; } + // wait for the final result - wait full timeout time + if (waitResponse(120000L, GF(GSM_NL "+UULOC: ")) != 1) { return false; } + + // +UULOC: ,